This commit is contained in:
Lizandro Guarnizo
2026-01-28 02:23:36 -05:00
parent 37d6bf1e20
commit 6ebf5c9a22
30 changed files with 2264 additions and 118 deletions
+115 -58
View File
@@ -1425,6 +1425,9 @@ if (!isset($_SESSION['user_id'])) {
// Solo incrementar unread_count si no es la conversación activa
if (String(this.currentUserId) !== String(userId)) {
conv.unread_count = (conv.unread_count || 0) + 1;
} else {
// Si es la conversación activa, mantener unread_count en 0
conv.unread_count = 0;
}
// Mover al inicio de la lista
@@ -1445,8 +1448,7 @@ if (!isset($_SESSION['user_id'])) {
// Re-renderizar solo la lista de conversaciones
console.log('🔄 Re-renderizando lista de conversaciones...');
console.log('🔍 Verificando this:', this);
console.log('🔍 this.renderConversations existe?', typeof this.renderConversations);
console.log('🔍 Filtro activo:', this.conversationFilter);
console.log('🔍 this.conversations:', this.conversations);
if (typeof this.renderConversations === 'function') {
@@ -2252,8 +2254,14 @@ if (!isset($_SESSION['user_id'])) {
filterSelect.value = this.conversationFilter;
filterSelect.addEventListener('change', (e) => {
this.conversationFilter = e.target.value || 'all';
// reload conversations from first page
this.loadConversations(1, false);
console.log('🔍 Filtro cambiado a:', this.conversationFilter);
// Re-renderizar la lista actual sin recargar desde el servidor
this.renderConversations();
// También recargar desde el servidor para asegurar datos actualizados
// (pero esto es secundario y puede ser en background)
this.loadConversations(1, false).catch(err => {
console.warn('Error recargando conversaciones después de cambiar filtro:', err);
});
});
}
}
@@ -2505,8 +2513,16 @@ if (!isset($_SESSION['user_id'])) {
}
console.log('✅ Container encontrado:', container);
console.log('🔍 Filtro activo:', this.conversationFilter);
if (this.conversations.length === 0) {
// Aplicar filtro de no leídos
let conversationsToShow = this.conversations;
if (this.conversationFilter === 'unread') {
conversationsToShow = this.conversations.filter(c => (c.unread_count || 0) > 0);
console.log(`🔍 Filtro 'unread' aplicado: ${conversationsToShow.length} de ${this.conversations.length} conversaciones`);
}
if (conversationsToShow.length === 0) {
const emptyMsg = this.conversationFilter === 'unread' ? 'No hay conversaciones no leídas' : 'No hay conversaciones aún';
container.innerHTML = `
<div class="text-center p-4">
@@ -2518,13 +2534,35 @@ if (!isset($_SESSION['user_id'])) {
return;
}
console.log('📋 Generando HTML para', this.conversations.length, 'conversaciones');
console.log('📋 Generando HTML para', conversationsToShow.length, 'conversaciones');
// Ahora el backend devuelve por usuario: last_message, last_time, unread_count y avatar_url
const html = this.conversations.map(conv => {
const html = conversationsToShow.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);
// Mostrar miniatura si el último mensaje es multimedia y tiene archivo local
let preview = '';
const hasLocalMedia = conv.last_local_file || conv.last_local_thumb;
const isMediaType = conv.message_type && ['image', 'video', 'audio', 'document'].includes(conv.message_type);
if (hasLocalMedia && isMediaType) {
const thumbSrc = conv.last_local_thumb || conv.last_local_file;
const mediaIcon = {
'image': '🖼️',
'video': '🎥',
'audio': '🎵',
'document': '📎'
}[conv.message_type] || '📎';
if (conv.message_type === 'image' || conv.message_type === 'video') {
preview = `<img src="/${thumbSrc}" alt="media" style="width:40px; height:40px; object-fit:cover; border-radius:4px; margin-right:8px; vertical-align:middle;"> ${mediaIcon} ${conv.last_message || 'Multimedia'}`;
} else {
preview = `${mediaIcon} ${conv.last_message || 'Archivo'}`;
}
} else {
preview = this.truncateText(conv.last_message || 'Sin mensajes', 50);
}
const avatar = conv.avatar_url ? `<img src="${conv.avatar_url}" alt="avatar" class="conversation-avatar-img">` : `<div class="conversation-avatar">${this.getInitials(conv.name)}</div>`;
@@ -2555,7 +2593,7 @@ if (!isset($_SESSION['user_id'])) {
console.log('📄 HTML generado, longitud:', html.length, 'caracteres');
// Detectar nuevas notificaciones: comparar unread counts previos
// Detectar nuevas notificaciones: comparar unread counts previos (usar todas las conversaciones, no solo las filtradas)
if (!this._prevConversations) this._prevConversations = {};
this.conversations.forEach(c => {
const prev = this._prevConversations[c.user_id] || { unread_count: 0 };
@@ -2690,6 +2728,14 @@ if (!isset($_SESSION['user_id'])) {
// Clear any staged messages from a previous conversation and hide indicator
try { if (this._stagedMessageIds) { this._stagedMessageIds.clear(); this._stagedCount = 0; this.hideNewMessagesIndicator(); } } catch(e) {}
// Actualizar inmediatamente el contador de no leídos a 0 en la lista local
const conv = this.conversations.find(c => c.user_id === userId);
if (conv) {
conv.unread_count = 0;
// Re-renderizar la lista para reflejar el cambio inmediatamente
this.renderConversations();
}
// Actualizar UI
document.getElementById('no-conversation').style.display = 'none';
document.getElementById('chat-area').style.display = 'flex';
@@ -2706,8 +2752,7 @@ if (!isset($_SESSION['user_id'])) {
});
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);
// Actualizar estado del toggle del bot según datos de la conversación (ya obtuvimos conv arriba)
if (conv) {
const btn = document.getElementById('bot-toggle');
const holdIndicator = document.getElementById('hold-indicator');
@@ -4744,6 +4789,7 @@ if (!isset($_SESSION['user_id'])) {
const uploadResponse = await fetch('api/upload_media.php', {
method: 'POST',
credentials: 'same-origin', // Enviar cookies de sesión
body: formData
});
const uploadContentType = uploadResponse.headers.get('content-type') || '';
@@ -4769,6 +4815,7 @@ if (!isset($_SESSION['user_id'])) {
const sendResponse = await fetch('api/send_media_message.php', {
method: 'POST',
credentials: 'same-origin', // Enviar cookies de sesión
headers: {
'Content-Type': 'application/json'
},
@@ -4834,9 +4881,13 @@ if (!isset($_SESSION['user_id'])) {
}
// Éxito
console.log('✅ Archivo enviado exitosamente, recargando mensajes...');
this.cancelMediaUpload();
await this.loadconversations(this.currentUserId, false);
// SSE actualizará las conversaciones automáticamente
// Recargar mensajes para mostrar el multimedia recién enviado
await this.loadMessages(this.currentUserId, true, true);
// SSE actualizará las conversaciones en la lista automáticamente
// this.loadConversations();
} catch (error) {
@@ -4867,27 +4918,60 @@ if (!isset($_SESSION['user_id'])) {
renderMediaMessage(message) {
if (!message) return '';
const mediaType = message.message_type || message.media_type || 'text';
const mediaUrl = message.media_url || message.media_url_external || '';
const mediaUrl = message.media_url || '';
const mediaUrlExternal = message.media_url_external || '';
const caption = message.content || '';
const localFile = message.local_file || '';
const localThumb = message.local_thumb || '';
const thumb = localThumb ? (`/${localThumb}`) : (localFile ? (`/${localFile}`) : (message.media_url_external || mediaUrl));
const messageId = message.id || message.message_id || '';
// Debug completo del mensaje
if (mediaType === 'audio' || mediaType === 'image' || mediaType === 'video') {
console.log(`🎵 Media message (${mediaType}) - Datos completos:`, {
id: messageId,
localFile: localFile,
localThumb: localThumb,
mediaUrl: mediaUrl,
mediaUrlExternal: mediaUrlExternal,
hasLocalFile: !!localFile,
messageType: mediaType
});
}
// Prioridad: archivo local > proxy con DB id > media_url_external > URL externa
let full = '';
let thumb = '';
if (localFile) {
full = `/api/version/media-url.php?local=${encodeURIComponent(localFile)}`;
} else if (message.media_url_external) {
if (message.media_url_external.indexOf('api/get_media.php?id=') !== -1) {
const parts = message.media_url_external.split('id=');
const mid = parts[1] ? decodeURIComponent(parts[1]) : '';
full = `/api/version/media-url.php?id=${encodeURIComponent(mid)}`;
} else if (/^https?:\/\//i.test(message.media_url_external)) {
full = message.media_url_external;
} else {
full = `/api/version/media-url.php?url=${encodeURIComponent(message.media_url_external)}`;
}
// ✅ PRIORIDAD 1: Archivo local guardado
full = `/${localFile}`;
thumb = localThumb ? `/${localThumb}` : `/${localFile}`;
console.log('✅ Usando archivo local:', full);
} else if (messageId && !mediaUrlExternal) {
// ⚠️ PRIORIDAD 2: Usar proxy con el ID de la base de datos solo si no hay media_url_external
full = `/api/version/media-url.php?id=${encodeURIComponent(messageId)}`;
thumb = full;
console.log('⚠️ No hay local_file, usando proxy con messageId:', messageId);
} else if (mediaUrlExternal) {
// 🔄 PRIORIDAD 3: Usar media_url_external (puede ser api/get_media.php o URL directa)
full = mediaUrlExternal.startsWith('/') ? mediaUrlExternal : `/${mediaUrlExternal}`;
thumb = full;
console.log('🔄 Usando media_url_external:', full);
} else if (mediaUrl && /^\d+$/.test(mediaUrl)) {
// 📱 PRIORIDAD 4: Es un ID de WhatsApp (solo números)
full = `/api/get_media.php?id=${encodeURIComponent(mediaUrl)}`;
thumb = full;
console.log('📱 Usando whatsapp_media_id con get_media.php:', mediaUrl);
} else if (mediaUrl && /^https?:\/\//i.test(mediaUrl)) {
// 🌐 PRIORIDAD 5: URL externa directa (puede estar caducada)
full = mediaUrl;
thumb = mediaUrl;
console.log('🌐 Usando URL externa:', full);
} else {
full = mediaUrl || '';
// ❌ Fallback: sin media disponible
console.warn('❌ No hay fuente de media disponible para el mensaje', messageId);
full = '';
thumb = '';
}
switch (mediaType) {
@@ -4913,22 +4997,8 @@ if (!isset($_SESSION['user_id'])) {
${caption ? `<div>${escapeHtml(caption)}</div>` : ''}
`;
case 'audio': {
let audioSrc = '';
if (localFile) {
audioSrc = `/${localFile}`;
} else if (message.media_url_external) {
if (message.media_url_external.indexOf('api/get_media.php?id=') !== -1) {
const parts = message.media_url_external.split('id=');
const mid = parts[1] ? decodeURIComponent(parts[1]) : '';
audioSrc = `/api/version/media-url.php?id=${encodeURIComponent(mid)}`;
} else if (/^https?:\/\//i.test(message.media_url_external)) {
audioSrc = message.media_url_external;
} else {
audioSrc = `/api/version/media-url.php?url=${encodeURIComponent(message.media_url_external)}`;
}
} else {
audioSrc = mediaUrl;
}
// Para audio, usar la misma lógica de full
let audioSrc = full;
return `
<div class="message-media">
<audio controls>
@@ -4939,20 +5009,7 @@ if (!isset($_SESSION['user_id'])) {
`;
}
case 'document': {
let docUrl = '';
if (localFile) {
docUrl = `/api/version/media-url.php?local=${encodeURIComponent(localFile)}&download=1`;
} else if (message.media_url_external) {
if (message.media_url_external.indexOf('api/get_media.php?id=') !== -1) {
const parts = message.media_url_external.split('id=');
const mid = parts[1] ? decodeURIComponent(parts[1]) : '';
docUrl = `/api/version/media-url.php?id=${encodeURIComponent(mid)}&download=1`;
} else if (/^https?:\/\//i.test(message.media_url_external)) {
docUrl = `/api/version/media-url.php?url=${encodeURIComponent(message.media_url_external)}&download=1`;
} else {
docUrl = `/api/version/media-url.php?url=${encodeURIComponent(message.media_url_external)}&download=1`;
}
}
let docUrl = full;
if (!docUrl) {
return `
<div class="message-document disabled">