Files
nomina_ventas/app/Services/TelegramBotService.php

259 lines
11 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
{
$state = $account->bot_state;
$data = $account->bot_data ?? [];
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) {
case 'AWAITING_SALE_DATE':
case 'AWAITING_EXPENSE_DATE':
$data['date'] = $this->parseDate($text);
$nextState = ($state === 'AWAITING_SALE_DATE') ? 'AWAITING_SALE_USER_AMOUNT' : 'AWAITING_EXPENSE_AMOUNT';
$nextMsg = ($state === 'AWAITING_SALE_DATE') ? "💰 Monto vendido por *usuario*:" : "💸 Monto del gasto:";
$account->update(['bot_state' => $nextState, 'bot_data' => $data]);
$this->sendMessage($chatId, $nextMsg, $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, "🖥️ Monto vendido según *sistema*:", $this->cancelKeyboard());
break;
case 'AWAITING_SALE_SYSTEM_AMOUNT':
if (!is_numeric($text)) return $this->sendInvalidNumeric($chatId);
$data['system_sales'] = (float) $text;
$month = $account->user->getCurrentMonth();
if (!$month) return $this->sendNoOpenMonth($chatId, $account);
$month->dailySales()->create(['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;
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;
$month = $account->user->getCurrentMonth();
if (!$month) return $this->sendNoOpenMonth($chatId, $account);
$month->expenses()->create(['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;
}
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; } }
}