u
This commit is contained in:
@@ -154,6 +154,24 @@ class WhatsAppWebhook {
|
|||||||
'reaction_emoji' => $emoji
|
'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') {
|
} elseif (isset($message['text']) || ($message['type'] ?? '') === 'text') {
|
||||||
$messageText = isset($message['text']['body']) ? $message['text']['body'] : ($message['body'] ?? '');
|
$messageText = isset($message['text']['body']) ? $message['text']['body'] : ($message['body'] ?? '');
|
||||||
$messageType = 'text';
|
$messageType = 'text';
|
||||||
|
|||||||
@@ -23,3 +23,12 @@ Stack trace:
|
|||||||
[2026-01-21 02:31:42] [INFO] Advisor requested for 573010000123
|
[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:44] [INFO] Advisor released hold for 573010000123
|
||||||
[2026-01-21 02:31:49] [INFO] Bot paused for 4 hours 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
|
||||||
|
|||||||
@@ -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";
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
@@ -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
@@ -33,9 +33,16 @@ class BotService {
|
|||||||
// ignore logging errors
|
// 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'])) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,12 +105,16 @@ class BotService {
|
|||||||
return;
|
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']]);
|
$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']]);
|
$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'])) {
|
if ($lastOutgoing && $lastIncoming && strtotime($lastOutgoing['created_at']) >= strtotime($lastIncoming['created_at'])) {
|
||||||
// El asesor ya respondió, no enviar respuestas automáticas
|
// El asesor ya respondió, marcar flag pero no retornar (permitir menús y comandos explícitos)
|
||||||
return;
|
$advisorReplied = true;
|
||||||
|
error_log("[BotService] processMessage - advisorReplied=true for user {$user['id']}");
|
||||||
}
|
}
|
||||||
// Verificar si el usuario está en un menú
|
// Verificar si el usuario está en un menú
|
||||||
if ($user['current_menu_id']) {
|
if ($user['current_menu_id']) {
|
||||||
@@ -111,7 +122,17 @@ class BotService {
|
|||||||
} else {
|
} else {
|
||||||
// No usar las "respuestas rápidas" (autoresponses) automáticamente desde el bot.
|
// 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".
|
// 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;
|
$optionNumber = (int)$messageText;
|
||||||
|
|
||||||
// Buscar la opción seleccionada
|
// Buscar la opción seleccionada por número
|
||||||
$option = $this->db->fetch(
|
$option = $this->db->fetch(
|
||||||
"SELECT * FROM menu_options WHERE menu_id = :menu_id AND option_number = :option_number AND is_active = 1",
|
"SELECT * FROM menu_options WHERE menu_id = :menu_id AND option_number = :option_number AND is_active = 1",
|
||||||
['menu_id' => $currentMenuId, 'option_number' => $optionNumber]
|
['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) {
|
if (!$option) {
|
||||||
$this->whatsappService->sendTextMessage(
|
$this->whatsappService->sendTextMessage(
|
||||||
$phoneNumber,
|
$phoneNumber,
|
||||||
|
|||||||
Reference in New Issue
Block a user