This commit is contained in:
Lizandro Guarnizo
2026-02-03 21:30:55 -05:00
parent 44423adb32
commit 1aae148396
89 changed files with 8581 additions and 7687 deletions
+759 -25
View File
@@ -904,6 +904,10 @@ if (!isset($_SESSION['user_id'])) {
<div id="bot-toggle-container" style="display:flex;align-items:center;gap:6px;margin-right:8px;">
<button class="btn btn-sm btn-outline-secondary" id="bot-toggle">Bot: On</button>
</div>
<!-- Botón programar recordatorio -->
<button class="btn btn-sm btn-success" id="schedule-reminder-btn" title="Programar recordatorio">
<i class="fas fa-calendar-plus"></i>
</button>
<!-- Botón actualizar mensajes -->
<button class="btn btn-sm btn-outline-light" id="refresh-messages-btn" title="Actualizar mensajes"><i class="fas fa-sync-alt"></i></button>
<button class="btn btn-sm btn-outline-secondary" id="mark-unread-btn" title="Marcar conversación como no leída" style="display:none;">Marcar no leído</button>
@@ -1010,9 +1014,9 @@ if (!isset($_SESSION['user_id'])) {
// ESTE LOG DEBE APARECER PRIMERO
console.clear();
console.log('%c═══════════════════════════════════════════', 'color: #25d366; font-weight: bold;');
console.log('%c🚀 WHATSAPP BOT v1.2.0 - Desarrollado por U-Site.app', 'background: #25d366; color: white; padding: 10px 20px; font-size: 18px; font-weight: bold; border-radius: 5px;');
console.log('%c🚀 WHATSAPP BOT v1.3.0 - Desarrollado por U-Site.app', 'background: #25d366; color: white; padding: 10px 20px; font-size: 18px; font-weight: bold; border-radius: 5px;');
console.log('%c═══════════════════════════════════════════', 'color: #25d366; font-weight: bold;');
window.__APP_VERSION__ = '1.2.0';
window.__APP_VERSION__ = '1.3.0';
</script>
<?php
@@ -1030,10 +1034,10 @@ if (!isset($_SESSION['user_id'])) {
<script src="assets/js/chat-common.js?v=<?php echo $asset_v; ?>"></script>
<script>
// ============================================
// 🚀 VERSIÓN ACTUALIZADA - 27 ENERO 2026
// 🚀 VERSIÓN ACTUALIZADA - 3 FEBRERO 2026
// ============================================
console.log('%c📦 Asset Version:', 'font-weight: bold; color: #2575fc;', '<?php echo $asset_v; ?>');
console.log('%c✨ Cambios: SSE mejorado, updateConversationInList con logging completo', 'color: #666;');
console.log('%c✨ Cambios: Gestión de usuarios del sistema, búsqueda mejorada en BD, plantillas optimizadas', 'color: #666;');
console.log('============================================');
// Fallback ligero para showAlert (si no existe una implementación global)
@@ -1783,9 +1787,13 @@ if (!isset($_SESSION['user_id'])) {
}
setupEventListeners() {
// Búsqueda de conversaciones
// Búsqueda de conversaciones con debounce
let searchTimeout;
document.getElementById('search-input').addEventListener('input', (e) => {
this.searchConversations(e.target.value);
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
this.searchConversations(e.target.value);
}, 500); // Esperar 500ms después de que el usuario deje de escribir
});
// Envío de mensajes
@@ -2257,6 +2265,30 @@ if (!isset($_SESSION['user_id'])) {
});
});
}
// Botón de programar recordatorio
const reminderBtn = document.getElementById('schedule-reminder-btn');
if (reminderBtn) {
reminderBtn.addEventListener('click', () => this.showReminderModal());
}
// Botón de guardar recordatorio
const saveReminderBtn = document.getElementById('save-reminder-btn');
if (saveReminderBtn) {
saveReminderBtn.addEventListener('click', () => this.saveReminder());
}
// Selector de plantilla en recordatorio
const reminderTemplate = document.getElementById('reminder-template');
if (reminderTemplate) {
reminderTemplate.addEventListener('change', () => this.handleReminderTemplateChange());
}
// Botón de enviar plantilla con variables
const sendTemplateWithVarsBtn = document.getElementById('send-template-with-vars-btn');
if (sendTemplateWithVarsBtn) {
sendTemplateWithVarsBtn.addEventListener('click', () => this.sendTemplateWithVariables());
}
}
setupAutoRefresh() {
@@ -4397,6 +4429,309 @@ if (!isset($_SESSION['user_id'])) {
console.warn('updateMessageReactionInView failed', e);
}
}
// ========== FUNCIONES DE RECORDATORIO ==========
async showReminderModal() {
console.log('🔵 showReminderModal - currentUserId:', this.currentUserId);
console.log('📋 conversations:', this.conversations);
if (!this.currentUserId) {
alert('Selecciona una conversación primero');
return;
}
// Buscar usuario en conversaciones - probar múltiples formas
let user = this.conversations.find(c => c.id == this.currentUserId);
if (!user) {
user = this.conversations.find(c => c.user_id == this.currentUserId);
}
console.log('👤 Usuario encontrado:', user);
if (user) {
const userName = user.name || user.user_name || 'Sin nombre';
const phoneNumber = user.phone_number || user.phone || 'Sin teléfono';
document.getElementById('reminder-user-name').value = `${userName} (${phoneNumber})`;
console.log('✅ Usuario establecido:', userName, phoneNumber);
} else {
console.error('❌ Usuario no encontrado en conversaciones');
// Intentar obtener desde el DOM
const nameEl = document.getElementById('chat-name-text') || document.getElementById('chat-name');
const phoneEl = document.getElementById('chat-phone');
if (nameEl && phoneEl) {
document.getElementById('reminder-user-name').value = `${nameEl.textContent} (${phoneEl.textContent})`;
console.log('✅ Usuario obtenido del DOM');
} else {
document.getElementById('reminder-user-name').value = 'Usuario actual';
console.warn('⚠️ No se pudo obtener nombre del usuario');
}
}
// Establecer fecha mínima (hoy)
const today = new Date().toISOString().split('T')[0];
document.getElementById('reminder-date').setAttribute('min', today);
document.getElementById('reminder-date').value = today;
// Cargar plantillas aprobadas
await this.loadReminderTemplates();
// Mostrar modal
const modal = new bootstrap.Modal(document.getElementById('reminderModal'));
modal.show();
}
async loadReminderTemplates() {
try {
const response = await this.apiCall('get_templates.php?approved_only=1');
const select = document.getElementById('reminder-template');
if (response && response.success && response.data) {
select.innerHTML = '<option value="">Seleccionar plantilla...</option>';
response.data.forEach(template => {
const option = document.createElement('option');
option.value = template.id;
option.textContent = `${template.name} (${template.language_code})`;
option.dataset.templateId = template.id;
option.dataset.templateName = template.template_name;
option.dataset.language = template.language_code;
option.dataset.bodyText = template.body_text || '';
// Detectar variables tanto numéricas como con nombres
option.dataset.hasVariables = template.body_text && /\{\{[^\}]+\}\}/.test(template.body_text);
select.appendChild(option);
});
}
} catch (error) {
console.error('Error loading templates:', error);
}
}
async handleReminderTemplateChange() {
const select = document.getElementById('reminder-template');
const selectedOption = select.options[select.selectedIndex];
console.log('🔵 handleReminderTemplateChange llamado');
console.log('📋 Option seleccionada:', selectedOption);
if (!selectedOption || !selectedOption.value) {
document.getElementById('reminder-variables-container').style.display = 'none';
document.getElementById('reminder-preview').style.display = 'none';
return;
}
const hasVariables = selectedOption.dataset.hasVariables === 'true';
const bodyText = selectedOption.dataset.bodyText || '';
console.log('🔍 hasVariables:', hasVariables);
console.log('📝 bodyText:', bodyText);
if (hasVariables) {
// Usar get_template_details.php para obtener variables con sus metadatos
try {
const templateId = selectedOption.dataset.templateId;
const detailsResp = await this.apiCall(`get_template_details.php?id=${templateId}`);
if (detailsResp && detailsResp.success && detailsResp.template) {
const variables = detailsResp.template.variables || [];
console.log('✅ Variables obtenidas de API:', variables);
// Generar campos
const fieldsContainer = document.getElementById('reminder-variables-fields');
fieldsContainer.innerHTML = '';
variables.forEach(v => {
const div = document.createElement('div');
div.className = 'mb-2';
div.innerHTML = `
<label class="form-label small">${v.label}</label>
<input type="text"
class="form-control form-control-sm reminder-variable-input"
data-index="${v.index}"
data-placeholder="${v.placeholder || ''}"
placeholder="${v.example || 'Ingrese valor...'}"
required>
`;
fieldsContainer.appendChild(div);
});
// Event listeners para actualizar preview
fieldsContainer.querySelectorAll('.reminder-variable-input').forEach(input => {
input.addEventListener('input', () => this.updateReminderPreview());
});
}
} catch (error) {
console.error('Error obteniendo detalles de plantilla:', error);
}
document.getElementById('reminder-variables-container').style.display = 'block';
} else {
document.getElementById('reminder-variables-container').style.display = 'none';
}
// Actualizar preview
this.updateReminderPreview();
}
updateReminderPreview() {
const select = document.getElementById('reminder-template');
const selectedOption = select.options[select.selectedIndex];
if (!selectedOption || !selectedOption.value) {
document.getElementById('reminder-preview').style.display = 'none';
return;
}
let bodyText = selectedOption.dataset.bodyText || '';
const hasVariables = selectedOption.dataset.hasVariables === 'true';
if (hasVariables) {
const inputs = document.querySelectorAll('.reminder-variable-input');
inputs.forEach(input => {
const index = input.dataset.index;
const value = input.value || `{{${index}}}`;
bodyText = bodyText.replace(new RegExp(`\\{\\{${index}\\}\\}`, 'g'), value);
});
}
const previewContainer = document.getElementById('reminder-preview-content');
previewContainer.innerHTML = bodyText.replace(/\n/g, '<br>');
document.getElementById('reminder-preview').style.display = 'block';
}
async saveReminder() {
if (!this.currentUserId) return;
const date = document.getElementById('reminder-date').value;
const time = document.getElementById('reminder-time').value;
const select = document.getElementById('reminder-template');
const selectedOption = select.options[select.selectedIndex];
if (!date || !time) {
alert('Por favor completa fecha y hora');
return;
}
if (!selectedOption || !selectedOption.value) {
alert('Por favor selecciona una plantilla');
return;
}
// Recopilar parámetros si hay variables
const hasVariables = selectedOption.dataset.hasVariables === 'true' || selectedOption.dataset.hasVariables === true;
console.log('🔍 hasVariables:', hasVariables, 'tipo:', typeof selectedOption.dataset.hasVariables);
let parameters = null;
if (hasVariables) {
const inputs = document.querySelectorAll('.reminder-variable-input');
console.log('📝 Inputs encontrados:', inputs.length);
if (inputs.length === 0) {
console.log('⚠️ No hay inputs de variables');
} else {
// Detectar tipo de variables usando el placeholder del primer input
const firstInput = inputs[0];
const placeholder = firstInput?.dataset?.placeholder || '';
const isNumericVar = /^\{\{\d+\}\}$/.test(placeholder);
console.log('🔍 Detectando tipo de variables en recordatorio:');
console.log(' - Placeholder ejemplo:', placeholder);
console.log(' - Es numérica?', isNumericVar);
let allFilled = true;
if (isNumericVar) {
// Variables numéricas: crear array ordenado
const tempObj = {};
inputs.forEach(input => {
const value = input.value.trim();
const index = parseInt(input.dataset.index || input.dataset.var);
if (!value) {
allFilled = false;
input.classList.add('is-invalid');
} else {
input.classList.remove('is-invalid');
tempObj[index] = value;
}
});
// Convertir a array ordenado
const sortedKeys = Object.keys(tempObj).map(Number).sort((a, b) => a - b);
parameters = sortedKeys.map(key => tempObj[key]);
console.log('📊 Enviando como array ordenado:', parameters);
} else {
// Variables con nombres: crear objeto
parameters = {};
inputs.forEach(input => {
const value = input.value.trim();
const placeholder = input.dataset.placeholder || '';
const varName = placeholder.replace(/\{\{|\}\}/g, '');
if (!value) {
allFilled = false;
input.classList.add('is-invalid');
} else {
input.classList.remove('is-invalid');
parameters[varName] = value;
}
});
console.log('📦 Enviando como objeto con nombres:', parameters);
}
if (!allFilled) {
alert('Por favor completa todas las variables');
return;
}
}
} else {
console.log('⚠️ Plantilla sin variables');
}
const templateId = selectedOption.dataset.templateId;
const templateName = selectedOption.dataset.templateName;
const language = selectedOption.dataset.language;
const payload = {
user_id: parseInt(this.currentUserId),
message_type: 'template',
template_id: parseInt(templateId),
template_name: templateName,
template_language: language,
template_parameters: parameters,
scheduled_date: date,
scheduled_time: time
};
console.log('💾 Guardando recordatorio:', payload);
console.log('📊 Parámetros detalle:', {
esNull: parameters === null,
esArray: Array.isArray(parameters),
longitud: parameters ? parameters.length : 0,
valores: parameters
});
try {
const response = await this.apiCall('schedule_message.php', {
body: payload
});
console.log('✅ Respuesta del servidor:', response);
if (response && response.success) {
alert('✅ Recordatorio programado exitosamente');
bootstrap.Modal.getInstance(document.getElementById('reminderModal')).hide();
// Limpiar formulario
document.getElementById('reminder-template').value = '';
document.getElementById('reminder-variables-container').style.display = 'none';
document.getElementById('reminder-preview').style.display = 'none';
} else {
alert('Error: ' + (response.error || 'Error desconocido'));
}
} catch (error) {
console.error('Error saving reminder:', error);
alert('Error al programar recordatorio: ' + error.message);
}
}
async loadQuickReplies() {
const panel = document.getElementById('quick-replies-panel');
@@ -4474,8 +4809,210 @@ if (!isset($_SESSION['user_id'])) {
}
async sendTemplateQuick(templateName, language) {
// delegate to unified sendTemplateMessage helper
await this.sendTemplateMessage(templateName, language, null);
console.log('🔵 sendTemplateQuick:', templateName, language);
// Primero, cargar detalles de la plantilla para verificar si tiene variables
try {
// Buscar el template_id desde la caché o hacer una petición
const templatesResp = await this.apiCall('get_templates.php?approved_only=1');
const template = templatesResp.data.find(t => t.template_name === templateName);
if (!template) {
throw new Error('Plantilla no encontrada');
}
console.log('📋 Template encontrado:', template);
// Verificar si tiene variables (tanto numéricas como con nombres)
const hasVariables = template.body_text && /\{\{[^\}]+\}\}/.test(template.body_text);
console.log('🔍 Tiene variables?', hasVariables);
if (hasVariables) {
// Mostrar modal para pedir variables
await this.showTemplateVariablesModal(template);
} else {
// Enviar directamente sin variables
await this.sendTemplateMessage(templateName, language, null);
}
} catch (error) {
console.error('❌ Error en sendTemplateQuick:', error);
alert('Error al cargar plantilla: ' + error.message);
}
}
async showTemplateVariablesModal(template) {
console.log('🔵 showTemplateVariablesModal:', template);
// Guardar template actual para usarlo al enviar
this.currentTemplate = template;
// Establecer nombre de plantilla en modal
document.getElementById('template-modal-name').textContent = template.name || template.template_name;
try {
// Usar get_template_details.php para obtener variables con metadatos
const detailsResp = await this.apiCall(`get_template_details.php?id=${template.id}`);
if (!detailsResp || !detailsResp.success) {
throw new Error('No se pudieron cargar los detalles de la plantilla');
}
const variables = detailsResp.template.variables || [];
console.log('📝 Variables obtenidas:', variables);
// Guardar template completo con variables para usarlo después
this.currentTemplate = { ...template, variables: variables };
// Generar campos de entrada
const container = document.getElementById('template-variables-container');
let html = '';
variables.forEach((variable) => {
html += `
<div class="mb-3">
<label class="form-label"><strong>${variable.label}</strong>
${variable.example ? `<small class="text-muted">(ej: ${variable.example})</small>` : ''}
</label>
<input
type="text"
class="form-control template-var-input"
data-var="${variable.index}"
data-placeholder="${variable.placeholder}"
placeholder="${variable.example || 'Ingrese valor...'}"
onkeyup="window.chatApp.updateTemplatePreview()"
required
>
</div>
`;
});
container.innerHTML = html;
// Generar vista previa inicial
this.updateTemplatePreview();
// Mostrar modal
const modal = new bootstrap.Modal(document.getElementById('templateVariablesModal'));
modal.show();
} catch (error) {
console.error('❌ Error cargando detalles de plantilla:', error);
alert('Error al cargar plantilla: ' + error.message);
}
}
async updateTemplatePreview() {
if (!this.currentTemplate) return;
const previewContainer = document.getElementById('template-preview');
const previewContent = document.getElementById('template-preview-content');
// Recopilar parámetros
const inputs = document.querySelectorAll('.template-var-input');
const parameters = [];
inputs.forEach(input => {
const varIndex = parseInt(input.dataset.var) - 1;
parameters[varIndex] = input.value || '';
});
try {
// Usar preview_template.php para renderizar correctamente
const response = await this.apiCall('preview_template.php', {
body: {
template_id: this.currentTemplate.id,
parameters: parameters
}
});
if (response && response.success && response.preview) {
previewContent.innerHTML = response.preview.html || response.preview.body.replace(/\n/g, '<br>');
previewContainer.style.display = 'block';
}
} catch (error) {
console.error('Error generando preview:', error);
// Fallback: mostrar body_text sin procesar
previewContent.innerHTML = this.currentTemplate.body_text.replace(/\n/g, '<br>');
previewContainer.style.display = 'block';
}
}
async sendTemplateWithVariables() {
console.log('🔵 sendTemplateWithVariables');
if (!this.currentTemplate) {
alert('No hay plantilla seleccionada');
return;
}
// Recopilar variables
const inputs = document.querySelectorAll('.template-var-input');
let allFilled = true;
// Detectar si son variables numéricas o con nombres usando el placeholder
const firstInput = inputs[0];
const placeholder = firstInput?.dataset?.placeholder || '';
// Si el placeholder es {{1}}, {{2}}, etc. -> numéricas
// Si es {{nombre_tema}}, {{fecha}}, etc. -> con nombres
const isNumericVar = /^\{\{\d+\}\}$/.test(placeholder);
console.log('🔍 Detectando tipo de variables:');
console.log(' - Placeholder ejemplo:', placeholder);
console.log(' - Es numérica?', isNumericVar);
let parameters;
if (isNumericVar) {
// Variables numéricas {{1}}, {{2}} - usar array
parameters = [];
inputs.forEach(input => {
const value = input.value.trim();
if (!value) {
allFilled = false;
input.classList.add('is-invalid');
} else {
input.classList.remove('is-invalid');
parameters.push(value);
}
});
console.log('📊 Enviando como array:', parameters);
} else {
// Variables con nombres {{fecha}}, {{motivo}} - usar objeto
parameters = {};
inputs.forEach(input => {
const value = input.value.trim();
const placeholder = input.dataset.placeholder || '';
// Extraer nombre de {{fecha}} -> "fecha"
const varName = placeholder.replace(/\{\{|\}\}/g, '');
if (!value) {
allFilled = false;
input.classList.add('is-invalid');
} else {
input.classList.remove('is-invalid');
parameters[varName] = value;
}
});
console.log('📦 Enviando como objeto:', parameters);
}
if (!allFilled) {
alert('Por favor completa todas las variables');
return;
}
console.log('📝 Parámetros finales:', parameters);
// Cerrar modal
const modal = bootstrap.Modal.getInstance(document.getElementById('templateVariablesModal'));
if (modal) modal.hide();
// Enviar plantilla con parámetros
await this.sendTemplateMessage(
this.currentTemplate.template_name,
this.currentTemplate.language_code || 'es',
parameters
);
// Limpiar template actual
this.currentTemplate = null;
}
async sendTemplateMessage(templateName, language = 'es', parameters = null) {
@@ -4547,10 +5084,49 @@ if (!isset($_SESSION['user_id'])) {
async sendQuickReply(text) {
if (!text) return;
if (!this.currentUserId) return;
const input = document.getElementById('message-input');
if (!input) return;
input.value = text;
await this.sendMessage();
// No copiar al input para preservar saltos de línea
// Enviar directamente usando la API
input.disabled = true;
const sendBtn = document.getElementById('send-btn');
if (sendBtn) sendBtn.disabled = true;
try {
const recipient = this.getConversationPhone(this.currentUserId);
if (!recipient) {
alert('No se pudo determinar el número de destino');
return;
}
const requestBody = {
recipient: recipient,
type: 'text',
message: text
};
const result = await this.apiCall('send_message.php', { body: requestBody });
if (result && result.success) {
// Mostrar inmediatamente en la vista
this.addMessageToView(text, 'outgoing');
typeof showAlert !== 'undefined' && showAlert('Mensaje enviado correctamente', 'success');
// Recargar mensajes en background
this.loadconversations(this.currentUserId, false).catch(e=>console.warn(e));
} else {
throw new Error(result && result.error ? result.error : 'Error desconocido');
}
} catch (error) {
console.error('Error sending quick reply:', error);
alert('Error al enviar respuesta rápida: ' + (error.message || error));
} finally {
input.disabled = false;
if (sendBtn) sendBtn.disabled = false;
input.focus();
}
}
scrollToBottom() {
@@ -4784,7 +5360,52 @@ if (!isset($_SESSION['user_id'])) {
}
}
searchConversations(query) {
async searchConversations(query) {
console.log('🔍 Buscando conversaciones:', query);
// Limpiar búsqueda si el query está vacío
if (!query || query.trim() === '') {
// Recargar conversaciones normales
await this.loadConversations(1, false);
return;
}
// Buscar en la base de datos
try {
const response = await this.apiCall(`get_conversations.php?search=${encodeURIComponent(query.trim())}`);
if (response && response.success) {
this.conversations = response.data || [];
this.currentPage = 1;
this.hasMore = false; // Desactivar paginación en búsqueda
this.renderConversations();
console.log(`✅ Búsqueda completada: ${this.conversations.length} resultados`);
if (this.conversations.length === 0) {
const container = document.getElementById('conversation-list');
container.innerHTML = `
<div style="padding: 20px; text-align: center; color: #999;">
<i class="fas fa-search" style="font-size: 48px; margin-bottom: 10px; opacity: 0.3;"></i>
<p>No se encontraron conversaciones con "${query}"</p>
<small>Intenta buscar por nombre, teléfono o contenido del mensaje</small>
</div>
`;
}
} else {
console.error('Error en búsqueda:', response);
// Fallback a búsqueda local si falla el servidor
this.searchConversationsLocal(query);
}
} catch (error) {
console.error('Error buscando conversaciones:', error);
// Fallback a búsqueda local
this.searchConversationsLocal(query);
}
}
// Búsqueda local (fallback) - solo filtra las conversaciones ya cargadas
searchConversationsLocal(query) {
const items = document.querySelectorAll('.conversation-item');
items.forEach(item => {
const name = item.querySelector('.conversation-name').textContent.toLowerCase();
@@ -5041,6 +5662,9 @@ if (!isset($_SESSION['user_id'])) {
const localThumb = message.local_thumb || '';
const messageId = message.id || message.message_id || '';
// Obtener la URL base del sitio para URLs absolutas
const baseUrl = window.location.origin;
// Debug completo del mensaje
if (mediaType === 'audio' || mediaType === 'image' || mediaType === 'video') {
console.log(`🎵 Media message (${mediaType}) - Datos completos:`, {
@@ -5050,7 +5674,8 @@ if (!isset($_SESSION['user_id'])) {
mediaUrl: mediaUrl,
mediaUrlExternal: mediaUrlExternal,
hasLocalFile: !!localFile,
messageType: mediaType
messageType: mediaType,
baseUrl: baseUrl
});
}
@@ -5059,23 +5684,27 @@ if (!isset($_SESSION['user_id'])) {
let thumb = '';
if (localFile) {
// ✅ PRIORIDAD 1: Archivo local guardado
full = `/${localFile}`;
thumb = localThumb ? `/${localThumb}` : `/${localFile}`;
console.log('✅ Usando archivo local:', full);
// ✅ PRIORIDAD 1: Archivo local guardado (usar URL absoluta)
full = localFile.startsWith('http') ? localFile : `${baseUrl}/${localFile.replace(/^\//, '')}`;
thumb = localThumb
? (localThumb.startsWith('http') ? localThumb : `${baseUrl}/${localThumb.replace(/^\//, '')}`)
: full;
console.log('✅ Usando archivo local (URL absoluta):', 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)}`;
// ⚠️ PRIORIDAD 2: Usar proxy con el ID de la base de datos (URL absoluta)
full = `${baseUrl}/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}`;
// 🔄 PRIORIDAD 3: Usar media_url_external (convertir a URL absoluta si es relativa)
full = mediaUrlExternal.startsWith('http')
? mediaUrlExternal
: `${baseUrl}/${mediaUrlExternal.replace(/^\//, '')}`;
thumb = full;
console.log('🔄 Usando media_url_external:', full);
console.log('🔄 Usando media_url_external (URL absoluta):', 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)}`;
// 📱 PRIORIDAD 4: Es un ID de WhatsApp (solo números) - usar URL absoluta
full = `${baseUrl}/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)) {
@@ -5115,11 +5744,25 @@ if (!isset($_SESSION['user_id'])) {
case 'audio': {
// Para audio, usar la misma lógica de full
let audioSrc = full;
if (!audioSrc) {
return `
<div class="message-media">
<div class="alert alert-warning mb-0" style="font-size: 13px;">
<i class="fas fa-exclamation-triangle"></i> Audio no disponible
<br><small>El audio puede haber expirado o no está disponible</small>
</div>
</div>
`;
}
return `
<div class="message-media">
<audio controls>
<audio controls controlsList="nodownload" preload="metadata" onerror="this.parentElement.innerHTML='<div class=\\'alert alert-danger mb-0\\' style=\\'font-size:13px;\\'><i class=\\'fas fa-times-circle\\'></i> Error al cargar audio<br><small>Archivo no encontrado o no disponible</small></div>'">
<source src="${escapeHtml(audioSrc)}" type="audio/mpeg">
Tu navegador no soporta audio.
<source src="${escapeHtml(audioSrc)}" type="audio/ogg">
<source src="${escapeHtml(audioSrc)}" type="audio/wav">
Tu navegador no soporta reproducción de audio.
</audio>
</div>
`;
@@ -5364,5 +6007,96 @@ if (!isset($_SESSION['user_id'])) {
</div>
</div>
<!-- Modal para programar recordatorio -->
<div class="modal fade" id="reminderModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title">
<i class="fas fa-calendar-plus"></i> Programar Recordatorio
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Cliente</label>
<input type="text" class="form-control" id="reminder-user-name" readonly>
</div>
<div class="mb-3">
<label class="form-label">Fecha</label>
<input type="date" class="form-control" id="reminder-date" required>
</div>
<div class="mb-3">
<label class="form-label">Hora</label>
<input type="time" class="form-control" id="reminder-time" value="09:00" required>
</div>
<div class="mb-3">
<label class="form-label">Seleccionar Plantilla</label>
<select class="form-select" id="reminder-template" required>
<option value="">Cargando plantillas...</option>
</select>
</div>
<div id="reminder-variables-container" style="display:none;">
<label class="form-label">Variables de la Plantilla</label>
<div id="reminder-variables-fields"></div>
</div>
<div id="reminder-preview" class="mt-3" style="display:none;">
<h6 class="border-bottom pb-2">Vista Previa</h6>
<div id="reminder-preview-content" class="p-3 bg-light rounded"></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Cancelar
</button>
<button type="button" class="btn btn-success" id="save-reminder-btn">
<i class="fas fa-save"></i> Programar Recordatorio
</button>
</div>
</div>
</div>
</div>
<!-- Modal para variables de plantilla (envío normal) -->
<div class="modal fade" id="templateVariablesModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title">
<i class="fas fa-edit"></i> Variables de la Plantilla
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<strong id="template-modal-name"></strong>
</div>
<div id="template-variables-container">
<!-- Variables dinámicas -->
</div>
<div id="template-preview" class="mt-3" style="display:none;">
<h6 class="border-bottom pb-2">Vista Previa</h6>
<div id="template-preview-content" class="p-3 bg-light rounded"></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Cancelar
</button>
<button type="button" class="btn btn-primary" id="send-template-with-vars-btn">
<i class="fas fa-paper-plane"></i> Enviar Plantilla
</button>
</div>
</div>
</div>
</div>
</body>
</html>