1332 lines
46 KiB
JavaScript
1332 lines
46 KiB
JavaScript
/**
|
|
* WhatsApp Bot Manager - JavaScript
|
|
* Fecha: 13 de noviembre de 2025
|
|
*/
|
|
|
|
class WhatsAppBotManager {
|
|
constructor() {
|
|
this.apiBaseUrl = './api/';
|
|
this.currentTab = 'dashboard';
|
|
this.currentMenuId = null;
|
|
this.charts = {};
|
|
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.setupEventListeners();
|
|
this.loadDashboard();
|
|
this.setupCharts();
|
|
|
|
// Auto-refresh cada 30 segundos
|
|
setInterval(() => {
|
|
if (this.currentTab === 'dashboard') {
|
|
this.refreshStats();
|
|
}
|
|
}, 30000);
|
|
|
|
// Verificar sesión cada 5 minutos
|
|
setInterval(() => {
|
|
this.checkSession();
|
|
}, 300000); // 5 minutos
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Navegación de tabs - solo enlaces que tengan data-tab
|
|
document.querySelectorAll('.nav-link[data-tab]').forEach(link => {
|
|
link.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
const tabName = link.getAttribute('data-tab');
|
|
if (tabName) {
|
|
this.showTab(tabName);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Formularios
|
|
this.setupFormListeners();
|
|
|
|
// Búsquedas
|
|
this.setupSearchListeners();
|
|
}
|
|
|
|
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 menú
|
|
const menuForm = document.getElementById('menu-form');
|
|
if (menuForm) {
|
|
menuForm.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
this.saveMenu();
|
|
});
|
|
}
|
|
|
|
// Formulario de envío de mensaje
|
|
const sendMessageForm = document.getElementById('send-message-form');
|
|
if (sendMessageForm) {
|
|
sendMessageForm.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
this.sendMessage();
|
|
});
|
|
}
|
|
|
|
// Formulario de mensaje masivo
|
|
const broadcastForm = document.getElementById('broadcast-form');
|
|
if (broadcastForm) {
|
|
broadcastForm.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
this.sendBroadcast();
|
|
});
|
|
}
|
|
|
|
// Cambio de tipo de mensaje
|
|
const messageType = document.getElementById('message-type');
|
|
if (messageType) {
|
|
messageType.addEventListener('change', () => {
|
|
this.toggleMessageFields();
|
|
});
|
|
}
|
|
}
|
|
|
|
setupSearchListeners() {
|
|
// Búsqueda de conversaciones
|
|
const searchConversations = document.getElementById('search-conversations');
|
|
if (searchConversations) {
|
|
searchConversations.addEventListener('input', (e) => {
|
|
this.searchConversations(e.target.value);
|
|
});
|
|
}
|
|
}
|
|
|
|
showTab(tabName) {
|
|
// Ocultar todas las pestañas
|
|
document.querySelectorAll('.tab-content').forEach(tab => {
|
|
tab.classList.remove('active');
|
|
});
|
|
|
|
// Remover clase active de navegación
|
|
document.querySelectorAll('.nav-link').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;
|
|
|
|
// Cargar datos específicos del tab
|
|
this.loadTabData(tabName);
|
|
}
|
|
|
|
loadTabData(tabName) {
|
|
switch (tabName) {
|
|
case 'dashboard':
|
|
this.loadDashboard();
|
|
break;
|
|
case 'conversations':
|
|
this.loadConversations();
|
|
break;
|
|
case 'users':
|
|
this.loadUsers();
|
|
break;
|
|
case 'menus':
|
|
this.loadMenus();
|
|
break;
|
|
case 'send_message':
|
|
// Cargar lista de usuarios y plantillas cuando se muestra el tab de Enviar Mensaje
|
|
this.loadMessageUsers();
|
|
break;
|
|
case 'templates':
|
|
this.loadTemplates();
|
|
break;
|
|
case 'autoresponses':
|
|
this.loadAutoResponses();
|
|
break;
|
|
case 'system_config':
|
|
this.loadsystem_config();
|
|
break;
|
|
case 'logs':
|
|
this.loadLogs();
|
|
break;
|
|
}
|
|
}
|
|
|
|
async loadDashboard() {
|
|
try {
|
|
const stats = await this.apiCall('get_stats.php');
|
|
this.updateStats(stats);
|
|
|
|
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);
|
|
}
|
|
|
|
this.updateChart();
|
|
} catch (error) {
|
|
this.showError('Error cargando dashboard: ' + error.message);
|
|
}
|
|
}
|
|
|
|
updateStats(stats) {
|
|
if (stats) {
|
|
document.getElementById('total-users').textContent = stats.total_users || 0;
|
|
document.getElementById('conversations-today').textContent = stats.conversations_today || 0;
|
|
document.getElementById('active-users').textContent = stats.active_users || 0;
|
|
document.getElementById('total-conversations').textContent = stats.total_conversations || 0;
|
|
}
|
|
}
|
|
|
|
updateRecentconversations(conversations) {
|
|
const container = document.getElementById('recent-conversations');
|
|
if (!container) return;
|
|
|
|
container.innerHTML = '';
|
|
|
|
if (conversations && conversations.length > 0) {
|
|
conversations.forEach(message => {
|
|
const messageElement = this.createRecentMessageElement(message);
|
|
container.appendChild(messageElement);
|
|
});
|
|
} else {
|
|
container.innerHTML = '<div class="text-center text-muted">No hay mensajes recientes</div>';
|
|
}
|
|
}
|
|
|
|
createRecentMessageElement(message) {
|
|
const div = document.createElement('div');
|
|
div.className = 'recent-message';
|
|
|
|
const direction = message.direction === 'incoming' ? '📥' : '📤';
|
|
const time = new Date(message.created_at).toLocaleString();
|
|
|
|
div.innerHTML = `
|
|
<div class="d-flex justify-content-between">
|
|
<span class="fw-bold">${direction} ${message.phone_number}</span>
|
|
<span class="time">${time}</span>
|
|
</div>
|
|
<div class="content">${this.truncateText(message.content, 50)}</div>
|
|
`;
|
|
|
|
return div;
|
|
}
|
|
|
|
setupCharts() {
|
|
const ctx = document.getElementById('conversationsChart');
|
|
if (ctx) {
|
|
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
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
async updateChart() {
|
|
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();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating chart:', error);
|
|
}
|
|
}
|
|
|
|
async loadConversations() {
|
|
try {
|
|
const response = await this.apiCall('get_conversations.php');
|
|
console.log('Respuesta get_conversations:', response); // Debug
|
|
|
|
if (response && response.success && Array.isArray(response.data)) {
|
|
this.updateConversationsTable(response.data);
|
|
} else if (response && Array.isArray(response)) {
|
|
// Retrocompatibilidad por si la API devuelve directamente el array
|
|
this.updateConversationsTable(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);
|
|
}
|
|
}
|
|
|
|
updateConversationsTable(conversations) {
|
|
const tbody = document.getElementById('conversations-table');
|
|
if (!tbody) return;
|
|
|
|
tbody.innerHTML = '';
|
|
|
|
console.log('Datos para tabla de conversaciones:', conversations); // Debug
|
|
|
|
if (!Array.isArray(conversations)) {
|
|
console.error('conversations no es un array:', typeof conversations, conversations);
|
|
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-muted">Error: datos inválidos</td></tr>';
|
|
return;
|
|
}
|
|
|
|
if (conversations.length === 0) {
|
|
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-muted">No hay conversaciones</td></tr>';
|
|
return;
|
|
}
|
|
|
|
conversations.forEach(conv => {
|
|
const row = this.createConversationRow(conv);
|
|
tbody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
createConversationRow(conv) {
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML = `
|
|
<td>
|
|
<div class="fw-bold">${conv.phone_number}</div>
|
|
<small class="text-muted">${conv.name || 'Sin nombre'}</small>
|
|
</td>
|
|
<td>${this.truncateText(conv.last_message, 50)}</td>
|
|
<td>
|
|
<span class="badge badge-${this.getMessageTypeColor(conv.message_type)}">
|
|
${conv.message_type}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<span class="badge badge-${this.getStatusColor(conv.status)}">
|
|
${conv.status}
|
|
</span>
|
|
</td>
|
|
<td>${new Date(conv.last_activity).toLocaleString()}</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-primary" onclick="app.viewConversation('${conv.user_id}')">
|
|
<i class="fas fa-eye"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-success" onclick="app.replyToUser('${conv.phone_number}')">
|
|
<i class="fas fa-reply"></i>
|
|
</button>
|
|
</td>
|
|
`;
|
|
return tr;
|
|
}
|
|
|
|
async loadUsers() {
|
|
try {
|
|
const response = await this.apiCall('get_users.php');
|
|
if (response && response.success && Array.isArray(response.data)) {
|
|
this.updateUsersTable(response.data);
|
|
} else if (response && Array.isArray(response)) {
|
|
// Retrocompatibilidad por si la API devuelve directamente el array
|
|
this.updateUsersTable(response);
|
|
} else {
|
|
this.showError('Error: formato de datos inválido para usuarios');
|
|
}
|
|
} catch (error) {
|
|
this.showError('Error cargando usuarios: ' + error.message);
|
|
}
|
|
}
|
|
|
|
updateUsersTable(users) {
|
|
const tbody = document.getElementById('users-table');
|
|
if (!tbody) return;
|
|
|
|
tbody.innerHTML = '';
|
|
|
|
users.forEach(user => {
|
|
const row = this.createUserRow(user);
|
|
tbody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
createUserRow(user) {
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML = `
|
|
<td>${user.id}</td>
|
|
<td>${user.phone_number}</td>
|
|
<td>${user.name || 'Sin nombre'}</td>
|
|
<td>
|
|
<span class="badge badge-${this.getStatusColor(user.status)}">
|
|
${user.status}
|
|
</span>
|
|
</td>
|
|
<td>${user.current_menu || 'Ninguno'}</td>
|
|
<td>${new Date(user.created_at).toLocaleString()}</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-primary" onclick="app.editUser(${user.id})">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-warning" onclick="app.blockUser(${user.id})">
|
|
<i class="fas fa-ban"></i>
|
|
</button>
|
|
</td>
|
|
`;
|
|
return tr;
|
|
}
|
|
|
|
async loadMenus() {
|
|
try {
|
|
const response = await this.apiCall('get_menus.php');
|
|
let menus = response;
|
|
if (response && response.success && Array.isArray(response.data)) {
|
|
menus = response.data;
|
|
} else if (!Array.isArray(response)) {
|
|
this.showError('Error: formato de datos inválido para menús');
|
|
return;
|
|
}
|
|
this.updateMenusTree(menus);
|
|
this.loadMenuParents(menus);
|
|
} catch (error) {
|
|
this.showError('Error cargando menús: ' + error.message);
|
|
}
|
|
}
|
|
|
|
updateMenusTree(menus) {
|
|
const container = document.getElementById('menus-tree');
|
|
if (!container) return;
|
|
|
|
container.innerHTML = '';
|
|
|
|
// Organizar menús por jerarquía
|
|
const rootMenus = menus.filter(menu => menu.is_root);
|
|
|
|
rootMenus.forEach(menu => {
|
|
const menuElement = this.createMenuElement(menu, menus);
|
|
container.appendChild(menuElement);
|
|
});
|
|
}
|
|
|
|
createMenuElement(menu, allMenus) {
|
|
const div = document.createElement('div');
|
|
div.className = 'menu-item ' + (menu.is_root ? 'root' : 'child');
|
|
|
|
div.innerHTML = `
|
|
<div>
|
|
<h6 class="mb-1">${menu.title}</h6>
|
|
<small class="text-muted">${menu.description || 'Sin descripción'}</small>
|
|
<div class="mt-2">
|
|
<span class="badge badge-${menu.is_active ? 'success' : 'secondary'}">
|
|
${menu.is_active ? 'Activo' : 'Inactivo'}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<button class="btn btn-sm btn-primary me-2" onclick="app.editMenu(${menu.id})">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-info me-2" onclick="app.manageMenuOptions(${menu.id})">
|
|
<i class="fas fa-list"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-danger" onclick="app.deleteMenu(${menu.id})">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
// Agregar submenús
|
|
const childMenus = allMenus.filter(child => child.parent_id === menu.id);
|
|
if (childMenus.length > 0) {
|
|
const childContainer = document.createElement('div');
|
|
childContainer.style.marginLeft = '20px';
|
|
childContainer.style.marginTop = '10px';
|
|
|
|
childMenus.forEach(child => {
|
|
const childElement = this.createMenuElement(child, allMenus);
|
|
childContainer.appendChild(childElement);
|
|
});
|
|
|
|
div.appendChild(childContainer);
|
|
}
|
|
|
|
return div;
|
|
}
|
|
|
|
loadMenuParents(menus) {
|
|
const select = document.getElementById('menu-parent');
|
|
if (!select) return;
|
|
|
|
select.innerHTML = '<option value="">Sin padre (Menú raíz)</option>';
|
|
|
|
menus.forEach(menu => {
|
|
const option = document.createElement('option');
|
|
option.value = menu.id;
|
|
option.textContent = menu.title;
|
|
select.appendChild(option);
|
|
});
|
|
}
|
|
|
|
async loadMessageUsers() {
|
|
const userSelect = document.getElementById('message-recipient');
|
|
const templateSelect = document.getElementById('message-template');
|
|
|
|
// Mostrar estados de carga
|
|
if (userSelect) userSelect.innerHTML = '<option value="">Cargando usuarios...</option>';
|
|
if (templateSelect) templateSelect.innerHTML = '<option value="">Cargando plantillas...</option>';
|
|
|
|
try {
|
|
const usersResponse = await this.apiCall('get_users.php');
|
|
const templatesResponse = await this.apiCall('get_templates.php');
|
|
|
|
let users = [];
|
|
if (usersResponse && usersResponse.success && Array.isArray(usersResponse.data)) {
|
|
users = usersResponse.data;
|
|
} else if (Array.isArray(usersResponse)) {
|
|
users = usersResponse;
|
|
}
|
|
|
|
let templates = [];
|
|
if (templatesResponse && templatesResponse.success && Array.isArray(templatesResponse.data)) {
|
|
templates = templatesResponse.data;
|
|
} else if (Array.isArray(templatesResponse)) {
|
|
templates = templatesResponse;
|
|
}
|
|
|
|
console.log('loadMessageUsers - users:', users.length, 'templates:', templates.length);
|
|
|
|
// Actualizar selects
|
|
this.updateMessageUserSelect(users);
|
|
this.updateTemplateSelect(templates);
|
|
} catch (error) {
|
|
console.error('Error loadMessageUsers:', error);
|
|
if (userSelect) userSelect.innerHTML = '<option value="">Error cargando usuarios</option>';
|
|
if (templateSelect) templateSelect.innerHTML = '<option value="">Error cargando plantillas</option>';
|
|
this.showError('Error cargando usuarios o plantillas: ' + (error.message || error));
|
|
}
|
|
}
|
|
|
|
updateMessageUserSelect(users) {
|
|
const select = document.getElementById('message-recipient');
|
|
if (!select) return;
|
|
|
|
select.innerHTML = '<option value="">Seleccionar usuario...</option>';
|
|
|
|
users.forEach(user => {
|
|
const option = document.createElement('option');
|
|
option.value = user.phone_number;
|
|
option.textContent = `${user.phone_number} ${user.name ? '(' + user.name + ')' : ''}`;
|
|
select.appendChild(option);
|
|
});
|
|
}
|
|
|
|
updateTemplateSelect(templates) {
|
|
const select = document.getElementById('message-template');
|
|
if (!select) return;
|
|
|
|
select.innerHTML = '<option value="">Seleccionar plantilla...</option>';
|
|
|
|
templates.forEach(template => {
|
|
if (template.status === 'approved') {
|
|
const option = document.createElement('option');
|
|
option.value = template.template_name;
|
|
option.textContent = template.name;
|
|
select.appendChild(option);
|
|
}
|
|
});
|
|
}
|
|
|
|
toggleMessageFields() {
|
|
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';
|
|
}
|
|
}
|
|
|
|
async sendMessage() {
|
|
const recipientType = document.getElementById('recipient-type').value;
|
|
const messageType = document.getElementById('message-type').value;
|
|
|
|
let recipient;
|
|
if (recipientType === 'existing') {
|
|
recipient = document.getElementById('message-recipient').value;
|
|
} else {
|
|
recipient = document.getElementById('manual-recipient').value.trim();
|
|
|
|
// Para números manuales, solo permitir plantillas
|
|
if (messageType !== 'template') {
|
|
this.showError('Para números manuales solo se pueden enviar plantillas aprobadas');
|
|
return;
|
|
}
|
|
}
|
|
|
|
const messageText = document.getElementById('message-text').value;
|
|
const template = document.getElementById('message-template').value;
|
|
|
|
if (!recipient) {
|
|
this.showError('Por favor ingresa un destinatario');
|
|
return;
|
|
}
|
|
|
|
// Validar formato de número manual
|
|
if (recipientType === 'manual') {
|
|
const phoneRegex = /^\+\d{10,15}$/;
|
|
if (!phoneRegex.test(recipient)) {
|
|
this.showError('El número debe tener el formato: +57XXXXXXXXX (10-15 dígitos)');
|
|
return;
|
|
}
|
|
}
|
|
|
|
const data = {
|
|
recipient: recipient,
|
|
type: messageType
|
|
};
|
|
|
|
if (messageType === 'text') {
|
|
if (!messageText) {
|
|
this.showError('Por favor escribe un mensaje');
|
|
return;
|
|
}
|
|
data.message = messageText;
|
|
} else if (messageType === 'template') {
|
|
if (!template) {
|
|
this.showError('Por favor selecciona una plantilla');
|
|
return;
|
|
}
|
|
data.template = template;
|
|
}
|
|
|
|
try {
|
|
this.showLoading('Enviando mensaje...');
|
|
const result = await this.apiCall('send_message.php', 'POST', data);
|
|
|
|
if (result.success) {
|
|
this.showSuccess('Mensaje enviado correctamente');
|
|
document.getElementById('send-message-form').reset();
|
|
|
|
// Resetear la interfaz
|
|
this.resetMessageForm();
|
|
|
|
// Actualizar conversaciones si es necesario
|
|
if (this.currentTab === 'conversations') {
|
|
this.loadConversations();
|
|
}
|
|
} else {
|
|
this.showError(result.error || 'Error enviando mensaje');
|
|
}
|
|
} catch (error) {
|
|
this.showError('Error enviando mensaje: ' + error.message);
|
|
} finally {
|
|
this.hideLoading();
|
|
}
|
|
}
|
|
|
|
resetMessageForm() {
|
|
document.getElementById('recipient-type').value = 'existing';
|
|
document.getElementById('message-type').value = 'text';
|
|
toggleRecipientType();
|
|
toggleMessageType();
|
|
}
|
|
} else {
|
|
this.showError(result.error || 'Error enviando mensaje');
|
|
}
|
|
} catch (error) {
|
|
this.showError('Error enviando mensaje: ' + error.message);
|
|
} finally {
|
|
this.hideLoading();
|
|
}
|
|
}
|
|
|
|
async sendBroadcast() {
|
|
// Funcionalidad de envío masivo deshabilitada temporalmente desde UI
|
|
this.showError('Envío masivo deshabilitado temporalmente');
|
|
return;
|
|
}
|
|
|
|
async saveMenu() {
|
|
const formData = {
|
|
name: document.getElementById('menu-name').value,
|
|
title: document.getElementById('menu-title').value,
|
|
description: document.getElementById('menu-description').value,
|
|
parent_id: document.getElementById('menu-parent').value || null,
|
|
is_active: document.getElementById('menu-active').checked ? 1 : 0
|
|
};
|
|
|
|
try {
|
|
this.showLoading('Guardando menú...');
|
|
const result = await this.apiCall('save_menu.php', 'POST', formData);
|
|
|
|
if (result.success) {
|
|
this.showSuccess('Menú guardado correctamente');
|
|
document.getElementById('menu-form').reset();
|
|
this.loadMenus();
|
|
} else {
|
|
this.showError(result.error || 'Error guardando menú');
|
|
}
|
|
} catch (error) {
|
|
this.showError('Error guardando menú: ' + error.message);
|
|
} finally {
|
|
this.hideLoading();
|
|
}
|
|
}
|
|
|
|
async savesystem_config() {
|
|
const formData = {
|
|
whatsapp_token: document.getElementById('whatsapp-token').value,
|
|
phone_number_id: document.getElementById('phone-number-id').value,
|
|
webhook_token: document.getElementById('webhook-token').value,
|
|
business_name: document.getElementById('business-name').value,
|
|
welcome_message: document.getElementById('welcome-message').value
|
|
};
|
|
|
|
try {
|
|
this.showLoading('Guardando configuración...');
|
|
const result = await this.apiCall('save_system_config.php', 'POST', formData);
|
|
|
|
if (result.success) {
|
|
this.showSuccess('Configuración guardada correctamente');
|
|
} else {
|
|
this.showError(result.error || 'Error guardando configuración');
|
|
}
|
|
} catch (error) {
|
|
this.showError('Error guardando configuración: ' + error.message);
|
|
} finally {
|
|
this.hideLoading();
|
|
}
|
|
}
|
|
|
|
async loadsystem_config() {
|
|
try {
|
|
const system_config = await this.apiCall('get_system_config.php');
|
|
|
|
if (system_config) {
|
|
document.getElementById('whatsapp-token').value = system_config.whatsapp_token || '';
|
|
document.getElementById('phone-number-id').value = system_config.phone_number_id || '';
|
|
document.getElementById('webhook-token').value = system_config.webhook_token || '';
|
|
document.getElementById('business-name').value = system_config.business_name || '';
|
|
document.getElementById('welcome-message').value = system_config.welcome_message || '';
|
|
}
|
|
} catch (error) {
|
|
this.showError('Error cargando configuración: ' + 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) return;
|
|
|
|
tbody.innerHTML = '';
|
|
|
|
logs.forEach(log => {
|
|
const row = this.createLogRow(log);
|
|
tbody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
createLogRow(log) {
|
|
const tr = document.createElement('tr');
|
|
tr.innerHTML = `
|
|
<td>${new Date(log.created_at).toLocaleString()}</td>
|
|
<td>${log.ip_address}</td>
|
|
<td>
|
|
<span class="badge badge-${log.status_code === 200 ? 'success' : 'danger'}">
|
|
${log.status_code}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="app.viewLogDetails('${log.id}', 'request')">
|
|
Ver Request
|
|
</button>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-outline-primary" onclick="app.viewLogDetails('${log.id}', 'response')">
|
|
Ver Response
|
|
</button>
|
|
</td>
|
|
`;
|
|
return tr;
|
|
}
|
|
|
|
// Utility methods
|
|
async apiCall(endpoint, method = 'GET', data = null) {
|
|
const options = {
|
|
method: method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest' // Para identificar peticiones AJAX
|
|
}
|
|
};
|
|
|
|
if (data && method !== 'GET') {
|
|
options.body = JSON.stringify(data);
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(this.apiBaseUrl + endpoint, options);
|
|
|
|
// Verificar si es error de autenticación (401)
|
|
if (response.status === 401) {
|
|
try {
|
|
const errorData = await response.json();
|
|
if (errorData.redirect) {
|
|
this.showError(errorData.error || 'Sesión expirada. Redirigiendo al login...');
|
|
setTimeout(() => {
|
|
window.location.href = errorData.redirect;
|
|
}, 2000);
|
|
return null;
|
|
}
|
|
} catch (e) {
|
|
// Si no puede parsear JSON, redirigir al login
|
|
this.showError('Sesión expirada. Redirigiendo al login...');
|
|
setTimeout(() => {
|
|
window.location.href = 'login.php';
|
|
}, 2000);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
// Verificar si la respuesta indica problemas de autenticación
|
|
if (result.success === false && result.redirect) {
|
|
this.showError(result.error || 'Sesión expirada. Redirigiendo al login...');
|
|
setTimeout(() => {
|
|
window.location.href = result.redirect;
|
|
}, 2000);
|
|
return null;
|
|
}
|
|
|
|
return result;
|
|
|
|
} catch (error) {
|
|
console.error('Error en apiCall:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
showError(message) {
|
|
this.showNotification(message, 'error');
|
|
}
|
|
|
|
showSuccess(message) {
|
|
this.showNotification(message, 'success');
|
|
}
|
|
|
|
showWarning(message) {
|
|
this.showNotification(message, 'warning');
|
|
}
|
|
|
|
showInfo(message) {
|
|
this.showNotification(message, 'info');
|
|
}
|
|
|
|
showNotification(message, type = 'info') {
|
|
// Crear notificación toast
|
|
const toast = document.createElement('div');
|
|
toast.className = `alert alert-${type} position-fixed`;
|
|
toast.style.top = '20px';
|
|
toast.style.right = '20px';
|
|
toast.style.zIndex = '9999';
|
|
toast.style.minWidth = '300px';
|
|
toast.innerHTML = `
|
|
<i class="fas fa-${this.getNotificationIcon(type)}"></i>
|
|
${message}
|
|
<button type="button" class="btn-close" onclick="this.parentElement.remove()"></button>
|
|
`;
|
|
|
|
document.body.appendChild(toast);
|
|
|
|
// Auto-remove después de 5 segundos
|
|
setTimeout(() => {
|
|
if (toast.parentElement) {
|
|
toast.remove();
|
|
}
|
|
}, 5000);
|
|
}
|
|
|
|
getNotificationIcon(type) {
|
|
const icons = {
|
|
success: 'check-circle',
|
|
error: 'exclamation-circle',
|
|
warning: 'exclamation-triangle',
|
|
info: 'info-circle'
|
|
};
|
|
return icons[type] || 'info-circle';
|
|
}
|
|
|
|
showLoading(message = 'Cargando...') {
|
|
const loading = document.createElement('div');
|
|
loading.id = 'loading-overlay';
|
|
loading.className = 'position-fixed w-100 h-100 d-flex align-items-center justify-content-center';
|
|
loading.style.top = '0';
|
|
loading.style.left = '0';
|
|
loading.style.backgroundColor = 'rgba(0,0,0,0.5)';
|
|
loading.style.zIndex = '9999';
|
|
loading.innerHTML = `
|
|
<div class="bg-white p-4 rounded text-center">
|
|
<div class="spinner mb-3"></div>
|
|
<div>${message}</div>
|
|
</div>
|
|
`;
|
|
|
|
document.body.appendChild(loading);
|
|
}
|
|
|
|
hideLoading() {
|
|
const loading = document.getElementById('loading-overlay');
|
|
if (loading) {
|
|
loading.remove();
|
|
}
|
|
}
|
|
|
|
truncateText(text, length) {
|
|
if (!text) return '';
|
|
return text.length > length ? text.substring(0, length) + '...' : text;
|
|
}
|
|
|
|
getStatusColor(status) {
|
|
const colors = {
|
|
'active': 'success',
|
|
'inactive': 'secondary',
|
|
'blocked': 'danger',
|
|
'sent': 'info',
|
|
'delivered': 'success',
|
|
'read': 'success',
|
|
'failed': 'danger'
|
|
};
|
|
return colors[status] || 'secondary';
|
|
}
|
|
|
|
getMessageTypeColor(type) {
|
|
const colors = {
|
|
'text': 'primary',
|
|
'image': 'info',
|
|
'audio': 'warning',
|
|
'video': 'danger',
|
|
'document': 'secondary',
|
|
'template': 'success'
|
|
};
|
|
return colors[type] || 'primary';
|
|
}
|
|
|
|
// Funciones específicas
|
|
searchConversations(query) {
|
|
// Función de búsqueda de conversaciones
|
|
const tbody = document.getElementById('conversations-table');
|
|
if (!tbody) return;
|
|
|
|
const rows = tbody.getElementsByTagName('tr');
|
|
const searchTerm = query.toLowerCase();
|
|
|
|
for (let row of rows) {
|
|
const phoneNumber = row.cells[0]?.textContent?.toLowerCase() || '';
|
|
const lastMessage = row.cells[1]?.textContent?.toLowerCase() || '';
|
|
|
|
if (phoneNumber.includes(searchTerm) || lastMessage.includes(searchTerm)) {
|
|
row.style.display = '';
|
|
} else {
|
|
row.style.display = 'none';
|
|
}
|
|
}
|
|
}
|
|
|
|
refreshData() {
|
|
this.loadTabData(this.currentTab);
|
|
this.showSuccess('Datos actualizados');
|
|
}
|
|
|
|
refreshStats() {
|
|
this.loadDashboard();
|
|
}
|
|
|
|
exportUsers() {
|
|
window.open(this.apiBaseUrl + 'export_users.php', '_blank');
|
|
}
|
|
|
|
viewConversation(userId) {
|
|
// Implementar modal o página de conversación
|
|
console.log('Ver conversación del usuario:', userId);
|
|
}
|
|
|
|
replyToUser(phoneNumber) {
|
|
document.getElementById('message-recipient').value = phoneNumber;
|
|
this.showTab('conversations');
|
|
}
|
|
|
|
replyToMessage(messageId, userPhone) {
|
|
// Poner en el input del chat y almacenar reply_to en data attribute
|
|
document.getElementById('message-input').focus();
|
|
document.getElementById('message-input').dataset.replyTo = messageId;
|
|
this.showSuccess('Preparado para responder al mensaje ' + messageId);
|
|
}
|
|
|
|
async reactToMessage(messageId, userPhone) {
|
|
const emoji = prompt('Escribe el emoji de reacción (ej: ❤️, 👍):');
|
|
if (!emoji) return;
|
|
|
|
try {
|
|
const resp = await fetch('api/send_reaction.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ to: userPhone, message_id: messageId, emoji: emoji })
|
|
});
|
|
const data = await resp.json();
|
|
if (data.success) {
|
|
this.showSuccess('Reacción enviada');
|
|
// Refrescar mensajes
|
|
await this.loadconversations(this.currentUserId, false);
|
|
} else {
|
|
this.showError('Error enviando reacción: ' + (data.error || 'desconocido'));
|
|
}
|
|
} catch (err) {
|
|
console.error('reactToMessage error', err);
|
|
this.showError('Error comunicándose con el servidor');
|
|
}
|
|
}
|
|
|
|
|
|
editUser(userId) {
|
|
// Implementar modal de edición de usuario
|
|
console.log('Editar usuario:', userId);
|
|
}
|
|
|
|
blockUser(userId) {
|
|
if (confirm('¿Estás seguro de bloquear este usuario?')) {
|
|
// Implementar bloqueo de usuario
|
|
console.log('Bloquear usuario:', userId);
|
|
}
|
|
}
|
|
|
|
editMenu(menuId) {
|
|
this.currentMenuId = menuId;
|
|
// Cargar datos del menú en el formulario
|
|
console.log('Editar menú:', menuId);
|
|
}
|
|
|
|
deleteMenu(menuId) {
|
|
if (confirm('¿Estás seguro de eliminar este menú?')) {
|
|
// Implementar eliminación
|
|
console.log('Eliminar menú:', menuId);
|
|
}
|
|
}
|
|
|
|
manageMenuOptions(menuId) {
|
|
// Mostrar modal de opciones de menú
|
|
console.log('Gestionar opciones del menú:', menuId);
|
|
}
|
|
|
|
viewLogDetails(logId, type) {
|
|
// Mostrar detalles del log en modal
|
|
console.log('Ver detalles del log:', logId, type);
|
|
}
|
|
|
|
clearLogs() {
|
|
if (confirm('¿Estás seguro de eliminar todos los logs?')) {
|
|
// Implementar limpieza de logs
|
|
console.log('Limpiar logs');
|
|
}
|
|
}
|
|
|
|
refreshLogs() {
|
|
this.loadLogs();
|
|
}
|
|
|
|
async loadTemplates() {
|
|
try {
|
|
const response = await this.apiCall('get_templates.php');
|
|
if (response && response.success && Array.isArray(response.data)) {
|
|
this.updateTemplatesTable(response.data);
|
|
} else if (response && Array.isArray(response)) {
|
|
// Retrocompatibilidad por si la API devuelve directamente el array
|
|
this.updateTemplatesTable(response);
|
|
} else {
|
|
this.showError('Error: formato de datos inválido para plantillas');
|
|
}
|
|
} catch (error) {
|
|
this.showError('Error cargando plantillas: ' + error.message);
|
|
}
|
|
}
|
|
|
|
updateTemplatesTable(templates) {
|
|
const tbody = document.getElementById('templates-table');
|
|
if (!tbody) return;
|
|
|
|
tbody.innerHTML = '';
|
|
|
|
if (templates && templates.length > 0) {
|
|
templates.forEach(template => {
|
|
const row = this.createTemplateRow(template);
|
|
tbody.appendChild(row);
|
|
});
|
|
} else {
|
|
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-muted">No hay plantillas registradas</td></tr>';
|
|
}
|
|
}
|
|
|
|
createTemplateRow(template) {
|
|
const tr = document.createElement('tr');
|
|
const statusBadge = this.getStatusBadge(template.status);
|
|
|
|
tr.innerHTML = `
|
|
<td>${template.name}</td>
|
|
<td><code>${template.template_name}</code></td>
|
|
<td>${template.language_code.toUpperCase()}</td>
|
|
<td>
|
|
<span class="badge bg-secondary">${template.category}</span>
|
|
</td>
|
|
<td>${statusBadge}</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<button class="btn btn-outline-primary" onclick="app.editTemplate(${template.id})" title="Editar">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button class="btn btn-outline-danger" onclick="app.deleteTemplate(${template.id})" title="Eliminar">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
`;
|
|
return tr;
|
|
}
|
|
|
|
getStatusBadge(status) {
|
|
const badges = {
|
|
'approved': '<span class="badge bg-success">Aprobada</span>',
|
|
'pending': '<span class="badge bg-warning">Pendiente</span>',
|
|
'rejected': '<span class="badge bg-danger">Rechazada</span>'
|
|
};
|
|
return badges[status] || '<span class="badge bg-secondary">Desconocido</span>';
|
|
}
|
|
|
|
async saveTemplate() {
|
|
const formData = {
|
|
name: document.getElementById('template-name').value.trim(),
|
|
template_name: document.getElementById('template-whatsapp-name').value.trim(),
|
|
language_code: document.getElementById('template-language').value,
|
|
category: document.getElementById('template-category').value
|
|
};
|
|
|
|
if (!formData.name || !formData.template_name) {
|
|
this.showError('Por favor complete todos los campos requeridos');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
this.showLoading('Guardando plantilla...');
|
|
const result = await this.apiCall('save_template.php', 'POST', formData);
|
|
|
|
if (result.success) {
|
|
this.showSuccess(result.message);
|
|
document.getElementById('template-form').reset();
|
|
|
|
// Cerrar modal
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('createTemplateModal'));
|
|
modal.hide();
|
|
|
|
// Recargar plantillas
|
|
this.loadTemplates();
|
|
} else {
|
|
this.showError(result.error || 'Error guardando plantilla');
|
|
}
|
|
} catch (error) {
|
|
this.showError('Error guardando plantilla: ' + error.message);
|
|
} finally {
|
|
this.hideLoading();
|
|
}
|
|
}
|
|
|
|
async editTemplate(templateId) {
|
|
// Por ahora, solo permitir cambiar el estado
|
|
const status = prompt('Cambiar estado de la plantilla:\n- pending (Pendiente)\n- approved (Aprobada)\n- rejected (Rechazada)', 'approved');
|
|
|
|
if (!status || !['pending', 'approved', 'rejected'].includes(status)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
this.showLoading('Actualizando plantilla...');
|
|
const result = await this.apiCall('update_template_status.php', 'POST', {
|
|
id: templateId,
|
|
status: status
|
|
});
|
|
|
|
if (result.success) {
|
|
this.showSuccess(result.message);
|
|
this.loadTemplates();
|
|
} else {
|
|
this.showError(result.error || 'Error actualizando plantilla');
|
|
}
|
|
} catch (error) {
|
|
this.showError('Error actualizando plantilla: ' + error.message);
|
|
} finally {
|
|
this.hideLoading();
|
|
}
|
|
}
|
|
|
|
async deleteTemplate(templateId) {
|
|
if (!confirm('¿Estás seguro de que deseas eliminar esta plantilla?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
this.showLoading('Eliminando plantilla...');
|
|
const result = await this.apiCall('delete_template.php', 'POST', { id: templateId });
|
|
if (result && result.success) {
|
|
this.showSuccess('Plantilla eliminada');
|
|
this.loadTemplates();
|
|
} else {
|
|
this.showError(result.error || 'Error eliminando plantilla');
|
|
}
|
|
} catch (error) {
|
|
this.showError('Error eliminando plantilla: ' + error.message);
|
|
} finally {
|
|
this.hideLoading();
|
|
}
|
|
}
|
|
|
|
// Método para verificar el estado de la sesión
|
|
async checkSession() {
|
|
try {
|
|
const result = await this.apiCall('get_stats.php');
|
|
// Si la llamada es exitosa, la sesión está activa
|
|
if (!result) {
|
|
// La función apiCall ya manejó la redirección al login
|
|
return false;
|
|
}
|
|
return true;
|
|
} catch (error) {
|
|
console.warn('Error verificando sesión:', error);
|
|
// No mostrar error, solo en caso de problemas de red
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Inicializar la aplicación
|
|
const app = new WhatsAppBotManager();
|
|
|
|
// Funciones globales - usando function declarations para evitar problemas con SES
|
|
window.refreshData = function() {
|
|
if (typeof app !== 'undefined') app.refreshData();
|
|
};
|
|
window.exportUsers = function() {
|
|
if (typeof app !== 'undefined') app.exportUsers();
|
|
};
|
|
window.refreshLogs = function() {
|
|
if (typeof app !== 'undefined') app.refreshLogs();
|
|
};
|
|
window.clearLogs = function() {
|
|
if (typeof app !== 'undefined') app.clearLogs();
|
|
};
|
|
|
|
// Las funciones de modales se definen ahora en el HTML para evitar problemas con SES
|
|
// window.showCreateTemplateModal y window.showCreateMenuModal están en index.php
|
|
|
|
window.toggleRecipientType = () => {
|
|
const recipientType = document.getElementById('recipient-type').value;
|
|
const existingGroup = document.getElementById('existing-recipient-group');
|
|
const manualGroup = document.getElementById('manual-recipient-group');
|
|
const messageTypeSelect = document.getElementById('message-type');
|
|
|
|
if (recipientType === 'existing') {
|
|
existingGroup.style.display = 'block';
|
|
manualGroup.style.display = 'none';
|
|
// Habilitar todas las opciones
|
|
messageTypeSelect.innerHTML = `
|
|
<option value="text">Texto</option>
|
|
<option value="template">Plantilla</option>
|
|
`;
|
|
} else {
|
|
existingGroup.style.display = 'none';
|
|
manualGroup.style.display = 'block';
|
|
// Solo permitir plantillas para números manuales
|
|
messageTypeSelect.innerHTML = `
|
|
<option value="template">Plantilla (Solo opción disponible)</option>
|
|
`;
|
|
messageTypeSelect.value = 'template';
|
|
toggleMessageType(); // Actualizar la interfaz
|
|
}
|
|
};
|
|
|
|
// Funciones movidas al HTML inline para evitar problemas con SES
|
|
// window.showCreateMenuModal y window.saveNewMenu están ahora en index.php
|
|
|
|
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 === 'text') {
|
|
textGroup.style.display = 'block';
|
|
templateGroup.style.display = 'none';
|
|
} else {
|
|
textGroup.style.display = 'none';
|
|
templateGroup.style.display = 'block';
|
|
}
|
|
}; |