Debug: Añadido log al webhook y corregida excepcion CSRF

This commit is contained in:
2026-04-19 20:05:41 -06:00
parent d05324a28a
commit b0fa3f4fde
5 changed files with 213 additions and 257 deletions

View File

@@ -95,6 +95,7 @@ class TelegramController extends Controller
*/
public function webhook(Request $request)
{
Log::debug('>>> Webhook hit detected <<<');
try {
$update = $request->all();

View File

@@ -24,6 +24,8 @@ class TelegramAccount extends Model
'chat_id',
'verification_code',
'is_verified',
'bot_state',
'bot_data',
];
/**
@@ -35,6 +37,7 @@ class TelegramAccount extends Model
{
return [
'is_verified' => 'boolean',
'bot_data' => 'array',
];
}

View File

@@ -18,9 +18,6 @@ class TelegramBotService
$this->webhookUrl = rtrim(config('app.url'), '/') . '/telegram/webhook';
}
/**
* Procesar actualización recibida del webhook
*/
public function handleUpdate(array $update): array
{
if (!isset($update['message'])) {
@@ -30,308 +27,233 @@ class TelegramBotService
$message = $update['message'];
$chatId = $message['chat']['id'];
$text = $message['text'] ?? '';
$from = $message['from'] ?? [];
Log::info('Telegram update received', [
'chat_id' => $chatId,
'text' => $text,
'from' => $from
]);
Log::info('Telegram update received', ['chat_id' => $chatId, 'text' => $text]);
// Verificar si el usuario está verificado
$telegramAccount = TelegramAccount::where('chat_id', $chatId)->first();
if (!$telegramAccount || !$telegramAccount->is_verified) {
return $this->handleUnverifiedUser($chatId, $text);
}
// Procesar comandos del usuario verificado
return $this->handleCommand($telegramAccount->user, $text, $chatId);
if ($telegramAccount->bot_state) {
return $this->handleBotState($telegramAccount, $text, $chatId);
}
return $this->handleCommand($telegramAccount, $text, $chatId);
}
/**
* Manejar usuario no verificado
*/
private function handleUnverifiedUser(string $chatId, string $text): array
{
// Si es un código de verificación (6 dígitos numéricos)
if (strlen($text) === 6 && is_numeric($text)) {
// Buscamos la cuenta que tiene este código de verificación y no está verificada
$telegramAccount = TelegramAccount::where('verification_code', $text)
->where('is_verified', false)
->first();
if ($telegramAccount) {
// Actualizar la cuenta con el chat_id del usuario que mandó el código
$telegramAccount->update([
'chat_id' => $chatId,
'is_verified' => true,
'verification_code' => null
]);
$user = $telegramAccount->user;
$this->sendMessage($chatId, "¡Hola {$user->name}! 👋\n\nVerificación exitosa. Tu cuenta de Telegram ha sido vinculada correctamente. Ahora puedes usar comandos como /resumen para ver tu estado.");
$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];
} else {
$this->sendMessage($chatId, "❌ El código $text es inválido o ya ha sido usado. Por favor, genera un código nuevo en tu panel de usuario.");
return ['ok' => true, 'verified' => false];
}
}
// Mensaje de bienvenida para usuarios no verificados
$this->sendMessage($chatId, "👋 ¡Hola! Soy el bot de Nómina Pegaso.\n\nPara usar este bot, primero debes vincular tu cuenta:\n\n1⃣ Ve a tu panel web\n2⃣ Sección Telegram -> Vincular\n3⃣ Envía aquí el código de 6 dígitos que veas allá.");
$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];
}
/**
* Manejar comandos de usuario verificado
*/
private function handleCommand(User $user, string $text, string $chatId): array
private function handleBotState(TelegramAccount $account, string $text, string $chatId): array
{
$command = strtolower(trim($text));
$commandParts = explode(' ', $command);
$mainCommand = $commandParts[0] ?? '';
$state = $account->bot_state;
$data = $account->bot_data ?? [];
switch ($mainCommand) {
case '/start':
$this->sendMessage($chatId, "¡Hola {$user->name}! Usa /help para ver los comandos disponibles.");
break;
case '/help':
$this->sendHelp($chatId);
break;
case '/mes':
$this->showCurrentMonth($user, $chatId);
break;
case '/ventas':
$this->showSales($user, $chatId);
break;
case '/gastos':
$this->showExpenses($user, $chatId);
break;
case '/resumen':
$this->showSummary($user, $chatId);
break;
default:
$this->sendMessage($chatId, "Comando no reconocido. Usa /help para ver los comandos disponibles.");
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];
}
/**
* Enviar mensaje
*/
public function sendMessage(string $chatId, string $text): array
private function handleCommand(TelegramAccount $account, string $text, string $chatId): array
{
if (!$this->botToken) {
Log::warning('Telegram bot token not configured');
return ['ok' => false, 'error' => 'Bot token not configured'];
$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 {
$response = Http::post("https://api.telegram.org/bot{$this->botToken}/sendMessage", [
'chat_id' => $chatId,
'text' => $text,
'parse_mode' => 'Markdown'
]);
return $response->json();
} catch (\Exception $e) {
Log::error('Telegram send message error', ['error' => $e->getMessage()]);
return ['ok' => false, 'error' => $e->getMessage()];
}
return Http::post("https://api.telegram.org/bot{$this->botToken}/sendMessage", $params)->json();
} catch (\Exception $e) { return ['ok' => false]; }
}
/**
* Enviar mensaje de ayuda
*/
private function sendHelp(string $chatId): void
private function parseDate(string $text): string
{
$text = "📋 *Comandos disponibles:*\n\n" .
"• /start - Iniciar bot\n" .
"• /help - Mostrar ayuda\n" .
"• /mes - Ver mes actual\n" .
"• /ventas - Ver ventas del mes\n" .
"• /gastos - Ver gastos del mes\n" .
"• /resumen - Resumen de comisiones\n";
$this->sendMessage($chatId, $text);
$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');
}
/**
* Mostrar mes actual
*/
private function showCurrentMonth(User $user, string $chatId): void
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) {
$this->sendMessage($chatId, "No tienes ningún mes abierto actualmente.");
return;
}
$statusText = match($month->status) {
'open' => '🟢 Abierto',
'closed' => '🟡 Cerrado',
'paid' => '✅ Pagado',
default => 'Desconocido'
};
$text = "📅 *Mes Actual*\n\n" .
"• *Nombre:* {$month->name} {$month->year}\n" .
"• *Estado:* {$statusText}";
$this->sendMessage($chatId, $text);
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);
}
/**
* Mostrar ventas del mes
*/
private function showSales(User $user, string $chatId): void
{
private function showSales(User $user, string $chatId) {
$month = $user->getCurrentMonth();
if (!$month) {
$this->sendMessage($chatId, "No tienes ningún mes abierto actualmente.");
return;
}
$totalUserSales = $month->dailySales()->sum('user_sales');
$totalSystemSales = $month->dailySales()->sum('system_sales');
$diff = $totalUserSales - $totalSystemSales;
$text = "💰 *Ventas del Mes*\n\n" .
"• *Usuario:* $" . number_format($totalUserSales, 2) . "\n" .
"• *Sistema:* $" . number_format($totalSystemSales, 2) . "\n" .
"• *Diferencia:* $" . number_format($diff, 2);
$this->sendMessage($chatId, $text);
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));
}
/**
* Mostrar gastos del mes
*/
private function showExpenses(User $user, string $chatId): void
{
private function showExpenses(User $user, string $chatId) {
$month = $user->getCurrentMonth();
if (!$month) {
$this->sendMessage($chatId, "No tienes ningún mes abierto actualmente.");
return;
}
$totalExpenses = $month->expenses()->sum('amount');
$expensesList = $month->expenses()->latest()->limit(5)->get();
$text = "📝 *Gastos del Mes*\n\n" .
"• *Total:* $" . number_format($totalExpenses, 2) . "\n\n";
if ($expensesList->count() > 0) {
$text .= "Últimos gastos:\n";
foreach ($expensesList as $expense) {
$text .= "{$expense->description}: \$" . number_format($expense->amount, 2) . "\n";
}
}
$this->sendMessage($chatId, $text);
if (!$month) return;
$t = $month->expenses()->sum('amount');
$this->sendMenu($chatId, "📝 *Total Gastos:* \$" . number_format($t, 2));
}
/**
* Mostrar resumen de comisiones
*/
private function showSummary(User $user, string $chatId): void
{
$month = $user->getCurrentMonth();
if (!$month) {
$this->sendMessage($chatId, "No tienes ningún mes abierto actualmente.");
return;
}
$data = CommissionCalculator::calculateForMonth($user, $month);
$text = "💵 *Resumen de Comisiones*\n\n" .
"• *Mes:* {$data['month_name']}\n" .
"• *Ventas Sistema:* \$" . number_format($data['total_system_sales'], 2) . "\n" .
"• *Comisión ({$data['commission_percentage']}%):* \$" . number_format($data['commission_amount'], 2) . "\n" .
"• *Salario:* \$" . number_format($data['monthly_salary'], 2) . "\n" .
"• *Gastos:* \$" . number_format($data['total_expenses'], 2) . "\n" .
"• *Total a Recibir:* \$" . number_format($data['total_earning'], 2);
$this->sendMessage($chatId, $text);
}
/**
* Generar código de verificación
*/
public static function generateVerificationCode(): string
{
return str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
}
/**
* Configurar webhook
*/
public function setWebhook(): bool
{
if (!$this->botToken || !$this->webhookUrl) {
Log::warning('Cannot set webhook: missing configuration');
return false;
}
try {
$response = Http::post("https://api.telegram.org/bot{$this->botToken}/setWebhook", [
'url' => $this->webhookUrl
]);
return $response->json('ok', false);
} catch (\Exception $e) {
Log::error('Telegram set webhook error', ['error' => $e->getMessage()]);
return false;
}
}
/**
* Obtener información del webhook
*/
public function getWebhookInfo(): array
{
if (!$this->botToken) {
return ['ok' => false, 'error' => 'Bot token not configured'];
}
try {
$response = Http::get("https://api.telegram.org/bot{$this->botToken}/getWebhookInfo");
return $response->json();
} catch (\Exception $e) {
Log::error('Telegram get webhook info error', ['error' => $e->getMessage()]);
return ['ok' => false, 'error' => $e->getMessage()];
}
}
/**
* Borrar webhook
*/
public function deleteWebhook(): bool
{
if (!$this->botToken) {
return false;
}
try {
$response = Http::post("https://api.telegram.org/bot{$this->botToken}/deleteWebhook");
return $response->json('ok', false);
} catch (\Exception $e) {
Log::error('Telegram delete webhook error', ['error' => $e->getMessage()]);
return false;
}
}
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; } }
}