From d9942456c8e6788ec216c5bae120fc828a3702cf Mon Sep 17 00:00:00 2001
From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com>
Date: Fri, 23 Jan 2026 20:45:43 -0500
Subject: [PATCH] up
---
api/get_conversations.php | 37 ++++++++++++++-----
conversations.php | 76 +++++++++++++++++++++++++++++++++------
services/BotService.php | 16 ++++-----
3 files changed, 102 insertions(+), 27 deletions(-)
diff --git a/api/get_conversations.php b/api/get_conversations.php
index d811d9b..fe51f5a 100644
--- a/api/get_conversations.php
+++ b/api/get_conversations.php
@@ -14,9 +14,14 @@ header('Access-Control-Allow-Headers: Content-Type');
try {
$db = Database::getInstance();
+ // Paginación: recibir page y limit desde query string (con límites razonables)
+ $limit = isset($_GET['limit']) ? intval($_GET['limit']) : 50;
+ $limit = max(1, min(200, $limit)); // máximo 200 por página
+ $page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
+ $offset = ($page - 1) * $limit;
+
// Obtener conversaciones agregadas por usuario: último mensaje + conteo de no leídos + avatar
- $conversations = $db->fetchAll(
- "SELECT
+ $sql = "SELECT
u.id AS user_id,
COALESCE(u.name, u.phone_number) AS name,
u.phone_number,
@@ -43,14 +48,19 @@ try {
) lm ON lm.user_id = u.id
GROUP BY u.id
ORDER BY lm.created_at DESC
- LIMIT 500"
- );
-
+ LIMIT %d OFFSET %d";
+
+ $conversations = $db->fetchAll(sprintf($sql, $limit, $offset));
+
+ // Conteo total de usuarios con al menos una conversación (útil para paginar)
+ $totalRow = $db->fetch("SELECT COUNT(DISTINCT user_id) AS count FROM conversations");
+ $total = isset($totalRow['count']) ? intval($totalRow['count']) : 0;
+
// Si no hay datos, retornar array vacío
if (empty($conversations)) {
$conversations = [];
}
-
+
// Formatear datos para el frontend
$conversations = array_map(function($conv) {
return [
@@ -71,8 +81,19 @@ try {
'in_service_at' => $conv['in_service_at'] ?? null
];
}, $conversations);
-
- echo json_encode($conversations);
+
+ // Indicar metadatos para la paginación
+ $hasMore = ($page * $limit) < $total;
+
+ echo json_encode([
+ 'success' => true,
+ 'data' => $conversations,
+ 'page' => $page,
+ 'limit' => $limit,
+ 'has_more' => (bool)$hasMore,
+ 'total' => $total
+ ]);
+
} catch (Exception $e) {
error_log("Error in get_conversations.php: " . $e->getMessage());
diff --git a/conversations.php b/conversations.php
index 70aadd9..fa54b0a 100644
--- a/conversations.php
+++ b/conversations.php
@@ -455,6 +455,9 @@
Cargando conversaciones...
+
+
+
@@ -534,7 +537,11 @@
this.currentConversationId = null;
this.currentUserId = null;
this.conversations = [];
- this.conversations = [];
+ // Pagination state
+ this.conversationsPage = 1;
+ this.conversationsLimit = 50;
+ this.hasMoreConversations = true;
+ this.loadingConversations = false;
this.init();
}
@@ -671,6 +678,20 @@
document.getElementById('file-input').addEventListener('change', (e) => {
this.handleFileSelect(e);
});
+
+ // Carga paginada: botón "Cargar más" y scroll infinito
+ const loadMoreBtn = document.getElementById('load-more-btn');
+ if (loadMoreBtn) {
+ loadMoreBtn.addEventListener('click', () => this.loadMoreConversations());
+ }
+ const convList = document.getElementById('conversation-list');
+ if (convList) {
+ convList.addEventListener('scroll', () => {
+ if (this.hasMoreConversations && !this.loadingConversations && (convList.scrollTop + convList.clientHeight >= convList.scrollHeight - 60)) {
+ this.loadMoreConversations();
+ }
+ });
+ }
}
setupAutoRefresh() {
@@ -687,30 +708,63 @@
}, 10000);
}
- async loadConversations() {
+ async loadConversations(page = 1, append = false) {
+ if (this.loadingConversations) return;
+ this.loadingConversations = true;
+ const loadMoreContainer = document.getElementById('load-more-container');
+ const loadMoreBtn = document.getElementById('load-more-btn');
+ if (loadMoreBtn) loadMoreBtn.disabled = true;
try {
- const response = await fetch('api/get_conversations.php');
- const data = await response.json();
+ const url = `api/get_conversations.php?page=${page}&limit=${this.conversationsLimit}`;
+ const resp = await fetch(url);
+ const data = await resp.json();
console.log('Respuesta get_conversations:', data); // Debug
-
- // Manejar nuevo formato de respuesta de API
+
+ let items = [];
+ let hasMore = false;
if (data && data.success && Array.isArray(data.data)) {
- this.conversations = data.data;
- this.renderConversations();
+ items = data.data;
+ hasMore = !!data.has_more || (page * this.conversationsLimit) < (data.total || 0);
} else if (Array.isArray(data)) {
- // Retrocompatibilidad con formato anterior
- this.conversations = data;
- this.renderConversations();
+ items = data;
+ hasMore = items.length === this.conversationsLimit;
} else {
console.error('Error cargando conversaciones: formato de datos inválido', data);
alert('Error cargando conversaciones: ' + (data.error || 'formato de datos inválido'));
+ this.loadingConversations = false;
+ if (loadMoreBtn) loadMoreBtn.disabled = false;
+ return;
+ }
+
+ if (append) {
+ this.conversations = this.conversations.concat(items);
+ } else {
+ this.conversations = items;
+ }
+
+ this.hasMoreConversations = hasMore;
+ this.conversationsPage = page;
+ this.renderConversations();
+
+ if (loadMoreContainer) {
+ loadMoreContainer.style.display = this.hasMoreConversations ? 'block' : 'none';
}
} catch (error) {
console.error('Error loading conversations:', error);
alert('Error cargando conversaciones: ' + error.message);
+ } finally {
+ this.loadingConversations = false;
+ if (loadMoreBtn) loadMoreBtn.disabled = false;
}
}
+ async loadMoreConversations() {
+ if (!this.hasMoreConversations || this.loadingConversations) return;
+ await this.loadConversations(this.conversationsPage + 1, true);
+ }
+
+
+
renderConversations() {
const container = document.getElementById('conversation-list');
diff --git a/services/BotService.php b/services/BotService.php
index 5e473d8..35e4843 100644
--- a/services/BotService.php
+++ b/services/BotService.php
@@ -109,7 +109,7 @@ class BotService {
// Si hay un tiempo de expiración y aún no pasó, notificar y retornar
if ($until && $until > $now) {
try {
- $this->whatsappService->sendTextMessage($phoneNumber, "⚠️ Has sido puesto en espera por un asesor. Estarás en espera hasta " . date('H:i', $until) . ". Si no hay respuesta, podrás usar *menu*." );
+ $this->whatsappService->sendTextMessage($phoneNumber, "⚠️ Has sido puesto en espera por un asesor. Estarás en espera hasta " . date('H:i', $until) . ". Si no hay respuesta, podrás usar *MENU*." );
} catch (Exception $e) {
// ignore
}
@@ -127,7 +127,7 @@ class BotService {
$until = !empty($user['bot_paused_until']) ? strtotime($user['bot_paused_until']) : null;
if ($until && $until > $now) {
try {
- $this->whatsappService->sendTextMessage($phoneNumber, "🔔 Tu solicitud de asesor está pendiente. Un miembro del equipo te atenderá en breve. Si no, podrás usar *menu* después de " . date('H:i', $until) . ".");
+ $this->whatsappService->sendTextMessage($phoneNumber, "🔔 Tu solicitud de asesor está pendiente. Un miembro del equipo te atenderá en breve. Si no, podrás usar *MENU* después de " . date('H:i', $until) . ".");
} catch (Exception $e) {
// ignore
}
@@ -176,7 +176,7 @@ class BotService {
} 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.");
+ //$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
}
@@ -203,7 +203,7 @@ class BotService {
*/
public function sendWelcomeMessage($phoneNumber) {
$enabled = getConfigFromDB('welcome_enabled', '1');
- $welcomeMessage = getConfigFromDB('welcome_message', '¡Hola! 👋 Bienvenido a nuestro servicio automatizado. Escribe *menu* para ver las opciones disponibles.');
+ $welcomeMessage = getConfigFromDB('welcome_message', '¡Hola! 👋 Bienvenido a nuestro servicio automatizado. Escribe *MENU* para ver las opciones disponibles.');
if (!$enabled || !$welcomeMessage) return;
@@ -330,7 +330,7 @@ class BotService {
if (!is_numeric($messageText)) {
$this->whatsappService->sendTextMessage(
$phoneNumber,
- "❌ Por favor, responde solo con el número de la opción deseada o la palabra *menu* para iniciar nuevamente."
+ "❌ Por favor, responde solo con el número de la opción deseada o la palabra *MENU* para iniciar nuevamente."
);
error_log("[BotService] processMenuSelection - non-numeric response from user {$user['id']}: '{$messageText}'");
return;
@@ -467,7 +467,7 @@ class BotService {
* Enviar mensaje por defecto cuando no hay coincidencias
*/
private function sendDefaultNoMatch($phoneNumber) {
- $defaultMessage = "🤖 No entendí tu mensaje. Escribe *menu* para ver las opciones disponibles o *asesor* para obtener ayuda.";
+ $defaultMessage = "🤖 No entendí tu mensaje. Escribe *MENU* para ver las opciones disponibles o *ASESOR* para obtener ayuda.";
$this->whatsappService->sendTextMessage($phoneNumber, $defaultMessage);
}
@@ -511,7 +511,7 @@ class BotService {
// 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]);
// 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. Te hemos puesto en espera por {$minutes} minutos. Si no hay respuesta, podrás volver a usar *menu*." );
+ $this->whatsappService->sendTextMessage($phoneNumber, "🔔 Se ha solicitado un asesor. Te hemos puesto en espera por {$minutes} minutos. Si no hay respuesta, podrás volver a usar *MENU*." );
if (function_exists('writeLog')) writeLog('INFO', "Advisor requested for $phoneNumber until $pausedUntil");
} catch (Exception $e) {
error_log('putOnHold failed: ' . $e->getMessage());
@@ -537,7 +537,7 @@ class BotService {
public function releaseHold($phoneNumber) {
try {
$this->db->update('users', ['on_hold' => 0, 'bot_paused_until' => null, 'advisor_requested' => 0], 'phone_number = :phone', ['phone' => $phoneNumber]);
- $this->whatsappService->sendTextMessage($phoneNumber, "🔔 Tu conversación ha sido retomada por el equipo. Puedes usar *menu* para continuar.");
+ $this->whatsappService->sendTextMessage($phoneNumber, "🔔 Tu conversación ha sido retomada por el equipo. Puedes usar *MENU* para continuar.");
if (function_exists('writeLog')) writeLog('INFO', "Advisor released hold for $phoneNumber");
} catch (Exception $e) {
error_log('releaseHold failed: ' . $e->getMessage());