@@ -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 \n Verificació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 \n Verificació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 \n Para usar este bot, primero debes vincular tu cuenta: \n \n 1️ ⃣ Ve a tu panel web \n 2️ ⃣ Sección Telegram -> Vincular \n 3️ ⃣ 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 \n Para 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_for mat( $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 ; } }
}