This commit is contained in:
Lizandro Guarnizo
2026-02-03 21:30:55 -05:00
parent 44423adb32
commit 1aae148396
89 changed files with 8581 additions and 7687 deletions
+82 -3
View File
@@ -87,6 +87,15 @@ class WhatsAppBotManager {
});
}
// Contador de caracteres para broadcast
const broadcastMessage = document.getElementById('broadcast-message');
const charCount = document.getElementById('char-count');
if (broadcastMessage && charCount) {
broadcastMessage.addEventListener('input', (e) => {
charCount.textContent = e.target.value.length;
});
}
// Cambio de tipo de mensaje
const messageType = document.getElementById('message-type');
if (messageType) {
@@ -695,9 +704,79 @@ class WhatsAppBotManager {
}
async sendBroadcast() {
// Funcionalidad de envío masivo deshabilitada temporalmente desde UI
this.showError('Envío masivo deshabilitado temporalmente');
return;
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 variables
const variables = {};
document.querySelectorAll('.broadcast-template-var').forEach(input => {
variables[input.dataset.var] = input.value;
});
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;
}
try {
this.showLoading('Enviando mensajes masivos...');
const result = await this.apiCall('send_broadcast.php', 'POST', 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';
} else {
this.showError(result.error || 'Error enviando broadcast');
}
} catch (error) {
this.showError('Error enviando broadcast: ' + error.message);
} finally {
this.hideLoading();
}
}
async saveMenu() {
+752 -32
View File
@@ -112,12 +112,21 @@ class SimpleWhatsAppManager {
case 'users':
this.loadUsers();
break;
case 'admin_users':
this.loadAdminUsers();
break;
case 'send_message':
this.loadconversations();
break;
case 'templates':
this.loadTemplates();
break;
case 'broadcast':
// Recargar plantillas cuando se abre el tab de broadcast
if (typeof loadBroadcastTemplates === 'function') {
loadBroadcastTemplates();
}
break;
case 'menus':
this.loadMenus();
break;
@@ -403,14 +412,19 @@ class SimpleWhatsAppManager {
}
}
async loadUsers() {
this.log('Cargando usuarios');
async loadUsers(page = 1, search = '') {
this.log('Cargando usuarios - página ' + page);
try {
const response = await this.apiCall('get_users.php');
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 || []);
this.updateUsersList(response.data || [], response.pagination || {});
} else {
this.showError('No se pudieron cargar los usuarios');
}
@@ -420,15 +434,17 @@ class SimpleWhatsAppManager {
}
}
updateUsersList(users) {
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 = '<tr><td colspan="7" class="text-center text-muted">No hay usuarios registrados</td></tr>';
this.updateUsersPagination({});
return;
}
@@ -459,6 +475,75 @@ class SimpleWhatsAppManager {
});
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 = `
<div class="d-flex justify-content-between align-items-center mt-3">
<div class="text-muted">
Mostrando página ${currentPage} de ${totalPages} (${total} usuarios en total)
</div>
<nav>
<ul class="pagination pagination-sm mb-0">
<li class="page-item ${!pagination.hasPrev ? 'disabled' : ''}">
<a class="page-link" href="#" onclick="window.whatsappManager.loadUsers(1); return false;">
<i class="fas fa-angle-double-left"></i>
</a>
</li>
<li class="page-item ${!pagination.hasPrev ? 'disabled' : ''}">
<a class="page-link" href="#" onclick="window.whatsappManager.loadUsers(${currentPage - 1}); return false;">
<i class="fas fa-angle-left"></i>
</a>
</li>
`;
// Mostrar páginas alrededor de la actual
const maxPages = 5;
let startPage = Math.max(1, currentPage - Math.floor(maxPages / 2));
let endPage = Math.min(totalPages, startPage + maxPages - 1);
if (endPage - startPage < maxPages - 1) {
startPage = Math.max(1, endPage - maxPages + 1);
}
for (let i = startPage; i <= endPage; i++) {
html += `
<li class="page-item ${i === currentPage ? 'active' : ''}">
<a class="page-link" href="#" onclick="window.whatsappManager.loadUsers(${i}); return false;">${i}</a>
</li>
`;
}
html += `
<li class="page-item ${!pagination.hasNext ? 'disabled' : ''}">
<a class="page-link" href="#" onclick="window.whatsappManager.loadUsers(${currentPage + 1}); return false;">
<i class="fas fa-angle-right"></i>
</a>
</li>
<li class="page-item ${!pagination.hasNext ? 'disabled' : ''}">
<a class="page-link" href="#" onclick="window.whatsappManager.loadUsers(${totalPages}); return false;">
<i class="fas fa-angle-double-right"></i>
</a>
</li>
</ul>
</nav>
</div>
`;
paginationContainer.innerHTML = html;
}
async loadConversations() {
@@ -716,6 +801,7 @@ class SimpleWhatsAppManager {
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 },
@@ -739,6 +825,7 @@ class SimpleWhatsAppManager {
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,
@@ -831,6 +918,9 @@ class SimpleWhatsAppManager {
}
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);
@@ -875,13 +965,15 @@ class SimpleWhatsAppManager {
// Limpiar opciones existentes (excepto la primera)
select.innerHTML = '<option value="">Seleccionar plantilla...</option>';
// Agregar plantillas
templates.forEach(template => {
const option = document.createElement('option');
option.value = template.template_name;
option.textContent = `${template.name} - ${template.category || 'Sin descripción'}`;
select.appendChild(option);
});
// 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() {
@@ -1367,38 +1459,90 @@ class SimpleWhatsAppManager {
return;
}
// Prepare template payload; support named parameters (e.g. { name: 'Juan' })
const tplName = templateSelect.value;
let tplParams = null;
// 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;
}
// If there's a hidden input with JSON parameters (index.php), use it
// 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 {
tplParams = JSON.parse(paramsInput.value);
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);
tplParams = null;
}
}
// If no params provided and template is contacto_nuevo, prompt operator for name
if ((!tplParams || (typeof tplParams === 'object' && Object.keys(tplParams).length === 0)) && tplName === 'contacto_nuevo') {
const defaultName = '';
const inputName = prompt('Ingrese el nombre para la plantilla contacto_nuevo (dejar vacío para usar el número):', defaultName);
if (inputName === null) {
// user cancelled
return;
}
const clean = (typeof inputName === 'string') ? inputName.trim() : '';
tplParams = { name: clean || recipient };
}
messageData = {
recipient: recipient,
type: 'template',
template_name: tplName,
parameters: tplParams || {}
template_name: templateName, // Usar template_name en lugar de ID
parameters: tplParams
};
}
@@ -1651,6 +1795,164 @@ class SimpleWhatsAppManager {
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;
}
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';
} else {
this.showError(result.error || 'Error enviando broadcast');
}
} catch (error) {
this.showError('Error enviando broadcast: ' + error.message);
}
}
// Cargar usuarios administradores del sistema
async loadAdminUsers() {
this.log('Cargando usuarios administradores...');
try {
const response = await this.apiCall('list_admin_users.php');
if (response && response.success && response.data) {
renderAdminUsersTable(response.data);
} else {
this.showError('Error cargando usuarios administradores');
}
} catch (error) {
this.log('Error cargando usuarios administradores: ' + error.message, 'error');
this.showError('Error cargando usuarios: ' + error.message);
}
}
}
// Inicializar cuando el DOM esté listo
@@ -2839,9 +3141,121 @@ window.toggleMessageType = function () {
} 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');
if (template.variables && template.variables.length > 0) {
console.log(`✅ Se encontraron ${template.variables.length} variables`);
let html = '<label class="form-label fw-bold"><i class="fas fa-edit"></i> Variables de la Plantilla</label>';
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 += `
<div class="mb-2">
<label class="form-label small text-muted">${varLabel}</label>
<input
type="text"
class="form-control form-control-sm message-template-var"
data-var="${variable.index || index + 1}"
data-placeholder="${placeholder}"
placeholder="${example || 'Ingrese ' + varLabel.toLowerCase()}"
oninput="updateMessagePreview()"
>
</div>
`;
});
console.log('📝 HTML generado para variables:', html);
variablesContainer.innerHTML = html;
variablesContainer.style.display = 'block';
console.log('✅ Variables container mostrado');
} else {
console.log('⚠️ Template sin variables');
variablesContainer.innerHTML = '<p class="text-muted small">Esta plantilla no tiene variables</p>';
variablesContainer.style.display = 'block';
}
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, '<br>');
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');
@@ -4113,6 +4527,90 @@ window.exportUsers = function() {
}
};
// 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 = '<i class="fas fa-spinner fa-spin"></i> 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 = '<i class="fas fa-sync-alt"></i> Sincronizar desde Facebook';
}
}
};
// Función para refrescar logs
window.refreshLogs = function() {
console.log('Refrescando logs...');
@@ -4189,4 +4687,226 @@ window.clearLogs = async function() {
} 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 = `
<tr>
<td colspan="7" class="text-center text-muted">
<i class="fas fa-inbox"></i> No hay usuarios registrados
</td>
</tr>
`;
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') :
'<span class="text-muted">Nunca</span>';
const createdAt = new Date(user.created_at).toLocaleDateString('es-CO');
return `
<tr>
<td><strong>${escapeHtml(user.username)}</strong></td>
<td>${escapeHtml(user.full_name || '-')}</td>
<td>${escapeHtml(user.email || '-')}</td>
<td>
<span class="badge bg-${isActive ? 'success' : 'secondary'}">
${isActive ? 'Activo' : 'Inactivo'}
</span>
</td>
<td><small>${lastLogin}</small></td>
<td><small>${createdAt}</small></td>
<td>
<button class="btn btn-sm btn-primary"
onclick="editAdminUser(${user.id})"
title="Editar">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-sm btn-warning"
onclick="changeAdminPassword(${user.id}, '${escapeHtml(user.username)}')"
title="Cambiar contraseña">
<i class="fas fa-key"></i>
</button>
<button class="btn btn-sm btn-${isActive ? 'secondary' : 'success'}"
onclick="toggleAdminUserStatus(${user.id}, ${!isActive})"
title="${isActive ? 'Desactivar' : 'Activar'}">
<i class="fas fa-${isActive ? 'ban' : 'check'}"></i>
</button>
</td>
</tr>
`;
}).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);
}
};