Files
nomina_ventas/app/Services/TelegramBotService.php

288 lines
12 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Services;
use App\Models\TelegramAccount;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class TelegramBotService
{
private ?string $botToken;
private ?string $webhookUrl;
public function __construct()
{
$this->botToken = config('services.telegram.bot_token');
$this->webhookUrl = rtrim(config('app.url'), '/') . '/telegram/webhook';
}
public function handleUpdate(array $update): array
{
if (!isset($update['message'])) {
return ['ok' => false, 'error' => 'No message found'];
}
$message = $update['message'];
$chatId = $message['chat']['id'];
$text = $message['text'] ?? '';
Log::info('Telegram update received', ['chat_id' => $chatId, 'text' => $text]);
$telegramAccount = TelegramAccount::where('chat_id', $chatId)->first();
if (!$telegramAccount || !$telegramAccount->is_verified) {
return $this->handleUnverifiedUser($chatId, $text);
}
if ($telegramAccount->bot_state) {
return $this->handleBotState($telegramAccount, $text, $chatId);
}
return $this->handleCommand($telegramAccount, $text, $chatId);
}
private function handleUnverifiedUser(string $chatId, string $text): array
{
if (strlen($text) === 6 && is_numeric($text)) {
$telegramAccount = TelegramAccount::where('verification_code', $text)
->where('is_verified', false)
->first();
if ($telegramAccount) {
$telegramAccount->update(['chat_id' => $chatId, 'is_verified' => true, 'verification_code' => null]);
$this->sendMenu($chatId, "¡Hola {$telegramAccount->user->name}! 👋\n\nVerificación exitosa. He activado tu menú de control:");
return ['ok' => true, 'verified' => true];
}
}
$this->sendMessage($chatId, "👋 ¡Hola! Soy el bot de Nómina Pegaso.\n\nPara empezar, envía el código de 6 dígitos de tu panel web.");
return ['ok' => true, 'verified' => false];
}
private function handleBotState(TelegramAccount $account, string $text, string $chatId): array
{
try {
$state = $account->bot_state;
$data = $account->bot_data ?? [];
// Permitir cancelar
if (strtolower(trim($text)) === '/cancelar' || $text === '❌ Cancelar') {
$account->update(['bot_state' => null, 'bot_data' => null]);
$this->sendMenu($chatId, "❌ Operación cancelada.");
return ['ok' => true];
}
switch ($state) {
// --- FLUJO VENTA ---
case 'AWAITING_SALE_DATE':
$data['date'] = $this->parseDate($text);
$account->update(['bot_state' => 'AWAITING_SALE_USER_AMOUNT', 'bot_data' => $data]);
$this->sendMessage($chatId, "💰 Ingresa el monto vendido por el *usuario*:", $this->cancelKeyboard());
break;
case 'AWAITING_SALE_USER_AMOUNT':
if (!is_numeric($text)) return $this->sendInvalidNumeric($chatId);
$data['user_sales'] = (float) $text;
$account->update(['bot_state' => 'AWAITING_SALE_SYSTEM_AMOUNT', 'bot_data' => $data]);
$this->sendMessage($chatId, "🖥️ Ingresa el monto vendido según el *sistema*:", $this->cancelKeyboard());
break;
case 'AWAITING_SALE_SYSTEM_AMOUNT':
if (!is_numeric($text)) return $this->sendInvalidNumeric($chatId);
$data['system_sales'] = (float) $text;
$user = $account->user;
$month = $user->getCurrentMonth();
if (!$month) return $this->sendNoOpenMonth($chatId, $account);
$month->dailySales()->create([
'user_id' => $user->id,
'date' => $data['date'],
'user_sales' => $data['user_sales'],
'system_sales' => $data['system_sales'],
]);
$account->update(['bot_state' => null, 'bot_data' => null]);
$this->sendMenu($chatId, "✅ *Venta registrada!*\n\n📅 {$data['date']}\n👤 U: $" . number_format($data['user_sales'], 2) . "\n🖥️ S: $" . number_format($data['system_sales'], 2));
break;
// --- FLUJO GASTO ---
case 'AWAITING_EXPENSE_DATE':
$data['date'] = $this->parseDate($text);
$account->update(['bot_state' => 'AWAITING_EXPENSE_AMOUNT', 'bot_data' => $data]);
$this->sendMessage($chatId, "💸 Ingresa el monto del gasto:", $this->cancelKeyboard());
break;
case 'AWAITING_EXPENSE_AMOUNT':
if (!is_numeric($text)) return $this->sendInvalidNumeric($chatId);
$data['amount'] = (float) $text;
$account->update(['bot_state' => 'AWAITING_EXPENSE_DESC', 'bot_data' => $data]);
$this->sendMessage($chatId, "📝 Describe el gasto:", $this->cancelKeyboard());
break;
case 'AWAITING_EXPENSE_DESC':
$data['description'] = $text;
$user = $account->user;
$month = $user->getCurrentMonth();
if (!$month) return $this->sendNoOpenMonth($chatId, $account);
$month->expenses()->create([
'user_id' => $user->id,
'date' => $data['date'],
'amount' => $data['amount'],
'description' => $data['description'],
'type' => 'other',
]);
$account->update(['bot_state' => null, 'bot_data' => null]);
$this->sendMenu($chatId, "✅ *Gasto registrado!*\n\n📅 {$data['date']}\n💰 $" . number_format($data['amount'], 2) . "\n📝 {$data['description']}");
break;
}
} catch (\Exception $e) {
Log::error('Bot interaction error: ' . $e->getMessage());
$account->update(['bot_state' => null, 'bot_data' => null]);
$this->sendMenu($chatId, "❌ Ocurrió un error al guardar los datos. Por favor intenta de nuevo.");
}
return ['ok' => true];
}
private function handleCommand(TelegramAccount $account, string $text, string $chatId): array
{
$cmd = strtolower(trim($text));
$user = $account->user;
switch ($cmd) {
case '/start':
case '/menu':
case '🏠 menú':
$this->sendMenu($chatId, "Control de Nómina - {$user->name}");
break;
case '/resumen':
case '💵 resumen':
$this->showSummary($user, $chatId);
break;
case '/agregar_venta':
case ' agregar venta':
$account->update(['bot_state' => 'AWAITING_SALE_DATE']);
$this->sendMessage($chatId, "🗓️ ¿Fecha de la venta?", $this->dateKeyboard());
break;
case '/agregar_gasto':
case ' agregar gasto':
$account->update(['bot_state' => 'AWAITING_EXPENSE_DATE']);
$this->sendMessage($chatId, "🗓️ ¿Fecha del gasto?", $this->dateKeyboard());
break;
case '/ventas':
case '💰 mis ventas':
$this->showSales($user, $chatId);
break;
case '/gastos':
case '📝 mis gastos':
$this->showExpenses($user, $chatId);
break;
case '/help':
$this->sendMenu($chatId, "Usa los botones del teclado para navegar o enviar comandos.");
break;
default:
$this->sendMenu($chatId, "❓ No entiendo ese comando.");
}
return ['ok' => true];
}
private function sendMenu(string $chatId, string $text)
{
$keyboard = [
'keyboard' => [
[['text' => '💵 Resumen'], ['text' => '🏠 Menú']],
[['text' => ' Agregar Venta'], ['text' => ' Agregar Gasto']],
[['text' => '💰 Mis Ventas'], ['text' => '📝 Mis Gastos']]
],
'resize_keyboard' => true,
'one_time_keyboard' => false
];
return $this->sendMessage($chatId, $text, $keyboard);
}
private function dateKeyboard() {
return [
'keyboard' => [[['text' => 'Hoy'], ['text' => 'Ayer']], [['text' => '❌ Cancelar']]],
'resize_keyboard' => true,
'one_time_keyboard' => true
];
}
private function cancelKeyboard() {
return [
'keyboard' => [[['text' => '❌ Cancelar']]],
'resize_keyboard' => true,
'one_time_keyboard' => true
];
}
public function sendMessage(string $chatId, string $text, $replyMarkup = null): array
{
if (!$this->botToken) return ['ok' => false];
$params = ['chat_id' => $chatId, 'text' => $text, 'parse_mode' => 'Markdown'];
if ($replyMarkup) $params['reply_markup'] = json_encode($replyMarkup);
try {
return Http::post("https://api.telegram.org/bot{$this->botToken}/sendMessage", $params)->json();
} catch (\Exception $e) { return ['ok' => false]; }
}
private function parseDate(string $text): string
{
$text = strtolower(trim($text));
if ($text === 'hoy') return date('Y-m-d');
if ($text === 'ayer') return date('Y-m-d', strtotime('-1 day'));
$ts = strtotime($text);
return $ts ? date('Y-m-d', $ts) : date('Y-m-d');
}
private function sendInvalidNumeric(string $chatId): array
{
$this->sendMessage($chatId, "⚠️ Ingresa solo números.", $this->cancelKeyboard());
return ['ok' => true];
}
private function sendNoOpenMonth(string $chatId, $account): array
{
$account->update(['bot_state' => null, 'bot_data' => null]);
$this->sendMenu($chatId, "❌ No hay mes abierto.");
return ['ok' => true];
}
private function showSummary(User $user, string $chatId) {
$month = $user->getCurrentMonth();
if (!$month) return $this->sendMessage($chatId, "No hay mes abierto.");
$data = \App\Services\CommissionCalculator::calculateForMonth($user, $month);
$text = "💵 *Resumen Financiero*\n\n" .
"• Comisiones: \$" . number_format($data['commission_amount'], 2) . "\n" .
"• Salario: \$" . number_format($data['monthly_salary'], 2) . "\n" .
"• Gastos: \$" . number_format($data['total_expenses'], 2) . "\n" .
"⭐ *Neto: \$" . number_format($data['total_earning'], 2) . "*";
$this->sendMenu($chatId, $text);
}
private function showSales(User $user, string $chatId) {
$month = $user->getCurrentMonth();
if (!$month) return;
$u = $month->dailySales()->sum('user_sales');
$s = $month->dailySales()->sum('system_sales');
$this->sendMenu($chatId, "💰 *Ventas*\n👤 U: \$" . number_format($u, 2) . "\n🖥️ S: \$" . number_format($s, 2));
}
private function showExpenses(User $user, string $chatId) {
$month = $user->getCurrentMonth();
if (!$month) return;
$t = $month->expenses()->sum('amount');
$this->sendMenu($chatId, "📝 *Total Gastos:* \$" . number_format($t, 2));
}
public static function generateVerificationCode(): string { return str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT); }
public function setWebhook(): bool { try { return Http::post("https://api.telegram.org/bot{$this->botToken}/setWebhook", ['url' => $this->webhookUrl])->json('ok', false); } catch (\Exception $e) { return false; } }
public function getWebhookInfo(): array { try { return Http::get("https://api.telegram.org/bot{$this->botToken}/getWebhookInfo")->json(); } catch (\Exception $e) { return ['ok' => false]; } }
public function deleteWebhook(): bool { try { return Http::post("https://api.telegram.org/bot{$this->botToken}/deleteWebhook")->json('ok', false); } catch (\Exception $e) { return false; } }
}