This commit is contained in:
lizandrogd
2026-01-21 15:19:58 -05:00
parent ddc9a68a40
commit b709d65190
6 changed files with 171 additions and 7 deletions
+18
View File
@@ -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';
+9
View File
@@ -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
+42
View File
@@ -0,0 +1,42 @@
<?php
require_once __DIR__ . '/../api/webhook.php';
require_once __DIR__ . '/../config/config.php';
$w = new WhatsAppWebhook();
// Build a fake 'value' structure with interactive list_reply
$value = [
'messages' => [
[
'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";
}
+27
View File
@@ -0,0 +1,27 @@
<?php
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../services/BotService.php';
$db = Database::getInstance();
$phone = '573010000001';
// ensure user
$user = $db->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);
+33
View File
@@ -0,0 +1,33 @@
<?php
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../services/BotService.php';
$db = Database::getInstance();
$phone = '573010000003';
// ensure user
$user = $db->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);
+42 -7
View File
@@ -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,