From 14275e4e0af9ddf6c912711a38e1038f62f5d9fc Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Sat, 24 Jan 2026 01:54:52 -0500 Subject: [PATCH] Update BotService.php --- services/BotService.php | 166 +++++++++++++++++++++++++++++++++++----- 1 file changed, 145 insertions(+), 21 deletions(-) diff --git a/services/BotService.php b/services/BotService.php index 4f7e5af..085c4d2 100644 --- a/services/BotService.php +++ b/services/BotService.php @@ -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 */