diff --git a/api/webhook.php b/api/webhook.php index c6bc5eb..e2d2ae8 100644 --- a/api/webhook.php +++ b/api/webhook.php @@ -154,6 +154,24 @@ class WhatsAppWebhook { 'reaction_emoji' => $emoji ]; + } elseif (isset($message['interactive'])) { + // Interactive replies (list or button) - normalize to text so bot can process + $messageType = 'text'; + if (isset($message['interactive']['list_reply']['title'])) { + $messageText = $message['interactive']['list_reply']['title']; + // If title starts with a number ("1. Option"), convert to the number for menu selection + if (preg_match('/^\s*(\d+)\b/', $messageText, $m)) { + $messageText = $m[1]; + } + } elseif (isset($message['interactive']['button_reply']['title'])) { + $messageText = $message['interactive']['button_reply']['title']; + if (preg_match('/^\s*(\d+)\b/', $messageText, $m)) { + $messageText = $m[1]; + } + } else { + $messageText = json_encode($message['interactive']); + } + error_log(sprintf('[webhook] interactive received title=%s mapped=%s', substr($message['interactive']['list_reply']['title'] ?? ($message['interactive']['button_reply']['title'] ?? json_encode($message['interactive'])),0,200), substr($messageText,0,200))); } elseif (isset($message['text']) || ($message['type'] ?? '') === 'text') { $messageText = isset($message['text']['body']) ? $message['text']['body'] : ($message['body'] ?? ''); $messageType = 'text'; diff --git a/logs/system.log b/logs/system.log index 1e635b0..30b6d48 100644 --- a/logs/system.log +++ b/logs/system.log @@ -23,3 +23,12 @@ Stack trace: [2026-01-21 02:31:42] [INFO] Advisor requested for 573010000123 [2026-01-21 02:31:44] [INFO] Advisor released hold for 573010000123 [2026-01-21 02:31:49] [INFO] Bot paused for 4 hours for 573010000123 +[2026-01-21 15:12:23] [INFO] Cleared advisor_requested for user 140 after outgoing message +[2026-01-21 15:12:27] [INFO] Cleared advisor_requested for user 140 after outgoing message +[2026-01-21 15:13:50] [INFO] Cleared advisor_requested for user 140 after outgoing message +[2026-01-21 15:13:53] [INFO] Cleared advisor_requested for user 140 after outgoing message +[2026-01-21 15:13:54] [INFO] Bot paused for 5 minutes for 573010000001 +[2026-01-21 15:15:06] [INFO] Cleared advisor_requested for user 141 after outgoing message +[2026-01-21 15:16:53] [INFO] Cleared advisor_requested for user 142 after outgoing message +[2026-01-21 15:16:56] [INFO] Cleared advisor_requested for user 142 after outgoing message +[2026-01-21 15:16:57] [INFO] Bot paused for 5 minutes for 573010000003 diff --git a/scripts/test_interactive_flow.php b/scripts/test_interactive_flow.php new file mode 100644 index 0000000..8f2c007 --- /dev/null +++ b/scripts/test_interactive_flow.php @@ -0,0 +1,42 @@ + [ + [ + 'from' => '573010000002', + 'id' => 'TEST_INT_1', + 'type' => 'interactive', + 'interactive' => [ + 'type' => 'list_reply', + 'list_reply' => ['id' => 'option_2', 'title' => 'Consultar resultados'] + ], + 'timestamp' => time() + ] + ] +]; + +// Set current menu for user (simulate that menu was shown previously) +$db = Database::getInstance(); +$db->update('users', ['current_menu_id' => 1, 'current_step' => 1], 'phone_number = :phone', ['phone' => '573010000002']); + +// Use reflection to call private method processconversations +$ref = new ReflectionClass($w); +$m = $ref->getMethod('processconversations'); +$m->setAccessible(true); + +$m->invoke($w, $value); + +// Check DB for last conversations for that phone +$db = Database::getInstance(); +$user = $db->fetch('SELECT * FROM users WHERE phone_number = ?', ['573010000002']); +if ($user) { + $rows = $db->fetchAll('SELECT * FROM conversations WHERE user_id = ? ORDER BY created_at DESC LIMIT 5', [$user['id']]); + print_r($user); + print_r($rows); +} else { + echo "User not found\n"; +} diff --git a/scripts/test_menu_flow.php b/scripts/test_menu_flow.php new file mode 100644 index 0000000..117edfe --- /dev/null +++ b/scripts/test_menu_flow.php @@ -0,0 +1,27 @@ +fetch('SELECT * FROM users WHERE phone_number = ?', [$phone]); +if (!$user) { + $id = $db->insert('users', ['phone_number' => $phone, 'status' => 'active', 'created_at' => date('Y-m-d H:i:s')]); + $user = $db->fetch('SELECT * FROM users WHERE id = ?', [$id]); +} +$bot = new BotService(); + +echo "User id: {$user['id']} phone: {$phone}\n"; + +// Simulate sending 'menu' +echo "--- Sending 'menu' ---\n"; +$bot->processMessage($user, 'menu', 'text'); +$user = $db->fetch('SELECT * FROM users WHERE id = ?', [$user['id']]); +print_r(['after menu user' => $user]); + +// Simulate user selecting option 2 +echo "--- Sending '2' ---\n"; +$bot->processMessage($user, '2', 'text'); +// show last conversation entries +$rows = $db->fetchAll('SELECT * FROM conversations WHERE user_id = ? ORDER BY created_at DESC LIMIT 5', [$user['id']]); +print_r($rows); diff --git a/scripts/test_menu_with_advisor_reply.php b/scripts/test_menu_with_advisor_reply.php new file mode 100644 index 0000000..083805a --- /dev/null +++ b/scripts/test_menu_with_advisor_reply.php @@ -0,0 +1,33 @@ +fetch('SELECT * FROM users WHERE phone_number = ?', [$phone]); +if (!$user) { + $id = $db->insert('users', ['phone_number' => $phone, 'status' => 'active', 'created_at' => date('Y-m-d H:i:s')]); + $user = $db->fetch('SELECT * FROM users WHERE id = ?', [$id]); +} +$bot = new BotService(); + +echo "User id: {$user['id']} phone: {$phone}\n"; + +// Simulate incoming message 'Hola' +$db->insert('conversations', ['user_id' => $user['id'], 'message_id' => 'INC1', 'direction' => 'incoming', 'message_type' => 'text', 'content' => 'Hola', 'status' => 'received', 'created_at' => date('Y-m-d H:i:s')]); + +// Simulate advisor outgoing reply AFTER the incoming message +$db->insert('conversations', ['user_id' => $user['id'], 'message_id' => 'OUT1', 'direction' => 'outgoing', 'message_type' => 'text', 'content' => 'Hola, soy el asesor', 'status' => 'sent', 'created_at' => date('Y-m-d H:i:s')]); + +// Now user sends 'menu' +echo "--- Sending 'menu' ---\n"; +$user = $db->fetch('SELECT * FROM users WHERE id = ?', [$user['id']]); +$bot->processMessage($user, 'menu', 'text'); +$user = $db->fetch('SELECT * FROM users WHERE id = ?', [$user['id']]); +print_r(['after menu user' => $user]); + +// Simulate user selecting option 2 +echo "--- Sending '2' ---\n"; +$bot->processMessage($user, '2', 'text'); +$rows = $db->fetchAll('SELECT * FROM conversations WHERE user_id = ? ORDER BY created_at DESC LIMIT 5', [$user['id']]); +print_r($rows); diff --git a/services/BotService.php b/services/BotService.php index abebd50..6845cd2 100644 --- a/services/BotService.php +++ b/services/BotService.php @@ -33,9 +33,16 @@ class BotService { // ignore logging errors } - // Verificar si es un nuevo usuario (enviar mensaje de bienvenida) + // Verificar si es un nuevo usuario (iniciar con menú principal) if ($this->isNewUser($user['id'])) { - $this->sendWelcomeMessage($phoneNumber); + // En lugar de welcome textual, mostramos el menú principal y marcamos welcome_sent + try { + $this->showMainMenu($phoneNumber); + $this->db->update('users', ['welcome_sent_at' => date('Y-m-d H:i:s')], 'phone_number = :phone', ['phone' => $phoneNumber]); + error_log("[BotService] processMessage - initial menu sent to new user {$user['id']}"); + } catch (Exception $e) { + error_log('[BotService] Failed to send initial menu: ' . $e->getMessage()); + } return; } @@ -98,12 +105,16 @@ class BotService { return; } - // Si el asesor ya respondió después del último mensaje del usuario, frenar al bot + // Verificar si el asesor ya respondió después del último mensaje del usuario. + // En ese caso NO enviaremos respuestas automáticas, pero sí permitiremos + // que el usuario interactúe con menús manualmente. $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']]); + $advisorReplied = false; if ($lastOutgoing && $lastIncoming && strtotime($lastOutgoing['created_at']) >= strtotime($lastIncoming['created_at'])) { - // El asesor ya respondió, no enviar respuestas automáticas - return; + // El asesor ya respondió, marcar flag pero no retornar (permitir menús y comandos explícitos) + $advisorReplied = true; + error_log("[BotService] processMessage - advisorReplied=true for user {$user['id']}"); } // Verificar si el usuario está en un menú if ($user['current_menu_id']) { @@ -111,7 +122,17 @@ class BotService { } else { // No usar las "respuestas rápidas" (autoresponses) automáticamente desde el bot. // Estas respuestas solo se mostrarán en la UI del chat como "respuestas rápidas". - $this->sendDefaultNoMatch($phoneNumber); + // Si un asesor ya respondió recientemente, no enviar respuestas automáticas. + if (empty($advisorReplied)) { + $this->sendDefaultNoMatch($phoneNumber); + } else { + // Opcional: informar que el asesor ya respondió y no podemos enviar automáticas + try { + $this->whatsappService->sendTextMessage($phoneNumber, "🔕 Un asesor ya ha intervenido en la conversación, por favor espera su respuesta o utiliza *menu* para ver opciones."); + } catch (Exception $e) { + // ignore + } + } } } @@ -269,12 +290,26 @@ class BotService { $optionNumber = (int)$messageText; - // Buscar la opción seleccionada + // Buscar la opción seleccionada por número $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] ); + // Si no encontramos por número, intentar buscar por texto (caso de replies interactivos que envían título) + if (!$option) { + $txt = strtolower(trim($messageText)); + if (!empty($txt)) { + $option = $this->db->fetch( + "SELECT * FROM menu_options WHERE menu_id = :menu_id AND is_active = 1 AND (LOWER(text) = :txt OR LOWER(text) LIKE :like) LIMIT 1", + ['menu_id' => $currentMenuId, 'txt' => $txt, 'like' => "%{$txt}%"] + ); + if ($option) { + error_log("[BotService] processMenuSelection - matched option by text for user {$user['id']} menu={$currentMenuId} text='{$txt}' => option_number={$option['option_number']}"); + } + } + } + if (!$option) { $this->whatsappService->sendTextMessage( $phoneNumber,