Update BotService.php

This commit is contained in:
Lizandro Guarnizo
2026-01-24 01:54:52 -05:00
parent 79ab122ceb
commit 14275e4e0a
+145 -21
View File
@@ -326,28 +326,36 @@ class BotService {
*/
private function processMenuSelection($user, $messageText) {
$phoneNumber = $user['phone_number'];
// Obtener el current_menu_id más reciente desde la tabla users (evita estados desincronizados)
$cur = $this->db->fetch("SELECT current_menu_id FROM users WHERE phone_number = :phone", ['phone' => $phoneNumber]);
$currentMenuId = $cur ? $cur['current_menu_id'] : ($user['current_menu_id'] ?? null);
// Fallback: si usamos ConversationStateService para guardar estados, respetarlo
if (empty($currentMenuId)) {
try {
$stateSvc = new ConversationStateService();
$currentMenuId = $stateSvc->getCurrentMenuId($phoneNumber);
} catch (Exception $e) {
// ignore if service not available
}
}
// DEBUG
try { error_log("[BotService] processMenuSelection - user_id={$user['id']} menu_id={$currentMenuId} received='{$messageText}'"); } catch (Throwable $t) {}
$currentMenuId = null;
// Soportar comando "atrás" o "volver" para navegar al menú padre
// Intentar primero el servicio de estados (si existe) — esto permite flujos que usan user_states en lugar de la columna users.current_menu_id
try {
$stateSvc = new ConversationStateService();
$currentMenuId = $stateSvc->getCurrentMenuId($phoneNumber);
} catch (Exception $e) {
// ignore if service not available
}
// Si no tenemos valor desde el servicio de estados, consultar columna users
if (empty($currentMenuId)) {
$cur = $this->db->fetch("SELECT current_menu_id FROM users WHERE phone_number = :phone", ['phone' => $phoneNumber]);
$currentMenuId = $cur ? $cur['current_menu_id'] : ($user['current_menu_id'] ?? null);
}
// DEBUG
try { error_log("[BotService] processMenuSelection - RESOLVED menu_id={$currentMenuId} for phone={$phoneNumber} user_id={$user['id']} incoming='{$messageText}'"); } catch (Throwable $t) {}
// 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'];
if (in_array($cmd, $backCommands, true)) {
$this->goToParentMenu($phoneNumber, $currentMenuId);
try { error_log("[BotService] processMenuSelection - BACK command detected for phone={$phoneNumber} resolved_menu={$currentMenuId}"); } catch (Throwable $t) {}
if (!empty($currentMenuId)) {
$this->goToParentMenu($phoneNumber, $currentMenuId);
} else {
// Si no hay menú actual, intentar volver al menú previo almacenado en historial
$this->goToPreviousMenu($phoneNumber);
}
return;
}
@@ -503,14 +511,38 @@ class BotService {
/**
* Salir del menú actual
* Guarda el menú previo en el historial para permitir "ATRÁS" después de salir
*/
private function exitMenu($phoneNumber) {
// Obtener menú actual antes de limpiar
try {
$cur = $this->db->fetch("SELECT current_menu_id FROM users WHERE phone_number = :phone", ['phone' => $phoneNumber]);
$previousMenu = $cur ? $cur['current_menu_id'] : null;
if (!empty($previousMenu)) {
try {
$this->pushMenuToHistory($phoneNumber, $previousMenu);
} catch (Exception $e) {
error_log('[BotService] exitMenu - failed to push previous menu: ' . $e->getMessage());
}
}
} catch (Exception $e) {
// ignore
}
$this->db->update(
'users',
['current_menu_id' => null, 'current_step' => 0, 'session_data' => null],
'phone_number = :phone',
['phone' => $phoneNumber]
);
// Mantener el estado en ConversationStateService (no borrar el historial)
try {
$stateSvc = new ConversationStateService();
$stateSvc->setCurrentMenu($phoneNumber, null);
} catch (Exception $e) {
// ignore
}
}
/**
@@ -671,26 +703,30 @@ class BotService {
// Intentar resolver por ID numérico
if (!empty($currentMenuId) && is_numeric($currentMenuId)) {
$menu = $this->db->fetch("SELECT id, parent_id FROM menus WHERE id = :id", ['id' => (int)$currentMenuId]);
$menu = $this->db->fetch("SELECT id, parent_id, name, title FROM menus WHERE id = :id", ['id' => (int)$currentMenuId]);
}
// Si no existe, intentar resolver por nombre (caso action_value = 'main_menu')
if (!$menu && !empty($currentMenuId)) {
$menu = $this->db->fetch("SELECT id, parent_id FROM menus WHERE name = :name", ['name' => $currentMenuId]);
$menu = $this->db->fetch("SELECT id, parent_id, name, title FROM menus WHERE name = :name", ['name' => $currentMenuId]);
}
if ($menu) {
$parentId = $menu['parent_id'] ?? null;
try { error_log("[BotService] goToParentMenu - resolved menu id={$menu['id']} name={$menu['name']} title='{$menu['title']}' parent_id=" . ($parentId ?? 'null') . " for phone={$phoneNumber}"); } catch (Throwable $t) {}
if (!empty($parentId)) {
$this->showMenu($phoneNumber, $parentId);
return;
} else {
// Si no hay padre, re-mostrar el menú actual (mejor UX) o fallback al menú principal
// Si no hay padre, re-mostrar el menú actual (mejor UX)
if (!empty($menu['id'])) {
$this->showMenu($phoneNumber, $menu['id']);
return;
}
}
} else {
try { error_log("[BotService] goToParentMenu - could not resolve menu for currentMenuId='{$currentMenuId}' phone={$phoneNumber}"); } catch (Throwable $t) {}
}
// Fallback final a menú principal
@@ -703,17 +739,105 @@ class BotService {
/**
* Actualizar estado del menú del usuario
* - Guarda el menú actual en el historial (ConversationStateService) antes de actualizar
*/
private function updateUserMenuState($phoneNumber, $menuId) {
// Obtener menú previo
try {
$cur = $this->db->fetch("SELECT current_menu_id FROM users WHERE phone_number = :phone", ['phone' => $phoneNumber]);
$previousMenu = $cur ? $cur['current_menu_id'] : null;
} catch (Exception $e) {
$previousMenu = null;
}
// Si hay un menú previo diferente al nuevo, empujarlo al historial
if (!empty($previousMenu) && $previousMenu != $menuId) {
try {
$this->pushMenuToHistory($phoneNumber, $previousMenu);
} catch (Exception $e) {
error_log('[BotService] failed to push menu to history: ' . $e->getMessage());
}
}
// Actualizar columna users
$this->db->update(
'users',
['current_menu_id' => $menuId, 'current_step' => 1],
'phone_number = :phone',
['phone' => $phoneNumber]
);
// También actualizar ConversationStateService por compatibilidad
try {
$stateSvc = new ConversationStateService();
$stateSvc->setCurrentMenu($phoneNumber, (int)$menuId);
} catch (Exception $e) {
// ignore if service not available
}
}
/**
* Empujar un menú al historial del usuario (se almacena en ConversationStateService.state_data.menu_history)
*/
private function pushMenuToHistory(string $phoneNumber, $menuId) {
try {
$stateSvc = new ConversationStateService();
$history = (array)$stateSvc->getStateData($phoneNumber, 'menu_history');
if (!is_array($history)) $history = [];
// Añadir al final y limitar a 10 entradas
$history[] = $menuId;
if (count($history) > 10) array_shift($history);
return $stateSvc->updateStateData($phoneNumber, ['menu_history' => $history]);
} catch (Exception $e) {
error_log('[BotService] pushMenuToHistory failed: ' . $e->getMessage());
return false;
}
}
/**
* Sacar el último menú del historial (LIFO) y retornarlo
*/
private function popMenuFromHistory(string $phoneNumber) {
try {
$stateSvc = new ConversationStateService();
$history = (array)$stateSvc->getStateData($phoneNumber, 'menu_history');
if (!is_array($history) || empty($history)) return null;
$last = array_pop($history);
$stateSvc->updateStateData($phoneNumber, ['menu_history' => $history]);
return $last;
} catch (Exception $e) {
error_log('[BotService] popMenuFromHistory failed: ' . $e->getMessage());
return null;
}
}
/**
* Ir al menú anterior según historial. Si no hay historial, fallback a main menu.
*/
private function goToPreviousMenu($phoneNumber) {
$prev = $this->popMenuFromHistory($phoneNumber);
if (!empty($prev)) {
// Intentar mostrar por ID primero
if (is_numeric($prev)) {
$this->showMenu($phoneNumber, (int)$prev);
return;
}
// Si no es numérico, intentar resolver por nombre
$menu = $this->db->fetch("SELECT id FROM menus WHERE name = :name", ['name' => $prev]);
if ($menu && !empty($menu['id'])) {
$this->showMenu($phoneNumber, $menu['id']);
return;
}
}
// Fallback
$this->showMainMenu($phoneNumber);
}
/**
* Obtener estadísticas del bot
*/