259 lines
11 KiB
PHP
Executable File
259 lines
11 KiB
PHP
Executable File
<?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; } }
|
||
} |