db = Database::getInstance(); $this->whatsappService = new WhatsAppService(); } /** * Procesar mensaje entrante */ public function processMessage($user, $messageText, $messageType = 'text') { // Solo procesar mensajes de texto if ($messageType !== 'text') { return; } $messageText = trim($messageText); $phoneNumber = $user['phone_number']; // Verificar si es un nuevo usuario (enviar mensaje de bienvenida) if ($this->isNewUser($user['id'])) { $this->sendWelcomeMessage($phoneNumber); return; } // Procesar comandos especiales if ($this->processSpecialCommands($phoneNumber, $messageText)) { return; } // Si el usuario está en espera por un asesor, no procesar if (!empty($user['on_hold']) && $user['on_hold']) { try { $this->whatsappService->sendTextMessage($phoneNumber, "⚠️ Has sido puesto en espera por un asesor. En breve te contactarán."); } catch (Exception $e) { // ignore } return; } // Si el bot está pausado por bot_paused_until, no responder automáticamente if (!empty($user['bot_paused_until']) && strtotime($user['bot_paused_until']) > time()) { return; } // Si el asesor ya respondió después del último mensaje del usuario, frenar al bot $lastIncoming = $this->db->fetch("SELECT created_at FROM conversations WHERE user_id = :uid AND direction = 'incoming' ORDER BY created_at DESC LIMIT 1", ['uid' => $user['id']]); $lastOutgoing = $this->db->fetch("SELECT created_at FROM conversations WHERE user_id = :uid AND direction = 'outgoing' ORDER BY created_at DESC LIMIT 1", ['uid' => $user['id']]); if ($lastOutgoing && $lastIncoming && strtotime($lastOutgoing['created_at']) >= strtotime($lastIncoming['created_at'])) { // El asesor ya respondió, no enviar respuestas automáticas return; } // Verificar si el usuario está en un menú if ($user['current_menu_id']) { $this->processMenuSelection($user, $messageText); } else { // Procesar respuestas automáticas o comando menu $this->processAutoResponses($phoneNumber, $messageText); } } /** * Verificar si es un nuevo usuario */ private function isNewUser($userId) { $messageCount = $this->db->fetch( "SELECT COUNT(*) as count FROM conversations WHERE user_id = :user_id", ['user_id' => $userId] ); // Evitar reenviar welcome si ya se envió $user = $this->db->fetch("SELECT welcome_sent_at FROM users WHERE id = :id", ['id' => $userId]); $welcomeSent = !empty($user['welcome_sent_at']); return ($messageCount['count'] <= 1) && !$welcomeSent; // Solo el mensaje actual y si no se ha enviado welcome } /** * Enviar mensaje de bienvenida */ public function sendWelcomeMessage($phoneNumber) { $enabled = getConfigFromDB('welcome_enabled', '1'); $welcomeMessage = getConfigFromDB('welcome_message', '¡Hola! 👋 Bienvenido a nuestro servicio automatizado. Escribe *menu* para ver las opciones disponibles.'); if (!$enabled || !$welcomeMessage) return; $this->whatsappService->sendTextMessage($phoneNumber, $welcomeMessage); // Registrar que se envió el welcome try { $this->db->update('users', ['welcome_sent_at' => date('Y-m-d H:i:s')], 'phone_number = :phone', ['phone' => $phoneNumber]); } catch (Exception $e) { error_log('Failed to set welcome_sent_at for ' . $phoneNumber . ': ' . $e->getMessage()); } } /** * Procesar comandos especiales */ private function processSpecialCommands($phoneNumber, $messageText) { $command = strtolower($messageText); switch ($command) { case 'menu': case 'menú': case 'inicio': $this->showMainMenu($phoneNumber); return true; case 'salir': case 'exit': case 'cancelar': $this->exitMenu($phoneNumber); return true; case 'asesor': case 'ayuda': // Poner la conversación en espera y notificar al usuario $this->putOnHold($phoneNumber); return true; } return false; } /** * Mostrar menú principal */ private function showMainMenu($phoneNumber) { $mainMenu = $this->db->fetch( "SELECT * FROM menus WHERE is_root = 1 AND is_active = 1 ORDER BY order_position LIMIT 1" ); if ($mainMenu) { $this->showMenu($phoneNumber, $mainMenu['id']); } else { $this->whatsappService->sendTextMessage( $phoneNumber, "❌ No hay menús configurados. Contacta con soporte." ); } } /** * Mostrar menú específico */ private function showMenu($phoneNumber, $menuId) { // Obtener información del menú $menu = $this->db->fetch( "SELECT * FROM menus WHERE id = :id AND is_active = 1", ['id' => $menuId] ); if (!$menu) { $this->whatsappService->sendTextMessage( $phoneNumber, "❌ Menú no encontrado." ); return; } // Obtener opciones del menú $options = $this->db->fetchAll( "SELECT * FROM menu_options WHERE menu_id = :menu_id AND is_active = 1 ORDER BY option_number", ['menu_id' => $menuId] ); if (empty($options)) { $this->whatsappService->sendTextMessage( $phoneNumber, "❌ Este menú no tiene opciones configuradas." ); return; } // Construir mensaje del menú $menuText = "📋 *" . $menu['title'] . "*\n\n"; if ($menu['description']) { $menuText .= $menu['description'] . "\n\n"; } foreach ($options as $option) { $menuText .= $option['option_number'] . ". " . $option['text'] . "\n"; } $menuText .= "\n💬 *Responde con el número de la opción que deseas*"; // Actualizar estado del usuario $this->updateUserMenuState($phoneNumber, $menuId); // Enviar mensaje $this->whatsappService->sendTextMessage($phoneNumber, $menuText); } /** * Procesar selección de menú */ private function processMenuSelection($user, $messageText) { $phoneNumber = $user['phone_number']; $currentMenuId = $user['current_menu_id']; // Verificar si es un número if (!is_numeric($messageText)) { $this->whatsappService->sendTextMessage( $phoneNumber, "❌ Por favor, responde solo con el número de la opción deseada." ); return; } $optionNumber = (int)$messageText; // Buscar la opción seleccionada $option = $this->db->fetch( "SELECT * FROM menu_options WHERE menu_id = :menu_id AND option_number = :option_number AND is_active = 1", ['menu_id' => $currentMenuId, 'option_number' => $optionNumber] ); if (!$option) { $this->whatsappService->sendTextMessage( $phoneNumber, "❌ Opción inválida. Por favor, selecciona una opción válida del menú." ); return; } // Procesar acción de la opción $this->processMenuAction($phoneNumber, $option); } /** * Procesar acción del menú */ private function processMenuAction($phoneNumber, $option) { switch ($option['action_type']) { case 'menu': // Navegar a otro menú $targetMenu = $this->db->fetch( "SELECT * FROM menus WHERE name = :name AND is_active = 1", ['name' => $option['action_value']] ); if ($targetMenu) { $this->showMenu($phoneNumber, $targetMenu['id']); } else { $this->whatsappService->sendTextMessage( $phoneNumber, "❌ Menú no encontrado: " . $option['action_value'] ); } break; case 'message': // Enviar mensaje de respuesta $this->whatsappService->sendTextMessage($phoneNumber, $option['action_value']); $this->exitMenu($phoneNumber); break; case 'api_call': // Llamar API externa (implementar según necesidad) $this->processApiCall($phoneNumber, $option['action_value']); break; case 'end': // Finalizar conversación $message = $option['action_value'] ?: "Gracias por usar nuestro servicio. ¡Hasta pronto!"; $this->whatsappService->sendTextMessage($phoneNumber, $message); $this->exitMenu($phoneNumber); // Pausar el bot para esta conversación por 4 horas $this->pauseConversationForHours($phoneNumber, 4); break; } } /** * Procesar llamada API externa */ private function processApiCall($phoneNumber, $apiEndpoint) { // Aquí puedes implementar llamadas a APIs externas // Por ejemplo, consultar saldos, procesar pagos, etc. $this->whatsappService->sendTextMessage( $phoneNumber, "🔄 Procesando tu solicitud... Un momento por favor." ); // Simular procesamiento sleep(2); $this->whatsappService->sendTextMessage( $phoneNumber, "✅ Tu solicitud ha sido procesada exitosamente." ); $this->exitMenu($phoneNumber); } /** * Procesar respuestas automáticas */ private function processAutoResponses($phoneNumber, $messageText) { $keyword = strtolower($messageText); // Buscar respuesta automática por keyword $autoResponse = $this->db->fetch( "SELECT * FROM auto_responses WHERE trigger_type = 'keyword' AND LOWER(trigger_value) = :keyword AND is_active = 1", ['keyword' => $keyword] ); if ($autoResponse) { $this->whatsappService->sendTextMessage($phoneNumber, $autoResponse['response_text']); // Si la keyword es 'menu', mostrar el menú if ($keyword === 'menu' || $keyword === 'menú') { $this->showMainMenu($phoneNumber); } } else { // Respuesta por defecto para mensajes no reconocidos $defaultMessage = "🤖 No entendí tu mensaje. Escribe *menu* para ver las opciones disponibles o *ayuda* para obtener ayuda."; $this->whatsappService->sendTextMessage($phoneNumber, $defaultMessage); } } /** * Salir del menú actual */ private function exitMenu($phoneNumber) { $this->db->update( 'users', ['current_menu_id' => null, 'current_step' => 0, 'session_data' => null], 'phone_number = :phone', ['phone' => $phoneNumber] ); } /** * Poner la conversación en pausa por 4 horas (llamada desde opciones 'end') */ private function pauseConversationForHours($phoneNumber, $hours = 4) { try { $pausedUntil = date('Y-m-d H:i:s', strtotime("+{$hours} hours")); $this->db->update('users', ['bot_paused_until' => $pausedUntil], 'phone_number = :phone', ['phone' => $phoneNumber]); if (function_exists('writeLog')) writeLog('INFO', "Bot paused for {$hours} hours for {$phoneNumber}"); } catch (Exception $e) { error_log('pauseConversationForHours failed: ' . $e->getMessage()); } } /** * Poner conversación en espera (asesor solicitado) */ public function putOnHold($phoneNumber) { try { $this->db->update('users', ['on_hold' => 1], 'phone_number = :phone', ['phone' => $phoneNumber]); $this->whatsappService->sendTextMessage($phoneNumber, "🔔 Se ha solicitado un asesor. Te hemos puesto en espera. Te contactaremos en breve."); if (function_exists('writeLog')) writeLog('INFO', "Advisor requested for $phoneNumber"); } catch (Exception $e) { error_log('putOnHold failed: ' . $e->getMessage()); } } public function releaseHold($phoneNumber) { try { $this->db->update('users', ['on_hold' => 0], 'phone_number = :phone', ['phone' => $phoneNumber]); $this->whatsappService->sendTextMessage($phoneNumber, "🔔 Tu conversación ha sido retomada por el equipo."); if (function_exists('writeLog')) writeLog('INFO', "Advisor released hold for $phoneNumber"); } catch (Exception $e) { error_log('releaseHold failed: ' . $e->getMessage()); } } /** * Actualizar estado del menú del usuario */ private function updateUserMenuState($phoneNumber, $menuId) { $this->db->update( 'users', ['current_menu_id' => $menuId, 'current_step' => 1], 'phone_number = :phone', ['phone' => $phoneNumber] ); } /** * Mostrar ayuda */ private function showHelp($phoneNumber) { $helpText = "🆘 *Ayuda del Sistema*\n\n"; $helpText .= "📋 *menu* - Mostrar menú principal\n"; $helpText .= "❌ *salir* - Salir del menú actual\n"; $helpText .= "🆘 *ayuda* - Mostrar esta ayuda\n\n"; $helpText .= "💡 *Consejos:*\n"; $helpText .= "• Responde solo con números en los menús\n"; $helpText .= "• Escribe palabras clave para obtener respuestas rápidas\n"; $helpText .= "• Si tienes problemas, escribe *soporte*"; $this->whatsappService->sendTextMessage($phoneNumber, $helpText); } /** * Procesar mensaje de soporte */ public function processSupportMessage($phoneNumber, $message) { // Notificar a administradores sobre consulta de soporte $this->whatsappService->sendTextMessage( $phoneNumber, "🆘 Tu mensaje ha sido enviado a nuestro equipo de soporte. Te contactaremos pronto.\n\n" . "📞 También puedes llamarnos al: +57 1 234 5678\n" . "📧 O escribirnos a: soporte@miempresa.com" ); // Aquí podrías implementar notificaciones a administradores // Por ejemplo, enviar email o notificación a Slack } /** * Obtener estadísticas del bot */ public function getBotStats() { $stats = []; // Total de usuarios $stats['total_users'] = $this->db->fetch( "SELECT COUNT(*) as count FROM users" )['count']; // Usuarios activos (último mes) $stats['active_users'] = $this->db->fetch( "SELECT COUNT(DISTINCT user_id) as count FROM conversations WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)" )['count']; // Total de mensajes $stats['total_messages'] = $this->db->fetch( "SELECT COUNT(*) as count FROM conversations" )['count']; // Mensajes hoy $stats['messages_today'] = $this->db->fetch( "SELECT COUNT(*) as count FROM conversations WHERE DATE(created_at) = CURDATE()" )['count']; return $stats; } } ?>