up
This commit is contained in:
+20
@@ -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
|
||||
@@ -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)
|
||||
# ==================================================
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
USUARIO: admin
|
||||
CONTRASEÑA: 2fa904eee46f
|
||||
FECHA: 2026-01-04 03:04:54
|
||||
@@ -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 U‑Site.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 **U‑Site.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.*
|
||||
@@ -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'] ?? '']);
|
||||
@@ -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()]);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
|
||||
@@ -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>";
|
||||
?>
|
||||
@@ -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
@@ -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";
|
||||
?>
|
||||
@@ -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";
|
||||
?>
|
||||
@@ -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>";
|
||||
?>
|
||||
@@ -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>
|
||||
@@ -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>";
|
||||
?>
|
||||
@@ -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>";
|
||||
?>
|
||||
@@ -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 " 📊 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>";
|
||||
?>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>";
|
||||
?>
|
||||
@@ -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>";
|
||||
?>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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]}
|
||||
|
||||
@@ -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';
|
||||
?>
|
||||
@@ -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';
|
||||
@@ -1,5 +0,0 @@
|
||||
<?php
|
||||
ini_set('display_errors',1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
require_once __DIR__ . '/../api/send_reply.php';
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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";
|
||||
@@ -0,0 +1 @@
|
||||
OK
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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";
|
||||
?>
|
||||
@@ -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.
@@ -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();
|
||||
}
|
||||
?>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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();
|
||||
?>
|
||||
@@ -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)-\'',
|
||||
'<script>alert(1)</script>'
|
||||
];
|
||||
|
||||
// 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.
@@ -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();
|
||||
}
|
||||
?>
|
||||
@@ -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();
|
||||
}
|
||||
?>
|
||||
@@ -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.
@@ -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 +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 +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"}}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user