up
This commit is contained in:
+759
-25
@@ -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>
|
||||
Reference in New Issue
Block a user