delete test

This commit is contained in:
lizandrogd
2026-01-13 01:48:33 -05:00
parent b0d44b0095
commit 0544e81463
34 changed files with 3141 additions and 3337 deletions
+387
View File
@@ -0,0 +1,387 @@
<?php
/**
* Bot Service Mejorado - Laboratorio Ximena Caicedo
* Integra todos los flujos y servicios del chatbot
* Fecha: 13 de enero de 2026
*/
require_once __DIR__ . '/WhatsAppService.php';
require_once __DIR__ . '/BusinessHoursService.php';
require_once __DIR__ . '/ConversationStateService.php';
require_once __DIR__ . '/NLPService.php';
require_once __DIR__ . '/MenuService.php';
class BotServiceLab
{
private $db;
private $whatsappService;
private $businessHoursService;
private $stateService;
private $nlpService;
private $menuService;
public function __construct()
{
$this->db = Database::getInstance();
$this->whatsappService = new WhatsAppService();
$this->businessHoursService = new BusinessHoursService();
$this->stateService = new ConversationStateService();
$this->nlpService = new NLPService();
$this->menuService = new MenuService();
}
/**
* Procesa un mensaje entrante
* @param array $user
* @param string $messageText
* @param string $messageType
* @param string|null $mediaId
*/
public function processMessage(array $user, string $messageText, string $messageType = 'text', ?string $mediaId = null): void
{
$phoneNumber = $user['phone_number'];
try {
// 1. Verificar si el usuario está con un asesor
if ($this->stateService->isWaitingForAdvisor($phoneNumber)) {
// No procesar automáticamente, está con asesor humano
$this->logMessage($phoneNumber, "Usuario con asesor, mensaje no procesado automáticamente");
return;
}
// 2. Verificar horario de atención
if (!$this->businessHoursService->isBusinessHours()) {
if ($this->businessHoursService->shouldShowOutOfHoursMessage($phoneNumber)) {
$message = $this->businessHoursService->getMessage();
$this->sendMessage($phoneNumber, $message);
}
return;
}
// 3. Verificar si el estado ha expirado
if ($this->stateService->isStateExpired($phoneNumber)) {
$this->stateService->clearState($phoneNumber);
}
// 4. Procesar según el tipo de mensaje
if ($messageType !== 'text' && $mediaId) {
$this->processMediaMessage($user, $messageType, $mediaId, $messageText);
return;
}
// 5. Verificar si es el primer mensaje (bienvenida)
if ($this->isFirstMessage($phoneNumber)) {
$this->sendWelcomeMessage($phoneNumber);
return;
}
// 6. Procesar mensaje de texto con NLP
$this->processTextMessage($user, $messageText);
} catch (Exception $e) {
error_log("Error en BotServiceLab->processMessage: " . $e->getMessage());
$this->sendMessage($phoneNumber, "Lo siento, ocurrió un error. Por favor intenta de nuevo o escribe *ASESOR* para ayuda.");
}
}
/**
* Procesa un mensaje de texto
* @param array $user
* @param string $messageText
*/
private function processTextMessage(array $user, string $messageText): void
{
$phoneNumber = $user['phone_number'];
// Procesar con NLP para encontrar intención
$response = $this->nlpService->processMessage($messageText, $phoneNumber);
if (!$response) {
// No se encontró respuesta, enviar mensaje por defecto
$this->sendMessage($phoneNumber, "❓ No he comprendido tu mensaje.\n\nEscribe *MENÚ* para ver las opciones disponibles.");
return;
}
// Incrementar intentos si no entendió
if ($response['type'] === 'default') {
$attempts = $this->stateService->incrementAttempts($phoneNumber);
// Si son 3 intentos fallidos, transferir a asesor
if ($attempts >= 3) {
$this->transferToAdvisor($phoneNumber, 'No se pudo entender el mensaje después de 3 intentos');
return;
}
} else {
// Resetear intentos si entendió correctamente
$this->stateService->resetAttempts($phoneNumber);
}
// Detectar frustración
if ($this->nlpService->detectsFrustration($messageText)) {
$this->transferToAdvisor($phoneNumber, 'Frustración detectada');
return;
}
// Ejecutar acción según el tipo de respuesta
$this->executeResponse($phoneNumber, $response);
}
/**
* Ejecuta la acción según el tipo de respuesta
* @param string $phoneNumber
* @param array $response
*/
private function executeResponse(string $phoneNumber, array $response): void
{
switch ($response['type']) {
case 'text':
$this->sendMessage($phoneNumber, $response['message']);
break;
case 'menu':
$menuId = $response['menu_id'] ?? null;
if ($menuId) {
$this->sendMenu($phoneNumber, $menuId);
}
break;
case 'template':
$templateName = $response['template_name'] ?? null;
if ($templateName) {
$this->sendTemplate($phoneNumber, $templateName);
}
break;
case 'transfer':
$message = $response['message'] ?? 'Conectando con asesor...';
$this->transferToAdvisor($phoneNumber, 'Solicitud del usuario', $message);
break;
}
}
/**
* Procesa mensajes multimedia
* @param array $user
* @param string $messageType
* @param string $mediaId
* @param string $caption
*/
private function processMediaMessage(array $user, string $messageType, string $mediaId, string $caption = ''): void
{
$phoneNumber = $user['phone_number'];
$currentState = $this->stateService->getState($phoneNumber);
// Si está esperando foto de orden médica o documento
if ($currentState && in_array($currentState['state'], [
ConversationStateService::STATE_AWAITING_ORDER_PHOTO,
ConversationStateService::STATE_AWAITING_ID_PHOTO
])) {
if ($messageType === 'image' || $messageType === 'document') {
$this->handleUploadedDocument($user, $mediaId, $currentState['state']);
} else {
$this->sendMessage($phoneNumber, "Por favor envía una imagen o documento PDF.");
}
return;
}
// Si no está esperando media, informar
$this->sendMessage($phoneNumber, "He recibido tu archivo. ¿En qué puedo ayudarte?\n\nEscribe *MENÚ* para ver las opciones.");
}
/**
* Maneja documentos subidos (órdenes médicas, ID, etc.)
* @param array $user
* @param string $mediaId
* @param string $expectedType
*/
private function handleUploadedDocument(array $user, string $mediaId, string $expectedType): void
{
$phoneNumber = $user['phone_number'];
try {
// Descargar el archivo desde WhatsApp
$mediaUrl = $this->whatsappService->downloadMedia($mediaId);
if (!$mediaUrl) {
$this->sendMessage($phoneNumber, "❌ No pude descargar el archivo. Por favor intenta de nuevo.");
return;
}
// Guardar información del documento
if ($expectedType === ConversationStateService::STATE_AWAITING_ORDER_PHOTO) {
$this->stateService->updateStateData($phoneNumber, [
'order_photo_url' => $mediaUrl,
'order_received_at' => date('Y-m-d H:i:s')
]);
$this->sendMessage($phoneNumber, "✅ Orden médica recibida.\n\nAhora necesito los datos del paciente:\n\n✓ Nombre completo:\n✓ Número de documento:\n✓ Dirección completa:\n✓ Número de celular:\n✓ Correo electrónico:\n✓ ¿Es particular o por seguro?\n\nEnvíalos en UN SOLO mensaje.");
$this->stateService->setState($phoneNumber, ConversationStateService::STATE_AWAITING_PATIENT_DATA);
} elseif ($expectedType === ConversationStateService::STATE_AWAITING_ID_PHOTO) {
$this->stateService->updateStateData($phoneNumber, [
'id_photo_url' => $mediaUrl,
'id_received_at' => date('Y-m-d H:i:s')
]);
$this->sendMessage($phoneNumber, "✅ Documento de identidad recibido.\n\nProcesando tu solicitud...");
// Aquí iría lógica adicional según el flujo
$this->stateService->setState($phoneNumber, ConversationStateService::STATE_VALIDATING_DATA);
}
} catch (Exception $e) {
error_log("Error downloading media: " . $e->getMessage());
$this->sendMessage($phoneNumber, "❌ Hubo un problema al procesar el archivo. Por favor intenta nuevamente.");
}
}
/**
* Envía el mensaje de bienvenida
* @param string $phoneNumber
*/
private function sendWelcomeMessage(string $phoneNumber): void
{
$message = $this->businessHoursService->getMessage();
$this->sendMessage($phoneNumber, $message);
// Si está en horario, enviar menú principal
if ($this->businessHoursService->isBusinessHours()) {
$mainMenuId = $this->menuService->getMainMenuId();
if ($mainMenuId) {
$this->stateService->setCurrentMenu($phoneNumber, $mainMenuId);
}
}
}
/**
* Envía un menú
* @param string $phoneNumber
* @param int $menuId
*/
private function sendMenu(string $phoneNumber, int $menuId): void
{
$menu = $this->menuService->getMenu($menuId);
if (!$menu) {
$this->sendMessage($phoneNumber, "❌ Menú no encontrado.");
return;
}
$this->sendMessage($phoneNumber, $menu['welcome_message']);
$this->stateService->setCurrentMenu($phoneNumber, $menuId);
}
/**
* Transfiere la conversación a un asesor
* @param string $phoneNumber
* @param string $reason
* @param string $message
*/
private function transferToAdvisor(string $phoneNumber, string $reason, string $message = null): void
{
// Enviar mensaje al usuario
$userMessage = $message ?? '👤 SOLICITUD DE ASESOR\n\nUn momento por favor, estamos conectando tu consulta con uno de nuestros asesores.\n\nEn breve te atenderemos.\n\n*Tu consulta es importante para nosotros* 💙';
$this->sendMessage($phoneNumber, $userMessage);
// Marcar en el estado
$this->stateService->markForTransfer($phoneNumber, $reason);
// Aquí se podría notificar al equipo de asesores
// Por ejemplo, enviar notificación, email, etc.
$this->logMessage($phoneNumber, "Transferido a asesor: $reason");
}
/**
* Envía una plantilla de WhatsApp
* @param string $phoneNumber
* @param string $templateName
* @param array $parameters
*/
private function sendTemplate(string $phoneNumber, string $templateName, array $parameters = []): void
{
try {
$result = $this->whatsappService->sendTemplateMessage($phoneNumber, $templateName, $parameters);
if (!$result) {
$this->sendMessage($phoneNumber, "❌ No pude enviar la plantilla. Por favor contacta a un asesor.");
}
} catch (Exception $e) {
error_log("Error sending template: " . $e->getMessage());
$this->sendMessage($phoneNumber, "❌ Error al enviar plantilla.");
}
}
/**
* Envía un mensaje de texto
* @param string $phoneNumber
* @param string $message
* @return bool
*/
private function sendMessage(string $phoneNumber, string $message): bool
{
try {
$result = $this->whatsappService->sendTextMessage($phoneNumber, $message);
if ($result) {
// Guardar en BD
$this->saveOutgoingMessage($phoneNumber, $message);
}
return $result;
} catch (Exception $e) {
error_log("Error sending message: " . $e->getMessage());
return false;
}
}
/**
* Guarda un mensaje saliente en la BD
* @param string $phoneNumber
* @param string $message
*/
private function saveOutgoingMessage(string $phoneNumber, string $message): void
{
try {
$stmt = $this->db->prepare("
INSERT INTO conversations
(phone_number, message_type, message_text, direction, status, created_at)
VALUES (?, 'text', ?, 'outgoing', 'sent', NOW())
");
$stmt->execute([$phoneNumber, $message]);
} catch (Exception $e) {
error_log("Error saving outgoing message: " . $e->getMessage());
}
}
/**
* Verifica si es el primer mensaje del usuario
* @param string $phoneNumber
* @return bool
*/
private function isFirstMessage(string $phoneNumber): bool
{
$stmt = $this->db->prepare("
SELECT COUNT(*) as count
FROM conversations
WHERE phone_number = ?
AND direction = 'incoming'
");
$stmt->execute([$phoneNumber]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result && $result['count'] == 1; // Solo tiene un mensaje (el actual)
}
/**
* Registra un log de la conversación
* @param string $phoneNumber
* @param string $message
*/
private function logMessage(string $phoneNumber, string $message): void
{
error_log("[Bot - $phoneNumber] $message");
}
}