menus
This commit is contained in:
@@ -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
@@ -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
@@ -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()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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>
|
||||
@@ -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
@@ -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>";
|
||||
?>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user