This commit is contained in:
lizandrogd
2026-01-12 22:42:41 -05:00
parent 5cca545806
commit 01c30ca594
12 changed files with 1397 additions and 106 deletions
+85
View File
@@ -0,0 +1,85 @@
<?php
/**
* API - Eliminar menú
* Fecha: 12 de enero de 2026
*/
require_once '../config/config.php';
// Verificar autenticación
requireAuthentication();
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Método no permitido']);
exit;
}
try {
$input = json_decode(file_get_contents('php://input'), true);
if (!$input || !isset($input['menu_id'])) {
http_response_code(400);
echo json_encode(['error' => 'ID del menú es requerido']);
exit;
}
$menuId = (int)$input['menu_id'];
if ($menuId <= 0) {
http_response_code(400);
echo json_encode(['error' => 'ID de menú inválido']);
exit;
}
$db = Database::getInstance();
// Verificar que el menú existe
$menu = $db->fetch("SELECT id, name FROM menus WHERE id = :id", ['id' => $menuId]);
if (!$menu) {
http_response_code(404);
echo json_encode(['error' => 'Menú no encontrado']);
exit;
}
// Iniciar transacción
$db->beginTransaction();
try {
// Eliminar opciones del menú
$db->execute("DELETE FROM menu_options WHERE menu_id = :menu_id", ['menu_id' => $menuId]);
// Eliminar el menú
$result = $db->execute("DELETE FROM menus WHERE id = :id", ['id' => $menuId]);
if ($result) {
$db->commit();
echo json_encode([
'success' => true,
'message' => "Menú '{$menu['name']}' eliminado correctamente"
]);
} else {
$db->rollback();
echo json_encode(['error' => 'Error eliminando menú']);
}
} catch (Exception $e) {
$db->rollback();
throw $e;
}
} catch (Exception $e) {
error_log("Error in delete_menu.php: " . $e->getMessage());
http_response_code(500);
echo json_encode([
'error' => 'Error interno del servidor',
'debug' => $e->getMessage()
]);
}
?>
+133 -21
View File
@@ -1,7 +1,7 @@
<?php
/**
* API - Obtener menús
* Fecha: 13 de noviembre de 2025
* Fecha: 12 de enero de 2026 - Actualizado para diagnostico mejorado
*/
require_once '../config/config.php';
@@ -15,10 +15,55 @@ header('Access-Control-Allow-Methods: GET');
header('Access-Control-Allow-Headers: Content-Type');
try {
$db = Database::getInstance();
// Test inicial de conexión con diagnóstico detallado
error_log("=== INICIO DEBUG GET MENUS ===");
$menus = $db->fetchAll(
"SELECT
$db = Database::getInstance();
$connection = $db->getConnection();
if (!$connection) {
throw new Exception("No se pudo obtener la conexión a la base de datos");
}
error_log("Conexión DB establecida correctamente");
// Verificar base de datos actual
$currentDB = $db->fetch("SELECT DATABASE() as current_db");
error_log("Base de datos actual: " . json_encode($currentDB));
// Verificar si las tablas existen
try {
$showTablesQuery = "SHOW TABLES LIKE 'menus'";
$tablesExist = $db->fetchAll($showTablesQuery);
error_log("Query SHOW TABLES ejecutada. Resultado: " . json_encode($tablesExist));
if (empty($tablesExist)) {
error_log("Tabla 'menus' no existe, pero datos indican que ya existe. Continuando con consulta...");
} else {
error_log("Tabla 'menus' ya existe");
}
} catch (Exception $e) {
error_log("ERROR creando/verificando tablas: " . $e->getMessage());
echo json_encode([
'success' => false,
'error' => 'Error en estructura de base de datos',
'debug' => $e->getMessage(),
'step' => 'table_verification',
'data' => []
]);
exit;
}
// Obtener menús con consulta simplificada
try {
error_log("Ejecutando consulta de menús...");
// Verificar estructura de tabla
$tableStructure = $db->fetchAll("DESCRIBE menus");
error_log("Estructura tabla menus: " . json_encode($tableStructure));
// Consulta adaptada a la estructura real de la tabla existente
$menusQuery = "SELECT
id,
name,
title,
@@ -29,26 +74,93 @@ try {
order_position,
created_at,
updated_at
FROM menus
ORDER BY order_position ASC, title ASC"
);
// Obtener opciones de cada menú
foreach ($menus as &$menu) {
$options = $db->fetchAll(
"SELECT * FROM menu_options
WHERE menu_id = :menu_id
ORDER BY option_number ASC",
['menu_id' => $menu['id']]
);
$menu['options'] = $options;
FROM menus ORDER BY order_position ASC, id ASC";
$menus = $db->fetchAll($menusQuery);
error_log("Menús obtenidos: " . count($menus) . " registros");
// Transformar datos para compatibilidad con JavaScript
foreach ($menus as &$menu) {
// Mapear campos para compatibilidad
$menu['menu_name'] = $menu['title'] ?? $menu['name'];
$menu['menu_key'] = $menu['name'];
$menu['status'] = ($menu['is_active'] == 1) ? 'active' : 'inactive';
$menu['welcome_message'] = ''; // No disponible en estructura actual
}
error_log("Menús encontrados: " . count($menus));
if (!empty($menus)) {
error_log("Primer menú: " . json_encode($menus[0]));
}
// Procesar opciones con manejo de errores para estructura desconocida
foreach ($menus as &$menu) {
try {
// Primero verificar si la tabla menu_options existe
$optionsTableExists = $db->fetchAll("SHOW TABLES LIKE 'menu_options'");
if (!empty($optionsTableExists)) {
// Verificar estructura de la tabla
$optionsStructure = $db->fetchAll("DESCRIBE menu_options");
error_log("Estructura menu_options: " . json_encode($optionsStructure));
$optionsQuery = "SELECT * FROM menu_options WHERE menu_id = ? ORDER BY id ASC";
$options = $db->fetchAll($optionsQuery, [$menu['id']]);
} else {
error_log("Tabla menu_options no existe");
$options = [];
}
$menu['options'] = $options;
$menu['options_count'] = count($options);
} catch (Exception $e) {
error_log("Error opciones menú {$menu['id']}: " . $e->getMessage());
$menu['options'] = [];
$menu['options_count'] = 0;
}
}
error_log("=== FIN DEBUG GET MENUS SUCCESS ===");
echo json_encode([
'success' => true,
'data' => $menus,
'total' => count($menus),
'message' => 'Menús cargados exitosamente',
'debug_info' => [
'db_name' => DB_NAME,
'total_found' => count($menus),
'timestamp' => date('Y-m-d H:i:s')
]
]);
} catch (Exception $e) {
error_log("ERROR consulta menús: " . $e->getMessage());
error_log("Stack trace: " . $e->getTraceAsString());
error_log("=== FIN DEBUG GET MENUS ERROR ===");
echo json_encode([
'success' => false,
'error' => 'Error en consulta de menús',
'debug' => $e->getMessage(),
'code' => $e->getCode(),
'step' => 'menu_query',
'data' => []
]);
}
echo json_encode($menus);
} catch (Exception $e) {
error_log("Error in get_menus.php: " . $e->getMessage());
error_log("ERROR GENERAL get_menus.php: " . $e->getMessage());
error_log("Stack trace: " . $e->getTraceAsString());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor']);
echo json_encode([
'success' => false,
'error' => 'Error general del sistema',
'debug' => $e->getMessage(),
'step' => 'initialization',
'data' => []
]);
}
?>
+109 -28
View File
@@ -1,11 +1,14 @@
<?php
/**
* API - Guardar menú
* Fecha: 13 de noviembre de 2025
* Fecha: 12 de enero de 2026 - Actualizado para nueva estructura
*/
require_once '../config/config.php';
// Verificar autenticación
requireAuthentication();
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST');
@@ -13,52 +16,130 @@ header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Método no permitido']);
echo json_encode(['success' => false, 'error' => 'Método no permitido']);
exit;
}
try {
$input = json_decode(file_get_contents('php://input'), true);
if (!$input || !isset($input['name']) || !isset($input['title'])) {
if (!$input || !isset($input['name'])) {
http_response_code(400);
echo json_encode(['error' => 'Nombre y título son requeridos']);
echo json_encode(['success' => false, 'error' => 'Nombre del menú es requerido']);
exit;
}
$db = Database::getInstance();
// Verificar si es menú raíz
$isRoot = empty($input['parent_id']) ? 1 : 0;
// Iniciar transacción
$db->beginTransaction();
// Obtener próximo order_position
$maxOrder = $db->fetch(
"SELECT COALESCE(MAX(order_position), 0) as max_order FROM menus"
);
try {
// Verificar estructura de tabla antes de insertar
$tableStructure = $db->fetchAll("DESCRIBE menus");
$availableColumns = array_column($tableStructure, 'Field');
$menuData = [
'name' => $input['name'],
'title' => $input['title'],
'description' => $input['description'] ?? '',
'parent_id' => empty($input['parent_id']) ? null : $input['parent_id'],
'is_root' => $isRoot,
'is_active' => $input['is_active'] ?? 1,
'order_position' => $maxOrder['max_order'] + 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
];
error_log("Columnas disponibles en tabla menus: " . json_encode($availableColumns));
$menuId = $db->insert('menus', $menuData);
// Adaptar datos a la estructura real de la tabla
$menuData = [];
echo json_encode([
'success' => true,
'message' => 'Menú guardado correctamente',
'menu_id' => $menuId
]);
// Campos básicos que deberían existir
if (in_array('name', $availableColumns)) {
$menuData['name'] = $input['menu_key'] ?? $input['name']; // Usar menu_key como name
}
if (in_array('title', $availableColumns)) {
$menuData['title'] = $input['name'] ?? 'Nuevo Menú'; // Usar name como title
}
if (in_array('description', $availableColumns)) {
$menuData['description'] = $input['description'] ?? '';
}
if (in_array('parent_id', $availableColumns)) {
$menuData['parent_id'] = empty($input['parent_id']) ? null : (int)$input['parent_id'];
}
if (in_array('is_root', $availableColumns)) {
$menuData['is_root'] = empty($input['parent_id']) ? 1 : 0;
}
if (in_array('is_active', $availableColumns)) {
$menuData['is_active'] = ($input['status'] === 'active') ? 1 : 0;
}
if (in_array('order_position', $availableColumns)) {
// Obtener próxima posición
$maxOrder = $db->fetch("SELECT COALESCE(MAX(order_position), 0) as max_order FROM menus");
$menuData['order_position'] = ($maxOrder['max_order'] ?? 0) + 1;
}
if (in_array('created_at', $availableColumns)) {
$menuData['created_at'] = date('Y-m-d H:i:s');
}
if (in_array('updated_at', $availableColumns)) {
$menuData['updated_at'] = date('Y-m-d H:i:s');
}
$menuId = $db->insert('menus', $menuData);
if (!$menuId) {
throw new Exception('Error insertando menú');
}
// Procesar opciones si las hay
$optionsCount = 0;
if (isset($input['options']) && is_array($input['options'])) {
foreach ($input['options'] as $option) {
if (!empty($option['key']) && !empty($option['text'])) {
$optionData = [
'menu_id' => $menuId,
'option_key' => $option['key'],
'option_text' => $option['text'],
'option_action' => $option['action'] ?? 'message',
'option_value' => $option['value'] ?? '',
'order_index' => $option['order_index'] ?? $optionsCount + 1,
'is_active' => true,
'created_at' => date('Y-m-d H:i:s')
];
$optionId = $db->insert('menu_options', $optionData);
if ($optionId) {
$optionsCount++;
}
}
}
}
// Actualizar contador de opciones
$db->execute("UPDATE menus SET options_count = :count WHERE id = :id", [
'count' => $optionsCount,
'id' => $menuId
]);
$db->commit();
echo json_encode([
'success' => true,
'message' => 'Menú guardado correctamente',
'menu_id' => $menuId,
'options_created' => $optionsCount
]);
} catch (Exception $e) {
$db->rollback();
throw $e;
}
} catch (Exception $e) {
error_log("Error in save_menu.php: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor: ' . $e->getMessage()]);
echo json_encode([
'success' => false,
'error' => 'Error interno del servidor',
'debug' => $e->getMessage()
]);
}
?>
+38
View File
@@ -0,0 +1,38 @@
<?php
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
try {
echo json_encode([
'message' => 'Iniciando test de conexión DB...',
'timestamp' => date('Y-m-d H:i:s')
]);
echo "\n\n";
$db = Database::getInstance();
echo json_encode([
'success' => true,
'message' => 'Conexión a base de datos exitosa',
'database' => DB_NAME,
'host' => DB_HOST
]);
echo "\n\n";
// Test de tabla
$tables = $db->fetchAll("SHOW TABLES");
echo json_encode([
'success' => true,
'message' => 'Tablas encontradas',
'tables' => $tables
]);
} catch (Exception $e) {
echo json_encode([
'success' => false,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
}
?>
+259 -1
View File
@@ -114,6 +114,9 @@ class SimpleWhatsAppManager {
case 'templates':
this.loadTemplates();
break;
case 'menus':
this.loadMenus();
break;
case 'logs':
this.loadLogs();
break;
@@ -794,6 +797,26 @@ class SimpleWhatsAppManager {
}
}
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;
@@ -828,6 +851,47 @@ class SimpleWhatsAppManager {
container.innerHTML = html;
}
updateMenusList(menus) {
const container = document.getElementById('menus-table');
if (!container) return;
if (menus.length === 0) {
container.innerHTML = '<tr><td colspan="7" class="text-center text-muted">No hay menús configurados</td></tr>';
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 += `
<tr>
<td><strong>${menu.name || menu.menu_name}</strong></td>
<td><code>${menu.menu_key || menu.trigger}</code></td>
<td>${this.truncateText(menu.description || '', 50)}</td>
<td><span class="badge bg-${menu.status === 'active' ? 'success' : 'secondary'}">${menu.status || 'active'}</span></td>
<td>${menu.options_count || 0} opciones</td>
<td>${formatDate}</td>
<td>
<button class="btn btn-sm btn-outline-primary me-1" onclick="editMenu(${menu.id})" title="Editar">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-sm btn-outline-info me-1" onclick="viewMenuOptions(${menu.id})" title="Ver opciones">
<i class="fas fa-list"></i>
</button>
<button class="btn btn-sm btn-outline-danger" onclick="deleteMenu(${menu.id})" title="Eliminar">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
`;
});
container.innerHTML = html;
}
async saveTemplate() {
this.log('Guardando plantilla');
@@ -874,6 +938,106 @@ class SimpleWhatsAppManager {
}
}
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'
};
// 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 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');
@@ -1406,15 +1570,109 @@ window.deleteTemplate = function (templateId) {
}
};
// Hacer que app.saveTemplate() funcione
// 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();
}
}
};
// Funciones globales para gestión de menús
window.editMenu = function (menuId) {
console.log('Editando menú:', menuId);
// TODO: Implementar edición de menús
alert(`Editar menú ${menuId} - Función en desarrollo`);
};
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 = function (menuId) {
console.log('Viendo opciones del menú:', menuId);
// TODO: Implementar visualización de opciones del menú
alert(`Ver opciones del menú ${menuId} - Función en desarrollo`);
};
// 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 = `
<div class="menu-option-item border rounded p-3 mb-2">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0">Opción ${optionIndex}</h6>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeMenuOption(this)">
<i class="fas fa-trash"></i>
</button>
</div>
<div class="row">
<div class="col-md-3">
<label class="form-label">Clave</label>
<input type="text" class="form-control option-key" placeholder="ej: 1" required>
</div>
<div class="col-md-4">
<label class="form-label">Texto</label>
<input type="text" class="form-control option-text" placeholder="ej: Información" required>
</div>
<div class="col-md-3">
<label class="form-label">Acción</label>
<select class="form-select option-action">
<option value="message">Enviar mensaje</option>
<option value="submenu">Submenú</option>
<option value="template">Plantilla</option>
<option value="function">Función personalizada</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label">Valor</label>
<input type="text" class="form-control option-value" placeholder="Contenido">
</div>
</div>
</div>
`;
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();
}
};
// Función para actualizar numeración de opciones
function updateOptionNumbers() {
const container = document.getElementById('menu-options-container');
if (!container) return;
const options = container.querySelectorAll('.menu-option-item');
options.forEach((option, index) => {
const header = option.querySelector('h6');
if (header) {
header.textContent = `Opción ${index + 1}`;
}
});
}
// Funciones para el formulario de mensajes
window.toggleRecipientType = function () {
const recipientType = document.getElementById('recipient-type').value;
+26 -7
View File
@@ -40,22 +40,36 @@ class Database {
public function query($sql, $params = []) {
try {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
if (!empty($params)) {
$stmt->execute($params);
} else {
$stmt->execute();
}
return $stmt;
} catch (PDOException $e) {
error_log("Query failed: " . $e->getMessage() . " SQL: " . $sql);
throw new Exception("Error en la consulta a la base de datos");
error_log("Query failed: " . $e->getMessage() . " SQL: " . $sql . " Params: " . json_encode($params));
throw new Exception("Error en la consulta a la base de datos: " . $e->getMessage());
}
}
public function fetch($sql, $params = []) {
$stmt = $this->query($sql, $params);
return $stmt->fetch();
try {
$stmt = $this->query($sql, $params);
return $stmt->fetch();
} catch (Exception $e) {
error_log("Fetch failed: " . $e->getMessage());
throw $e;
}
}
public function fetchAll($sql, $params = []) {
$stmt = $this->query($sql, $params);
return $stmt->fetchAll();
try {
$stmt = $this->query($sql, $params);
return $stmt->fetchAll();
} catch (Exception $e) {
error_log("FetchAll failed: " . $e->getMessage());
throw $e;
}
}
public function insert($table, $data) {
@@ -99,6 +113,11 @@ class Database {
return $this->pdo->rollback();
}
public function execute($sql, $params = []) {
$stmt = $this->query($sql, $params);
return $stmt->rowCount();
}
public function lastInsertId() {
return $this->pdo->lastInsertId();
}
+3
View File
@@ -50,6 +50,9 @@ function env($key, $default = null) {
return $_ENV[$key] ?? getenv($key) ?: $default;
}
// Incluir clase Database
require_once __DIR__ . '/../classes/Database.php';
// Función para verificar si la instalación está completada
function isInstallationCompleted() {
return file_exists(dirname(__DIR__) . '/.installation_completed');
+88 -49
View File
@@ -244,56 +244,31 @@ try {
<!-- Menus Tab -->
<div id="menus" class="tab-content">
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-list"></i> Menús Configurados</h5>
<button class="btn btn-primary" onclick="showCreateMenuModal()">
<i class="fas fa-plus"></i> Nuevo Menú
</button>
</div>
<div class="card-body">
<div id="menus-tree">
<!-- Árbol de menús -->
</div>
</div>
</div>
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-list"></i> Menús Configurados</h5>
<button class="btn btn-primary" onclick="openCreateMenuModal()">
<i class="fas fa-plus"></i> Nuevo Menú
</button>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-edit"></i> Editor de Menú</h5>
</div>
<div class="card-body">
<form id="menu-form">
<div class="mb-3">
<label class="form-label">Nombre del Menú</label>
<input type="text" class="form-control" id="menu-name" required>
</div>
<div class="mb-3">
<label class="form-label">Título</label>
<input type="text" class="form-control" id="menu-title" required>
</div>
<div class="mb-3">
<label class="form-label">Descripción</label>
<textarea class="form-control" id="menu-description" rows="3"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Menú Padre</label>
<select class="form-control" id="menu-parent">
<option value="">Sin padre (Menú raíz)</option>
</select>
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="menu-active" checked>
<label class="form-check-label">Activo</label>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Guardar Menú
</button>
</form>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Nombre</th>
<th>Clave</th>
<th>Descripción</th>
<th>Estado</th>
<th>Opciones</th>
<th>Fecha Creación</th>
<th>Acciones</th>
</tr>
</thead>
<tbody id="menus-table">
<!-- Contenido dinámico -->
</tbody>
</table>
</div>
</div>
</div>
@@ -742,6 +717,70 @@ try {
});
}
</script>
<!-- Modal para crear menú -->
<div class="modal fade" id="createMenuModal" tabindex="-1" aria-labelledby="createMenuModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="createMenuModalLabel">
<i class="fas fa-plus"></i> Crear Nuevo Menú
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="menu-form">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Nombre del Menú *</label>
<input type="text" class="form-control" id="menu-name" placeholder="ej: Menú Principal" required>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Clave del Menú *</label>
<input type="text" class="form-control" id="menu-key" placeholder="ej: main_menu" required>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Descripción</label>
<textarea class="form-control" id="menu-description" rows="2" placeholder="Descripción del menú"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Mensaje de Bienvenida</label>
<textarea class="form-control" id="menu-welcome-message" rows="3" placeholder="¡Hola! Bienvenido. Por favor selecciona una opción:"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Estado</label>
<select class="form-control" id="menu-status">
<option value="active">Activo</option>
<option value="inactive">Inactivo</option>
</select>
</div>
<hr>
<h6>📋 Opciones del Menú</h6>
<div id="menu-options-container">
<!-- Opciones se agregan dinámicamente -->
</div>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="addMenuOption()">
<i class="fas fa-plus"></i> Agregar Opción
</button>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times"></i> Cancelar
</button>
<button type="button" class="btn btn-primary" onclick="app.saveMenu()">
<i class="fas fa-save"></i> Guardar Menú
</button>
</div>
</div>
</div>
</div>
</body>
</html>
+100
View File
@@ -0,0 +1,100 @@
<?php
/**
* Test directo de la API get_menus.php con diagnóstico
*/
echo "<!DOCTYPE html><html><head><title>Test Diagnóstico Menús</title>";
echo "<style>body{font-family:monospace;padding:20px;} .success{color:green;} .error{color:red;} .info{color:blue;} pre{background:#f0f0f0;padding:10px;border:1px solid #ccc;}</style></head><body>";
echo "<h1>🔧 Test Diagnóstico Get Menús API</h1>";
try {
echo "<h2>📞 Llamada directa a get_menus.php</h2>";
// Hacer llamada a la API
$url = 'http://localhost:8000/api/get_menus.php';
echo "<p><strong>URL:</strong> $url</p>";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_COOKIEFILE, ''); // Enable cookies
// Agregar cookie de sesión si existe
if (isset($_COOKIE['PHPSESSID'])) {
curl_setopt($ch, CURLOPT_COOKIE, 'PHPSESSID=' . $_COOKIE['PHPSESSID']);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
curl_close($ch);
$headers = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
echo "<h3>🔍 Resultado de la llamada</h3>";
echo "<p><strong>HTTP Code:</strong> $httpCode</p>";
echo "<h4>Headers:</h4>";
echo "<pre>" . htmlspecialchars($headers) . "</pre>";
echo "<h4>Body Response:</h4>";
echo "<pre>" . htmlspecialchars($body) . "</pre>";
// Intentar decodificar JSON
if ($body) {
$jsonData = json_decode($body, true);
if ($jsonData !== null) {
echo "<h4>📋 JSON Decodificado:</h4>";
echo "<pre>" . json_encode($jsonData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "</pre>";
if (isset($jsonData['success'])) {
if ($jsonData['success']) {
echo "<div class='success'>✅ API respondió exitosamente</div>";
if (isset($jsonData['data'])) {
echo "<p><strong>Menús encontrados:</strong> " . count($jsonData['data']) . "</p>";
}
} else {
echo "<div class='error'>❌ API reportó error: " . ($jsonData['error'] ?? 'Error desconocido') . "</div>";
if (isset($jsonData['debug'])) {
echo "<p><strong>Debug info:</strong> " . $jsonData['debug'] . "</p>";
}
}
}
} else {
echo "<div class='error'>❌ Respuesta no es JSON válido</div>";
}
}
// Mostrar logs si existen
echo "<h3>📋 Logs del Sistema</h3>";
$logFile = dirname(__DIR__) . '/logs/system.log';
if (file_exists($logFile)) {
$logs = file_get_contents($logFile);
$recentLogs = array_slice(explode("\n", $logs), -20); // Últimas 20 líneas
echo "<h4>Últimas 20 líneas del log:</h4>";
echo "<pre>" . htmlspecialchars(implode("\n", $recentLogs)) . "</pre>";
} else {
echo "<p>No se encontró archivo de log en: $logFile</p>";
}
// Mostrar error log de PHP si existe
$errorLogFile = dirname(__DIR__) . '/logs/error.log';
if (file_exists($errorLogFile)) {
$errorLogs = file_get_contents($errorLogFile);
$recentErrorLogs = array_slice(explode("\n", $errorLogs), -10); // Últimas 10 líneas
echo "<h4>Últimas 10 líneas del error log:</h4>";
echo "<pre>" . htmlspecialchars(implode("\n", $recentErrorLogs)) . "</pre>";
}
} catch (Exception $e) {
echo "<div class='error'>❌ Error en test: " . $e->getMessage() . "</div>";
}
echo "<hr><p><small>Test ejecutado: " . date('Y-m-d H:i:s') . "</small></p>";
echo "</body></html>";
?>
+200
View File
@@ -0,0 +1,200 @@
<?php
/**
* Test de conexión y estructura de menús
* Fecha: 12 de enero de 2026
*/
require_once 'config/config.php';
echo "<!DOCTYPE html>";
echo "<html><head><title>Test Menús - WhatsApp Bot</title>";
echo "<style>body{font-family:Arial;padding:20px;} .success{color:green;} .error{color:red;} .info{color:blue;} pre{background:#f5f5f5;padding:10px;}</style>";
echo "</head><body>";
echo "<h1>🔧 Test de Funcionalidad de Menús</h1>";
try {
$db = Database::getInstance();
echo "<div class='success'>✅ Conexión a base de datos: EXITOSA</div>";
// Verificar tabla menus
echo "<h2>📋 Verificando tabla 'menus'</h2>";
try {
$result = $db->fetchAll("SHOW TABLES LIKE 'menus'");
if (count($result) > 0) {
echo "<div class='success'>✅ Tabla 'menus' existe</div>";
// Mostrar estructura
$structure = $db->fetchAll("DESCRIBE menus");
echo "<h3>Estructura de tabla 'menus':</h3>";
echo "<pre>";
foreach ($structure as $column) {
echo "{$column['Field']} - {$column['Type']} - {$column['Null']} - {$column['Default']}\n";
}
echo "</pre>";
} else {
echo "<div class='error'>❌ Tabla 'menus' NO existe</div>";
echo "<div class='info'>💡 Creando tabla 'menus'...</div>";
$createMenusTable = "
CREATE TABLE IF NOT EXISTS menus (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
menu_key VARCHAR(100) NOT NULL,
description TEXT,
welcome_message TEXT,
status ENUM('active', 'inactive') DEFAULT 'active',
options_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_menu_key (menu_key),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
$db->execute($createMenusTable);
echo "<div class='success'>✅ Tabla 'menus' creada</div>";
}
} catch (Exception $e) {
echo "<div class='error'>❌ Error verificando tabla 'menus': " . $e->getMessage() . "</div>";
}
// Verificar tabla menu_options
echo "<h2>📋 Verificando tabla 'menu_options'</h2>";
try {
$result = $db->fetchAll("SHOW TABLES LIKE 'menu_options'");
if (count($result) > 0) {
echo "<div class='success'>✅ Tabla 'menu_options' existe</div>";
// Mostrar estructura
$structure = $db->fetchAll("DESCRIBE menu_options");
echo "<h3>Estructura de tabla 'menu_options':</h3>";
echo "<pre>";
foreach ($structure as $column) {
echo "{$column['Field']} - {$column['Type']} - {$column['Null']} - {$column['Default']}\n";
}
echo "</pre>";
} else {
echo "<div class='error'>❌ Tabla 'menu_options' NO existe</div>";
echo "<div class='info'>💡 Creando tabla 'menu_options'...</div>";
$createMenuOptionsTable = "
CREATE TABLE IF NOT EXISTS menu_options (
id INT AUTO_INCREMENT PRIMARY KEY,
menu_id INT NOT NULL,
option_key VARCHAR(10) NOT NULL,
option_text VARCHAR(255) NOT NULL,
option_action ENUM('message', 'submenu', 'template', 'function') DEFAULT 'message',
option_value TEXT,
order_index INT DEFAULT 0,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (menu_id) REFERENCES menus(id) ON DELETE CASCADE,
INDEX idx_menu_id (menu_id),
INDEX idx_option_key (option_key),
INDEX idx_order (order_index)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
$db->execute($createMenuOptionsTable);
echo "<div class='success'>✅ Tabla 'menu_options' creada</div>";
}
} catch (Exception $e) {
echo "<div class='error'>❌ Error verificando tabla 'menu_options': " . $e->getMessage() . "</div>";
}
// Verificar menús existentes
echo "<h2>📱 Menús existentes</h2>";
try {
$menus = $db->fetchAll("SELECT * FROM menus ORDER BY created_at DESC");
if (count($menus) > 0) {
echo "<div class='info'>📋 Se encontraron " . count($menus) . " menús:</div>";
echo "<pre>";
foreach ($menus as $menu) {
echo "ID: {$menu['id']} | Nombre: {$menu['name']} | Clave: {$menu['menu_key']} | Estado: {$menu['status']}\n";
}
echo "</pre>";
} else {
echo "<div class='info'>📋 No hay menús configurados aún</div>";
// Crear menú de ejemplo
echo "<div class='info'>💡 Creando menú de ejemplo...</div>";
$menuData = [
'name' => 'Menú Principal',
'menu_key' => 'main',
'description' => 'Menú principal del bot',
'welcome_message' => '¡Hola! Bienvenido a nuestro servicio. Por favor selecciona una opción:',
'status' => 'active',
'options_count' => 0
];
$menuId = $db->insert('menus', $menuData);
if ($menuId) {
echo "<div class='success'>✅ Menú de ejemplo creado (ID: {$menuId})</div>";
// Crear opciones de ejemplo
$options = [
['menu_id' => $menuId, 'option_key' => '1', 'option_text' => 'Información', 'option_action' => 'message', 'option_value' => 'Aquí tienes información sobre nuestros servicios', 'order_index' => 1],
['menu_id' => $menuId, 'option_key' => '2', 'option_text' => 'Soporte', 'option_action' => 'message', 'option_value' => 'Nuestro equipo de soporte te ayudará en breve', 'order_index' => 2],
['menu_id' => $menuId, 'option_key' => '0', 'option_text' => 'Salir', 'option_action' => 'message', 'option_value' => 'Gracias por contactarnos', 'order_index' => 3]
];
foreach ($options as $option) {
$db->insert('menu_options', $option);
}
// Actualizar contador de opciones
$db->execute("UPDATE menus SET options_count = 3 WHERE id = :id", ['id' => $menuId]);
echo "<div class='success'>✅ Opciones de menú creadas</div>";
}
}
} catch (Exception $e) {
echo "<div class='error'>❌ Error verificando menús: " . $e->getMessage() . "</div>";
}
// Test de API
echo "<h2>🔌 Test de API</h2>";
try {
$apiUrl = 'http://localhost:8000/api/get_menus.php';
echo "<div class='info'>📞 Probando API: {$apiUrl}</div>";
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => 'Content-Type: application/json',
'timeout' => 10
]
]);
$response = @file_get_contents($apiUrl, false, $context);
if ($response !== false) {
$data = json_decode($response, true);
if ($data !== null) {
echo "<div class='success'>✅ API responde correctamente</div>";
echo "<pre>" . json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "</pre>";
} else {
echo "<div class='error'>❌ API responde pero JSON inválido</div>";
echo "<pre>" . htmlspecialchars($response) . "</pre>";
}
} else {
echo "<div class='error'>❌ No se pudo conectar a la API</div>";
}
} catch (Exception $e) {
echo "<div class='error'>❌ Error probando API: " . $e->getMessage() . "</div>";
}
} catch (Exception $e) {
echo "<div class='error'>❌ Error de conexión: " . $e->getMessage() . "</div>";
}
echo "<hr>";
echo "<p><strong>Estado del servidor:</strong> El servidor PHP está corriendo en <a href='http://localhost:8000' target='_blank'>http://localhost:8000</a></p>";
echo "<p><strong>Aplicación principal:</strong> <a href='http://localhost:8000/index.php' target='_blank'>WhatsApp Bot Manager</a></p>";
echo "<p><strong>Fecha de prueba:</strong> " . date('Y-m-d H:i:s') . "</p>";
echo "</body></html>";
?>
+153
View File
@@ -0,0 +1,153 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test Menús - WhatsApp Bot Manager</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.success { color: green; }
.error { color: red; }
.info { color: blue; }
button { padding: 10px 15px; margin: 5px; cursor: pointer; }
pre { background: #f5f5f5; padding: 15px; border-radius: 5px; overflow-x: auto; }
.result { margin: 10px 0; padding: 10px; border-radius: 5px; }
</style>
</head>
<body>
<h1>🔧 Test de Funcionalidad de Menús</h1>
<p>Este test verificará que la funcionalidad de menús esté trabajando correctamente.</p>
<div id="results"></div>
<h2>🧪 Pruebas de API</h2>
<button onclick="testGetMenus()">📋 Probar get_menus.php</button>
<button onclick="testSaveMenu()">💾 Probar save_menu.php</button>
<button onclick="testDeleteMenu()">🗑️ Probar delete_menu.php</button>
<button onclick="clearResults()">🧽 Limpiar Resultados</button>
<div id="test-results"></div>
<script>
function clearResults() {
document.getElementById('test-results').innerHTML = '';
}
function showResult(title, data, isError = false) {
const div = document.createElement('div');
div.className = `result ${isError ? 'error' : 'success'}`;
div.innerHTML = `
<h3>${title}</h3>
<pre>${typeof data === 'object' ? JSON.stringify(data, null, 2) : data}</pre>
`;
document.getElementById('test-results').appendChild(div);
}
async function testGetMenus() {
try {
const response = await fetch('./api/get_menus.php');
const data = await response.json();
showResult('✅ GET Menús - Respuesta', data);
if (data.success && data.data.length > 0) {
showResult('📊 Total de menús encontrados', `${data.data.length} menús`);
} else if (data.success && data.data.length === 0) {
showResult('️ Sin menús', 'No se encontraron menús configurados');
}
} catch (error) {
showResult('❌ Error GET Menús', error.message, true);
}
}
async function testSaveMenu() {
const testMenu = {
name: 'Menú de Prueba',
menu_key: 'test_' + Date.now(),
description: 'Menú creado para prueba automática',
welcome_message: '¡Hola! Este es un menú de prueba. Selecciona una opción:',
status: 'active',
options: [
{
key: '1',
text: 'Opción 1',
action: 'message',
value: 'Respuesta de la opción 1',
order_index: 1
},
{
key: '2',
text: 'Opción 2',
action: 'message',
value: 'Respuesta de la opción 2',
order_index: 2
}
]
};
try {
const response = await fetch('./api/save_menu.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(testMenu)
});
const data = await response.json();
showResult('✅ SAVE Menú - Respuesta', data);
if (data.success) {
// Actualizar lista después de crear
setTimeout(testGetMenus, 1000);
}
} catch (error) {
showResult('❌ Error SAVE Menú', error.message, true);
}
}
async function testDeleteMenu() {
// Primero obtener menús para encontrar uno que eliminar
try {
const getResponse = await fetch('./api/get_menus.php');
const getData = await getResponse.json();
if (getData.success && getData.data.length > 0) {
// Buscar un menú de prueba para eliminar
const testMenu = getData.data.find(menu => menu.menu_key.startsWith('test_'));
if (testMenu) {
const deleteResponse = await fetch('./api/delete_menu.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
menu_id: testMenu.id
})
});
const deleteData = await deleteResponse.json();
showResult(`🗑️ DELETE Menú (ID: ${testMenu.id})`, deleteData);
if (deleteData.success) {
// Actualizar lista después de eliminar
setTimeout(testGetMenus, 1000);
}
} else {
showResult('️ DELETE Menú', 'No se encontró un menú de prueba para eliminar. Crea uno primero.');
}
} else {
showResult('️ DELETE Menú', 'No hay menús para eliminar');
}
} catch (error) {
showResult('❌ Error DELETE Menú', error.message, true);
}
}
// Ejecutar test inicial
document.addEventListener('DOMContentLoaded', function() {
testGetMenus();
});
</script>
</body>
</html>
+203
View File
@@ -0,0 +1,203 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test JavaScript Menús</title>
<style>
body { font-family: Arial, sans-serif; max-width: 1000px; margin: 0 auto; padding: 20px; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
th { background-color: #f5f5f5; }
.btn { padding: 5px 10px; margin: 2px; border: none; border-radius: 3px; cursor: pointer; }
.btn-outline-primary { border: 1px solid #0066cc; color: #0066cc; background: white; }
.btn-outline-info { border: 1px solid #17a2b8; color: #17a2b8; background: white; }
.btn-outline-danger { border: 1px solid #dc3545; color: #dc3545; background: white; }
.badge { padding: 4px 8px; border-radius: 4px; font-size: 0.8em; }
.bg-success { background-color: #28a745; color: white; }
.bg-secondary { background-color: #6c757d; color: white; }
.me-1 { margin-right: 5px; }
.text-center { text-align: center; }
.text-muted { color: #6c757d; }
#result { margin: 20px 0; padding: 15px; background: #f8f9fa; border-radius: 5px; }
</style>
</head>
<body>
<h1>🧪 Test JavaScript - Lista de Menús</h1>
<button onclick="testMenusDisplay()">📋 Cargar y Mostrar Menús</button>
<button onclick="clearTable()">🧽 Limpiar Tabla</button>
<div id="result"></div>
<h2>📋 Tabla de Menús</h2>
<table>
<thead>
<tr>
<th>Nombre</th>
<th>Clave</th>
<th>Descripción</th>
<th>Estado</th>
<th>Opciones</th>
<th>Fecha Creación</th>
<th>Acciones</th>
</tr>
</thead>
<tbody id="menus-table">
<tr><td colspan="7" class="text-center text-muted">Haz clic en "Cargar y Mostrar Menús" para ver los datos</td></tr>
</tbody>
</table>
<script>
// Simular la funcionalidad de WhatsApp Manager
class MenuTester {
constructor() {
this.apiBaseUrl = './api/';
}
log(message, type = 'info') {
console.log(`[${type.toUpperCase()}] ${message}`);
}
truncateText(text, length) {
if (!text) return '';
return text.length > length ? text.substring(0, length) + '...' : text;
}
async apiCall(endpoint, options = {}) {
const url = `${this.apiBaseUrl}${endpoint}`;
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();
return data;
} catch (error) {
throw error;
}
}
updateMenusList(menus) {
const container = document.getElementById('menus-table');
if (!container) {
console.error('Contenedor menus-table no encontrado');
return;
}
if (menus.length === 0) {
container.innerHTML = '<tr><td colspan="7" class="text-center text-muted">No hay menús configurados</td></tr>';
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 += `
<tr>
<td><strong>${menu.menu_name || menu.name || 'Sin nombre'}</strong></td>
<td><code>${menu.menu_key || menu.name || 'sin-clave'}</code></td>
<td>${this.truncateText(menu.description || '', 50)}</td>
<td><span class="badge bg-${menu.status === 'active' ? 'success' : 'secondary'}">${menu.status || 'active'}</span></td>
<td>${menu.options_count || 0} opciones</td>
<td>${formatDate}</td>
<td>
<button class="btn btn-outline-primary me-1" onclick="editMenu(${menu.id})" title="Editar">
📝
</button>
<button class="btn btn-outline-info me-1" onclick="viewMenuOptions(${menu.id})" title="Ver opciones">
📋
</button>
<button class="btn btn-outline-danger" onclick="deleteMenu(${menu.id})" title="Eliminar">
🗑️
</button>
</td>
</tr>
`;
});
container.innerHTML = html;
console.log(`✅ Tabla actualizada con ${menus.length} menús`);
}
async loadMenus() {
try {
const response = await this.apiCall('get_menus.php');
document.getElementById('result').innerHTML = `
<h3>📡 Respuesta de API:</h3>
<pre>${JSON.stringify(response, null, 2)}</pre>
`;
if (response && response.success) {
this.updateMenusList(response.data || []);
return response.data;
} else if (response && Array.isArray(response)) {
this.updateMenusList(response);
return response;
} else {
throw new Error('Formato de respuesta inválido');
}
} catch (error) {
document.getElementById('result').innerHTML = `
<h3>❌ Error:</h3>
<p style="color: red;">${error.message}</p>
`;
console.error('Error cargando menús:', error);
}
}
}
// Instanciar el tester
const menuTester = new MenuTester();
// Funciones globales
function testMenusDisplay() {
console.log('🧪 Iniciando test de carga de menús...');
menuTester.loadMenus();
}
function clearTable() {
const container = document.getElementById('menus-table');
if (container) {
container.innerHTML = '<tr><td colspan="7" class="text-center text-muted">Tabla limpiada</td></tr>';
}
document.getElementById('result').innerHTML = '';
}
function editMenu(id) {
alert(`Editar menú ID: ${id}`);
}
function viewMenuOptions(id) {
alert(`Ver opciones del menú ID: ${id}`);
}
function deleteMenu(id) {
if (confirm(`¿Eliminar menú ID: ${id}?`)) {
alert(`Eliminando menú ID: ${id}`);
}
}
// Auto-cargar al inicio
document.addEventListener('DOMContentLoaded', function() {
console.log('✅ Página cargada, ready para test');
});
</script>
</body>
</html>