u
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
||||
Reference in New Issue
Block a user