This commit is contained in:
Lizandro Guarnizo
2026-01-22 10:33:16 -05:00
parent d6e50b2a98
commit 99bedaa5a6
88 changed files with 184 additions and 8549 deletions
+20
View File
@@ -0,0 +1,20 @@
# Ignorar archivos de desarrollo y pruebas
/dev/
/tmp/
/tests/
# Ignorar credenciales y entornos
.env
CREDENCIALES_BASICO.txt
# Logs y temporales
*.log
*.cache
*.pid
# Composer / Node
vendor/
node_modules/
# MacOS
.DS_Store
+10
View File
@@ -56,6 +56,16 @@ RewriteEngine On
RedirectMatch 403 ^/tests/.*$
</IfModule>
# Proteger directorio de desarrollo (dev) y archivos de pruebas
<IfModule mod_alias.c>
RedirectMatch 403 ^/dev/.*$
</IfModule>
# Proteger directorio temporal
<IfModule mod_alias.c>
RedirectMatch 403 ^/tmp/.*$
</IfModule>
# ==================================================
# HEADERS DE SEGURIDAD (Compatible HestiaCP)
# ==================================================
-3
View File
@@ -1,3 +0,0 @@
USUARIO: admin
CONTRASEÑA: 2fa904eee46f
FECHA: 2026-01-04 03:04:54
+88 -290
View File
@@ -1,295 +1,93 @@
# 🤖 Sistema de WhatsApp Bot Manager
# WhatsApp Bot Manager 🚀
Un sistema completo de gestión de chatbot para WhatsApp Business API con interfaz web moderna, menús parametrizables y gestión avanzada de conversaciones.
## ✨ Características
### 🔧 Funcionalidades Principales
- **Bot Inteligente** con menús parametrizables por números
- **Interfaz Web Moderna** para gestión completa
- **API REST** para integración con sistemas externos
- **Respuestas Automáticas** configurables
- **Envío de Mensajes** individuales y masivos
- **Gestión de Usuarios** con estados y seguimiento
- **Dashboard Analytics** con gráficos en tiempo real
- **Logs Detallados** de todas las interacciones
- **Sistema de Plantillas** para mensajes predefinidos
### 🎯 Sistema de Menús
- Menús jerárquicos con navegación por números (1, 2, 3, etc.)
- Acciones configurables: navegar, mensaje, API call, finalizar
- Soporte para servicios, pagos, soporte técnico
- Completamente parametrizable desde la interfaz
### 📱 Compatibilidad WhatsApp
- Integración completa con WhatsApp Business API
- Soporte para mensajes de texto, imágenes, audio, video
- Plantillas de WhatsApp Business
- Estados de entrega y lectura
- Webhook seguro con verificación
## 🚀 Instalación
### 1. Prerrequisitos
```bash
- PHP 8.0 o superior
- MySQL 5.7 o superior
- Servidor web (Apache/Nginx)
- WhatsApp Business API Token
- Dominio con SSL (para webhook)
```
### 2. Configuración de Base de Datos
```bash
# Importar el esquema de la base de datos
mysql -u pym -p'Nicolas2796*+' -h 46.202.93.92 whatsapp < database/schema.sql
```
### 3. Configuración del Proyecto
```bash
# Subir archivos al servidor
# Ajustar permisos
chmod 755 -R bot/
chmod 666 config/config.php
```
### 4. Configurar WhatsApp Business API
1. **Acceder a Facebook Developers**
- Crear una aplicación en developers.facebook.com
- Agregar el producto "WhatsApp Business API"
2. **Configurar Webhook**
- URL del Webhook: `https://tudominio.com/migrador/api/webhook.php`
- Token de Verificación: `mi_token_secreto_123`
- Suscribirse a: `conversations`
3. **Obtener Tokens**
- Token de Acceso (ya proporcionado)
- Phone Number ID: `938177732702718`
### 5. Configuración en la Interfaz
1. Abrir: `https://tudominio.com/migrador/`
2. Ir a **Configuración**
3. Completar:
- Token de WhatsApp
- Phone Number ID
- Token de Verificación
- Nombre de la empresa
- Mensaje de bienvenida
## 📖 Uso del Sistema
### 🎮 Interfaz Web
- **Dashboard**: Estadísticas y actividad en tiempo real
- **Conversaciones**: Ver y gestionar todas las conversaciones
- **Usuarios**: Administrar usuarios y estados
- **Menús**: Configurar y editar el flujo del bot
- **Mensajes**: Enviar mensajes individuales o masivos
- **Configuración**: Ajustar parámetros del sistema
### 🤖 Bot de WhatsApp
#### Comandos Disponibles
- `menu` - Mostrar menú principal
- `ayuda` - Mostrar ayuda
- `salir` - Salir del menú actual
#### Flujo de Menús (Ejemplo)
```
📋 Menú Principal
Selecciona una opción:
1. 💼 Ver servicios disponibles
2. 💳 Realizar pago
3. 🆘 Contactar soporte
0. ❌ Salir
💬 Responde con el número de la opción que deseas
```
### 🔧 API Endpoints
#### Webhook
```bash
POST /api/webhook.php
# Recibe mensajes de WhatsApp
```
#### Enviar Mensaje
```bash
POST /api/send_message.php
Content-Type: application/json
{
"recipient": "573168950803",
"type": "text",
"message": "Hola, este es un mensaje de prueba"
}
```
#### Obtener Estadísticas
```bash
GET /api/get_stats.php
```
## 🏗️ Estructura del Proyecto
```
migrador/
├── api/ # Endpoints de la API
│ ├── webhook.php # Webhook principal
│ ├── send_message.php # Envío de mensajes
│ ├── get_stats.php # Estadísticas
│ └── ... # Otras APIs
├── assets/ # Recursos estáticos
│ ├── css/ # Estilos CSS
│ └── js/ # JavaScript
├── classes/ # Clases principales
│ └── Database.php # Conexión a BD
├── config/ # Configuración
│ └── config.php # Configuración principal
├── database/ # Base de datos
│ └── schema.sql # Esquema de BD
├── services/ # Servicios del sistema
│ ├── WhatsAppService.php # Servicio WhatsApp
│ └── BotService.php # Lógica del bot
└── index.php # Página principal
```
## 📊 Base de Datos
### Tablas Principales
- `users` - Usuarios del sistema
- `conversations` - Historial de mensajes
- `menus` - Configuración de menús
- `menu_options` - Opciones de cada menú
- `autoresponses` - Respuestas automáticas
- `system_config` - Configuración del sistema
- `webhook_logs` - Logs de webhooks
## 🔧 Configuración Avanzada
### Menús Personalizados
```sql
-- Crear menú personalizado
INSERT INTO menus (name, title, description, is_root) VALUES
('mi_menu', '🏪 Mi Tienda', 'Menú de productos', TRUE);
-- Agregar opciones
INSERT INTO menu_options (menu_id, option_number, text, action_type, action_value) VALUES
(1, 1, '📱 Ver catálogo', 'message', 'Aquí tienes nuestro catálogo: www.mitienda.com'),
(1, 2, '🛒 Hacer pedido', 'message', 'Para hacer un pedido, envía el código del producto'),
(1, 0, '🔙 Volver', 'menu', 'main_menu');
```
### Respuestas Automáticas
```sql
-- Agregar respuesta automática
INSERT INTO autoresponses (trigger_type, trigger_value, response_text) VALUES
('keyword', 'precio', 'Para consultar precios, escribe *catalogo* o visita www.mitienda.com'),
('keyword', 'horarios', 'Atendemos de Lunes a Viernes de 8am a 6pm');
```
## 🔐 Seguridad
- Validación de tokens en webhook
- Escape de datos SQL
- Headers de seguridad HTTP
- Logs de todas las transacciones
- Autenticación de API
## 🐛 Solución de Problemas
### Error de Conexión a BD
```bash
# Verificar credenciales en config/config.php
# Verificar que el servidor MySQL esté activo
# Comprobar permisos del usuario de BD
```
### Webhook No Funciona
```bash
# Verificar que la URL sea accesible públicamente
# Comprobar que tenga SSL (HTTPS)
# Revisar logs en /api/get_logs.php
```
### Bot No Responde
```bash
# Verificar token de WhatsApp en configuración
# Comprobar que el webhook esté configurado
# Revisar logs de errores PHP
```
## 📞 Ejemplo de Uso Completo
### 1. Configuración Inicial
```bash
# Bot configurado para empresa de servicios
# Menús: Servicios, Pagos, Soporte
# Respuestas automáticas para palabras clave
```
### 2. Flujo de Usuario
```
Usuario: "Hola"
Bot: "¡Hola! 👋 Bienvenido a nuestro servicio automatizado. Escribe *menu* para ver las opciones disponibles."
Usuario: "menu"
Bot: "📋 Menú Principal
1. 💼 Ver servicios disponibles
2. 💳 Realizar pago
3. 🆘 Contactar soporte
0. ❌ Salir"
Usuario: "1"
Bot: "💼 Servicios
1. 📋 Consulta de información
2. 📞 Agendar cita
3. 💰 Cotizar servicio
0. 🔙 Volver al menú principal"
Usuario: "2"
Bot: "Para agendar una cita, por favor proporciona tu nombre completo y tu disponibilidad horaria."
```
## 🎨 Personalización
### Colores y Estilos
```css
/* Modificar en assets/css/styles.css */
:root {
--primary-color: #25d366; /* Verde WhatsApp */
--secondary-color: #128c7e;
--tertiary-color: #075e54;
}
```
### Mensajes del Bot
```sql
-- Modificar mensaje de bienvenida
UPDATE system_config
SET config_value = 'Tu mensaje personalizado'
WHERE config_key = 'welcome_message';
```
## 📈 Monitoreo
- Dashboard en tiempo real con métricas
- Gráficos de actividad por día
- Estados de entrega de mensajes
- Logs detallados de todas las operaciones
## 🤝 Soporte
Para soporte técnico o consultas:
- Email: soporte@tuempresa.com
- WhatsApp: +57 1 234 5678
- Web: www.tuempresa.com
**WhatsApp Bot Manager** es un panel administrativo para gestionar bots y envíos de WhatsApp. Desarrollado por USite.app, pensado para instalaciones autohospedadas con PHP y MySQL.
---
**Desarrollado con ❤️ para facilitar la comunicación empresarial a través de WhatsApp**
## 🔧 Características principales
© 2025 - Sistema WhatsApp Bot Manager v1.0.0
- Gestión de conversaciones y plantillas
- Integración con WhatsApp Business API
- Control de usuarios y seguridad (protección contra fuerza bruta)
- Manejo de archivos multimedia y envíos
- Scripts de instalación y migración incluidos
---
## ✅ Requisitos
- PHP 8.0+ (extensiones: PDO/MySQL, mbstring, curl, openssl, json)
- MySQL/MariaDB
- Servidor web (Apache/Nginx) o PHP built-in para desarrollo
---
## 🛠 Instalación rápida (desarrollo)
1. Clonar el repositorio y entrar en la carpeta:
```bash
git clone <repo-url> whatsapp
cd whatsapp
```
2. Configurar la base de datos en `config/config.php` o crear un archivo `.env` en la raíz.
3. Ejecutar el instalador desde el navegador: `http://tu-servidor/install.php` y seguir los pasos.
4. Para levantar un servidor de desarrollo rápido:
```bash
php -S 127.0.0.1:8000 -t . >/tmp/php-server.log 2>&1 &
# Abre: http://127.0.0.1:8000
```
---
## 🧭 Archivos y scripts útiles
- `install.php`, `instalar_bd.php` — instalador y creación de tablas
- `create_media_table.sql`, `execute_media_table.php` — tabla de media
- `config/config.php` — configuración principal y carga de `.env`
- `run_tests.php` — pruebas básicas
- `deploy.sh` — script de despliegue (producción)
- `verify_*` y `diagnostico_*` — páginas de diagnóstico y verificación
Consulta `README_SETUP.md`, `INSTALACION.md` y `DEPLOYMENT_GUIDE.md` para documentación extendida.
---
## 🎨 Personalización de UI
- Estilos principales: `assets/css/styles.css`
- Para ajustar el tamaño global de fuente cambia `html { font-size: ... }` o `body { font-size: ... }`
---
## 🧪 Solución rápida de problemas
- Logs del servidor integrado: `/tmp/php-server.log`
- Verificar tokens y configuración con: `verificar_servidor.php`, `verificar_token.php`, `verificar_whatsapp_config.php`
- Comprobar tablas con: `verify_media_table.php`, `check_table_structure.php`
---
## Notas de seguridad: directorios de pruebas
Los recursos de pruebas y depuración (scripts `test_*`, `debug_*`, `diagnostico_*`, `tests/`, `scripts/` de prueba, etc.) han sido movidos al directorio `dev/`. **No** expongas `dev/` en entornos de producción. Recomendaciones:
- Mantén `dev/` fuera del documento raíz público o protégelo con HTTP auth/.htaccess.
- Añade `dev/` y `tmp/` a `.gitignore` y rota credenciales que hayan estado en `CREDENCIALES_BASICO.txt`.
- Revisa y elimina cualquier endpoint que active modo `debug` vía parámetros en producción.
---
## 📬 Soporte y créditos
Desarrollado por **USite.app** — https://u-site.app
Para soporte, abre un issue o contacta al correo de soporte configurado en la app.
---
*README corto y enfocado. Revisa los archivos de documentación para guías detalladas.*
-4
View File
@@ -1,4 +0,0 @@
<?php
if (php_sapi_name() !== 'cli') header('Content-Type: application/json; charset=utf-8');
if (function_exists('writeLog')) writeLog('DEBUG','Health check called', ['uri' => $_SERVER['REQUEST_URI'] ?? '']);
echo json_encode(['ok'=>true,'uri'=>$_SERVER['REQUEST_URI'] ?? '']);
-16
View File
@@ -1,16 +0,0 @@
<?php
require_once __DIR__ . '/../config/config_enhanced.php';
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../api/webhook.php';
header('Content-Type: application/json; charset=utf-8');
try {
$w = new WhatsAppWebhook();
echo json_encode(['ok' => true, 'message' => 'Webhook class instantiated successfully']);
} catch (Throwable $t) {
http_response_code(500);
error_log('[debug_webhook_init] Throwable: ' . $t->getMessage());
error_log($t->getTraceAsString());
echo json_encode(['ok' => false, 'error' => $t->getMessage(), 'trace' => $t->getTraceAsString()]);
}
-80
View File
@@ -1,80 +0,0 @@
<?php
/**
* Endpoint/Script de prueba para enviar plantilla con varios idiomas
* Uso por navegador: GET /api/test_send_template.php?recipient=573168950803&template=encuesta_satisfaccin
* Uso por CLI: php api/test_send_template.php
*/
require_once __DIR__ . '/../config/config_enhanced.php';
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../services/WhatsAppService.php';
header('Content-Type: application/json; charset=utf-8');
error_reporting(E_ALL);
ini_set('display_errors', 1);
error_log("[test_send_template] START - " . date('c'));
try {
$recipient = $_GET['recipient'] ?? ($_SERVER['argv'][1] ?? '573168950803');
$template = $_GET['template'] ?? ($_SERVER['argv'][2] ?? 'encuesta_satisfaccin');
$languages = ['es', 'es_ES', 'es_MX', 'es_PE'];
$result = [
'recipient' => $recipient,
'template' => $template,
'attempts' => []
];
try {
$wh = new WhatsAppService();
error_log("[test_send_template] WhatsAppService initialized");
} catch (Exception $e) {
error_log("[test_send_template] ERROR init: " . $e->getMessage());
$result['error'] = 'Error initializing WhatsAppService: ' . $e->getMessage();
$result['trace'] = $e->getTraceAsString();
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
exit(1);
}
foreach ($languages as $lang) {
error_log("[test_send_template] Attempting language: $lang");
try {
$res = $wh->sendTemplateMessage($recipient, $template, $lang, []);
$result['attempts'][] = [
'language' => $lang,
'success' => true,
'response' => $res
];
error_log("[test_send_template] Success with language: $lang");
// stop on first success
break;
} catch (Exception $e) {
$msg = $e->getMessage();
error_log("[test_send_template] Attempt language $lang failed: " . $msg);
$result['attempts'][] = [
'language' => $lang,
'success' => false,
'error' => $msg,
'trace' => $e->getTraceAsString()
];
// continuar con siguiente candidato
}
}
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
} catch (Throwable $t) {
$err = '[test_send_template][FATAL] ' . $t->getMessage();
error_log($err);
error_log($t->getTraceAsString());
http_response_code(500);
echo json_encode(['error' => $t->getMessage(), 'trace' => $t->getTraceAsString()]);
exit(1);
}
// Si es CLI, exit code 0 si algún intento fue exitoso
$anySuccess = count(array_filter($result['attempts'], fn($a) => $a['success'])) > 0;
exit($anySuccess ? 0 : 1);
+10
View File
@@ -19,6 +19,16 @@
--info-color: #3b82f6;
}
/* Ajuste global: reducir tamaño de letra base en toda la app */
html {
font-size: 14px; /* Default 16px -> 14px para fuentes más pequeñas */
}
/* Ajuste base para compatibilidad con rem */
body {
font-size: 0.95rem;
}
* {
margin: 0;
padding: 0;
-175
View File
@@ -1,175 +0,0 @@
<?php
/**
* 🧪 CREADOR DE DATOS DE PRUEBA
* Crea usuarios y conversaciones de muestra para probar el sistema
*/
// Headers para mostrar errores
error_reporting(E_ALL);
ini_set('display_errors', 1);
echo "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
echo "<title>🧪 Datos de Prueba</title>";
echo "<style>body{font-family:monospace;margin:20px;background:#000;color:#00ff00}";
echo ".ok{color:#00ff00}.error{color:#ff0040}.warning{color:#ffaa00}";
echo ".section{background:#111;padding:15px;margin:10px 0;border:1px solid #333}";
echo "</style></head><body>";
echo "<h1>🧪 CREADOR DE DATOS DE PRUEBA</h1>";
try {
require_once 'config/config.php';
$pdo = new PDO(
"mysql:host=" . DB_HOST . ";port=" . DB_PORT . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET,
DB_USER,
DB_PASS,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
echo "<div class='ok'>✅ Conectado a la base de datos</div>";
// Verificar si ya hay datos
$stmt = $pdo->query("SELECT COUNT(*) as count FROM users");
$userCount = $stmt->fetch()['count'];
$stmt = $pdo->query("SELECT COUNT(*) as count FROM conversations");
$convCount = $stmt->fetch()['count'];
echo "<div class='section'>";
echo "<h2>📊 ESTADO ACTUAL</h2>";
echo "<div class='warning'>Usuarios existentes: $userCount</div>";
echo "<div class='warning'>Conversaciones existentes: $convCount</div>";
echo "</div>";
if ($_POST && isset($_POST['create_data'])) {
echo "<div class='section'>";
echo "<h2>🔨 CREANDO DATOS DE PRUEBA...</h2>";
// Datos de usuarios de muestra - PERSONALIZA AQUÍ
$sampleUsers = [
['phone' => '+573168950803', 'name' => 'Usuario Principal']
];
$userIds = [];
foreach ($sampleUsers as $user) {
// Verificar si el usuario ya existe
$stmt = $pdo->prepare("SELECT id FROM users WHERE phone_number = ?");
$stmt->execute([$user['phone']]);
$existingUser = $stmt->fetch();
if ($existingUser) {
$userIds[] = $existingUser['id'];
echo "<div class='warning'>⚠️ Usuario ya existe: {$user['name']} ({$user['phone']})</div>";
} else {
$stmt = $pdo->prepare("
INSERT INTO users (phone_number, name, status, created_at)
VALUES (?, ?, 'active', NOW())
");
$stmt->execute([$user['phone'], $user['name']]);
$userIds[] = $pdo->lastInsertId();
echo "<div class='ok'>✅ Usuario creado: {$user['name']} ({$user['phone']})</div>";
}
}
// Crear conversaciones de muestra
$sampleconversations = [
['text' => 'Hola, necesito información sobre sus servicios', 'direction' => 'incoming'],
['text' => '¡Hola! Claro, te ayudo con gusto. ¿Qué tipo de servicio te interesa?', 'direction' => 'outgoing'],
['text' => 'Estoy buscando precios de desarrollo web', 'direction' => 'incoming'],
['text' => 'Perfecto, tenemos varios paquetes disponibles. Te envío la información.', 'direction' => 'outgoing'],
['text' => 'Gracias, muy amables', 'direction' => 'incoming'],
['text' => 'Buenos días, ¿están disponibles?', 'direction' => 'incoming'],
['text' => '¡Buenos días! Sí, estamos aquí para ayudarte. ¿En qué podemos asistirte?', 'direction' => 'outgoing'],
['text' => 'Quería hacer una consulta sobre horarios', 'direction' => 'incoming'],
['text' => 'Hola! Me pueden ayudar con un problema técnico?', 'direction' => 'incoming'],
['text' => 'Por supuesto! Cuéntanos qué problema tienes', 'direction' => 'outgoing']
];
$conversationsCreated = 0;
foreach ($userIds as $index => $userId) {
// Crear 2-3 mensajes por usuario
$messageCount = rand(2, 4);
for ($i = 0; $i < $messageCount; $i++) {
$messageIndex = ($index * $messageCount + $i) % count($sampleconversations);
$message = $sampleconversations[$messageIndex];
$messageData = [
'user_id' => $userId,
'content' => $message['text'],
'direction' => $message['direction'],
'message_type' => 'text',
'status' => $message['direction'] === 'outgoing' ? 'delivered' : 'sent',
'created_at' => date('Y-m-d H:i:s', strtotime("-" . rand(1, 72) . " hours"))
];
// Intentar insertar en conversations
try {
$stmt = $pdo->prepare("
INSERT INTO conversations (user_id, content, direction, message_type, status, created_at)
VALUES (?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$messageData['user_id'],
$messageData['content'],
$messageData['direction'],
$messageData['message_type'],
$messageData['status'],
$messageData['created_at']
]);
$conversationsCreated++;
} catch (Exception $e) {
// Si falla, intentar en conversations
try {
$stmt = $pdo->prepare("
INSERT INTO conversations (user_id, message_text, direction, message_type, status, created_at)
VALUES (?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$messageData['user_id'],
$messageData['content'],
$messageData['direction'],
$messageData['message_type'],
$messageData['status'],
$messageData['created_at']
]);
$conversationsCreated++;
} catch (Exception $e2) {
echo "<div class='error'>❌ Error creando mensaje: " . $e2->getMessage() . "</div>";
}
}
}
}
echo "<div class='ok'>✅ Usuarios creados: " . count($userIds) . "</div>";
echo "<div class='ok'>✅ Mensajes/conversaciones creados: $conversationsCreated</div>";
echo "<div class='ok'>🎉 ¡Datos de prueba creados exitosamente!</div>";
echo "<br><div class='warning'><strong>SIGUIENTE PASO:</strong></div>";
echo "<div class='warning'>• Visita: <a href='conversations.php' style='color:#ffaa00'>conversations.php</a></div>";
echo "<div class='warning'>• O ve al panel: <a href='index.php' style='color:#ffaa00'>index.php</a></div>";
echo "</div>";
}
} catch (Exception $e) {
echo "<div class='error'>❌ Error: " . htmlspecialchars($e->getMessage()) . "</div>";
}
if (!$_POST) {
echo "<div class='section'>";
echo "<h2>⚠️ CREAR DATOS DE PRUEBA</h2>";
echo "<div class='warning'>Esto creará usuarios y conversaciones de muestra para probar el sistema</div>";
echo "<div class='warning'>Solo ejecuta esto si no tienes datos reales o quieres probar</div>";
echo "<form method='POST'>";
echo "<button type='submit' name='create_data' value='1' style='padding:10px 20px;background:#333;color:#fff;border:1px solid #555;cursor:pointer'>🧪 Crear Datos de Prueba</button>";
echo "</form>";
echo "</div>";
}
echo "</body></html>";
?>
-269
View File
@@ -1,269 +0,0 @@
<?php
/**
* Instalador de depuración para hosting compartido
* Diagnóstica problemas específicos de conexión y configuración
* Desarrollado por U-Site.app
* Fecha: 13 de noviembre de 2025
*/
error_reporting(E_ALL);
ini_set('display_errors', 1);
$step = $_GET['step'] ?? 'test';
$dbHost = $_POST['db_host'] ?? 'localhost';
$dbPort = $_POST['db_port'] ?? '3306';
$dbName = $_POST['db_name'] ?? '';
$dbUser = $_POST['db_user'] ?? '';
$dbPass = $_POST['db_pass'] ?? '';
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🔍 Diagnóstico de Instalación</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
.console-output {
background: #1e1e1e;
color: #00ff00;
border-radius: 10px;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
max-height: 500px;
overflow-y: auto;
padding: 15px;
}
.success { color: #00ff00; }
.error { color: #ff4444; }
.warning { color: #ffaa00; }
.info { color: #00aaff; }
</style>
</head>
<body class="bg-dark text-light">
<div class="container mt-4">
<div class="row">
<div class="col-12">
<h2><i class="fas fa-bug me-2"></i>Diagnóstico de Instalación WhatsApp Bot</h2>
<p class="text-muted">Herramienta de depuración para identificar problemas</p>
</div>
</div>
<?php if ($step === 'test'): ?>
<!-- Formulario de prueba -->
<form method="POST" action="?step=debug">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Host de BD</label>
<input type="text" class="form-control" name="db_host" value="<?= htmlspecialchars($dbHost) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label">Puerto</label>
<input type="text" class="form-control" name="db_port" value="<?= htmlspecialchars($dbPort) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label">Nombre de BD</label>
<input type="text" class="form-control" name="db_name" value="<?= htmlspecialchars($dbName) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label">Usuario de BD</label>
<input type="text" class="form-control" name="db_user" value="<?= htmlspecialchars($dbUser) ?>" required>
</div>
<div class="col-12">
<label class="form-label">Contraseña de BD</label>
<input type="password" class="form-control" name="db_pass" value="<?= htmlspecialchars($dbPass) ?>" required>
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">
<i class="fas fa-search me-2"></i>Ejecutar Diagnóstico
</button>
</div>
</div>
</form>
<?php elseif ($step === 'debug'): ?>
<!-- Ejecutar diagnóstico -->
<div class="console-output">
<div class="info">WhatsApp Bot - Diagnóstico de Instalación</div>
<div class="info">Fecha: <?= date('Y-m-d H:i:s') ?></div>
<div class="info">═══════════════════════════════════════════</div>
<?php
// Test 1: Verificar extensiones PHP
echo "<div class='info'>📋 Test 1: Verificando extensiones PHP...</div>";
$requiredExtensions = ['pdo', 'pdo_mysql', 'json', 'curl', 'mbstring'];
foreach ($requiredExtensions as $ext) {
if (extension_loaded($ext)) {
echo "<div class='success'>✅ Extensión '$ext': Disponible</div>";
} else {
echo "<div class='error'>❌ Extensión '$ext': NO disponible</div>";
}
}
// Test 2: Verificar archivos del sistema
echo "<div class='info'>📁 Test 2: Verificando archivos del sistema...</div>";
$requiredFiles = [
'config/config.php' => 'Configuración principal',
'classes/Database.php' => 'Clase Database',
'database/schema.sql' => 'Esquema de BD',
'api/webhook.php' => 'Webhook API'
];
foreach ($requiredFiles as $file => $desc) {
if (file_exists($file)) {
$size = filesize($file);
echo "<div class='success'>✅ $desc ($file): Existe ($size bytes)</div>";
} else {
echo "<div class='error'>❌ $desc ($file): NO existe</div>";
}
}
// Test 3: Probar conexión a servidor MySQL
echo "<div class='info'>🔌 Test 3: Probando conexión a servidor MySQL...</div>";
try {
$dsn = "mysql:host={$dbHost};port={$dbPort};charset=utf8mb4";
$pdo_test = new PDO($dsn, $dbUser, $dbPass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 5,
]);
echo "<div class='success'>✅ Conexión al servidor MySQL: EXITOSA</div>";
// Obtener información del servidor
$version = $pdo_test->query("SELECT VERSION()")->fetchColumn();
echo "<div class='success'>✅ Versión MySQL: $version</div>";
} catch (PDOException $e) {
echo "<div class='error'>❌ Error de conexión: " . $e->getMessage() . "</div>";
echo "<div class='warning'>💡 Posibles causas:</div>";
echo "<div class='warning'> - Credenciales incorrectas</div>";
echo "<div class='warning'> - MySQL no está corriendo</div>";
echo "<div class='warning'> - Firewall bloqueando conexión</div>";
}
// Test 4: Verificar acceso a base de datos específica
echo "<div class='info'>💾 Test 4: Verificando acceso a base de datos '$dbName'...</div>";
try {
$dsn = "mysql:host={$dbHost};port={$dbPort};dbname={$dbName};charset=utf8mb4";
$pdo_db = new PDO($dsn, $dbUser, $dbPass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 5,
]);
echo "<div class='success'>✅ Acceso a base de datos '$dbName': EXITOSO</div>";
// Verificar permisos
try {
$pdo_db->exec("CREATE TABLE test_permissions (id INT PRIMARY KEY)");
$pdo_db->exec("DROP TABLE test_permissions");
echo "<div class='success'>✅ Permisos CREATE/DROP: Disponibles</div>";
} catch (PDOException $e) {
echo "<div class='warning'>⚠️ Permisos limitados: " . $e->getMessage() . "</div>";
}
// Listar tablas existentes
$stmt = $pdo_db->query("SHOW TABLES");
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
if (count($tables) > 0) {
echo "<div class='info'>📋 Tablas existentes (" . count($tables) . "):</div>";
foreach ($tables as $table) {
echo "<div class='info'> - $table</div>";
}
} else {
echo "<div class='warning'>⚠️ Base de datos vacía (sin tablas)</div>";
}
} catch (PDOException $e) {
echo "<div class='error'>❌ Error accediendo a BD '$dbName': " . $e->getMessage() . "</div>";
echo "<div class='warning'>💡 Posibles causas:</div>";
echo "<div class='warning'> - Base de datos no existe</div>";
echo "<div class='warning'> - Usuario sin permisos en esta BD</div>";
echo "<div class='warning'> - Nombre de BD incorrecto</div>";
}
// Test 5: Probar carga del archivo config.php
echo "<div class='info'>⚙️ Test 5: Probando carga de configuración...</div>";
try {
if (file_exists('config/config.php')) {
$configContent = file_get_contents('config/config.php');
// Verificar constantes principales
$constants = ['DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS'];
foreach ($constants as $constant) {
if (strpos($configContent, $constant) !== false) {
echo "<div class='success'>✅ Constante '$constant': Definida en config</div>";
} else {
echo "<div class='error'>❌ Constante '$constant': NO encontrada</div>";
}
}
// Intentar incluir config (sin ejecutar)
echo "<div class='success'>✅ Archivo config.php: Legible</div>";
} else {
echo "<div class='error'>❌ Archivo config.php: NO existe</div>";
}
} catch (Exception $e) {
echo "<div class='error'>❌ Error leyendo config: " . $e->getMessage() . "</div>";
}
// Test 6: Verificar schema.sql
echo "<div class='info'>📄 Test 6: Verificando archivo schema.sql...</div>";
try {
if (file_exists('database/schema.sql')) {
$schema = file_get_contents('database/schema.sql');
$statements = explode(';', $schema);
$createTables = 0;
$insertData = 0;
foreach ($statements as $statement) {
if (preg_match('/CREATE TABLE/i', $statement)) $createTables++;
if (preg_match('/INSERT INTO/i', $statement)) $insertData++;
}
echo "<div class='success'>✅ Schema SQL: $createTables tablas, $insertData inserts</div>";
// Verificar tablas principales
$mainTables = ['users', 'conversations', 'menus', 'system_config'];
foreach ($mainTables as $table) {
if (strpos($schema, "CREATE TABLE $table") !== false) {
echo "<div class='success'>✅ Tabla '$table': Definida en schema</div>";
} else {
echo "<div class='warning'>⚠️ Tabla '$table': NO definida en schema</div>";
}
}
} else {
echo "<div class='error'>❌ Archivo schema.sql: NO existe</div>";
}
} catch (Exception $e) {
echo "<div class='error'>❌ Error leyendo schema: " . $e->getMessage() . "</div>";
}
echo "<div class='info'>═══════════════════════════════════════════</div>";
echo "<div class='info'>Diagnóstico completado: " . date('H:i:s') . "</div>";
?>
</div>
<div class="mt-4">
<h5>🚀 Acciones sugeridas:</h5>
<div class="row g-3">
<div class="col-md-4">
<a href="install_manual.php" class="btn btn-success w-100">
<i class="fas fa-tools me-2"></i>Instalar Manual
</a>
</div>
<div class="col-md-4">
<a href="?step=test" class="btn btn-secondary w-100">
<i class="fas fa-redo me-2"></i>Ejecutar Otro Test
</a>
</div>
<div class="col-md-4">
<a href="test.php" class="btn btn-info w-100">
<i class="fas fa-vial me-2"></i>Test Sistema
</a>
</div>
</div>
</div>
<?php endif; ?>
</div>
</body>
</html>
-107
View File
@@ -1,107 +0,0 @@
<?php
/**
* Script de diagnóstico del sistema de login
* Desarrollado por U-Site.app
*/
echo "🔍 DIAGNÓSTICO DEL SISTEMA DE LOGIN\n";
echo "===================================\n\n";
// Verificar configuración
echo "1. VERIFICANDO CONFIGURACIÓN:\n";
require_once 'config/config.php';
echo " Usuario configurado: " . ADMIN_USERNAME . "\n";
echo " Hash de contraseña: " . substr(ADMIN_PASSWORD, 0, 20) . "...\n";
echo " Longitud del hash: " . strlen(ADMIN_PASSWORD) . " caracteres\n\n";
// Probar contraseña
echo "2. PROBANDO CONTRASEÑA:\n";
$testPassword = '2fa904eee46f';
echo " Contraseña a probar: " . $testPassword . "\n";
if (password_verify($testPassword, ADMIN_PASSWORD)) {
echo " ✅ La contraseña FUNCIONA correctamente\n";
} else {
echo " ❌ La contraseña NO funciona\n";
echo " 🔧 Regenerando hash...\n";
$newHash = password_hash($testPassword, PASSWORD_DEFAULT);
echo " Nuevo hash: " . $newHash . "\n";
// Actualizar config.php
$configFile = 'config/config.php';
$config = file_get_contents($configFile);
$pattern = "/define\('ADMIN_PASSWORD',\s*'[^']*'\);/";
$replacement = "define('ADMIN_PASSWORD', '" . $newHash . "');";
$newConfig = preg_replace($pattern, $replacement, $config);
if (file_put_contents($configFile, $newConfig)) {
echo " ✅ Hash actualizado en config.php\n";
}
}
// Verificar instalación
echo "\n3. VERIFICANDO INSTALACIÓN:\n";
$installationFile = '.installation_completed';
if (file_exists($installationFile)) {
echo " ✅ Sistema instalado correctamente\n";
} else {
echo " ❌ Sistema NO está instalado\n";
echo " 🔧 Creando archivo de instalación...\n";
file_put_contents($installationFile, date('Y-m-d H:i:s'));
echo " ✅ Archivo de instalación creado\n";
}
// Verificar funciones de seguridad
echo "\n4. VERIFICANDO FUNCIONES DE SEGURIDAD:\n";
function checkLoginAttempts($ip) {
// Simulación - en producción debería revisar base de datos
return true; // Permitir login
}
function clearLoginAttempts($ip) {
// Simulación - limpiar intentos
return true;
}
function recordFailedLogin($ip) {
// Simulación - registrar fallo
return true;
}
function isInstallationCompleted() {
return file_exists('.installation_completed');
}
echo " ✅ Funciones de seguridad disponibles\n";
// Simular proceso de login
echo "\n5. SIMULANDO PROCESO DE LOGIN:\n";
$testUsername = 'admin';
$testPassword = '2fa904eee46f';
echo " Usuario: " . $testUsername . "\n";
echo " Contraseña: " . $testPassword . "\n";
if ($testUsername === ADMIN_USERNAME) {
echo " ✅ Usuario correcto\n";
if (password_verify($testPassword, ADMIN_PASSWORD)) {
echo " ✅ Contraseña correcta\n";
echo " ✅ LOGIN DEBERÍA FUNCIONAR\n";
} else {
echo " ❌ Contraseña incorrecta\n";
echo " ❌ LOGIN FALLARÁ\n";
}
} else {
echo " ❌ Usuario incorrecto\n";
}
echo "\n6. CREDENCIALES FINALES:\n";
echo " Usuario: admin\n";
echo " Contraseña: 2fa904eee46f\n";
echo "\n🌐 Accede en: http://localhost:8080/login.php\n";
?>
-68
View File
@@ -1,68 +0,0 @@
<?php
/**
* Debug específico del proceso de login
*/
echo "🔍 DEBUG DEL PROCESO DE LOGIN\n";
echo "=============================\n\n";
// Simular datos del POST
$_POST['username'] = 'admin';
$_POST['password'] = '2fa904eee46f';
echo "Datos POST simulados:\n";
echo "Username: " . $_POST['username'] . "\n";
echo "Password: " . $_POST['password'] . "\n\n";
// Cargar config
require_once 'config/config.php';
echo "Configuración cargada:\n";
echo "ADMIN_USERNAME: " . ADMIN_USERNAME . "\n";
echo "ADMIN_PASSWORD (primeros 20 chars): " . substr(ADMIN_PASSWORD, 0, 20) . "...\n";
echo "Longitud hash: " . strlen(ADMIN_PASSWORD) . " caracteres\n\n";
// Simular el proceso del login.php
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
echo "Variables procesadas:\n";
echo "Username procesado: '$username'\n";
echo "Password procesado: '$password'\n\n";
echo "Verificaciones:\n";
// 1. Verificar usuario
if ($username === ADMIN_USERNAME) {
echo "✅ Usuario correcto\n";
// 2. Verificar contraseña
$adminHash = constant('ADMIN_PASSWORD');
echo "Hash a verificar: " . substr($adminHash, 0, 20) . "...\n";
if (password_verify($password, $adminHash)) {
echo "✅ Contraseña correcta\n";
echo "✅ LOGIN DEBERÍA SER EXITOSO\n";
} else {
echo "❌ Contraseña INCORRECTA\n";
// Debug adicional
echo "\nDEBUG ADICIONAL:\n";
echo "Password ingresado: '$password'\n";
echo "Longitud password: " . strlen($password) . "\n";
echo "Hash almacenado: $adminHash\n";
echo "Longitud hash: " . strlen($adminHash) . "\n";
// Probar crear nuevo hash
$testHash = password_hash($password, PASSWORD_DEFAULT);
echo "Nuevo hash de prueba: $testHash\n";
echo "¿Nuevo hash funciona?: " . (password_verify($password, $testHash) ? 'SÍ' : 'NO') . "\n";
}
} else {
echo "❌ Usuario incorrecto\n";
echo "Usuario esperado: '" . ADMIN_USERNAME . "'\n";
echo "Usuario recibido: '$username'\n";
}
echo "\n🌐 Ahora prueba en: http://localhost:8080/login.php\n";
?>
-64
View File
@@ -1,64 +0,0 @@
<?php
require_once 'config/config.php';
$db = Database::getInstance();
echo "<h2>Últimos 10 mensajes multimedia en la BD</h2>";
echo "<style>
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #4CAF50; color: white; }
tr:nth-child(even) { background-color: #f2f2f2; }
pre { background: #f4f4f4; padding: 10px; overflow-x: auto; }
</style>";
$conversations = $db->fetchAll("
SELECT
c.id,
c.user_id,
u.phone_number,
c.direction,
c.message_type,
c.content,
c.media_url,
c.created_at
FROM conversations c
LEFT JOIN users u ON c.user_id = u.id
WHERE c.message_type IN ('image', 'audio', 'video', 'document')
ORDER BY c.created_at DESC
LIMIT 10
");
echo "<table>";
echo "<tr>
<th>ID</th>
<th>Teléfono</th>
<th>Dir</th>
<th>Tipo</th>
<th>Content</th>
<th>Media URL</th>
<th>Fecha</th>
</tr>";
foreach ($conversations as $msg) {
echo "<tr>";
echo "<td>{$msg['id']}</td>";
echo "<td>{$msg['phone_number']}</td>";
echo "<td>{$msg['direction']}</td>";
echo "<td><strong>{$msg['message_type']}</strong></td>";
echo "<td><pre>" . htmlspecialchars(substr($msg['content'], 0, 200)) . "</pre></td>";
echo "<td><pre>" . htmlspecialchars(substr($msg['media_url'], 0, 100)) . "</pre></td>";
echo "<td>" . date('H:i:s', strtotime($msg['created_at'])) . "</td>";
echo "</tr>";
}
echo "</table>";
echo "<h3>Análisis:</h3>";
echo "<ul>";
echo "<li>Si 'Content' tiene JSON → Problema: se guardó la respuesta de WhatsApp en lugar del caption</li>";
echo "<li>Si 'Media URL' está vacía o tiene JSON → Problema: no se guardó correctamente la URL</li>";
echo "<li>Si 'Media URL' tiene un ID (sin http) → Problema: es el media_id de WhatsApp, no la URL local</li>";
echo "<li>Correcto: Media URL debería tener http://localhost/uploads/...</li>";
echo "</ul>";
?>
-313
View File
@@ -1,313 +0,0 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WhatsApp Bot Manager - Debug</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.debug-info { background: #f8f9fa; border-left: 4px solid #007bff; padding: 15px; margin: 10px 0; }
.success { color: #28a745; }
.error { color: #dc3545; }
.warning { color: #ffc107; }
</style>
</head>
<body>
<div class="container mt-4">
<h1>🔧 WhatsApp Bot Manager - Debug</h1>
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5>📡 Test de Conectividad</h5>
</div>
<div class="card-body">
<button onclick="testAPI()" class="btn btn-primary">Test API Simple</button>
<button onclick="testConfig()" class="btn btn-info">Test Configuraciones</button>
<button onclick="testWhatsApp()" class="btn btn-success">Test WhatsApp</button>
<div id="api-results" class="mt-3"></div>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h5>💾 Guardar Config de Prueba</h5>
</div>
<div class="card-body">
<div class="mb-2">
<input type="text" id="test-key" class="form-control" placeholder="Clave" value="test_frontend">
</div>
<div class="mb-2">
<input type="text" id="test-value" class="form-control" placeholder="Valor" value="Valor desde frontend">
</div>
<button onclick="saveTestConfig()" class="btn btn-warning">Guardar Test</button>
<div id="save-results" class="mt-2"></div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5>📊 Estado del Sistema</h5>
</div>
<div class="card-body">
<div id="system-status">
<div class="text-center">
<div class="spinner-border" role="status"></div>
<p>Verificando estado...</p>
</div>
</div>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h5>⚙️ Configuraciones Actuales</h5>
</div>
<div class="card-body">
<button onclick="loadConfigs()" class="btn btn-outline-primary btn-sm">🔄 Cargar</button>
<div id="config-list" class="mt-2">
<p class="text-muted">Haz clic en "Cargar" para ver las configuraciones</p>
</div>
</div>
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-header">
<h5>📝 Log de Debug</h5>
</div>
<div class="card-body">
<div id="debug-log" style="height: 200px; overflow-y: auto; background: #f8f9fa; padding: 10px; font-family: monospace; font-size: 12px;">
<!-- Log entries will appear here -->
</div>
</div>
</div>
</div>
<script>
// Sistema de logging simple
function logDebug(message, type = 'info') {
const log = document.getElementById('debug-log');
const timestamp = new Date().toLocaleTimeString();
const colors = {
info: '#007bff',
success: '#28a745',
error: '#dc3545',
warning: '#ffc107'
};
const entry = document.createElement('div');
entry.style.color = colors[type];
entry.innerHTML = `[${timestamp}] ${message}`;
log.appendChild(entry);
log.scrollTop = log.scrollHeight;
}
// Función para hacer peticiones AJAX simples
async function makeRequest(url, options = {}) {
logDebug(`Haciendo petición a: ${url}`, 'info');
try {
const response = await fetch(url, {
method: options.method || 'GET',
headers: {
'Content-Type': 'application/json',
...options.headers
},
body: options.body ? JSON.stringify(options.body) : undefined
});
logDebug(`Respuesta recibida: ${response.status} ${response.statusText}`, response.ok ? 'success' : 'error');
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
const data = await response.json();
logDebug(`Datos recibidos: ${JSON.stringify(data).substring(0, 100)}...`, 'success');
return data;
} catch (error) {
logDebug(`Error en petición: ${error.message}`, 'error');
throw error;
}
}
// Test básico de API
async function testAPI() {
const resultsDiv = document.getElementById('api-results');
resultsDiv.innerHTML = '<div class="spinner-border spinner-border-sm"></div> Probando API...';
try {
const result = await makeRequest('/api/test_api.php?action=ping');
resultsDiv.innerHTML = `
<div class="alert alert-success">
<strong>✅ API Funcionando:</strong><br>
Mensaje: ${result.message}<br>
Hora: ${result.timestamp}
</div>
`;
} catch (error) {
resultsDiv.innerHTML = `
<div class="alert alert-danger">
<strong>❌ Error:</strong> ${error.message}
</div>
`;
}
}
// Test de configuraciones
async function testConfig() {
const resultsDiv = document.getElementById('api-results');
resultsDiv.innerHTML = '<div class="spinner-border spinner-border-sm"></div> Probando configuraciones...';
try {
const result = await makeRequest('/api/test_api.php?action=test_db');
resultsDiv.innerHTML = `
<div class="alert alert-${result.success ? 'success' : 'danger'}">
<strong>${result.success ? '✅' : '❌'} Base de Datos:</strong><br>
Conectada: ${result.data?.db_connected ? 'Sí' : 'No'}<br>
Tabla existe: ${result.data?.table_exists ? 'Sí' : 'No'}<br>
Configuraciones: ${result.data?.config_count || 0}
</div>
`;
} catch (error) {
resultsDiv.innerHTML = `
<div class="alert alert-danger">
<strong>❌ Error:</strong> ${error.message}
</div>
`;
}
}
// Test de WhatsApp
async function testWhatsApp() {
const resultsDiv = document.getElementById('api-results');
resultsDiv.innerHTML = '<div class="spinner-border spinner-border-sm"></div> Probando WhatsApp...';
try {
const result = await makeRequest('/api/test_api.php?action=whatsapp');
resultsDiv.innerHTML = `
<div class="alert alert-info">
<strong>📱 WhatsApp Config:</strong><br>
Token: ${result.data.token || '(vacío)'}<br>
Phone ID: ${result.data.phone_number_id || '(vacío)'}<br>
Válido: ${result.data.status.valid ? 'Sí' : 'No'}
</div>
`;
} catch (error) {
resultsDiv.innerHTML = `
<div class="alert alert-danger">
<strong>❌ Error:</strong> ${error.message}
</div>
`;
}
}
// Guardar configuración de prueba
async function saveTestConfig() {
const key = document.getElementById('test-key').value;
const value = document.getElementById('test-value').value;
const resultsDiv = document.getElementById('save-results');
if (!key || !value) {
resultsDiv.innerHTML = '<div class="alert alert-warning">Por favor llena ambos campos</div>';
return;
}
resultsDiv.innerHTML = '<div class="spinner-border spinner-border-sm"></div> Guardando...';
try {
const result = await makeRequest('/api/test_api.php?action=save_test', {
method: 'POST',
body: { key, value }
});
resultsDiv.innerHTML = `
<div class="alert alert-${result.success ? 'success' : 'danger'}">
<strong>${result.success ? '✅' : '❌'} ${result.message}</strong><br>
${result.success ? `Guardado: ${result.data.key} = ${result.data.value}` : ''}
</div>
`;
} catch (error) {
resultsDiv.innerHTML = `
<div class="alert alert-danger">
<strong>❌ Error:</strong> ${error.message}
</div>
`;
}
}
// Cargar configuraciones
async function loadConfigs() {
const configDiv = document.getElementById('config-list');
configDiv.innerHTML = '<div class="spinner-border spinner-border-sm"></div> Cargando...';
try {
const result = await makeRequest('/api/test_api.php?action=all');
if (result.success && result.data && Object.keys(result.data).length > 0) {
let html = '<div class="table-responsive"><table class="table table-sm"><thead><tr><th>Clave</th><th>Valor</th></tr></thead><tbody>';
for (const [key, config] of Object.entries(result.data)) {
const value = config.value || config;
const displayValue = typeof value === 'string' && value.length > 30
? value.substring(0, 30) + '...'
: value;
html += `<tr><td><code>${key}</code></td><td>${displayValue}</td></tr>`;
}
html += '</tbody></table></div>';
configDiv.innerHTML = html;
} else {
configDiv.innerHTML = '<div class="text-muted">No se encontraron configuraciones</div>';
}
} catch (error) {
configDiv.innerHTML = `<div class="alert alert-danger">Error: ${error.message}</div>`;
}
}
// Cargar estado del sistema
async function loadSystemStatus() {
const statusDiv = document.getElementById('system-status');
try {
const result = await makeRequest('/api/test_api.php?action=status');
if (result.success) {
const data = result.data;
statusDiv.innerHTML = `
<div class="row">
<div class="col-md-6">
<p><strong>Configuraciones:</strong> ${data.total_configs}</p>
<p><strong>WhatsApp:</strong> ${data.whatsapp_configured ? '✅ Configurado' : '❌ Sin configurar'}</p>
</div>
<div class="col-md-6">
<p><strong>BD Migrada:</strong> ${data.database_migrated ? '✅ Sí' : '❌ No'}</p>
<p><strong>Instalación:</strong> ${data.installation_completed ? '✅ Completada' : '⚠️ Pendiente'}</p>
</div>
</div>
<small class="text-muted">Última actualización: ${data.server_time}</small>
`;
}
} catch (error) {
statusDiv.innerHTML = `<div class="alert alert-danger">Error cargando estado: ${error.message}</div>`;
}
}
// Inicialización
document.addEventListener('DOMContentLoaded', function() {
logDebug('Sistema de debug iniciado', 'info');
loadSystemStatus();
});
</script>
</body>
</html>
-272
View File
@@ -1,272 +0,0 @@
<?php
/**
* 🚨 DIAGNÓSTICO RÁPIDO - ERROR 500
* Script para identificar la causa del error Internal Server Error
*/
// Deshabilitar reporting de errores por si está causando problemas
error_reporting(E_ALL);
ini_set('display_errors', 1);
echo "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
echo "<title>🚨 Diagnóstico Error 500</title>";
echo "<style>body{font-family:monospace;margin:20px;background:#1a1a1a;color:#00ff00}";
echo ".ok{color:#00ff00}.error{color:#ff0040}.warning{color:#ffaa00}";
echo ".section{background:#222;padding:15px;margin:10px 0;border-radius:5px}";
echo "h2{color:#00ccff;border-bottom:2px solid #333}";
echo "</style></head><body>";
echo "<h1>🚨 DIAGNÓSTICO ERROR 500 - WhatsApp Bot</h1>";
echo "<p>Fecha: " . date('Y-m-d H:i:s') . "</p>";
// 1. TEST BÁSICO PHP
echo "<div class='section'><h2>📍 1. TEST BÁSICO PHP</h2>";
echo "<div class='ok'>✅ PHP está funcionando (versión: " . phpversion() . ")</div>";
if (version_compare(phpversion(), '7.4', '>=')) {
echo "<div class='ok'>✅ Versión PHP compatible</div>";
} else {
echo "<div class='error'>❌ Versión PHP muy antigua (requiere 7.4+)</div>";
}
echo "</div>";
// 2. TEST ARCHIVOS CRÍTICOS
echo "<div class='section'><h2>📂 2. ARCHIVOS CRÍTICOS</h2>";
$critical_files = [
'config/config.php' => 'Configuración principal',
'classes/Database.php' => 'Clase Database',
'index.php' => 'Dashboard principal',
'login.php' => 'Sistema de login',
'.htaccess' => 'Configuración Apache'
];
foreach ($critical_files as $file => $desc) {
if (file_exists($file)) {
echo "<div class='ok'>✅ $file ($desc)</div>";
// Test de carga PHP (sin shell_exec)
if (str_ends_with($file, '.php')) {
try {
// Intentar incluir el archivo para verificar errores
if ($file === 'config/config.php') {
// Test especial para config.php
ob_start();
$error_before = error_get_last();
include_once $file;
$output = ob_get_clean();
$error_after = error_get_last();
if ($error_after && $error_after !== $error_before) {
echo "<div class='error'> → ❌ Error en archivo: " . htmlspecialchars($error_after['message']) . "</div>";
} elseif (!empty($output)) {
echo "<div class='warning'> → ⚠️ Archivo produce output: " . htmlspecialchars(substr($output, 0, 100)) . "</div>";
} else {
echo "<div class='ok'> → ✅ Archivo se carga correctamente</div>";
}
} else {
echo "<div class='ok'> → ✅ Archivo PHP existe</div>";
}
} catch (ParseError $e) {
echo "<div class='error'> → ❌ ERROR SINTAXIS: " . htmlspecialchars($e->getMessage()) . "</div>";
} catch (Error $e) {
echo "<div class='error'> → ❌ ERROR: " . htmlspecialchars($e->getMessage()) . "</div>";
} catch (Exception $e) {
echo "<div class='warning'> → ⚠️ ADVERTENCIA: " . htmlspecialchars($e->getMessage()) . "</div>";
}
}
} else {
echo "<div class='error'>❌ FALTA: $file ($desc)</div>";
}
}
echo "</div>";
// 3. TEST PERMISOS
echo "<div class='section'><h2>🔐 3. PERMISOS</h2>";
$dirs_permissions = [
'logs/' => ['required' => '777', 'writable' => true],
'uploads/' => ['required' => '777', 'writable' => true],
'config/' => ['required' => '755', 'writable' => false],
'.' => ['required' => '755', 'writable' => false]
];
foreach ($dirs_permissions as $dir => $config) {
if (is_dir($dir)) {
$perms = substr(sprintf('%o', fileperms($dir)), -3);
echo "<div class='ok'>✅ $dir existe (permisos: $perms)</div>";
if ($config['writable'] && !is_writable($dir)) {
echo "<div class='error'> → ❌ No es escribible (necesita chmod 777)</div>";
}
} else {
echo "<div class='error'>❌ Directorio $dir no existe</div>";
}
}
echo "</div>";
// 4. TEST CONFIGURACIÓN
echo "<div class='section'><h2>⚙️ 4. CONFIGURACIÓN</h2>";
if (file_exists('config/config.php')) {
try {
ob_start();
include_once 'config/config.php';
$config_output = ob_get_clean();
if (!empty($config_output)) {
echo "<div class='error'>❌ config.php produce output: " . htmlspecialchars($config_output) . "</div>";
} else {
echo "<div class='ok'>✅ config.php se carga sin errores</div>";
}
// Verificar constantes básicas
$constants = ['DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS'];
foreach ($constants as $const) {
if (defined($const)) {
$value = constant($const);
$display_value = ($const === 'DB_PASS') ? '***' : $value;
echo "<div class='ok'>✅ $const = '$display_value'</div>";
} else {
echo "<div class='error'>❌ Constante $const no definida</div>";
}
}
} catch (Exception $e) {
echo "<div class='error'>❌ Error cargando config.php: " . htmlspecialchars($e->getMessage()) . "</div>";
} catch (Error $e) {
echo "<div class='error'>❌ Error fatal en config.php: " . htmlspecialchars($e->getMessage()) . "</div>";
}
} else {
echo "<div class='error'>❌ config/config.php no encontrado</div>";
}
echo "</div>";
// 5. TEST BASE DE DATOS
echo "<div class='section'><h2>🗄️ 5. BASE DE DATOS</h2>";
if (defined('DB_HOST') && defined('DB_NAME')) {
try {
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4";
$pdo = new PDO($dsn, DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
echo "<div class='ok'>✅ Conexión a base de datos exitosa</div>";
// Verificar tablas principales
$tables = ['conversations', 'conversations', 'system_config'];
foreach ($tables as $table) {
$stmt = $pdo->query("SHOW TABLES LIKE '$table'");
if ($stmt->rowCount() > 0) {
echo "<div class='ok'>✅ Tabla '$table' existe</div>";
} else {
echo "<div class='warning'>⚠️ Tabla '$table' no existe</div>";
}
}
} catch (PDOException $e) {
echo "<div class='error'>❌ Error BD: " . htmlspecialchars($e->getMessage()) . "</div>";
}
} else {
echo "<div class='error'>❌ Configuración de BD incompleta</div>";
}
echo "</div>";
// 6. TEST .HTACCESS
echo "<div class='section'><h2>🛡️ 6. .HTACCESS</h2>";
if (file_exists('.htaccess')) {
$htaccess_size = filesize('.htaccess');
echo "<div class='ok'>✅ .htaccess existe ($htaccess_size bytes)</div>";
// Verificar sintaxis básica
$htaccess_content = file_get_contents('.htaccess');
$problematic_lines = [];
// Buscar sintaxis antigua que puede causar problemas
if (strpos($htaccess_content, 'Order Allow,Deny') !== false) {
$problematic_lines[] = "Sintaxis antigua 'Order Allow,Deny' (usar 'Require all denied')";
}
if (strpos($htaccess_content, 'Allow from') !== false) {
$problematic_lines[] = "Sintaxis antigua 'Allow from' (usar 'Require ip')";
}
if (empty($problematic_lines)) {
echo "<div class='ok'>✅ Sintaxis .htaccess parece correcta</div>";
} else {
foreach ($problematic_lines as $issue) {
echo "<div class='error'>❌ $issue</div>";
}
}
} else {
echo "<div class='error'>❌ .htaccess no encontrado</div>";
}
echo "</div>";
// 7. TEST MÓDULOS APACHE
echo "<div class='section'><h2>🔧 7. EXTENSIONES PHP</h2>";
$required_extensions = ['pdo', 'pdo_mysql', 'curl', 'json', 'mbstring', 'openssl'];
foreach ($required_extensions as $ext) {
if (extension_loaded($ext)) {
echo "<div class='ok'>✅ Extensión '$ext' habilitada</div>";
} else {
echo "<div class='error'>❌ Extensión '$ext' NO habilitada</div>";
}
}
// Verificar funciones deshabilitadas
$dangerous_functions = ['shell_exec', 'exec', 'system', 'passthru'];
$disabled_functions = explode(',', ini_get('disable_functions'));
$disabled_functions = array_map('trim', $disabled_functions);
echo "<div class='ok'><strong>Funciones del sistema:</strong></div>";
foreach ($dangerous_functions as $func) {
if (in_array($func, $disabled_functions)) {
echo "<div class='warning'>⚠️ $func() deshabilitada (normal en hosting)</div>";
} else {
echo "<div class='ok'>✅ $func() habilitada</div>";
}
}
echo "</div>";
// 8. INFORMACIÓN DEL SERVIDOR
echo "<div class='section'><h2>🖥️ 8. INFO SERVIDOR</h2>";
echo "<div class='ok'>Servidor: " . ($_SERVER['SERVER_SOFTWARE'] ?? 'Desconocido') . "</div>";
echo "<div class='ok'>PHP: " . PHP_VERSION . "</div>";
echo "<div class='ok'>SAPI: " . php_sapi_name() . "</div>";
echo "<div class='ok'>Directorio: " . __DIR__ . "</div>";
echo "<div class='ok'>Usuario: " . get_current_user() . "</div>";
// Límites PHP importantes
$limits = [
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'upload_max_filesize' => ini_get('upload_max_filesize'),
'post_max_size' => ini_get('post_max_size')
];
foreach ($limits as $setting => $value) {
echo "<div class='ok'>$setting: $value</div>";
}
echo "</div>";
// 9. SUGERENCIAS DE SOLUCIÓN
echo "<div class='section'><h2>🔧 9. ACCIONES RECOMENDADAS</h2>";
echo "<div class='warning'><strong>Si hay errores arriba, prueba estas soluciones:</strong></div>";
echo "<div class='warning'>1. <strong>Error sintaxis PHP:</strong> Corregir archivo mostrado</div>";
echo "<div class='warning'>2. <strong>Permisos:</strong> chmod 777 logs/ uploads/</div>";
echo "<div class='warning'>3. <strong>Base datos:</strong> Verificar credenciales en config.php</div>";
echo "<div class='warning'>4. <strong>.htaccess:</strong> Renombrar temporalmente a .htaccess.bak</div>";
echo "<div class='warning'>5. <strong>PHP version:</strong> Cambiar a PHP 8.1+ en panel</div>";
echo "<br><div class='ok'><strong>TESTS RÁPIDOS:</strong></div>";
echo "<div class='ok'>• Renombra .htaccess → .htaccess.bak y prueba</div>";
echo "<div class='ok'>• Ve a login.php directamente</div>";
echo "<div class='ok'>• Revisa error log del servidor</div>";
echo "</div>";
echo "<div class='section'><h2>📱 10. CONTACTO</h2>";
echo "<div class='ok'>Si necesitas ayuda, envía screenshot de este diagnóstico</div>";
echo "</div>";
echo "</body></html>";
?>
-237
View File
@@ -1,237 +0,0 @@
<?php
/**
* 🔧 DIAGNÓSTICO API - WhatsApp Bot
* Prueba específica de los archivos de la API
*/
// Headers para mostrar errores
error_reporting(E_ALL);
ini_set('display_errors', 1);
echo "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
echo "<title>🔧 Diagnóstico API</title>";
echo "<style>body{font-family:monospace;margin:20px;background:#000;color:#00ff00}";
echo ".ok{color:#00ff00}.error{color:#ff0040}.warning{color:#ffaa00}";
echo ".section{background:#111;padding:15px;margin:10px 0;border:1px solid #333}";
echo "pre{background:#222;padding:10px;color:#ccc;overflow:auto;max-height:200px}";
echo "</style></head><body>";
echo "<h1>🔧 DIAGNÓSTICO DE LA API</h1>";
echo "<div class='warning'>Ejecutándose desde: " . __DIR__ . "</div>";
// 1. Verificar estructura de carpetas
echo "<div class='section'>";
echo "<h2>📁 ESTRUCTURA DE ARCHIVOS...</h2>";
// Verificar que existe la carpeta api
if (!is_dir('api')) {
echo "<div class='error'>❌ CRÍTICO: Carpeta 'api' no encontrada</div>";
exit;
}
echo "<div class='ok'>✅ Carpeta 'api' encontrada</div>";
// Verificar config desde diferentes ubicaciones
$config_paths = [
'config/config.php' => 'Ruta desde raíz',
'api/../config/config.php' => 'Ruta desde API (relativa)',
'../config/config.php' => 'Ruta desde API (parent)'
];
foreach ($config_paths as $path => $desc) {
if (file_exists($path)) {
echo "<div class='ok'>✅ $desc: $path existe</div>";
} else {
echo "<div class='error'>❌ $desc: $path NO existe</div>";
}
}
echo "</div>";
// 2. Listar archivos de la API
echo "<div class='section'>";
echo "<h2>📋 ARCHIVOS DE LA API...</h2>";
$api_files = glob('api/*.php');
if (empty($api_files)) {
echo "<div class='error'>❌ No se encontraron archivos PHP en la API</div>";
exit;
}
echo "<div class='ok'>✅ Encontrados " . count($api_files) . " archivos en la API:</div>";
foreach ($api_files as $file) {
$filename = basename($file);
$size = filesize($file);
$readable = is_readable($file) ? '✅' : '❌';
echo "<div class='warning'>$readable $filename ($size bytes)</div>";
}
echo "</div>";
// 3. Analizar el primer archivo de API
echo "<div class='section'>";
echo "<h2>🔍 ANÁLISIS DEL PRIMER ARCHIVO...</h2>";
$test_file = $api_files[0];
$filename = basename($test_file);
echo "<div class='warning'>Analizando: $filename</div>";
$content = file_get_contents($test_file);
if (!$content) {
echo "<div class='error'>❌ No se pudo leer el archivo</div>";
} else {
// Buscar require_once
if (preg_match('/require_once\s+[\'"]([^\'"]+)[\'"]/', $content, $matches)) {
$required_path = $matches[1];
echo "<div class='warning'>🔗 Requiere: $required_path</div>";
// Verificar si la ruta existe desde la perspectiva del archivo API
$full_path_from_api = 'api/' . $required_path;
$parent_path_from_api = dirname('api/' . basename($test_file)) . '/' . $required_path;
if (file_exists($required_path)) {
echo "<div class='ok'>✅ Ruta directa '$required_path' existe</div>";
} elseif (file_exists($full_path_from_api)) {
echo "<div class='warning'>⚠️ Ruta '$required_path' existe como '$full_path_from_api'</div>";
} else {
echo "<div class='error'>❌ Ruta '$required_path' NO existe</div>";
echo "<div class='error'>❌ Tampoco existe como '$full_path_from_api'</div>";
}
} else {
echo "<div class='warning'>⚠️ No se encontró require_once en el archivo</div>";
}
}
echo "</div>";
// 4. Test de carga de configuración DESDE la API
echo "<div class='section'>";
echo "<h2>⚙️ TEST CARGA CONFIG DESDE API...</h2>";
// Simular estar en la carpeta API
$original_dir = getcwd();
try {
// Cambiar al directorio API
chdir('api');
echo "<div class='warning'>📁 Cambiado a directorio: " . getcwd() . "</div>";
// Intentar cargar configuración con diferentes rutas
$config_attempts = [
'../config/config.php' => 'Ruta parent (.../config/config.php)',
'config/config.php' => 'Ruta directa (config/config.php)',
'../config.php' => 'Config en raíz (../config.php)'
];
$config_loaded = false;
foreach ($config_attempts as $path => $desc) {
echo "<div class='warning'>🔄 Probando: $desc</div>";
if (file_exists($path)) {
echo "<div class='ok'>✅ Archivo existe: $path</div>";
try {
// Capturar errores
ob_start();
$error_before = error_get_last();
include_once $path;
$output = ob_get_clean();
$error_after = error_get_last();
if ($error_after && $error_after !== $error_before) {
echo "<div class='error'>❌ Error cargando: " . htmlspecialchars($error_after['message']) . "</div>";
} else {
echo "<div class='ok'>✅ Config cargado desde: $path</div>";
$config_loaded = true;
// Verificar algunas constantes
$test_constants = ['DB_HOST', 'DB_NAME', 'DB_USER'];
foreach ($test_constants as $const) {
if (defined($const)) {
echo "<div class='ok'>✅ $const definido</div>";
} else {
echo "<div class='error'>❌ $const no definido</div>";
}
}
break;
}
} catch (Exception $e) {
echo "<div class='error'>❌ Excepción: " . htmlspecialchars($e->getMessage()) . "</div>";
}
} else {
echo "<div class='error'>❌ Archivo no existe: $path</div>";
}
}
if (!$config_loaded) {
echo "<div class='error'>❌ CRÍTICO: No se pudo cargar ninguna configuración</div>";
}
} catch (Exception $e) {
echo "<div class='error'>❌ Error general: " . htmlspecialchars($e->getMessage()) . "</div>";
} finally {
// Volver al directorio original
chdir($original_dir);
echo "<div class='warning'>📁 Vuelto a directorio: " . getcwd() . "</div>";
}
echo "</div>";
// 5. Revisar el .htaccess
echo "<div class='section'>";
echo "<h2>🔧 VERIFICACIÓN .HTACCESS...</h2>";
if (file_exists('.htaccess')) {
echo "<div class='ok'>✅ .htaccess existe en raíz</div>";
$htaccess_content = file_get_contents('.htaccess');
// Verificar reglas que pueden afectar a la API
if (strpos($htaccess_content, 'RewriteEngine On') !== false) {
echo "<div class='ok'>✅ RewriteEngine habilitado</div>";
}
if (strpos($htaccess_content, 'api/') !== false) {
echo "<div class='warning'>⚠️ Hay reglas específicas para api/</div>";
preg_match_all('/.*api.*/', $htaccess_content, $api_rules);
foreach ($api_rules[0] as $rule) {
echo "<div class='warning'>📋 " . htmlspecialchars(trim($rule)) . "</div>";
}
}
} else {
echo "<div class='error'>❌ .htaccess no encontrado</div>";
}
// Verificar .htaccess en API
if (file_exists('api/.htaccess')) {
echo "<div class='warning'>⚠️ .htaccess existe en api/</div>";
} else {
echo "<div class='ok'>✅ No hay .htaccess específico en api/</div>";
}
echo "</div>";
// 6. Resumen y recomendaciones
echo "<div class='section'>";
echo "<h2>📊 DIAGNÓSTICO Y RECOMENDACIONES</h2>";
echo "<div class='warning'><strong>PROBLEMA IDENTIFICADO:</strong></div>";
echo "<div class='error'>Los archivos de la API están buscando 'config/config.php' desde su propia carpeta</div>";
echo "<div class='error'>Pero config.php está en '../config/config.php' desde la perspectiva de la API</div>";
echo "<br><div class='warning'><strong>SOLUCIONES:</strong></div>";
echo "<div class='ok'>1. Cambiar todas las rutas en api/ de 'config/config.php' a '../config/config.php'</div>";
echo "<div class='ok'>2. O crear un archivo api/config.php que incluya '../config/config.php'</div>";
echo "<div class='ok'>3. Verificar permisos de archivos</div>";
echo "<br><div class='warning'><strong>ARCHIVO CORRECTOR AUTOMÁTICO:</strong></div>";
echo "<div class='ok'>Necesitas ejecutar un script que corrija todas las rutas en los archivos API</div>";
echo "</div>";
echo "</body></html>";
?>
-105
View File
@@ -1,105 +0,0 @@
<?php
/**
* Diagnóstico del sistema de envío de mensajes
* Fecha: 4 de enero de 2026
*/
require_once 'config/config.php';
echo "<h2>🔍 Diagnóstico del Sistema de Envío de Mensajes</h2>";
echo "<h3>1. Configuración de WhatsApp</h3>";
echo "TOKEN: " . (WHATSAPP_TOKEN !== 'TU_TOKEN_DE_WHATSAPP_AQUI' ? '✅ Configurado' : '❌ No configurado') . "<br>";
echo "PHONE_NUMBER_ID: " . (WHATSAPP_PHONE_NUMBER_ID !== 'TU_PHONE_ID_AQUI' ? '✅ Configurado' : '❌ No configurado') . "<br>";
echo "API_URL: " . WHATSAPP_API_URL . "<br>";
echo "WEBHOOK_TOKEN: " . (WEBHOOK_VERIFY_TOKEN !== 'mi_token_secreto_123' ? '✅ Configurado' : '❌ Token por defecto') . "<br>";
echo "<h3>2. Conexión a Base de Datos</h3>";
try {
$db = Database::getInstance();
echo "✅ Conexión exitosa<br>";
// Verificar tablas
$tables = ['users', 'conversations', 'conversations'];
foreach ($tables as $table) {
try {
$stmt = $db->query("SHOW TABLES LIKE '$table'");
if ($stmt->rowCount() > 0) {
echo "✅ Tabla '$table' existe<br>";
// Contar registros
$stmt = $db->query("SELECT COUNT(*) as total FROM $table");
$count = $stmt->fetch()['total'];
echo "&nbsp;&nbsp;📊 Registros: $count<br>";
} else {
echo "❌ Tabla '$table' no existe<br>";
}
} catch (Exception $e) {
echo "❌ Error verificando tabla '$table': " . $e->getMessage() . "<br>";
}
}
} catch (Exception $e) {
echo "❌ Error de conexión: " . $e->getMessage() . "<br>";
}
echo "<h3>3. Clases del Sistema</h3>";
$classes = ['Database', 'WhatsAppService', 'BotService'];
foreach ($classes as $class) {
if (class_exists($class)) {
echo "✅ Clase '$class' cargada<br>";
} else {
echo "❌ Clase '$class' no encontrada<br>";
}
}
echo "<h3>4. Archivos del Sistema</h3>";
$files = [
'config/config.php',
'classes/Database.php',
'services/WhatsAppService.php',
'api/send_message.php',
'api/webhook.php'
];
foreach ($files as $file) {
if (file_exists($file)) {
echo "$file<br>";
} else {
echo "$file no encontrado<br>";
}
}
echo "<h3>5. Test de Envío (simulado)</h3>";
try {
if (class_exists('WhatsAppService')) {
$whatsapp = new WhatsAppService();
echo "✅ WhatsAppService creado<br>";
// Verificar que los tokens estén configurados
if (WHATSAPP_TOKEN !== 'TU_TOKEN_DE_WHATSAPP_AQUI' && WHATSAPP_PHONE_NUMBER_ID !== 'TU_PHONE_ID_AQUI') {
echo "⚠️ Tokens configurados - Ready para enviar mensajes<br>";
} else {
echo "❌ Tokens no configurados - No se pueden enviar mensajes<br>";
}
}
} catch (Exception $e) {
echo "❌ Error creando WhatsAppService: " . $e->getMessage() . "<br>";
}
echo "<h3>6. Logs del Sistema</h3>";
$logFile = 'logs/system.log';
if (file_exists($logFile)) {
echo "✅ Archivo de logs existe<br>";
$logs = file_get_contents($logFile);
$lines = array_slice(explode("\n", $logs), -10); // Últimas 10 líneas
echo "<pre style='background: #f5f5f5; padding: 10px; border-left: 3px solid #007cba;'>";
echo implode("\n", $lines);
echo "</pre>";
} else {
echo "⚠️ No hay logs disponibles<br>";
}
echo "<hr>";
echo "<p><strong>✅ Si todos los elementos muestran ✅, el sistema está listo.</strong></p>";
echo "<p><strong>❌ Si hay elementos con ❌, necesitan ser corregidos.</strong></p>";
?>
-333
View File
@@ -1,333 +0,0 @@
<?php
/**
* Herramienta de Diagnóstico de Login - WhatsApp Bot Manager
* Fecha: 4 de enero de 2026
*/
require_once 'config/config.php';
// Solo permitir acceso desde localhost por seguridad
$allowedIPs = ['127.0.0.1', '::1', 'localhost'];
$clientIP = $_SERVER['REMOTE_ADDR'] ?? $_SERVER['HTTP_X_FORWARDED_FOR'] ?? 'unknown';
if (!in_array($clientIP, $allowedIPs) && $clientIP !== 'unknown') {
die("❌ Acceso denegado. Esta utilidad solo puede ejecutarse desde localhost por seguridad.");
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🔧 Diagnóstico de Login - WhatsApp Bot Manager</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #25D366 0%, #128C7E 100%);
color: #333;
margin: 0;
padding: 20px;
min-height: 100vh;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 40px;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
h1 {
color: #25D366;
text-align: center;
margin-bottom: 30px;
}
.info-box {
background: #e8f5e8;
padding: 20px;
border-left: 5px solid #25D366;
margin: 20px 0;
border-radius: 5px;
}
.warning-box {
background: #fff3cd;
padding: 20px;
border-left: 5px solid #ffc107;
margin: 20px 0;
border-radius: 5px;
}
.error-box {
background: #f8d7da;
padding: 20px;
border-left: 5px solid #dc3545;
margin: 20px 0;
border-radius: 5px;
}
.form-group {
margin: 20px 0;
}
label {
display: block;
font-weight: bold;
margin-bottom: 8px;
color: #333;
}
input[type="password"], input[type="text"] {
width: 100%;
padding: 12px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 16px;
box-sizing: border-box;
}
input[type="password"]:focus, input[type="text"]:focus {
outline: none;
border-color: #25D366;
}
.btn {
background: #25D366;
color: white;
padding: 12px 30px;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
width: 100%;
margin: 10px 0;
}
.btn:hover {
background: #1aa347;
}
.btn-danger {
background: #dc3545;
}
.btn-danger:hover {
background: #c82333;
}
.current-info {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
margin: 20px 0;
}
code {
background: #f8f9fa;
padding: 2px 6px;
border-radius: 3px;
font-family: monospace;
}
.test-box {
background: #e7f3ff;
padding: 20px;
border: 2px solid #007bff;
border-radius: 8px;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="container">
<h1>🔧 Diagnóstico Completo de Login</h1>
<div class="warning-box">
<strong>⚠️ Herramienta de Emergencia:</strong>
<p>Esta utilidad diagnostica y soluciona problemas de login del sistema WhatsApp Bot Manager.</p>
</div>
<?php
// Información actual del sistema
echo '<div class="current-info">';
echo '<h3>📋 Estado Actual del Sistema:</h3>';
echo '<p><strong>Usuario administrador:</strong> <code>' . ADMIN_USERNAME . '</code></p>';
echo '<p><strong>Hash actual:</strong> <code>' . substr(ADMIN_PASSWORD, 0, 60) . '...</code></p>';
echo '<p><strong>Timeout de sesión:</strong> ' . (SESSION_TIMEOUT/60) . ' minutos</p>';
echo '<p><strong>Intentos máximos:</strong> ' . MAX_LOGIN_ATTEMPTS . ' intentos</p>';
echo '<p><strong>Tiempo de bloqueo:</strong> ' . (LOGIN_LOCKOUT_TIME/60) . ' minutos</p>';
echo '</div>';
// Verificar si hay bloqueos activos
$attemptsFile = '.login_attempts.json';
if (file_exists($attemptsFile)) {
$attempts = json_decode(file_get_contents($attemptsFile), true);
if (!empty($attempts)) {
echo '<div class="error-box">';
echo '<h4>🚫 Bloqueos Activos Detectados:</h4>';
foreach ($attempts as $ip => $data) {
echo '<p><strong>IP:</strong> ' . $ip . ' - <strong>Intentos:</strong> ' . $data['attempts'] . ' - <strong>Bloqueado hasta:</strong> ' . date('Y-m-d H:i:s', $data['locked_until']) . '</p>';
}
echo '</div>';
}
}
// Procesar acciones
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['action'])) {
// Verificar contraseña
if ($_POST['action'] === 'test_password') {
$testPassword = $_POST['test_password'] ?? '';
echo '<div class="test-box">';
echo '<h4>🔍 Resultado de la Prueba:</h4>';
echo '<p><strong>Contraseña probada:</strong> ' . htmlspecialchars($testPassword) . '</p>';
echo '<p><strong>Hash almacenado:</strong> ' . ADMIN_PASSWORD . '</p>';
if (password_verify($testPassword, ADMIN_PASSWORD)) {
echo '<div class="info-box">✅ <strong>CORRECTO:</strong> La contraseña funciona perfectamente.</div>';
echo '<p><strong>Diagnóstico:</strong> El problema no es la contraseña. Revisa:</p>';
echo '<ul>';
echo '<li>¿Estás usando el usuario correcto? (' . ADMIN_USERNAME . ')</li>';
echo '<li>¿Tu IP está bloqueada por intentos fallidos?</li>';
echo '<li>¿Hay espacios extra en la contraseña?</li>';
echo '</ul>';
} else {
echo '<div class="error-box">❌ <strong>INCORRECTO:</strong> La contraseña no coincide con el hash.</div>';
echo '<p><strong>Diagnóstico:</strong> El hash en la base de datos no corresponde a la contraseña ingresada.</p>';
}
echo '</div>';
}
// Resetear contraseña a "admin123"
if ($_POST['action'] === 'reset_admin123') {
$defaultPassword = 'admin123';
$newHash = password_hash($defaultPassword, PASSWORD_DEFAULT);
// Leer el archivo config.php
$configFile = 'config/config.php';
$configContent = file_get_contents($configFile);
// Reemplazar la línea del password
$pattern = "/define\('ADMIN_PASSWORD', '[^']*'\);/";
$replacement = "define('ADMIN_PASSWORD', '" . $newHash . "');";
$newConfigContent = preg_replace($pattern, $replacement, $configContent);
if (file_put_contents($configFile, $newConfigContent)) {
echo '<div class="info-box">✅ <strong>Contraseña reseteada exitosamente!</strong></div>';
echo '<div class="warning-box"><strong>🔑 Credenciales de acceso:</strong><br>';
echo '<strong>Usuario:</strong> admin<br>';
echo '<strong>Contraseña:</strong> admin123<br>';
echo '<strong>URL de login:</strong> <a href="login.php" target="_blank">login.php</a></div>';
// Log del cambio
$logEntry = "[" . date('Y-m-d H:i:s') . "] Password reset to default from IP: " . $clientIP . PHP_EOL;
file_put_contents('logs/system.log', $logEntry, FILE_APPEND | LOCK_EX);
} else {
echo '<div class="error-box">❌ Error al actualizar el archivo de configuración.</div>';
}
}
// Limpiar bloqueos
if ($_POST['action'] === 'clear_blocks') {
if (file_exists($attemptsFile)) {
unlink($attemptsFile);
echo '<div class="info-box">✅ Todos los bloqueos de IP han sido eliminados.</div>';
} else {
echo '<div class="warning-box">⚠️ No hay bloqueos activos para eliminar.</div>';
}
}
// Contraseña personalizada
if ($_POST['action'] === 'set_custom_password') {
$newPassword = $_POST['new_password'] ?? '';
$confirmPassword = $_POST['confirm_password'] ?? '';
if (empty($newPassword) || empty($confirmPassword)) {
echo '<div class="error-box">❌ Todos los campos son requeridos.</div>';
} elseif ($newPassword !== $confirmPassword) {
echo '<div class="error-box">❌ Las contraseñas no coinciden.</div>';
} elseif (strlen($newPassword) < 4) {
echo '<div class="error-box">❌ La contraseña debe tener al menos 4 caracteres.</div>';
} else {
$newHash = password_hash($newPassword, PASSWORD_DEFAULT);
// Leer y actualizar config.php
$configFile = 'config/config.php';
$configContent = file_get_contents($configFile);
$pattern = "/define\('ADMIN_PASSWORD', '[^']*'\);/";
$replacement = "define('ADMIN_PASSWORD', '" . $newHash . "');";
$newConfigContent = preg_replace($pattern, $replacement, $configContent);
if (file_put_contents($configFile, $newConfigContent)) {
echo '<div class="info-box">✅ <strong>Contraseña personalizada establecida!</strong></div>';
echo '<div class="warning-box"><strong>🔑 Nueva contraseña:</strong> ' . htmlspecialchars($newPassword) . '</div>';
} else {
echo '<div class="error-box">❌ Error al actualizar el archivo de configuración.</div>';
}
}
}
}
}
?>
<!-- Formulario para probar contraseña -->
<form method="post" style="margin-bottom: 30px;">
<h3>🔍 Probar Contraseña Actual</h3>
<div class="form-group">
<label for="test_password">Contraseña a probar:</label>
<input type="text" id="test_password" name="test_password" required>
<small>Escribe la contraseña que crees que debería funcionar</small>
</div>
<input type="hidden" name="action" value="test_password">
<button type="submit" class="btn">🔍 Probar Contraseña</button>
</form>
<hr>
<!-- Botón de reset rápido -->
<form method="post" style="margin-bottom: 30px;">
<h3>🚨 Reset de Emergencia</h3>
<p>Esto establecerá la contraseña por defecto: <strong>admin123</strong></p>
<input type="hidden" name="action" value="reset_admin123">
<button type="submit" class="btn btn-danger" onclick="return confirm('¿Estás seguro de resetear la contraseña a admin123?')">🔑 Resetear a admin123</button>
</form>
<hr>
<!-- Establecer contraseña personalizada -->
<form method="post" style="margin-bottom: 30px;">
<h3>🔧 Establecer Contraseña Personalizada</h3>
<div class="form-group">
<label for="new_password">Nueva contraseña:</label>
<input type="password" id="new_password" name="new_password" required>
</div>
<div class="form-group">
<label for="confirm_password">Confirmar contraseña:</label>
<input type="password" id="confirm_password" name="confirm_password" required>
</div>
<input type="hidden" name="action" value="set_custom_password">
<button type="submit" class="btn">🔧 Establecer Nueva Contraseña</button>
</form>
<hr>
<!-- Limpiar bloqueos -->
<form method="post">
<h3>🗑️ Limpiar Bloqueos de IP</h3>
<p>Elimina todos los bloqueos por intentos de login fallidos</p>
<input type="hidden" name="action" value="clear_blocks">
<button type="submit" class="btn btn-danger">🗑️ Limpiar Todos los Bloqueos</button>
</form>
<div class="info-box" style="margin-top: 30px;">
<h4>📋 Pasos Recomendados:</h4>
<ol>
<li><strong>Prueba</strong> la contraseña que crees que debería funcionar</li>
<li>Si no funciona, usa <strong>Reset de Emergencia</strong> (admin123)</li>
<li><strong>Limpia los bloqueos</strong> si tu IP está bloqueada</li>
<li><strong>Prueba el login</strong> en <a href="login.php" target="_blank">login.php</a></li>
<li><strong>Cambia</strong> la contraseña por una segura una vez dentro</li>
</ol>
</div>
<div class="warning-box">
<h4>🔒 Recordatorios de Seguridad:</h4>
<ul>
<li>Elimina este archivo después de usarlo</li>
<li>Usa siempre contraseñas seguras en producción</li>
<li>No dejes archivos de diagnóstico en servidores públicos</li>
</ul>
</div>
</div>
</body>
</html>
-301
View File
@@ -1,301 +0,0 @@
<?php
/**
* Herramienta de Diagnóstico de Login - Versión Mejorada
* WhatsApp Bot Manager
* Fecha: 4 de enero de 2026
*/
require_once 'config/config.php';
// Solo permitir acceso desde localhost por seguridad
$allowedIPs = ['127.0.0.1', '::1', 'localhost'];
$clientIP = $_SERVER['REMOTE_ADDR'] ?? $_SERVER['HTTP_X_FORWARDED_FOR'] ?? 'unknown';
if (!in_array($clientIP, $allowedIPs) && $clientIP !== 'unknown') {
die("❌ Acceso denegado. Esta utilidad solo puede ejecutarse desde localhost por seguridad.");
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🔍 Diagnóstico de Login Avanzado - WhatsApp Bot Manager</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #25D366 0%, #128C7E 100%);
color: #333;
margin: 0;
padding: 20px;
min-height: 100vh;
}
.container {
max-width: 1000px;
margin: 0 auto;
background: white;
padding: 40px;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
h1 {
color: #25D366;
text-align: center;
margin-bottom: 30px;
}
.info-box {
background: #e8f5e8;
padding: 20px;
border-left: 5px solid #25D366;
margin: 20px 0;
border-radius: 5px;
}
.warning-box {
background: #fff3cd;
padding: 20px;
border-left: 5px solid #ffc107;
margin: 20px 0;
border-radius: 5px;
}
.error-box {
background: #f8d7da;
padding: 20px;
border-left: 5px solid #dc3545;
margin: 20px 0;
border-radius: 5px;
}
.form-group {
margin: 20px 0;
}
label {
display: block;
font-weight: bold;
margin-bottom: 8px;
color: #333;
}
input[type="password"], input[type="text"] {
width: 100%;
padding: 12px;
border: 2px solid #ddd;
border-radius: 8px;
font-size: 16px;
box-sizing: border-box;
}
input:focus {
outline: none;
border-color: #25D366;
}
.btn {
background: #25D366;
color: white;
padding: 12px 30px;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
margin: 10px 5px;
}
.btn:hover {
background: #1aa347;
}
.diagnostic-result {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
font-family: monospace;
border: 1px solid #dee2e6;
}
.admin-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
.admin-table th, .admin-table td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}
.admin-table th {
background: #25D366;
color: white;
}
.admin-table tr:nth-child(even) {
background: #f8f9fa;
}
.status-active {
color: #28a745;
font-weight: bold;
}
.status-inactive {
color: #dc3545;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<h1>🔍 Diagnóstico de Login Avanzado</h1>
<?php
try {
$pdo = createDbConnection();
echo '<div class="info-box">';
echo '<h4>✅ Conexión a Base de Datos: EXITOSA</h4>';
echo '</div>';
// Verificar estado del sistema
$isMigrated = isAuthSystemMigrated();
echo '<div class="info-box">';
echo '<h4>📊 Estado del Sistema de Autenticación</h4>';
echo '<p><strong>Sistema migrado a BD:</strong> ' . ($isMigrated ? '✅ SÍ' : '❌ NO - Sistema Legacy') . '</p>';
if ($isMigrated) {
echo '<p><strong>Tabla admin_users:</strong> ✅ Existe</p>';
// Contar administradores
$stmt = $pdo->query("SELECT COUNT(*) as total, COUNT(CASE WHEN is_active = 1 THEN 1 END) as active FROM admin_users");
$counts = $stmt->fetch();
echo '<p><strong>Total administradores:</strong> ' . $counts['total'] . '</p>';
echo '<p><strong>Administradores activos:</strong> ' . $counts['active'] . '</p>';
} else {
echo '<p><strong>Usuario por defecto:</strong> admin (sistema hardcodeado)</p>';
}
echo '</div>';
// Mostrar lista de administradores si existe la tabla
if ($isMigrated) {
echo '<h3>👥 Lista de Administradores en Base de Datos</h3>';
$stmt = $pdo->query("SELECT * FROM admin_users ORDER BY created_at DESC");
$admins = $stmt->fetchAll();
if (empty($admins)) {
echo '<div class="warning-box">⚠️ No hay administradores en la base de datos</div>';
} else {
echo '<table class="admin-table">';
echo '<tr><th>ID</th><th>Usuario</th><th>Nombre</th><th>Email</th><th>Estado</th><th>Creado</th><th>Último Login</th></tr>';
foreach ($admins as $admin) {
echo '<tr>';
echo '<td>' . $admin['id'] . '</td>';
echo '<td><strong>' . htmlspecialchars($admin['username']) . '</strong></td>';
echo '<td>' . htmlspecialchars($admin['full_name'] ?? 'Sin nombre') . '</td>';
echo '<td>' . htmlspecialchars($admin['email'] ?? 'Sin email') . '</td>';
echo '<td><span class="status-' . ($admin['is_active'] ? 'active">✅ Activo' : 'inactive">❌ Inactivo') . '</span></td>';
echo '<td>' . ($admin['created_at'] ?? 'No disponible') . '</td>';
echo '<td>' . ($admin['last_login'] ?? 'Nunca') . '</td>';
echo '</tr>';
}
echo '</table>';
}
}
// Procesar test de login
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['test_user'])) {
$testUsername = trim($_POST['username']);
$testPassword = $_POST['password'];
echo '<div class="diagnostic-result">';
echo '<h4>🔍 Resultado del Diagnóstico de Login</h4>';
echo '<strong>Usuario probado:</strong> ' . htmlspecialchars($testUsername) . '<br>';
echo '<strong>Fecha/Hora:</strong> ' . date('Y-m-d H:i:s') . '<br><br>';
// Probar autenticación
$authResult = authenticateUser($testUsername, $testPassword);
if ($authResult) {
echo '<div style="color: green; font-weight: bold;">✅ LOGIN EXITOSO</div><br>';
echo '<strong>Datos del usuario autenticado:</strong><br>';
echo '- ID: ' . $authResult['id'] . '<br>';
echo '- Usuario: ' . htmlspecialchars($authResult['username']) . '<br>';
echo '- Nombre: ' . htmlspecialchars($authResult['full_name']) . '<br>';
echo '- Email: ' . htmlspecialchars($authResult['email']) . '<br>';
} else {
echo '<div style="color: red; font-weight: bold;">❌ LOGIN FALLIDO</div><br>';
// Diagnóstico detallado
if ($isMigrated) {
$stmt = $pdo->prepare("SELECT * FROM admin_users WHERE username = ?");
$stmt->execute([$testUsername]);
$user = $stmt->fetch();
if (!$user) {
echo '<strong>Razón:</strong> Usuario "' . htmlspecialchars($testUsername) . '" no existe en BD<br>';
} elseif (!$user['is_active']) {
echo '<strong>Razón:</strong> Usuario existe pero está INACTIVO<br>';
} else {
echo '<strong>Razón:</strong> Usuario existe y está activo, pero la contraseña es incorrecta<br>';
// Mostrar información del usuario encontrado
echo '<br><strong>Información del usuario en BD:</strong><br>';
echo '- ID: ' . $user['id'] . '<br>';
echo '- Usuario: ' . htmlspecialchars($user['username']) . '<br>';
echo '- Nombre: ' . htmlspecialchars($user['full_name'] ?? 'Sin nombre') . '<br>';
echo '- Activo: ' . ($user['is_active'] ? 'Sí' : 'No') . '<br>';
echo '- Hash de contraseña: ' . substr($user['password_hash'], 0, 20) . '...<br>';
}
} else {
if ($testUsername !== 'admin') {
echo '<strong>Razón:</strong> Sistema legacy - solo acepta usuario "admin"<br>';
} else {
echo '<strong>Razón:</strong> Usuario correcto pero contraseña incorrecta<br>';
echo '<strong>Sistema:</strong> Legacy (hardcodeado)<br>';
echo '<strong>Hash esperado:</strong> ' . substr(ADMIN_PASSWORD, 0, 20) . '...<br>';
}
}
}
echo '</div>';
}
} catch (Exception $e) {
echo '<div class="error-box">';
echo '<h4>❌ Error de Sistema</h4>';
echo '<p>Error: ' . htmlspecialchars($e->getMessage()) . '</p>';
echo '</div>';
}
?>
<h3>🧪 Probar Credenciales de Login</h3>
<form method="post">
<div class="form-group">
<label for="username">Usuario:</label>
<input type="text" id="username" name="username" required
value="<?php echo htmlspecialchars($_POST['username'] ?? ''); ?>"
placeholder="Ingresa el nombre de usuario">
</div>
<div class="form-group">
<label for="password">Contraseña:</label>
<input type="password" id="password" name="password" required
placeholder="Ingresa la contraseña">
</div>
<input type="hidden" name="test_user" value="1">
<button type="submit" class="btn">🔍 Probar Autenticación</button>
</form>
<div class="info-box" style="margin-top: 30px;">
<h4>🌐 Enlaces del Sistema:</h4>
<p><strong>Login oficial:</strong> <a href="login.php" target="_blank">login.php</a></p>
<p><strong>Panel principal:</strong> <a href="index.php" target="_blank">index.php</a></p>
<p><strong>Gestión de admins:</strong> <a href="gestion_admin_unificada.php" target="_blank">gestion_admin_unificada.php</a></p>
<p><strong>Migración a BD:</strong> <a href="migrate_to_database_auth.php" target="_blank">migrate_to_database_auth.php</a></p>
</div>
<div class="warning-box" style="margin-top: 30px;">
<h4>⚠️ Notas de Seguridad:</h4>
<ul>
<li>Esta herramienta solo funciona desde localhost por seguridad</li>
<li>Elimínala en producción</li>
<li>Los errores de autenticación se registran en logs del sistema</li>
<li>Si el sistema está migrado, solo usuarios en la tabla admin_users pueden loguearse</li>
</ul>
</div>
</div>
</body>
</html>
-264
View File
@@ -1,264 +0,0 @@
<?php
/**
* 🔐 DIAGNÓSTICO TOKEN WHATSAPP - WhatsApp Bot
* Verifica y valida el token de WhatsApp Business API
*/
// Headers para mostrar errores
error_reporting(E_ALL);
ini_set('display_errors', 1);
echo "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
echo "<title>🔐 Diagnóstico Token WhatsApp</title>";
echo "<style>body{font-family:monospace;margin:20px;background:#000;color:#00ff00}";
echo ".ok{color:#00ff00}.error{color:#ff0040}.warning{color:#ffaa00}";
echo ".section{background:#111;padding:15px;margin:10px 0;border:1px solid #333}";
echo "pre{background:#222;padding:10px;color:#ccc;overflow:auto;max-height:300px}";
echo ".token{background:#333;padding:10px;font-family:courier;word-break:break-all}";
echo "</style></head><body>";
echo "<h1>🔐 DIAGNÓSTICO TOKEN WHATSAPP</h1>";
// Cargar configuración
try {
require_once 'config/config.php';
} catch (Exception $e) {
echo "<div class='error'>❌ Error cargando configuración: " . htmlspecialchars($e->getMessage()) . "</div>";
exit;
}
// 1. Verificar constantes de WhatsApp
echo "<div class='section'>";
echo "<h2>📋 CONFIGURACIÓN ACTUAL...</h2>";
$whatsapp_constants = [
'WHATSAPP_TOKEN' => 'Token de acceso de WhatsApp',
'WHATSAPP_PHONE_NUMBER_ID' => 'ID del número de teléfono',
'WHATSAPP_API_URL' => 'URL base de la API',
'WEBHOOK_VERIFY_TOKEN' => 'Token de verificación del webhook'
];
foreach ($whatsapp_constants as $const => $desc) {
if (defined($const)) {
$value = constant($const);
if ($const === 'WHATSAPP_TOKEN') {
// Mostrar información detallada del token
echo "<div class='ok'>✅ $const definido ($desc)</div>";
echo "<div class='warning'>📝 Longitud: " . strlen($value) . " caracteres</div>";
if ($value === 'TU_TOKEN_DE_WHATSAPP_AQUI') {
echo "<div class='error'>❌ CRÍTICO: Token no configurado (valor por defecto)</div>";
} else {
// Verificar formato básico del token
if (preg_match('/^[A-Za-z0-9_-]+$/', $value)) {
echo "<div class='ok'>✅ Formato básico válido (solo caracteres permitidos)</div>";
} else {
echo "<div class='error'>❌ Formato inválido: contiene caracteres no permitidos</div>";
}
// Mostrar primeros y últimos caracteres
$preview = substr($value, 0, 10) . '...' . substr($value, -10);
echo "<div class='warning'>🔍 Vista previa: $preview</div>";
// Verificar longitud típica
if (strlen($value) < 50) {
echo "<div class='warning'>⚠️ Token parece muy corto (típico > 100 caracteres)</div>";
} elseif (strlen($value) > 500) {
echo "<div class='warning'>⚠️ Token parece muy largo (típico < 300 caracteres)</div>";
} else {
echo "<div class='ok'>✅ Longitud parece apropiada</div>";
}
}
} else {
if (in_array($const, ['WEBHOOK_VERIFY_TOKEN'])) {
$display_value = str_repeat('*', min(strlen($value), 20));
} else {
$display_value = $value;
}
echo "<div class='ok'>✅ $const = '$display_value' ($desc)</div>";
}
} else {
echo "<div class='error'>❌ $const no definida ($desc)</div>";
}
}
echo "</div>";
// 2. Test de formato del token
if (defined('WHATSAPP_TOKEN') && WHATSAPP_TOKEN !== 'TU_TOKEN_DE_WHATSAPP_AQUI') {
echo "<div class='section'>";
echo "<h2>🔍 ANÁLISIS DETALLADO DEL TOKEN...</h2>";
$token = WHATSAPP_TOKEN;
// Verificar si parece ser un token de Facebook/Meta
if (preg_match('/^[A-Z]{3,5}[A-Za-z0-9_-]{100,}$/', $token)) {
echo "<div class='ok'>✅ Parece un token válido de Facebook/Meta</div>";
} else {
echo "<div class='warning'>⚠️ No tiene el formato típico de token de Facebook/Meta</div>";
}
// Verificar estructura común
if (strpos($token, 'EAA') === 0) {
echo "<div class='ok'>✅ Comienza con 'EAA' (formato esperado)</div>";
} else {
echo "<div class='warning'>⚠️ No comienza con 'EAA' (formato típico de Facebook)</div>";
}
// Verificar caracteres
$allowed_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-';
$invalid_chars = [];
for ($i = 0; $i < strlen($token); $i++) {
$char = $token[$i];
if (strpos($allowed_chars, $char) === false) {
$invalid_chars[] = $char;
}
}
if (empty($invalid_chars)) {
echo "<div class='ok'>✅ Solo contiene caracteres permitidos</div>";
} else {
echo "<div class='error'>❌ Caracteres inválidos encontrados: " . implode(', ', array_unique($invalid_chars)) . "</div>";
}
echo "</div>";
// 3. Test de conexión a la API de WhatsApp
echo "<div class='section'>";
echo "<h2>🌐 TEST DE CONEXIÓN API WHATSAPP...</h2>";
if (defined('WHATSAPP_PHONE_NUMBER_ID') && WHATSAPP_PHONE_NUMBER_ID !== 'TU_PHONE_ID_AQUI') {
$phone_id = WHATSAPP_PHONE_NUMBER_ID;
$api_url = WHATSAPP_API_URL . $phone_id;
echo "<div class='warning'>🔗 Probando URL: $api_url</div>";
// Configurar cURL para test
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $api_url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json'
],
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_USERAGENT => 'WhatsApp Bot Validator/1.0'
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_error = curl_error($ch);
curl_close($ch);
if ($curl_error) {
echo "<div class='error'>❌ Error de conexión cURL: " . htmlspecialchars($curl_error) . "</div>";
} else {
echo "<div class='ok'>✅ Conexión establecida (HTTP $http_code)</div>";
if ($response) {
$json_response = json_decode($response, true);
if ($http_code === 200) {
echo "<div class='ok'>✅ Token válido - API responde correctamente</div>";
if ($json_response) {
echo "<div class='ok'>📋 Respuesta JSON válida</div>";
if (isset($json_response['id'])) {
echo "<div class='ok'>✅ Phone Number ID verificado: " . htmlspecialchars($json_response['id']) . "</div>";
}
}
} elseif ($http_code === 401) {
echo "<div class='error'>❌ ERROR 401: Token inválido o expirado</div>";
if ($json_response && isset($json_response['error'])) {
$error = $json_response['error'];
echo "<div class='error'>📝 Mensaje: " . htmlspecialchars($error['message'] ?? 'Sin mensaje') . "</div>";
echo "<div class='error'>📝 Tipo: " . htmlspecialchars($error['type'] ?? 'Sin tipo') . "</div>";
echo "<div class='error'>📝 Código: " . htmlspecialchars($error['code'] ?? 'Sin código') . "</div>";
}
} elseif ($http_code === 403) {
echo "<div class='error'>❌ ERROR 403: Token válido pero sin permisos</div>";
} else {
echo "<div class='warning'>⚠️ Respuesta inesperada (HTTP $http_code)</div>";
}
if ($json_response && isset($json_response['error'])) {
echo "<div class='warning'>📋 Respuesta completa:</div>";
echo "<pre>" . htmlspecialchars(json_encode($json_response, JSON_PRETTY_PRINT)) . "</pre>";
} elseif (strlen($response) < 500) {
echo "<div class='warning'>📋 Respuesta:</div>";
echo "<pre>" . htmlspecialchars($response) . "</pre>";
}
}
}
} else {
echo "<div class='error'>❌ PHONE_NUMBER_ID no configurado correctamente</div>";
}
echo "</div>";
}
// 4. Instrucciones para obtener token válido
echo "<div class='section'>";
echo "<h2>📖 CÓMO OBTENER UN TOKEN VÁLIDO</h2>";
echo "<div class='warning'><strong>PASOS PARA OBTENER TOKEN:</strong></div>";
echo "<div class='warning'>1. Ve a: <a href='https://developers.facebook.com/' style='color:#ffaa00'>Facebook Developers</a></div>";
echo "<div class='warning'>2. Crea una aplicación de WhatsApp Business</div>";
echo "<div class='warning'>3. Ve a WhatsApp > API Setup</div>";
echo "<div class='warning'>4. Copia el 'Temporary access token'</div>";
echo "<div class='warning'>5. Para producción, genera un token permanente</div>";
echo "<br><div class='warning'><strong>FORMATO CORRECTO:</strong></div>";
echo "<div class='ok'>✅ Debe comenzar con: EAA</div>";
echo "<div class='ok'>✅ Longitud típica: 100-300 caracteres</div>";
echo "<div class='ok'>✅ Solo letras, números, guiones y guiones bajos</div>";
if (defined('WHATSAPP_TOKEN') && WHATSAPP_TOKEN === 'TU_TOKEN_DE_WHATSAPP_AQUI') {
echo "<br><div class='error'><strong>⚠️ ACCIÓN REQUERIDA:</strong></div>";
echo "<div class='error'>Debes reemplazar 'TU_TOKEN_DE_WHATSAPP_AQUI' con tu token real</div>";
echo "<div class='warning'>Edita config/config.php línea ~45 o crea archivo .env</div>";
}
echo "</div>";
// 5. Verificación de Phone Number ID
echo "<div class='section'>";
echo "<h2>📱 VERIFICACIÓN PHONE NUMBER ID</h2>";
if (defined('WHATSAPP_PHONE_NUMBER_ID')) {
$phone_id = WHATSAPP_PHONE_NUMBER_ID;
if ($phone_id === 'TU_PHONE_ID_AQUI') {
echo "<div class='error'>❌ Phone Number ID no configurado</div>";
} else {
echo "<div class='ok'>✅ Phone Number ID: $phone_id</div>";
// Verificar formato (debe ser numérico)
if (preg_match('/^\d+$/', $phone_id)) {
echo "<div class='ok'>✅ Formato correcto (solo números)</div>";
} else {
echo "<div class='error'>❌ Formato incorrecto (debe ser solo números)</div>";
}
// Verificar longitud típica
if (strlen($phone_id) >= 10 && strlen($phone_id) <= 20) {
echo "<div class='ok'>✅ Longitud apropiada</div>";
} else {
echo "<div class='warning'>⚠️ Longitud inusual (" . strlen($phone_id) . " dígitos)</div>";
}
}
}
echo "</div>";
echo "</body></html>";
?>
-258
View File
@@ -1,258 +0,0 @@
<?php
/**
* Diagnóstico avanzado de WhatsApp API
* Fecha: 5 de enero de 2026
*/
require_once 'config/config.php';
header('Content-Type: text/html; charset=utf-8');
echo "<!DOCTYPE html>
<html>
<head>
<title>Diagnóstico WhatsApp API</title>
<style>
body { font-family: 'Segoe UI', sans-serif; margin: 20px; background: #f5f5f5; }
.container { max-width: 900px; margin: 0 auto; }
.card { background: white; padding: 20px; margin: 15px 0; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.success { color: #28a745; font-weight: bold; }
.error { color: #dc3545; font-weight: bold; }
.warning { color: #ffc107; font-weight: bold; }
.info { color: #17a2b8; font-weight: bold; }
pre { background: #f8f9fa; padding: 10px; border-radius: 4px; overflow-x: auto; }
.config-table { width: 100%; border-collapse: collapse; }
.config-table th, .config-table td { border: 1px solid #ddd; padding: 8px; text-align: left; }
.config-table th { background-color: #f2f2f2; }
</style>
</head>
<body>
<div class='container'>
<h1>🔍 Diagnóstico WhatsApp API</h1>";
// 1. Verificar configuraciones
echo "<div class='card'>
<h2>📋 Configuración Actual</h2>
<table class='config-table'>
<tr><th>Parámetro</th><th>Valor</th><th>Estado</th></tr>";
$config_items = [
'WHATSAPP_TOKEN' => WHATSAPP_TOKEN,
'WHATSAPP_PHONE_NUMBER_ID' => WHATSAPP_PHONE_NUMBER_ID,
'WHATSAPP_API_URL' => WHATSAPP_API_URL,
'WEBHOOK_VERIFY_TOKEN' => WEBHOOK_VERIFY_TOKEN
];
foreach ($config_items as $key => $value) {
$status = '';
if ($key === 'WHATSAPP_TOKEN') {
$display_value = substr($value, 0, 20) . '...';
$status = ($value !== 'TU_TOKEN_DE_WHATSAPP_AQUI' && !empty($value)) ?
"<span class='success'>✅ Configurado</span>" :
"<span class='error'>❌ No configurado</span>";
} elseif ($key === 'WHATSAPP_PHONE_NUMBER_ID') {
$display_value = $value;
$status = ($value !== 'TU_PHONE_ID_AQUI' && !empty($value)) ?
"<span class='success'>✅ Configurado</span>" :
"<span class='error'>❌ No configurado</span>";
} else {
$display_value = $value;
$status = !empty($value) ?
"<span class='success'>✅ Configurado</span>" :
"<span class='error'>❌ No configurado</span>";
}
echo "<tr><td><strong>$key</strong></td><td>$display_value</td><td>$status</td></tr>";
}
echo "</table></div>";
// 2. Verificar conexión a base de datos
echo "<div class='card'>
<h2>🗄️ Base de Datos</h2>";
try {
$db = Database::getInstance();
echo "<p class='success'>✅ Conexión a base de datos exitosa</p>";
// Verificar tabla system_config
try {
$configs = $db->fetchAll("SELECT config_key, config_value FROM system_config WHERE config_key IN ('whatsapp_token', 'whatsapp_phone_number_id', 'whatsapp_api_url')");
if (!empty($configs)) {
echo "<h3>Configuración en BD:</h3>";
echo "<table class='config-table'>";
echo "<tr><th>Clave</th><th>Valor</th></tr>";
foreach ($configs as $config) {
$display = ($config['config_key'] === 'whatsapp_token') ?
substr($config['config_value'], 0, 20) . '...' :
$config['config_value'];
echo "<tr><td>" . htmlspecialchars($config['config_key']) . "</td><td>" . htmlspecialchars($display) . "</td></tr>";
}
echo "</table>";
} else {
echo "<p class='warning'>⚠️ No hay configuración de WhatsApp en la base de datos</p>";
}
} catch (Exception $e) {
echo "<p class='warning'>⚠️ Tabla system_config no encontrada: " . $e->getMessage() . "</p>";
}
} catch (Exception $e) {
echo "<p class='error'>❌ Error de base de datos: " . $e->getMessage() . "</p>";
}
echo "</div>";
// 3. Probar conectividad con Facebook Graph API
echo "<div class='card'>
<h2>🌐 Conectividad Facebook Graph API</h2>";
if (WHATSAPP_TOKEN !== 'TU_TOKEN_DE_WHATSAPP_AQUI' && WHATSAPP_PHONE_NUMBER_ID !== 'TU_PHONE_ID_AQUI') {
// Prueba 1: Verificar el Phone Number ID
echo "<h3>📞 Verificación del Phone Number ID</h3>";
$ch = curl_init();
$url = WHATSAPP_API_URL . WHATSAPP_PHONE_NUMBER_ID;
$headers = [
'Authorization: Bearer ' . WHATSAPP_TOKEN,
'Content-Type: application/json'
];
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 15,
CURLOPT_SSL_VERIFYPEER => true
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
echo "<p><strong>URL:</strong> $url</p>";
echo "<p><strong>HTTP Code:</strong> $httpCode</p>";
if ($error) {
echo "<p class='error'>❌ Error cURL: $error</p>";
} else {
$decoded = json_decode($response, true);
if ($httpCode === 200) {
echo "<p class='success'>✅ Phone Number ID válido</p>";
echo "<h4>Información del número:</h4>";
echo "<pre>" . json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "</pre>";
} else {
echo "<p class='error'>❌ Phone Number ID inválido o sin permisos</p>";
echo "<h4>Respuesta de la API:</h4>";
echo "<pre>" . json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "</pre>";
if (isset($decoded['error'])) {
echo "<div class='error'>";
echo "<h4>Detalles del error:</h4>";
echo "<p><strong>Mensaje:</strong> " . htmlspecialchars($decoded['error']['message'] ?? 'N/A') . "</p>";
echo "<p><strong>Tipo:</strong> " . htmlspecialchars($decoded['error']['type'] ?? 'N/A') . "</p>";
echo "<p><strong>Código:</strong> " . htmlspecialchars($decoded['error']['code'] ?? 'N/A') . "</p>";
if (isset($decoded['error']['error_subcode'])) {
echo "<p><strong>Subcódigo:</strong> " . htmlspecialchars($decoded['error']['error_subcode']) . "</p>";
}
echo "</div>";
}
}
}
// Prueba 2: Verificar permisos del token
echo "<h3>🔑 Verificación de permisos del token</h3>";
$ch = curl_init();
$debugUrl = "https://graph.facebook.com/debug_token?input_token=" . WHATSAPP_TOKEN . "&access_token=" . WHATSAPP_TOKEN;
curl_setopt_array($ch, [
CURLOPT_URL => $debugUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_SSL_VERIFYPEER => true
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if (!$error && $httpCode === 200) {
$decoded = json_decode($response, true);
if (isset($decoded['data'])) {
echo "<h4>Información del token:</h4>";
echo "<pre>" . json_encode($decoded['data'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "</pre>";
}
} else {
echo "<p class='warning'>⚠️ No se pudo verificar el token: $error</p>";
}
} else {
echo "<p class='error'>❌ Token o Phone Number ID no configurados correctamente</p>";
}
echo "</div>";
// 4. Recomendaciones
echo "<div class='card'>
<h2>💡 Recomendaciones y Soluciones</h2>";
$recommendations = [];
if (WHATSAPP_TOKEN === 'TU_TOKEN_DE_WHATSAPP_AQUI') {
$recommendations[] = "❌ <strong>Configure el token de WhatsApp:</strong> Vaya a la configuración del sistema y ingrese su token de WhatsApp Business API.";
}
if (WHATSAPP_PHONE_NUMBER_ID === 'TU_PHONE_ID_AQUI') {
$recommendations[] = "❌ <strong>Configure el Phone Number ID:</strong> Vaya a la configuración del sistema y ingrese su Phone Number ID.";
}
if (WHATSAPP_PHONE_NUMBER_ID === '858157464051987') {
$recommendations[] = "⚠️ <strong>Phone Number ID problemático:</strong> El ID '858157464051987' está generando errores. Verifique que:
<ul>
<li>El número esté verificado en Facebook Business</li>
<li>Tenga los permisos correctos (whatsapp_business_messaging)</li>
<li>La aplicación esté en modo de producción</li>
<li>El token tenga acceso a este número</li>
</ul>";
}
$recommendations[] = "✅ <strong>Verificar en Meta for Developers:</strong>
<ul>
<li>Vaya a <a href='https://developers.facebook.com/apps' target='_blank'>Facebook for Developers</a></li>
<li>Seleccione su aplicación de WhatsApp Business</li>
<li>Vaya a Configuración > WhatsApp > Números de teléfono</li>
<li>Verifique que el número esté activo y verificado</li>
<li>Copie el Phone Number ID correcto</li>
</ul>";
$recommendations[] = "🔧 <strong>Pasos para resolver el error:</strong>
<ol>
<li>Verifique que el Phone Number ID sea correcto</li>
<li>Asegúrese de que el token tenga permisos para el número</li>
<li>Verifique que la aplicación esté aprobada para producción</li>
<li>Compruebe que el número esté verificado en Meta Business</li>
</ol>";
foreach ($recommendations as $rec) {
echo "<div style='margin: 10px 0; padding: 10px; border-left: 4px solid #007bff; background: #f8f9ff;'>$rec</div>";
}
echo "</div>";
echo "<div class='card'>
<h2>🛠️ Acciones Rápidas</h2>
<div style='display: flex; gap: 10px; flex-wrap: wrap;'>
<a href='index.php#system_config' style='padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 5px;'>⚙️ Ir a Configuración</a>
<a href='configurar_whatsapp.php' style='padding: 10px 20px; background: #28a745; color: white; text-decoration: none; border-radius: 5px;'>📱 Configurar WhatsApp</a>
<a href='test_whatsapp_completo.php' style='padding: 10px 20px; background: #17a2b8; color: white; text-decoration: none; border-radius: 5px;'>🧪 Probar API</a>
</div>
</div>";
echo "</div></body></html>";
?>
-194
View File
@@ -1,194 +0,0 @@
<?php
require_once 'config/config.php';
// Verificar autenticación
if (!isUserLoggedIn()) {
header('Location: login.php');
exit;
}
$currentUser = $_SESSION['admin_username'] ?? 'admin';
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WhatsApp Bot Manager - Simple</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="assets/css/styles.css" rel="stylesheet">
<style>
.sidebar { min-height: 100vh; background: #f8f9fa; }
.content-area { padding: 20px; }
.tab-content { display: none; }
.tab-content.active { display: block; }
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<!-- Sidebar -->
<div class="col-md-3 sidebar">
<div class="d-flex flex-column p-3">
<h4 class="mb-4">WhatsApp Bot</h4>
<ul class="nav nav-pills flex-column">
<li class="nav-item">
<a class="nav-link active" data-tab="dashboard" href="#">
📊 Dashboard
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-tab="conversations" href="#">
💬 Conversaciones
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-tab="users" href="#">
👥 Usuarios
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-tab="system_config" href="#">
⚙️ Configuraciones
</a>
</li>
</ul>
<hr>
<div class="mt-auto">
<small class="text-muted">Usuario: <?= htmlspecialchars($currentUser) ?></small><br>
<a href="logout.php" class="btn btn-outline-danger btn-sm mt-2">Cerrar Sesión</a>
</div>
</div>
</div>
<!-- Main Content -->
<div class="col-md-9 content-area">
<!-- Dashboard Tab -->
<div id="dashboard" class="tab-content active">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>📊 Dashboard</h2>
<small class="text-muted">Sistema funcionando correctamente</small>
</div>
<div class="row mb-4">
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<h3 id="total-users" class="text-primary">--</h3>
<p class="card-text">Configuraciones</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<h5 id="conversations-today" class="text-success">--</h5>
<p class="card-text">WhatsApp</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<h5 id="active-users" class="text-info">--</h5>
<p class="card-text">Base de Datos</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center">
<div class="card-body">
<h5 id="total-conversations" class="text-warning">--</h5>
<p class="card-text">Instalación</p>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h5>🔧 Acciones Rápidas</h5>
</div>
<div class="card-body">
<a href="debug_system.php" class="btn btn-primary">🐛 Sistema Debug</a>
<a href="config_status.php" class="btn btn-info">📊 Estado Config</a>
<a href="test_config_system.php" class="btn btn-success">🧪 Panel Pruebas</a>
</div>
</div>
</div>
<!-- Conversations Tab -->
<div id="conversations" class="tab-content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>💬 Conversaciones</h2>
<button class="btn btn-primary btn-sm" onclick="whatsappManager.loadConversations()">🔄 Actualizar</button>
</div>
<div id="conversations-list">
<div class="text-center text-muted">
<div class="spinner-border" role="status"></div>
<p>Cargando conversaciones...</p>
</div>
</div>
</div>
<!-- Users Tab -->
<div id="users" class="tab-content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>👥 Usuarios</h2>
<button class="btn btn-primary btn-sm" onclick="whatsappManager.loadUsers()">🔄 Actualizar</button>
</div>
<div id="users-list">
<div class="text-center text-muted">
<div class="spinner-border" role="status"></div>
<p>Cargando usuarios...</p>
</div>
</div>
</div>
<!-- system_config Tab -->
<div id="system_config" class="tab-content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>⚙️ Configuraciones</h2>
</div>
<div class="card">
<div class="card-header">
<h5>📱 Configuración WhatsApp</h5>
</div>
<div class="card-body">
<form id="system_config-form">
<div class="mb-3">
<label for="whatsapp_token" class="form-label">Token de WhatsApp</label>
<input type="text" class="form-control" id="whatsapp_token" name="whatsapp_token"
placeholder="Ingresa tu token de WhatsApp Business API">
</div>
<div class="mb-3">
<label for="whatsapp_phone_id" class="form-label">Phone Number ID</label>
<input type="text" class="form-control" id="whatsapp_phone_id" name="whatsapp_phone_id"
placeholder="Ingresa el Phone Number ID">
</div>
<div class="mb-3">
<label for="webhook_token" class="form-label">Token de Webhook</label>
<input type="text" class="form-control" id="webhook_token" name="webhook_token"
placeholder="Token para verificación del webhook">
</div>
<button type="submit" class="btn btn-primary">💾 Guardar Configuraciones</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/app_simple.js"></script>
</body>
</html>
-267
View File
@@ -1,267 +0,0 @@
<?php
/**
* Script de Instalación Automática
* Laboratorio Ximena Caicedo - Sistema de Chatbot WhatsApp
* Fecha: 13 de enero de 2026
*/
require_once __DIR__ . '/config/config.php';
// Configuración de tiempo de ejecución
set_time_limit(300); // 5 minutos
ini_set('display_errors', 1);
error_reporting(E_ALL);
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Instalación - Laboratorio Ximena Caicedo</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<style>
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 40px 0;
}
.install-container {
max-width: 800px;
margin: 0 auto;
}
.install-card {
background: white;
border-radius: 15px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
padding: 40px;
}
.step {
padding: 15px;
margin: 10px 0;
border-radius: 8px;
background: #f8f9fa;
}
.step.success {
background: #d4edda;
border-left: 4px solid #28a745;
}
.step.error {
background: #f8d7da;
border-left: 4px solid #dc3545;
}
.step.running {
background: #d1ecf1;
border-left: 4px solid #17a2b8;
}
.logo {
text-align: center;
margin-bottom: 30px;
}
.logo i {
font-size: 60px;
color: #667eea;
}
</style>
</head>
<body>
<div class="install-container">
<div class="install-card">
<div class="logo">
<i class="fas fa-flask"></i>
<h2>Laboratorio Ximena Caicedo</h2>
<p class="text-muted">Instalación del Sistema de Chatbot WhatsApp</p>
</div>
<div id="installation-steps">
<?php
$success = true;
$steps = [];
try {
// PASO 1: Verificar conexión a la base de datos
echo '<div class="step running">';
echo '<i class="fas fa-database"></i> Verificando conexión a la base de datos...';
flush();
$db = Database::getInstance();
echo ' <span class="text-success"><i class="fas fa-check-circle"></i> Conectado</span>';
echo '</div>';
$steps[] = ['name' => 'Conexión DB', 'status' => 'success'];
// PASO 2: Crear tabla de estados si no existe
echo '<div class="step running">';
echo '<i class="fas fa-table"></i> Creando tabla de estados de conversación...';
flush();
$sqlStates = file_get_contents(__DIR__ . '/migrations/add_conversation_states.sql');
$statementsStates = array_filter(array_map('trim', explode(';', $sqlStates)));
foreach ($statementsStates as $statement) {
if (!empty($statement) && substr($statement, 0, 6) !== 'SELECT') {
$db->getConnection()->exec($statement);
}
}
echo ' <span class="text-success"><i class="fas fa-check-circle"></i> Completado</span>';
echo '</div>';
$steps[] = ['name' => 'Tabla user_states', 'status' => 'success'];
// PASO 3: Importar menús y respuestas automáticas
echo '<div class="step running">';
echo '<i class="fas fa-list"></i> Importando menús y respuestas automáticas...';
flush();
$sqlSetup = file_get_contents(__DIR__ . '/setup_laboratorio_ximena.sql');
$statementsSetup = array_filter(array_map('trim', explode(';', $sqlSetup)));
$menuCount = 0;
$responseCount = 0;
foreach ($statementsSetup as $statement) {
if (!empty($statement) && substr($statement, 0, 2) !== '--') {
if (stripos($statement, 'INSERT INTO menus') !== false) {
$menuCount++;
} elseif (stripos($statement, 'INSERT INTO autoresponses') !== false) {
$responseCount++;
}
try {
$db->getConnection()->exec($statement);
} catch (Exception $e) {
// Ignorar errores de duplicados
if (strpos($e->getMessage(), 'Duplicate entry') === false) {
throw $e;
}
}
}
}
echo " <span class=\"text-success\"><i class=\"fas fa-check-circle\"></i> {$menuCount} menús, {$responseCount} respuestas</span>";
echo '</div>';
$steps[] = ['name' => 'Menús y respuestas', 'status' => 'success'];
// PASO 4: Verificar servicios
echo '<div class="step running">';
echo '<i class="fas fa-cogs"></i> Verificando servicios del sistema...';
flush();
$services = [
'BusinessHoursService',
'ConversationStateService',
'NLPService',
'MenuService',
'PatientDataValidator',
'BotServiceLab'
];
$servicesOk = true;
foreach ($services as $service) {
$file = __DIR__ . "/services/{$service}.php";
if (!file_exists($file)) {
echo " <span class=\"text-danger\"><i class=\"fas fa-times-circle\"></i> Falta {$service}.php</span>";
$servicesOk = false;
break;
}
}
if ($servicesOk) {
echo ' <span class="text-success"><i class="fas fa-check-circle"></i> ' . count($services) . ' servicios verificados</span>';
$steps[] = ['name' => 'Servicios', 'status' => 'success'];
} else {
$success = false;
}
echo '</div>';
// PASO 5: Obtener estadísticas
echo '<div class="step running">';
echo '<i class="fas fa-chart-bar"></i> Obteniendo estadísticas...';
flush();
$stmt = $db->query("
SELECT
(SELECT COUNT(*) FROM menus WHERE is_active = 1) as menus_activos,
(SELECT COUNT(*) FROM menu_options WHERE is_active = 1) as opciones_activas,
(SELECT COUNT(*) FROM autoresponses WHERE is_active = 1) as respuestas_activas,
(SELECT COUNT(*) FROM users) as total_usuarios
");
$stats = $stmt->fetch(PDO::FETCH_ASSOC);
echo ' <span class="text-success"><i class="fas fa-check-circle"></i> Completado</span>';
echo '</div>';
// Mostrar resumen
if ($success) {
echo '<div class="alert alert-success mt-4">';
echo '<h4><i class="fas fa-check-circle"></i> ¡Instalación Completada Exitosamente!</h4>';
echo '<hr>';
echo '<h5>📊 Estadísticas del Sistema:</h5>';
echo '<ul>';
echo "<li><strong>Menús activos:</strong> {$stats['menus_activos']}</li>";
echo "<li><strong>Opciones de menú:</strong> {$stats['opciones_activas']}</li>";
echo "<li><strong>Respuestas automáticas:</strong> {$stats['respuestas_activas']}</li>";
echo "<li><strong>Usuarios registrados:</strong> {$stats['total_usuarios']}</li>";
echo '</ul>';
echo '<hr>';
echo '<h5>🚀 Próximos Pasos:</h5>';
echo '<ol>';
echo '<li>Configura tus credenciales de WhatsApp Business API en el panel de administración</li>';
echo '<li>Prueba el menú principal enviando un mensaje de WhatsApp</li>';
echo '<li>Revisa las respuestas automáticas en la sección correspondiente</li>';
echo '<li>Personaliza los mensajes según las necesidades del laboratorio</li>';
echo '</ol>';
echo '<div class="text-center mt-4">';
echo '<a href="../index.php" class="btn btn-primary btn-lg">';
echo '<i class="fas fa-arrow-right"></i> Ir al Panel de Administración';
echo '</a>';
echo '</div>';
echo '</div>';
}
} catch (Exception $e) {
$success = false;
echo '<div class="step error">';
echo '<i class="fas fa-times-circle"></i> Error: ' . htmlspecialchars($e->getMessage());
echo '</div>';
echo '<div class="alert alert-danger mt-4">';
echo '<h4><i class="fas fa-exclamation-triangle"></i> Error en la Instalación</h4>';
echo '<p>Hubo un problema durante la instalación. Por favor:</p>';
echo '<ol>';
echo '<li>Verifica que la base de datos existe y tiene permisos correctos</li>';
echo '<li>Revisa la configuración en <code>config/config.php</code></li>';
echo '<li>Consulta el error arriba para más detalles</li>';
echo '<li>Intenta ejecutar la instalación nuevamente</li>';
echo '</ol>';
echo '<div class="text-center mt-3">';
echo '<button class="btn btn-warning" onclick="location.reload()">';
echo '<i class="fas fa-redo"></i> Reintentar Instalación';
echo '</button>';
echo '</div>';
echo '</div>';
}
?>
</div>
<?php if ($success): ?>
<div class="card mt-4 bg-light">
<div class="card-body">
<h5><i class="fas fa-book"></i> Documentación</h5>
<p>Para más información sobre cómo usar el sistema:</p>
<ul>
<li><a href="../REPORTE_SISTEMA_COMPLETO.md" target="_blank">Reporte del Sistema Completo</a></li>
<li><a href="../flujos_chatbot_whatsapp.md" target="_blank">Flujos del Chatbot</a></li>
<li><a href="../diagramas_flujo_chatbot.md" target="_blank">Diagramas de Flujo</a></li>
</ul>
</div>
</div>
<?php endif; ?>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
+16 -42
View File
@@ -91,8 +91,11 @@ if ($_POST && !$loginBlocked) {
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.75rem;
padding: 2rem 1rem;
}
.login-card {
background: rgba(255,255,255,0.95);
@@ -167,6 +170,15 @@ if ($_POST && !$loginBlocked) {
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
/* Pie de desarrollador discreto */
.developer-footer {
font-size: 0.85rem;
color: rgba(31,41,55,0.65);
text-align: center;
width: 100%;
max-width: 420px;
}
</style>
</head>
<body>
@@ -176,7 +188,6 @@ if ($_POST && !$loginBlocked) {
<i class="fab fa-whatsapp logo-icon"></i>
<h2 class="mt-3 fw-bold">WhatsApp Bot Manager</h2>
<p class="text-muted mb-0">Panel de Administración</p>
<small class="text-muted">Desarrollado por <strong>U-Site.app</strong></small>
</div>
<!-- Características de Seguridad -->
@@ -271,48 +282,11 @@ if ($_POST && !$loginBlocked) {
<?php endif; ?>
</button>
</form>
</div>
<!-- Information -->
<div class="mt-4">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
<strong>Información del Sistema:</strong>
<ul class="mb-0 mt-2">
<li>Usuario por defecto: <code>admin</code></li>
<li>Contraseña generada en la instalación</li>
<li>Máximo <?= MAX_LOGIN_ATTEMPTS ?> intentos por IP</li>
<li>Bloqueo de <?= LOGIN_LOCKOUT_TIME / 60 ?> minutos tras fallos</li>
</ul>
</div>
</div>
<!-- Links útiles -->
<div class="mt-4 text-center">
<div class="d-grid gap-2">
<a href="test.php" class="btn btn-outline-primary">
<i class="fas fa-check-circle me-2"></i>Probar Sistema
</a>
<?php if (file_exists('CREDENCIALES_SISTEMA.txt')): ?>
<a href="CREDENCIALES_SISTEMA.txt" class="btn btn-outline-warning btn-sm">
<i class="fas fa-file-text me-2"></i>Ver Credenciales
</a>
<?php endif; ?>
</div>
</div>
<!-- Developer Info -->
<div class="developer-info">
<h6><i class="fas fa-code me-2"></i>Desarrollado por U-Site.app</h6>
<p class="mb-2">Sistema profesional de WhatsApp Bot con máxima seguridad</p>
<div class="d-flex justify-content-center gap-3">
<a href="https://u-site.app" target="_blank" class="text-white">
<i class="fas fa-globe me-1"></i>Website
</a>
<a href="mailto:support@u-site.app" class="text-white">
<i class="fas fa-envelope me-1"></i>Soporte
</a>
</div>
</div>
<!-- Pequeño pie discreto del desarrollador -->
<div class="developer-footer mt-3">
<small style="color: rgba(246, 247, 248, 0.65); text-align: center;">Desarrollado con ❤️ por <a href="https://u-site.app" target="_blank" rel="noopener noreferrer" style="text-decoration: underline; color:white;">usite</a></small>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
+12
View File
@@ -51,3 +51,15 @@ Stack trace:
[2026-01-21 22:06:42] [INFO] Cleared advisor_requested for user 135 after outgoing message
[2026-01-21 22:06:43] [INFO] Operator 2 attending conversation for 573168950803
[2026-01-21 22:07:15] [INFO] Cleared advisor_requested for user 135 after outgoing message
[2026-01-21 22:56:59] [INFO] Survey response saved {"id":"1","phone":"573111111111","parsed":{"\u00c2\u00bfFue f\u00c3\u00a1cil interactuar con el chat autom\u00c3\u00a1tico?":"No","\u00c2\u00bfEl asesor humano resolvi\u00c3\u00b3 tu solicitud?":"No","Calificaci\u00c3\u00b3n":"4","Comentario":"bien"}}
[2026-01-21 22:57:43] [INFO] Survey response saved {"id":"2","phone":"573111111111","parsed":{"\u00c2\u00bfFue f\u00c3\u00a1cil interactuar con el chat autom\u00c3\u00a1tico?":"No","\u00c2\u00bfEl asesor humano resolvi\u00c3\u00b3 tu solicitud?":"No","Calificaci\u00c3\u00b3n":"4","Comentario":"bien"}}
[2026-01-21 22:58:27] [INFO] Survey response saved {"id":"3","phone":"573111111111","parsed":{"\u00c2\u00bfFue f\u00c3\u00a1cil interactuar con el chat autom\u00c3\u00a1tico?":"No","\u00c2\u00bfEl asesor humano resolvi\u00c3\u00b3 tu solicitud?":"No","Calificaci\u00c3\u00b3n":"4","Comentario":"bien"}}
[2026-01-21 22:58:27] [INFO] Survey response saved {"id":"4","phone":"573111111111","parsed":{"\u00bfFue f\u00e1cil interactuar con el chat autom\u00e1tico?":"S\u00ed","\u00bfEl asesor humano resolvi\u00f3 tu solicitud?":"No","Calificaci\u00f3n":"5","Comentario":"excelente servicio"}}
[2026-01-21 23:00:56] [INFO] Survey response saved {"id":"5","phone":"573111111111","parsed":{"\u00c2\u00bfFue f\u00c3\u00a1cil interactuar con el chat autom\u00c3\u00a1tico?":"No","\u00c2\u00bfEl asesor humano resolvi\u00c3\u00b3 tu solicitud?":"No","Calificaci\u00c3\u00b3n":"4","Comentario":"bien"}}
[2026-01-21 23:00:56] [INFO] Survey response saved {"id":"6","phone":"573111111111","parsed":{"\u00bfFue f\u00e1cil interactuar con el chat autom\u00e1tico?":"S\u00ed","\u00bfEl asesor humano resolvi\u00f3 tu solicitud?":"No","Calificaci\u00f3n":"5","Comentario":"excelente servicio"}}
[2026-01-21 23:11:49] [INFO] Survey response saved {"id":"7","phone":"573111111111","parsed":{"\u00c2\u00bfFue f\u00c3\u00a1cil interactuar con el chat autom\u00c3\u00a1tico?":"No","\u00c2\u00bfEl asesor humano resolvi\u00c3\u00b3 tu solicitud?":"No","Calificaci\u00c3\u00b3n":"4","Comentario":"bien"}}
[2026-01-21 23:11:49] [INFO] Survey response saved {"id":"8","phone":"573111111111","parsed":{"\u00bfFue f\u00e1cil interactuar con el chat autom\u00e1tico?":"S\u00ed","\u00bfEl asesor humano resolvi\u00f3 tu solicitud?":"No","Calificaci\u00f3n":"5","Comentario":"excelente servicio"}}
[2026-01-21 23:14:51] [INFO] Survey response saved {"id":"9","phone":"573111111111","parsed":{"\u00c2\u00bfFue f\u00c3\u00a1cil interactuar con el chat autom\u00c3\u00a1tico?":"No","\u00c2\u00bfEl asesor humano resolvi\u00c3\u00b3 tu solicitud?":"No","Calificaci\u00c3\u00b3n":"4","Comentario":"bien"}}
[2026-01-21 23:14:51] [INFO] Survey response saved {"id":"10","phone":"573111111111","parsed":{"\u00bfFue f\u00e1cil interactuar con el chat autom\u00e1tico?":"S\u00ed","\u00bfEl asesor humano resolvi\u00f3 tu solicitud?":"No","Calificaci\u00f3n":"5","Comentario":"excelente servicio"}}
[2026-01-21 23:49:55] [INFO] delete_template.php start {"raw_input":{"id":"1"},"query":{"debug":"true"}}
[2026-01-21 23:49:55] [INFO] delete_template.php parsed ids {"ids":[1]}
-10
View File
@@ -1,10 +0,0 @@
<?php
/**
* Ejecutor de Tests Completos - WhatsApp Bot System
* Acceso directo a la suite completa de tests
* Fecha: 3 de enero de 2026
*/
// Redirigir a la suite completa de tests
require_once 'tests/run_all_tests.php';
?>
-15
View File
@@ -1,15 +0,0 @@
<?php
ini_set('display_errors',1);
error_reporting(E_ALL);
// Test calling API endpoint internally by setting input stream
$payload = json_encode([
'to' => '573001234567',
'message_id' => '<TEST_MSGID>',
'emoji' => '👍',
'dry_run' => true
]);
file_put_contents('php://memory', $payload); // no effect; instead call service directly
require_once __DIR__ . '/../api/send_reaction.php';
-5
View File
@@ -1,5 +0,0 @@
<?php
ini_set('display_errors',1);
error_reporting(E_ALL);
require_once __DIR__ . '/../api/send_reply.php';
+27
View File
@@ -0,0 +1,27 @@
<?php
require_once __DIR__ . '/../config/config.php';
$db = Database::getInstance();
try {
$col = $db->fetch("SHOW TABLES LIKE 'survey_responses'");
if ($col) {
echo "Table survey_responses already exists\n";
exit;
}
$sql = "CREATE TABLE survey_responses (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NULL,
phone_number VARCHAR(30) NULL,
q_easy_interact TINYINT(1) NULL,
q_human_resolved TINYINT(1) NULL,
rating INT NULL,
comment TEXT NULL,
raw_text TEXT NULL,
created_at DATETIME NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
$db->query($sql);
echo "Created table survey_responses\n";
} catch (Exception $e) {
echo "Failed creating survey_responses: " . $e->getMessage() . "\n";
}
-13
View File
@@ -1,13 +0,0 @@
<?php
ini_set('display_errors',1);
error_reporting(E_ALL);
require_once __DIR__ . '/../config/config.php';
try {
$db = Database::getInstance();
echo "DB instance ok\n";
} catch (Throwable $t) {
echo "ERROR: " . $t->getMessage() . "\n";
echo $t->getTraceAsString() . "\n";
}
-8
View File
@@ -1,8 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
$db = Database::getInstance();
$now = date('Y-m-d H:i:s');
$id = $db->insert('operator_activity', ['user_id' => 1, 'operator_id' => 1, 'action' => 'test', 'details' => 'test insert', 'created_at' => $now]);
var_dump($id);
$rows = $db->fetchAll('SELECT * FROM operator_activity ORDER BY created_at DESC LIMIT 5');
var_dump($rows);
-48
View File
@@ -1,48 +0,0 @@
<?php
/**
* Script de prueba: enviar plantilla a un número y mostrar logs
* Usage: php send_template_test.php
*/
require_once __DIR__ . '/../config/config_enhanced.php';
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../services/WhatsAppService.php';
error_reporting(E_ALL);
ini_set('display_errors', 1);
$recipient = '573168950803';
$template = 'encuesta_satisfaccin';
$initialLang = 'es';
$candidates = [$initialLang, 'es_ES', 'es_MX', 'es_PE'];
echo "=== TEST: Enviar plantilla '$template' a $recipient ===\n";
try {
$wh = new WhatsAppService();
echo "WhatsAppService initialized successfully.\n";
} catch (Exception $e) {
echo "ERROR initializing WhatsAppService: " . $e->getMessage() . "\n";
echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
exit(1);
}
foreach ($candidates as $lang) {
echo "Intentando con language = $lang ...\n";
try {
$result = $wh->sendTemplateMessage($recipient, $template, $lang, []);
echo "Resultado OK (language=$lang):\n";
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
exit(0);
} catch (Exception $e) {
echo "FALLÓ (language=$lang): " . $e->getMessage() . "\n";
// Mostrar detalles si es posible
if (isset($e->getTrace()[0]['args'][0])) {
echo "Detalle trace arg0: " . var_export($e->getTrace()[0]['args'][0], true) . "\n";
}
// continuar con siguiente candidato
}
}
echo "Todos los intentos fallaron. Revisa WhatsApp Business Manager para esa plantilla y los idiomas disponibles.\n";
exit(1);
-23
View File
@@ -1,23 +0,0 @@
<?php
require_once __DIR__ . '/../config/config_enhanced.php';
$db = Database::getInstance();
// Get an existing user_id (user of conversations)
$user = $db->fetch('SELECT id, phone_number FROM users LIMIT 1');
$userId = $user['id'] ?? null;
$data = [
'user_id' => $userId,
'type' => 'message',
'message' => 'Prueba: nuevo mensaje recibido',
'data' => json_encode(['phone' => $user['phone_number'] ?? '']),
'is_read' => 0,
'created_at' => date('Y-m-d H:i:s')
];
$id = $db->insert('notifications', $data);
if ($id) {
echo "Inserted notification id=$id for user_id={$userId}\n";
} else {
echo "Failed to insert notification\n";
}
-49
View File
@@ -1,49 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../services/BotService.php';
$db = Database::getInstance();
$bot = new BotService();
// Crear usuario de prueba
$phone = '573010000123';
$db->execute('DELETE FROM users WHERE phone_number = :p', ['p' => $phone]);
$userId = $db->insert('users', ['phone_number' => $phone, 'status' => 'active', 'created_at' => date('Y-m-d H:i:s')]);
$user = $db->fetch('SELECT * FROM users WHERE id = :id', ['id' => $userId]);
// Simular comando asesor
$bot->processMessage($user, 'asesor', 'text');
// Intentar también llamar directamente putOnHold para comprobar
$bot->putOnHold($phone);
$user2 = $db->fetch('SELECT * FROM users WHERE id = :id', ['id' => $userId]);
echo "User after putOnHold: " . json_encode($user2) . "\n";
if ($user2['on_hold']) {
echo "ON_HOLD set correctly for $phone\n";
} else {
echo "ON_HOLD NOT SET!\n";
}
// Simular finalizar conversación via menu action (invoke processMenuAction indirectly)
// Crear menu option end test: insert temporary menu and option
$menuId = $db->insert('menus', ['name'=>'test_menu_end','title'=>'Test End','is_root'=>0,'is_active'=>1,'created_at'=>date('Y-m-d H:i:s')]);
$optId = $db->insert('menu_options', ['menu_id'=>$menuId,'option_number'=>9,'text'=>'Terminar','action_type'=>'end','action_value'=>'Gracias, cerrando.','is_active'=>1,'created_at'=>date('Y-m-d H:i:s')]);
// Liberar hold y luego establecer menu para probar pause
$bot->releaseHold($phone);
$db->update('users', ['current_menu_id'=>$menuId], 'id = :id', ['id' => $userId]);
// Simular selection '9' - recargar usuario para que tenga el current_menu_id
$user = $db->fetch('SELECT * FROM users WHERE id = :id', ['id' => $userId]);
$bot->processMessage($user, '9', 'text');
$user3 = $db->fetch('SELECT * FROM users WHERE id = :id', ['id' => $userId]);
if (!empty($user3['bot_paused_until'])) {
echo "Bot paused until: {$user3['bot_paused_until']}\n";
} else {
echo "Bot pause NOT SET!\n";
}
// Cleanup
$db->execute('DELETE FROM menu_options WHERE menu_id = :mid', ['mid' => $menuId]);
$db->execute('DELETE FROM menus WHERE id = :id', ['id' => $menuId]);
$db->execute('DELETE FROM users WHERE id = :id', ['id' => $userId]);
echo "Done\n";
-26
View File
@@ -1,26 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../services/BotService.php';
$db = Database::getInstance();
$phone = '573000000999';
// Cleanup
$db->execute('DELETE FROM users WHERE phone_number = :p', ['p' => $phone]);
$id = $db->insert('users', ['phone_number' => $phone, 'status' => 'active', 'created_at' => date('Y-m-d H:i:s')]);
$db->update('users', ['advisor_requested' => 1], 'id = :id', ['id' => $id]);
$bot = new BotService();
$bot->attendConversation($phone, 1);
$user = $db->fetch('SELECT * FROM users WHERE id = :id', ['id' => $id]);
$activity = $db->fetch('SELECT * FROM operator_activity WHERE user_id = :uid ORDER BY created_at DESC LIMIT 1', ['uid' => $id]);
echo "User in_service: " . ($user['in_service'] ?? 'NULL') . PHP_EOL;
echo "in_service_by: " . ($user['in_service_by'] ?? 'NULL') . PHP_EOL;
echo "in_service_at: " . ($user['in_service_at'] ?? 'NULL') . PHP_EOL;
if ($activity) {
echo "Activity: action={$activity['action']} operator_id={$activity['operator_id']} details={$activity['details']}\n";
} else {
echo "No operator activity found\n";
}
-31
View File
@@ -1,31 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../services/BotService.php';
$db = Database::getInstance();
$bot = new BotService();
$phone = '573012345678';
$db->execute('DELETE FROM users WHERE phone_number = :p', ['p' => $phone]);
$userId = $db->insert('users', ['phone_number' => $phone, 'status' => 'active', 'bot_enabled' => 0, 'welcome_sent_at' => date('Y-m-d H:i:s'), 'created_at' => date('Y-m-d H:i:s')]);
$user = $db->fetch('SELECT * FROM users WHERE id = :id', ['id' => $userId]);
// Clear conversations
$db->execute('DELETE FROM conversations WHERE user_id = :uid', ['uid' => $userId]);
// Send message while bot disabled
$bot->processMessage($user, 'hola', 'text');
$outs = $db->fetchAll("SELECT * FROM conversations WHERE user_id = :uid AND direction = 'outgoing'", ['uid' => $userId]);
echo "Outgoing after disabled: " . count($outs) . "\n";
// Enable bot
$db->update('users', ['bot_enabled' => 1], 'id = :id', ['id' => $userId]);
$user2 = $db->fetch('SELECT * FROM users WHERE id = :id', ['id' => $userId]);
$bot->processMessage($user2, 'hola', 'text');
$outs2 = $db->fetchAll("SELECT * FROM conversations WHERE user_id = :uid AND direction = 'outgoing'", ['uid' => $userId]);
echo "Outgoing after enabled: " . count($outs2) . "\n";
// Cleanup
$db->execute('DELETE FROM conversations WHERE user_id = :uid', ['uid' => $userId]);
$db->execute('DELETE FROM users WHERE id = :id', ['id' => $userId]);
echo "Done\n";
-46
View File
@@ -1,46 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
// Define a no-op requireAuthentication to bypass auth for test
if (!function_exists('requireAuthentication')) {
function requireAuthentication() {
return true;
}
}
session_start();
// Mock admin session so requireAuthentication passes
$_SESSION['admin_user'] = ['id' => 1, 'username' => 'admin'];
$db = Database::getInstance();
// Clean up any existing test template called 'test_delete_tpl'
try { $db->execute('DELETE FROM message_templates WHERE name = ?', ['test_delete_tpl']); } catch (Exception $e) { }
// Insert a test template
$id = $db->insert('message_templates', [
'name' => 'test_delete_tpl',
'template_name' => 'test_delete_tpl_name',
'language_code' => 'en_US',
'category' => 'utility',
'status' => 'approved',
'created_at' => date('Y-m-d H:i:s')
]);
echo "Inserted template id: $id\n";
// Simulate POST body
$_POST = ['id' => $id];
// Capture output
ob_start();
include __DIR__ . '/../api/delete_template.php';
$out = ob_get_clean();
echo "API Output: $out\n";
// Verify it was deleted
$check = $db->fetch('SELECT * FROM message_templates WHERE id = :id', ['id' => $id]);
if (!$check) {
echo "Template successfully deleted from DB.\n";
} else {
echo "Template still exists in DB: " . json_encode($check) . "\n";
}
-21
View File
@@ -1,21 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../services/BotService.php';
$db = Database::getInstance();
$phone = '573010000010';
// ensure user
$user = $db->fetch('SELECT * FROM users WHERE phone_number = ?', [$phone]);
if (!$user) {
$id = $db->insert('users', ['phone_number' => $phone, 'status' => 'active', 'created_at' => date('Y-m-d H:i:s')]);
$user = $db->fetch('SELECT * FROM users WHERE id = ?', [$id]);
}
$bot = new BotService();
echo "User id: {$user['id']} phone: {$phone}\n";
// Simulate sending 'INFORMACION ENVIADA' (uppercase + phrase)
echo "--- Sending 'INFORMACION ENVIADA' ---\n";
$bot->processMessage($user, 'INFORMACION ENVIADA', 'text');
// show last conversation entries
$rows = $db->fetchAll('SELECT * FROM conversations WHERE user_id = ? ORDER BY created_at DESC LIMIT 5', [$user['id']]);
print_r($rows);
-25
View File
@@ -1,25 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../services/BotService.php';
$db = Database::getInstance();
$phone = '573000000998';
// Cleanup
$db->execute('DELETE FROM users WHERE phone_number = :p', ['p' => $phone]);
$id = $db->insert('users', ['phone_number' => $phone, 'status' => 'active', 'created_at' => date('Y-m-d H:i:s')]);
$db->update('users', ['advisor_requested' => 1], 'id = :id', ['id' => $id]);
$bot = new BotService();
$bot->attendConversation($phone, 1);
$user = $db->fetch('SELECT * FROM users WHERE id = :id', ['id' => $id]);
echo "After attend: in_service={$user['in_service']} in_service_by={$user['in_service_by']}\n";
$bot->finishAttendConversation($phone, 1);
$user2 = $db->fetch('SELECT * FROM users WHERE id = :id', ['id' => $id]);
echo "After finish: in_service={$user2['in_service']} in_service_by={$user2['in_service_by']}\n";
$activity = $db->fetch('SELECT * FROM operator_activity WHERE user_id = :uid ORDER BY created_at DESC LIMIT 1', ['uid' => $id]);
if ($activity) {
echo "Activity: action={$activity['action']} details={$activity['details']}\n";
} else {
echo "No activity\n";
}
-13
View File
@@ -1,13 +0,0 @@
<?php
// Test get_conversation_detail.php in debug mode
$_GET['debug'] = 'true';
$_GET['user_id'] = 2;
chdir(__DIR__ . '/../api');
ob_start();
try {
include 'get_conversation_detail.php';
} catch (Throwable $t) {
echo 'Error: ' . $t->getMessage();
}
$out = ob_get_clean();
echo $out;
-11
View File
@@ -1,11 +0,0 @@
<?php
// Ejecutar api/get_conversations.php y mostrar salida
chdir(__DIR__ . '/../api');
ob_start();
try {
include 'get_conversations.php';
} catch (Throwable $t) {
echo 'Error: ' . $t->getMessage();
}
$out = ob_get_clean();
echo $out;
-13
View File
@@ -1,13 +0,0 @@
<?php
// Script de prueba para ejecutar api/get_templates.php en modo debug
$_GET['debug'] = 'true';
chdir(__DIR__ . '/../api');
ob_start();
try {
include 'get_templates.php';
} catch (Throwable $t) {
echo "Throwable: " . $t->getMessage() . "\n";
echo $t->getTraceAsString() . "\n";
}
$out = ob_get_clean();
echo $out;
-13
View File
@@ -1,13 +0,0 @@
<?php
// Test API get_user_conversations.php
$_GET['user_id'] = 2;
$_GET['debug'] = 'true';
chdir(__DIR__ . '/../api');
ob_start();
try {
include 'get_user_conversations.php';
} catch (Throwable $t) {
echo 'Error: ' . $t->getMessage();
}
$out = ob_get_clean();
echo $out;
-13
View File
@@ -1,13 +0,0 @@
<?php
// Script de prueba para ejecutar api/get_users.php en modo debug
$_GET['debug'] = 'true';
chdir(__DIR__ . '/../api');
ob_start();
try {
include 'get_users.php';
} catch (Throwable $t) {
echo "Throwable: " . $t->getMessage() . "\n";
echo $t->getTraceAsString() . "\n";
}
$out = ob_get_clean();
echo $out;
-21
View File
@@ -1,21 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../services/BotService.php';
$db = Database::getInstance();
$user = $db->fetch('SELECT id, phone_number FROM users LIMIT 1');
if (!$user) {
echo "No users found\n";
exit(1);
}
// Mark user as in_service
$db->update('users', ['in_service' => 1, 'in_service_by' => 1, 'in_service_at' => date('Y-m-d H:i:s')], 'id = :id', ['id' => $user['id']]);
// Fetch stale user array (simulate that caller has an old copy without in_service)
$staleUser = ['id' => $user['id'], 'phone_number' => $user['phone_number']];
$bot = new BotService();
$bot->processMessage($staleUser, 'hola', 'text');
echo "processMessage invoked (should have skipped bot processing if in_service)\n";
View File
+1
View File
@@ -0,0 +1 @@
OK
-19
View File
@@ -1,19 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
try {
$db = Database::getInstance();
$mid = 'wamid.TEST' . rand(1000,9999);
$db->insert('conversations', [
'user_id' => 2,
'message_id' => $mid,
'direction' => 'incoming',
'message_type' => 'document',
'content' => 'usuarios_blancos.csv',
'media_url' => 'https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1379066300634610&source=webhook&ext=1769010393&hash=EXAMPLE',
'status' => 'received'
]);
$row = $db->fetch('SELECT * FROM conversations WHERE message_id = ?', [$mid]);
echo json_encode($row, JSON_PRETTY_PRINT) . "\n";
} catch (Throwable $t) {
echo 'Error: ' . $t->getMessage() . "\n";
}
-42
View File
@@ -1,42 +0,0 @@
<?php
require_once __DIR__ . '/../api/webhook.php';
require_once __DIR__ . '/../config/config.php';
$w = new WhatsAppWebhook();
// Build a fake 'value' structure with interactive list_reply
$value = [
'messages' => [
[
'from' => '573010000002',
'id' => 'TEST_INT_1',
'type' => 'interactive',
'interactive' => [
'type' => 'list_reply',
'list_reply' => ['id' => 'option_2', 'title' => 'Consultar resultados']
],
'timestamp' => time()
]
]
];
// Set current menu for user (simulate that menu was shown previously)
$db = Database::getInstance();
$db->update('users', ['current_menu_id' => 1, 'current_step' => 1], 'phone_number = :phone', ['phone' => '573010000002']);
// Use reflection to call private method processconversations
$ref = new ReflectionClass($w);
$m = $ref->getMethod('processconversations');
$m->setAccessible(true);
$m->invoke($w, $value);
// Check DB for last conversations for that phone
$db = Database::getInstance();
$user = $db->fetch('SELECT * FROM users WHERE phone_number = ?', ['573010000002']);
if ($user) {
$rows = $db->fetchAll('SELECT * FROM conversations WHERE user_id = ? ORDER BY created_at DESC LIMIT 5', [$user['id']]);
print_r($user);
print_r($rows);
} else {
echo "User not found\n";
}
-12
View File
@@ -1,12 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../classes/MediaService.php';
$mediaId = $argv[1] ?? null;
if (!$mediaId) { echo "Usage: php test_media_store.php <mediaId>\n"; exit(1); }
$ms = new MediaService();
try {
$res = $ms->fetchAndStoreFromGraph($mediaId, date('Y/m'));
echo json_encode($res, JSON_PRETTY_PRINT) . "\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
-41
View File
@@ -1,41 +0,0 @@
<?php
// Usage: php scripts/test_media_url.php <mediaId>
$mediaId = $argv[1] ?? '1379066300634610';
// 1) JSON request (requires auth) - use local cookie session if possible
$opts = ['http' => ['method' => 'GET', 'header' => "Accept: application/json\r\n"]];
$ctx = stream_context_create($opts);
$url = "http://localhost/whatsapp/api/version/media-url.php?id=" . urlencode($mediaId);
echo "Requesting JSON -> $url\n";
$s = @file_get_contents($url, false, $ctx);
if ($s === false) {
echo "JSON request failed (probably 401 if not authenticated)\n";
} else {
echo $s . "\n";
}
// 2) Direct GET (simulate browser) should receive a Location header (we can't follow in this simple test)
$headers = get_headers($url, 1);
if ($headers) {
echo "\nResponse headers:\n";
print_r($headers);
} else {
echo "No headers received (server unreachable)\n";
}
// 3) Proxy download test
$downloadUrl = $url . '&download=1';
echo "\nDownload test -> $downloadUrl\n";
$opts2 = ['http' => ['method' => 'GET']];
$ctx2 = stream_context_create($opts2);
$stream = @fopen($downloadUrl, 'r', false, $ctx2);
if (!$stream) {
echo "Download request failed (check server or token)\n";
} else {
$meta = stream_get_meta_data($stream);
echo "Opened stream, meta keys: " . implode(',', array_keys($meta)) . "\n";
// Read a small part
$part = fread($stream, 200);
echo "First bytes:\n" . substr($part,0,200) . "\n";
fclose($stream);
}
-27
View File
@@ -1,27 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../services/BotService.php';
$db = Database::getInstance();
$phone = '573010000001';
// ensure user
$user = $db->fetch('SELECT * FROM users WHERE phone_number = ?', [$phone]);
if (!$user) {
$id = $db->insert('users', ['phone_number' => $phone, 'status' => 'active', 'created_at' => date('Y-m-d H:i:s')]);
$user = $db->fetch('SELECT * FROM users WHERE id = ?', [$id]);
}
$bot = new BotService();
echo "User id: {$user['id']} phone: {$phone}\n";
// Simulate sending 'menu'
echo "--- Sending 'menu' ---\n";
$bot->processMessage($user, 'menu', 'text');
$user = $db->fetch('SELECT * FROM users WHERE id = ?', [$user['id']]);
print_r(['after menu user' => $user]);
// Simulate user selecting option 2
echo "--- Sending '2' ---\n";
$bot->processMessage($user, '2', 'text');
// show last conversation entries
$rows = $db->fetchAll('SELECT * FROM conversations WHERE user_id = ? ORDER BY created_at DESC LIMIT 5', [$user['id']]);
print_r($rows);
-33
View File
@@ -1,33 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../services/BotService.php';
$db = Database::getInstance();
$phone = '573010000003';
// ensure user
$user = $db->fetch('SELECT * FROM users WHERE phone_number = ?', [$phone]);
if (!$user) {
$id = $db->insert('users', ['phone_number' => $phone, 'status' => 'active', 'created_at' => date('Y-m-d H:i:s')]);
$user = $db->fetch('SELECT * FROM users WHERE id = ?', [$id]);
}
$bot = new BotService();
echo "User id: {$user['id']} phone: {$phone}\n";
// Simulate incoming message 'Hola'
$db->insert('conversations', ['user_id' => $user['id'], 'message_id' => 'INC1', 'direction' => 'incoming', 'message_type' => 'text', 'content' => 'Hola', 'status' => 'received', 'created_at' => date('Y-m-d H:i:s')]);
// Simulate advisor outgoing reply AFTER the incoming message
$db->insert('conversations', ['user_id' => $user['id'], 'message_id' => 'OUT1', 'direction' => 'outgoing', 'message_type' => 'text', 'content' => 'Hola, soy el asesor', 'status' => 'sent', 'created_at' => date('Y-m-d H:i:s')]);
// Now user sends 'menu'
echo "--- Sending 'menu' ---\n";
$user = $db->fetch('SELECT * FROM users WHERE id = ?', [$user['id']]);
$bot->processMessage($user, 'menu', 'text');
$user = $db->fetch('SELECT * FROM users WHERE id = ?', [$user['id']]);
print_r(['after menu user' => $user]);
// Simulate user selecting option 2
echo "--- Sending '2' ---\n";
$bot->processMessage($user, '2', 'text');
$rows = $db->fetchAll('SELECT * FROM conversations WHERE user_id = ? ORDER BY created_at DESC LIMIT 5', [$user['id']]);
print_r($rows);
-35
View File
@@ -1,35 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../api/webhook.php';
// Simular payload de mensaje entrante para que webhook cree notificación
$payload = [
'object' => 'whatsapp_business_account',
'entry' => [
[
'changes' => [
[
'field' => 'conversations',
'value' => [
'conversations' => [
[
'from' => '573019999900',
'id' => 'NTFTEST1',
'timestamp' => time(),
'type' => 'text',
'text' => ['body' => 'Notificacion test']
]
]
]
]
]
]
]
];
$w = new WhatsAppWebhook();
$w->processPayload($payload);
$db = Database::getInstance();
$rows = $db->fetchAll('SELECT * FROM notifications ORDER BY id DESC LIMIT 5');
foreach ($rows as $r) echo json_encode($r) . "\n";
-22
View File
@@ -1,22 +0,0 @@
<?php
echo "TEST START\n";
ini_set('display_errors', 1);
error_reporting(E_ALL);
echo "Before config load\n";
require_once __DIR__ . '/../config/config_enhanced.php';
echo "After config load\n";
require_once __DIR__ . '/../classes/Database.php';
echo "After DB load\n";
require_once __DIR__ . '/../api/webhook.php';
echo "After webhook include\n";
$json = file_get_contents(__DIR__ . '/webhook_payload_ABGGFlA5Fpa.json');
$data = json_decode($json, true);
try {
echo "Calling processPayload...\n";
$w = new WhatsAppWebhook();
$ok = $w->processPayload($data);
echo 'processPayload returned: ' . ($ok ? 'true' : 'false') . PHP_EOL;
} catch (Exception $e) {
echo 'EXCEPTION: ' . $e->getMessage() . PHP_EOL;
}
-121
View File
@@ -1,121 +0,0 @@
<?php
ini_set('display_errors',1);
error_reporting(E_ALL);
require_once __DIR__ . '/../api/webhook.php';
$db = Database::getInstance();
$webhook = new WhatsAppWebhook();
// Test reaction
$testPhone = '573007777000';
$userId = $db->insert('users', ['phone_number' => $testPhone, 'name' => 'Reactor', 'status' => 'active']);
$origMsgId = '1001';
$db->insert('conversations', [
'user_id' => $userId,
'message_id' => $origMsgId,
'direction' => 'incoming',
'message_type' => 'text',
'content' => 'Original msg',
'status' => 'received',
'created_at' => date('Y-m-d H:i:s')
]);
$payload = [
'object' => 'whatsapp_business_account',
'entry' => [
[
'changes' => [
[
'field' => 'conversations',
'value' => [
'messaging_product' => 'whatsapp',
'metadata' => [
'display_phone_number' => '16505551111',
'phone_number_id' => '123'
],
'contacts' => [
[ 'profile' => ['name' => 'reactor'], 'wa_id' => $testPhone ]
],
'conversations' => [
[
'from' => $testPhone,
'id' => '2001',
'timestamp' => time(),
'type' => 'reaction',
'reaction' => ['message_id' => $origMsgId, 'emoji' => '👍']
]
]
]
]
]
]
]
];
$r = $webhook->processPayload($payload);
$conv = $db->fetch('SELECT * FROM conversations WHERE message_id = :mid', ['mid' => '2001']);
if ($conv) {
echo "Reaction saved: reaction_to_message_id={$conv['reaction_to_message_id']}, reaction_emoji={$conv['reaction_emoji']}\n";
} else {
echo "Reaction not saved\n";
}
// Cleanup reaction test
$db->execute('DELETE FROM conversations WHERE message_id = :mid', ['mid' => '2001']);
$db->execute('DELETE FROM conversations WHERE message_id = :mid', ['mid' => $origMsgId]);
$db->execute('DELETE FROM users WHERE id = :id', ['id' => $userId]);
// Test reply/context
$testPhone2 = '573007777111';
$userId2 = $db->insert('users', ['phone_number' => $testPhone2, 'name' => 'Replier', 'status' => 'active']);
$origMsgId2 = '1002';
$db->insert('conversations', [
'user_id' => $userId2,
'message_id' => $origMsgId2,
'direction' => 'incoming',
'message_type' => 'text',
'content' => 'Original for reply',
'status' => 'received',
'created_at' => date('Y-m-d H:i:s')
]);
$payload2 = [
'object' => 'whatsapp_business_account',
'entry' => [
[
'changes' => [
[
'field' => 'conversations',
'value' => [
'conversations' => [
[
'from' => $testPhone2,
'id' => '2002',
'timestamp' => time(),
'type' => 'text',
'text' => ['body' => 'Reply message'],
'context' => ['id' => $origMsgId2]
]
]
]
]
]
]
]
];
$r2 = $webhook->processPayload($payload2);
$conv2 = $db->fetch('SELECT * FROM conversations WHERE message_id = :mid', ['mid' => '2002']);
if ($conv2) {
echo "Reply saved: reply_to_message_id={$conv2['reply_to_message_id']}\n";
} else {
echo "Reply not saved\n";
}
// Cleanup reply test
$db->execute('DELETE FROM conversations WHERE message_id = :mid', ['mid' => '2002']);
$db->execute('DELETE FROM conversations WHERE message_id = :mid', ['mid' => $origMsgId2]);
$db->execute('DELETE FROM users WHERE id = :id', ['id' => $userId2]);
echo "Done\n";
-20
View File
@@ -1,20 +0,0 @@
<?php
require_once __DIR__ . '/../config/config_enhanced.php';
require_once __DIR__ . '/../classes/Database.php';
require_once __DIR__ . '/../api/webhook.php';
try {
echo "Starting...\n";
$db = Database::getInstance();
echo "Got DB\n";
$row = $db->fetch('SELECT id, request_body FROM webhook_logs ORDER BY created_at DESC LIMIT 1');
echo "Latest log id: " . ($row['id'] ?? 'none') . PHP_EOL;
$payload = json_decode($row['request_body'], true);
echo "Decoded payload keys: " . implode(',', array_keys($payload)) . PHP_EOL;
$w = new WhatsAppWebhook();
echo "Webhook instance created\n";
$res = $w->processPayload($payload);
echo 'ProcessPayload => ' . ($res ? 'OK' : 'FAILED') . PHP_EOL;
} catch (Exception $e) {
echo 'ERROR: ' . $e->getMessage() . PHP_EOL;
}
-12
View File
@@ -1,12 +0,0 @@
<?php
ini_set('display_errors',1);
error_reporting(E_ALL);
try {
require_once __DIR__ . '/../config/config_enhanced.php';
echo "config_enhanced OK\n";
require_once __DIR__ . '/../config/config.php';
echo "config.php OK\n";
} catch (Throwable $t) {
echo "Throwable on require: " . $t->getMessage() . "\n";
echo $t->getTraceAsString() . "\n";
}
-12
View File
@@ -1,12 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
$wh = new WhatsAppService();
$to = '573022548060';
$message = 'Prueba de envío (desarrollo) ' . date('Y-m-d H:i:s');
try {
$res = $wh->sendTextMessage($to, $message);
echo "Response:\n";
echo json_encode($res, JSON_PRETTY_PRINT) . "\n";
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
-35
View File
@@ -1,35 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../services/BotService.php';
$db = Database::getInstance();
$bot = new BotService();
$phone = '573011111222';
$db->execute('DELETE FROM users WHERE phone_number = :p', ['p' => $phone]);
$id = $db->insert('users', ['phone_number' => $phone, 'status' => 'active', 'created_at' => date('Y-m-d H:i:s')]);
// Ensure welcome_message config exists
$message = getConfigFromDB('welcome_message', null);
if (!$message) {
$m = 'Bienvenido a nuestro servicio (mensaje por defecto)';
// Insert config if missing
try {
$db->insert('system_config', ['config_key' => 'welcome_message', 'config_value' => $m, 'created_at' => date('Y-m-d H:i:s')]);
echo "Inserted welcome_message config\n";
} catch (Exception $e) {
// ignore
}
}
$bot->sendWelcomeMessage($phone);
$user = $db->fetch('SELECT * FROM users WHERE id = :id', ['id' => $id]);
if (!empty($user['welcome_sent_at'])) {
echo "welcome_sent_at recorded: {$user['welcome_sent_at']}\n";
} else {
echo "welcome_sent_at NOT set\n";
}
// cleanup
$db->execute('DELETE FROM users WHERE id = :id', ['id' => $id]);
echo "Done\n";
-11
View File
@@ -1,11 +0,0 @@
<?php
require_once __DIR__ . '/../config/config.php';
try {
$db = Database::getInstance();
$ids = [3];
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$existing = $db->fetchAll("SELECT id, name, template_name FROM message_templates WHERE id IN ($placeholders)", $ids);
var_export($existing);
} catch (Exception $e) {
echo "Exception: " . $e->getMessage() . PHP_EOL;
}
-21
View File
@@ -1,21 +0,0 @@
<?php
ini_set('display_errors',1);
error_reporting(E_ALL);
require_once __DIR__ . '/../services/WhatsAppService.php';
try {
$s = new WhatsAppService();
echo "WhatsAppService loaded\n";
echo "Dry-run reaction:\n";
$r = $s->sendReaction('573001234567', '<MSGID123>', '❤️', true);
print_r($r);
echo "Dry-run text reply:\n";
$rr = $s->sendTextReply('573001234567', '<MSGID123>', 'Gracias por tu mensaje', false, true);
print_r($rr);
} catch (Throwable $t) {
echo "ERROR: " . $t->getMessage() . "\n";
}
-16
View File
@@ -1,16 +0,0 @@
<?php
ini_set('display_errors',1);
error_reporting(E_ALL);
echo "START wa_service_test\n";
require_once __DIR__ . '/../services/WhatsAppService.php';
echo "AFTER REQUIRE\n";
try {
$s = new WhatsAppService();
echo "WhatsAppService OK\n";
} catch (Throwable $t) {
echo "ERROR: " . $t->getMessage() . "\n";
echo $t->getTraceAsString() . "\n";
}
echo "END wa_service_test\n";
-102
View File
@@ -1,102 +0,0 @@
<?php
ini_set('display_errors',1);
error_reporting(E_ALL);
require_once __DIR__ . '/../api/webhook.php';
$payload = [
"object" => "whatsapp_business_account",
"entry" => [
[
"id" => "0",
"changes" => [
[
"field" => "conversations",
"value" => [
"messaging_product" => "whatsapp",
"metadata" => [
"display_phone_number" => "16505551111",
"phone_number_id" => "123456123"
],
"contacts" => [
[
"profile" => ["name" => "test user name"],
"wa_id" => "16315551181"
]
],
"conversations" => [
[
"from" => "16315551181",
"id" => "ABGGFlA5Fpa",
"timestamp" => "1504902988",
"type" => "text",
"text" => ["body" => "this is a text message"]
]
]
]
]
]
]
]
];
try {
$w = new WhatsAppWebhook();
echo "Webhook instance created\n";
$result = $w->processPayload($payload);
echo "processPayload result: " . ($result ? 'true' : 'false') . "\n";
// Simular reacción entrante
$reactionPayload = [
"object" => "whatsapp_business_account",
"entry" => [
[
"id" => "0",
"changes" => [
[
"field" => "conversations",
"value" => [
"messaging_product" => "whatsapp",
"metadata" => [
"display_phone_number" => "16505551111",
"phone_number_id" => "123456123"
],
"contacts" => [
[
"profile" => ["name" => "reactor"],
"wa_id" => "16315551181"
]
],
"conversations" => [
[
"from" => "16315551181",
"id" => "REACTION1",
"timestamp" => "1504902990",
"type" => "reaction",
"reaction" => ["message_id" => "ABGGFlA5Fpa", "emoji" => "❤️"]
]
]
]
]
]
]
]
];
$r2 = $w->processPayload($reactionPayload);
echo "processReaction result: " . ($r2 ? 'true' : 'false') . "\n";
} catch (Throwable $t) {
echo "Fatal: " . $t->getMessage() . "\n";
echo $t->getTraceAsString() . "\n";
}
// Show last lines of system log
$log = __DIR__ . '/../logs/system.log';
if (file_exists($log)) {
echo "-- Last log lines --\n";
$lines = array_slice(file($log), -40);
foreach ($lines as $line) echo $line;
} else {
echo "No log file found\n";
}
-13
View File
@@ -1,13 +0,0 @@
<?php
ini_set('display_errors',1);
error_reporting(E_ALL);
require_once __DIR__ . '/../api/webhook.php';
try {
$w = new WhatsAppWebhook();
echo "Webhook instance OK\n";
} catch (Throwable $t) {
echo "ERROR: " . $t->getMessage() . "\n";
echo $t->getTraceAsString() . "\n";
}
-53
View File
@@ -1,53 +0,0 @@
<?php
/**
* Script simple de diagnóstico de login
*/
echo "🔍 DIAGNÓSTICO SIMPLE DEL LOGIN\n";
echo "===============================\n\n";
// Verificar contraseña directamente
$currentPassword = '2fa904eee46f';
echo "Contraseña a verificar: " . $currentPassword . "\n";
// Leer hash del config.php manualmente
$configContent = file_get_contents('config/config.php');
if (preg_match("/define\('ADMIN_PASSWORD',\s*'([^']*)'.*\);/", $configContent, $matches)) {
$storedHash = $matches[1];
echo "Hash encontrado en config: " . substr($storedHash, 0, 20) . "...\n";
echo "Longitud del hash: " . strlen($storedHash) . " caracteres\n";
if (password_verify($currentPassword, $storedHash)) {
echo "✅ LA CONTRASEÑA SÍ FUNCIONA\n";
echo "\nEl problema puede ser:\n";
echo "1. JavaScript deshabilitado\n";
echo "2. Campos del formulario mal nombrados\n";
echo "3. Sesiones no iniciadas\n";
echo "4. Headers ya enviados\n";
} else {
echo "❌ LA CONTRASEÑA NO FUNCIONA\n";
echo "🔧 Generando nuevo hash correcto...\n";
$correctHash = password_hash($currentPassword, PASSWORD_DEFAULT);
echo "Nuevo hash: " . $correctHash . "\n";
// Reemplazar en config.php
$newConfig = preg_replace(
"/define\('ADMIN_PASSWORD',\s*'[^']*'\);/",
"define('ADMIN_PASSWORD', '" . $correctHash . "');",
$configContent
);
if (file_put_contents('config/config.php', $newConfig)) {
echo "✅ Config.php actualizado\n";
}
}
} else {
echo "❌ No se encontró ADMIN_PASSWORD en config.php\n";
}
echo "\n📋 CREDENCIALES:\n";
echo "Usuario: admin\n";
echo "Contraseña: 2fa904eee46f\n";
echo "\n🌐 URL: http://localhost:8080/login.php\n";
?>
-57
View File
@@ -1,57 +0,0 @@
<?php
/**
* Script de debug para probar API de multimedia
*/
// URL del API
$apiUrl = 'http://localhost/api/send_media_message.php';
// Datos de prueba
$data = [
'recipient' => '573168950803',
'media_url' => 'http://localhost/uploads/test.jpg',
'media_type' => 'image',
'caption' => 'Test de imagen',
'filename' => 'test.jpg'
];
echo "=== TEST API MULTIMEDIA ===\n\n";
echo "URL: $apiUrl\n";
echo "Datos enviados:\n";
print_r($data);
echo "\n";
// Hacer petición
$ch = curl_init($apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);
echo "=== RESPUESTA ===\n";
echo "HTTP Code: $httpCode\n";
echo "Content-Type: $contentType\n";
echo "\nRespuesta RAW:\n";
echo $response;
echo "\n\n";
// Intentar decodificar JSON
echo "=== DECODIFICACIÓN JSON ===\n";
$decoded = json_decode($response, true);
if ($decoded === null) {
echo "ERROR: No se pudo decodificar JSON\n";
echo "JSON Error: " . json_last_error_msg() . "\n";
echo "\nPrimeros 500 caracteres de la respuesta:\n";
echo substr($response, 0, 500) . "\n";
} else {
echo "JSON decodificado correctamente:\n";
print_r($decoded);
}
?>
Binary file not shown.
Binary file not shown.
Binary file not shown.
-491
View File
@@ -1,491 +0,0 @@
<?php
/**
* Tests específicos para APIs del sistema WhatsApp Bot
* Fecha: 3 de enero de 2026
*/
class APITestSuite {
private $testResults = [];
public function runAllAPITests() {
echo "<h2>🔥 Tests Exhaustivos de APIs</h2>";
// Configurar sesión de test
$this->setupTestSession();
// Tests de mensajería
$this->testSendMessageAPI();
$this->testBroadcastAPI();
// Tests de plantillas
$this->testTemplateAPIs();
// Tests de usuarios
$this->testUserAPIs();
// Tests de configuración
$this->testConfigurationAPIs();
// Tests de analytics
$this->testAnalyticsAPIs();
$this->showAPITestSummary();
}
private function setupTestSession() {
$_SESSION['admin_logged_in'] = true;
$_SESSION['last_activity'] = time();
$_SESSION['login_ip'] = '127.0.0.1';
}
private function testSendMessageAPI() {
echo "<h4>📨 Tests de API send_message.php</h4>";
// Test 1: Verificar que el archivo existe
$this->apiTest("Verificar archivo send_message existe", function() {
if (!file_exists(__DIR__ . '/../api/send_message.php')) {
throw new Exception("Archivo send_message.php no existe");
}
return "Archivo send_message.php existe";
});
// Test 2: Verificar contenido básico del archivo
$this->apiTest("Verificar estructura de send_message", function() {
$content = file_get_contents(__DIR__ . '/../api/send_message.php');
if (strpos($content, 'requireAuthentication()') === false) {
throw new Exception("API no incluye verificación de autenticación");
}
if (strpos($content, 'Content-Type: application/json') === false) {
throw new Exception("API no establece Content-Type JSON");
}
return "Estructura de send_message correcta";
});
// Test 3: Verificar que usa validaciones
$this->apiTest("Verificar validaciones en send_message", function() {
$content = file_get_contents(__DIR__ . '/../api/send_message.php');
$validations = [
'recipient' => strpos($content, 'recipient') !== false,
'type' => strpos($content, 'type') !== false,
'message' => strpos($content, 'message') !== false
];
foreach ($validations as $field => $found) {
if (!$found) {
throw new Exception("Campo '$field' no está siendo validado");
}
}
return "Validaciones básicas presentes";
});
// Test 4: Verificar integración con WhatsApp
$this->apiTest("Verificar integración WhatsApp", function() {
$content = file_get_contents(__DIR__ . '/../api/send_message.php');
if (strpos($content, 'WhatsAppService') === false &&
strpos($content, 'sendMessage') === false) {
throw new Exception("No integra con servicio de WhatsApp");
}
return "Integración WhatsApp presente";
});
}
private function testBroadcastAPI() {
echo "<h4>📢 Tests de API send_broadcast.php</h4>";
$this->apiTest("Verificar archivo send_broadcast existe", function() {
if (!file_exists(__DIR__ . '/../api/send_broadcast.php')) {
throw new Exception("Archivo send_broadcast.php no existe");
}
return "Archivo de broadcast existe";
});
// Test de contenido del archivo
$this->apiTest("Verificar estructura de broadcast", function() {
$content = file_get_contents(__DIR__ . '/../api/send_broadcast.php');
if (strpos($content, 'requireAuthentication()') === false) {
throw new Exception("API no requiere autenticación");
}
if (strpos($content, 'recipients') === false && strpos($content, 'users') === false) {
throw new Exception("No maneja lista de destinatarios");
}
return "Estructura de broadcast correcta";
});
}
private function testTemplateAPIs() {
echo "<h4>📝 Tests de APIs de Plantillas</h4>";
// Test get_templates.php
$this->apiTest("API get_templates existe y estructura", function() {
if (!file_exists(__DIR__ . '/../api/get_templates.php')) {
throw new Exception("get_templates.php no existe");
}
$content = file_get_contents(__DIR__ . '/../api/get_templates.php');
if (strpos($content, 'requireAuthentication()') === false) {
throw new Exception("get_templates no requiere autenticación");
}
if (strpos($content, 'message_templates') === false) {
throw new Exception("No consulta tabla message_templates");
}
return "API get_templates correcta";
});
// Test save_template.php
$this->apiTest("API save_template existe y validaciones", function() {
if (!file_exists(__DIR__ . '/../api/save_template.php')) {
throw new Exception("save_template.php no existe");
}
$content = file_get_contents(__DIR__ . '/../api/save_template.php');
$validations = [
'autenticación' => strpos($content, 'requireAuthentication()') !== false,
'name' => strpos($content, 'name') !== false,
'template_name' => strpos($content, 'template_name') !== false,
'language_code' => strpos($content, 'language_code') !== false
];
foreach ($validations as $check => $found) {
if (!$found) {
throw new Exception("Validación '$check' faltante");
}
}
return "API save_template completa";
});
// Test update_template_status.php
$this->apiTest("API update_template_status existe", function() {
if (!file_exists(__DIR__ . '/../api/update_template_status.php')) {
throw new Exception("update_template_status.php no existe");
}
$content = file_get_contents(__DIR__ . '/../api/update_template_status.php');
if (strpos($content, 'requireAuthentication()') === false) {
throw new Exception("update_template_status no requiere autenticación");
}
if (strpos($content, 'status') === false) {
throw new Exception("No maneja campo status");
}
return "API update_template_status correcta";
});
// Test de integración con base de datos
$this->apiTest("Verificar tablas de plantillas", function() {
$db = Database::getInstance();
// Verificar que la tabla existe
$result = $db->fetchAll("SHOW TABLES LIKE 'message_templates'");
$tables = $result ? $result : [];
if (empty($tables)) {
throw new Exception("Tabla message_templates no existe");
}
// Verificar estructura de la tabla
$columns = $db->fetchAll("DESCRIBE message_templates");
if (!$columns || empty($columns)) {
throw new Exception("No se puede obtener estructura de message_templates");
}
$requiredColumns = ['id', 'name', 'template_name', 'status', 'created_at'];
$existingColumns = array_map(function($col) {
return $col['Field'];
}, $columns);
foreach ($requiredColumns as $required) {
if (!in_array($required, $existingColumns)) {
throw new Exception("Columna '$required' faltante en message_templates");
}
}
return "Estructura de tabla message_templates correcta";
});
}
private function testUserAPIs() {
echo "<h4>👥 Tests de APIs de Usuarios</h4>";
// Test get_users.php
$this->apiTest("API get_users existe y estructura", function() {
if (!file_exists(__DIR__ . '/../api/get_users.php')) {
throw new Exception("get_users.php no existe");
}
$content = file_get_contents(__DIR__ . '/../api/get_users.php');
if (strpos($content, 'requireAuthentication()') === false) {
throw new Exception("get_users no requiere autenticación");
}
if (strpos($content, 'users') === false && strpos($content, 'conversations') === false) {
throw new Exception("No consulta tablas de usuarios");
}
return "API get_users correcta";
});
// Test export_users.php
$this->apiTest("API export_users existe", function() {
if (!file_exists(__DIR__ . '/../api/export_users.php')) {
throw new Exception("export_users.php no existe");
}
$content = file_get_contents(__DIR__ . '/../api/export_users.php');
if (strpos($content, 'requireAuthentication()') === false) {
throw new Exception("export_users no requiere autenticación");
}
if (strpos($content, 'CSV') === false && strpos($content, 'header') === false) {
throw new Exception("No implementa exportación CSV");
}
return "API export_users correcta";
});
// Verificar tabla de usuarios
$this->apiTest("Verificar estructura tabla usuarios", function() {
$db = Database::getInstance();
// Verificar tabla conversations (usuarios)
$result = $db->fetchAll("SHOW TABLES LIKE 'conversations'");
if (!$result || empty($result)) {
throw new Exception("Tabla conversations no existe");
}
return "Estructura de usuarios correcta";
});
}
private function testConfigurationAPIs() {
echo "<h4>⚙️ Tests de APIs de Configuración</h4>";
// Test get_system_config.php
$this->apiTest("API get_system_config existe", function() {
if (!file_exists(__DIR__ . '/../api/get_system_config.php')) {
throw new Exception("get_system_config.php no existe");
}
$content = file_get_contents(__DIR__ . '/../api/get_system_config.php');
if (strpos($content, 'requireAuthentication()') === false) {
throw new Exception("get_system_config no requiere autenticación");
}
return "API get_system_config correcta";
});
// Test save_system_config.php
$this->apiTest("API save_system_config existe", function() {
if (!file_exists(__DIR__ . '/../api/save_system_config.php')) {
throw new Exception("save_system_config.php no existe");
}
$content = file_get_contents(__DIR__ . '/../api/save_system_config.php');
if (strpos($content, 'requireAuthentication()') === false) {
throw new Exception("save_system_config no requiere autenticación");
}
$fields = ['business_name', 'whatsapp_token', 'phone_number_id'];
foreach ($fields as $field) {
if (strpos($content, $field) === false) {
throw new Exception("Campo '$field' no está siendo procesado");
}
}
return "API save_system_config completa";
});
// Verificar tabla system_config
$this->apiTest("Verificar tabla configuración", function() {
$db = Database::getInstance();
$result = $db->fetchAll("SHOW TABLES LIKE 'system_config'");
if (!$result || empty($result)) {
throw new Exception("Tabla system_config no existe");
}
return "Tabla de configuración existe";
});
}
private function testAnalyticsAPIs() {
echo "<h4>📊 Tests de APIs de Analytics</h4>";
// Test get_stats.php
$this->apiTest("API get_stats existe y estructura", function() {
if (!file_exists(__DIR__ . '/../api/get_stats.php')) {
throw new Exception("get_stats.php no existe");
}
$content = file_get_contents(__DIR__ . '/../api/get_stats.php');
if (strpos($content, 'requireAuthentication()') === false) {
throw new Exception("get_stats no requiere autenticación");
}
$stats = ['total_users', 'conversations_today', 'active_users', 'total_conversations'];
foreach ($stats as $stat) {
if (strpos($content, $stat) === false) {
throw new Exception("Estadística '$stat' no está implementada");
}
}
return "API get_stats completa";
});
// Test get_chart_data.php
$this->apiTest("API get_chart_data existe", function() {
if (!file_exists(__DIR__ . '/../api/get_chart_data.php')) {
throw new Exception("get_chart_data.php no existe");
}
$content = file_get_contents(__DIR__ . '/../api/get_chart_data.php');
if (strpos($content, 'requireAuthentication()') === false) {
throw new Exception("get_chart_data no requiere autenticación");
}
return "API get_chart_data correcta";
});
// Test get_recent_messages.php
$this->apiTest("API get_recent_messages existe", function() {
if (!file_exists(__DIR__ . '/../api/get_recent_messages.php')) {
throw new Exception("get_recent_messages.php no existe");
}
$content = file_get_contents(__DIR__ . '/../api/get_recent_messages.php');
if (strpos($content, 'conversations') === false) {
throw new Exception("No consulta mensajes");
}
return "API get_recent_messages correcta";
});
// Test de integridad de datos
$this->apiTest("Verificar tablas de analytics", function() {
$db = Database::getInstance();
$tables = ['conversations', 'conversations'];
foreach ($tables as $table) {
$result = $db->fetchAll("SHOW TABLES LIKE '$table'");
if (!$result || empty($result)) {
throw new Exception("Tabla '$table' necesaria para analytics no existe");
}
}
return "Tablas para analytics existen";
});
}
private function callAPI($endpoint, $method = 'GET', $data = null) {
// Configurar el entorno para la llamada
$_SERVER['REQUEST_METHOD'] = $method;
if ($method === 'POST' && $data) {
$_POST = $data;
// Simular php://input para APIs que lo usan
$GLOBALS['HTTP_RAW_POST_DATA'] = json_encode($data);
}
ob_start();
try {
// Cambiar al directorio padre temporalmente para que las rutas relativas funcionen
$originalDir = getcwd();
chdir(__DIR__ . '/..');
include "api/$endpoint";
// Restaurar directorio original
chdir($originalDir);
$output = ob_get_clean();
// Intentar decodificar como JSON
$decoded = json_decode($output, true);
if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
// Si no es JSON válido, retornar el output raw
return ['raw_output' => $output];
}
return $decoded;
} catch (Exception $e) {
ob_end_clean();
// Restaurar directorio en caso de excepción
if (isset($originalDir)) {
chdir($originalDir);
}
throw $e;
} finally {
// Restaurar directorio y limpiar variables globales
if (isset($originalDir)) {
chdir($originalDir);
}
unset($_POST);
unset($GLOBALS['HTTP_RAW_POST_DATA']);
}
}
private function apiTest($name, $callable) {
try {
$result = $callable();
echo "<div class='alert alert-success'><i class='fas fa-check'></i> <strong>$name:</strong> $result</div>";
$this->testResults[] = ['name' => $name, 'status' => 'passed', 'message' => $result];
} catch (Exception $e) {
echo "<div class='alert alert-danger'><i class='fas fa-times'></i> <strong>$name:</strong> " . $e->getMessage() . "</div>";
$this->testResults[] = ['name' => $name, 'status' => 'failed', 'message' => $e->getMessage()];
}
}
private function showAPITestSummary() {
$total = count($this->testResults);
$passed = count(array_filter($this->testResults, function($r) { return $r['status'] === 'passed'; }));
$failed = $total - $passed;
$successRate = $total > 0 ? ($passed / $total) * 100 : 0;
$alertClass = $successRate === 100.0 ? 'success' : ($successRate >= 80 ? 'warning' : 'danger');
echo "<div class='alert alert-$alertClass mt-4'>";
echo "<h4><i class='fas fa-api'></i> Resumen Tests de APIs</h4>";
echo "<p><strong>Total de tests API:</strong> $total | ";
echo "<strong>Pasaron:</strong> $passed | ";
echo "<strong>Fallaron:</strong> $failed | ";
echo "<strong>Éxito:</strong> " . number_format($successRate, 1) . "%</p>";
echo "</div>";
}
}
// No ejecutar si es incluido por el runner principal
if (basename($_SERVER['PHP_SELF']) === 'api_tests.php') {
$configPath = __DIR__ . '/../config/config.php';
if (file_exists($configPath)) {
require_once $configPath;
}
$suite = new APITestSuite();
$suite->runAllAPITests();
}
?>
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
-644
View File
@@ -1,644 +0,0 @@
<?php
/**
* Test Runner Principal - Sistema WhatsApp Bot
* Ejecuta todos los tests del sistema
* Fecha: 3 de enero de 2026
*/
// Determinar la ruta correcta al archivo de configuración
$configPath = __DIR__ . '/../config/config.php';
if (!file_exists($configPath)) {
die("Error: No se puede encontrar el archivo de configuración en: $configPath");
}
require_once $configPath;
header('Content-Type: text/html; charset=utf-8');
class TestRunner {
private $results = [];
private $totalTests = 0;
private $passedTests = 0;
private $failedTests = 0;
private $startTime;
public function __construct() {
$this->startTime = microtime(true);
}
public function runAllTests() {
echo $this->getHtmlHeader();
echo "<h1 class='text-center mb-4'>🧪 Suite de Tests Completa - WhatsApp Bot</h1>";
echo "<div class='alert alert-info'>";
echo "<h5><i class='fas fa-info-circle'></i> Suite de Tests Comprensiva</h5>";
echo "<p>Esta suite ejecuta <strong>más de 50 tests</strong> cubriendo todas las funcionalidades críticas:</p>";
echo "<ul>";
echo "<li><strong>Infraestructura:</strong> Archivos, configuración, base de datos</li>";
echo "<li><strong>APIs:</strong> Todas las 17 APIs del sistema</li>";
echo "<li><strong>Servicios:</strong> WhatsAppService, BotService, Webhook</li>";
echo "<li><strong>Seguridad:</strong> Autenticación, autorización, validación</li>";
echo "<li><strong>Plantillas:</strong> Sistema completo implementado</li>";
echo "<li><strong>Integración:</strong> Flujos end-to-end</li>";
echo "</ul>";
echo "</div>";
// Tests de infraestructura
$this->runInfrastructureTests();
// Tests de servicios
$this->runServiceTests();
// Tests específicos incluidos
$this->runSpecializedTests();
// Tests de APIs
$this->runAPITests();
// Tests de webhook
$this->runWebhookTests();
// Tests de seguridad
$this->runSecurityTests();
// Tests de plantillas
$this->runTemplateTests();
// Tests de base de datos
$this->runDatabaseTests();
$this->showSummary();
echo $this->getHtmlFooter();
}
private function runSpecializedTests() {
$this->showTestSection("🔬 Tests Especializados");
$this->test("Ejecutar Suite de APIs", function() {
ob_start();
require_once 'tests/api_tests.php';
$apiSuite = new APITestSuite();
$apiSuite->runAllAPITests();
$output = ob_get_clean();
// Contar tests exitosos en la salida
$successCount = substr_count($output, 'alert-success');
$errorCount = substr_count($output, 'alert-danger');
if ($errorCount > $successCount) {
throw new Exception("APIs: $errorCount errores, $successCount éxitos");
}
return "Suite de APIs: $successCount tests pasaron, $errorCount fallaron";
});
$this->test("Ejecutar Suite de Servicios", function() {
ob_start();
require_once 'tests/service_tests.php';
$serviceSuite = new ServiceTestSuite();
$serviceSuite->runAllServiceTests();
$output = ob_get_clean();
$successCount = substr_count($output, 'alert-success');
$errorCount = substr_count($output, 'alert-danger');
return "Suite de Servicios: $successCount tests pasaron, $errorCount fallaron";
});
$this->test("Ejecutar Suite de Webhook", function() {
ob_start();
require_once 'tests/webhook_tests.php';
$webhookSuite = new WebhookTestSuite();
$webhookSuite->runAllWebhookTests();
$output = ob_get_clean();
$successCount = substr_count($output, 'alert-success');
$errorCount = substr_count($output, 'alert-danger');
return "Suite de Webhook: $successCount tests pasaron, $errorCount fallaron";
});
$this->test("Ejecutar Suite de Seguridad", function() {
ob_start();
require_once 'tests/security_tests.php';
$securitySuite = new SecurityTestSuite();
$securitySuite->runAllSecurityTests();
$output = ob_get_clean();
$successCount = substr_count($output, 'alert-success');
$warningCount = substr_count($output, 'alert-warning');
$errorCount = substr_count($output, 'alert-danger');
if ($warningCount > 0) {
return "Suite de Seguridad: $successCount OK, $warningCount advertencias, $errorCount errores";
}
return "Suite de Seguridad: $successCount tests pasaron, $errorCount fallaron";
});
$this->test("Ejecutar Suite de Plantillas", function() {
ob_start();
require_once 'tests/template_tests.php';
$templateSuite = new TemplateTestSuite();
$templateSuite->runAllTemplateTests();
$output = ob_get_clean();
$successCount = substr_count($output, 'alert-success');
$errorCount = substr_count($output, 'alert-danger');
return "Suite de Plantillas: $successCount tests pasaron, $errorCount fallaron";
});
}
private function runInfrastructureTests() {
$this->showTestSection("🏗️ Tests de Infraestructura");
$this->test("Verificar archivos principales", function() {
$required = [
__DIR__ . '/../config/config.php',
__DIR__ . '/../classes/Database.php',
__DIR__ . '/../services/WhatsAppService.php',
__DIR__ . '/../services/BotService.php',
__DIR__ . '/../api/webhook.php',
__DIR__ . '/../index.php'
];
foreach ($required as $file) {
if (!file_exists($file)) {
throw new Exception("Archivo faltante: $file");
}
}
return "Todos los archivos principales existen";
});
$this->test("Verificar configuración", function() {
if (!defined('DB_HOST') || !defined('WHATSAPP_TOKEN')) {
throw new Exception("Constantes de configuración faltantes");
}
return "Configuración cargada correctamente";
});
$this->test("Conexión a base de datos", function() {
$db = Database::getInstance();
$result = $db->fetch("SELECT 1 as test");
if ($result['test'] !== 1) {
throw new Exception("Test de conexión fallido");
}
return "Conexión DB exitosa";
});
}
private function runServiceTests() {
$this->showTestSection("🔧 Tests de Servicios");
$this->test("Inicialización WhatsAppService", function() {
$service = new WhatsAppService();
if (!$service) {
throw new Exception("No se pudo inicializar WhatsAppService");
}
return "WhatsAppService inicializado correctamente";
});
$this->test("Inicialización BotService", function() {
$service = new BotService();
if (!$service) {
throw new Exception("No se pudo inicializar BotService");
}
return "BotService inicializado correctamente";
});
$this->test("Formateo de número de teléfono", function() {
$service = new WhatsAppService();
$reflection = new ReflectionClass($service);
$method = $reflection->getMethod('formatPhoneNumber');
$method->setAccessible(true);
$result1 = $method->invokeArgs($service, ['573001234567']);
$result2 = $method->invokeArgs($service, ['+573001234567']);
if ($result1 !== '573001234567' || $result2 !== '573001234567') {
throw new Exception("Formateo incorrecto: $result1, $result2");
}
return "Formateo de números funciona correctamente";
});
}
private function runAPITests() {
$this->showTestSection("🌐 Tests de APIs");
// Test API de estadísticas
$this->test("API get_stats.php", function() {
$result = $this->makeAPICall('get_stats.php');
if (!isset($result['total_users'])) {
throw new Exception("Estructura de respuesta incorrecta");
}
return "API de estadísticas responde correctamente";
});
// Test API de usuarios
$this->test("API get_users.php", function() {
$result = $this->makeAPICall('get_users.php');
if (!is_array($result)) {
throw new Exception("Respuesta debe ser un array");
}
return "API de usuarios responde correctamente";
});
// Test API de plantillas
$this->test("API get_templates.php", function() {
$result = $this->makeAPICall('get_templates.php');
if (!is_array($result)) {
throw new Exception("Respuesta debe ser un array");
}
return "API de plantillas responde correctamente";
});
// Test creación de plantilla
$this->test("API save_template.php", function() {
$testData = [
'name' => 'Test Template ' . time(),
'template_name' => 'test_template_' . time(),
'language_code' => 'es',
'category' => 'utility'
];
$result = $this->makeAPICall('save_template.php', 'POST', $testData);
if (!$result['success']) {
throw new Exception("Error creando plantilla: " . ($result['error'] ?? 'Unknown'));
}
return "API de creación de plantillas funciona";
});
}
private function runWebhookTests() {
$this->showTestSection("🔗 Tests de Webhook");
$this->test("Webhook verification", function() {
$testUrl = '/api/webhook.php?hub_mode=subscribe&hub_verify_token=' . WEBHOOK_VERIFY_TOKEN . '&hub_challenge=test123';
// Simular petición GET de verificación
$_GET = [
'hub_mode' => 'subscribe',
'hub_verify_token' => WEBHOOK_VERIFY_TOKEN,
'hub_challenge' => 'test123'
];
ob_start();
$webhook = new WhatsAppWebhook();
$webhook->handleRequest();
$output = ob_get_clean();
if ($output !== 'test123') {
throw new Exception("Verificación de webhook fallida: $output");
}
return "Webhook verification funcionando";
});
$this->test("Webhook procesamiento de mensaje", function() {
// Test básico de estructura de webhook
$testPayload = [
'object' => 'whatsapp_business_account',
'entry' => [
[
'changes' => [
[
'value' => [
'conversations' => [
[
'from' => '573001234567',
'text' => ['body' => 'test message'],
'timestamp' => time()
]
]
]
]
]
]
]
];
// Esto requeriría más setup para simular completamente
return "Estructura de webhook reconocida (test básico)";
});
}
private function runSecurityTests() {
$this->showTestSection("🔒 Tests de Seguridad");
$this->test("Verificar autenticación requerida", function() {
// Simular usuario no logueado
unset($_SESSION['admin_logged_in']);
if (isUserLoggedIn()) {
throw new Exception("Función de autenticación no funciona correctamente");
}
return "Verificación de autenticación funciona";
});
$this->test("Verificar función requireAuthentication", function() {
unset($_SESSION['admin_logged_in']);
ob_start();
try {
requireAuthentication();
$output = ob_get_clean();
throw new Exception("requireAuthentication no bloqueó usuario no autenticado");
} catch (Exception $e) {
$output = ob_get_clean();
// Si hay output, significa que la función funcionó
if (empty($output)) {
throw new Exception("requireAuthentication no generó respuesta JSON");
}
}
return "Función requireAuthentication protege correctamente";
});
$this->test("Validación de tokens", function() {
if (empty(WHATSAPP_TOKEN) || WHATSAPP_TOKEN === 'TU_TOKEN_DE_WHATSAPP_AQUI') {
throw new Exception("Token de WhatsApp no configurado");
}
if (empty(WEBHOOK_VERIFY_TOKEN) || WEBHOOK_VERIFY_TOKEN === 'mi_token_secreto_123') {
return "⚠️ Token de webhook usando valor por defecto (cambiar en producción)";
}
return "Tokens configurados correctamente";
});
}
private function runTemplateTests() {
$this->showTestSection("📝 Tests de Plantillas");
$this->test("Verificar tabla message_templates", function() {
$db = Database::getInstance();
$tables = $db->fetchAll("SHOW TABLES LIKE 'message_templates'");
if (empty($tables)) {
throw new Exception("Tabla message_templates no existe");
}
return "Tabla message_templates existe";
});
$this->test("Insertar plantilla de prueba", function() {
$db = Database::getInstance();
$testName = 'test_template_' . time();
$id = $db->insert('message_templates', [
'name' => $testName,
'template_name' => $testName,
'language_code' => 'es',
'category' => 'utility',
'status' => 'pending'
]);
if (!$id) {
throw new Exception("No se pudo insertar plantilla de prueba");
}
// Limpiar
$db->execute("DELETE FROM message_templates WHERE id = :id", ['id' => $id]);
return "Inserción y eliminación de plantillas funciona";
});
$this->test("Estados de plantillas", function() {
$db = Database::getInstance();
$validStatuses = ['pending', 'approved', 'rejected'];
// Verificar que la columna acepta solo estos valores
$columns = $db->fetchAll("DESCRIBE message_templates");
$statusColumn = null;
foreach ($columns as $column) {
if ($column['Field'] === 'status') {
$statusColumn = $column;
break;
}
}
if (!$statusColumn || strpos($statusColumn['Type'], 'enum') === false) {
throw new Exception("Columna status no es ENUM o no existe");
}
return "Estados de plantillas correctamente definidos";
});
}
private function runDatabaseTests() {
$this->showTestSection("💾 Tests de Base de Datos");
$this->test("Verificar todas las tablas principales", function() {
$db = Database::getInstance();
$requiredTables = [
'users', 'conversations', 'menus', 'menu_options',
'system_config', 'autoresponses', 'message_templates',
'webhook_logs'
];
$existingTables = [];
foreach ($requiredTables as $table) {
$result = $db->fetchAll("SHOW TABLES LIKE '$table'");
if (empty($result)) {
throw new Exception("Tabla faltante: $table");
}
$existingTables[] = $table;
}
return "Todas las tablas principales existen: " . implode(', ', $existingTables);
});
$this->test("CRUD básico en tabla users", function() {
$db = Database::getInstance();
$testPhone = '573999' . rand(100000, 999999);
// Create
$id = $db->insert('users', [
'phone_number' => $testPhone,
'name' => 'Test User',
'status' => 'active'
]);
if (!$id) throw new Exception("Insert fallido");
// Read
$user = $db->fetch("SELECT * FROM users WHERE id = :id", ['id' => $id]);
if ($user['phone_number'] !== $testPhone) {
throw new Exception("Select fallido");
}
// Update
$updated = $db->update('users', ['name' => 'Updated User'], ['id' => $id]);
if (!$updated) throw new Exception("Update fallido");
// Delete
$deleted = $db->execute("DELETE FROM users WHERE id = :id", ['id' => $id]);
if (!$deleted) throw new Exception("Delete fallido");
return "CRUD completo funciona correctamente";
});
$this->test("Integridad referencial", function() {
$db = Database::getInstance();
// Verificar que las foreign keys están definidas
$foreignKeys = $db->fetchAll("
SELECT TABLE_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE()
AND REFERENCED_TABLE_NAME IS NOT NULL
");
if (empty($foreignKeys)) {
return "⚠️ Sin foreign keys definidas (recomendado para integridad)";
}
return "Integridad referencial configurada: " . count($foreignKeys) . " FK";
});
}
private function test($name, $callable) {
$this->totalTests++;
try {
$result = $callable();
$this->passedTests++;
$this->showTestResult($name, true, $result);
} catch (Exception $e) {
$this->failedTests++;
$this->showTestResult($name, false, $e->getMessage());
}
}
private function makeAPICall($endpoint, $method = 'GET', $data = null) {
// Simular sesión de administrador para tests
$_SESSION['admin_logged_in'] = true;
$_SESSION['last_activity'] = time();
$url = "api/$endpoint";
if ($method === 'GET' && $data) {
$url .= '?' . http_build_query($data);
}
$context = stream_context_create([
'http' => [
'method' => $method,
'header' => 'Content-Type: application/json',
'content' => $data ? json_encode($data) : null
]
]);
// Para tests, incluir el archivo directamente
ob_start();
if ($method === 'POST' && $data) {
// Simular POST data
$_POST = $data;
file_put_contents('php://input', json_encode($data));
}
try {
include $url;
$output = ob_get_clean();
return json_decode($output, true);
} catch (Exception $e) {
ob_get_clean();
throw $e;
}
}
private function showTestSection($title) {
echo "<div class='card mb-4'>";
echo "<div class='card-header'><h5>$title</h5></div>";
echo "<div class='card-body'>";
}
private function showTestResult($name, $passed, $message) {
$class = $passed ? 'success' : 'danger';
$icon = $passed ? 'check-circle' : 'times-circle';
echo "<div class='alert alert-$class'>";
echo "<i class='fas fa-$icon'></i> ";
echo "<strong>$name:</strong> $message";
echo "</div>";
if (!$passed) {
echo "</div></div>"; // Cerrar card si hay error
}
}
private function showSummary() {
echo "</div></div>"; // Cerrar última card
$successRate = $this->totalTests > 0 ? ($this->passedTests / $this->totalTests) * 100 : 0;
$duration = round((microtime(true) - $this->startTime) * 1000, 2);
$alertClass = $successRate === 100.0 ? 'success' : ($successRate >= 80 ? 'warning' : 'danger');
echo "<div class='alert alert-$alertClass'>";
echo "<h4><i class='fas fa-chart-pie'></i> Resumen de Tests</h4>";
echo "<div class='row'>";
echo "<div class='col-md-3'><strong>Total:</strong> {$this->totalTests}</div>";
echo "<div class='col-md-3'><strong>Pasaron:</strong> {$this->passedTests}</div>";
echo "<div class='col-md-3'><strong>Fallaron:</strong> {$this->failedTests}</div>";
echo "<div class='col-md-3'><strong>Éxito:</strong> " . number_format($successRate, 1) . "%</div>";
echo "</div>";
echo "<p class='mt-3'><strong>Tiempo de ejecución:</strong> {$duration}ms</p>";
if ($successRate === 100.0) {
echo "<p class='mb-0'><strong>🎉 ¡Todos los tests pasaron! El sistema está funcionando correctamente.</strong></p>";
} else if ($successRate >= 80) {
echo "<p class='mb-0'><strong>⚠️ La mayoría de tests pasaron, pero hay algunas fallas que requieren atención.</strong></p>";
} else {
echo "<p class='mb-0'><strong>🚨 Múltiples fallas detectadas. El sistema requiere correcciones urgentes.</strong></p>";
}
echo "</div>";
}
private function getHtmlHeader() {
return '
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test Suite - WhatsApp Bot</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
';
}
private function getHtmlFooter() {
return '
<div class="text-center mt-4">
<a href="index.php" class="btn btn-primary"><i class="fas fa-home"></i> Ir al Panel</a>
<a href="test.php" class="btn btn-secondary"><i class="fas fa-cog"></i> Test Básico</a>
<a href="whatsapp_tester.php" class="btn btn-info"><i class="fas fa-phone"></i> Test WhatsApp</a>
</div>
<hr>
<p class="text-center text-muted"><small>Suite de tests ejecutada el: ' . date('Y-m-d H:i:s') . '</small></p>
</div>
</body>
</html>
';
}
}
// Ejecutar todos los tests
$runner = new TestRunner();
$runner->runAllTests();
?>
-575
View File
@@ -1,575 +0,0 @@
<?php
/**
* Tests de Seguridad para el Sistema WhatsApp Bot
* Fecha: 3 de enero de 2026
*/
class SecurityTestSuite {
private $db;
private $originalSession;
public function __construct() {
$this->db = Database::getInstance();
$this->originalSession = $_SESSION ?? [];
}
public function runAllSecurityTests() {
echo "<h2>🔒 Tests Exhaustivos de Seguridad</h2>";
$this->testAuthentication();
$this->testAuthorization();
$this->testSessionSecurity();
$this->testInputValidation();
$this->testSQLInjectionPrevention();
$this->testXSSPrevention();
$this->testCSRFProtection();
$this->testRateLimiting();
$_SESSION = $this->originalSession;
}
private function testAuthentication() {
echo "<h3>🔐 Tests de Autenticación</h3>";
$this->securityTest("Función isUserLoggedIn - usuario no logueado", function() {
unset($_SESSION['admin_logged_in']);
if (isUserLoggedIn()) {
throw new Exception("isUserLoggedIn retorna true sin sesión activa");
}
return "Usuario no logueado detectado correctamente";
});
$this->securityTest("Función isUserLoggedIn - usuario logueado", function() {
$_SESSION['admin_logged_in'] = true;
$_SESSION['last_activity'] = time();
if (!isUserLoggedIn()) {
throw new Exception("isUserLoggedIn retorna false con sesión activa");
}
return "Usuario logueado detectado correctamente";
});
$this->securityTest("Función requireAuthentication bloquea acceso", function() {
unset($_SESSION['admin_logged_in']);
ob_start();
try {
requireAuthentication();
$output = ob_get_clean();
throw new Exception("requireAuthentication no bloqueó usuario no autenticado");
} catch (Exception $e) {
$output = ob_get_clean();
// Verificar que hay salida JSON de error
$decoded = json_decode($output, true);
if (!$decoded || !isset($decoded['success']) || $decoded['success'] !== false) {
throw new Exception("requireAuthentication no retorna JSON de error apropiado");
}
return "requireAuthentication bloquea correctamente usuarios no autenticados";
}
});
$this->securityTest("Timeout de sesión funciona", function() {
$_SESSION['admin_logged_in'] = true;
$_SESSION['last_activity'] = time() - (SESSION_TIMEOUT + 100); // Sesión expirada
// Simular verificación de timeout (normalmente en config.php)
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity']) > SESSION_TIMEOUT) {
unset($_SESSION['admin_logged_in']);
}
if (isUserLoggedIn()) {
throw new Exception("Sesión expirada no fue invalidada");
}
return "Timeout de sesión funciona correctamente";
});
$this->securityTest("Verificación de contraseña hash", function() {
$testPassword = 'test123';
$hash = password_hash($testPassword, PASSWORD_DEFAULT);
if (!password_verify($testPassword, $hash)) {
throw new Exception("Verificación de hash falló");
}
if (password_verify('wrong_password', $hash)) {
throw new Exception("Hash acepta contraseña incorrecta");
}
return "Sistema de hash de contraseñas funciona correctamente";
});
}
private function testAuthorization() {
echo "<h3>👤 Tests de Autorización</h3>";
$this->securityTest("APIs requieren autenticación", function() {
$protectedAPIs = [
'get_stats.php', 'get_users.php', 'send_message.php',
'save_template.php', 'get_templates.php', 'save_system_config.php'
];
unset($_SESSION['admin_logged_in']);
foreach ($protectedAPIs as $api) {
ob_start();
try {
// Cambiar al directorio padre temporalmente
$originalDir = getcwd();
chdir(__DIR__ . '/..');
include "api/$api";
// Restaurar directorio original
chdir($originalDir);
$output = ob_get_clean();
// Si no retorna error 401, falla el test
if (strpos($output, '"success":false') === false) {
throw new Exception("API $api no requiere autenticación");
}
} catch (Exception $e) {
ob_end_clean();
// Restaurar directorio en caso de excepción
if (isset($originalDir)) {
chdir($originalDir);
}
// Si hay excepción por no estar autenticado, está bien
if (strpos($e->getMessage(), 'autenticación') === false &&
strpos($e->getMessage(), 'autorizado') === false) {
throw $e;
}
}
}
return "APIs críticas requieren autenticación: " . count($protectedAPIs) . " APIs protegidas";
});
$this->securityTest("Panel principal redirige sin login", function() {
unset($_SESSION['admin_logged_in']);
// Simular acceso a index.php
ob_start();
try {
// El index.php ahora debería redirigir
$_SERVER['REQUEST_URI'] = '/index.php';
// Verificar que la verificación está implementada
$indexContent = file_get_contents('index.php');
if (strpos($indexContent, 'isUserLoggedIn()') === false) {
throw new Exception("index.php no verifica autenticación");
}
return "Panel principal protegido con verificación de login";
} finally {
ob_end_clean();
}
});
}
private function testSessionSecurity() {
echo "<h3>🛡️ Tests de Seguridad de Sesión</h3>";
$this->securityTest("Configuración de sesión segura", function() {
$issues = [];
// Verificar configuración de cookies
if (!ini_get('session.cookie_httponly')) {
$issues[] = "session.cookie_httponly debería estar habilitado";
}
if (ini_get('session.cookie_secure') && !isset($_SERVER['HTTPS'])) {
$issues[] = "session.cookie_secure habilitado sin HTTPS";
}
if (ini_get('session.use_only_cookies') != 1) {
$issues[] = "session.use_only_cookies debería estar habilitado";
}
if (!empty($issues)) {
return "⚠️ Mejoras recomendadas: " . implode(', ', $issues);
}
return "Configuración de sesión es apropiada";
});
$this->securityTest("Regeneración de ID de sesión", function() {
session_start();
$oldId = session_id();
session_regenerate_id(true);
$newId = session_id();
if ($oldId === $newId) {
throw new Exception("ID de sesión no se regeneró");
}
return "Regeneración de ID de sesión funciona";
});
$this->securityTest("Validación de IP de sesión", function() {
$_SESSION['admin_logged_in'] = true;
$_SESSION['login_ip'] = '192.168.1.1';
$currentIp = $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
// En un sistema más seguro, esto debería validarse
if (isset($_SESSION['login_ip']) && $_SESSION['login_ip'] !== $currentIp) {
// Esto es opcional, pero es una buena práctica
return "⚠️ Validación de IP no implementada (feature de seguridad adicional)";
}
return "Validación de IP de sesión (básica)";
});
}
private function testInputValidation() {
echo "<h3>✅ Tests de Validación de Entrada</h3>";
$this->securityTest("Validación de número de teléfono", function() {
$validNumbers = ['+573001234567', '573001234567', '57 300 123 4567'];
$invalidNumbers = ['123', 'abc', '', '+1', '++573001234567', '<script>'];
$whatsappService = new WhatsAppService();
$reflection = new ReflectionClass($whatsappService);
$formatMethod = $reflection->getMethod('formatPhoneNumber');
$formatMethod->setAccessible(true);
// Números válidos deberían formatarse
foreach ($validNumbers as $number) {
$result = $formatMethod->invokeArgs($whatsappService, [$number]);
if (empty($result) || !is_string($result)) {
throw new Exception("Número válido '$number' no se formateó correctamente");
}
}
// Números inválidos deberían ser rechazados o limpiados
foreach ($invalidNumbers as $number) {
try {
$result = $formatMethod->invokeArgs($whatsappService, [$number]);
if ($result === $number && strpos($number, '<script>') !== false) {
throw new Exception("Entrada maliciosa no fue filtrada");
}
} catch (Exception $e) {
// Error esperado para entradas inválidas
continue;
}
}
return "Validación de números de teléfono funciona";
});
$this->securityTest("Sanitización de entrada de plantillas", function() {
$maliciousInputs = [
'<script>alert("xss")</script>',
'"; DROP TABLE users; --',
'../../../etc/passwd',
'<?php system($_GET[c]); ?>',
'javascript:alert(1)'
];
foreach ($maliciousInputs as $input) {
// Test crear plantilla con entrada maliciosa
try {
$_SESSION['admin_logged_in'] = true;
$testData = [
'name' => $input,
'template_name' => 'safe_name',
'language_code' => 'es',
'category' => 'utility'
];
ob_start();
$_POST = $testData;
$GLOBALS['HTTP_RAW_POST_DATA'] = json_encode($testData);
include 'api/save_template.php';
$output = ob_get_clean();
$result = json_decode($output, true);
// Si se creó exitosamente, verificar que se sanitizó
if (isset($result['success']) && $result['success']) {
$created = $this->db->fetch(
"SELECT * FROM message_templates WHERE template_name = 'safe_name' ORDER BY id DESC LIMIT 1"
);
if ($created && $created['name'] === $input) {
// Limpiar
$this->db->execute("DELETE FROM message_templates WHERE id = :id", ['id' => $created['id']]);
throw new Exception("Entrada maliciosa '$input' no fue sanitizada");
}
if ($created) {
$this->db->execute("DELETE FROM message_templates WHERE id = :id", ['id' => $created['id']]);
}
}
} catch (Exception $e) {
ob_end_clean();
// Si rechaza entrada maliciosa, está bien
continue;
} finally {
unset($_POST);
unset($GLOBALS['HTTP_RAW_POST_DATA']);
}
}
return "Entradas maliciosas son filtradas o rechazadas";
});
}
private function testSQLInjectionPrevention() {
echo "<h3>💉 Tests de Prevención de Inyección SQL</h3>";
$this->securityTest("Prepared statements en Database class", function() {
$db = Database::getInstance();
// Test con entrada potencialmente maliciosa
$maliciousId = "1'; DROP TABLE users; --";
try {
$result = $db->fetch(
"SELECT * FROM users WHERE id = :id",
['id' => $maliciousId]
);
// Verificar que la tabla users sigue existiendo
$tableCheck = $db->fetchAll("SHOW TABLES LIKE 'users'");
if (empty($tableCheck)) {
throw new Exception("Tabla eliminada - posible inyección SQL exitosa");
}
return "Prepared statements previenen inyección SQL básica";
} catch (Exception $e) {
// Si hay error por tipo de dato incorrecto, está bien
if (strpos($e->getMessage(), 'DROP') !== false) {
throw new Exception("Inyección SQL detectada en error");
}
return "Prepared statements rechazan entrada maliciosa";
}
});
$this->securityTest("Validación en búsquedas de usuario", function() {
$maliciousPhone = "'; SELECT password FROM admin_users; --";
try {
$webhook = new WhatsAppWebhook();
$reflection = new ReflectionClass($webhook);
$method = $reflection->getMethod('getUserByPhone');
$method->setAccessible(true);
$result = $method->invokeArgs($webhook, [$maliciousPhone]);
// No debería retornar datos de otras tablas
if (is_array($result) && isset($result['password'])) {
throw new Exception("Posible inyección SQL - datos no esperados retornados");
}
return "Búsqueda de usuarios resistente a inyección SQL";
} catch (Exception $e) {
// Error esperado con entrada maliciosa
return "Búsqueda de usuarios rechaza entrada maliciosa";
}
});
}
private function testXSSPrevention() {
echo "<h3>🚫 Tests de Prevención XSS</h3>";
$this->securityTest("Escape de salida en APIs", function() {
$xssPayloads = [
'<script>alert("xss")</script>',
'"><img src=x onerror=alert(1)>',
'javascript:alert(1)',
'\'-alert(1)-\'',
'&lt;script&gt;alert(1)&lt;/script&gt;'
];
// Test con API que retorna datos del usuario
$_SESSION['admin_logged_in'] = true;
foreach ($xssPayloads as $payload) {
// Crear usuario con payload XSS
$userId = $this->db->insert('users', [
'phone_number' => '573000000001',
'name' => $payload,
'status' => 'active'
]);
if ($userId) {
ob_start();
include 'api/get_users.php';
$output = ob_get_clean();
$data = json_decode($output, true);
if (is_array($data)) {
foreach ($data as $user) {
if (isset($user['name']) && $user['name'] === $payload) {
// Limpiar
$this->db->execute("DELETE FROM users WHERE id = :id", ['id' => $userId]);
// En JSON, esto es relativamente seguro, pero verificar
if (strpos($payload, '<script>') !== false) {
return "⚠️ XSS payload en JSON - seguro si se maneja correctamente en frontend";
}
}
}
}
// Limpiar
$this->db->execute("DELETE FROM users WHERE id = :id", ['id' => $userId]);
}
}
return "APIs JSON relativamente seguras contra XSS";
});
}
private function testCSRFProtection() {
echo "<h3">🔄 Tests de Protección CSRF</h3>";
$this->securityTest("Verificar headers de seguridad", function() {
$expectedHeaders = [
'X-Content-Type-Options: nosniff',
'X-Frame-Options: SAMEORIGIN',
'X-XSS-Protection: 1; mode=block'
];
// Verificar que config.php establece estos headers
$configContent = file_get_contents(__DIR__ . '/../config/config.php');
$foundHeaders = 0;
foreach ($expectedHeaders as $header) {
if (strpos($configContent, $header) !== false) {
$foundHeaders++;
}
}
if ($foundHeaders === 0) {
return "⚠️ Headers de seguridad no configurados";
}
if ($foundHeaders < count($expectedHeaders)) {
return "⚠️ Algunos headers de seguridad faltantes ($foundHeaders/" . count($expectedHeaders) . ")";
}
return "Headers de seguridad configurados correctamente";
});
$this->securityTest("Validación de origen de petición", function() {
// Test básico - en producción debería validar Referer/Origin
$_SERVER['HTTP_REFERER'] = 'http://evil-site.com';
$_SERVER['REQUEST_METHOD'] = 'POST';
// Por ahora, no hay validación CSRF implementada
return "⚠️ Protección CSRF no implementada (recomendado para producción)";
});
}
private function testRateLimiting() {
echo "<h3">⏱️ Tests de Limitación de Velocidad</h3>";
$this->securityTest("Sistema de intentos de login", function() {
$testIp = '192.168.1.100';
// Verificar que la función checkLoginAttempts existe
if (!function_exists('checkLoginAttempts')) {
throw new Exception("Función checkLoginAttempts no existe");
}
// Test inicial - debería permitir
$allowed = checkLoginAttempts($testIp);
if (!$allowed) {
// Limpiar intentos previos
clearLoginAttempts($testIp);
$allowed = checkLoginAttempts($testIp);
}
if (!$allowed) {
throw new Exception("IP limpia debería estar permitida");
}
// Registrar fallos hasta bloquear
for ($i = 0; $i < MAX_LOGIN_ATTEMPTS; $i++) {
recordFailedLogin($testIp);
}
// Ahora debería estar bloqueada
$blocked = checkLoginAttempts($testIp);
if ($blocked) {
throw new Exception("IP debería estar bloqueada tras múltiples fallos");
}
// Limpiar
clearLoginAttempts($testIp);
return "Sistema de rate limiting funciona correctamente";
});
$this->securityTest("Configuración de límites", function() {
if (!defined('MAX_LOGIN_ATTEMPTS') || MAX_LOGIN_ATTEMPTS > 10) {
return "⚠️ MAX_LOGIN_ATTEMPTS muy alto (recomendado: 3-5)";
}
if (!defined('LOGIN_LOCKOUT_TIME') || LOGIN_LOCKOUT_TIME < 300) {
return "⚠️ LOGIN_LOCKOUT_TIME muy bajo (recomendado: 5+ minutos)";
}
return "Configuración de rate limiting apropiada";
});
}
private function securityTest($name, $callable) {
try {
$result = $callable();
$alertClass = 'success';
$icon = 'shield-alt';
if (strpos($result, '⚠️') !== false) {
$alertClass = 'warning';
$icon = 'exclamation-triangle';
}
echo "<div class='alert alert-$alertClass'><i class='fas fa-$icon'></i> <strong>$name:</strong> $result</div>";
} catch (Exception $e) {
echo "<div class='alert alert-danger'><i class='fas fa-times-circle'></i> <strong>$name:</strong> " . $e->getMessage() . "</div>";
}
}
}
// No ejecutar si es incluido por el runner principal
if (basename($_SERVER['PHP_SELF']) === 'security_tests.php') {
$configPath = __DIR__ . '/../config/config.php';
if (file_exists($configPath)) {
require_once $configPath;
}
$suite = new SecurityTestSuite();
$suite->runAllSecurityTests();
}
?>
Binary file not shown.
Binary file not shown.
-384
View File
@@ -1,384 +0,0 @@
<?php
/**
* Tests para WhatsAppService y BotService
* Fecha: 3 de enero de 2026
*/
class ServiceTestSuite {
private $whatsappService;
private $botService;
private $db;
public function __construct() {
$this->db = Database::getInstance();
}
public function runAllServiceTests() {
echo "<h2>🔧 Tests Exhaustivos de Servicios</h2>";
$this->testWhatsAppService();
$this->testBotService();
$this->testWebhookClass();
}
private function testWhatsAppService() {
echo "<h3>📱 Tests WhatsAppService</h3>";
$this->serviceTest("Inicialización WhatsAppService", function() {
$this->whatsappService = new WhatsAppService();
if (!$this->whatsappService) {
throw new Exception("No se pudo crear instancia de WhatsAppService");
}
// Verificar propiedades mediante reflexión
$reflection = new ReflectionClass($this->whatsappService);
$properties = $reflection->getProperties();
$requiredProps = ['token', 'phoneNumberId', 'apiUrl', 'db'];
foreach ($requiredProps as $prop) {
$found = false;
foreach ($properties as $property) {
if ($property->getName() === $prop) {
$found = true;
break;
}
}
if (!$found) {
throw new Exception("Propiedad requerida '$prop' no encontrada");
}
}
return "WhatsAppService inicializado con todas las propiedades";
});
$this->serviceTest("Método formatPhoneNumber", function() {
if (!$this->whatsappService) {
$this->whatsappService = new WhatsAppService();
}
$reflection = new ReflectionClass($this->whatsappService);
$method = $reflection->getMethod('formatPhoneNumber');
$method->setAccessible(true);
// Tests de diferentes formatos
$tests = [
['input' => '573001234567', 'expected' => '573001234567'],
['input' => '+573001234567', 'expected' => '573001234567'],
['input' => '57 300 123 4567', 'expected' => '573001234567'],
['input' => '+57 (300) 123-4567', 'expected' => '573001234567'],
];
foreach ($tests as $test) {
$result = $method->invokeArgs($this->whatsappService, [$test['input']]);
if ($result !== $test['expected']) {
throw new Exception("Formato incorrecto para '{$test['input']}': esperado '{$test['expected']}', obtenido '$result'");
}
}
return "Formateo de números funciona para todos los casos";
});
$this->serviceTest("Validación de estructura de mensaje de texto", function() {
if (!$this->whatsappService) {
$this->whatsappService = new WhatsAppService();
}
$reflection = new ReflectionClass($this->whatsappService);
// Verificar que el método sendTextMessage existe
if (!$reflection->hasMethod('sendTextMessage')) {
throw new Exception("Método sendTextMessage no existe");
}
return "Método sendTextMessage existe y es accesible";
});
$this->serviceTest("Validación de estructura de template message", function() {
if (!$this->whatsappService) {
$this->whatsappService = new WhatsAppService();
}
$reflection = new ReflectionClass($this->whatsappService);
// Verificar que el método sendTemplateMessage existe
if (!$reflection->hasMethod('sendTemplateMessage')) {
throw new Exception("Método sendTemplateMessage no existe");
}
$method = $reflection->getMethod('sendTemplateMessage');
$params = $method->getParameters();
// Verificar parámetros esperados
$expectedParams = ['to', 'templateName', 'language', 'parameters'];
if (count($params) < 2) {
throw new Exception("sendTemplateMessage debe tener al menos 2 parámetros");
}
return "Método sendTemplateMessage tiene estructura correcta";
});
$this->serviceTest("Test método buildMessage", function() {
if (!$this->whatsappService) {
$this->whatsappService = new WhatsAppService();
}
$reflection = new ReflectionClass($this->whatsappService);
if ($reflection->hasMethod('buildMessage')) {
$method = $reflection->getMethod('buildMessage');
$method->setAccessible(true);
// Test con datos de texto
$textData = [
'messaging_product' => 'whatsapp',
'to' => '573001234567',
'type' => 'text',
'text' => ['body' => 'Test message']
];
$result = $method->invokeArgs($this->whatsappService, [$textData]);
if ($result !== 'Test message') {
throw new Exception("buildMessage no procesó correctamente mensaje de texto");
}
return "buildMessage funciona correctamente";
}
return "Método buildMessage no existe (puede ser privado o diferente implementación)";
});
$this->serviceTest("Configuración de tokens", function() {
if (!defined('WHATSAPP_TOKEN') || empty(WHATSAPP_TOKEN) || WHATSAPP_TOKEN === 'TU_TOKEN_DE_WHATSAPP_AQUI') {
throw new Exception("WHATSAPP_TOKEN no está configurado correctamente");
}
if (!defined('WHATSAPP_PHONE_NUMBER_ID') || empty(WHATSAPP_PHONE_NUMBER_ID) || WHATSAPP_PHONE_NUMBER_ID === 'TU_PHONE_ID_AQUI') {
throw new Exception("WHATSAPP_PHONE_NUMBER_ID no está configurado correctamente");
}
if (!defined('WHATSAPP_API_URL') || empty(WHATSAPP_API_URL)) {
throw new Exception("WHATSAPP_API_URL no está configurado");
}
return "Todos los tokens de WhatsApp están configurados";
});
}
private function testBotService() {
echo "<h3>🤖 Tests BotService</h3>";
$this->serviceTest("Inicialización BotService", function() {
$this->botService = new BotService();
if (!$this->botService) {
throw new Exception("No se pudo crear instancia de BotService");
}
return "BotService inicializado correctamente";
});
$this->serviceTest("Método processMessage existe", function() {
if (!$this->botService) {
$this->botService = new BotService();
}
$reflection = new ReflectionClass($this->botService);
if (!$reflection->hasMethod('processMessage')) {
throw new Exception("Método processMessage no existe");
}
$method = $reflection->getMethod('processMessage');
$params = $method->getParameters();
if (count($params) < 1) {
throw new Exception("processMessage debe recibir al menos 1 parámetro");
}
return "Método processMessage tiene estructura correcta";
});
$this->serviceTest("Test de respuestas automáticas", function() {
// Verificar que hay respuestas automáticas configuradas
$responses = $this->db->fetchAll(
"SELECT * FROM autoresponses WHERE trigger_type = 'keyword'"
);
if (empty($responses)) {
throw new Exception("No hay respuestas automáticas configuradas");
}
// Verificar estructura de respuestas
$firstResponse = $responses[0];
$requiredFields = ['trigger_value', 'response_text'];
foreach ($requiredFields as $field) {
if (!isset($firstResponse[$field])) {
throw new Exception("Campo requerido '$field' faltante en autoresponses");
}
}
return "Respuestas automáticas configuradas correctamente: " . count($responses) . " respuestas";
});
$this->serviceTest("Test procesamiento de menús", function() {
// Verificar que hay menús configurados
$menus = $this->db->fetchAll(
"SELECT * FROM menus WHERE is_active = 1"
);
if (empty($menus)) {
throw new Exception("No hay menús activos configurados");
}
// Verificar que el menú raíz existe
$rootMenu = $this->db->fetch(
"SELECT * FROM menus WHERE is_root = 1 AND is_active = 1"
);
if (!$rootMenu) {
throw new Exception("No hay menú raíz configurado");
}
// Verificar opciones del menú raíz
$options = $this->db->fetchAll(
"SELECT * FROM menu_options WHERE menu_id = :id ORDER BY option_number",
['id' => $rootMenu['id']]
);
return "Sistema de menús configurado: " . count($menus) . " menús, " . count($options) . " opciones en menú raíz";
});
$this->serviceTest("Test gestión de usuarios", function() {
// Generar teléfono de prueba único para evitar colisiones
$testPhone = '573' . rand(100000000, 999999999);
// Asegurar limpieza previa (por si quedó un registro previo con mismo número)
$this->db->execute("DELETE FROM users WHERE phone_number = :phone", ['phone' => $testPhone]);
// Crear usuario de prueba
$userId = $this->db->insert('users', [
'phone_number' => $testPhone,
'name' => 'Usite',
'status' => 'active',
'current_step' => 0,
'created_at' => date('Y-m-d H:i:s')
]);
if (!$userId) {
throw new Exception("No se pudo crear usuario de prueba");
}
// Verificar que se creó correctamente
$user = $this->db->fetch(
"SELECT * FROM users WHERE id = :id",
['id' => $userId]
);
if ($user['phone_number'] !== $testPhone) {
throw new Exception("Usuario no se creó correctamente");
}
// Test actualización de estado
$updated = $this->db->update('users',
['current_step' => 1],
'id = :id',
['id' => $userId]
);
if (!$updated) {
throw new Exception("No se pudo actualizar usuario");
}
// Limpiar
$this->db->execute("DELETE FROM users WHERE id = :id", ['id' => $userId]);
return "Gestión de usuarios funciona correctamente";
});
}
private function testWebhookClass() {
echo "<h3>🔗 Tests Clase WhatsAppWebhook</h3>";
$this->serviceTest("Clase WhatsAppWebhook existe", function() {
if (!class_exists('WhatsAppWebhook')) {
// Intentar cargar el archivo
if (file_exists(__DIR__ . '/../api/webhook.php')) {
// Cambiar directorio temporalmente
$originalDir = getcwd();
chdir(__DIR__ . '/..');
include_once 'api/webhook.php';
// Restaurar directorio
chdir($originalDir);
}
if (!class_exists('WhatsAppWebhook')) {
throw new Exception("Clase WhatsAppWebhook no existe");
}
}
return "Clase WhatsAppWebhook disponible";
});
$this->serviceTest("Inicialización WhatsAppWebhook", function() {
$webhook = new WhatsAppWebhook();
if (!$webhook) {
throw new Exception("No se pudo crear instancia de WhatsAppWebhook");
}
return "WhatsAppWebhook inicializado correctamente";
});
$this->serviceTest("Métodos requeridos en webhook", function() {
$webhook = new WhatsAppWebhook();
$reflection = new ReflectionClass($webhook);
$requiredMethods = ['handleRequest'];
foreach ($requiredMethods as $method) {
if (!$reflection->hasMethod($method)) {
throw new Exception("Método requerido '$method' no existe en WhatsAppWebhook");
}
}
return "Webhook tiene todos los métodos requeridos";
});
$this->serviceTest("Token de verificación webhook", function() {
if (!defined('WEBHOOK_VERIFY_TOKEN') || empty(WEBHOOK_VERIFY_TOKEN)) {
throw new Exception("WEBHOOK_VERIFY_TOKEN no está definido");
}
if (WEBHOOK_VERIFY_TOKEN === 'mi_token_secreto_123') {
return "⚠️ Usando token por defecto - cambiar en producción";
}
return "Token de webhook configurado correctamente";
});
}
private function serviceTest($name, $callable) {
try {
$result = $callable();
echo "<div class='alert alert-success'><i class='fas fa-check-circle'></i> <strong>$name:</strong> $result</div>";
} catch (Exception $e) {
echo "<div class='alert alert-danger'><i class='fas fa-times-circle'></i> <strong>$name:</strong> " . $e->getMessage() . "</div>";
}
}
}
// No ejecutar si es incluido por el runner principal
if (basename($_SERVER['PHP_SELF']) === 'service_tests.php') {
$configPath = __DIR__ . '/../config/config.php';
if (file_exists($configPath)) {
require_once $configPath;
}
$suite = new ServiceTestSuite();
$suite->runAllServiceTests();
}
?>
-692
View File
@@ -1,692 +0,0 @@
<?php
/**
* Tests específicos para el Sistema de Plantillas
* Fecha: 3 de enero de 2026
*/
class TemplateTestSuite {
private $db;
private $testTemplateIds = [];
public function __construct() {
$this->db = Database::getInstance();
}
public function runAllTemplateTests() {
echo "<h2>📝 Tests Exhaustivos de Sistema de Plantillas</h2>";
$this->testTemplateDatabase();
$this->testTemplateAPIs();
$this->testTemplateValidation();
$this->testTemplateIntegration();
$this->testTemplateWorkflow();
// Limpiar plantillas de prueba
$this->cleanup();
}
private function testTemplateDatabase() {
echo "<h3>💾 Tests de Base de Datos de Plantillas</h3>";
$this->templateTest("Estructura tabla message_templates", function() {
$columns = $this->db->fetchAll("DESCRIBE message_templates");
$requiredColumns = [
'id', 'name', 'template_name', 'language_code',
'category', 'status', 'created_at'
];
$existingColumns = array_column($columns, 'Field');
foreach ($requiredColumns as $col) {
if (!in_array($col, $existingColumns)) {
throw new Exception("Columna requerida '$col' faltante");
}
}
// Verificar tipos de datos
$statusColumn = array_filter($columns, function($col) {
return $col['Field'] === 'status';
});
$statusColumn = array_values($statusColumn)[0];
if (strpos($statusColumn['Type'], 'enum') === false) {
throw new Exception("Columna status debería ser ENUM");
}
if (strpos($statusColumn['Type'], 'pending') === false ||
strpos($statusColumn['Type'], 'approved') === false ||
strpos($statusColumn['Type'], 'rejected') === false) {
throw new Exception("Estados de plantilla incorrectos en ENUM");
}
return "Estructura de tabla message_templates correcta";
});
$this->templateTest("CRUD básico en message_templates", function() {
$testName = 'Test CRUD Template ' . time();
// CREATE
$id = $this->db->insert('message_templates', [
'name' => $testName,
'template_name' => 'test_crud_' . time(),
'language_code' => 'es',
'category' => 'utility',
'status' => 'pending'
]);
if (!$id) {
throw new Exception("No se pudo insertar plantilla");
}
$this->testTemplateIds[] = $id;
// READ
$template = $this->db->fetch(
"SELECT * FROM message_templates WHERE id = :id",
['id' => $id]
);
if ($template['name'] !== $testName) {
throw new Exception("Datos incorrectos al leer plantilla");
}
// UPDATE
$updated = $this->db->update('message_templates',
['status' => 'approved'],
['id' => $id]
);
if (!$updated) {
throw new Exception("No se pudo actualizar plantilla");
}
// Verificar actualización
$updatedTemplate = $this->db->fetch(
"SELECT status FROM message_templates WHERE id = :id",
['id' => $id]
);
if ($updatedTemplate['status'] !== 'approved') {
throw new Exception("Estado no se actualizó correctamente");
}
// DELETE se hace en cleanup()
return "CRUD completo en message_templates funciona";
});
$this->templateTest("Constrains y validaciones", function() {
// Test unicidad de nombre
$duplicateName = 'Unique Test ' . time();
$id1 = $this->db->insert('message_templates', [
'name' => $duplicateName,
'template_name' => 'unique_test_1',
'language_code' => 'es',
'status' => 'pending'
]);
$this->testTemplateIds[] = $id1;
// Intentar insertar duplicado
try {
$id2 = $this->db->insert('message_templates', [
'name' => $duplicateName,
'template_name' => 'unique_test_2',
'language_code' => 'es',
'status' => 'pending'
]);
if ($id2) {
$this->testTemplateIds[] = $id2;
throw new Exception("Se permitió nombre duplicado");
}
} catch (Exception $e) {
// Error esperado por constraint
if (strpos($e->getMessage(), 'Duplicate') !== false ||
strpos($e->getMessage(), 'UNIQUE') !== false) {
return "Constraint de unicidad funciona correctamente";
}
throw $e;
}
return "Validaciones de base de datos funcionan";
});
}
private function testTemplateAPIs() {
echo "<h3>🔌 Tests de APIs de Plantillas</h3>";
// Configurar sesión de admin
$_SESSION['admin_logged_in'] = true;
$_SESSION['last_activity'] = time();
$this->templateTest("API get_templates.php", function() {
ob_start();
include 'api/get_templates.php';
$output = ob_get_clean();
$templates = json_decode($output, true);
if (!is_array($templates)) {
throw new Exception("get_templates debe retornar array JSON");
}
return "API get_templates responde correctamente";
});
$this->templateTest("API save_template.php - plantilla válida", function() {
$testData = [
'name' => 'API Test Template ' . time(),
'template_name' => 'api_test_' . time(),
'language_code' => 'es',
'category' => 'utility'
];
$_POST = $testData;
$GLOBALS['HTTP_RAW_POST_DATA'] = json_encode($testData);
$_SERVER['REQUEST_METHOD'] = 'POST';
ob_start();
try {
include 'api/save_template.php';
$output = ob_get_clean();
$result = json_decode($output, true);
if (!$result['success']) {
throw new Exception("Error creando plantilla: " . ($result['error'] ?? 'Unknown'));
}
// Verificar que se creó en BD
$created = $this->db->fetch(
"SELECT * FROM message_templates WHERE name = :name",
['name' => $testData['name']]
);
if (!$created) {
throw new Exception("Plantilla no se guardó en base de datos");
}
$this->testTemplateIds[] = $created['id'];
return "API save_template crea plantilla correctamente";
} finally {
unset($_POST);
unset($GLOBALS['HTTP_RAW_POST_DATA']);
ob_end_clean();
}
});
$this->templateTest("API save_template.php - validaciones", function() {
$invalidData = [
['name' => '', 'template_name' => 'test', 'error_expected' => 'nombre es requerido'],
['name' => 'Test', 'template_name' => '', 'error_expected' => 'nombre de la plantilla'],
[/* datos vacíos */ 'error_expected' => 'inválidos']
];
foreach ($invalidData as $testCase) {
$_POST = $testCase;
unset($testCase['error_expected']);
$GLOBALS['HTTP_RAW_POST_DATA'] = json_encode($testCase);
$_SERVER['REQUEST_METHOD'] = 'POST';
ob_start();
try {
include 'api/save_template.php';
$output = ob_get_clean();
$result = json_decode($output, true);
if ($result['success']) {
throw new Exception("Debería rechazar datos inválidos");
}
} finally {
unset($_POST);
unset($GLOBALS['HTTP_RAW_POST_DATA']);
}
}
return "API save_template valida datos correctamente";
});
$this->templateTest("API update_template_status.php", function() {
// Crear plantilla para probar
$id = $this->db->insert('message_templates', [
'name' => 'Status Update Test',
'template_name' => 'status_update_test',
'language_code' => 'es',
'status' => 'pending'
]);
$this->testTemplateIds[] = $id;
$updateData = [
'id' => $id,
'status' => 'approved'
];
$_POST = $updateData;
$GLOBALS['HTTP_RAW_POST_DATA'] = json_encode($updateData);
$_SERVER['REQUEST_METHOD'] = 'POST';
ob_start();
try {
include 'api/update_template_status.php';
$output = ob_get_clean();
$result = json_decode($output, true);
if (!$result['success']) {
throw new Exception("Error actualizando estado: " . ($result['error'] ?? 'Unknown'));
}
// Verificar actualización en BD
$updated = $this->db->fetch(
"SELECT status FROM message_templates WHERE id = :id",
['id' => $id]
);
if ($updated['status'] !== 'approved') {
throw new Exception("Estado no se actualizó en base de datos");
}
return "API update_template_status funciona correctamente";
} finally {
unset($_POST);
unset($GLOBALS['HTTP_RAW_POST_DATA']);
}
});
}
private function testTemplateValidation() {
echo "<h3"> Tests de Validación de Plantillas</h3>";
$this->templateTest("Validación de nombres", function() {
$invalidNames = [
'', // Vacío
str_repeat('a', 200), // Muy largo
'<script>alert(1)</script>', // XSS
'SELECT * FROM users', // SQL-like
'template with \x00 null byte'
];
foreach ($invalidNames as $name) {
try {
$id = $this->db->insert('message_templates', [
'name' => $name,
'template_name' => 'test_validation_' . time(),
'language_code' => 'es',
'status' => 'pending'
]);
if ($id) {
$this->testTemplateIds[] = $id;
// Verificar que se sanitizó o rechazó apropiadamente
$saved = $this->db->fetch(
"SELECT name FROM message_templates WHERE id = :id",
['id' => $id]
);
if ($saved['name'] === $name && (
strpos($name, '<script>') !== false ||
strlen($name) > 100 ||
empty($name)
)) {
throw new Exception("Nombre inválido '$name' no fue filtrado");
}
}
} catch (Exception $e) {
// Error esperado para nombres inválidos
continue;
}
}
return "Validación de nombres de plantillas funciona";
});
$this->templateTest("Validación de estados", function() {
$id = $this->db->insert('message_templates', [
'name' => 'State Test',
'template_name' => 'state_test',
'language_code' => 'es',
'status' => 'pending'
]);
$this->testTemplateIds[] = $id;
// Estados válidos
$validStates = ['pending', 'approved', 'rejected'];
foreach ($validStates as $state) {
$updated = $this->db->update('message_templates',
['status' => $state],
['id' => $id]
);
if (!$updated) {
throw new Exception("No se pudo actualizar a estado válido: $state");
}
}
// Estado inválido
try {
$this->db->update('message_templates',
['status' => 'invalid_status'],
['id' => $id]
);
throw new Exception("Estado inválido fue aceptado");
} catch (Exception $e) {
// Error esperado por constraint ENUM
if (strpos($e->getMessage(), 'invalid_status') === false) {
throw $e;
}
}
return "Validación de estados funciona correctamente";
});
$this->templateTest("Validación de idiomas", function() {
$validLanguages = ['es', 'en', 'pt', 'fr'];
$invalidLanguages = ['', 'español', 'eng', 'xyz123'];
// Idiomas válidos
foreach ($validLanguages as $lang) {
$id = $this->db->insert('message_templates', [
'name' => "Lang Test $lang",
'template_name' => "lang_test_$lang",
'language_code' => $lang,
'status' => 'pending'
]);
if (!$id) {
throw new Exception("Idioma válido '$lang' rechazado");
}
$this->testTemplateIds[] = $id;
}
// Idiomas inválidos (dependiendo de validación implementada)
foreach ($invalidLanguages as $lang) {
try {
$id = $this->db->insert('message_templates', [
'name' => "Invalid Lang Test $lang",
'template_name' => "invalid_lang_$lang",
'language_code' => $lang,
'status' => 'pending'
]);
if ($id) {
$this->testTemplateIds[] = $id;
// Por ahora, idiomas inválidos pueden ser aceptados
// En futuras versiones se puede agregar validación más estricta
}
} catch (Exception $e) {
// Error esperado para idiomas inválidos
continue;
}
}
return "Validación de idiomas (básica implementada)";
});
}
private function testTemplateIntegration() {
echo "<h3">🔗 Tests de Integración de Plantillas</h3>";
$this->templateTest("Integración con WhatsAppService", function() {
// Crear plantilla aprobada
$id = $this->db->insert('message_templates', [
'name' => 'Integration Test',
'template_name' => 'integration_test',
'language_code' => 'es',
'category' => 'utility',
'status' => 'approved'
]);
$this->testTemplateIds[] = $id;
$whatsappService = new WhatsAppService();
// Verificar que el método sendTemplateMessage existe y acepta los parámetros
$reflection = new ReflectionClass($whatsappService);
if (!$reflection->hasMethod('sendTemplateMessage')) {
throw new Exception("WhatsAppService no tiene método sendTemplateMessage");
}
$method = $reflection->getMethod('sendTemplateMessage');
$params = $method->getParameters();
if (count($params) < 2) {
throw new Exception("sendTemplateMessage debe tener al menos 2 parámetros");
}
// Test estructura de llamada (sin envío real)
try {
// Este test no envía realmente, solo verifica estructura
$result = $whatsappService->sendTemplateMessage(
'+573001234567',
'integration_test',
'es',
[]
);
// En un entorno real, esto fallaría por token/conectividad
// Pero la estructura debería estar correcta
return "Integración WhatsAppService estructura correcta";
} catch (Exception $e) {
// Error esperado por falta de conectividad real
if (strpos($e->getMessage(), 'cURL') !== false ||
strpos($e->getMessage(), 'connection') !== false ||
strpos($e->getMessage(), 'token') !== false) {
return "Integración WhatsAppService - estructura OK, falla conectividad (esperado)";
}
throw $e;
}
});
$this->templateTest("Filtrado de plantillas por estado", function() {
// Crear plantillas con diferentes estados
$states = ['pending', 'approved', 'rejected'];
$templateIds = [];
foreach ($states as $state) {
$id = $this->db->insert('message_templates', [
'name' => "Filter Test $state",
'template_name' => "filter_test_$state",
'language_code' => 'es',
'status' => $state
]);
$templateIds[] = $id;
$this->testTemplateIds[] = $id;
}
// Test filtrar solo aprobadas
$approved = $this->db->fetchAll(
"SELECT * FROM message_templates WHERE status = 'approved' AND name LIKE 'Filter Test%'"
);
if (count($approved) !== 1) {
throw new Exception("Filtro de plantillas aprobadas no funciona");
}
// Test filtrar todas
$all = $this->db->fetchAll(
"SELECT * FROM message_templates WHERE name LIKE 'Filter Test%'"
);
if (count($all) !== 3) {
throw new Exception("No se crearon todas las plantillas de prueba");
}
return "Filtrado de plantillas por estado funciona";
});
}
private function testTemplateWorkflow() {
echo "<h3">🔄 Tests de Flujo de Trabajo de Plantillas</h3>";
$this->templateTest("Ciclo completo: Crear Aprobar Usar", function() {
// 1. Crear plantilla
$templateName = 'Workflow Test ' . time();
$whatsappName = 'workflow_test_' . time();
$id = $this->db->insert('message_templates', [
'name' => $templateName,
'template_name' => $whatsappName,
'language_code' => 'es',
'category' => 'utility',
'status' => 'pending'
]);
if (!$id) {
throw new Exception("No se pudo crear plantilla");
}
$this->testTemplateIds[] = $id;
// 2. Verificar estado inicial
$template = $this->db->fetch(
"SELECT * FROM message_templates WHERE id = :id",
['id' => $id]
);
if ($template['status'] !== 'pending') {
throw new Exception("Estado inicial debería ser 'pending'");
}
// 3. Aprobar plantilla
$updated = $this->db->update('message_templates',
['status' => 'approved'],
['id' => $id]
);
if (!$updated) {
throw new Exception("No se pudo aprobar plantilla");
}
// 4. Verificar que ahora está disponible para uso
$approvedTemplate = $this->db->fetch(
"SELECT * FROM message_templates WHERE id = :id AND status = 'approved'",
['id' => $id]
);
if (!$approvedTemplate) {
throw new Exception("Plantilla aprobada no está disponible");
}
// 5. Simular uso en envío de mensaje
$_SESSION['admin_logged_in'] = true;
$messageData = [
'recipient' => '+573001234567',
'type' => 'template',
'template' => $whatsappName
];
$_POST = $messageData;
$GLOBALS['HTTP_RAW_POST_DATA'] = json_encode($messageData);
$_SERVER['REQUEST_METHOD'] = 'POST';
ob_start();
try {
include 'api/send_message.php';
$output = ob_get_clean();
$result = json_decode($output, true);
// En testing, probablemente falle por conectividad, pero la validación debería pasar
if (isset($result['error']) && strpos($result['error'], $whatsappName) === false) {
// Si no menciona la plantilla en el error, es porque la validación pasó
return "Ciclo completo de plantilla: Crear Aprobar Validar uso";
}
if ($result['success']) {
return "Ciclo completo de plantilla exitoso (conexión real funcionando)";
}
return "Ciclo de plantilla completado - falla esperada en envío real";
} finally {
unset($_POST);
unset($GLOBALS['HTTP_RAW_POST_DATA']);
ob_end_clean();
}
});
$this->templateTest("Rechazo de plantillas no aprobadas", function() {
// Crear plantilla pendiente
$id = $this->db->insert('message_templates', [
'name' => 'Pending Test',
'template_name' => 'pending_test',
'language_code' => 'es',
'status' => 'pending'
]);
$this->testTemplateIds[] = $id;
// Intentar usar plantilla no aprobada
$pendingTemplates = $this->db->fetchAll(
"SELECT * FROM message_templates WHERE status = 'approved' AND template_name = 'pending_test'"
);
if (!empty($pendingTemplates)) {
throw new Exception("Plantilla pendiente aparece como aprobada");
}
return "Plantillas no aprobadas correctamente excluidas";
});
}
private function templateTest($name, $callable) {
try {
$result = $callable();
echo "<div class='alert alert-success'><i class='fas fa-check'></i> <strong>$name:</strong> $result</div>";
} catch (Exception $e) {
echo "<div class='alert alert-danger'><i class='fas fa-times'></i> <strong>$name:</strong> " . $e->getMessage() . "</div>";
}
}
private function cleanup() {
foreach ($this->testTemplateIds as $id) {
try {
$this->db->execute("DELETE FROM message_templates WHERE id = :id", ['id' => $id]);
} catch (Exception $e) {
// Ignorar errores de limpieza
}
}
}
public function __destruct() {
$this->cleanup();
}
}
// No ejecutar si es incluido por el runner principal
if (basename($_SERVER['PHP_SELF']) === 'template_tests.php') {
$configPath = __DIR__ . '/../config/config.php';
if (file_exists($configPath)) {
require_once $configPath;
}
$suite = new TemplateTestSuite();
$suite->runAllTemplateTests();
}
?>
-654
View File
@@ -1,654 +0,0 @@
<?php
/**
* Tests específicos para Webhook de WhatsApp
* Fecha: 3 de enero de 2026
*/
class WebhookTestSuite {
private $db;
private $originalServer;
private $originalGet;
private $originalPost;
public function __construct() {
$this->db = Database::getInstance();
$this->backupGlobals();
}
public function runAllWebhookTests() {
echo "<h2>🔗 Tests Exhaustivos de Webhook</h2>";
$this->testWebhookVerification();
$this->testWebhookMessageProcessing();
$this->testWebhookSecurity();
$this->testWebhookLogging();
$this->testWebhookErrorHandling();
$this->restoreGlobals();
}
private function backupGlobals() {
$this->originalServer = $_SERVER ?? [];
$this->originalGet = $_GET ?? [];
$this->originalPost = $_POST ?? [];
}
private function restoreGlobals() {
$_SERVER = $this->originalServer;
$_GET = $this->originalGet;
$_POST = $this->originalPost;
}
private function testWebhookVerification() {
echo "<h3>✅ Tests de Verificación de Webhook</h3>";
$this->webhookTest("Verificación exitosa con token correcto", function() {
// Configurar entorno para verificación
$_SERVER['REQUEST_METHOD'] = 'GET';
$_GET = [
'hub_mode' => 'subscribe',
'hub_verify_token' => WEBHOOK_VERIFY_TOKEN,
'hub_challenge' => 'test_challenge_123'
];
ob_start();
try {
$webhook = new WhatsAppWebhook();
$reflection = new ReflectionClass($webhook);
$method = $reflection->getMethod('verifyWebhook');
$method->setAccessible(true);
$method->invoke($webhook);
$output = ob_get_clean();
if ($output !== 'test_challenge_123') {
throw new Exception("Challenge no retornado correctamente: '$output'");
}
return "Verificación exitosa retorna challenge";
} catch (Exception $e) {
ob_end_clean();
throw $e;
}
});
$this->webhookTest("Rechazo con token incorrecto", function() {
$_SERVER['REQUEST_METHOD'] = 'GET';
$_GET = [
'hub_mode' => 'subscribe',
'hub_verify_token' => 'token_incorrecto',
'hub_challenge' => 'test_challenge_123'
];
ob_start();
try {
$webhook = new WhatsAppWebhook();
$reflection = new ReflectionClass($webhook);
$method = $reflection->getMethod('verifyWebhook');
$method->setAccessible(true);
$method->invoke($webhook);
$output = ob_get_clean();
// Debería devolver error, no el challenge
if ($output === 'test_challenge_123') {
throw new Exception("Token incorrecto fue aceptado");
}
return "Token incorrecto rechazado correctamente";
} catch (Exception $e) {
ob_end_clean();
// Si hay excepción, está bien (debería rechazar)
if (strpos($e->getMessage(), 'Token de verificación inválido') !== false) {
return "Token incorrecto rechazado con mensaje apropiado";
}
throw $e;
}
});
$this->webhookTest("Validación de parámetros requeridos", function() {
$_SERVER['REQUEST_METHOD'] = 'GET';
$_GET = [
'hub_mode' => 'subscribe',
// Falta hub_verify_token
'hub_challenge' => 'test_challenge_123'
];
ob_start();
try {
$webhook = new WhatsAppWebhook();
$reflection = new ReflectionClass($webhook);
$method = $reflection->getMethod('verifyWebhook');
$method->setAccessible(true);
$method->invoke($webhook);
$output = ob_get_clean();
if ($output === 'test_challenge_123') {
throw new Exception("Parámetros faltantes fueron aceptados");
}
return "Parámetros faltantes rechazados correctamente";
} catch (Exception $e) {
ob_end_clean();
return "Parámetros faltantes rechazados (excepción esperada)";
}
});
}
private function testWebhookMessageProcessing() {
echo "<h3>📨 Tests de Procesamiento de Mensajes</h3>";
$this->webhookTest("Estructura de payload de WhatsApp", function() {
$validPayload = [
'object' => 'whatsapp_business_account',
'entry' => [
[
'id' => '123456789',
'changes' => [
[
'value' => [
'messaging_product' => 'whatsapp',
'metadata' => [
'display_phone_number' => '573001234567',
'phone_number_id' => WHATSAPP_PHONE_NUMBER_ID
],
'conversations' => [
[
'from' => '573009876543',
'id' => 'wamid.test123',
'timestamp' => time(),
'text' => [
'body' => 'Hola, mensaje de prueba'
],
'type' => 'text'
]
]
],
'field' => 'conversations'
]
]
]
]
];
// Verificar estructura básica
if (!isset($validPayload['object']) || $validPayload['object'] !== 'whatsapp_business_account') {
throw new Exception("Estructura de payload incorrecta");
}
if (!isset($validPayload['entry'][0]['changes'][0]['value']['conversations'])) {
throw new Exception("Estructura de mensajes incorrecta");
}
return "Estructura de payload de WhatsApp es válida";
});
$this->webhookTest("Procesamiento de mensaje de texto", function() {
$testPhone = '573009999999';
// Limpiar usuario de prueba si existe
$this->db->execute("DELETE FROM users WHERE phone_number = :phone", ['phone' => $testPhone]);
$_SERVER['REQUEST_METHOD'] = 'POST';
$payload = [
'object' => 'whatsapp_business_account',
'entry' => [
[
'changes' => [
[
'value' => [
'conversations' => [
[
'from' => $testPhone,
'id' => 'test_message_id',
'timestamp' => time(),
'text' => ['body' => 'hola'],
'type' => 'text'
]
]
]
]
]
]
]
];
// Simular php://input
$GLOBALS['HTTP_RAW_POST_DATA'] = json_encode($payload);
ob_start();
try {
$webhook = new WhatsAppWebhook();
$reflection = new ReflectionClass($webhook);
$method = $reflection->getMethod('processIncomingMessage');
$method->setAccessible(true);
$method->invoke($webhook);
$output = ob_get_clean();
// Verificar que el usuario se creó
$user = $this->db->fetch(
"SELECT * FROM users WHERE phone_number = :phone",
['phone' => $testPhone]
);
if (!$user) {
throw new Exception("Usuario no fue creado automáticamente");
}
// Limpiar
$this->db->execute("DELETE FROM users WHERE id = :id", ['id' => $user['id']]);
return "Mensaje de texto procesado y usuario creado";
} catch (Exception $e) {
ob_end_clean();
throw $e;
} finally {
unset($GLOBALS['HTTP_RAW_POST_DATA']);
}
});
$this->webhookTest("Guardado de conversación", function() {
$testPhone = '573008888888';
// Crear usuario de prueba
$userId = $this->db->insert('users', [
'phone_number' => $testPhone,
'name' => 'Test Conversation User',
'status' => 'active'
]);
// Simular guardado de conversación
$webhook = new WhatsAppWebhook();
$reflection = new ReflectionClass($webhook);
$method = $reflection->getMethod('saveMessage');
$method->setAccessible(true);
$messageData = [
'user_id' => $userId,
'message_id' => 'test_msg_123',
'direction' => 'incoming',
'message_type' => 'text',
'content' => 'Mensaje de prueba',
'status' => 'received',
'created_at' => date('Y-m-d H:i:s')
];
$conversationId = $method->invokeArgs($webhook, [$messageData]);
if (!$conversationId) {
throw new Exception("Conversación no se guardó");
}
// Verificar que se guardó correctamente
$conversation = $this->db->fetch(
"SELECT * FROM conversations WHERE id = :id",
['id' => $conversationId]
);
if ($conversation['content'] !== 'Mensaje de prueba') {
throw new Exception("Contenido de conversación incorrecto");
}
// Limpiar
$this->db->execute("DELETE FROM conversations WHERE id = :id", ['id' => $conversationId]);
$this->db->execute("DELETE FROM users WHERE id = :id", ['id' => $userId]);
return "Conversación guardada correctamente";
});
$this->webhookTest("Procesamiento de reacción entrante", function() {
$testPhone = '573007777000';
// Crear usuario y mensaje referenciado
$userId = $this->db->insert('users', ['phone_number' => $testPhone, 'name' => 'Reactor', 'status' => 'active']);
$origMsgId = '1001';
$this->db->insert('conversations', [
'user_id' => $userId,
'message_id' => $origMsgId,
'direction' => 'incoming',
'message_type' => 'text',
'content' => 'Original msg',
'status' => 'received',
'created_at' => date('Y-m-d H:i:s')
]);
$payload = [
'object' => 'whatsapp_business_account',
'entry' => [
[
'changes' => [
[
'field' => 'conversations',
'value' => [
'conversations' => [
[
'from' => $testPhone,
'id' => '2001',
'timestamp' => time(),
'type' => 'reaction',
'reaction' => ['message_id' => $origMsgId, 'emoji' => '👍']
]
]
]
]
]
]
]
];
$webhook = new WhatsAppWebhook();
$result = $webhook->processPayload($payload);
$conv = $this->db->fetch("SELECT * FROM conversations WHERE message_id = :mid", ['mid' => '2001']);
// Limpiar
$this->db->execute("DELETE FROM conversations WHERE message_id = :mid", ['mid' => '2001']);
$this->db->execute("DELETE FROM conversations WHERE message_id = :mid", ['mid' => $origMsgId]);
$this->db->execute("DELETE FROM users WHERE id = :id", ['id' => $userId]);
if (!$conv) throw new Exception("Reacción no se guardó");
if (intval($conv['reaction_to_message_id']) !== intval($origMsgId)) throw new Exception("reaction_to_message_id no guardado correctamente");
if ($conv['reaction_emoji'] !== '👍') throw new Exception("reaction_emoji incorrecto");
return "Reacción procesada y guardada correctamente";
});
$this->webhookTest("Procesamiento de reply/context entrante", function() {
$testPhone = '573007777111';
$userId = $this->db->insert('users', ['phone_number' => $testPhone, 'name' => 'Replier', 'status' => 'active']);
$origMsgId = '1002';
$this->db->insert('conversations', [
'user_id' => $userId,
'message_id' => $origMsgId,
'direction' => 'incoming',
'message_type' => 'text',
'content' => 'Original for reply',
'status' => 'received',
'created_at' => date('Y-m-d H:i:s')
]);
$payload = [
'object' => 'whatsapp_business_account',
'entry' => [
[
'changes' => [
[
'field' => 'conversations',
'value' => [
'conversations' => [
[
'from' => $testPhone,
'id' => '2002',
'timestamp' => time(),
'type' => 'text',
'text' => ['body' => 'Reply message'],
'context' => ['id' => $origMsgId]
]
]
]
]
]
]
]
];
$webhook = new WhatsAppWebhook();
$result = $webhook->processPayload($payload);
$conv = $this->db->fetch("SELECT * FROM conversations WHERE message_id = :mid", ['mid' => '2002']);
// Limpiar
$this->db->execute("DELETE FROM conversations WHERE message_id = :mid", ['mid' => '2002']);
$this->db->execute("DELETE FROM conversations WHERE message_id = :mid", ['mid' => $origMsgId]);
$this->db->execute("DELETE FROM users WHERE id = :id", ['id' => $userId]);
if (!$conv) throw new Exception("Reply no se guardó");
if ($conv['reply_to_message_id'] != $origMsgId) throw new Exception("reply_to_message_id no guardado correctamente");
return "Reply/context procesado y guardado correctamente";
});
}
private function testWebhookSecurity() {
echo "<h3>🔒 Tests de Seguridad de Webhook</h3>";
$this->webhookTest("Validación de origen de petición", function() {
// Test con User-Agent no esperado
$_SERVER['HTTP_USER_AGENT'] = 'SuspiciousBot/1.0';
$_SERVER['REQUEST_METHOD'] = 'POST';
// En un webhook real, esto debería validarse
// Por ahora solo verificamos que el webhook no explote
return "Validación de User-Agent (implementación básica)";
});
$this->webhookTest("Resistencia a payloads malformados", function() {
$_SERVER['REQUEST_METHOD'] = 'POST';
// Payload inválido (JSON malformado)
$GLOBALS['HTTP_RAW_POST_DATA'] = '{"invalid": json malformed}';
ob_start();
try {
$webhook = new WhatsAppWebhook();
$reflection = new ReflectionClass($webhook);
$method = $reflection->getMethod('processIncomingMessage');
$method->setAccessible(true);
$method->invoke($webhook);
$output = ob_get_clean();
// No debería explotar, debería manejar el error
return "Payload malformado manejado sin errores críticos";
} catch (Exception $e) {
ob_end_clean();
// Si maneja la excepción apropiadamente, está bien
return "Payload malformado rechazado apropiadamente";
} finally {
unset($GLOBALS['HTTP_RAW_POST_DATA']);
}
});
$this->webhookTest("Prevención de inyección SQL", function() {
$maliciousPhone = "'; DROP TABLE users; --";
// Intentar crear usuario con payload malicioso
try {
$webhook = new WhatsAppWebhook();
$reflection = new ReflectionClass($webhook);
$method = $reflection->getMethod('createUser');
$method->setAccessible(true);
// Esto debería usar parámetros preparados y no ser vulnerable
$result = $method->invokeArgs($webhook, [$maliciousPhone]);
// Verificar que la tabla users sigue existiendo
$tablesCheck = $this->db->fetchAll("SHOW TABLES LIKE 'users'");
if (empty($tablesCheck)) {
throw new Exception("Posible inyección SQL - tabla eliminada");
}
// Limpiar si se creó algo
$this->db->execute(
"DELETE FROM users WHERE phone_number = :phone",
['phone' => $maliciousPhone]
);
return "Resistente a inyección SQL básica";
} catch (Exception $e) {
// Si hay error en la inserción por caracteres inválidos, está bien
return "Inyección SQL prevenida (error esperado en inserción)";
}
});
}
private function testWebhookLogging() {
echo "<h3>📋 Tests de Logging de Webhook</h3>";
$this->webhookTest("Verificar tabla webhook_logs", function() {
$tables = $this->db->fetchAll("SHOW TABLES LIKE 'webhook_logs'");
if (empty($tables)) {
throw new Exception("Tabla webhook_logs no existe");
}
// Verificar estructura
$columns = $this->db->fetchAll("DESCRIBE webhook_logs");
$requiredColumns = ['id', 'request_body', 'response_body', 'status_code', 'ip_address', 'created_at'];
$existingColumns = array_column($columns, 'Field');
foreach ($requiredColumns as $col) {
if (!in_array($col, $existingColumns)) {
throw new Exception("Columna requerida '$col' faltante en webhook_logs");
}
}
return "Tabla webhook_logs existe con estructura correcta";
});
$this->webhookTest("Logging de webhooks", function() {
$webhook = new WhatsAppWebhook();
$reflection = new ReflectionClass($webhook);
if ($reflection->hasMethod('logWebhook')) {
$method = $reflection->getMethod('logWebhook');
$method->setAccessible(true);
$testRequest = '{"test": "request"}';
$testResponse = '{"test": "response"}';
$testStatus = 200;
$logId = $method->invokeArgs($webhook, [$testRequest, $testResponse, $testStatus]);
if ($logId) {
// Verificar que se guardó
$log = $this->db->fetch(
"SELECT * FROM webhook_logs WHERE id = :id",
['id' => $logId]
);
if ($log && $log['status_code'] == 200) {
// Limpiar
$this->db->execute("DELETE FROM webhook_logs WHERE id = :id", ['id' => $logId]);
return "Logging de webhook funciona correctamente";
}
}
}
return "Método de logging no existe o no funciona (feature opcional)";
});
}
private function testWebhookErrorHandling() {
echo "<h3>⚠️ Tests de Manejo de Errores</h3>";
$this->webhookTest("Manejo de payload vacío", function() {
$_SERVER['REQUEST_METHOD'] = 'POST';
$GLOBALS['HTTP_RAW_POST_DATA'] = '';
ob_start();
try {
$webhook = new WhatsAppWebhook();
$webhook->handleRequest();
$output = ob_get_clean();
// Debería manejar el payload vacío sin explotar
return "Payload vacío manejado sin errores críticos";
} catch (Exception $e) {
ob_end_clean();
return "Payload vacío genera excepción controlada";
} finally {
unset($GLOBALS['HTTP_RAW_POST_DATA']);
}
});
$this->webhookTest("Manejo de método HTTP inválido", function() {
$_SERVER['REQUEST_METHOD'] = 'DELETE';
ob_start();
try {
$webhook = new WhatsAppWebhook();
$webhook->handleRequest();
$output = ob_get_clean();
// Debería rechazar método inválido
return "Método HTTP inválido rechazado apropiadamente";
} catch (Exception $e) {
ob_end_clean();
return "Método inválido genera error controlado";
}
});
$this->webhookTest("Resistencia a concurrencia", function() {
// Test básico - verificar que no hay race conditions obvios
$testPhone = '573007777777';
// Simular múltiples mensajes del mismo usuario
for ($i = 0; $i < 3; $i++) {
try {
$webhook = new WhatsAppWebhook();
$reflection = new ReflectionClass($webhook);
$method = $reflection->getMethod('getUserByPhone');
$method->setAccessible(true);
$user = $method->invokeArgs($webhook, [$testPhone]);
// No debería explotar en llamadas concurrentes
} catch (Exception $e) {
// Errores esperados en concurrencia
}
}
return "Resistente a llamadas concurrentes básicas";
});
}
private function webhookTest($name, $callable) {
try {
$result = $callable();
echo "<div class='alert alert-success'><i class='fas fa-check'></i> <strong>$name:</strong> $result</div>";
} catch (Exception $e) {
echo "<div class='alert alert-danger'><i class='fas fa-times'></i> <strong>$name:</strong> " . $e->getMessage() . "</div>";
}
}
}
// No ejecutar si es incluido por el runner principal
if (basename($_SERVER['PHP_SELF']) === 'webhook_tests.php' || php_sapi_name() === 'cli') {
$configPath = __DIR__ . '/../config/config.php';
if (file_exists($configPath)) {
require_once $configPath;
}
// Asegurar que la clase WhatsAppWebhook esté disponible cuando se ejecutan tests desde CLI
$webhookPath = __DIR__ . '/../api/webhook.php';
if (file_exists($webhookPath)) {
require_once $webhookPath;
}
$suite = new WebhookTestSuite();
echo "RUNNING WEBHOOK TESTS\n";
$suite->runAllWebhookTests();
}
?>
Binary file not shown.
-86
View File
@@ -1,86 +0,0 @@
<?php
/**
* Tests para WhatsAppService saveOutgoingMessage
*/
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../services/WhatsAppService.php';
$db = Database::getInstance();
$service = new WhatsAppService();
$ref = new ReflectionClass($service);
$method = $ref->getMethod('saveOutgoingMessage');
$method->setAccessible(true);
// Test reaction save
$phone = '573001234999';
$db->execute('DELETE FROM users WHERE phone_number = :p', ['p' => $phone]);
$userId = $db->insert('users', ['phone_number' => $phone, 'name' => 'Outgoing Reactor', 'status' => 'active']);
$data = [
'to' => $phone,
'type' => 'reaction',
'reaction' => ['message_id' => '5001', 'emoji' => '❤️']
];
$response = ['conversations' => [['id' => 'out_msg_1']]];
$foundUser = $db->fetch('SELECT * FROM users WHERE phone_number = :p', ['p' => $phone]);
echo "User found for outgoing reaction? " . ($foundUser ? 'yes' : 'no') . "\n";
$method->invokeArgs($service, [$data, $response]);
$conv = $db->fetch('SELECT * FROM conversations WHERE message_id = :mid', ['mid' => 'out_msg_1']);
if ($conv) {
echo "Outgoing reaction saved: id={$conv['id']} reaction_to={$conv['reaction_to_message_id']} emoji={$conv['reaction_emoji']}\n";
} else {
echo "Outgoing reaction NOT saved\n";
$recent = $db->fetchAll('SELECT id,message_id,message_type,direction,content,created_at FROM conversations WHERE user_id = :uid ORDER BY id DESC LIMIT 5', ['uid' => $userId]);
echo "Recent rows for user:\n";
foreach ($recent as $r) echo json_encode($r) . "\n";
echo "Trying direct DB insert to see if constraints block it...\n";
$msg = [
'user_id' => $userId,
'message_id' => 'direct_out_msg_1',
'direction' => 'outgoing',
'message_type' => 'reaction',
'content' => '❤️',
'status' => 'sent',
'created_at' => date('Y-m-d H:i:s')
];
try {
$res = $db->insert('conversations', $msg);
echo "Direct insert result: " . ($res ? 'ok id=' . $res : 'failed') . "\n";
$r2 = $db->fetch('SELECT * FROM conversations WHERE message_id = :mid', ['mid' => 'direct_out_msg_1']);
echo "Fetched direct row: " . json_encode($r2) . "\n";
$db->execute('DELETE FROM conversations WHERE message_id = :mid', ['mid' => 'direct_out_msg_1']);
} catch (Exception $e) {
echo "Direct insert threw: " . $e->getMessage() . "\n";
}
}
// cleanup
$db->execute('DELETE FROM conversations WHERE message_id = :mid', ['mid' => 'out_msg_1']);
$db->execute('DELETE FROM users WHERE id = :id', ['id' => $userId]);
// Test text reply save
$phone2 = '573001234998';
$db->execute('DELETE FROM users WHERE phone_number = :p', ['p' => $phone2]);
$userId2 = $db->insert('users', ['phone_number' => $phone2, 'name' => 'Outgoing Replier', 'status' => 'active']);
$data2 = [
'to' => $phone2,
'type' => 'text',
'context' => ['message_id' => '5002'],
'text' => ['body' => 'Gracias']
];
$response2 = ['conversations' => [['id' => 'out_msg_2']]];
$method->invokeArgs($service, [$data2, $response2]);
$conv2 = $db->fetch('SELECT * FROM conversations WHERE message_id = :mid', ['mid' => 'out_msg_2']);
if ($conv2) {
echo "Outgoing reply saved: id={$conv2['id']} reply_to={$conv2['reply_to_message_id']}\n";
} else {
echo "Outgoing reply NOT saved\n";
}
// cleanup
$db->execute('DELETE FROM conversations WHERE message_id = :mid', ['mid' => 'out_msg_2']);
$db->execute('DELETE FROM users WHERE id = :id', ['id' => $userId2]);
echo "Done tests\n";
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
{"success":true,"message":"Mensaje simulado enviado correctamente (modo debug) - NOTA: En producci\u00f3n, usa plantillas para n\u00fameros que no han respondido en 24h","data":{"recipient":"573168950803","type":"template","timestamp":"2026-01-20 23:28:33","warning":"Para env\u00edo real, verifica que el usuario haya respondido en las \u00faltimas 24h o usa plantillas","template_name":"encuesta_satisfaccin","language":"es_MX","format":"whatsapp_standard"}}
-1
View File
@@ -1 +0,0 @@
{"success":true,"message":"Mensaje simulado enviado correctamente (modo debug) - NOTA: En producci\u00f3n, usa plantillas para n\u00fameros que no han respondido en 24h","data":{"recipient":"573168950803","type":"template","timestamp":"2026-01-20 23:15:31","warning":"Para env\u00edo real, verifica que el usuario haya respondido en las \u00faltimas 24h o usa plantillas","template_name":"encuesta_satisfaccin","language":"es_ES","format":"simple"}}
-251
View File
@@ -1,251 +0,0 @@
<?php
/**
* Tester de conectividad WhatsApp Business API
* Verifica que la configuración de WhatsApp esté funcionando
*/
require_once 'config/config.php';
header('Content-Type: text/html; charset=utf-8');
// Función para probar la conectividad con WhatsApp API
function testWhatsAppAPI() {
$results = [];
// Test 1: Validar configuración
$errors = validateWhatsAppConfig();
$results['config'] = [
'status' => empty($errors) ? 'success' : 'error',
'message' => empty($errors) ? 'Configuración válida' : implode(', ', $errors),
'details' => empty($errors) ? 'Todos los tokens están configurados' : 'Completa la configuración en .env o config.php'
];
if (!empty($errors)) {
return $results; // No continuar si la configuración es inválida
}
// Test 2: Probar conectividad básica
try {
$whatsappService = new WhatsAppService();
$results['connectivity'] = [
'status' => 'success',
'message' => 'Servicio de WhatsApp inicializado correctamente',
'details' => 'La clase WhatsAppService está funcionando'
];
} catch (Exception $e) {
$results['connectivity'] = [
'status' => 'error',
'message' => 'Error inicializando servicio: ' . $e->getMessage(),
'details' => 'Verifica la configuración y conexión a base de datos'
];
return $results;
}
// Test 3: Verificar Phone Number ID
try {
$url = WHATSAPP_API_URL . WHATSAPP_PHONE_NUMBER_ID;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . WHATSAPP_TOKEN,
'Content-Type: application/json'
],
CURLOPT_TIMEOUT => 10,
CURLOPT_CONNECTTIMEOUT => 5
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
$results['api_test'] = [
'status' => 'error',
'message' => 'Error de conectividad: ' . $error,
'details' => 'Problema de red o configuración SSL'
];
} elseif ($httpCode === 200) {
$results['api_test'] = [
'status' => 'success',
'message' => 'API de WhatsApp respondió correctamente',
'details' => 'Token y Phone ID válidos'
];
} elseif ($httpCode === 401) {
$results['api_test'] = [
'status' => 'error',
'message' => 'Token de acceso inválido o expirado',
'details' => 'Genera un nuevo token en Facebook for Developers'
];
} elseif ($httpCode === 404) {
$results['api_test'] = [
'status' => 'error',
'message' => 'Phone Number ID no encontrado',
'details' => 'Verifica el Phone Number ID en la configuración'
];
} else {
$results['api_test'] = [
'status' => 'warning',
'message' => "Respuesta inesperada: HTTP $httpCode",
'details' => 'La API respondió pero con un código inusual'
];
}
} catch (Exception $e) {
$results['api_test'] = [
'status' => 'error',
'message' => 'Error probando API: ' . $e->getMessage(),
'details' => 'Excepción durante la prueba de conectividad'
];
}
return $results;
}
$testResults = testWhatsAppAPI();
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test WhatsApp API - Bot Manager</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<style>
.test-item {
padding: 15px;
margin: 10px 0;
border-radius: 8px;
border: 1px solid #dee2e6;
}
.test-success { background-color: #d4edda; border-color: #c3e6cb; }
.test-warning { background-color: #fff3cd; border-color: #ffeaa7; }
.test-error { background-color: #f8d7da; border-color: #f5c6cb; }
</style>
</head>
<body>
<div class="container mt-5">
<div class="row">
<div class="col-md-8 offset-md-2">
<h1 class="text-center mb-4">
<i class="fab fa-whatsapp text-success"></i>
Test de Conectividad WhatsApp API
</h1>
<div class="card mb-4">
<div class="card-header">
<h5><i class="fas fa-info-circle"></i> Configuración Actual</h5>
</div>
<div class="card-body">
<table class="table table-sm">
<tr>
<td><strong>Token:</strong></td>
<td><?= WHATSAPP_TOKEN !== 'TU_TOKEN_DE_WHATSAPP_AQUI' ? '***CONFIGURADO***' : 'NO CONFIGURADO' ?></td>
</tr>
<tr>
<td><strong>Phone ID:</strong></td>
<td><?= WHATSAPP_PHONE_NUMBER_ID !== 'TU_PHONE_ID_AQUI' ? WHATSAPP_PHONE_NUMBER_ID : 'NO CONFIGURADO' ?></td>
</tr>
<tr>
<td><strong>Webhook Token:</strong></td>
<td><?= WEBHOOK_VERIFY_TOKEN ?></td>
</tr>
<tr>
<td><strong>API URL:</strong></td>
<td><?= WHATSAPP_API_URL ?></td>
</tr>
</table>
</div>
</div>
<?php foreach ($testResults as $testName => $test): ?>
<?php
$statusClass = 'test-' . $test['status'];
$iconClass = [
'success' => 'fa-check-circle text-success',
'error' => 'fa-times-circle text-danger',
'warning' => 'fa-exclamation-triangle text-warning'
][$test['status']];
$testTitles = [
'config' => 'Validación de Configuración',
'connectivity' => 'Inicialización del Servicio',
'api_test' => 'Conectividad con WhatsApp API'
];
?>
<div class="test-item <?= $statusClass ?>">
<h6><i class="fas <?= $iconClass ?>"></i> <?= $testTitles[$testName] ?? $testName ?></h6>
<p class="mb-1"><strong><?= $test['message'] ?></strong></p>
<small class="text-muted"><?= $test['details'] ?></small>
</div>
<?php endforeach; ?>
<div class="card mt-4">
<div class="card-header">
<h5><i class="fas fa-tools"></i> Próximos Pasos</h5>
</div>
<div class="card-body">
<?php
$allSuccess = array_reduce($testResults, function($carry, $test) {
return $carry && $test['status'] === 'success';
}, true);
?>
<?php if ($allSuccess): ?>
<div class="alert alert-success">
<h5><i class="fas fa-thumbs-up"></i> ¡Perfecto!</h5>
<p>Tu configuración de WhatsApp está funcionando correctamente.</p>
<p><strong>Ya puedes:</strong></p>
<ul>
<li>Configurar el webhook en Facebook for Developers</li>
<li>Empezar a recibir y enviar mensajes</li>
<li>Usar el panel de administración</li>
</ul>
</div>
<?php else: ?>
<div class="alert alert-warning">
<h5><i class="fas fa-wrench"></i> Configuración Requerida</h5>
<p>Necesitas completar la configuración antes de continuar:</p>
<ol>
<li>Obtén tu token de WhatsApp Business API desde <a href="https://developers.facebook.com/" target="_blank">Facebook for Developers</a></li>
<li>Configura los valores en tu archivo <code>.env</code> o <code>config.php</code></li>
<li>Vuelve a ejecutar esta prueba</li>
</ol>
</div>
<?php endif; ?>
<hr>
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<a href="system_validator.php" class="btn btn-secondary">
<i class="fas fa-check"></i> Validador General
</a>
<a href="quick_config.php" class="btn btn-primary">
<i class="fas fa-cog"></i> Configuración Rápida
</a>
<?php if ($allSuccess): ?>
<a href="index.php" class="btn btn-success">
<i class="fas fa-rocket"></i> Ir al Panel
</a>
<?php endif; ?>
</div>
</div>
</div>
<div class="text-center mt-4">
<small class="text-muted">
<i class="fas fa-sync-alt"></i>
<a href="<?= $_SERVER['REQUEST_URI'] ?>">Recargar página</a> para volver a probar
</small>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>