/** * WhatsApp Bot Manager - JavaScript Simplificado * Versión sin librerías externas problemáticas */ class SimpleWhatsAppManager { constructor() { this.apiBaseUrl = './api/'; this.currentTab = 'dashboard'; this.init(); } init() { this.log('Iniciando sistema simplificado'); this.setupEventListeners(); this.setupCharts(); this.loadDashboard(); } log(message, type = 'info') { const timestamp = new Date().toLocaleTimeString(); console.log(`[${timestamp}] [${type.toUpperCase()}] ${message}`); } setupEventListeners() { this.log('Configurando event listeners'); // Navegación de tabs - más defensiva const navLinks = document.querySelectorAll('.nav-link[data-tab]'); if (navLinks) { navLinks.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const tabName = link.getAttribute('data-tab'); if (tabName) { this.showTab(tabName); } }); }); } // Formularios this.setupFormListeners(); } setupFormListeners() { // Formulario de configuración const system_configForm = document.getElementById('system_config-form'); if (system_configForm) { system_configForm.addEventListener('submit', (e) => { e.preventDefault(); this.savesystem_config(); }); } // Formulario de envío de mensaje const sendForm = document.getElementById('send-message-form'); if (sendForm) { sendForm.addEventListener('submit', (e) => { e.preventDefault(); this.sendMessage(); }); } } showTab(tabName) { this.log(`Cambiando a tab: ${tabName}`); // Ocultar todas las pestañas const tabs = document.querySelectorAll('.tab-content'); if (tabs) { tabs.forEach(tab => tab.classList.remove('active')); } // Remover clase active de navegación const navLinks = document.querySelectorAll('.nav-link'); if (navLinks) { navLinks.forEach(link => link.classList.remove('active')); } // Mostrar pestaña seleccionada const targetTab = document.getElementById(tabName); if (targetTab) { targetTab.classList.add('active'); } // Activar enlace de navegación const activeLink = document.querySelector(`[data-tab="${tabName}"]`); if (activeLink) { activeLink.classList.add('active'); } this.currentTab = tabName; this.loadTabData(tabName); } loadTabData(tabName) { this.log(`Cargando datos para tab: ${tabName}`); switch (tabName) { case 'dashboard': this.loadDashboard(); break; case 'conversations': this.loadConversations(); break; case 'users': this.loadUsers(); break; case 'admin_users': this.loadAdminUsers(); break; case 'send_message': this.loadconversations(); break; case 'templates': this.loadTemplates(); break; case 'whatsapp_profile': if (typeof window.loadWhatsAppProfile === 'function') { window.loadWhatsAppProfile(); } break; case 'broadcast': // Recargar plantillas cuando se abre el tab de broadcast if (typeof loadBroadcastTemplates === 'function') { loadBroadcastTemplates(); } break; case 'menus': this.loadMenus(); break; case 'autoresponses': this.loadAutoResponses(); break; case 'terms': if (typeof termsAdmin !== 'undefined' && typeof termsAdmin.cargar === 'function') { termsAdmin.cargar(1); } break; case 'logs': this.loadLogs(); break; case 'system_config': this.loadsystem_config(); break; default: this.log(`Tab no reconocido: ${tabName}`, 'warning'); } } async apiCall(endpoint, options = {}) { const url = `${this.apiBaseUrl}${endpoint}${endpoint.includes('?') ? '&' : '?'}debug=true`; this.log(`API Call: ${url}`, 'info'); try { const response = await fetch(url, { method: options.method || 'GET', headers: { 'Content-Type': 'application/json', ...options.headers }, body: options.body ? JSON.stringify(options.body) : undefined }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); this.log(`API Response: ${JSON.stringify(data).substring(0, 100)}...`, 'success'); return data; } catch (error) { this.log(`API Error: ${error.message}`, 'error'); this.showError('Error en petición: ' + error.message); throw error; } } async apiCallReal(endpoint, options = {}) { const url = `${this.apiBaseUrl}${endpoint}`; this.log(`API Call (REAL): ${url}`, 'info'); try { const response = await fetch(url, { method: options.method || 'GET', headers: { 'Content-Type': 'application/json', ...options.headers }, body: options.body ? JSON.stringify(options.body) : undefined }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); this.log(`API Response (REAL): ${JSON.stringify(data).substring(0, 100)}...`, 'success'); return data; } catch (error) { this.log(`API Error (REAL): ${error.message}`, 'error'); this.showError('Error en petición real: ' + error.message); throw error; } } async loadDashboard() { this.log('Cargando dashboard'); try { // Cargar estadísticas básicas const stats = await this.apiCall('get_stats.php'); this.updateStats(stats); // Cargar mensajes recientes const recentconversationsResponse = await this.apiCall('get_recent_messages.php'); if (recentconversationsResponse && recentconversationsResponse.success && Array.isArray(recentconversationsResponse.data)) { this.updateRecentconversations(recentconversationsResponse.data); } else if (recentconversationsResponse && Array.isArray(recentconversationsResponse)) { // Retrocompatibilidad por si la API devuelve directamente el array this.updateRecentconversations(recentconversationsResponse); } // Actualizar gráfico si existe this.updateChart(); } catch (error) { this.showError('Error cargando dashboard: ' + error.message); } } updateStats(stats) { this.log('Actualizando estadísticas del dashboard'); if (stats) { const elements = [ { id: 'total-users', value: stats.total_users || 0 }, { id: 'conversations-today', value: stats.conversations_today || 0 }, { id: 'active-users', value: stats.active_users || 0 }, { id: 'total-conversations', value: stats.total_conversations || 0 } ]; elements.forEach(item => { const element = document.getElementById(item.id); if (element) { element.textContent = item.value; this.log(`Estadística actualizada: ${item.id} = ${item.value}`); } }); // Actualizar badge de conversaciones en el menú lateral (usar mensajes pendientes si existe) const convBadge = document.getElementById('conversations-count'); if (convBadge) { const val = (typeof stats.unread_messages !== 'undefined') ? stats.unread_messages : (stats.total_conversations || 0); convBadge.textContent = val; convBadge.style.display = val > 0 ? 'inline-block' : 'none'; } } } updateRecentconversations(conversations) { const container = document.getElementById('recent-conversations'); if (!container) { this.log('Contenedor de mensajes recientes no encontrado', 'warning'); return; } container.innerHTML = ''; if (conversations && conversations.length > 0) { this.log(`Mostrando ${conversations.length} mensajes recientes`); conversations.forEach(message => { const messageElement = this.createRecentMessageElement(message); container.appendChild(messageElement); }); } else { container.innerHTML = '
No hay mensajes recientes
'; } } createRecentMessageElement(message) { const div = document.createElement('div'); div.className = 'recent-message mb-3 p-2 border-bottom'; const direction = message.direction === 'incoming' ? '📥' : '📤'; const time = new Date(message.created_at).toLocaleString(); div.innerHTML = `
${direction} ${message.phone_number} ${time}
${this.truncateText(message.content, 50)}
`; return div; } truncateText(text, length) { if (!text) return ''; return text.length > length ? text.substring(0, length) + '...' : text; } async updateChart() { if (!this.charts || !this.charts.conversations) { this.log('Gráfico no inicializado, omitiendo actualización', 'warning'); return; } try { const chartData = await this.apiCall('get_chart_data.php'); if (chartData && this.charts.conversations) { this.charts.conversations.data.labels = chartData.labels || []; this.charts.conversations.data.datasets[0].data = chartData.data || []; this.charts.conversations.update(); this.log('Gráfico actualizado correctamente'); } } catch (error) { this.log('Error actualizando gráfico: ' + error.message, 'warning'); } } setupCharts() { const ctx = document.getElementById('conversationsChart'); if (!ctx) { this.log('Canvas de gráfico no encontrado', 'warning'); return; } if (typeof Chart === 'undefined') { this.log('Chart.js no está cargado', 'warning'); return; } try { this.charts = this.charts || {}; this.charts.conversations = new Chart(ctx, { type: 'line', data: { labels: [], datasets: [{ label: 'Mensajes', data: [], borderColor: '#25d366', backgroundColor: 'rgba(37, 211, 102, 0.1)', borderWidth: 3, fill: true, tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, grid: { color: 'rgba(0,0,0,0.1)' } }, x: { grid: { color: 'rgba(0,0,0,0.1)' } } }, plugins: { legend: { display: false } } } }); this.log('Gráfico de mensajes inicializado'); } catch (error) { this.log('Error inicializando gráfico: ' + error.message, 'error'); } } async loadUsers(page = 1, search = '') { this.log('Cargando usuarios - página ' + page); try { let url = `get_users.php?page=${page}&limit=20`; if (search) { url += `&search=${encodeURIComponent(search)}`; } const response = await this.apiCall(url); if (response && response.success) { this.updateUsersList(response.data || [], response.pagination || {}); } else { this.showError('No se pudieron cargar los usuarios'); } } catch (error) { this.showError('Error cargando usuarios: ' + error.message); } } updateUsersList(users, pagination = {}) { const container = document.getElementById('users-table'); if (!container) return; // Guardar datos para uso posterior this.usersData = users; this.usersPagination = pagination; if (users.length === 0) { container.innerHTML = 'No hay usuarios registrados'; this.updateUsersPagination({}); return; } let html = ''; users.forEach(user => { const createdDate = new Date(user.created_at); const formatDate = createdDate.toLocaleDateString() + ' ' + createdDate.toLocaleTimeString('es', { hour: '2-digit', minute: '2-digit' }); html += ` ${user.id} ${user.phone_number} ${user.name || 'Sin nombre'} ${user.status} ${user.current_menu || 'Sin menú'} ${formatDate} `; }); container.innerHTML = html; this.updateUsersPagination(pagination); } updateUsersPagination(pagination) { const paginationContainer = document.getElementById('users-pagination'); if (!paginationContainer) return; if (!pagination.totalPages || pagination.totalPages <= 1) { paginationContainer.innerHTML = ''; return; } const currentPage = pagination.page || 1; const totalPages = pagination.totalPages; const total = pagination.total || 0; let html = `
Mostrando página ${currentPage} de ${totalPages} (${total} usuarios en total)
`; paginationContainer.innerHTML = html; } async loadConversations() { this.log('Cargando conversaciones'); try { const response = await this.apiCall('get_conversation_list.php'); console.log('Respuesta get_conversation_list:', response); // Debug if (response && response.success && Array.isArray(response.conversations)) { this.updateConversationsList(response.conversations); } else if (response && Array.isArray(response)) { // Retrocompatibilidad por si la API devuelve directamente el array this.updateConversationsList(response); } else { console.error('Formato de respuesta inesperado:', response); this.showError('Error: formato de datos inválido para conversaciones'); } } catch (error) { console.error('Error en loadConversations:', error); this.showError('Error cargando conversaciones: ' + error.message); } } updateConversationsList(conversations) { const container = document.getElementById('conversations-container'); if (!container) return; if (conversations.length === 0) { container.innerHTML = `
No hay conversaciones

Los chats aparecerán aquí cuando recibas o envíes mensajes

`; return; } let html = ''; conversations.forEach(conv => { const timeAgo = conv.time_ago || this.formatTimeAgo(conv.last_message_time || conv.created_at); const lastMessage = conv.last_message || conv.content || 'Sin mensajes'; const userName = conv.name || conv.phone_number; const userInitial = userName.charAt(0).toUpperCase(); const isOnline = Math.random() > 0.6; // Simulación de estado online // Unread badge const unreadBadge = (conv.unread_count && conv.unread_count > 0) ? `
${conv.unread_count}
` : ''; // Add class to highlight unread conversation (darker green) const unreadClass = (conv.unread_count && conv.unread_count > 0) ? ' unread' : ''; // Attention badge for advisor requests const attentionBadge = conv.advisor_requested ? `
` : ''; const attentionClass = conv.advisor_requested ? ' attention' : ''; // Status icon const statusIcon = conv.last_direction === 'outgoing' ? '' : ''; // Message type indicator const messageTypeIcon = conv.last_message_type === 'template' ? ' ' : conv.last_message_type === 'image' ? ' ' : ''; html += `
${userInitial}
${userName} ${conv.last_message_status === 'read' ? '' : ''}
${timeAgo}
${statusIcon}
${messageTypeIcon}${lastMessage}
${unreadBadge} ${attentionBadge}
`; }); container.innerHTML = html; // Agregar funcionalidad de búsqueda this.setupConversationSearch(conversations); } setupConversationSearch(conversations) { const searchInput = document.getElementById('search-conversations'); if (!searchInput) return; searchInput.addEventListener('input', (e) => { const query = e.target.value.toLowerCase().trim(); if (!query) { this.updateConversationsDisplay(conversations); return; } const filtered = conversations.filter(conv => { const name = (conv.name || '').toLowerCase(); const phone = (conv.phone_number || '').toLowerCase(); const message = (conv.last_message || conv.content || '').toLowerCase(); return name.includes(query) || phone.includes(query) || message.includes(query); }); this.updateConversationsDisplay(filtered); }); } updateConversationsDisplay(conversations) { const container = document.getElementById('conversations-container'); if (!container) return; if (conversations.length === 0) { container.innerHTML = `
No se encontraron conversaciones

Intenta con otros términos de búsqueda

`; return; } let html = ''; conversations.forEach(conv => { const timeAgo = conv.time_ago || this.formatTimeAgo(conv.last_message_time || conv.created_at); const lastMessage = conv.last_message || conv.content || 'Sin mensajes'; const userName = conv.name || conv.phone_number; const userInitial = userName.charAt(0).toUpperCase(); const isOnline = Math.random() > 0.6; const unreadBadge = (conv.unread_count && conv.unread_count > 0) ? `
${conv.unread_count}
` : ''; // Attention badge for advisor requests (ensure defined for filtered view) const attentionBadge = conv.advisor_requested ? `
` : ''; const attentionClass = conv.advisor_requested ? ' attention' : ''; const statusIcon = conv.last_direction === 'outgoing' ? '' : ''; const messageTypeIcon = conv.last_message_type === 'template' ? ' ' : conv.last_message_type === 'image' ? ' ' : ''; const unreadClass = (conv.unread_count && conv.unread_count > 0) ? ' unread' : ''; html += `
${userInitial}
${userName} ${conv.last_message_status === 'read' ? '' : ''}
${timeAgo}
${statusIcon}
${messageTypeIcon}${lastMessage}
${unreadBadge} ${attentionBadge}
`; }); container.innerHTML = html; } formatTimeAgo(datetime) { if (!datetime) return ''; const now = new Date(); const date = new Date(datetime); const diff = Math.floor((now - date) / 1000); if (diff < 60) return 'Ahora'; if (diff < 3600) return `${Math.floor(diff/60)}m`; if (diff < 86400) return `${Math.floor(diff/3600)}h`; if (diff < 2592000) return `${Math.floor(diff/86400)}d`; return date.toLocaleDateString(); } async loadsystem_config() { this.log('Cargando configuraciones'); try { const response = await this.apiCall('manage_config.php?action=whatsapp'); if (response && response.success) { this.updatesystem_configForm(response.data); } } catch (error) { this.showError('Error cargando configuraciones: ' + error.message); } // Cargar siempre el estado del documento de términos if (typeof loadTermsDoc === 'function') loadTermsDoc(); } updatesystem_configForm(data) { if (!data) return; const fields = [ { id: 'whatsapp-token', value: data.whatsapp_token }, { id: 'phone-number-id', value: data.phone_number_id }, { id: 'business-account-id', value: data.business_account_id }, { id: 'webhook-token', value: data.webhook_verify_token }, { id: 'api-url', value: data.api_url }, { id: 'business-name', value: data.business_name }, { id: 'welcome-message', value: data.welcome_message }, { id: 'terms-message', value: data.terms_message }, { id: 'terms-rejected-message', value: data.terms_rejected_message } ]; fields.forEach(field => { const element = document.getElementById(field.id); if (element && field.value) { element.value = field.value; this.log(`Campo ${field.id} actualizado con valor: ${field.value}`); } else { this.log(`Campo ${field.id} no encontrado o sin valor`, 'warning'); } }); } async savesystem_config() { this.log('Guardando configuraciones'); const system_config = { token: document.getElementById('whatsapp-token').value, phone_number_id: document.getElementById('phone-number-id').value, business_account_id: document.getElementById('business-account-id').value, webhook_verify_token: document.getElementById('webhook-token').value, api_url: document.getElementById('api-url').value, business_name: document.getElementById('business-name').value, welcome_message: document.getElementById('welcome-message').value, terms_message: document.getElementById('terms-message')?.value || '', terms_rejected_message: document.getElementById('terms-rejected-message')?.value || '' }; try { const response = await this.apiCall('manage_config.php?action=save_whatsapp', { method: 'POST', body: { whatsapp: system_config } }); if (response && response.success) { this.showSuccess('Configuraciones guardadas exitosamente'); } else { this.showError('Error guardando configuraciones'); } } catch (error) { this.showError('Error: ' + error.message); } } showError(message) { this.log(`Error: ${message}`, 'error'); this.showAlert(message, 'danger'); } showSuccess(message) { this.log(`Success: ${message}`, 'success'); this.showAlert(message, 'success'); } showInfo(message) { this.log(`Info: ${message}`, 'info'); this.showAlert(message, 'info'); } showWarning(message) { this.log(`Warning: ${message}`, 'warning'); this.showAlert(message, 'warning'); } showAlert(message, type) { // Crear alerta temporal const alertDiv = document.createElement('div'); alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`; alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;'; alertDiv.innerHTML = ` ${message} `; document.body.appendChild(alertDiv); // Auto-remover después de 5 segundos setTimeout(() => { if (alertDiv.parentNode) { alertDiv.parentNode.removeChild(alertDiv); } }, 5000); } async loadconversations() { this.log('Cargando interfaz de mensajes'); const userSelect = document.getElementById('message-recipient'); const templateSelect = document.getElementById('message-template'); if (userSelect) userSelect.innerHTML = ''; if (templateSelect) templateSelect.innerHTML = ''; try { // Cargar TODOS los usuarios sin límite const usersResponse = await this.apiCall('get_users.php?limit=10000'); let users = []; if (usersResponse && usersResponse.success && Array.isArray(usersResponse.data)) { users = usersResponse.data; } else if (Array.isArray(usersResponse)) { users = usersResponse; } // Cargar plantillas para el select const templatesResponse = await this.apiCall('get_templates.php'); let templates = []; if (templatesResponse && templatesResponse.success && Array.isArray(templatesResponse.data)) { templates = templatesResponse.data; } else if (Array.isArray(templatesResponse)) { templates = templatesResponse; } this.log(`Usuarios cargados: ${users.length}, Plantillas cargadas: ${templates.length}`); // Guardar plantillas para uso posterior this.templatesData = templates; if (users.length > 0) { this.populateUsersSelect(users); } else if (userSelect) { userSelect.innerHTML = ''; } if (templates.length > 0) { this.populateTemplatesSelect(templates); } else if (templateSelect) { templateSelect.innerHTML = ''; } } catch (error) { console.error('Error loadconversations:', error); if (userSelect) userSelect.innerHTML = ''; if (templateSelect) templateSelect.innerHTML = ''; this.showError('Error cargando datos para mensajes: ' + (error.message || error)); } } populateUsersSelect(users) { const select = document.getElementById('message-recipient'); if (!select) return; // Destruir Select2 si existe if ($(select).data('select2')) { $(select).select2('destroy'); } // Limpiar opciones existentes select.innerHTML = ''; // Agregar todos los usuarios users.forEach(user => { const option = document.createElement('option'); option.value = user.id; option.textContent = `${user.name || user.phone_number} (${user.phone_number})`; select.appendChild(option); }); // Inicializar Select2 con búsqueda $(select).select2({ theme: 'bootstrap-5', placeholder: 'Buscar y seleccionar usuario...', allowClear: true, width: '100%' }); } populateTemplatesSelect(templates) { const select = document.getElementById('message-template'); if (!select) return; // Limpiar opciones existentes (excepto la primera) select.innerHTML = ''; // Agregar plantillas (solo las aprobadas) templates .filter(template => template.status && template.status.toUpperCase() === 'APPROVED') .forEach(template => { const option = document.createElement('option'); option.value = template.id; // Usar ID en lugar de template_name option.textContent = `${template.name} - ${template.category || 'Sin descripción'}`; select.appendChild(option); }); } async loadTemplates() { this.log('Cargando plantillas'); try { const response = await this.apiCall('get_templates.php'); if (response && response.success) { const tmpl = response.data || []; this.templatesData = tmpl; this.templates = tmpl; // compatibilidad con app.js this.updateTemplatesList(tmpl); } else { this.showError('Error cargando plantillas'); } } catch (error) { this.showError('Error cargando plantillas: ' + error.message); } } async loadMenus() { this.log('Cargando menús'); try { const response = await this.apiCall('get_menus.php'); if (response && response.success) { this.updateMenusList(response.data || []); } else if (response && Array.isArray(response)) { // Retrocompatibilidad this.updateMenusList(response); } else { this.showError('Error cargando menús'); } } catch (error) { this.showError('Error cargando menús: ' + error.message); } } updateTemplatesList(templates) { const container = document.getElementById('templates-table'); if (!container) return; // Guardar datos para uso posterior this.templatesData = templates; if (templates.length === 0) { container.innerHTML = 'No hay plantillas registradas'; return; } let html = ''; templates.forEach(template => { html += ` ${template.name} ${template.template_name} ${template.language_code || 'es'} ${template.category || 'utility'} ${template.status || 'pending'} `; }); container.innerHTML = html; } updateMenusList(menus) { const container = document.getElementById('menus-table'); if (!container) return; if (menus.length === 0) { container.innerHTML = 'No hay menús configurados'; return; } let html = ''; menus.forEach(menu => { const createdDate = new Date(menu.created_at || Date.now()); const formatDate = createdDate.toLocaleDateString() + ' ' + createdDate.toLocaleTimeString('es', { hour: '2-digit', minute: '2-digit' }); html += ` ${menu.name || menu.menu_name} ${menu.menu_key || menu.trigger} ${this.truncateText(menu.description || '', 50)} ${menu.status || 'active'} ${(menu.menu_type || 'main') === 'no_advisor' ? '
🔴 Asesor ausente' : ''} ${menu.options_count || 0} opciones ${formatDate} `; }); container.innerHTML = html; } async saveTemplate() { this.log('Guardando plantilla'); try { const templateData = { name: document.getElementById('template-name').value, whatsapp_name: document.getElementById('template-whatsapp-name').value, language: document.getElementById('template-language').value, category: document.getElementById('template-category').value }; // Validar campos requeridos if (!templateData.name || !templateData.whatsapp_name) { this.showError('Nombre descriptivo y nombre de WhatsApp son requeridos'); return; } const response = await this.apiCall('save_template.php', { method: 'POST', body: templateData }); if (response && response.success) { const status = response.status || 'pending'; if (status === 'approved') { this.showSuccess('Plantilla guardada y marcada como APROBADA.'); } else { this.showSuccess('Plantilla guardada correctamente. Estado: PENDIENTE.'); } // Cerrar modal const modal = document.getElementById('createTemplateModal'); if (modal) { const bsModal = bootstrap.Modal.getInstance(modal); if (bsModal) bsModal.hide(); } // Limpiar formulario document.getElementById('template-form').reset(); // Recargar plantillas this.loadTemplates(); } else { this.showError(`Error guardando plantilla: ${response?.error || 'Error desconocido'}`); } } catch (error) { this.showError('Error guardando plantilla: ' + error.message); } } async saveMenu() { this.log('Guardando menú'); try { const menuData = { name: document.getElementById('menu-name')?.value || '', menu_key: document.getElementById('menu-key')?.value || '', description: document.getElementById('menu-description')?.value || '', welcome_message: document.getElementById('menu-welcome-message')?.value || '', status: document.getElementById('menu-status')?.value || 'active', menu_type: document.getElementById('menu-type')?.value || 'main' }; // Validar campos requeridos if (!menuData.name || !menuData.menu_key) { this.showError('Nombre del menú y clave del menú son requeridos'); return; } // Obtener opciones del menú const optionsContainer = document.getElementById('menu-options-container'); const options = []; if (optionsContainer) { const optionItems = optionsContainer.querySelectorAll('.menu-option-item'); optionItems.forEach((item, index) => { const optionKey = item.querySelector('.option-key')?.value || ''; const optionText = item.querySelector('.option-text')?.value || ''; const optionAction = item.querySelector('.option-action')?.value || 'message'; const optionValue = item.querySelector('.option-value')?.value || ''; if (optionKey && optionText) { options.push({ key: optionKey, text: optionText, action: optionAction, value: optionValue, order_index: index + 1 }); } }); } menuData.options = options; this.log(`Datos del menú a guardar: ${JSON.stringify(menuData)}`); const response = await this.apiCall('save_menu.php', { method: 'POST', body: menuData }); if (response && response.success) { this.showSuccess('Menú guardado correctamente'); // Cerrar modal const modal = document.getElementById('createMenuModal'); if (modal) { const bsModal = bootstrap.Modal.getInstance(modal); if (bsModal) bsModal.hide(); } // Limpiar formulario const form = document.getElementById('menu-form'); if (form) form.reset(); // Limpiar opciones const optionsContainer = document.getElementById('menu-options-container'); if (optionsContainer) optionsContainer.innerHTML = ''; // Recargar menús this.loadMenus(); } else { this.showError(`Error guardando menú: ${response?.error || 'Error desconocido'}`); } } catch (error) { this.showError('Error guardando menú: ' + error.message); } } async loadAutoResponses() { this.log('Cargando respuestas automáticas'); try { const response = await this.apiCall('get_autoresponses.php'); if (response && response.success) { this.updateAutoResponsesList(response.data || []); } else if (response && Array.isArray(response)) { this.updateAutoResponsesList(response); } else { this.showError('Error cargando respuestas automáticas'); } } catch (error) { this.showError('Error cargando respuestas automáticas: ' + error.message); } } updateAutoResponsesList(autoresponses) { const container = document.getElementById('autoresponses-table'); if (!container) return; // Almacenar los datos para uso posterior this.autoResponsesData = autoresponses; if (autoresponses.length === 0) { container.innerHTML = 'No hay respuestas automáticas configuradas'; return; } let html = ''; autoresponses.forEach(response => { const typeLabels = { 'keyword': '🔑 Palabra Clave', 'contains': '📝 Contiene', 'exact': '🎯 Exacta', 'welcome': '👋 Bienvenida', 'default': '🤖 Por Defecto' }; const typeLabel = typeLabels[response.trigger_type] || response.trigger_type; html += ` ${typeLabel} ${response.trigger_value || '-'} ${this.truncateText(response.response_text || '', 60)} ${response.is_active == 1 ? 'Activo' : 'Inactivo'} `; }); container.innerHTML = html; } async saveAutoResponse() { this.log('Guardando respuesta automática'); try { const autoresponseId = document.getElementById('autoresponse-id')?.value || ''; const responseData = { trigger_type: document.getElementById('autoresponse-trigger-type')?.value || 'keyword', trigger_value: document.getElementById('autoresponse-trigger-value')?.value || '', response_text: document.getElementById('autoresponse-text')?.value || '', response_type: document.getElementById('autoresponse-type')?.value || 'text', priority: document.getElementById('autoresponse-priority')?.value || 0, template_name: document.getElementById('autoresponse-template-name')?.value || '', menu_id: document.getElementById('autoresponse-menu-id')?.value || '', is_active: document.getElementById('autoresponse-active')?.value === '1' ? 1 : 0 }; // Si hay ID, es una actualización if (autoresponseId) { responseData.id = autoresponseId; } // Validar campos requeridos if (!responseData.response_text) { this.showError('El texto de respuesta es requerido'); return; } if (responseData.trigger_type !== 'welcome' && responseData.trigger_type !== 'default' && !responseData.trigger_value) { this.showError('El disparador es requerido para este tipo de respuesta'); return; } this.log(`Datos de respuesta automática a guardar: ${JSON.stringify(responseData)}`); const response = await this.apiCall('save_autoresponse.php', { method: 'POST', body: responseData }); if (response && response.success) { this.showSuccess('Respuesta automática guardada correctamente'); // Cerrar modal const modal = document.getElementById('createAutoResponseModal'); if (modal) { const bsModal = bootstrap.Modal.getInstance(modal); if (bsModal) bsModal.hide(); } // Limpiar formulario const form = document.getElementById('autoresponse-form'); if (form) form.reset(); // Recargar respuestas automáticas this.loadAutoResponses(); } else { this.showError(`Error guardando respuesta automática: ${response?.error || 'Error desconocido'}`); } } catch (error) { this.showError('Error guardando respuesta automática: ' + error.message); } } async deleteAutoResponseAction(responseId) { this.log(`Eliminando respuesta automática ID: ${responseId}`); try { const response = await this.apiCall('delete_autoresponse.php', { method: 'POST', body: { id: responseId } }); if (response && response.success) { this.showSuccess('Respuesta automática eliminada correctamente'); this.loadAutoResponses(); // Recargar la lista } else { this.showError(`Error eliminando respuesta automática: ${response?.error || 'Error desconocido'}`); } } catch (error) { this.showError('Error eliminando respuesta automática: ' + error.message); } } async deleteMenuAction(menuId) { this.log(`Eliminando menú ID: ${menuId}`); try { const response = await this.apiCall('delete_menu.php', { method: 'POST', body: { menu_id: menuId } }); if (response && response.success) { this.showSuccess('Menú eliminado correctamente'); this.loadMenus(); // Recargar la lista de menús } else { this.showError(`Error eliminando menú: ${response?.error || 'Error desconocido'}`); } } catch (error) { this.showError('Error eliminando menú: ' + error.message); } } async sendMessage() { this.log('Enviando mensaje'); try { const recipientType = document.getElementById('recipient-type').value; const messageType = document.getElementById('message-type').value; let recipient; let messageData = {}; // Obtener destinatario if (recipientType === 'existing') { const userSelect = document.getElementById('message-recipient'); if (!userSelect.value) { this.showError('Por favor selecciona un usuario'); return; } // Obtener datos del usuario seleccionado const selectedOption = userSelect.options[userSelect.selectedIndex]; const userId = userSelect.value; // Necesitamos obtener el número de teléfono del usuario const usersResponse = await this.apiCall('get_users.php'); if (usersResponse && usersResponse.success) { const user = usersResponse.data.find(u => u.id == userId); if (user) { recipient = user.phone_number; } else { this.showError('Usuario no encontrado'); return; } } else { this.showError('Error obteniendo datos del usuario'); return; } } else { recipient = document.getElementById('manual-recipient').value; if (!recipient) { this.showError('Por favor ingresa un número de teléfono'); return; } } // Preparar mensaje según el tipo if (messageType === 'text') { const messageText = document.getElementById('message-text').value; if (!messageText) { this.showError('Por favor escribe un mensaje'); return; } messageData = { recipient: recipient, type: 'text', message: messageText }; } else { const templateSelect = document.getElementById('message-template'); if (!templateSelect.value) { this.showError('Por favor selecciona una plantilla'); return; } // Obtener ID y nombre de la plantilla const templateId = templateSelect.value; const selectedOption = templateSelect.options[templateSelect.selectedIndex]; // Buscar la plantilla en templatesData para obtener el template_name let templateName = null; if (this.templatesData) { const template = this.templatesData.find(t => t.id == templateId); if (template) { templateName = template.template_name; } } if (!templateName) { this.showError('No se pudo obtener el nombre de la plantilla'); return; } // Recopilar variables de los inputs let tplParams = {}; const varInputs = document.querySelectorAll('.message-template-var'); if (varInputs.length > 0) { console.log(`🔍 Recopilando ${varInputs.length} variables de la plantilla`); // Detectar si las variables son numéricas o con nombres let hasNumericVars = false; let hasNamedVars = false; varInputs.forEach(input => { const placeholder = input.dataset.placeholder || ''; if (/^\{\{\d+\}\}$/.test(placeholder)) { hasNumericVars = true; } else { hasNamedVars = true; } }); console.log(`📊 Tipo de variables detectado: ${hasNumericVars ? 'numéricas' : 'con nombres'}`); if (hasNumericVars && !hasNamedVars) { // Variables numéricas: crear array ordenado por índice const tempObj = {}; varInputs.forEach(input => { const index = parseInt(input.dataset.var); tempObj[index] = input.value || ''; }); // Convertir a array ordenado const sortedKeys = Object.keys(tempObj).map(Number).sort((a, b) => a - b); tplParams = sortedKeys.map(key => tempObj[key]); console.log('📊 Enviando como array ordenado:', tplParams); } else { // Variables con nombres: crear objeto varInputs.forEach(input => { const placeholder = input.dataset.placeholder || ''; // Extraer nombre de la variable: {{nombre_tema}} -> nombre_tema const varName = placeholder.replace(/\{\{|\}\}/g, ''); tplParams[varName] = input.value || ''; }); console.log('📦 Enviando como objeto con nombres:', tplParams); } } else { console.log('ℹ️ Plantilla sin variables'); } // Si hay input oculto con parámetros (para compatibilidad), usarlo const paramsInput = document.getElementById('template-params'); if (paramsInput && paramsInput.value) { try { const hiddenParams = JSON.parse(paramsInput.value); if (Object.keys(hiddenParams).length > 0) { tplParams = hiddenParams; console.log('📝 Usando parámetros del input oculto:', tplParams); } } catch (e) { console.warn('Invalid JSON in #template-params, ignoring', e); } } // Subir imagen de header si el usuario seleccionó una let headerImageUrl = null; const msgImgInput = document.getElementById('message-template-image'); if (msgImgInput && msgImgInput.files && msgImgInput.files[0]) { try { this.showInfo('Subiendo imagen de la plantilla...'); headerImageUrl = await this._uploadTemplateImage(msgImgInput); } catch (e) { this.showError('Error subiendo imagen de la plantilla: ' + e.message); return; } } messageData = { recipient: recipient, type: 'template', template_name: templateName, // Usar template_name en lugar de ID parameters: tplParams, ...(headerImageUrl ? { header_image_url: headerImageUrl } : {}) }; } this.log(`Enviando mensaje a ${recipient}: ${JSON.stringify(messageData)}`); // Enviar mensaje por WhatsApp const response = await this.apiCallReal('send_message.php', { method: 'POST', body: messageData }); if (response && response.success) { this.showSuccess(`Mensaje enviado correctamente a ${recipient}`); // Limpiar formulario document.getElementById('send-message-form').reset(); document.getElementById('message-recipient').value = ''; } else { this.showError(`Error enviando mensaje: ${response?.error || 'Error desconocido'}`); } } catch (error) { this.showError('Error enviando mensaje: ' + error.message); } } async loadLogs() { try { const response = await this.apiCall('get_logs.php'); if (response && response.success && Array.isArray(response.data)) { this.updateLogsTable(response.data); } else if (response && Array.isArray(response)) { // Retrocompatibilidad por si la API devuelve directamente el array this.updateLogsTable(response); } else { this.showError('Error: formato de datos inválido para logs'); } } catch (error) { this.showError('Error cargando logs: ' + error.message); } } updateLogsTable(logs) { const tbody = document.getElementById('logs-table'); if (!tbody) { this.log('Tabla de logs no encontrada', 'warning'); return; } tbody.innerHTML = ''; if (!logs || logs.length === 0) { tbody.innerHTML = 'No hay logs registrados'; return; } logs.forEach(log => { const row = this.createLogRow(log); if (row) { tbody.appendChild(row); } }); } createLogRow(log) { if (!log) return null; const tr = document.createElement('tr'); tr.setAttribute('data-level', (log.level || 'INFO').toUpperCase()); tr.setAttribute('data-message', (log.message || log.mensaje || '').toLowerCase()); tr.setAttribute('data-source', (log.source || log.origen || '').toLowerCase()); // Fecha y hora const tdDateTime = document.createElement('td'); const dateStr = log.datetime || log.created_at || 'N/A'; tdDateTime.innerHTML = `${dateStr}`; tr.appendChild(tdDateTime); // Nivel const tdLevel = document.createElement('td'); const level = (log.level || log.tipo || 'INFO').toUpperCase(); const levelIcon = { 'ERROR': '❌', 'WARNING': '⚠️', 'INFO': 'ℹ️', 'DEBUG': '🔧', 'SUCCESS': '✅' }[level] || 'ℹ️'; const levelBadge = document.createElement('span'); levelBadge.className = `badge bg-${this.getLevelBadgeClass(level)}`; levelBadge.textContent = `${levelIcon} ${level}`; tdLevel.appendChild(levelBadge); tr.appendChild(tdLevel); // Mensaje const tdMessage = document.createElement('td'); const message = log.message || log.mensaje || ''; tdMessage.innerHTML = `${this.escapeHtml(this.truncateText(message, 120))}`; if (log.data) { const dataBtn = document.createElement('button'); dataBtn.className = 'btn btn-xs btn-link text-muted ms-2'; dataBtn.innerHTML = ''; dataBtn.title = 'Ver datos adicionales'; dataBtn.onclick = () => this.showLogDetails(log); tdMessage.appendChild(dataBtn); } tr.appendChild(tdMessage); // Origen const tdSource = document.createElement('td'); const source = log.source || log.origen || 'Sistema'; tdSource.innerHTML = `${this.escapeHtml(source)}`; tr.appendChild(tdSource); // Acciones const tdActions = document.createElement('td'); const viewButton = document.createElement('button'); viewButton.className = 'btn btn-sm btn-outline-info'; viewButton.innerHTML = ''; viewButton.title = 'Ver detalles completos'; viewButton.onclick = () => this.showLogDetails(log); tdActions.appendChild(viewButton); tr.appendChild(tdActions); return tr; } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } getLevelBadgeClass(level) { const levelClasses = { 'ERROR': 'danger', 'WARNING': 'warning', 'WARN': 'warning', 'INFO': 'info', 'DEBUG': 'secondary', 'SUCCESS': 'success' }; return levelClasses[level?.toUpperCase()] || 'secondary'; } showLogDetails(log) { const details = { 'Fecha/Hora': log.datetime || log.created_at || 'N/A', 'Nivel': (log.level || log.tipo || 'INFO').toUpperCase(), 'Mensaje': log.message || log.mensaje || '', 'Origen': log.source || log.origen || 'Sistema', 'Datos Adicionales': log.data ? JSON.stringify(log.data, null, 2) : 'N/A' }; let detailsHtml = '
'; for (const [key, value] of Object.entries(details)) { detailsHtml += `
${key}:
${value}
`; } detailsHtml += '
'; // Mostrar en modal o alert if (typeof bootstrap !== 'undefined') { // Si Bootstrap está disponible, crear modal this.showModalAlert('Detalles del Log', detailsHtml); } else { // Fallback a alert simple alert(`Detalles del Log:\n\n${Object.entries(details).map(([k,v]) => `${k}: ${v}`).join('\n')}`); } } showModalAlert(title, content) { // Crear modal temporal si no existe let modal = document.getElementById('tempLogModal'); if (!modal) { modal = document.createElement('div'); modal.id = 'tempLogModal'; modal.className = 'modal fade'; modal.innerHTML = ` `; document.body.appendChild(modal); } modal.querySelector('.modal-title').textContent = title; modal.querySelector('.modal-body').innerHTML = content; const bootstrapModal = new bootstrap.Modal(modal); bootstrapModal.show(); } async deleteUserAction(userId, userName, userPhone) { if (!confirm(`¿Estás seguro de que quieres eliminar este usuario?\n\nUsuario: ${userName}\nTeléfono: ${userPhone}\n\nEsta acción no se puede deshacer y eliminará:\n• Todos sus mensajes\n• Su historial de conversación\n• Sus datos personales`)) { return; } this.log(`Eliminando usuario ID: ${userId}`); try { const response = await this.apiCall('delete_user.php', { method: 'POST', body: { id: userId } }); if (response && response.success) { this.showSuccess('Usuario eliminado correctamente'); this.loadUsers(); // Recargar la lista } else { this.showError(`Error eliminando usuario: ${response?.error || 'Error desconocido'}`); } } catch (error) { this.showError('Error eliminando usuario: ' + error.message); } } async deleteTemplateAction(templateId) { // intentar obtener el nombre de la plantilla para confirmar const tmpl = (this.templatesData || []).find(t => t.id === templateId) || {}; const name = tmpl.name || tmpl.template_name || `ID ${templateId}`; if (!confirm(`¿Estás seguro de que quiere eliminar la plantilla "${name}"? Esta acción no se puede deshacer.`)) return; this.log(`Eliminando plantilla ID: ${templateId}`); try { const response = await this.apiCall('delete_template.php', { method: 'POST', body: { id: templateId } }); if (response && response.success) { this.showSuccess('Plantilla eliminada correctamente'); this.loadTemplates(); // Recargar la lista } else { this.showError(`Error eliminando plantilla: ${response?.error || 'Error desconocido'}`); } } catch (error) { this.showError('Error eliminando plantilla: ' + error.message); } } async sendBroadcast() { const selectionType = document.getElementById('broadcast-selection-type').value; const messageType = document.getElementById('broadcast-message-type').value; let payload = { selection_type: selectionType, message_type: messageType }; // Validar según tipo de selección if (selectionType === 'filter') { payload.filter = document.getElementById('broadcast-filter').value; } else { const selectedUsers = $('#broadcast-users').val(); if (!selectedUsers || selectedUsers.length === 0) { this.showError('Debes seleccionar al menos un usuario'); return; } payload.user_ids = selectedUsers; } // Validar según tipo de mensaje if (messageType === 'text') { const message = document.getElementById('broadcast-message').value; if (!message.trim()) { this.showError('El mensaje no puede estar vacío'); return; } payload.message = message; } else { const templateId = document.getElementById('broadcast-template').value; if (!templateId) { this.showError('Debes seleccionar una plantilla'); return; } payload.template_id = templateId; // Recoger y validar variables const variablesInputs = document.querySelectorAll('.broadcast-template-var'); let hasEmpty = false; // Detectar si las variables son numéricas o con nombres usando el placeholder const firstInput = variablesInputs[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 variables; if (isNumericVar) { // Variables numéricas {{1}}, {{2}} - usar array ordenado const tempVars = {}; variablesInputs.forEach(input => { const varIndex = parseInt(input.dataset.var); if (!input.value.trim()) { input.classList.add('is-invalid'); hasEmpty = true; } else { input.classList.remove('is-invalid'); tempVars[varIndex] = input.value; } }); if (hasEmpty) { this.showError('Por favor completa todos los campos de variables de la plantilla'); return; } // Convertir a array ordenado por índice variables = []; const sortedKeys = Object.keys(tempVars).map(k => parseInt(k)).sort((a, b) => a - b); sortedKeys.forEach(key => { variables.push(tempVars[key]); }); console.log('📝 Variables numéricas recopiladas:', tempVars); console.log('📊 Array ordenado:', variables); } else { // Variables con nombres {{fecha}}, {{motivo}} - usar objeto variables = {}; variablesInputs.forEach(input => { const placeholder = input.dataset.placeholder || ''; const varName = placeholder.replace(/\{\{|\}\}/g, ''); // Extraer nombre de {{fecha}} if (!input.value.trim()) { input.classList.add('is-invalid'); hasEmpty = true; } else { input.classList.remove('is-invalid'); variables[varName] = input.value; } }); if (hasEmpty) { this.showError('Por favor completa todos los campos de variables de la plantilla'); return; } console.log('📝 Variables con nombres recopiladas:', variables); } payload.template_variables = variables; } // Confirmar envío const targetText = selectionType === 'filter' ? `usuarios filtrados por "${payload.filter}"` : `${payload.user_ids.length} usuario(s) seleccionado(s)`; const messageText = messageType === 'text' ? 'mensaje de texto' : 'plantilla'; if (!confirm(`¿Enviar este ${messageText} a ${targetText}?`)) { return; } // Subir imagen de header broadcast si existe const bcImgInput = document.getElementById('broadcast-template-image'); if (bcImgInput && bcImgInput.files && bcImgInput.files[0]) { try { this.showInfo('Subiendo imagen de la plantilla...'); payload.header_image_url = await this._uploadTemplateImage(bcImgInput); } catch (e) { this.showError('Error subiendo imagen de la plantilla: ' + e.message); return; } } try { this.showInfo('Enviando mensajes masivos...'); const result = await this.apiCall('send_broadcast.php', { method: 'POST', body: payload }); if (result.success) { this.showSuccess(`✅ Mensajes enviados exitosamente!\nEnviados: ${result.sent_count}\nErrores: ${result.error_count}\nTotal usuarios: ${result.total_users}`); document.getElementById('broadcast-form').reset(); $('#broadcast-users').val(null).trigger('change'); document.getElementById('broadcast-template-variables').style.display = 'none'; document.getElementById('broadcast-template-preview').style.display = 'none'; // Refrescar historial automáticamente if (typeof window.loadBroadcastHistory === 'function') { setTimeout(() => window.loadBroadcastHistory(1), 800); } } else { this.showError(result.error || 'Error enviando broadcast'); } } catch (error) { this.showError('Error enviando broadcast: ' + error.message); } } // Sube una imagen al servidor y retorna la URL pública async _uploadTemplateImage(fileInput) { const file = fileInput.files[0]; if (!file) return null; const fd = new FormData(); fd.append('file', file); const resp = await fetch('./api/upload_media.php', { method: 'POST', body: fd }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const data = await resp.json(); if (data.success && data.public_url) return data.public_url; throw new Error(data.error || 'No se pudo subir la imagen'); } // Gestión de usuarios centralizada en lab_usuarios.php async loadAdminUsers() { // No-op: la UI redirige a lab_usuarios.php } } // Inicializar cuando el DOM esté listo document.addEventListener('DOMContentLoaded', function () { console.log('DOM cargado, inicializando WhatsApp Manager...'); try { window.whatsappManager = new SimpleWhatsAppManager(); console.log('WhatsApp Manager inicializado correctamente'); } catch (error) { console.error('Error inicializando WhatsApp Manager:', error); } }); // Funciones globales para modales (compatibilidad con el HTML existente) window.openCreateTemplateModal = function () { console.log('🔵 Abriendo modal de plantilla...'); const modalElement = document.getElementById('createTemplateModal'); if (!modalElement) { console.error('❌ Modal de plantilla no encontrado'); return; } try { const form = document.getElementById('template-form'); if (form) form.reset(); if (typeof bootstrap !== 'undefined' && bootstrap.Modal) { const modal = new bootstrap.Modal(modalElement); modal.show(); } else { modalElement.style.display = 'block'; modalElement.classList.add('show'); } console.log('✅ Modal de plantilla abierto'); } catch (error) { console.error('❌ Error abriendo modal de plantilla:', error); } }; window.openCreateMenuModal = function () { console.log('🔵 Abriendo modal de menú...'); const modalElement = document.getElementById('createMenuModal'); if (!modalElement) { console.error('❌ Modal de menú no encontrado'); return; } try { const form = document.getElementById('menu-form'); if (form) form.reset(); if (typeof bootstrap !== 'undefined' && bootstrap.Modal) { const modal = new bootstrap.Modal(modalElement); modal.show(); } else { modalElement.style.display = 'block'; modalElement.classList.add('show'); } console.log('✅ Modal de menú abierto'); } catch (error) { console.error('❌ Error abriendo modal de menú:', error); } }; // Función global para debugging window.debugAPI = async function (endpoint) { try { const manager = window.whatsappManager; if (manager) { const result = await manager.apiCall(endpoint); console.log('Debug API Result:', result); return result; } else { console.error('Manager no inicializado'); } } catch (error) { console.error('Debug API Error:', error); } }; // Función para abrir ventana de chat window.openChatWindow = async function (userId) { console.log('Abriendo chat del usuario:', userId); // Marcar conversación como leída en el backend y refrescar lista localmente try { await fetch('api/mark_conversation_read.php', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({user_id: userId})}); if (window.whatsappManager && typeof window.whatsappManager.loadConversations === 'function') { window.whatsappManager.loadConversations(); } } catch (e) { console.warn('mark_conversation_read failed', e); } // Abrir nueva ventana de chat const chatUrl = `chat_window.php?user_id=${userId}&debug=true`; const windowFeatures = 'width=1000,height=700,resizable=yes,scrollbars=yes,status=yes,toolbar=no,menubar=no,location=no'; const chatWindow = window.open(chatUrl, `chat_${userId}`, windowFeatures); if (chatWindow) { chatWindow.focus(); } else { alert('No se pudo abrir la ventana de chat. Verifica que tu navegador permita ventanas emergentes.'); } }; // Función para mostrar modal de chat function showChatModal(user, conversations) { // Escapar datos del usuario para evitar XSS const userName = escapeHtml(user.name || 'Usuario'); const userPhone = escapeHtml(user.phone_number || ''); const modalHtml = ` `; // Remover modal anterior si existe const existingModal = document.getElementById('chatModal'); if (existingModal) { existingModal.remove(); } document.body.insertAdjacentHTML('beforeend', modalHtml); // Event listener para cambio de tipo de mensaje document.getElementById('messageType').addEventListener('change', function() { const templateSelector = document.getElementById('templateSelector'); const messageInput = document.getElementById('messageInput'); if (this.value === 'template') { templateSelector.style.display = 'block'; messageInput.placeholder = 'Parámetros de la plantilla (opcional)'; } else { templateSelector.style.display = 'none'; messageInput.placeholder = 'Escribe tu mensaje...'; } }); // Mostrar modal const modal = new bootstrap.Modal(document.getElementById('chatModal')); modal.show(); // Scroll al final setTimeout(() => { const chatContainer = document.getElementById('chat-conversations'); chatContainer.scrollTop = chatContainer.scrollHeight; }, 200); } // Generar HTML de mensajes del chat function generateChatconversations(conversations) { if (!conversations || conversations.length === 0) { return '


No hay mensajes en esta conversación
'; } let html = ''; conversations.forEach(msg => { const isOutgoing = msg.direction === 'outgoing'; const messageClass = isOutgoing ? 'outgoing' : 'incoming'; const alignClass = isOutgoing ? 'justify-content-end' : 'justify-content-start'; const bgClass = isOutgoing ? 'bg-success text-white' : 'bg-white'; // Escapar el contenido del mensaje const messageContent = escapeHtml(msg.content) || 'Sin contenido'; const messageTime = escapeHtml(msg.time || ''); html += `
${messageContent}
${messageTime} ${isOutgoing ? '✓✓' : ''}
`; }); return html; } // Función para escapar HTML y prevenir XSS function escapeHtml(text) { if (!text) return ''; const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; return text.toString().replace(/[&<>"']/g, function(m) { return map[m]; }); } // Función para enviar mensaje desde el chat window.sendChatMessage = async function (userId) { const messageType = document.getElementById('messageType').value; const messageInput = document.getElementById('messageInput'); const message = messageInput.value.trim(); if (!message && messageType === 'text') { alert('Por favor escribe un mensaje'); return; } if (messageType === 'template') { const templateSelect = document.getElementById('templateSelect'); const template = templateSelect.value; if (!template) { alert('Por favor selecciona una plantilla'); return; } await sendTemplateMessage(userId, template, message); } else { await sendTextMessage(userId, message); } messageInput.value = ''; }; // Función para enviar mensaje de texto async function sendTextMessage(userId, message) { try { const manager = window.whatsappManager; const usersResponse = await manager.apiCall('get_users.php?debug=true'); // Manejar diferentes formatos de respuesta let users = []; if (Array.isArray(usersResponse)) { users = usersResponse; } else if (usersResponse && usersResponse.data && Array.isArray(usersResponse.data)) { users = usersResponse.data; } else if (usersResponse && usersResponse.users && Array.isArray(usersResponse.users)) { users = usersResponse.users; } else { console.error('Formato de respuesta de usuarios inesperado:', usersResponse); throw new Error('Error obteniendo lista de usuarios'); } const user = users.find(u => u.id == userId); if (!user) { throw new Error('Usuario no encontrado'); } const response = await manager.apiCall('send_message.php', { method: 'POST', body: { recipient: user.phone_number, type: 'text', message: message } }); if (response && response.success) { // Refrescar chat openChatWindow(userId); showAlert('Mensaje enviado correctamente', 'success'); } else { throw new Error(response.error || 'Error enviando mensaje'); } } catch (error) { console.error('Error enviando mensaje:', error); showAlert('Error enviando mensaje: ' + error.message, 'danger'); } } // Función para enviar mensaje de plantilla async function sendTemplateMessage(userId, template, parameters) { try { const manager = window.whatsappManager; const usersResponse = await manager.apiCall('get_users.php?debug=true'); // Manejar diferentes formatos de respuesta let users = []; if (Array.isArray(usersResponse)) { users = usersResponse; } else if (usersResponse && usersResponse.data && Array.isArray(usersResponse.data)) { users = usersResponse.data; } else if (usersResponse && usersResponse.users && Array.isArray(usersResponse.users)) { users = usersResponse.users; } else { console.error('Formato de respuesta de usuarios inesperado:', usersResponse); throw new Error('Error obteniendo lista de usuarios'); } const user = users.find(u => u.id == userId); if (!user) { throw new Error('Usuario no encontrado'); } const response = await manager.apiCall('send_message.php', { method: 'POST', body: { recipient: user.phone_number, type: 'template', template: template, language: 'en_US', parameters: parameters ? parameters.split(',') : [] } }); if (response && response.success) { // Refrescar chat openChatWindow(userId); showAlert('Mensaje de plantilla enviado correctamente', 'success'); } else { throw new Error(response.error || 'Error enviando plantilla'); } } catch (error) { console.error('Error enviando plantilla:', error); showAlert('Error enviando plantilla: ' + error.message, 'danger'); } } // Función para ver detalles de conversación window.viewConversationDetails = async function (userId) { console.log('Mostrando detalles de conversación para usuario:', userId); try { // Obtener datos del usuario const usersResponse = await window.whatsappManager.apiCall('get_users.php'); let user = null; if (usersResponse && usersResponse.success && Array.isArray(usersResponse.data)) { user = usersResponse.data.find(u => u.id == userId); } if (!user) { window.whatsappManager.showError('Usuario no encontrado'); return; } // Obtener estadísticas de la conversación (simuladas por ahora) const stats = { total_conversations: Math.floor(Math.random() * 50) + 1, conversations_today: Math.floor(Math.random() * 10), last_activity: user.updated_at || user.created_at, first_contact: user.created_at, status: user.status || 'active' }; // Mostrar modal con detalles showConversationDetailsModal(user, stats); } catch (error) { console.error('Error obteniendo detalles de conversación:', error); window.whatsappManager.showError('Error obteniendo detalles: ' + error.message); } }; // Función para mostrar modal de detalles de conversación function showConversationDetailsModal(user, stats) { console.log('Mostrando modal de detalles para:', user); const userName = escapeHtml(user.name || 'Sin nombre'); const userPhone = escapeHtml(user.phone_number || ''); const userStatus = user.status || 'unknown'; const currentMenu = user.current_menu || 'Sin menú'; // Formatear fechas const firstContact = user.created_at ? new Date(user.created_at).toLocaleString('es') : 'N/A'; const lastActivity = stats.last_activity ? new Date(stats.last_activity).toLocaleString('es') : 'N/A'; const statusBadgeClass = userStatus === 'active' ? 'success' : userStatus === 'inactive' ? 'secondary' : 'warning'; const modalHtml = ` `; // Remover modal anterior si existe const existingModal = document.getElementById('conversationDetailsModal'); if (existingModal) { existingModal.remove(); } document.body.insertAdjacentHTML('beforeend', modalHtml); // Mostrar modal const modal = new bootstrap.Modal(document.getElementById('conversationDetailsModal')); modal.show(); } // Función auxiliar para calcular tiempo transcurrido function calculateTimeAgo(datetime) { if (!datetime) return 'N/A'; const now = new Date(); const date = new Date(datetime); const diffInSeconds = Math.floor((now - date) / 1000); if (diffInSeconds < 60) return 'Hace menos de 1 minuto'; if (diffInSeconds < 3600) return `Hace ${Math.floor(diffInSeconds / 60)} minutos`; if (diffInSeconds < 86400) return `Hace ${Math.floor(diffInSeconds / 3600)} horas`; if (diffInSeconds < 2592000) return `Hace ${Math.floor(diffInSeconds / 86400)} días`; return date.toLocaleDateString('es'); } // Función para mostrar alertas function showAlert(message, type = 'info') { const alertHtml = ` `; document.body.insertAdjacentHTML('beforeend', alertHtml); // Auto-dismiss después de 5 segundos setTimeout(() => { const alert = document.querySelector('.alert'); if (alert) { alert.remove(); } }, 5000); } // Función para ver conversación específica (mantener compatibilidad) window.viewConversation = function (userId) { openChatWindow(userId); }; // Función para editar usuario window.editUser = function (userId) { console.log('Editando usuario:', userId); // Buscar el usuario en los datos cargados if (window.whatsappManager && window.whatsappManager.usersData) { const user = window.whatsappManager.usersData.find(u => u.id == userId); if (user) { showEditUserModal(user); } else { alert('Error: Usuario no encontrado'); } } else { alert('Error: Datos no disponibles, recarga la página'); } }; // Función para ver historial de usuario window.viewUserHistory = function (userId) { console.log('Viendo historial del usuario:', userId); // Por ahora mostrar un modal simple const modalHtml = ` `; // Remover modal anterior const existing = document.getElementById('userHistoryModal'); if (existing) existing.remove(); document.body.insertAdjacentHTML('beforeend', modalHtml); const modal = new bootstrap.Modal(document.getElementById('userHistoryModal')); modal.show(); }; // Función para confirmar eliminación de usuario window.confirmDeleteUser = function (userId) { if (confirm('¿Estás seguro de que deseas eliminar este usuario?\n\nEsta acción no se puede deshacer.')) { deleteUser(userId); } }; // Función para eliminar usuario window.deleteUser = async function (userId) { console.log('Eliminando usuario:', userId); try { const response = await window.whatsappManager.apiCall('delete_user.php', { method: 'POST', body: { user_id: userId } }); if (response && response.success) { window.whatsappManager.showSuccess('Usuario eliminado correctamente'); // Cerrar modal si está abierto const modal = document.getElementById('conversationDetailsModal'); if (modal) { const bsModal = bootstrap.Modal.getInstance(modal); if (bsModal) bsModal.hide(); } // Recargar datos if (window.whatsappManager.currentTab === 'users') { window.whatsappManager.loadUsers(); } else if (window.whatsappManager.currentTab === 'conversations') { window.whatsappManager.loadConversations(); } } else { window.whatsappManager.showError('Error eliminando usuario: ' + (response?.error || 'Error desconocido')); } } catch (error) { window.whatsappManager.showError('Error eliminando usuario: ' + error.message); } }; // Función para eliminar usuario window.deleteUser = function (userId) { console.log('Eliminando usuario:', userId); // Buscar el usuario para mostrar información let userName = 'Usuario ' + userId; let userPhone = ''; if (window.whatsappManager && window.whatsappManager.usersData) { const user = window.whatsappManager.usersData.find(u => u.id == userId); if (user) { userName = user.name || 'Usuario ' + userId; userPhone = user.phone_number || ''; } } if (window.whatsappManager) { window.whatsappManager.deleteUserAction(userId, userName, userPhone); } }; // Funciones para plantillas window.showCreateTemplateModal = function () { const modal = new bootstrap.Modal(document.getElementById('createTemplateModal')); modal.show(); }; window.editTemplate = function (templateId) { console.log('Editando plantilla:', templateId); // Las plantillas de WhatsApp no se pueden editar una vez creadas // Solo se pueden crear nuevas o eliminar existentes if (window.whatsappManager) { window.whatsappManager.showInfo( 'Las plantillas de WhatsApp aprobadas no se pueden editar una vez creadas. ' + 'Si necesitas cambiar el contenido, debes crear una nueva plantilla y enviarla a revisión.' ); } else { alert('Las plantillas no se pueden editar. Crea una nueva plantilla si necesitas modificar el contenido.'); } }; window.deleteTemplate = function (templateId) { console.log('Eliminando plantilla:', templateId); // Buscar la plantilla para mostrar información let templateName = 'Plantilla ' + templateId; if (window.whatsappManager && window.whatsappManager.templatesData) { const template = window.whatsappManager.templatesData.find(t => t.id == templateId); if (template) { templateName = template.name || 'Plantilla ' + templateId; } } if (confirm(`¿Está seguro que desea eliminar la plantilla "${templateName}"?\n\nEsta acción no se puede deshacer.`)) { if (window.whatsappManager) { window.whatsappManager.deleteTemplateAction(templateId); } } }; // Función para editar usuario - Modal simple window.showEditUserModal = function(user) { const modalHtml = ` `; // Eliminar modal anterior si existe const existingModal = document.getElementById('editUserModal'); if (existingModal) { existingModal.remove(); } // Agregar nuevo modal document.body.insertAdjacentHTML('beforeend', modalHtml); // Mostrar modal const modal = new bootstrap.Modal(document.getElementById('editUserModal')); modal.show(); }; // Función para guardar usuario editado window.saveEditedUser = async function() { const userId = document.getElementById('edit-user-id').value; const name = document.getElementById('edit-user-name').value; const status = document.getElementById('edit-user-status').value; if (window.whatsappManager) { try { const response = await window.whatsappManager.apiCall('update_user.php', { method: 'POST', body: { id: userId, name: name, status: status } }); if (response && response.success) { window.whatsappManager.showSuccess('Usuario actualizado correctamente'); // Cerrar modal const modal = bootstrap.Modal.getInstance(document.getElementById('editUserModal')); if (modal) modal.hide(); // Recargar lista window.whatsappManager.loadUsers(); } else { window.whatsappManager.showError(`Error: ${response?.error || 'Error desconocido'}`); } } catch (error) { window.whatsappManager.showError('Error actualizando usuario: ' + error.message); } } }; // Hacer que app.saveTemplate() y app.saveMenu() funcionen window.app = { saveTemplate: function () { if (window.whatsappManager) { window.whatsappManager.saveTemplate(); } }, saveMenu: function () { if (window.whatsappManager) { window.whatsappManager.saveMenu(); } }, saveAutoResponse: function () { if (window.whatsappManager) { window.whatsappManager.saveAutoResponse(); } } }; // Funciones globales para gestión de menús window.editMenu = async function (menuId) { console.log('Editando menú:', menuId); try { // Obtener datos del menú const response = await window.whatsappManager.apiCall('get_menus.php'); if (response && response.success) { const menu = response.data.find(m => m.id == menuId); if (menu) { showEditMenuModal(menu); } else { window.whatsappManager.showError('Menú no encontrado'); } } else { window.whatsappManager.showError('Error cargando datos del menú'); } } catch (error) { console.error('Error editando menú:', error); window.whatsappManager.showError('Error editando menú: ' + error.message); } }; window.deleteMenu = function (menuId) { console.log('Eliminando menú:', menuId); if (confirm(`¿Está seguro que desea eliminar el menú ${menuId}?`)) { if (window.whatsappManager) { window.whatsappManager.deleteMenuAction(menuId); } } }; window.viewMenuOptions = async function (menuId) { console.log('Viendo opciones del menú:', menuId); try { // Obtener datos del menú y sus opciones const response = await window.whatsappManager.apiCall('get_menus.php'); if (response && response.success) { const menu = response.data.find(m => m.id == menuId); if (menu) { showMenuOptionsModal(menu); } else { window.whatsappManager.showError('Menú no encontrado'); } } else { window.whatsappManager.showError('Error cargando opciones del menú'); } } catch (error) { console.error('Error viendo opciones:', error); window.whatsappManager.showError('Error viendo opciones: ' + error.message); } }; // Función para agregar nueva opción al menú window.addMenuOption = function () { const container = document.getElementById('menu-options-container'); if (!container) return; const optionIndex = container.children.length + 1; const optionHtml = ` `; container.insertAdjacentHTML('beforeend', optionHtml); }; // Función para remover opción del menú window.removeMenuOption = function (button) { const optionItem = button.closest('.menu-option-item'); if (optionItem) { optionItem.remove(); updateOptionNumbers(); } }; // Funciones globales para gestión de respuestas automáticas // La función real está más abajo en el archivo (línea ~2390) window.deleteAutoResponse = function (responseId) { console.log('Eliminando respuesta automática:', responseId); if (confirm(`¿Está seguro que desea eliminar la respuesta automática ${responseId}?`)) { if (window.whatsappManager) { window.whatsappManager.deleteAutoResponseAction(responseId); } } }; // Funciones para modal de autorespuestas window.showCreateAutoResponseModal = function() { document.getElementById('autoresponse-form').reset(); document.getElementById('autoresponse-id').value = ''; document.getElementById('createAutoResponseModalLabel').innerHTML = ' Crear Nueva Respuesta Automática'; // Reset campos específicos updateTriggerFields('keyword'); updateResponseTypeFields('text'); const modal = new bootstrap.Modal(document.getElementById('createAutoResponseModal')); modal.show(); }; window.showEditAutoResponseModal = function(autoresponse) { document.getElementById('autoresponse-id').value = autoresponse.id; document.getElementById('createAutoResponseModalLabel').innerHTML = ' Editar Respuesta Automática'; // Llenar campos del formulario document.getElementById('autoresponse-trigger-type').value = autoresponse.trigger_type; document.getElementById('autoresponse-trigger-value').value = autoresponse.trigger_value || ''; document.getElementById('autoresponse-text').value = autoresponse.response_text; document.getElementById('autoresponse-type').value = autoresponse.response_type; document.getElementById('autoresponse-priority').value = autoresponse.priority; document.getElementById('autoresponse-active').value = autoresponse.is_active ? '1' : '0'; document.getElementById('autoresponse-template-name').value = autoresponse.template_name || ''; document.getElementById('autoresponse-menu-id').value = autoresponse.menu_id || ''; // Actualizar campos según el tipo updateTriggerFields(autoresponse.trigger_type); updateResponseTypeFields(autoresponse.response_type); const modal = new bootstrap.Modal(document.getElementById('createAutoResponseModal')); modal.show(); }; window.updateTriggerFields = function(triggerType) { const valueGroup = document.getElementById('trigger-value-group'); const valueLabel = document.getElementById('trigger-value-label'); const valueInput = document.getElementById('autoresponse-trigger-value'); const valueHelp = document.getElementById('trigger-value-help'); if (!valueGroup || !valueLabel || !valueInput || !valueHelp) { console.warn('Elementos del modal de autorespuesta no encontrados'); return; } switch (triggerType) { case 'keyword': valueGroup.style.display = 'block'; valueLabel.textContent = 'Palabras Clave *'; valueInput.placeholder = 'hola,buenos días,hi,hello'; valueInput.required = true; valueHelp.textContent = 'Separa múltiples palabras con comas'; break; case 'contains': valueGroup.style.display = 'block'; valueLabel.textContent = 'Texto a Buscar *'; valueInput.placeholder = 'precio'; valueInput.required = true; valueHelp.textContent = 'Texto que debe contener el mensaje del usuario'; break; case 'exact': valueGroup.style.display = 'block'; valueLabel.textContent = 'Mensaje Exacto *'; valueInput.placeholder = 'quiero información'; valueInput.required = true; valueHelp.textContent = 'El mensaje debe coincidir exactamente'; break; case 'welcome': case 'default': valueGroup.style.display = 'none'; valueInput.required = false; valueInput.value = ''; break; } }; window.updateResponseTypeFields = function(responseType) { const templateFields = document.getElementById('template-fields'); const menuFields = document.getElementById('menu-fields'); if (!templateFields || !menuFields) { console.warn('Campos adicionales del modal no encontrados'); return; } // Ocultar todos los campos adicionales templateFields.style.display = 'none'; menuFields.style.display = 'none'; // Mostrar campos según el tipo switch (responseType) { case 'template': templateFields.style.display = 'block'; break; case 'menu': menuFields.style.display = 'block'; // Cargar menús disponibles si no están cargados loadMenusForSelect(); break; } }; window.loadMenusForSelect = function() { fetch('/api/get_menus.php') .then(response => response.json()) .then(data => { if (data.success) { const select = document.getElementById('autoresponse-menu-id'); if (select) { select.innerHTML = ''; data.data.forEach(menu => { const option = document.createElement('option'); option.value = menu.id; option.textContent = `${menu.name} (${menu.menu_key})`; select.appendChild(option); }); } } }) .catch(error => console.error('Error loading menus for select:', error)); }; // Actualizar función editAutoResponse para usar el modal window.editAutoResponse = function (responseId) { console.log('Editando respuesta automática:', responseId); // Buscar la respuesta en los datos cargados if (window.whatsappManager && window.whatsappManager.autoResponsesData) { const autoresponse = window.whatsappManager.autoResponsesData.find(ar => ar.id == responseId); if (autoresponse) { showEditAutoResponseModal(autoresponse); } else { console.error('Respuesta automática no encontrada:', responseId); alert('Error: No se pudo cargar la respuesta automática'); } } else { console.error('Datos de respuestas automáticas no disponibles'); alert('Error: Datos no disponibles, recarga la página'); } }; window.showCreateAutoResponseModal = function () { console.log('🔵 Abriendo modal de respuesta automática...'); const modalElement = document.getElementById('createAutoResponseModal'); if (!modalElement) { console.error('❌ Modal de respuesta automática no encontrado'); return; } try { const form = document.getElementById('autoresponse-form'); if (form) form.reset(); if (typeof bootstrap !== 'undefined' && bootstrap.Modal) { const modal = new bootstrap.Modal(modalElement); modal.show(); } else { modalElement.style.display = 'block'; modalElement.classList.add('show'); } console.log('✅ Modal de respuesta automática abierto'); } catch (error) { console.error('❌ Error abriendo modal de respuesta automática:', error); } }; // Función para cambiar tipo de disparador window.toggleTriggerType = function () { const triggerType = document.getElementById('autoresponse-type')?.value; const triggerContainer = document.getElementById('trigger-container'); if (triggerContainer) { if (triggerType === 'welcome' || triggerType === 'default') { triggerContainer.style.display = 'none'; } else { triggerContainer.style.display = 'block'; const triggerInput = document.getElementById('autoresponse-trigger'); const triggerLabel = document.querySelector('label[for="autoresponse-trigger"]'); if (triggerInput && triggerLabel) { switch (triggerType) { case 'keyword': triggerLabel.textContent = 'Palabra Clave *'; triggerInput.placeholder = 'ej: hola, info, precios'; break; case 'contains': triggerLabel.textContent = 'Texto que debe contener *'; triggerInput.placeholder = 'ej: precio, información'; break; case 'exact': triggerLabel.textContent = 'Mensaje exacto *'; triggerInput.placeholder = 'ej: ¿Cuáles son sus precios?'; break; } } } } }; // Funciones para el formulario de mensajes window.toggleRecipientType = function () { const recipientType = document.getElementById('recipient-type').value; const existingGroup = document.getElementById('existing-recipient-group'); const manualGroup = document.getElementById('manual-recipient-group'); if (recipientType === 'manual') { existingGroup.style.display = 'none'; manualGroup.style.display = 'block'; } else { existingGroup.style.display = 'block'; manualGroup.style.display = 'none'; } }; window.toggleMessageType = function () { const messageType = document.getElementById('message-type').value; const textGroup = document.getElementById('message-text-group'); const templateGroup = document.getElementById('template-group'); if (messageType === 'template') { textGroup.style.display = 'none'; templateGroup.style.display = 'block'; } else { textGroup.style.display = 'block'; templateGroup.style.display = 'none'; // Ocultar variables y preview cuando no es plantilla const varsContainer = document.getElementById('message-template-variables'); const previewContainer = document.getElementById('message-template-preview'); if (varsContainer) varsContainer.style.display = 'none'; if (previewContainer) previewContainer.style.display = 'none'; } }; // Cargar detalles de plantilla para send_message window.loadMessageTemplateDetails = async function() { console.log('🔵 loadMessageTemplateDetails llamado'); const select = document.getElementById('message-template'); const templateId = select.value; console.log('Template ID seleccionado:', templateId); if (!templateId) { document.getElementById('message-template-variables').style.display = 'none'; document.getElementById('message-template-preview').style.display = 'none'; return; } try { const response = await window.whatsappManager.apiCall(`get_template_details.php?id=${templateId}`); console.log('✅ Detalles de plantilla:', response); if (response && response.success) { const template = response.template || response.data; if (!template) { console.error('❌ No se encontró template en la respuesta'); return; } console.log('📋 Template data:', template); console.log('📋 Variables:', template.variables); const variablesContainer = document.getElementById('message-template-variables'); // Detectar header de imagen const hasImageHeader = (template.header_type || '').toUpperCase() === 'IMAGE'; let html = ''; if (hasImageHeader) { html += `

Esta plantilla requiere una imagen de encabezado dinámica. Súbela aquí.

`; setTimeout(() => { const imgInput = document.getElementById('message-template-image'); if (imgInput) imgInput.addEventListener('change', function() { const file = this.files[0]; if (file) { const reader = new FileReader(); reader.onload = e => { document.getElementById('msg-image-thumb').src = e.target.result; document.getElementById('msg-image-preview').style.display = 'block'; }; reader.readAsDataURL(file); } }); }, 50); } if (template.variables && template.variables.length > 0) { console.log(`✅ Se encontraron ${template.variables.length} variables`); html += ``; template.variables.forEach((variable, index) => { const varLabel = variable.label || variable.name || `Variable ${variable.index || index + 1}`; const placeholder = variable.placeholder || `{{${variable.index || index + 1}}}`; const example = variable.example || ''; html += `
`; }); console.log('📝 HTML generado para variables:', html); variablesContainer.innerHTML = html; variablesContainer.style.display = 'block'; console.log('✅ Variables container mostrado'); } else { if (!hasImageHeader) html += '

Esta plantilla no tiene variables de texto

'; console.log('⚠️ Template sin variables de texto'); variablesContainer.innerHTML = html; variablesContainer.style.display = (hasImageHeader || html) ? 'block' : 'none'; } updateMessagePreview(); } else { console.error('❌ Respuesta sin success:', response); } } catch (error) { console.error('❌ Error cargando detalles de plantilla:', error); window.whatsappManager.showError('Error cargando plantilla'); } }; // Actualizar preview de plantilla para send_message window.updateMessagePreview = async function() { const templateId = document.getElementById('message-template').value; if (!templateId) return; // Recolectar variables como array (por índice) const variablesArray = []; document.querySelectorAll('.message-template-var').forEach(input => { const index = parseInt(input.dataset.var) - 1; // Convertir a índice de array (0-based) variablesArray[index] = input.value || `{{${input.dataset.var}}}`; }); try { const response = await window.whatsappManager.apiCall('preview_template.php', { method: 'POST', body: { template_id: templateId, parameters: variablesArray } }); if (response && response.success && response.preview) { const previewContainer = document.getElementById('message-template-preview'); const previewContent = document.getElementById('message-preview-content'); // Usar el HTML pre-formateado del servidor previewContent.innerHTML = response.preview.html || response.preview.body.replace(/\\n/g, '
'); previewContainer.style.display = 'block'; } else { console.error('Error en preview:', response); } } catch (error) { console.error('Error generando vista previa:', error); } }; // Función de diagnóstico window.runDiagnostic = function () { const resultsDiv = document.getElementById('diagnostic-results'); // Mostrar spinner de carga resultsDiv.innerHTML = `
Cargando...

Ejecutando diagnóstico...

`; fetch('./api/diagnose_whatsapp.php') .then(response => response.json()) .then(data => { if (data.success) { displayDiagnosticResults(data); } else { resultsDiv.innerHTML = `
Error: ${data.error || 'Error desconocido'}
`; } }) .catch(error => { console.error('Error en diagnóstico:', error); resultsDiv.innerHTML = `
Error de conexión: ${error.message}
`; }); }; function displayDiagnosticResults(data) { const resultsDiv = document.getElementById('diagnostic-results'); let html = `
Última verificación: ${data.timestamp}
Configuración
Phone Number ID ${data.config_status.phone_number_id ? '✓' : '✗'}
Access Token ${data.config_status.access_token ? '✓' : '✗'}
Webhook Token ${data.config_status.webhook_verify_token ? '✓' : '✗'}
Plantillas
${data.template_status.approved || 0}
Aprobadas
${data.template_status.pending || 0}
Pendientes
${data.template_status.rejected || 0}
Rechazadas
API WhatsApp
${data.api_status.reachable ? `✓ Conectado - ${data.api_status.phone_number || 'N/A'}` : `✗ ${data.api_status.error || 'No conectado'}` }
`; // Recomendaciones if (data.recommendations && data.recommendations.length > 0) { html += `
Recomendaciones
`; } html += '
'; resultsDiv.innerHTML = html; } // ============================================== // EVENT LISTENERS PARA MODALES DE AUTORESPUESTAS // ============================================== // Agregar event listeners cuando el DOM esté listo document.addEventListener('DOMContentLoaded', function() { // Event listener para cambio de tipo de trigger const triggerTypeSelect = document.getElementById('autoresponse-trigger-type'); if (triggerTypeSelect) { triggerTypeSelect.addEventListener('change', function() { updateTriggerFields(this.value); }); } // Event listener para cambio de tipo de respuesta const responseTypeSelect = document.getElementById('autoresponse-type'); if (responseTypeSelect) { responseTypeSelect.addEventListener('change', function() { updateResponseTypeFields(this.value); }); } // Event listener para el modal cuando se muestre const autoResponseModal = document.getElementById('createAutoResponseModal'); if (autoResponseModal) { autoResponseModal.addEventListener('shown.bs.modal', function () { // Enfocar el primer campo cuando se abra el modal const firstInput = autoResponseModal.querySelector('input, select, textarea'); if (firstInput) { firstInput.focus(); } }); } }); // ─── Gestión del documento PDF de Términos y Condiciones ───────────────────── (function () { function showTermsStatus(msg, type) { const el = document.getElementById('terms-upload-status'); if (!el) return; el.style.display = 'block'; el.className = 'mt-2 small text-' + (type || 'secondary'); el.textContent = msg; } function _fallbackCopy(text) { const ta = document.createElement('textarea'); ta.value = text; ta.style.cssText = 'position:fixed;top:-999px;left:-999px;opacity:0;'; document.body.appendChild(ta); ta.focus(); ta.select(); try { const ok = document.execCommand('copy'); showTermsStatus(ok ? '✔ URL copiada al portapapeles.' : '✖ No se pudo copiar.', ok ? 'success' : 'danger'); } catch (e) { showTermsStatus('✖ No se pudo copiar: ' + text, 'danger'); } document.body.removeChild(ta); } async function loadTermsDoc() { try { const res = await fetch('api/get_terms_config.php'); const json = await res.json(); if (!json.success || !json.data) return; const d = json.data; const nameEl = document.getElementById('terms-doc-name'); const verEl = document.getElementById('terms-doc-version'); const linkEl = document.getElementById('terms-doc-link'); const copyBtn = document.getElementById('terms-doc-copy-btn'); const currentEl = document.getElementById('terms-doc-current'); const msgAcEl = document.getElementById('terms-message'); const msgRjEl = document.getElementById('terms-rejected-message'); if (nameEl) nameEl.textContent = d.documento_nombre || '(sin nombre)'; if (verEl) verEl.textContent = d.version || '—'; if (linkEl) linkEl.href = d.documento_url || '#'; if (currentEl) currentEl.style.display = d.documento_url ? '' : 'none'; // Rellenar textos si están en terms_versions (no sobreescribir si ya se cargaron) if (msgAcEl && !msgAcEl.value && d.mensaje_aceptacion) { msgAcEl.value = d.mensaje_aceptacion; } if (msgRjEl && !msgRjEl.value && d.mensaje_rechazo) { msgRjEl.value = d.mensaje_rechazo; } if (copyBtn && d.documento_url) { copyBtn.onclick = () => { const url = d.documento_url; if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(url) .then(() => showTermsStatus('✔ URL copiada al portapapeles.', 'success')) .catch(() => _fallbackCopy(url)); } else { _fallbackCopy(url); } }; } } catch (e) { console.warn('loadTermsDoc error:', e); } } async function uploadTermsDocument() { const fileInput = document.getElementById('terms-pdf-file'); const versionEl = document.getElementById('terms-new-version'); const forceEl = document.getElementById('terms-force-reaccept'); const msgAcEl = document.getElementById('terms-message'); const msgRjEl = document.getElementById('terms-rejected-message'); const formData = new FormData(); if (fileInput && fileInput.files.length > 0) { formData.append('pdf', fileInput.files[0]); } if (versionEl) formData.append('version', versionEl.value.trim()); if (forceEl) formData.append('forzar_reenvio', forceEl.checked ? '1' : '0'); if (msgAcEl) formData.append('mensaje_aceptacion', msgAcEl.value); if (msgRjEl) formData.append('mensaje_rechazo', msgRjEl.value); showTermsStatus('Guardando…', 'secondary'); try { const res = await fetch('api/upload_terms_document.php', { method: 'POST', body: formData }); const json = await res.json(); if (json.success) { showTermsStatus('✔ ' + (json.message || 'Guardado correctamente.'), 'success'); if (json.documento_url) { const linkEl = document.getElementById('terms-doc-link'); if (linkEl) linkEl.href = json.documento_url; document.getElementById('terms-doc-current').style.display = ''; const nameEl = document.getElementById('terms-doc-name'); if (nameEl && fileInput && fileInput.files[0]) nameEl.textContent = fileInput.files[0].name; const verEl = document.getElementById('terms-doc-version'); if (verEl && versionEl) verEl.textContent = versionEl.value || '—'; } // Limpiar inputs de archivo if (fileInput) fileInput.value = ''; if (forceEl) forceEl.checked = false; } else { showTermsStatus('✖ ' + (json.error || 'Error al guardar.'), 'danger'); } } catch (e) { showTermsStatus('✖ Error de red: ' + e.message, 'danger'); } } document.addEventListener('DOMContentLoaded', function () { const uploadBtn = document.getElementById('terms-upload-btn'); if (uploadBtn) uploadBtn.addEventListener('click', uploadTermsDocument); // Cargar estado del documento cuando se activa el tab de configuración const cfgTab = document.querySelector('[data-bs-target="#system-config"], [href="#system-config"]'); if (cfgTab) { cfgTab.addEventListener('shown.bs.tab', loadTermsDoc); } // Si el tab ya está activo al cargar la página const cfgPanel = document.getElementById('system-config'); if (cfgPanel && cfgPanel.classList.contains('active')) { loadTermsDoc(); } }); // Exponer para que loadsystem_config() pueda llamarla window.loadTermsDoc = loadTermsDoc; })(); // ─── / Términos ─────────────────────────────────────────────────────────────── // Funciones globales para gestión de usuarios // Función para editar usuario window.editUser = function(userId) { console.log('Editando usuario:', userId); // Buscar los datos del usuario actual en la tabla const userRow = document.querySelector(`button[onclick="editUser(${userId})"]`).closest('tr'); const cells = userRow.querySelectorAll('td'); const currentData = { id: userId, phone: cells[1].textContent, name: cells[2].textContent === 'Sin nombre' ? '' : cells[2].textContent, status: cells[3].querySelector('.badge').textContent.toLowerCase() }; showEditUserModal(currentData); }; // Función para eliminar usuario window.deleteUser = function(userId) { console.log('Eliminando usuario:', userId); const userRow = document.querySelector(`button[onclick="deleteUser(${userId})"]`).closest('tr'); const userName = userRow.querySelectorAll('td')[2].textContent; const userPhone = userRow.querySelectorAll('td')[1].textContent; if (confirm(`¿Estás seguro de que quieres eliminar este usuario?\n\nUsuario: ${userName}\nTeléfono: ${userPhone}\n\nEsta acción no se puede deshacer y eliminará:\n• Todos sus mensajes\n• Su historial de conversación\n• Sus datos personales`)) { deleteUserAction(userId); } }; // Mostrar modal de edición de usuario function showEditUserModal(userData) { const modalHtml = ` `; // Remover modal anterior si existe const existingModal = document.getElementById('editUserModal'); if (existingModal) { existingModal.remove(); } document.body.insertAdjacentHTML('beforeend', modalHtml); const modal = new bootstrap.Modal(document.getElementById('editUserModal')); modal.show(); } // Guardar edición de usuario window.saveUserEdit = async function() { const userId = document.getElementById('editUserId').value; const userName = document.getElementById('editUserName').value.trim(); const userStatus = document.getElementById('editUserStatus').value; console.log('Guardando usuario:', { userId, userName, userStatus }); const userData = { user_id: userId, name: userName || null, // null si está vacío status: userStatus }; try { const response = await window.whatsappManager.apiCall('update_user.php', { method: 'POST', body: userData }); if (response && response.success) { window.whatsappManager.showSuccess('Usuario actualizado correctamente'); // Cerrar modal const modal = bootstrap.Modal.getInstance(document.getElementById('editUserModal')); if (modal) modal.hide(); // Recargar lista de usuarios setTimeout(() => { window.whatsappManager.loadUsers(); }, 500); } else { window.whatsappManager.showError(`Error actualizando usuario: ${response?.error || 'Error desconocido'}`); } } catch (error) { console.error('Error:', error); window.whatsappManager.showError('Error actualizando usuario: ' + error.message); } }; // Acción de eliminar usuario async function deleteUserAction(userId) { try { console.log('Eliminando usuario con ID:', userId); const response = await window.whatsappManager.apiCall('delete_user.php', { method: 'POST', body: { user_id: userId } }); if (response && response.success) { window.whatsappManager.showSuccess('Usuario eliminado correctamente'); // Recargar lista de usuarios setTimeout(() => { window.whatsappManager.loadUsers(); }, 500); } else { window.whatsappManager.showError(`Error eliminando usuario: ${response?.error || 'Error desconocido'}`); } } catch (error) { console.error('Error:', error); window.whatsappManager.showError('Error eliminando usuario: ' + error.message); } } // ========================= FUNCIONES MODALES PARA MENÚS ========================= // Modal para editar menú function showEditMenuModal(menuData) { const modalHtml = ` `; // Remover modal anterior si existe const existingModal = document.getElementById('editMenuModal'); if (existingModal) { existingModal.remove(); } document.body.insertAdjacentHTML('beforeend', modalHtml); const modal = new bootstrap.Modal(document.getElementById('editMenuModal')); modal.show(); } // Modal para ver opciones del menú function showMenuOptionsModal(menuData) { const modalHtml = ` `; // Remover modal anterior si existe const existingModal = document.getElementById('viewMenuOptionsModal'); if (existingModal) { existingModal.remove(); } document.body.insertAdjacentHTML('beforeend', modalHtml); const modal = new bootstrap.Modal(document.getElementById('viewMenuOptionsModal')); modal.show(); } // Guardar edición de menú window.saveMenuEdit = async function() { const menuId = document.getElementById('editMenuId').value; const menuName = document.getElementById('editMenuName').value.trim(); const menuKey = document.getElementById('editMenuKey').value.trim(); const menuDescription = document.getElementById('editMenuDescription').value.trim(); const menuWelcome = document.getElementById('editMenuWelcome').value.trim(); const menuStatus = document.getElementById('editMenuStatus').value; const menuType = document.getElementById('editMenuType')?.value || 'main'; if (!menuName || !menuKey) { window.whatsappManager.showError('Nombre y clave del menú son obligatorios'); return; } console.log('Guardando menú editado:', { menuId, menuName, menuKey, menuStatus, menuType }); const menuData = { id: menuId, name: menuName, menu_key: menuKey, description: menuDescription, welcome_message: menuWelcome, status: menuStatus, menu_type: menuType }; try { const response = await window.whatsappManager.apiCall('save_menu.php', { method: 'POST', body: menuData }); if (response && response.success) { window.whatsappManager.showSuccess('Menú actualizado correctamente'); // Cerrar modal const modal = bootstrap.Modal.getInstance(document.getElementById('editMenuModal')); if (modal) modal.hide(); // Recargar lista de menús setTimeout(() => { window.whatsappManager.loadMenus(); }, 500); } else { window.whatsappManager.showError(`Error actualizando menú: ${response?.error || 'Error desconocido'}`); } } catch (error) { console.error('Error:', error); window.whatsappManager.showError('Error actualizando menú: ' + error.message); } }; // Funciones auxiliares para opciones window.addMenuOptionInModal = function(menuId) { console.log('Agregando opción al menú:', menuId); const modalHtml = ` `; // Remover modal anterior si existe const existingModal = document.getElementById('addMenuOptionModal'); if (existingModal) { existingModal.remove(); } document.body.insertAdjacentHTML('beforeend', modalHtml); const modal = new bootstrap.Modal(document.getElementById('addMenuOptionModal')); modal.show(); }; window.editMenuOption = async function(menuId, optionId) { console.log('Editando opción:', { menuId, optionId }); try { // Obtener datos del menú y sus opciones const response = await window.whatsappManager.apiCall('get_menus.php'); if (response && response.success) { const menu = response.data.find(m => m.id == menuId); if (menu && menu.options) { const option = menu.options.find(o => o.id == optionId); if (option) { showEditMenuOptionModal(menuId, option); } else { window.whatsappManager.showError('Opción no encontrada'); } } else { window.whatsappManager.showError('Menú no encontrado'); } } else { window.whatsappManager.showError('Error cargando datos de la opción'); } } catch (error) { console.error('Error:', error); window.whatsappManager.showError('Error cargando opción: ' + error.message); } }; // Modal para editar opción de menú function showEditMenuOptionModal(menuId, optionData) { const modalHtml = ` `; // Remover modal anterior si existe const existingModal = document.getElementById('editMenuOptionModal'); if (existingModal) { existingModal.remove(); } document.body.insertAdjacentHTML('beforeend', modalHtml); const modal = new bootstrap.Modal(document.getElementById('editMenuOptionModal')); modal.show(); } // Función para guardar cambios en opción editada window.saveEditedMenuOption = async function() { const optionId = document.getElementById('editOptionId').value; const menuId = document.getElementById('editOptionMenuId').value; const optionKey = document.getElementById('editOptionKey').value.trim(); const optionText = document.getElementById('editOptionText').value.trim(); const optionAction = document.getElementById('editOptionAction').value; const optionValue = document.getElementById('editOptionValue').value.trim(); if (!optionKey || !optionText) { window.whatsappManager.showError('Clave y texto de la opción son obligatorios'); return; } console.log('Actualizando opción:', { optionId, menuId, optionKey, optionText }); const optionData = { option_id: optionId, menu_id: menuId, option_key: optionKey, option_text: optionText, option_action: optionAction, option_value: optionValue }; try { const response = await window.whatsappManager.apiCall('update_menu_option.php', { method: 'POST', body: optionData }); if (response && response.success) { window.whatsappManager.showSuccess('Opción actualizada correctamente'); // Cerrar modales const editModal = bootstrap.Modal.getInstance(document.getElementById('editMenuOptionModal')); if (editModal) editModal.hide(); const viewModal = bootstrap.Modal.getInstance(document.getElementById('viewMenuOptionsModal')); if (viewModal) viewModal.hide(); // Reabrir modal actualizado setTimeout(async () => { const menusResponse = await window.whatsappManager.apiCall('get_menus.php'); if (menusResponse && menusResponse.success) { const menu = menusResponse.data.find(m => m.id == menuId); if (menu) { showMenuOptionsModal(menu); } } }, 500); } else { window.whatsappManager.showError(`Error actualizando opción: ${response?.error || 'Error desconocido'}`); } } catch (error) { console.error('Error:', error); window.whatsappManager.showError('Error actualizando opción: ' + error.message); } }; window.saveNewMenuOption = async function() { const menuId = document.getElementById('addOptionMenuId').value; const optionKey = document.getElementById('addOptionKey').value.trim(); const optionText = document.getElementById('addOptionText').value.trim(); const optionAction = document.getElementById('addOptionAction').value; const optionValue = document.getElementById('addOptionValue').value.trim(); if (!optionKey || !optionText) { window.whatsappManager.showError('Clave y texto de la opción son obligatorios'); return; } console.log('Guardando nueva opción:', { menuId, optionKey, optionText, optionAction, optionValue }); const optionData = { menu_id: menuId, option_key: optionKey, option_text: optionText, option_action: optionAction, option_value: optionValue, is_active: true }; try { const response = await window.whatsappManager.apiCall('save_menu_option.php', { method: 'POST', body: optionData }); if (response && response.success) { window.whatsappManager.showSuccess('Opción agregada correctamente'); // Cerrar modal de agregar opción const addModal = bootstrap.Modal.getInstance(document.getElementById('addMenuOptionModal')); if (addModal) addModal.hide(); // Cerrar modal de ver opciones si está abierto const viewModal = bootstrap.Modal.getInstance(document.getElementById('viewMenuOptionsModal')); if (viewModal) viewModal.hide(); // Reabrir modal de ver opciones actualizado setTimeout(async () => { const menusResponse = await window.whatsappManager.apiCall('get_menus.php'); if (menusResponse && menusResponse.success) { const menu = menusResponse.data.find(m => m.id == menuId); if (menu) { showMenuOptionsModal(menu); } } }, 500); } else { window.whatsappManager.showError(`Error agregando opción: ${response?.error || 'Error desconocido'}`); } } catch (error) { console.error('Error:', error); window.whatsappManager.showError('Error agregando opción: ' + error.message); } }; window.deleteMenuOption = async function(menuId, optionId) { if (!confirm('¿Estás seguro de que deseas eliminar esta opción del menú?')) { return; } console.log('Eliminando opción:', { menuId, optionId }); try { const response = await window.whatsappManager.apiCall('delete_menu_option.php', { method: 'POST', body: { menu_id: menuId, option_id: optionId } }); if (response && response.success) { window.whatsappManager.showSuccess('Opción eliminada correctamente'); // Cerrar modal actual const viewModal = bootstrap.Modal.getInstance(document.getElementById('viewMenuOptionsModal')); if (viewModal) viewModal.hide(); // Reabrir modal actualizado setTimeout(async () => { const menusResponse = await window.whatsappManager.apiCall('get_menus.php'); if (menusResponse && menusResponse.success) { const menu = menusResponse.data.find(m => m.id == menuId); if (menu) { showMenuOptionsModal(menu); } } }, 500); } else { window.whatsappManager.showError(`Error eliminando opción: ${response?.error || 'Error desconocido'}`); } } catch (error) { console.error('Error:', error); window.whatsappManager.showError('Error eliminando opción: ' + error.message); } }; window.testMenuPreview = async function(menuId) { console.log('Mostrando vista previa del menú:', menuId); try { // Obtener datos del menú const response = await window.whatsappManager.apiCall('get_menus.php'); if (response && response.success) { const menu = response.data.find(m => m.id == menuId); if (menu) { showMenuPreviewModal(menu); } else { window.whatsappManager.showError('Menú no encontrado'); } } else { window.whatsappManager.showError('Error cargando datos del menú'); } } catch (error) { console.error('Error:', error); window.whatsappManager.showError('Error mostrando vista previa: ' + error.message); } }; // Modal de vista previa del menú estilo WhatsApp function showMenuPreviewModal(menuData) { const options = menuData.options || []; const welcomeMsg = menuData.welcome_message || `¡Hola! Bienvenido al ${menuData.title || menuData.name}`; // Construir el mensaje del menú como se vería en WhatsApp let menuMessage = `📋 *${menuData.title || menuData.name}*\n\n`; if (menuData.description) { menuMessage += `${menuData.description}\n\n`; } if (options.length > 0) { menuMessage += '*Opciones disponibles:*\n\n'; options.forEach(option => { const key = option.option_key || option.key || '?'; const text = option.option_text || option.text || 'Sin texto'; menuMessage += `${key}. ${text}\n`; }); menuMessage += '\n💬 *Responde con el número de la opción que deseas*'; } else { menuMessage += '⚠️ _Este menú no tiene opciones configuradas_'; } const modalHtml = ` `; // Remover modal anterior si existe const existingModal = document.getElementById('menuPreviewModal'); if (existingModal) { existingModal.remove(); } document.body.insertAdjacentHTML('beforeend', modalHtml); const modal = new bootstrap.Modal(document.getElementById('menuPreviewModal')); modal.show(); } // Función para copiar el mensaje de vista previa window.copyMenuPreview = function(message) { // Limpiar el mensaje de HTML const cleanMessage = message.replace(/<[^>]*>/g, ''); navigator.clipboard.writeText(cleanMessage).then(() => { window.whatsappManager.showSuccess('Mensaje copiado al portapapeles'); }).catch(err => { window.whatsappManager.showError('Error al copiar: ' + err.message); }); }; // Función para guardar nuevo menú desde el modal de index.php window.saveNewMenu = async function() { const menuName = document.getElementById('menu-name')?.value?.trim(); const menuDescription = document.getElementById('menu-description')?.value?.trim(); if (!menuName) { window.whatsappManager.showError('El nombre del menú es obligatorio'); return; } console.log('Guardando nuevo menú:', { menuName, menuDescription }); // Generar clave del menú basada en el nombre const menuKey = menuName.toLowerCase() .replace(/[^\w\s]/g, '') // Remover caracteres especiales .replace(/\s+/g, '_') // Reemplazar espacios con guiones bajos .substring(0, 20); // Limitar longitud const menuData = { name: menuName, menu_key: menuKey, description: menuDescription, welcome_message: `¡Hola! Bienvenido al ${menuName}. Por favor selecciona una opción:`, status: 'active', options: [] // Sin opciones inicialmente }; try { const response = await window.whatsappManager.apiCall('save_menu.php', { method: 'POST', body: menuData }); if (response && response.success) { window.whatsappManager.showSuccess('Menú creado correctamente'); // Limpiar formulario document.getElementById('menu-name').value = ''; document.getElementById('menu-description').value = ''; // Cerrar modal const modal = bootstrap.Modal.getInstance(document.getElementById('createMenuModal')); if (modal) modal.hide(); // Recargar lista de menús si existe la función setTimeout(() => { if (window.whatsappManager.loadMenus) { window.whatsappManager.loadMenus(); } if (typeof loadMenusTest === 'function') { loadMenusTest(); } }, 500); } else { window.whatsappManager.showError(`Error creando menú: ${response?.error || 'Error desconocido'}`); } } catch (error) { console.error('Error:', error); window.whatsappManager.showError('Error creando menú: ' + error.message); } }; // ========================= FUNCIONES GLOBALES ADICIONALES ========================= // Función para refrescar datos del dashboard window.refreshData = function() { console.log('Refrescando datos del dashboard'); if (window.whatsappManager) { window.whatsappManager.showInfo('Actualizando datos...'); // Recargar todas las secciones if (window.whatsappManager.loadUsers) { window.whatsappManager.loadUsers(); } if (window.whatsappManager.loadMenus) { window.whatsappManager.loadMenus(); } if (window.whatsappManager.loadTemplates) { window.whatsappManager.loadTemplates(); } if (window.whatsappManager.loadAutoResponses) { window.whatsappManager.loadAutoResponses(); } setTimeout(() => { window.whatsappManager.showSuccess('Datos actualizados correctamente'); }, 1000); } }; // Función para exportar usuarios window.exportConversations = function(filters) { const params = new URLSearchParams(filters || {}); const url = (window.whatsappManager ? window.whatsappManager.apiBaseUrl : 'api/') + 'export_conversations.php'; const full = params.toString() ? url + '?' + params.toString() : url; const a = document.createElement('a'); a.href = full; a.download = ''; document.body.appendChild(a); a.click(); document.body.removeChild(a); if (window.whatsappManager) window.whatsappManager.showSuccess('Descarga de conversaciones iniciada'); }; window.exportUsers = function() { console.log('Exportando usuarios...'); if (window.whatsappManager) { window.whatsappManager.showInfo('Preparando exportación de usuarios...'); // La API devuelve un archivo CSV directamente, así que abrimos en nueva ventana const url = window.whatsappManager.apiBaseUrl + 'export_users.php'; window.open(url, '_blank'); window.whatsappManager.showSuccess('Descarga de usuarios iniciada'); } }; // Función para buscar usuarios con debounce let searchUsersTimeout; window.searchUsers = function() { clearTimeout(searchUsersTimeout); searchUsersTimeout = setTimeout(() => { const searchInput = document.getElementById('users-search'); const searchTerm = searchInput ? searchInput.value.trim() : ''; if (window.whatsappManager && window.whatsappManager.loadUsers) { window.whatsappManager.loadUsers(1, searchTerm); } }, 500); }; // Función para sincronizar plantillas desde Facebook window.syncTemplatesFromFacebook = async function() { console.log('Sincronizando plantillas desde Facebook...'); if (!window.whatsappManager) { alert('Error: Sistema no inicializado'); return; } const btn = document.getElementById('btn-sync-templates'); if (btn) { btn.disabled = true; btn.innerHTML = ' Sincronizando...'; } try { window.whatsappManager.showInfo('Sincronizando plantillas desde Facebook/WhatsApp Business...'); const response = await window.whatsappManager.apiCall('sync_templates_from_facebook.php', { method: 'POST' }); if (response && response.success) { const data = response.data || {}; const message = ` ✅ Sincronización completada exitosamente: • ${data.synced || 0} plantillas nuevas • ${data.updated || 0} plantillas actualizadas • ${data.skipped || 0} sin cambios ${data.errors && data.errors.length > 0 ? '\n⚠️ ' + data.errors.length + ' errores encontrados' : ''} `; window.whatsappManager.showSuccess(message); // Recargar la lista de plantillas if (window.whatsappManager.loadTemplates) { window.whatsappManager.loadTemplates(); } // Mostrar errores si existen if (data.errors && data.errors.length > 0) { console.warn('Errores durante la sincronización:', data.errors); } } else { const errorMsg = response?.error || 'Error desconocido al sincronizar'; window.whatsappManager.showError(`Error al sincronizar plantillas: ${errorMsg}`); // Si el error es por falta de WABA ID, dar instrucciones if (errorMsg.includes('Business Account ID')) { setTimeout(() => { alert('💡 Para sincronizar plantillas necesitas configurar el Business Account ID (WABA ID).\n\n' + '1. Ve a la pestaña "Configuración"\n' + '2. Ingresa tu WABA ID en el campo correspondiente\n' + '3. Guarda la configuración\n' + '4. Vuelve a intentar la sincronización\n\n' + 'Puedes encontrar tu WABA ID en Meta Business Manager.'); }, 500); } } } catch (error) { console.error('Error sincronizando plantillas:', error); window.whatsappManager.showError('Error de conexión al sincronizar plantillas: ' + error.message); } finally { if (btn) { btn.disabled = false; btn.innerHTML = ' Sincronizar desde Facebook'; } } }; // Función para refrescar logs window.refreshLogs = function() { console.log('Refrescando logs...'); if (window.whatsappManager) { window.whatsappManager.showInfo('Actualizando logs...'); window.whatsappManager.loadLogs(); } else { alert('Error: Sistema no inicializado'); } }; // Función para filtrar logs window.filterLogs = function() { const searchTerm = (document.getElementById('log-search')?.value || '').toLowerCase(); const levelFilter = document.getElementById('log-level-filter')?.value || 'all'; const tbody = document.getElementById('logs-table'); if (!tbody) return; const rows = tbody.querySelectorAll('tr'); let visibleCount = 0; rows.forEach(row => { const level = row.getAttribute('data-level'); const message = row.getAttribute('data-message'); const source = row.getAttribute('data-source'); const matchesSearch = !searchTerm || (message && message.includes(searchTerm)) || (source && source.includes(searchTerm)); const matchesLevel = levelFilter === 'all' || level === levelFilter; if (matchesSearch && matchesLevel) { row.style.display = ''; visibleCount++; } else { row.style.display = 'none'; } }); console.log(`Mostrando ${visibleCount} de ${rows.length} logs`); }; // Función para limpiar logs window.clearLogs = async function() { console.log('Limpiando logs...'); if (!confirm('¿Estás seguro de que quieres limpiar todos los logs? Esta acción no se puede deshacer.')) { return; } if (window.whatsappManager) { try { const response = await window.whatsappManager.apiCall('clear_logs.php', { method: 'POST', body: { type: 'all' } }); if (response && response.success) { window.whatsappManager.showSuccess( `Logs limpiados correctamente. ${response.deleted_count || 0} registros eliminados.` ); // Recargar logs window.whatsappManager.loadLogs(); } else { window.whatsappManager.showError(`Error al limpiar logs: ${response?.error || 'Error desconocido'}`); } } catch (error) { window.whatsappManager.showError('Error al limpiar logs: ' + error.message); } } else { alert('Error: Sistema no inicializado'); } }; // ============================================ // GESTIÓN DE USUARIOS ADMINISTRADORES // ============================================ // Renderizar tabla de usuarios administradores function renderAdminUsersTable(users) { const tbody = document.getElementById('admin-users-table'); if (!tbody) return; if (!users || users.length === 0) { tbody.innerHTML = ` No hay usuarios registrados `; return; } tbody.innerHTML = users.map(user => { const isActive = parseInt(user.is_active) === 1; const lastLogin = user.last_login ? new Date(user.last_login).toLocaleString('es-CO') : 'Nunca'; const createdAt = new Date(user.created_at).toLocaleDateString('es-CO'); return ` ${escapeHtml(user.username)} ${escapeHtml(user.full_name || '-')} ${escapeHtml(user.email || '-')} ${isActive ? 'Activo' : 'Inactivo'} ${lastLogin} ${createdAt} `; }).join(''); } // Mostrar modal para crear usuario window.showCreateAdminUserModal = function() { const modal = new bootstrap.Modal(document.getElementById('adminUserModal')); document.getElementById('admin-user-modal-title').textContent = 'Nuevo Usuario'; document.getElementById('admin-user-form').reset(); document.getElementById('admin-user-id').value = ''; document.getElementById('password-group').style.display = 'block'; modal.show(); }; // Editar usuario existente window.editAdminUser = async function(userId) { try { const response = await window.whatsappManager.apiCall('list_admin_users.php'); if (response && response.success && response.data) { const user = response.data.find(u => parseInt(u.id) === parseInt(userId)); if (user) { document.getElementById('admin-user-modal-title').textContent = 'Editar Usuario'; document.getElementById('admin-user-id').value = user.id; document.getElementById('admin-username').value = user.username; document.getElementById('admin-fullname').value = user.full_name || ''; document.getElementById('admin-email').value = user.email || ''; document.getElementById('password-group').style.display = 'none'; const modal = new bootstrap.Modal(document.getElementById('adminUserModal')); modal.show(); } } } catch (error) { console.error('Error cargando usuario:', error); alert('Error al cargar datos del usuario'); } }; // Guardar usuario (crear o actualizar) window.saveAdminUser = async function() { const userId = document.getElementById('admin-user-id').value; const username = document.getElementById('admin-username').value.trim(); const fullName = document.getElementById('admin-fullname').value.trim(); const email = document.getElementById('admin-email').value.trim(); const password = document.getElementById('admin-password').value; if (!username) { alert('El nombre de usuario es requerido'); return; } // Si es nuevo usuario, validar contraseña if (!userId && !password) { alert('La contraseña es requerida para usuarios nuevos'); return; } if (!userId && password.length < 6) { alert('La contraseña debe tener al menos 6 caracteres'); return; } try { let response; if (userId) { // Actualizar usuario existente response = await window.whatsappManager.apiCall('update_admin_user.php', { method: 'POST', body: { user_id: userId, username, full_name: fullName, email } }); } else { // Crear nuevo usuario response = await window.whatsappManager.apiCall('create_admin_user.php', { method: 'POST', body: { username, full_name: fullName, email, password } }); } if (response && response.success) { window.whatsappManager.showSuccess(response.message || 'Usuario guardado correctamente'); bootstrap.Modal.getInstance(document.getElementById('adminUserModal')).hide(); window.whatsappManager.loadAdminUsers(); } else { alert(response?.error || 'Error al guardar usuario'); } } catch (error) { console.error('Error guardando usuario:', error); alert('Error al guardar usuario: ' + error.message); } }; // Cambiar contraseña de usuario window.changeAdminPassword = function(userId, username) { document.getElementById('change-password-user-id').value = userId; document.getElementById('change-password-username').textContent = username; document.getElementById('new-password').value = ''; document.getElementById('confirm-password').value = ''; const modal = new bootstrap.Modal(document.getElementById('changePasswordModal')); modal.show(); }; // Guardar nueva contraseña window.saveNewPassword = async function() { const userId = document.getElementById('change-password-user-id').value; const newPassword = document.getElementById('new-password').value; const confirmPassword = document.getElementById('confirm-password').value; if (!newPassword || newPassword.length < 6) { alert('La contraseña debe tener al menos 6 caracteres'); return; } if (newPassword !== confirmPassword) { alert('Las contraseñas no coinciden'); return; } try { const response = await window.whatsappManager.apiCall('change_admin_password.php', { method: 'POST', body: { user_id: userId, new_password: newPassword } }); if (response && response.success) { window.whatsappManager.showSuccess('Contraseña actualizada correctamente'); bootstrap.Modal.getInstance(document.getElementById('changePasswordModal')).hide(); } else { alert(response?.error || 'Error al cambiar contraseña'); } } catch (error) { console.error('Error cambiando contraseña:', error); alert('Error al cambiar contraseña: ' + error.message); } }; // Activar/Desactivar usuario window.toggleAdminUserStatus = async function(userId, isActive) { const action = isActive ? 'activar' : 'desactivar'; if (!confirm(`¿Está seguro de ${action} este usuario?`)) { return; } try { const response = await window.whatsappManager.apiCall('toggle_admin_user_status.php', { method: 'POST', body: { user_id: userId, is_active: isActive } }); if (response && response.success) { window.whatsappManager.showSuccess(`Usuario ${isActive ? 'activado' : 'desactivado'} correctamente`); window.whatsappManager.loadAdminUsers(); } else { alert(response?.error || `Error al ${action} usuario`); } } catch (error) { console.error(`Error ${action}ando usuario:`, error); alert(`Error al ${action} usuario: ` + error.message); } }; // ───────────────────────────────────────────────────────────────────────────── // PERFIL DE WHATSAPP BUSINESS // ───────────────────────────────────────────────────────────────────────────── window.loadWhatsAppProfile = async function() { if (!window.whatsappManager) return; window.whatsappManager.showInfo('Cargando perfil desde WhatsApp...'); try { const res = await window.whatsappManager.apiCall('get_whatsapp_profile.php'); if (!res || !res.success) { window.whatsappManager.showError(res?.error || 'Error al obtener el perfil'); return; } const p = res.data || {}; document.getElementById('wp-about').value = p.about || ''; document.getElementById('wp-description').value = p.description || ''; document.getElementById('wp-address').value = p.address || ''; document.getElementById('wp-email').value = p.email || ''; document.getElementById('wp-website').value = (p.websites && p.websites[0]) ? p.websites[0] : ''; const vertSel = document.getElementById('wp-vertical'); if (vertSel && p.vertical) { vertSel.value = p.vertical; } // Foto de perfil const img = document.getElementById('wp-profile-photo-preview'); const placeholder = document.getElementById('wp-profile-photo-placeholder'); if (p.profile_picture_url && img) { img.src = p.profile_picture_url; img.style.display = 'inline-block'; if (placeholder) placeholder.style.display = 'none'; } window.whatsappManager.showSuccess('Perfil cargado correctamente'); } catch (e) { window.whatsappManager.showError('Error: ' + e.message); } }; window.saveWhatsAppProfile = async function() { if (!window.whatsappManager) return; const payload = { about: document.getElementById('wp-about')?.value?.trim() || '', description: document.getElementById('wp-description')?.value?.trim() || '', address: document.getElementById('wp-address')?.value?.trim() || '', email: document.getElementById('wp-email')?.value?.trim() || '', vertical: document.getElementById('wp-vertical')?.value || '', websites: [document.getElementById('wp-website')?.value?.trim()].filter(Boolean), }; try { window.whatsappManager.showInfo('Guardando perfil...'); const res = await window.whatsappManager.apiCall('update_whatsapp_profile.php', { method: 'POST', body: payload, }); if (res && res.success) { window.whatsappManager.showSuccess(res.message || 'Perfil actualizado correctamente'); } else { window.whatsappManager.showError(res?.error || 'Error al guardar el perfil'); } } catch (e) { window.whatsappManager.showError('Error: ' + e.message); } }; window.uploadWhatsAppProfilePhoto = async function() { const fileInput = document.getElementById('wp-photo-file'); if (!fileInput || !fileInput.files || fileInput.files.length === 0) { alert('Selecciona una foto primero'); return; } const formData = new FormData(); formData.append('photo', fileInput.files[0]); try { window.whatsappManager.showInfo('Subiendo foto de perfil...'); const baseUrl = window.whatsappManager.baseUrl || 'api/'; const response = await fetch(baseUrl + 'upload_whatsapp_profile_photo.php', { method: 'POST', body: formData, credentials: 'same-origin', }); const res = await response.json(); if (res && res.success) { window.whatsappManager.showSuccess(res.message || 'Foto actualizada correctamente'); // Actualizar preview local const reader = new FileReader(); reader.onload = (e) => { const img = document.getElementById('wp-profile-photo-preview'); const ph = document.getElementById('wp-profile-photo-placeholder'); if (img) { img.src = e.target.result; img.style.display = 'inline-block'; } if (ph) { ph.style.display = 'none'; } }; reader.readAsDataURL(fileInput.files[0]); fileInput.value = ''; } else { window.whatsappManager.showError(res?.error || 'Error al subir la foto'); } } catch (e) { window.whatsappManager.showError('Error: ' + e.message); } }; // Cargar perfil al abrir la pestaña document.addEventListener('DOMContentLoaded', () => { const profileLinks = document.querySelectorAll('[data-tab="whatsapp_profile"]'); profileLinks.forEach(link => { link.addEventListener('click', () => { // Pequeño delay para que el tab esté visible setTimeout(() => window.loadWhatsAppProfile && window.loadWhatsAppProfile(), 100); }); }); }); // ───────────────────────────────────────────────────────────────────────────── // RESET + RE-SINCRONIZACIÓN DE PLANTILLAS // ───────────────────────────────────────────────────────────────────────────── window.resetAndSyncTemplates = async function() { if (!window.whatsappManager) { alert('Error: Sistema no inicializado'); return; } const confirmed = confirm( '⚠️ ATENCIÓN: Esta acción eliminará TODAS las plantillas locales y las volverá a importar desde WhatsApp/Facebook.\n\n' + '¿Estás seguro de que deseas continuar?' ); if (!confirmed) return; const btn = document.getElementById('btn-reset-sync-templates'); if (btn) { btn.disabled = true; btn.innerHTML = ' Procesando...'; } try { window.whatsappManager.showInfo('Eliminando plantillas locales y re-sincronizando desde WhatsApp...'); const response = await window.whatsappManager.apiCall('reset_and_sync_templates.php', { method: 'POST' }); if (response && response.success) { const d = response.data || {}; const msg = `✅ ${response.message}\n• Recibidas de Facebook: ${d.total_facebook || 0}\n• Importadas: ${d.imported || 0}${d.errors?.length ? '\n⚠️ ' + d.errors.length + ' errores' : ''}`; window.whatsappManager.showSuccess(msg); if (window.whatsappManager.loadTemplates) window.whatsappManager.loadTemplates(); } else { window.whatsappManager.showError(response?.error || 'Error desconocido al re-sincronizar'); } } catch (e) { window.whatsappManager.showError('Error de conexión: ' + e.message); } finally { if (btn) { btn.disabled = false; btn.innerHTML = ' Limpiar y Re-sincronizar'; } } };