From b4335f0cb65ebcf556efca195b89adfe5ae568cc Mon Sep 17 00:00:00 2001
From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com>
Date: Tue, 27 Jan 2026 11:06:32 -0500
Subject: [PATCH] Update conversations.php
---
conversations.php | 100 +++++++++++++++++++++++++---------------------
1 file changed, 55 insertions(+), 45 deletions(-)
diff --git a/conversations.php b/conversations.php
index 79ce46a..3e0cc1b 100644
--- a/conversations.php
+++ b/conversations.php
@@ -994,7 +994,8 @@
constructor() {
this.currentConversationId = null;
this.currentUserId = null;
- this.conversations = [];
+ this.conversationsList = []; // Lista de usuarios/conversaciones en el sidebar
+ this.currentMessages = []; // Mensajes de la conversación activa
// Track shown notifications to avoid duplicates from polling
this._shownNotifications = new Set();
// Track notifications the user dismissed/read locally so they don't reappear
@@ -1213,7 +1214,7 @@
if (fileUrl && this.currentUserId && String(this.currentUserId) === String(userId)) {
try { openMediaLightbox(fileUrl, data.file_name || ''); } catch (e) { console.warn('openMediaLightbox failed', e); }
} else if (userId) {
- const conv = this.conversations.find(c => c.user_id == userId);
+ const conv = this.conversationsList.find(c => c.user_id == userId);
if (conv) {
// Prefetch messages then open conversation using preloaded data to avoid double-fetch
try {
@@ -1480,7 +1481,7 @@
const j = await resp.json();
if (j && j.success) {
// update local model and UI
- const m = this.conversations.find(x => x.message_id == mid || x.id == mid);
+ const m = this.currentMessages.find(x => x.message_id == mid || x.id == mid);
if (m) m.reaction_emoji = emoji;
this.updateMessageReactionInView && this.updateMessageReactionInView(mid, emoji);
} else {
@@ -1944,12 +1945,12 @@
for (const m of messages) {
try {
// skip duplicates
- const exists = this.conversations && this.conversations.some(x => (x.message_id && m.message_id && String(x.message_id) === String(m.message_id)) || (x.id && m.id && Number(x.id) === Number(m.id)));
+ const exists = this.currentMessages && this.currentMessages.some(x => (x.message_id && m.message_id && String(x.message_id) === String(m.message_id)) || (x.id && m.id && Number(x.id) === Number(m.id)));
if (exists) continue;
if (nearBottomNow && !this._userScrolling) {
// append directly and mark for render
- this.conversations.push(m);
+ this.currentMessages.push(m);
appended++;
} else {
// stage for later (user is reading history)
@@ -2072,9 +2073,9 @@
}
if (append) {
- this.conversations = this.conversations.concat(items);
+ this.conversationsList = this.conversationsList.concat(items);
} else {
- this.conversations = items;
+ this.conversationsList = items;
}
this.hasMoreConversations = hasMore;
@@ -2103,7 +2104,7 @@
renderConversations() {
const container = document.getElementById('conversation-list');
- if (this.conversations.length === 0) {
+ if (this.conversationsList.length === 0) {
const emptyMsg = this.conversationFilter === 'unread' ? 'No hay conversaciones no leídas' : 'No hay conversaciones aún';
container.innerHTML = `
@@ -2115,7 +2116,7 @@
}
// Ahora el backend devuelve por usuario: last_message, last_time, unread_count y avatar_url
- const html = this.conversations.map(conv => {
+ const html = this.conversationsList.map(conv => {
const isActive = conv.user_id == this.currentUserId ? 'active' : '';
const time = this.formatTime(conv.last_time);
const preview = this.truncateText(conv.last_message || 'Sin mensajes', 50);
@@ -2149,7 +2150,7 @@
// Detectar nuevas notificaciones: comparar unread counts previos
if (!this._prevConversations) this._prevConversations = {};
- this.conversations.forEach(c => {
+ this.conversationsList.forEach(c => {
const prev = this._prevConversations[c.user_id] || { unread_count: 0 };
if (c.unread_count > prev.unread_count && c.user_id != this.currentUserId) {
// nueva notificación
@@ -2250,7 +2251,7 @@
}
getConversationPhone(userId) {
- const conv = this.conversations.find(c => c.user_id === userId);
+ const conv = this.conversationsList.find(c => c.user_id === userId);
if (conv) return conv.phone_number || conv.phone || conv.user_phone || null;
const el = document.getElementById('chat-phone');
return el ? el.textContent.trim() : null;
@@ -2296,7 +2297,7 @@
document.querySelector(`[data-user-id="${userId}"]`)?.classList.add('active');
// Actualizar estado del toggle del bot según datos de la conversación
- const conv = this.conversations.find(c => c.user_id === userId);
+ const conv = this.conversationsList.find(c => c.user_id === userId);
if (conv) {
const btn = document.getElementById('bot-toggle');
const holdIndicator = document.getElementById('hold-indicator');
@@ -2413,7 +2414,7 @@
attendStatus.textContent = '';
// refresh conv reference
- const c = this.conversations.find(x => x.user_id === userId) || conv;
+ const c = this.conversationsList.find(x => x.user_id === userId) || conv;
// Update only the in-chat status message based on server/local state
this.getUserState(userId).then(serverState => {
@@ -2434,7 +2435,7 @@
const toggleAttend = async () => {
try {
- const c = this.conversations.find(x => x.user_id === userId) || conv;
+ const c = this.conversationsList.find(x => x.user_id === userId) || conv;
// Preflight: obtener estado canonico del servidor con cache corta
const serverState = await this.getUserState(userId, true);
@@ -2728,9 +2729,10 @@
this._fullLoadInProgress = true;
const container = document.getElementById('chat-conversations');
- // si cargamos más antiguos, preservar scroll
+ // Guardar posición de scroll y determinar si el usuario estaba en el fondo
let prevScrollHeight = container ? container.scrollHeight : 0;
let prevScrollTop = container ? container.scrollTop : 0;
+ const wasAtBottom = container ? ((container.scrollHeight - container.scrollTop - container.clientHeight) < 150) : true;
// indicador top cuando cargamos anteriores
let topLoader = null;
@@ -2812,7 +2814,7 @@
}
// Si es carga inicial y el servidor devolvió vacío, pero ya teníamos mensajes, preservarlos.
- if (initial && Array.isArray(messages) && messages.length === 0 && Array.isArray(this.conversations) && this.conversations.length > 0) {
+ if (initial && Array.isArray(messages) && messages.length === 0 && Array.isArray(this.currentMessages) && this.currentMessages.length > 0) {
try { if (topLoader && topLoader.parentNode) topLoader.remove(); } catch(e) {}
// Ensure container visibility is restored if it was hidden during render
try { if (container && container.dataset && container.dataset.renderHidden) { container.style.visibility = 'visible'; delete container.dataset.renderHidden; } } catch(e) {}
@@ -2824,13 +2826,13 @@
if (initial) {
// Reemplazar sólo en carga inicial válida (puede estar vacía si no hay nada en DB)
- this.conversations = messages;
+ this.currentMessages = messages;
} else {
// Prepend sólo si hay mensajes nuevos
if (Array.isArray(messages) && messages.length > 0) {
// Evitar introducir duplicados que ya existen en la vista actual
try {
- const existingKeys = new Set((this.conversations || []).map(m => this.messageKey(m)));
+ const existingKeys = new Set((this.currentMessages || []).map(m => this.messageKey(m)));
const beforeLen = messages.length;
messages = messages.filter(m => {
const k = this.messageKey(m);
@@ -2848,7 +2850,7 @@
} catch (e) { console.warn('duplicate filtering failed', e); }
// Prepend mensajes antiguos
- this.conversations = messages.concat(this.conversations || []);
+ this.currentMessages = messages.concat(this.currentMessages || []);
} else {
// No hay mensajes nuevos para añadir; nada que hacer
}
@@ -2860,15 +2862,15 @@
// Update last/latest message timestamp so we can poll deltas later
try {
- if (Array.isArray(this.conversations) && this.conversations.length > 0) {
- const last = this.conversations[this.conversations.length - 1];
+ if (Array.isArray(this.currentMessages) && this.currentMessages.length > 0) {
+ const last = this.currentMessages[this.currentMessages.length - 1];
if (last && last.created_at) this._latestMessage = last.created_at;
}
} catch(e) { /* ignore */ }
// Improved dedupe: pick the most "readable" message when duplicates exist
try {
- if (!this.conversations || this.conversations.length === 0) {
+ if (!this.currentMessages || this.currentMessages.length === 0) {
// Nothing to dedupe
} else {
const best = new Map(); // key => { index, score }
@@ -2897,8 +2899,8 @@
return s;
};
- for (let i = this.conversations.length - 1; i >= 0; i--) {
- const m = this.conversations[i];
+ for (let i = this.currentMessages.length - 1; i >= 0; i--) {
+ const m = this.currentMessages[i];
if (!m) continue;
const mid = m.message_id || m.id || null;
let key = null;
@@ -2931,7 +2933,7 @@
// remove from highest index down
for (let j = uniq.length - 1; j >= 0; j--) {
const idx = uniq[j];
- const removed = this.conversations.splice(idx, 1)[0];
+ const removed = this.currentMessages.splice(idx, 1)[0];
console.debug('Dedupe removed message at index', idx, 'removed=', removed && (removed.message_id || removed.id || removed.content || removed.media_url) );
}
}
@@ -2953,7 +2955,7 @@
// Renderizar incrementalmente para evitar parpadeos y preservar reproducción de media
// Si cargamos paginación (initial=false), pasar la cantidad de mensajes recién obtenidos para insertarlos al inicio
const newlyFetched = (!initial && Array.isArray(messages)) ? messages.length : 0;
- if (this.conversations && this.conversations.length > 0) {
+ if (this.currentMessages && this.currentMessages.length > 0) {
this.renderMessagesIncremental(initial, newlyFetched, follow);
} else if (initial) {
// Si es carga inicial y no tenemos mensajes, mostrar una vista vacía
@@ -2967,8 +2969,17 @@
this._fullLoadInProgress = false;
if (initial) {
- // Scroll to bottom only if the caller requested following behavior
- if (follow) { this.scrollToBottom(); }
+ // Scroll to bottom SOLO si: (1) follow fue solicitado Y (2) el usuario estaba en el fondo O (3) es el primer mensaje
+ const shouldScrollToBottom = follow && (wasAtBottom || this.currentMessages.length === messages.length);
+ if (shouldScrollToBottom) {
+ this.scrollToBottom();
+ } else {
+ // Preservar la posición relativa del scroll
+ if (container && prevScrollHeight > 0) {
+ const scrollPercentage = prevScrollTop / prevScrollHeight;
+ container.scrollTop = container.scrollHeight * scrollPercentage;
+ }
+ }
// Re-show the container (we hid it before rendering to avoid jump-to-top)
try {
if (container && container.dataset && container.dataset.renderHidden) {
@@ -3043,10 +3054,10 @@
// Safety: remove any empty text-only messages that may have slipped into the model
try {
- const before = this.conversations ? this.conversations.length : 0;
- if (Array.isArray(this.conversations) && this.conversations.length > 0) {
- this.conversations = this.conversations.filter(m => !this.isEmptyMessage(m));
- const removed = before - this.conversations.length;
+ const before = this.currentMessages ? this.currentMessages.length : 0;
+ if (Array.isArray(this.currentMessages) && this.currentMessages.length > 0) {
+ this.currentMessages = this.currentMessages.filter(m => !this.isEmptyMessage(m));
+ const removed = before - this.currentMessages.length;
if (removed > 0 && console && console.info) console.info('Removed', removed, 'empty messages before render');
}
} catch (e) { console.warn('Failed to trim empty messages before render', e); }
@@ -3106,8 +3117,8 @@
// calculate near-bottom late to decide scrolling after DOM updates (avoids stale value)
- for (let i = 0; i < this.conversations.length; i++) {
- const msg = this.conversations[i];
+ for (let i = 0; i < this.currentMessages.length; i++) {
+ const msg = this.currentMessages[i];
const mid = String(msg.message_id || msg.id || ('local_' + i));
const existingEl = existing.get(mid);
@@ -3126,7 +3137,7 @@
try {
const rp = existingEl.querySelector('.reply-preview');
if (msg.reply_to_message_id) {
- const previewSrc = (this.conversations.find(m => m.message_id == msg.reply_to_message_id) || {}).content || ('Mensaje ' + msg.reply_to_message_id);
+ const previewSrc = (this.currentMessages.find(m => m.message_id == msg.reply_to_message_id) || {}).content || ('Mensaje ' + msg.reply_to_message_id);
if (rp) rp.textContent = 'En respuesta a: ' + previewSrc.substring(0,140);
else {
const div = document.createElement('div'); div.className = 'reply-preview'; div.textContent = 'En respuesta a: ' + previewSrc.substring(0,140); existingEl.insertBefore(div, existingEl.firstChild);
@@ -3249,7 +3260,7 @@
} catch (e) { /* ignore staging errors */ }
// insert date separator if this message is the first of a new day
- const prev = (i>0) ? this.conversations[i-1] : null;
+ const prev = (i>0) ? this.currentMessages[i-1] : null;
if (!prev || !sameDay(prev.created_at, msg.created_at)) {
const sep = document.createElement('div');
sep.className = 'date-sep';
@@ -3266,7 +3277,7 @@
let replyHtml = '';
if (msg.reply_to_message_id) {
- const target = this.conversations.find(m => m.message_id == msg.reply_to_message_id || m.id == msg.reply_to_message_id);
+ const target = this.currentMessages.find(m => m.message_id == msg.reply_to_message_id || m.id == msg.reply_to_message_id);
const previewText = target ? (target.content || target.message_text || '').substring(0,140) : ('Mensaje ' + msg.reply_to_message_id);
replyHtml = `
En respuesta a: ${window.escapeHtml(previewText)}
`;
}
@@ -3748,7 +3759,7 @@
// Append staged messages to conversation model
const toApplyCount = this._stagedMessages.length;
console.debug('applyStagedMessages: applying', toApplyCount, 'messages');
- this._stagedMessages.forEach(m => { this.conversations.push(m); });
+ this._stagedMessages.forEach(m => { this.currentMessages.push(m); });
// clear staged storage
this._stagedMessages = [];
@@ -3760,7 +3771,7 @@
this.scrollToBottom();
// Update latest timestamp
- try { const last = this.conversations[this.conversations.length - 1]; if (last && last.created_at) this._latestMessage = last.created_at; } catch(e){}
+ try { const last = this.currentMessages[this.currentMessages.length - 1]; if (last && last.created_at) this._latestMessage = last.created_at; } catch(e){}
} catch (e) {
console.warn('applyStagedMessages failed', e);
@@ -3901,7 +3912,7 @@
// If the template expects a named parameter `name`, provide it using user name or phone
if (templateName === 'contacto_nuevo') {
// Try to find conversation info
- let conv = this.conversations.find(c => c.user_id === this.currentUserId) || {};
+ let conv = this.conversationsList.find(c => c.user_id === this.currentUserId) || {};
let resolvedName = (conv.name || conv.full_name || '').trim();
if (!resolvedName) {
const nameEl = document.getElementById('chat-name-text') || document.getElementById('chat-name');
@@ -3982,7 +3993,7 @@
msg.message_type = options.message_type;
}
- this.conversations.push(msg);
+ this.currentMessages.push(msg);
this.renderMessagesIncremental();
this.scrollToBottom();
}
@@ -3992,7 +4003,7 @@
const preview = document.getElementById('reply-preview');
const previewText = document.getElementById('reply-preview-text');
const cancelBtn = document.getElementById('cancel-reply-btn');
- let msg = this.conversations.find(m => m.message_id == messageId || m.id == messageId);
+ let msg = this.currentMessages.find(m => m.message_id == messageId || m.id == messageId);
const setPreviewFromMsg = (m) => {
const text = m ? (m.content || m.message_text || (m.caption || '[Mensaje]')) : ('Mensaje ' + messageId);
@@ -4317,11 +4328,10 @@
}
// 2. Enviar mensaje con el archivo
- const user = this.conversations.find(c => c.user_id === this.currentUserId);
- const phone = user ? user.phone_number : null;
+ const phone = this.getConversationPhone(this.currentUserId);
if (!phone) {
- throw new Error('No se encontró el número de teléfono');
+ throw new Error('No se encontró el número de teléfono del usuario');
}
const sendResponse = await fetch('api/send_media_message.php', {