up
This commit is contained in:
+185
-22
@@ -48,7 +48,7 @@ class BotService {
|
||||
// DEBUG: log basic context
|
||||
try {
|
||||
$userId = $user['id'] ?? 'unknown';
|
||||
error_log("[BotService] processMessage start - user_id={$userId} phone={$phoneNumber} type={$messageType} message='" . substr($messageText,0,200) . "' current_menu_id=" . ($user['current_menu_id'] ?? 'null') . " on_hold=" . ($user['on_hold'] ?? '0') . " advisor_requested=" . ($user['advisor_requested'] ?? '0') . " bot_paused_until=" . ($user['bot_paused_until'] ?? 'null'));
|
||||
error_log("[BotService] processMessage start - user_id={$userId} phone={$phoneNumber} type={$messageType} message='" . substr($messageText,0,200) . "' current_menu_id=" . ($user['current_menu_id'] ?? 'null') . " on_hold=" . ($user['on_hold'] ?? '0') . " advisor_requested=" . ($user['advisor_requested'] ?? '0') . " bot_paused_until=" . ($user['bot_paused_until'] ?? 'null') . " bot_enabled=" . (isset($user['bot_enabled']) ? $user['bot_enabled'] : 'null'));
|
||||
} catch (Throwable $t) {
|
||||
// ignore logging errors
|
||||
}
|
||||
@@ -63,11 +63,12 @@ class BotService {
|
||||
try {
|
||||
// Support masculino/femenino, singular/plural: enviado, enviada, enviados, enviadas
|
||||
if (preg_match('/\b(enviad[oa]s?)\b/iu', $messageText)) {
|
||||
// NO enviar respuesta al usuario (para conservar el indicador de atención en la lista).
|
||||
// En su lugar, marcar la conversación como que requiere atención y crear un mensaje 'system'
|
||||
// Cuando el usuario confirma que envió la documentación, notificarle y transferir a un asesor.
|
||||
try {
|
||||
$this->db->update('users', ['advisor_requested' => 1], 'phone_number = :phone', ['phone' => $phoneNumber]);
|
||||
// Usar requestAdvisor para aplicar advisor_requested, pausar el bot y enviar notificación al usuario
|
||||
$this->requestAdvisor($phoneNumber, 3);
|
||||
|
||||
// Registrar mensaje de sistema para trazabilidad (no altera flujo de notificación)
|
||||
$sys = [
|
||||
'system' => 'attention',
|
||||
'type' => 'user_sent_documents',
|
||||
@@ -84,10 +85,10 @@ class BotService {
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log('[BotService] failed to mark advisor_requested on envio: ' . $e->getMessage());
|
||||
error_log('[BotService] failed to request advisor on envio: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
error_log("[BotService] processMessage - marked advisor_requested for user {$user['id']} (no outgoing message sent)");
|
||||
error_log("[BotService] processMessage - requested advisor for user {$user['id']} (sent notification to user)");
|
||||
return;
|
||||
}
|
||||
} catch (Throwable $t) {
|
||||
@@ -129,11 +130,8 @@ class BotService {
|
||||
// Permitir *navegación explícita* durante la espera: MENU, ATRÁS, números (opciones)
|
||||
$normalized = mb_strtolower(trim($messageText));
|
||||
$isNav = in_array($normalized, ['menu','menú','inicio','atras','atrás','volver','back'], true) || is_numeric($messageText);
|
||||
try {
|
||||
$this->whatsappService->sendTextMessage($phoneNumber, "🔔 Su solicitud de asesor está pendiente. Un miembro del equipo le atenderá en breve. Si no hay respuesta, podrá usar *MENU* después de " . date('g:i a', $until) . ".");
|
||||
} catch (Exception $e) {
|
||||
// ignore
|
||||
}
|
||||
// No enviar notificación automática al usuario cuando advisor_requested está activa.
|
||||
// Mantener navegación explícita permitida (MENU/ATRÁS/números); no enviar mensajes proactivos.
|
||||
if ($isNav) {
|
||||
try { error_log("[BotService] processMessage - advisor_requested active but navigation input allowed for user {$user['id']} message='{$messageText}'"); } catch (Throwable $t) {}
|
||||
|
||||
@@ -285,10 +283,10 @@ class BotService {
|
||||
}
|
||||
}
|
||||
|
||||
// Si el usuario ha desactivado el bot manualmente (toggle), no responder
|
||||
if (isset($user['bot_enabled']) && !$user['bot_enabled']) {
|
||||
return;
|
||||
}
|
||||
// Si el usuario ha desactivado el bot manualmente (toggle), evitar respuestas automáticas
|
||||
// pero permitir comandos explícitos (MENU, ATRÁS) y selección numérica dentro de menús.
|
||||
$botDisabled = (isset($user['bot_enabled']) && !$user['bot_enabled']);
|
||||
|
||||
|
||||
// Verificar si el asesor ya respondió después del último mensaje del usuario.
|
||||
// En ese caso NO enviaremos respuestas automáticas, pero sí permitiremos
|
||||
@@ -300,6 +298,9 @@ class BotService {
|
||||
// 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']}");
|
||||
try { error_log("[BotService] processMessage - lastOutgoing={$lastOutgoing['created_at']} lastIncoming={$lastIncoming['created_at']}"); } catch (Throwable $t) {}
|
||||
} else {
|
||||
try { error_log("[BotService] processMessage - advisorReplied=false lastOutgoing=" . ($lastOutgoing['created_at'] ?? 'null') . " lastIncoming=" . ($lastIncoming['created_at'] ?? 'null')) ; } catch (Throwable $t) {}
|
||||
}
|
||||
|
||||
// Resolver current_menu_id fresco (evitar usar $user desactualizado que viene del webhook)
|
||||
@@ -355,7 +356,11 @@ class BotService {
|
||||
// Estas respuestas solo se mostrarán en la UI del chat como "respuestas rápidas".
|
||||
// Si un asesor ya respondió recientemente, no enviar respuestas automáticas.
|
||||
if (empty($advisorReplied)) {
|
||||
$this->sendDefaultNoMatch($phoneNumber);
|
||||
if (empty($botDisabled)) {
|
||||
$this->sendDefaultNoMatch($phoneNumber);
|
||||
} else {
|
||||
try { error_log("[BotService] processMessage - bot disabled, skipping default no-match for user {$user['id']}"); } catch (Throwable $t) {}
|
||||
}
|
||||
} else {
|
||||
// Opcional: informar que el asesor ya respondió y no podemos enviar automáticas
|
||||
try {
|
||||
@@ -565,6 +570,133 @@ class BotService {
|
||||
// DEBUG
|
||||
try { error_log("[BotService] processMenuSelection - RESOLVED menu_id={$currentMenuId} for phone={$phoneNumber} user_id={$user['id']} incoming='{$messageText}'"); } catch (Throwable $t) {}
|
||||
|
||||
// Prioritario: si estamos esperando documentos para esta conversación, manejarlo aquí
|
||||
try {
|
||||
$stateSvcTemp = new ConversationStateService();
|
||||
$await = $stateSvcTemp->getStateData($phoneNumber, 'awaiting_documents');
|
||||
} catch (Exception $e) {
|
||||
$await = null;
|
||||
}
|
||||
|
||||
if (!empty($await)) {
|
||||
// Si llega multimedia (no text), marcar recibido y enviar ACK
|
||||
if ($messageType !== 'text') {
|
||||
try {
|
||||
$stateSvcTemp->updateStateData($phoneNumber, ['awaiting_documents' => ['since' => $await['since'], 'received' => true]]);
|
||||
} catch (Exception $e) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
$this->whatsappService->sendTextMessage($phoneNumber, "✅ Documento recibido. Cuando haya terminado, escriba *ENVIADA*.");
|
||||
} catch (Exception $e) {
|
||||
// ignore
|
||||
}
|
||||
return; // no procesar más
|
||||
}
|
||||
|
||||
// Si es texto y dice enviada => transferir a asesor
|
||||
if (preg_match('/\b(enviad[oa]s?)\b/iu', $messageText)) {
|
||||
try {
|
||||
$this->requestAdvisor($phoneNumber, 3);
|
||||
// limpiar estado awaiting_documents
|
||||
$stateSvcTemp->updateStateData($phoneNumber, ['awaiting_documents' => null]);
|
||||
|
||||
// registrar sistema para trazabilidad
|
||||
$sys = [
|
||||
'system' => 'user_documents_sent',
|
||||
'type' => 'user_confirmed_documents',
|
||||
'text' => 'Usuario confirmó envío de documentos con la palabra ENVIADA.'
|
||||
];
|
||||
$this->db->insert('conversations', [
|
||||
'user_id' => $user['id'],
|
||||
'direction' => 'incoming',
|
||||
'message_type' => 'text',
|
||||
'content' => json_encode($sys),
|
||||
'status' => 'received',
|
||||
'created_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
error_log('[BotService] failed to process ENVIADA: ' . $e->getMessage());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Permitir navegación explícita (MENU/ATRÁS/números) y en los demás casos evitar respuestas automáticas
|
||||
$cmd = mb_strtolower(trim($messageText));
|
||||
$backCommands = ['atras', 'atrás', 'volver', 'back'];
|
||||
if (in_array($cmd, ['menu','menú','inicio'], true)) {
|
||||
$this->showMainMenu($phoneNumber);
|
||||
return;
|
||||
}
|
||||
if (in_array($cmd, $backCommands, true)) {
|
||||
try {
|
||||
$cur = $this->db->fetch("SELECT current_menu_id FROM users WHERE phone_number = :phone", ['phone' => $phoneNumber]);
|
||||
$currentMenuId = $cur ? $cur['current_menu_id'] : null;
|
||||
} catch (Exception $e) {
|
||||
$currentMenuId = null;
|
||||
}
|
||||
|
||||
if (!empty($currentMenuId)) {
|
||||
$this->goToParentMenu($phoneNumber, $currentMenuId);
|
||||
} else {
|
||||
$this->goToPreviousMenu($phoneNumber);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_numeric(trim($messageText))) {
|
||||
try {
|
||||
$stateSvc2 = new ConversationStateService();
|
||||
$resolvedMenuId2 = $stateSvc2->getCurrentMenuId($phoneNumber);
|
||||
} catch (Exception $e) {
|
||||
$resolvedMenuId2 = null;
|
||||
}
|
||||
|
||||
if (empty($resolvedMenuId2)) {
|
||||
try {
|
||||
$cur = $this->db->fetch("SELECT current_menu_id FROM users WHERE phone_number = :phone", ['phone' => $phoneNumber]);
|
||||
$resolvedMenuId2 = $cur ? $cur['current_menu_id'] : null;
|
||||
} catch (Exception $e) {
|
||||
$resolvedMenuId2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($resolvedMenuId2)) {
|
||||
try {
|
||||
$hist = (array)$stateSvc2->getStateData($phoneNumber, 'menu_history');
|
||||
if (!empty($hist)) $resolvedMenuId2 = end($hist);
|
||||
} catch (Exception $e) {
|
||||
$resolvedMenuId2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($resolvedMenuId2)) {
|
||||
try {
|
||||
$this->db->update('users', ['current_menu_id' => $resolvedMenuId2, 'current_step' => 1], 'phone_number = :phone', ['phone' => $phoneNumber]);
|
||||
try {
|
||||
$stateSvc2->setCurrentMenu($phoneNumber, (int)$resolvedMenuId2);
|
||||
} catch (Exception $e) {
|
||||
// ignore
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log('[BotService] failed to set current_menu_id before processing numeric selection: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$user['current_menu_id'] = $resolvedMenuId2;
|
||||
$this->processMenuSelection($user, $messageText);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Si llegamos aquí y no es navegación, enviamos un aviso simple para que escriba ENVIADA
|
||||
try {
|
||||
$this->whatsappService->sendTextMessage($phoneNumber, "Si ya enviaste los documentos, escriba *ENVIADA* cuando hayas terminado. Si necesita volver al menú, escriba *MENU*. ");
|
||||
} catch (Exception $e) {
|
||||
// ignore
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Soportar comando "atrás" o "volver" para navegar al menú padre o al menú previo
|
||||
$cmd = mb_strtolower(trim($messageText));
|
||||
$backCommands = ['atras', 'atrás', 'volver', 'back'];
|
||||
@@ -662,9 +794,36 @@ class BotService {
|
||||
// Llamar API externa (implementar según necesidad)
|
||||
$this->processApiCall($phoneNumber, $option['action_value']);
|
||||
break;
|
||||
|
||||
case 'request_documents':
|
||||
// Solicitar al usuario que envíe documentación y activar estado awaiting_documents
|
||||
$message = $option['action_value'] ?: "Por favor, envíe los documentos o fotos requeridas.";
|
||||
$this->whatsappService->sendTextMessage($phoneNumber, $message);
|
||||
try {
|
||||
$stateSvc = new ConversationStateService();
|
||||
$stateSvc->updateStateData($phoneNumber, ['awaiting_documents' => ['since' => date('c'), 'received' => false]]);
|
||||
$this->whatsappService->sendTextMessage($phoneNumber, "Cuando hayas enviado los documentos, escribe *ENVIADA* para que un asesor lo revise.");
|
||||
$sys = [
|
||||
'system' => 'awaiting_documents_started',
|
||||
'type' => 'awaiting_documents',
|
||||
'text' => 'Se solicitó documentación y se espera confirmación ENVIADA.'
|
||||
];
|
||||
$this->db->insert('conversations', [
|
||||
'user_id' => $this->db->fetch('SELECT id FROM users WHERE phone_number = :phone', ['phone' => $phoneNumber])['id'] ?? null,
|
||||
'direction' => 'outgoing',
|
||||
'message_type' => 'text',
|
||||
'content' => json_encode($sys),
|
||||
'status' => 'sent',
|
||||
'created_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
error_log('[BotService] request_documents failed: ' . $e->getMessage());
|
||||
}
|
||||
$this->exitMenu($phoneNumber);
|
||||
break;
|
||||
|
||||
case 'end':
|
||||
// Finalizar conversación
|
||||
// Finalizar conversación (comportamiento por defecto)
|
||||
$message = $option['action_value'] ?: "Gracias por usar nuestro servicio. ¡Hasta pronto!";
|
||||
$this->whatsappService->sendTextMessage($phoneNumber, $message);
|
||||
$this->exitMenu($phoneNumber);
|
||||
@@ -791,7 +950,7 @@ class BotService {
|
||||
try {
|
||||
$pausedUntil = date('Y-m-d H:i:s', strtotime("+{$minutes} minutes"));
|
||||
// Guardar bandera y tiempo de expiración (usamos bot_paused_until que ya existe)
|
||||
$this->db->update('users', ['on_hold' => 1, 'bot_paused_until' => $pausedUntil, 'advisor_requested' => 1], 'phone_number = :phone', ['phone' => $phoneNumber]);
|
||||
$this->db->update('users', ['on_hold' => 1, 'bot_paused_until' => $pausedUntil, 'advisor_requested' => 1, 'bot_enabled' => 0], 'phone_number = :phone', ['phone' => $phoneNumber]);
|
||||
// Enviar mensaje (bot) marcando la solicitud; no incluimos metadata de operador para evitar limpiar la marca
|
||||
$this->whatsappService->sendTextMessage($phoneNumber, "🔔 Se ha solicitado un asesor. Se le ha puesto en espera por {$minutes} minutos. Si no hay respuesta, podrá volver a usar *MENU*." );
|
||||
if (function_exists('writeLog')) writeLog('INFO', "Advisor requested for $phoneNumber until $pausedUntil");
|
||||
@@ -807,7 +966,8 @@ class BotService {
|
||||
try {
|
||||
$pausedUntil = date('Y-m-d H:i:s', strtotime("+{$minutes} minutes"));
|
||||
// No ponemos on_hold para evitar bloqueo automático; usamos advisor_requested
|
||||
$this->db->update('users', ['advisor_requested' => 1, 'bot_paused_until' => $pausedUntil], 'phone_number = :phone', ['phone' => $phoneNumber]);
|
||||
// Marcar solicitud de asesor y pausar bot temporalmente
|
||||
$this->db->update('users', ['advisor_requested' => 1, 'bot_paused_until' => $pausedUntil, 'bot_enabled' => 0], 'phone_number = :phone', ['phone' => $phoneNumber]);
|
||||
$this->whatsappService->sendTextMessage($phoneNumber, "🔔 Le hemos transferido a un asesor; le responderemos en breve.");
|
||||
|
||||
if (function_exists('writeLog')) writeLog('INFO', "Advisor solicited for $phoneNumber until $pausedUntil");
|
||||
@@ -819,7 +979,8 @@ class BotService {
|
||||
public function releaseHold($phoneNumber) {
|
||||
try {
|
||||
// Clear hold/advisor flags and also ensure in_service is cleared for consistency
|
||||
$this->db->update('users', ['on_hold' => 0, 'bot_paused_until' => null, 'advisor_requested' => 0, 'in_service' => 0, 'in_service_by' => null, 'in_service_at' => null], 'phone_number = :phone', ['phone' => $phoneNumber]);
|
||||
// Clear hold and reactivate bot
|
||||
$this->db->update('users', ['on_hold' => 0, 'bot_paused_until' => null, 'advisor_requested' => 0, 'in_service' => 0, 'in_service_by' => null, 'in_service_at' => null, 'bot_enabled' => 1], 'phone_number = :phone', ['phone' => $phoneNumber]);
|
||||
$this->whatsappService->sendTextMessage($phoneNumber, "🔔 Su conversación ha sido retomada por el equipo. Puede usar *MENU* para continuar.");
|
||||
if (function_exists('writeLog')) writeLog('INFO', "Advisor released hold for $phoneNumber");
|
||||
} catch (Exception $e) {
|
||||
@@ -833,7 +994,8 @@ class BotService {
|
||||
public function attendConversation($phoneNumber, $operatorId = null) {
|
||||
try {
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$update = ['in_service' => 1, 'in_service_at' => $now, 'advisor_requested' => 0];
|
||||
// Marcar como en servicio y desactivar el bot temporalmente (solo mientras el operador atiende)
|
||||
$update = ['in_service' => 1, 'in_service_at' => $now, 'advisor_requested' => 0, 'bot_enabled' => 0];
|
||||
if ($operatorId) $update['in_service_by'] = $operatorId;
|
||||
$this->db->update('users', $update, 'phone_number = :phone', ['phone' => $phoneNumber]);
|
||||
|
||||
@@ -876,7 +1038,8 @@ class BotService {
|
||||
public function finishAttendConversation($phoneNumber, $operatorId = null) {
|
||||
try {
|
||||
// Limpiar estado de 'in_service'
|
||||
$this->db->update('users', ['in_service' => 0, 'in_service_by' => null, 'in_service_at' => null, 'advisor_requested' => 0], 'phone_number = :phone', ['phone' => $phoneNumber]);
|
||||
// Limpiar in_service y reactivar el bot
|
||||
$this->db->update('users', ['in_service' => 0, 'in_service_by' => null, 'in_service_at' => null, 'advisor_requested' => 0, 'bot_enabled' => 1], 'phone_number = :phone', ['phone' => $phoneNumber]);
|
||||
|
||||
// Enviar plantilla de encuesta (configurable)
|
||||
$template = getConfigFromDB('survey_template', 'encuesta_satisfaccin');
|
||||
|
||||
Reference in New Issue
Block a user