up
This commit is contained in:
@@ -0,0 +1 @@
|
||||
2026-01-04 18:36:01
|
||||
@@ -0,0 +1,35 @@
|
||||
# Configuración de ejemplo para WhatsApp Bot Manager
|
||||
# Copia este archivo como .env y completa los valores
|
||||
|
||||
# Base de datos
|
||||
DB_HOST=77.37.126.126
|
||||
#DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_NAME=usite_whatsapp_bot
|
||||
DB_USER=usite_whatsapp_user
|
||||
DB_PASS=6q!Vio0fn@PFmsLyHjH5
|
||||
DB_CHARSET=utf8mb4
|
||||
|
||||
# WhatsApp Business API
|
||||
WHATSAPP_TOKEN=EAAcGjPRqunIBQSjpWACfqgK5U5qSuJMovEC12quhGbeq2Xp0ZAhSmqIGBtKeLSNGZAwjoNkUSqqhB9ymc10y89GjYZCzoIH2n2XFCZC78wXL11Pgwv6eLdoZCtZBIdxnQaIrRzGHOt1WZC9z3ZCZBXRVmpCRdSFl7cThQXAwC8UZBswBlOuD8zBR8yHvAT2luZBNZCZAPStMq0HYqJNX7cmmHJ9MpQ5rk94ovJeCBundNWOafHO6VeKOtx3MlLx5oi5CBkEbH36IKWfOVqF6BcuqGjZCKKE63q1euZB55wrNewY
|
||||
WHATSAPP_PHONE_NUMBER_ID=938064202726485
|
||||
WHATSAPP_API_URL=https://graph.facebook.com/v22.0/
|
||||
WEBHOOK_VERIFY_TOKEN=XimenaCaicedo2025*
|
||||
|
||||
# Configuración general
|
||||
APP_NAME=WhatsApp Bot System
|
||||
APP_VERSION=1.0.0
|
||||
APP_URL=https://whatsapp.u-s.app/
|
||||
TIMEZONE=America/Bogota
|
||||
|
||||
# Seguridad
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=contraseña_admin_segura
|
||||
SESSION_TIMEOUT=1800
|
||||
MAX_LOGIN_ATTEMPTS=3
|
||||
LOGIN_LOCKOUT_TIME=900
|
||||
|
||||
# Logs
|
||||
ENABLE_LOGGING=true
|
||||
LOG_LEVEL=INFO
|
||||
LOG_FILE=logs/system.log
|
||||
@@ -0,0 +1,34 @@
|
||||
# Configuración de ejemplo para WhatsApp Bot Manager
|
||||
# Copia este archivo como .env y completa los valores
|
||||
|
||||
# Base de datos
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_NAME=whatsapp_bot
|
||||
DB_USER=whatsapp_user
|
||||
DB_PASS=tu_contraseña_aqui
|
||||
DB_CHARSET=utf8mb4
|
||||
|
||||
# WhatsApp Business API
|
||||
WHATSAPP_TOKEN=tu_token_de_whatsapp_aqui
|
||||
WHATSAPP_PHONE_NUMBER_ID=tu_phone_id_aqui
|
||||
WHATSAPP_API_URL=https://graph.facebook.com/v22.0/
|
||||
WEBHOOK_VERIFY_TOKEN=tu_token_secreto_de_verificacion
|
||||
|
||||
# Configuración general
|
||||
APP_NAME=WhatsApp Bot System
|
||||
APP_VERSION=1.0.0
|
||||
APP_URL=https://tudominio.com
|
||||
TIMEZONE=America/Bogota
|
||||
|
||||
# Seguridad
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=contraseña_admin_segura
|
||||
SESSION_TIMEOUT=1800
|
||||
MAX_LOGIN_ATTEMPTS=3
|
||||
LOGIN_LOCKOUT_TIME=900
|
||||
|
||||
# Logs
|
||||
ENABLE_LOGGING=true
|
||||
LOG_LEVEL=INFO
|
||||
LOG_FILE=logs/system.log
|
||||
@@ -0,0 +1,207 @@
|
||||
# ==================================================
|
||||
# WhatsApp Bot Manager - HestiaCP Compatible .htaccess
|
||||
# Optimizado para hosting compartido y VPS con HestiaCP
|
||||
# ==================================================
|
||||
|
||||
# Configuración básica de seguridad
|
||||
ServerSignature Off
|
||||
|
||||
# Desactivar listado de directorios
|
||||
Options -Indexes
|
||||
|
||||
# Activar RewriteEngine
|
||||
RewriteEngine On
|
||||
|
||||
# ==================================================
|
||||
# PROTECCIÓN DE ARCHIVOS CRÍTICOS
|
||||
# ==================================================
|
||||
|
||||
# Proteger archivos de configuración (compatible con HestiaCP)
|
||||
<FilesMatch "^(config|database|\.env).*\.(php|ini|conf)$">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
|
||||
# Proteger archivos sensibles
|
||||
<FilesMatch "\.(log|sql|md|txt|json|lock)$">
|
||||
<RequireAll>
|
||||
Require ip 127.0.0.1
|
||||
Require ip ::1
|
||||
</RequireAll>
|
||||
</FilesMatch>
|
||||
|
||||
# Proteger archivos de instalación después de completada
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteCond %{REQUEST_FILENAME} install.*\.php$ [NC]
|
||||
RewriteCond %{REMOTE_ADDR} !^127\.0\.0\.1$
|
||||
RewriteCond %{REMOTE_ADDR} !^::1$
|
||||
RewriteRule .* - [F,L]
|
||||
</IfModule>
|
||||
|
||||
# Proteger archivos dot (ocultos)
|
||||
<FilesMatch "^\.|\.bak$|\.old$|\.orig$|\.save$|\.swp$|\.tmp$">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
|
||||
# ==================================================
|
||||
# PROTECCIÓN DE DIRECTORIOS
|
||||
# ==================================================
|
||||
|
||||
# Proteger directorio de logs
|
||||
<IfModule mod_alias.c>
|
||||
RedirectMatch 403 ^/logs/.*$
|
||||
</IfModule>
|
||||
|
||||
# Proteger directorio de tests (si existe)
|
||||
<IfModule mod_alias.c>
|
||||
RedirectMatch 403 ^/tests/.*$
|
||||
</IfModule>
|
||||
|
||||
# ==================================================
|
||||
# HEADERS DE SEGURIDAD (Compatible HestiaCP)
|
||||
# ==================================================
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
# Prevenir clickjacking
|
||||
Header always set X-Frame-Options "SAMEORIGIN"
|
||||
|
||||
# Prevenir XSS
|
||||
Header always set X-XSS-Protection "1; mode=block"
|
||||
|
||||
# Prevenir MIME sniffing
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
|
||||
# Política de referrer
|
||||
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||
|
||||
# Content Security Policy básica
|
||||
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' cdn.jsdelivr.net cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' cdn.jsdelivr.net cdnjs.cloudflare.com; font-src 'self' cdnjs.cloudflare.com; img-src 'self' data:; connect-src 'self';"
|
||||
|
||||
# Permissions Policy
|
||||
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), autoplay=(), fullscreen=()"
|
||||
</IfModule>
|
||||
|
||||
# ==================================================
|
||||
# OPTIMIZACIÓN DE RENDIMIENTO
|
||||
# ==================================================
|
||||
|
||||
# Compresión GZIP (compatible HestiaCP)
|
||||
<IfModule mod_deflate.c>
|
||||
<FilesMatch "\.(html|htm|php|css|js|xml|txt|json)$">
|
||||
SetOutputFilter DEFLATE
|
||||
</FilesMatch>
|
||||
|
||||
# Excluir archivos ya comprimidos
|
||||
<FilesMatch "\.(zip|gz|rar|7z|jpg|jpeg|png|gif|webp|mp4|mp3|pdf)$">
|
||||
SetEnv no-gzip
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
# Cache estático (HestiaCP compatible)
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive On
|
||||
|
||||
# Archivos CSS y JavaScript
|
||||
<FilesMatch "\.(css|js)$">
|
||||
ExpiresDefault "access plus 1 month"
|
||||
</FilesMatch>
|
||||
|
||||
# Imágenes
|
||||
<FilesMatch "\.(jpg|jpeg|png|gif|webp|svg|ico)$">
|
||||
ExpiresDefault "access plus 6 months"
|
||||
</FilesMatch>
|
||||
|
||||
# Fuentes
|
||||
<FilesMatch "\.(woff|woff2|ttf|eot)$">
|
||||
ExpiresDefault "access plus 1 year"
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
# ==================================================
|
||||
# REGLAS DE REWRITE (HestiaCP)
|
||||
# ==================================================
|
||||
|
||||
# Redirigir HTTP a HTTPS (descomentear en producción)
|
||||
# RewriteCond %{HTTPS} off
|
||||
# RewriteCond %{HTTP_HOST} !^localhost [NC]
|
||||
# RewriteCond %{HTTP_HOST} !^127\.0\.0\.1 [NC]
|
||||
# RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
|
||||
|
||||
# Redirigir www a no-www (opcional, descomentear si necesario)
|
||||
# RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
|
||||
# RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
|
||||
|
||||
# Página de inicio personalizada
|
||||
DirectoryIndex index.php index.html
|
||||
|
||||
# ==================================================
|
||||
# PROTECCIÓN CONTRA ATAQUES COMUNES
|
||||
# ==================================================
|
||||
|
||||
# Bloquear injection attempts
|
||||
<IfModule mod_rewrite.c>
|
||||
# Bloquear SQL injection
|
||||
RewriteCond %{QUERY_STRING} (union|select|insert|delete|drop|create|update|or|and) [NC]
|
||||
RewriteRule .* - [F,L]
|
||||
|
||||
# Bloquear XSS
|
||||
RewriteCond %{QUERY_STRING} (\<|%3C).*script.*(\>|%3E) [NC]
|
||||
RewriteRule .* - [F,L]
|
||||
|
||||
# Bloquear file injection
|
||||
RewriteCond %{QUERY_STRING} \.\.\/ [NC]
|
||||
RewriteRule .* - [F,L]
|
||||
|
||||
# Bloquear acceso a wp-admin (si no es WordPress)
|
||||
RewriteCond %{REQUEST_URI} wp-admin [NC]
|
||||
RewriteRule .* - [F,L]
|
||||
</IfModule>
|
||||
|
||||
# Limitar tamaño de subida (HestiaCP compatible)
|
||||
<IfModule mod_php.c>
|
||||
php_value upload_max_filesize 10M
|
||||
php_value post_max_size 10M
|
||||
php_value max_execution_time 300
|
||||
php_value max_input_vars 3000
|
||||
</IfModule>
|
||||
|
||||
# Para PHP-FPM en HestiaCP
|
||||
<IfModule mod_fcgid.c>
|
||||
FcgidMaxRequestLen 10485760
|
||||
</IfModule>
|
||||
|
||||
# ==================================================
|
||||
# CONFIGURACIÓN ESPECÍFICA DE APIs
|
||||
# ==================================================
|
||||
|
||||
# Headers para APIs JSON
|
||||
<FilesMatch "^(api)/.*\.php$">
|
||||
<IfModule mod_headers.c>
|
||||
Header always set Content-Type "application/json; charset=utf-8"
|
||||
Header always set Access-Control-Allow-Origin "*"
|
||||
Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
|
||||
Header always set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# Webhook específico para WhatsApp
|
||||
<Files "webhook.php">
|
||||
<IfModule mod_headers.c>
|
||||
Header always set Access-Control-Allow-Origin "*"
|
||||
Header always set Access-Control-Allow-Methods "GET, POST"
|
||||
</IfModule>
|
||||
</Files>
|
||||
|
||||
# ==================================================
|
||||
# CONFIGURACIÓN DE ERRORES PERSONALIZADA
|
||||
# ==================================================
|
||||
|
||||
# Páginas de error personalizadas (opcional)
|
||||
# ErrorDocument 404 /404.html
|
||||
# ErrorDocument 500 /500.html
|
||||
# ErrorDocument 403 /403.html
|
||||
|
||||
# Ocultar información del servidor
|
||||
ServerTokens Prod
|
||||
|
||||
# Configuración de charset por defecto
|
||||
AddDefaultCharset UTF-8
|
||||
@@ -0,0 +1 @@
|
||||
2026-01-02 23:38:46
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
USUARIO: admin
|
||||
CONTRASEÑA: 2fa904eee46f
|
||||
FECHA: 2026-01-04 03:04:54
|
||||
@@ -0,0 +1,167 @@
|
||||
# Guía para Resolver Error de Plantillas WhatsApp
|
||||
|
||||
## Error: "Template name does not exist in the translation" (#132001)
|
||||
|
||||
### ¿Qué significa este error?
|
||||
Este error indica que WhatsApp no encuentra la plantilla especificada, o hay un problema con su configuración.
|
||||
|
||||
### Causas Comunes:
|
||||
1. La plantilla no existe en WhatsApp Business Manager
|
||||
2. La plantilla no está aprobada por Meta
|
||||
3. El idioma especificado no coincide con el de la plantilla
|
||||
4. El nombre de la plantilla está mal escrito
|
||||
|
||||
## Pasos para Resolverlo
|
||||
|
||||
### 1. Verificar en WhatsApp Business Manager
|
||||
1. Ve a [business.facebook.com](https://business.facebook.com)
|
||||
2. Selecciona tu cuenta de negocio
|
||||
3. Ve a **WhatsApp Manager** → **Plantillas de mensaje**
|
||||
4. Verifica que existe la plantilla `jaspers_market_plain_text_v1`
|
||||
5. Confirma que el estado es **"Aprobada"** (no "Pendiente de revisión")
|
||||
|
||||
### 2. Verificar el Idioma
|
||||
- Si la plantilla está en inglés, usa `language: "en"`
|
||||
- Si está en español, usa `language: "es"`
|
||||
- El idioma debe coincidir EXACTAMENTE
|
||||
|
||||
### 3. Verificar el Nombre
|
||||
- Usa el nombre EXACTO de la plantilla
|
||||
- No uses espacios ni caracteres especiales si no están en el nombre original
|
||||
- Verifica mayúsculas y minúsculas
|
||||
|
||||
### 4. Usar el Diagnóstico del Sistema
|
||||
1. Ve a la pestaña **Configuración**
|
||||
2. Haz clic en **"Ejecutar"** en el panel de Diagnóstico
|
||||
3. Revisa el estado de tus plantillas
|
||||
|
||||
## Ejemplos de Envío Correcto
|
||||
|
||||
### Formato Simple:
|
||||
```json
|
||||
{
|
||||
"phone": "+1234567890",
|
||||
"type": "template",
|
||||
"template_name": "jaspers_market_plain_text_v1",
|
||||
"language": "en",
|
||||
"parameters": ["Juan", "123456"]
|
||||
}
|
||||
```
|
||||
|
||||
### Formato WhatsApp Business API:
|
||||
```json
|
||||
{
|
||||
"messaging_product": "whatsapp",
|
||||
"to": "+1234567890",
|
||||
"type": "template",
|
||||
"template": {
|
||||
"name": "jaspers_market_plain_text_v1",
|
||||
"language": {
|
||||
"code": "en"
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"type": "body",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Juan"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "123456"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Crear una Nueva Plantilla
|
||||
|
||||
### En WhatsApp Business Manager:
|
||||
1. Ve a **Plantillas de mensaje** → **Crear plantilla**
|
||||
2. Escoge la categoría apropiada (UTILITY, MARKETING, AUTHENTICATION)
|
||||
3. Escribe el contenido usando variables como `{{1}}`, `{{2}}`, etc.
|
||||
4. Envía para aprobación
|
||||
5. Espera la aprobación de Meta (puede tomar 24-48 horas)
|
||||
|
||||
### Ejemplo de Plantilla Simple:
|
||||
```
|
||||
Nombre: mi_plantilla_bienvenida
|
||||
Idioma: Español
|
||||
Categoría: UTILITY
|
||||
Contenido: Hola {{1}}, bienvenido a nuestro servicio. Tu código es: {{2}}
|
||||
```
|
||||
|
||||
### Registrarla en el Sistema:
|
||||
1. Ve a la pestaña **Plantillas**
|
||||
2. Haz clic en **Nueva Plantilla**
|
||||
3. Completa los datos:
|
||||
- Nombre: `mi_plantilla_bienvenida`
|
||||
- Plantilla WhatsApp: `mi_plantilla_bienvenida` (igual al nombre)
|
||||
- Idioma: `es`
|
||||
- Categoría: `UTILITY`
|
||||
- Estado: `approved` (solo después de que Meta la apruebe)
|
||||
|
||||
## Herramientas de Debug
|
||||
|
||||
### 1. Verificar Plantillas Disponibles
|
||||
```bash
|
||||
curl -X GET "http://tu-dominio.com/api/check_templates.php"
|
||||
```
|
||||
|
||||
### 2. Ejecutar Diagnóstico Completo
|
||||
```bash
|
||||
curl -X GET "http://tu-dominio.com/api/diagnose_whatsapp.php?debug=1"
|
||||
```
|
||||
|
||||
### 3. Probar Envío con Debug
|
||||
```bash
|
||||
curl -X POST "http://tu-dominio.com/api/send_message.php?debug=1" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"phone": "+1234567890",
|
||||
"type": "template",
|
||||
"template_name": "mi_plantilla_test",
|
||||
"language": "es",
|
||||
"parameters": ["Pedro", "ABC123"]
|
||||
}'
|
||||
```
|
||||
|
||||
## Estados de Plantillas
|
||||
|
||||
| Estado | Descripción | ¿Se puede usar? |
|
||||
|--------|-------------|-----------------|
|
||||
| `pending` | En revisión por Meta | ❌ No |
|
||||
| `approved` | Aprobada por Meta | ✅ Sí |
|
||||
| `rejected` | Rechazada por Meta | ❌ No |
|
||||
| `disabled` | Deshabilitada | ❌ No |
|
||||
|
||||
## Resolución por Pasos
|
||||
|
||||
### ✅ Lista de Verificación:
|
||||
- [ ] La plantilla existe en WhatsApp Business Manager
|
||||
- [ ] La plantilla está aprobada (estado: "Aprobada")
|
||||
- [ ] El nombre de la plantilla es exacto
|
||||
- [ ] El idioma coincide con la configuración en Meta
|
||||
- [ ] Los parámetros coinciden con las variables de la plantilla
|
||||
- [ ] La configuración de WhatsApp Business API es correcta
|
||||
|
||||
### 🔧 Si Sigues Teniendo Problemas:
|
||||
1. Revisa el Access Token (puede haber expirado)
|
||||
2. Confirma los permisos de la aplicación de Facebook
|
||||
3. Verifica que el Phone Number ID es correcto
|
||||
4. Asegúrate de que el número de WhatsApp Business está verificado
|
||||
|
||||
## Contacto y Soporte
|
||||
|
||||
Si después de seguir estos pasos sigues teniendo problemas:
|
||||
1. Usa el diagnóstico del sistema para obtener un reporte detallado
|
||||
2. Verifica los logs en la pestaña "Logs"
|
||||
3. Comprueba la documentación oficial de WhatsApp Business API
|
||||
4. Considera crear una plantilla de prueba simple para verificar conectividad
|
||||
|
||||
---
|
||||
*Guía actualizada: Enero 2026*
|
||||
@@ -0,0 +1,214 @@
|
||||
# 🚀 Instalación en HestiaCP - WhatsApp Bot Manager
|
||||
|
||||
## 📦 Archivo para HestiaCP
|
||||
- **Archivo:** `whatsapp_HESTIACP_READY_20260104_104546.zip`
|
||||
- **Optimizado para:** HestiaCP + hosting compartido/VPS
|
||||
- **Tamaño:** ~168KB
|
||||
|
||||
## 🔧 .htaccess Reparado para HestiaCP
|
||||
|
||||
### ✅ Mejoras Implementadas:
|
||||
- **Compatibilidad HestiaCP:** Sintaxis moderna (Require all denied)
|
||||
- **Seguridad avanzada:** Protección contra inyecciones SQL/XSS
|
||||
- **Rendimiento:** Compresión GZIP y cache optimizado
|
||||
- **APIs:** Headers específicos para WhatsApp webhook
|
||||
- **CSP:** Content Security Policy configurado
|
||||
|
||||
### 🛡️ Protecciones de Seguridad:
|
||||
- ✅ Archivos de configuración protegidos
|
||||
- ✅ Archivos .env bloqueados
|
||||
- ✅ Logs inaccesibles desde web
|
||||
- ✅ Tests bloqueados en producción
|
||||
- ✅ Headers de seguridad completos
|
||||
|
||||
## 📋 Instalación en HestiaCP
|
||||
|
||||
### 1. Acceso al Panel
|
||||
```bash
|
||||
1. Acceder a HestiaCP: https://tu-servidor:8083
|
||||
2. Login con credenciales de admin
|
||||
3. Ir a "Web" → Seleccionar dominio
|
||||
```
|
||||
|
||||
### 2. Subir Archivos
|
||||
```bash
|
||||
1. File Manager → public_html/
|
||||
2. Subir whatsapp_HESTIACP_READY_20260104_104546.zip
|
||||
3. Extraer ZIP en public_html/
|
||||
4. Borrar archivo ZIP después
|
||||
```
|
||||
|
||||
### 3. Configurar Permisos en HestiaCP
|
||||
```bash
|
||||
1. En File Manager, seleccionar carpetas:
|
||||
- logs/ → Permisos 777
|
||||
- uploads/ → Permisos 777
|
||||
- config/ → Permisos 755
|
||||
|
||||
2. O por SSH:
|
||||
chmod 777 public_html/logs/
|
||||
chmod 777 public_html/uploads/
|
||||
chmod 644 public_html/.htaccess
|
||||
```
|
||||
|
||||
### 4. Crear Base de Datos
|
||||
```bash
|
||||
1. En HestiaCP → "DB"
|
||||
2. Add Database:
|
||||
- Name: whatsapp_bot
|
||||
- User: whatsapp_user
|
||||
- Password: [generar segura]
|
||||
3. Importar database/schema.sql
|
||||
```
|
||||
|
||||
### 5. Configurar config.php
|
||||
```php
|
||||
// Editar public_html/config/config.php
|
||||
define('DB_HOST', 'localhost');
|
||||
define('DB_NAME', 'usuario_whatsapp_bot'); // prefijo_nombre
|
||||
define('DB_USER', 'usuario_whatsapp_user'); // prefijo_usuario
|
||||
define('DB_PASS', 'tu_password_db');
|
||||
define('DB_PORT', 3306);
|
||||
define('DB_CHARSET', 'utf8mb4');
|
||||
```
|
||||
|
||||
## 🌐 Configuración Específica HestiaCP
|
||||
|
||||
### SSL/HTTPS (Recomendado)
|
||||
```bash
|
||||
1. En HestiaCP → "Web" → tu dominio
|
||||
2. "Edit" → SSL → "Generate" (Let's Encrypt)
|
||||
3. Activar "Force SSL redirect"
|
||||
4. En .htaccess descomenta las líneas HTTPS
|
||||
```
|
||||
|
||||
### PHP Version
|
||||
```bash
|
||||
1. En HestiaCP → "Web" → Edit domain
|
||||
2. "Advanced options" → PHP Version: 8.1+
|
||||
3. "Save"
|
||||
```
|
||||
|
||||
### Subdominio (Opcional)
|
||||
```bash
|
||||
1. En HestiaCP → "Web" → "Add Web Domain"
|
||||
2. Domain: bot.tudominio.com
|
||||
3. Mismo proceso de instalación
|
||||
```
|
||||
|
||||
## 🔑 Acceso al Sistema
|
||||
|
||||
### URLs de Acceso:
|
||||
- **Dashboard:** https://tudominio.com/
|
||||
- **Login:** https://tudominio.com/login.php
|
||||
- **Verificador:** https://tudominio.com/verificar_servidor.php
|
||||
- **Webhook:** https://tudominio.com/api/webhook.php
|
||||
|
||||
### Credenciales:
|
||||
- **Usuario:** admin
|
||||
- **Contraseña:** 2fa904eee46f
|
||||
|
||||
## 🧪 Verificación Post-Instalación
|
||||
|
||||
### 1. Test básico
|
||||
```bash
|
||||
1. Ir a: https://tudominio.com/verificar_servidor.php
|
||||
2. Verificar que todo esté en ✅ verde
|
||||
3. Corregir cualquier ❌ error mostrado
|
||||
```
|
||||
|
||||
### 2. Test de login
|
||||
```bash
|
||||
1. Ir a: https://tudominio.com/login.php
|
||||
2. Ingresar credenciales
|
||||
3. Verificar acceso al dashboard
|
||||
```
|
||||
|
||||
### 3. Test de webhook
|
||||
```bash
|
||||
1. Verificar: https://tudominio.com/api/webhook.php
|
||||
2. Debe mostrar: "Webhook endpoint activo"
|
||||
3. Configurar URL en WhatsApp Business API
|
||||
```
|
||||
|
||||
## ⚙️ Configuración WhatsApp API
|
||||
|
||||
### En el Dashboard:
|
||||
```bash
|
||||
1. Login al sistema
|
||||
2. Ir a "Configuraciones"
|
||||
3. Completar:
|
||||
- WhatsApp Access Token
|
||||
- Phone Number ID
|
||||
- Business Account ID
|
||||
- Webhook Verify Token
|
||||
```
|
||||
|
||||
### En WhatsApp Business API:
|
||||
```bash
|
||||
1. Facebook Developers → App → WhatsApp
|
||||
2. Configuration → Webhook URL:
|
||||
https://tudominio.com/api/webhook.php
|
||||
3. Verify Token: (copiar del dashboard)
|
||||
4. Suscribirse a: messages, message_status
|
||||
```
|
||||
|
||||
## 🚨 Solución de Problemas HestiaCP
|
||||
|
||||
### Error 500 - Internal Server Error
|
||||
```bash
|
||||
1. Revisar error log en HestiaCP → Statistics → Error Log
|
||||
2. Verificar permisos de archivos/carpetas
|
||||
3. Comprobar sintaxis de .htaccess
|
||||
4. Revisar versión PHP (requiere 7.4+)
|
||||
```
|
||||
|
||||
### Base de datos no conecta
|
||||
```bash
|
||||
1. Verificar credenciales en config/config.php
|
||||
2. Comprobar prefijo de usuario/DB en HestiaCP
|
||||
3. Verificar que DB esté en mismo servidor
|
||||
4. Probar conexión desde File Manager
|
||||
```
|
||||
|
||||
### Headers de seguridad
|
||||
```bash
|
||||
1. Si hay conflicto con CSP, editar .htaccess
|
||||
2. Ajustar Content-Security-Policy según necesidades
|
||||
3. Para debugging, comentar temporalmente CSP
|
||||
```
|
||||
|
||||
### Webhook no recibe mensajes
|
||||
```bash
|
||||
1. Verificar URL pública accesible
|
||||
2. Comprobar SSL activo
|
||||
3. Revisar logs en HestiaCP
|
||||
4. Test manual: curl -X POST https://tudominio.com/api/webhook.php
|
||||
```
|
||||
|
||||
## 📞 Soporte Específico HestiaCP
|
||||
|
||||
**Documentación oficial:** https://docs.hestiacp.com/
|
||||
**Panel de control:** Acceso vía puerto 8083
|
||||
**Logs del sistema:** /var/log/hestia/
|
||||
**PHP logs:** Panel → Statistics → Error Log
|
||||
|
||||
## 🎯 Optimizaciones HestiaCP
|
||||
|
||||
### Performance
|
||||
```bash
|
||||
1. Activar OPcache en PHP settings
|
||||
2. Configurar Redis/Memcached si disponible
|
||||
3. Optimizar MySQL en HestiaCP settings
|
||||
4. Usar Cloudflare para CDN
|
||||
```
|
||||
|
||||
### Backup
|
||||
```bash
|
||||
1. HestiaCP → Backups → Schedule
|
||||
2. Backup automático diario/semanal
|
||||
3. Incluir: Web, DB, Mail
|
||||
4. Almacenar en storage externo
|
||||
```
|
||||
|
||||
¡Sistema listo para funcionar en HestiaCP! 🚀
|
||||
@@ -0,0 +1,91 @@
|
||||
# 🚀 Instrucciones para Subir al Servidor
|
||||
|
||||
## 📁 Archivo Listo para Subir
|
||||
- **Archivo:** `whatsapp_fixed_20260104_103742.zip`
|
||||
- **Tamaño:** ~164KB
|
||||
- **Estado:** ✅ Todas las rutas corregidas para hosting
|
||||
|
||||
## 🔧 Cambios Realizados
|
||||
|
||||
### ✅ Rutas Corregidas
|
||||
- **Antes:** `require_once '../config/config.php';`
|
||||
- **Después:** `require_once 'config/config.php';`
|
||||
|
||||
**Archivos corregidos:**
|
||||
- ✅ api/webhook.php
|
||||
- ✅ api/send_message.php
|
||||
- ✅ api/send_broadcast.php
|
||||
- ✅ Todas las 17 APIs del sistema
|
||||
- ✅ Sistema de autenticación añadido en todas las APIs
|
||||
|
||||
## 📋 Proceso de Instalación
|
||||
|
||||
### 1. Subir Archivos
|
||||
```bash
|
||||
1. Extraer whatsapp_fixed_20260104_103742.zip
|
||||
2. Subir todos los archivos a public_html/
|
||||
3. Mantener la estructura de carpetas
|
||||
```
|
||||
|
||||
### 2. Configurar Permisos
|
||||
```bash
|
||||
chmod 755 api/
|
||||
chmod 755 config/
|
||||
chmod 755 classes/
|
||||
chmod 755 services/
|
||||
chmod 777 logs/
|
||||
chmod 777 uploads/
|
||||
```
|
||||
|
||||
### 3. Configurar Base de Datos
|
||||
```bash
|
||||
1. Importar database/schema.sql
|
||||
2. Editar config/config.php con datos de tu hosting:
|
||||
- DB_HOST
|
||||
- DB_NAME
|
||||
- DB_USER
|
||||
- DB_PASS
|
||||
```
|
||||
|
||||
### 4. Configurar WhatsApp API
|
||||
```bash
|
||||
1. Obtener tokens de WhatsApp Business API
|
||||
2. Actualizar en config/config.php:
|
||||
- WHATSAPP_TOKEN
|
||||
- WHATSAPP_PHONE_NUMBER_ID
|
||||
- WHATSAPP_API_URL
|
||||
```
|
||||
|
||||
## 🔑 Credenciales de Acceso
|
||||
- **Usuario:** admin
|
||||
- **Contraseña:** 2fa904eee46f
|
||||
- **URL Login:** https://tudominio.com/login.php
|
||||
|
||||
## 🧪 Verificar Instalación
|
||||
- **Dashboard:** https://tudominio.com/
|
||||
- **Tests:** https://tudominio.com/run_tests.php
|
||||
- **Webhook:** https://tudominio.com/api/webhook.php
|
||||
|
||||
## 🎯 Características Implementadas
|
||||
- ✅ Sistema de autenticación completo
|
||||
- ✅ Protección contra fuerza bruta
|
||||
- ✅ API segura con verificación de sesión
|
||||
- ✅ Sistema de plantillas WhatsApp
|
||||
- ✅ Envío de mensajes con templates
|
||||
- ✅ Dashboard con analytics
|
||||
- ✅ Suite de tests completa
|
||||
- ✅ Gestión de usuarios y conversaciones
|
||||
|
||||
## 🔒 Seguridad
|
||||
- ✅ Todas las APIs requieren autenticación
|
||||
- ✅ Protección contra ataques de fuerza bruta
|
||||
- ✅ Sesiones seguras con timeouts
|
||||
- ✅ Validación de entrada en formularios
|
||||
- ✅ Headers de seguridad implementados
|
||||
|
||||
## 📞 Soporte
|
||||
El sistema está completamente funcional y probado. Si tienes problemas:
|
||||
1. Verificar permisos de archivos
|
||||
2. Revisar configuración de base de datos
|
||||
3. Comprobar tokens de WhatsApp API
|
||||
4. Ejecutar tests desde run_tests.php
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
# 🚀 Guía de Configuración Completa - WhatsApp Bot Manager
|
||||
|
||||
## ✅ Estado del Sistema
|
||||
|
||||
**Sistema validado y optimizado** - 2 de enero de 2026
|
||||
|
||||
### 🛠️ Mejoras Implementadas
|
||||
|
||||
- ✅ Estructura de archivos validada y completa
|
||||
- ✅ Configuración mejorada con soporte para archivos .env
|
||||
- ✅ Sistema de logging centralizado implementado
|
||||
- ✅ Permisos de archivos configurados correctamente
|
||||
- ✅ Validación completa del sistema implementada
|
||||
- ✅ Protecciones de seguridad añadidas (.htaccess)
|
||||
- ✅ Classes y Services completamente funcionales
|
||||
- ✅ API endpoints validados y operativos
|
||||
- ✅ Interfaz web optimizada
|
||||
- ✅ Sistema de instaladores mejorado
|
||||
|
||||
## 🔧 Configuración Paso a Paso
|
||||
|
||||
### 1. **Configuración de Variables de Entorno (Recomendado)**
|
||||
|
||||
Crea un archivo `.env` en la raíz del proyecto usando `.env.example` como base:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Edita el archivo `.env` con tus valores reales:
|
||||
|
||||
```env
|
||||
# Base de datos
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_NAME=tu_base_datos
|
||||
DB_USER=tu_usuario
|
||||
DB_PASS=tu_contraseña
|
||||
|
||||
# WhatsApp Business API
|
||||
WHATSAPP_TOKEN=tu_token_real_de_facebook
|
||||
WHATSAPP_PHONE_NUMBER_ID=tu_phone_number_id_real
|
||||
WEBHOOK_VERIFY_TOKEN=tu_token_secreto_personalizado
|
||||
|
||||
# Configuración general
|
||||
APP_URL=https://tudominio.com
|
||||
TIMEZONE=America/Bogota
|
||||
```
|
||||
|
||||
### 2. **Instalación de Base de Datos**
|
||||
|
||||
Tienes varias opciones según tu tipo de hosting:
|
||||
|
||||
#### **VPS/Servidor Dedicado:**
|
||||
```
|
||||
http://tudominio.com/install.php
|
||||
```
|
||||
|
||||
#### **Hosting Compartido (cPanel):**
|
||||
```
|
||||
http://tudominio.com/install_manual.php
|
||||
```
|
||||
|
||||
#### **Instalación Básica:**
|
||||
```
|
||||
http://tudominio.com/install_simple.php
|
||||
```
|
||||
|
||||
### 3. **Validación del Sistema**
|
||||
|
||||
Verifica que todo esté funcionando correctamente:
|
||||
|
||||
```
|
||||
http://tudominio.com/system_validator.php
|
||||
```
|
||||
|
||||
Este validador te mostrará:
|
||||
- ✅ Estado de PHP y extensiones
|
||||
- ✅ Conexión a base de datos
|
||||
- ✅ Estructura de tablas
|
||||
- ✅ Archivos críticos
|
||||
- ✅ Permisos de escritura
|
||||
- ✅ Configuración de WhatsApp
|
||||
- ✅ Estado general del sistema
|
||||
|
||||
### 4. **Configuración de WhatsApp Business API**
|
||||
|
||||
#### **Obtener Token de Acceso:**
|
||||
1. Ve a [Facebook for Developers](https://developers.facebook.com/)
|
||||
2. Crea una aplicación de WhatsApp Business
|
||||
3. Genera un token de acceso permanente
|
||||
4. Obtén tu Phone Number ID
|
||||
|
||||
#### **Configurar Webhook:**
|
||||
1. URL del webhook: `https://tudominio.com/api/webhook.php`
|
||||
2. Token de verificación: (el que configuraste en .env)
|
||||
3. Eventos a suscribirse: `messages`
|
||||
|
||||
### 5. **Acceso al Panel de Administración**
|
||||
|
||||
```
|
||||
http://tudominio.com/index.php
|
||||
```
|
||||
|
||||
**Credenciales por defecto:**
|
||||
- Usuario: `admin`
|
||||
- Contraseña: (generada automáticamente durante la instalación)
|
||||
|
||||
## 📁 Archivos y Directorios Importantes
|
||||
|
||||
### **Archivos de Configuración:**
|
||||
- `config/config.php` - Configuración principal mejorada
|
||||
- `.env` - Variables de entorno (crear desde .env.example)
|
||||
- `.htaccess` - Protecciones de seguridad
|
||||
|
||||
### **Archivos de Instalación:**
|
||||
- `install.php` - Instalador automático (VPS)
|
||||
- `install_manual.php` - Para hosting compartido
|
||||
- `system_validator.php` - Validador del sistema
|
||||
|
||||
### **Core del Sistema:**
|
||||
- `classes/Database.php` - Conexión a base de datos
|
||||
- `services/BotService.php` - Lógica del chatbot
|
||||
- `services/WhatsAppService.php` - Integración con WhatsApp
|
||||
- `api/webhook.php` - Receptor de mensajes
|
||||
- `api/` - Endpoints de la API REST
|
||||
|
||||
### **Directorios:**
|
||||
- `logs/` - Archivos de log del sistema
|
||||
- `uploads/` - Archivos subidos por usuarios
|
||||
- `assets/` - CSS y JavaScript del frontend
|
||||
|
||||
## 🔒 Seguridad Implementada
|
||||
|
||||
### **Protecciones de Archivos:**
|
||||
- Archivo `.htaccess` con restricciones de acceso
|
||||
- Protección de archivos de configuración
|
||||
- Headers de seguridad HTTP
|
||||
- Protección contra ataques de fuerza bruta
|
||||
|
||||
### **Validaciones:**
|
||||
- Tokens de verificación en webhooks
|
||||
- Sanitización de entradas
|
||||
- Validación de permisos de usuario
|
||||
- Logs de seguridad
|
||||
|
||||
## 🚀 Funcionalidades Listas
|
||||
|
||||
### **Bot Inteligente:**
|
||||
- ✅ Sistema de menús parametrizables
|
||||
- ✅ Respuestas automáticas por keywords
|
||||
- ✅ Navegación por números (1, 2, 3...)
|
||||
- ✅ Comandos especiales (menu, ayuda, salir)
|
||||
- ✅ Seguimiento de estado por usuario
|
||||
|
||||
### **Panel Web:**
|
||||
- ✅ Dashboard con estadísticas en tiempo real
|
||||
- ✅ Gestión de usuarios y conversaciones
|
||||
- ✅ Configuración de menús dinámicos
|
||||
- ✅ Envío de mensajes individuales y masivos
|
||||
- ✅ Sistema de plantillas
|
||||
- ✅ Logs detallados
|
||||
|
||||
### **API REST:**
|
||||
- ✅ Webhook para recibir mensajes
|
||||
- ✅ Endpoints para envío de mensajes
|
||||
- ✅ APIs para estadísticas y gestión
|
||||
- ✅ Integración con sistemas externos
|
||||
|
||||
## 📱 Flujo de Mensajes
|
||||
|
||||
```
|
||||
Usuario → WhatsApp → Facebook API → webhook.php → BotService → WhatsAppService → Usuario
|
||||
```
|
||||
|
||||
## 🛠️ Comandos de Mantenimiento
|
||||
|
||||
### **Verificar Logs:**
|
||||
```bash
|
||||
tail -f logs/system.log
|
||||
```
|
||||
|
||||
### **Limpiar Logs:**
|
||||
```bash
|
||||
> logs/system.log
|
||||
```
|
||||
|
||||
### **Verificar Permisos:**
|
||||
```bash
|
||||
ls -la logs/ uploads/
|
||||
```
|
||||
|
||||
## 🆘 Solución de Problemas
|
||||
|
||||
### **Si el webhook no recibe mensajes:**
|
||||
1. Verifica la URL del webhook en Facebook
|
||||
2. Confirma que el token de verificación coincida
|
||||
3. Revisa los logs: `logs/system.log`
|
||||
4. Valida que el SSL esté funcionando
|
||||
|
||||
### **Si hay errores de base de datos:**
|
||||
1. Verifica las credenciales en `.env` o `config.php`
|
||||
2. Confirma que las tablas existan
|
||||
3. Ejecuta el validador del sistema
|
||||
|
||||
### **Si no se pueden escribir logs:**
|
||||
1. Verifica permisos del directorio `logs/`
|
||||
2. Ejecuta: `chmod 755 logs/`
|
||||
|
||||
## 📞 Soporte
|
||||
|
||||
- **Desarrollador:** U-Site.app
|
||||
- **Email:** support@u-site.app
|
||||
- **Documentación:** Archivos .md en el proyecto
|
||||
- **Validador:** system_validator.php
|
||||
|
||||
## 🎉 ¡Listo para Funcionar!
|
||||
|
||||
Tu sistema WhatsApp Bot Manager está **completamente validado y optimizado**.
|
||||
|
||||
**Próximos pasos:**
|
||||
1. Ejecuta `system_validator.php` para confirmar todo está bien
|
||||
2. Configura tus tokens de WhatsApp
|
||||
3. Accede al panel de administración
|
||||
4. ¡Comienza a usar tu chatbot!
|
||||
|
||||
---
|
||||
**Versión del Sistema:** 1.0.0
|
||||
**Fecha de Validación:** 2 de enero de 2026
|
||||
**Estado:** ✅ Sistema Completamente Operativo
|
||||
@@ -0,0 +1,94 @@
|
||||
# 🔐 Solución Problema de Seguridad - Acceso sin Autenticación
|
||||
|
||||
## 🚨 Problema Encontrado
|
||||
|
||||
El sistema tenía un **fallo crítico de seguridad** que permitía acceder al dashboard y a las APIs sin necesidad de iniciar sesión.
|
||||
|
||||
### Archivos afectados:
|
||||
- ❌ `index.php` - Dashboard principal sin verificación de login
|
||||
- ❌ `api/*.php` - APIs sin protección de autenticación
|
||||
|
||||
## ✅ Solución Implementada
|
||||
|
||||
### 1. Corregido `index.php`
|
||||
```php
|
||||
// ANTES - Sin verificación
|
||||
require_once 'config/config.php';
|
||||
// Mostraba el dashboard directamente
|
||||
|
||||
// DESPUÉS - Con verificación
|
||||
require_once 'config/config.php';
|
||||
|
||||
// Verificar autenticación - redirigir al login si no está logueado
|
||||
if (!isUserLoggedIn()) {
|
||||
header('Location: login.php');
|
||||
exit('Acceso denegado. <a href="login.php">Iniciar sesión</a>');
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Nueva función en `config.php`
|
||||
```php
|
||||
// Función para verificar autenticación en APIs
|
||||
function requireAuthentication() {
|
||||
if (!isUserLoggedIn()) {
|
||||
http_response_code(401);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'No autorizado. Inicie sesión.',
|
||||
'redirect' => 'login.php'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. APIs protegidas
|
||||
Archivos corregidos con `requireAuthentication()`:
|
||||
- ✅ `api/get_stats.php`
|
||||
- ✅ `api/save_settings.php`
|
||||
- ✅ `api/get_users.php`
|
||||
- ✅ `api/send_message.php`
|
||||
- ✅ `api/get_settings.php`
|
||||
- ✅ `api/get_menus.php`
|
||||
- ✅ `api/export_users.php`
|
||||
|
||||
## 🔒 Seguridad Actual
|
||||
|
||||
### Flujo de Autenticación:
|
||||
1. Usuario intenta acceder a `index.php`
|
||||
2. Sistema verifica `isUserLoggedIn()`
|
||||
3. Si no está logueado → Redirección a `login.php`
|
||||
4. Si está logueado → Acceso permitido
|
||||
|
||||
### Protección de APIs:
|
||||
1. Cliente hace petición a API
|
||||
2. `requireAuthentication()` verifica sesión
|
||||
3. Si no está autenticado → Error 401 JSON
|
||||
4. Si está autenticado → Procesamiento normal
|
||||
|
||||
## 🎯 Recomendaciones Adicionales
|
||||
|
||||
Para fortalecer aún más la seguridad:
|
||||
|
||||
1. **Revisar APIs restantes** - Aplicar `requireAuthentication()` a todas
|
||||
2. **HTTPS obligatorio** - Configurar SSL en producción
|
||||
3. **Rate Limiting** - Limitar requests por IP
|
||||
4. **Logs de seguridad** - Registrar intentos no autorizados
|
||||
5. **Token CSRF** - Protección contra ataques Cross-Site
|
||||
|
||||
## 📝 Credenciales por Defecto
|
||||
|
||||
- **Usuario**: admin
|
||||
- **Contraseña**: Se configura durante la instalación
|
||||
|
||||
## ⚠️ Importante
|
||||
|
||||
**Antes de esta corrección**, cualquier persona que conociera la URL podía:
|
||||
- Ver el dashboard completo
|
||||
- Acceder a todas las APIs
|
||||
- Ver datos de usuarios
|
||||
- Enviar mensajes
|
||||
- Modificar configuraciones
|
||||
|
||||
**Ahora**, todas estas acciones requieren autenticación previa.
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
// Archivo para aprobar plantillas manualmente
|
||||
require_once '../config/config.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
try {
|
||||
$db = Database::connect();
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
if ($action === 'approve_all') {
|
||||
// Aprobar todas las plantillas pendientes
|
||||
$stmt = $db->prepare("UPDATE whatsapp_templates SET status = 'approved' WHERE status = 'pending'");
|
||||
$result = $stmt->execute();
|
||||
|
||||
if ($result) {
|
||||
$affected = $stmt->rowCount();
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => "Se aprobaron $affected plantillas exitosamente"
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Error al aprobar las plantillas']);
|
||||
}
|
||||
}
|
||||
elseif ($action === 'approve_specific' && !empty($_POST['template_name'])) {
|
||||
// Aprobar plantilla específica
|
||||
$templateName = $_POST['template_name'];
|
||||
$stmt = $db->prepare("UPDATE whatsapp_templates SET status = 'approved' WHERE name = ?");
|
||||
$result = $stmt->execute([$templateName]);
|
||||
|
||||
if ($result && $stmt->rowCount() > 0) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => "Plantilla '$templateName' aprobada exitosamente"
|
||||
]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Plantilla no encontrada o ya aprobada']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
echo json_encode(['success' => false, 'message' => 'Acción no válida']);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Error: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
elseif ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||
// Mostrar estado actual de plantillas
|
||||
try {
|
||||
$db = Database::connect();
|
||||
$stmt = $db->prepare("SELECT name, status, language, category FROM whatsapp_templates ORDER BY name");
|
||||
$stmt->execute();
|
||||
$templates = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'templates' => $templates,
|
||||
'summary' => [
|
||||
'total' => count($templates),
|
||||
'approved' => count(array_filter($templates, fn($t) => $t['status'] === 'approved')),
|
||||
'pending' => count(array_filter($templates, fn($t) => $t['status'] === 'pending')),
|
||||
'rejected' => count(array_filter($templates, fn($t) => $t['status'] === 'rejected'))
|
||||
]
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Error: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
|
||||
try {
|
||||
// Usar conexión desde config
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Obtener todas las plantillas
|
||||
$stmt = $db->query("SELECT * FROM message_templates ORDER BY template_name");
|
||||
$templates = $stmt->fetchAll();
|
||||
|
||||
$result = [
|
||||
'success' => true,
|
||||
'templates' => [],
|
||||
'summary' => [
|
||||
'total' => count($templates),
|
||||
'approved' => 0,
|
||||
'pending' => 0,
|
||||
'rejected' => 0
|
||||
]
|
||||
];
|
||||
|
||||
foreach ($templates as $template) {
|
||||
$result['templates'][] = [
|
||||
'id' => $template['id'],
|
||||
'name' => $template['template_name'],
|
||||
'language' => $template['language_code'] ?: 'es',
|
||||
'status' => $template['status'] ?: 'pending',
|
||||
'category' => $template['category'] ?: 'UTILITY',
|
||||
'created' => $template['created_at'] ?: 'N/A'
|
||||
];
|
||||
|
||||
// Contar por estado
|
||||
$status = $template['status'] ?: 'pending';
|
||||
if (isset($result['summary'][$status])) {
|
||||
$result['summary'][$status]++;
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($result);
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Error al verificar plantillas: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST, GET');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
|
||||
try {
|
||||
// Verificar autenticación si no estás en modo debug
|
||||
$debugMode = isset($_GET['debug']);
|
||||
if (!$debugMode) {
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'No autorizado']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Obtener configuración de WhatsApp
|
||||
$whatsappConfig = getWhatsAppConfigFromDB();
|
||||
|
||||
$diagnostic = [
|
||||
'success' => true,
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'config_status' => [],
|
||||
'template_status' => [],
|
||||
'api_status' => []
|
||||
];
|
||||
|
||||
// 1. Verificar configuración básica
|
||||
$diagnostic['config_status'] = [
|
||||
'phone_number_id' => !empty($whatsappConfig['phone_number_id']),
|
||||
'access_token' => !empty($whatsappConfig['access_token']),
|
||||
'webhook_verify_token' => !empty($whatsappConfig['webhook_verify_token']),
|
||||
'business_account_id' => !empty($whatsappConfig['business_account_id'])
|
||||
];
|
||||
|
||||
$diagnostic['config_status']['complete'] = array_reduce($diagnostic['config_status'], function($carry, $item) {
|
||||
return $carry && $item;
|
||||
}, true);
|
||||
|
||||
// 2. Verificar plantillas en base de datos
|
||||
$db = Database::getInstance();
|
||||
$stmt = $db->query("SELECT status, COUNT(*) as count FROM message_templates GROUP BY status");
|
||||
$templateCounts = $stmt->fetchAll();
|
||||
|
||||
$diagnostic['template_status'] = [
|
||||
'total' => 0,
|
||||
'approved' => 0,
|
||||
'pending' => 0,
|
||||
'rejected' => 0
|
||||
];
|
||||
|
||||
foreach ($templateCounts as $count) {
|
||||
$status = $count['status'] ?: 'pending';
|
||||
$diagnostic['template_status'][$status] = (int)$count['count'];
|
||||
$diagnostic['template_status']['total'] += (int)$count['count'];
|
||||
}
|
||||
|
||||
// 3. Test básico de API (solo si hay configuración completa)
|
||||
if ($diagnostic['config_status']['complete']) {
|
||||
$apiUrl = "https://graph.facebook.com/v18.0/{$whatsappConfig['phone_number_id']}";
|
||||
$headers = [
|
||||
'Authorization: Bearer ' . $whatsappConfig['access_token'],
|
||||
'Content-Type: application/json'
|
||||
];
|
||||
|
||||
// Crear contexto para la solicitud HTTP
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'GET',
|
||||
'header' => implode("\r\n", $headers),
|
||||
'timeout' => 10
|
||||
]
|
||||
]);
|
||||
|
||||
$response = @file_get_contents($apiUrl, false, $context);
|
||||
|
||||
if ($response !== false) {
|
||||
$apiData = json_decode($response, true);
|
||||
$diagnostic['api_status'] = [
|
||||
'reachable' => true,
|
||||
'phone_number' => $apiData['display_phone_number'] ?? 'N/A',
|
||||
'verified_name' => $apiData['verified_name'] ?? 'N/A',
|
||||
'quality_rating' => $apiData['quality_rating'] ?? 'N/A'
|
||||
];
|
||||
} else {
|
||||
$diagnostic['api_status'] = [
|
||||
'reachable' => false,
|
||||
'error' => 'No se pudo conectar con la API de WhatsApp'
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$diagnostic['api_status'] = [
|
||||
'reachable' => false,
|
||||
'error' => 'Configuración incompleta'
|
||||
];
|
||||
}
|
||||
|
||||
// 4. Recomendaciones
|
||||
$diagnostic['recommendations'] = [];
|
||||
|
||||
if (!$diagnostic['config_status']['complete']) {
|
||||
$diagnostic['recommendations'][] = 'Completa la configuración de WhatsApp en la pestaña Configuración';
|
||||
}
|
||||
|
||||
if ($diagnostic['template_status']['approved'] === 0) {
|
||||
$diagnostic['recommendations'][] = 'Necesitas plantillas aprobadas para enviar mensajes';
|
||||
$diagnostic['recommendations'][] = 'Crea plantillas en WhatsApp Business Manager y espera su aprobación';
|
||||
}
|
||||
|
||||
if (!$diagnostic['api_status']['reachable']) {
|
||||
$diagnostic['recommendations'][] = 'Verifica que el token de acceso sea válido';
|
||||
$diagnostic['recommendations'][] = 'Confirma que el Phone Number ID sea correcto';
|
||||
}
|
||||
|
||||
if (empty($diagnostic['recommendations'])) {
|
||||
$diagnostic['recommendations'][] = 'Todo parece estar configurado correctamente';
|
||||
}
|
||||
|
||||
echo json_encode($diagnostic, JSON_PRETTY_PRINT);
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Error en diagnóstico: ' . $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Verificar autenticación
|
||||
requireAuthentication();
|
||||
|
||||
// Headers para descarga de archivo
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="usuarios_whatsapp_' . date('Y-m-d') . '.csv"');
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
/**
|
||||
* Script de corrección rápida para WhatsApp API
|
||||
* Fecha: 5 de enero de 2026
|
||||
*/
|
||||
|
||||
require_once 'config/config.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// Verificar autenticación si es POST
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
requireAuthentication();
|
||||
}
|
||||
|
||||
try {
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
|
||||
if ($method === 'GET') {
|
||||
// Obtener configuración actual y diagnóstico
|
||||
$db = Database::getInstance();
|
||||
|
||||
$currentConfig = [];
|
||||
$configKeys = ['whatsapp_token', 'whatsapp_phone_number_id', 'whatsapp_api_url', 'webhook_verify_token'];
|
||||
|
||||
foreach ($configKeys as $key) {
|
||||
$config = $db->fetch("SELECT config_value FROM system_config WHERE config_key = ?", [$key]);
|
||||
$currentConfig[$key] = $config['config_value'] ?? null;
|
||||
}
|
||||
|
||||
// Si no hay configuración en BD, usar constantes
|
||||
if (empty($currentConfig['whatsapp_token'])) {
|
||||
$currentConfig = [
|
||||
'whatsapp_token' => WHATSAPP_TOKEN,
|
||||
'whatsapp_phone_number_id' => WHATSAPP_PHONE_NUMBER_ID,
|
||||
'whatsapp_api_url' => WHATSAPP_API_URL,
|
||||
'webhook_verify_token' => WEBHOOK_VERIFY_TOKEN
|
||||
];
|
||||
}
|
||||
|
||||
// Diagnóstico del problema
|
||||
$diagnosis = [
|
||||
'has_valid_token' => $currentConfig['whatsapp_token'] !== 'TU_TOKEN_DE_WHATSAPP_AQUI' && !empty($currentConfig['whatsapp_token']),
|
||||
'has_valid_phone_id' => $currentConfig['whatsapp_phone_number_id'] !== 'TU_PHONE_ID_AQUI' && !empty($currentConfig['whatsapp_phone_number_id']),
|
||||
'problematic_phone_id' => $currentConfig['whatsapp_phone_number_id'] === '858157464051987',
|
||||
'error_message' => 'Object with ID \'858157464051987\' does not exist, cannot be loaded due to missing permissions, or does not support this operation'
|
||||
];
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'current_config' => $currentConfig,
|
||||
'diagnosis' => $diagnosis
|
||||
]);
|
||||
|
||||
} elseif ($method === 'POST') {
|
||||
// Actualizar configuración
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input) {
|
||||
throw new Exception('Datos inválidos');
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Validar datos requeridos
|
||||
$token = trim($input['whatsapp_token'] ?? '');
|
||||
$phoneId = trim($input['whatsapp_phone_number_id'] ?? '');
|
||||
$apiUrl = trim($input['whatsapp_api_url'] ?? WHATSAPP_API_URL);
|
||||
$webhookToken = trim($input['webhook_verify_token'] ?? WEBHOOK_VERIFY_TOKEN);
|
||||
|
||||
if (empty($token)) {
|
||||
throw new Exception('Token de WhatsApp es obligatorio');
|
||||
}
|
||||
|
||||
if (empty($phoneId)) {
|
||||
throw new Exception('Phone Number ID es obligatorio');
|
||||
}
|
||||
|
||||
// Validar formato del token
|
||||
if (!preg_match('/^EAA[A-Za-z0-9_-]+$/', $token)) {
|
||||
throw new Exception('Formato de token inválido. Debe empezar con EAA');
|
||||
}
|
||||
|
||||
// Validar formato del Phone Number ID
|
||||
if (!preg_match('/^\d{10,20}$/', $phoneId)) {
|
||||
throw new Exception('Phone Number ID debe ser numérico (10-20 dígitos)');
|
||||
}
|
||||
|
||||
// Probar conexión antes de guardar
|
||||
$testResult = testConnectionWithCredentials($token, $phoneId, $apiUrl);
|
||||
if (!$testResult['success']) {
|
||||
throw new Exception('Error al probar conexión: ' . $testResult['message']);
|
||||
}
|
||||
|
||||
// Guardar configuración
|
||||
$configs = [
|
||||
'whatsapp_token' => $token,
|
||||
'whatsapp_phone_number_id' => $phoneId,
|
||||
'whatsapp_api_url' => $apiUrl,
|
||||
'webhook_verify_token' => $webhookToken
|
||||
];
|
||||
|
||||
foreach ($configs as $key => $value) {
|
||||
$existing = $db->fetch("SELECT id FROM system_config WHERE config_key = ?", [$key]);
|
||||
|
||||
if ($existing) {
|
||||
$db->query(
|
||||
"UPDATE system_config SET config_value = ?, updated_at = NOW() WHERE config_key = ?",
|
||||
[$value, $key]
|
||||
);
|
||||
} else {
|
||||
$db->query(
|
||||
"INSERT INTO system_config (config_key, config_value, created_at, updated_at) VALUES (?, ?, NOW(), NOW())",
|
||||
[$key, $value]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Configuración actualizada y probada correctamente',
|
||||
'test_result' => $testResult
|
||||
]);
|
||||
|
||||
} elseif ($method === 'PUT') {
|
||||
// Solo probar conexión con configuración actual
|
||||
$currentToken = getConfigFromDB('whatsapp_token', WHATSAPP_TOKEN);
|
||||
$currentPhoneId = getConfigFromDB('whatsapp_phone_number_id', WHATSAPP_PHONE_NUMBER_ID);
|
||||
$currentApiUrl = getConfigFromDB('whatsapp_api_url', WHATSAPP_API_URL);
|
||||
|
||||
$testResult = testConnectionWithCredentials($currentToken, $currentPhoneId, $currentApiUrl);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'test_result' => $testResult
|
||||
]);
|
||||
|
||||
} else {
|
||||
throw new Exception('Método no permitido');
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
|
||||
function testConnectionWithCredentials($token, $phoneId, $apiUrl) {
|
||||
try {
|
||||
if (empty($token) || empty($phoneId)) {
|
||||
return ['success' => false, 'message' => 'Token o Phone ID vacío'];
|
||||
}
|
||||
|
||||
// Probar acceso al Phone Number ID
|
||||
$ch = curl_init();
|
||||
$url = $apiUrl . $phoneId;
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Content-Type: application/json'
|
||||
],
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_CONNECTTIMEOUT => 10,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_USERAGENT => 'WhatsApp-Bot-Diagnostic/1.0'
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
return ['success' => false, 'message' => 'Error de conexión: ' . $curlError];
|
||||
}
|
||||
|
||||
$decoded = json_decode($response, true);
|
||||
|
||||
if ($httpCode === 200 && $decoded) {
|
||||
$displayName = $decoded['display_phone_number'] ?? 'N/A';
|
||||
$verifiedName = $decoded['verified_name'] ?? 'No verificado';
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => "Conexión exitosa. Número: $displayName ($verifiedName)",
|
||||
'phone_info' => $decoded
|
||||
];
|
||||
} else {
|
||||
$errorMsg = 'Error desconocido';
|
||||
if ($decoded && isset($decoded['error'])) {
|
||||
$errorMsg = $decoded['error']['message'] ?? $errorMsg;
|
||||
$errorCode = $decoded['error']['code'] ?? 'N/A';
|
||||
$errorType = $decoded['error']['type'] ?? 'N/A';
|
||||
|
||||
$errorMsg = "[$errorCode/$errorType] $errorMsg";
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $errorMsg,
|
||||
'http_code' => $httpCode,
|
||||
'raw_response' => $response
|
||||
];
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
return ['success' => false, 'message' => 'Excepción: ' . $e->getMessage()];
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Obtener conversaciones recientes
|
||||
* Fecha: 13 de noviembre de 2025
|
||||
* Fecha: 4 de enero de 2026
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
@@ -14,28 +14,49 @@ header('Access-Control-Allow-Headers: Content-Type');
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Obtener conversaciones de la tabla conversations
|
||||
$conversations = $db->fetchAll(
|
||||
"SELECT
|
||||
c.id,
|
||||
c.user_id,
|
||||
c.content,
|
||||
COALESCE(c.content, '') as content,
|
||||
c.direction,
|
||||
c.message_type,
|
||||
c.status,
|
||||
c.created_at,
|
||||
u.phone_number,
|
||||
u.name
|
||||
COALESCE(u.name, u.phone_number) as name
|
||||
FROM conversations c
|
||||
JOIN users u ON c.user_id = u.id
|
||||
ORDER BY c.created_at DESC
|
||||
LIMIT 50"
|
||||
LIMIT 500"
|
||||
);
|
||||
|
||||
// Si no hay datos, retornar array vacío
|
||||
if (empty($conversations)) {
|
||||
$conversations = [];
|
||||
}
|
||||
|
||||
// Formatear datos para el frontend
|
||||
$conversations = array_map(function($conv) {
|
||||
return [
|
||||
'id' => intval($conv['id']),
|
||||
'user_id' => intval($conv['user_id']),
|
||||
'content' => $conv['content'] ?? '',
|
||||
'direction' => $conv['direction'] ?? 'incoming',
|
||||
'message_type' => $conv['message_type'] ?? 'text',
|
||||
'status' => $conv['status'] ?? 'sent',
|
||||
'created_at' => $conv['created_at'],
|
||||
'phone_number' => $conv['phone_number'],
|
||||
'name' => $conv['name'] ?? $conv['phone_number']
|
||||
];
|
||||
}, $conversations);
|
||||
|
||||
echo json_encode($conversations);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in get_conversations.php: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Error interno del servidor']);
|
||||
echo json_encode(['error' => 'Error interno del servidor: ' . $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Verificar autenticación
|
||||
requireAuthentication();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET');
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Verificar autenticación
|
||||
requireAuthentication();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET');
|
||||
|
||||
@@ -6,6 +6,16 @@
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Suprimir errores para obtener JSON limpio
|
||||
error_reporting(E_ERROR | E_PARSE);
|
||||
|
||||
// Modo debug: desactivar autenticación si existe el parámetro debug
|
||||
$debugMode = isset($_GET['debug']) && $_GET['debug'] === 'true';
|
||||
|
||||
if (!$debugMode) {
|
||||
requireAuthentication();
|
||||
}
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET');
|
||||
|
||||
+18
-2
@@ -6,6 +6,16 @@
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Suprimir errores para obtener JSON limpio
|
||||
error_reporting(E_ERROR | E_PARSE);
|
||||
|
||||
// Modo debug: desactivar autenticación si existe el parámetro debug
|
||||
$debugMode = isset($_GET['debug']) && $_GET['debug'] === 'true';
|
||||
|
||||
if (!$debugMode) {
|
||||
requireAuthentication();
|
||||
}
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET');
|
||||
@@ -27,11 +37,17 @@ try {
|
||||
ORDER BY name ASC"
|
||||
);
|
||||
|
||||
echo json_encode($templates);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $templates
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in get_templates.php: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Error interno del servidor']);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Error interno del servidor'
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Obtener mensajes de un usuario específico
|
||||
* Fecha: 4 de enero de 2026
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Verificar autenticación
|
||||
requireAuthentication();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
try {
|
||||
$userId = intval($_GET['user_id'] ?? 0);
|
||||
|
||||
if (!$userId) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'user_id es requerido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Obtener mensajes del usuario (de ambas tablas para compatibilidad)
|
||||
$messages = $db->fetchAll(
|
||||
"SELECT
|
||||
id,
|
||||
user_id,
|
||||
COALESCE(content, message_text) as content,
|
||||
direction,
|
||||
message_type,
|
||||
status,
|
||||
created_at
|
||||
FROM conversations
|
||||
WHERE user_id = :user_id
|
||||
UNION ALL
|
||||
SELECT
|
||||
id,
|
||||
user_id,
|
||||
message_text as content,
|
||||
direction,
|
||||
message_type,
|
||||
status,
|
||||
created_at
|
||||
FROM messages
|
||||
WHERE user_id = :user_id
|
||||
ORDER BY created_at ASC",
|
||||
['user_id' => $userId]
|
||||
);
|
||||
|
||||
// Si no hay mensajes en conversations, intentar con messages
|
||||
if (empty($messages)) {
|
||||
$messages = $db->fetchAll(
|
||||
"SELECT
|
||||
id,
|
||||
user_id,
|
||||
message_text as content,
|
||||
direction,
|
||||
message_type,
|
||||
status,
|
||||
created_at
|
||||
FROM messages
|
||||
WHERE user_id = :user_id
|
||||
ORDER BY created_at ASC",
|
||||
['user_id' => $userId]
|
||||
);
|
||||
}
|
||||
|
||||
// Formatear fechas y limpiar datos
|
||||
$messages = array_map(function($msg) {
|
||||
return [
|
||||
'id' => intval($msg['id']),
|
||||
'user_id' => intval($msg['user_id']),
|
||||
'content' => $msg['content'] ?? '',
|
||||
'direction' => $msg['direction'] ?? 'incoming',
|
||||
'message_type' => $msg['message_type'] ?? 'text',
|
||||
'status' => $msg['status'] ?? 'sent',
|
||||
'created_at' => $msg['created_at']
|
||||
];
|
||||
}, $messages);
|
||||
|
||||
echo json_encode($messages);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in get_user_messages.php: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Error interno del servidor']);
|
||||
}
|
||||
?>
|
||||
+18
-2
@@ -6,6 +6,16 @@
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Suprimir errores para obtener JSON limpio
|
||||
error_reporting(E_ERROR | E_PARSE);
|
||||
|
||||
// Modo debug: desactivar autenticación si existe el parámetro debug
|
||||
$debugMode = isset($_GET['debug']) && $_GET['debug'] === 'true';
|
||||
|
||||
if (!$debugMode) {
|
||||
requireAuthentication();
|
||||
}
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET');
|
||||
@@ -31,11 +41,17 @@ try {
|
||||
ORDER BY u.created_at DESC"
|
||||
);
|
||||
|
||||
echo json_encode($users);
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $users
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in get_users.php: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Error interno del servidor']);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Error interno del servidor'
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,326 @@
|
||||
<?php
|
||||
/**
|
||||
* API para gestionar configuraciones del sistema
|
||||
* Permite guardar y obtener configuraciones desde la base de datos
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Suprimir errores para obtener JSON limpio
|
||||
error_reporting(E_ERROR | E_PARSE);
|
||||
|
||||
// Modo debug: desactivar autenticación si existe el parámetro debug
|
||||
$debugMode = isset($_GET['debug']) && $_GET['debug'] === 'true';
|
||||
|
||||
if (!$debugMode) {
|
||||
requireAuthentication();
|
||||
} else {
|
||||
// Simular sesión para modo debug
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
$_SESSION['admin_logged_in'] = true;
|
||||
$_SESSION['admin_username'] = 'debug_user';
|
||||
}
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
try {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
handleGetRequest($action);
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
handlePostRequest($action);
|
||||
break;
|
||||
|
||||
case 'PUT':
|
||||
handlePutRequest($action);
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
handleDeleteRequest($action);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Método HTTP no permitido');
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
writeLog('ERROR', 'Error en API manage_config: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Maneja peticiones GET
|
||||
*/
|
||||
function handleGetRequest($action) {
|
||||
switch ($action) {
|
||||
case 'all':
|
||||
// Obtener todas las configuraciones
|
||||
$configs = getAllConfigsFromDB();
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $configs
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'whatsapp':
|
||||
// Obtener configuración específica de WhatsApp
|
||||
$whatsappConfig = getWhatsAppConfigFromDB();
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $whatsappConfig
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'validate_whatsapp':
|
||||
// Validar configuración de WhatsApp
|
||||
$validation = validateWhatsAppConfigFromDB();
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $validation
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'get':
|
||||
// Obtener configuración específica
|
||||
$key = $_GET['key'] ?? '';
|
||||
if (empty($key)) {
|
||||
throw new Exception('Clave de configuración requerida');
|
||||
}
|
||||
|
||||
$value = getConfigFromDB($key);
|
||||
$default = $_GET['default'] ?? null;
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'key' => $key,
|
||||
'value' => $value ?? $default,
|
||||
'exists' => $value !== null
|
||||
]
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
// Estado general del sistema de configuraciones
|
||||
$allConfigs = getAllConfigsFromDB();
|
||||
$whatsappValidation = validateWhatsAppConfigFromDB();
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'total_configs' => count($allConfigs),
|
||||
'whatsapp_configured' => $whatsappValidation['valid'],
|
||||
'database_migrated' => !empty($allConfigs),
|
||||
'installation_completed' => isInstallationCompleted()
|
||||
]
|
||||
]);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Acción GET no reconocida');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maneja peticiones POST
|
||||
*/
|
||||
function handlePostRequest($action) {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception('JSON inválido en el cuerpo de la petición');
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'save':
|
||||
// Guardar configuración individual
|
||||
$key = $input['key'] ?? '';
|
||||
$value = $input['value'] ?? '';
|
||||
$description = $input['description'] ?? null;
|
||||
|
||||
if (empty($key)) {
|
||||
throw new Exception('Clave de configuración requerida');
|
||||
}
|
||||
|
||||
$result = saveConfigToDB($key, $value, $description);
|
||||
|
||||
echo json_encode([
|
||||
'success' => $result,
|
||||
'message' => $result ? 'Configuración guardada exitosamente' : 'Error al guardar configuración'
|
||||
]);
|
||||
|
||||
if ($result) {
|
||||
writeLog('INFO', "Configuración '$key' actualizada", ['user' => $_SESSION['admin_username'] ?? 'unknown']);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'save_multiple':
|
||||
// Guardar múltiples configuraciones
|
||||
$configs = $input['configs'] ?? [];
|
||||
|
||||
if (empty($configs) || !is_array($configs)) {
|
||||
throw new Exception('Array de configuraciones requerido');
|
||||
}
|
||||
|
||||
$result = saveMultipleConfigsToDB($configs);
|
||||
|
||||
echo json_encode([
|
||||
'success' => $result,
|
||||
'message' => $result ? 'Configuraciones guardadas exitosamente' : 'Error al guardar configuraciones',
|
||||
'count' => count($configs)
|
||||
]);
|
||||
|
||||
if ($result) {
|
||||
writeLog('INFO', "Guardadas " . count($configs) . " configuraciones", [
|
||||
'keys' => array_keys($configs),
|
||||
'user' => $_SESSION['admin_username'] ?? 'unknown'
|
||||
]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'save_whatsapp':
|
||||
// Guardar configuración específica de WhatsApp
|
||||
$whatsappConfig = $input['whatsapp'] ?? [];
|
||||
|
||||
if (empty($whatsappConfig)) {
|
||||
throw new Exception('Configuración de WhatsApp requerida');
|
||||
}
|
||||
|
||||
$result = saveWhatsAppConfigToDB($whatsappConfig);
|
||||
|
||||
// Validar la configuración guardada
|
||||
$validation = validateWhatsAppConfigFromDB();
|
||||
|
||||
echo json_encode([
|
||||
'success' => $result,
|
||||
'message' => $result ? 'Configuración de WhatsApp guardada exitosamente' : 'Error al guardar configuración de WhatsApp',
|
||||
'validation' => $validation
|
||||
]);
|
||||
|
||||
if ($result) {
|
||||
writeLog('INFO', 'Configuración de WhatsApp actualizada', [
|
||||
'configured_fields' => $validation['configured_fields'],
|
||||
'user' => $_SESSION['admin_username'] ?? 'unknown'
|
||||
]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'initialize_defaults':
|
||||
// Inicializar configuraciones por defecto
|
||||
$result = initializeDefaultConfigs();
|
||||
|
||||
echo json_encode([
|
||||
'success' => $result,
|
||||
'message' => $result ? 'Configuraciones por defecto inicializadas' : 'Error al inicializar configuraciones'
|
||||
]);
|
||||
|
||||
if ($result) {
|
||||
writeLog('INFO', 'Configuraciones por defecto inicializadas', [
|
||||
'user' => $_SESSION['admin_username'] ?? 'unknown'
|
||||
]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'migrate_to_database':
|
||||
// Migrar configuraciones a la base de datos
|
||||
$result = migrateConfigsToDatabase();
|
||||
|
||||
echo json_encode([
|
||||
'success' => $result,
|
||||
'message' => $result ? 'Configuraciones migradas exitosamente a la base de datos' : 'Error en la migración'
|
||||
]);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Acción POST no reconocida');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maneja peticiones PUT (actualizar)
|
||||
*/
|
||||
function handlePutRequest($action) {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception('JSON inválido en el cuerpo de la petición');
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'update':
|
||||
// Actualizar configuración específica
|
||||
$key = $input['key'] ?? '';
|
||||
$value = $input['value'] ?? '';
|
||||
$description = $input['description'] ?? null;
|
||||
|
||||
if (empty($key)) {
|
||||
throw new Exception('Clave de configuración requerida');
|
||||
}
|
||||
|
||||
$result = saveConfigToDB($key, $value, $description);
|
||||
|
||||
echo json_encode([
|
||||
'success' => $result,
|
||||
'message' => $result ? 'Configuración actualizada exitosamente' : 'Error al actualizar configuración'
|
||||
]);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Acción PUT no reconocida');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maneja peticiones DELETE
|
||||
*/
|
||||
function handleDeleteRequest($action) {
|
||||
switch ($action) {
|
||||
case 'delete':
|
||||
$key = $_GET['key'] ?? '';
|
||||
|
||||
if (empty($key)) {
|
||||
throw new Exception('Clave de configuración requerida');
|
||||
}
|
||||
|
||||
$result = deleteConfigFromDB($key);
|
||||
|
||||
echo json_encode([
|
||||
'success' => $result,
|
||||
'message' => $result ? 'Configuración eliminada exitosamente' : 'Error al eliminar configuración'
|
||||
]);
|
||||
|
||||
if ($result) {
|
||||
writeLog('INFO', "Configuración '$key' eliminada", [
|
||||
'user' => $_SESSION['admin_username'] ?? 'unknown'
|
||||
]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'clear_cache':
|
||||
// Limpiar cache de configuraciones
|
||||
clearConfigCache();
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Cache de configuraciones limpiado'
|
||||
]);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Acción DELETE no reconocida');
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
|
||||
try {
|
||||
// Debug mode
|
||||
$debugMode = isset($_GET['debug']) || php_sapi_name() === 'cli';
|
||||
if (!$debugMode) {
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'No autorizado']);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Método no permitido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'JSON inválido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
$action = $input['action'] ?? '';
|
||||
|
||||
switch ($action) {
|
||||
case 'update_status':
|
||||
$templateId = intval($input['template_id'] ?? 0);
|
||||
$newStatus = $input['status'] ?? '';
|
||||
|
||||
if (!$templateId || !in_array($newStatus, ['pending', 'approved', 'rejected'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'template_id y status válido requeridos']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $db->query(
|
||||
"UPDATE message_templates SET status = ?, updated_at = NOW() WHERE id = ?",
|
||||
[$newStatus, $templateId]
|
||||
);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Estado de plantilla actualizado correctamente',
|
||||
'template_id' => $templateId,
|
||||
'new_status' => $newStatus
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Plantilla no encontrada o no se pudo actualizar'
|
||||
]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'bulk_approve':
|
||||
$templateIds = $input['template_ids'] ?? [];
|
||||
|
||||
if (empty($templateIds) || !is_array($templateIds)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'template_ids requerido como array']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$placeholders = str_repeat('?,', count($templateIds) - 1) . '?';
|
||||
$stmt = $db->query(
|
||||
"UPDATE message_templates SET status = 'approved', updated_at = NOW() WHERE id IN ($placeholders)",
|
||||
$templateIds
|
||||
);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Plantillas aprobadas masivamente',
|
||||
'updated_count' => $stmt->rowCount()
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'sync_from_meta':
|
||||
// Función futura para sincronizar desde Meta API
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Función no implementada aún',
|
||||
'note' => 'Para implementar: consultar API de Meta para estado real de plantillas'
|
||||
]);
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Acción no válida']);
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Error interno: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Verificar autenticación
|
||||
requireAuthentication();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST');
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Guardar/Crear plantilla de mensaje
|
||||
* Fecha: 3 de enero de 2026
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Suprimir errores para obtener JSON limpio
|
||||
error_reporting(E_ERROR | E_PARSE);
|
||||
|
||||
// Modo debug: desactivar autenticación si existe el parámetro debug
|
||||
$debugMode = isset($_GET['debug']) && $_GET['debug'] === 'true';
|
||||
|
||||
if (!$debugMode) {
|
||||
requireAuthentication();
|
||||
}
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Método no permitido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Datos inválidos']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$name = trim($input['name'] ?? '');
|
||||
$templateName = trim($input['whatsapp_name'] ?? $input['template_name'] ?? '');
|
||||
$languageCode = $input['language'] ?? $input['language_code'] ?? 'es';
|
||||
$category = $input['category'] ?? 'utility';
|
||||
|
||||
// Validaciones
|
||||
if (empty($name)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'El nombre es requerido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (empty($templateName)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'El nombre de la plantilla de WhatsApp es requerido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Verificar si ya existe
|
||||
$db = Database::getInstance();
|
||||
$existing = $db->fetch(
|
||||
"SELECT id FROM message_templates WHERE name = :name OR template_name = :template_name",
|
||||
['name' => $name, 'template_name' => $templateName]
|
||||
);
|
||||
|
||||
if ($existing) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Ya existe una plantilla con ese nombre']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Crear la plantilla
|
||||
$templateId = $db->insert('message_templates', [
|
||||
'name' => $name,
|
||||
'template_name' => $templateName,
|
||||
'language_code' => $languageCode,
|
||||
'category' => $category,
|
||||
'status' => 'pending', // Siempre inicia como pendiente
|
||||
'created_at' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
if ($templateId) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Plantilla creada correctamente. Estado: Pendiente de aprobación por WhatsApp.',
|
||||
'id' => $templateId,
|
||||
'note' => 'La plantilla debe ser aprobada en WhatsApp Business Manager antes de poder usarse.'
|
||||
]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Error creando la plantilla']);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in save_template.php: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Error interno del servidor: ' . $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Verificar autenticación
|
||||
requireAuthentication();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST');
|
||||
|
||||
+315
-7
@@ -1,11 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Enviar mensaje individual
|
||||
* Fecha: 13 de noviembre de 2025
|
||||
* Fecha: 4 de enero de 2026
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Suprimir errores para obtener JSON limpio
|
||||
error_reporting(E_ERROR | E_PARSE);
|
||||
|
||||
// Modo debug: desactivar autenticación si existe el parámetro debug
|
||||
$debugMode = isset($_GET['debug']) && $_GET['debug'] === 'true';
|
||||
|
||||
if (!$debugMode) {
|
||||
requireAuthentication();
|
||||
}
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: POST');
|
||||
@@ -20,15 +30,84 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input || !isset($input['recipient'])) {
|
||||
if (!$input) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Datos inválidos']);
|
||||
echo json_encode(['error' => 'JSON inválido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Modo debug: simular envío exitoso
|
||||
if ($debugMode) {
|
||||
$recipient = $input['recipient'] ?? $input['user_id'] ?? 'unknown';
|
||||
$message = $input['message'] ?? 'mensaje de prueba';
|
||||
$type = $input['type'] ?? 'text';
|
||||
$template = $input['template'] ?? $input['template_name'] ?? null;
|
||||
|
||||
$responseData = [
|
||||
'recipient' => $recipient,
|
||||
'type' => $type,
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'warning' => 'Para envío real, verifica que el usuario haya respondido en las últimas 24h o usa plantillas'
|
||||
];
|
||||
|
||||
if ($type === 'template') {
|
||||
// Detectar formato de plantilla
|
||||
if (isset($input['template']) && is_array($input['template'])) {
|
||||
// Formato WhatsApp estándar
|
||||
$templateData = $input['template'];
|
||||
$responseData['template_name'] = $templateData['name'] ?? 'unknown';
|
||||
$responseData['language'] = $templateData['language']['code'] ?? 'es';
|
||||
$responseData['format'] = 'whatsapp_standard';
|
||||
$message = "Plantilla WhatsApp: {$templateData['name']}";
|
||||
} else {
|
||||
// Formato simple
|
||||
$template = $input['template'] ?? $input['template_name'] ?? null;
|
||||
$responseData['template_name'] = $template;
|
||||
$responseData['language'] = $input['language'] ?? 'es';
|
||||
$responseData['format'] = 'simple';
|
||||
$message = "Plantilla: {$template}";
|
||||
}
|
||||
} else {
|
||||
$responseData['message'] = $message;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Mensaje simulado enviado correctamente (modo debug) - NOTA: En producción, usa plantillas para números que no han respondido en 24h',
|
||||
'data' => $responseData
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
// Modo 1: Envío directo a número de teléfono (formato original)
|
||||
if (isset($input['recipient'])) {
|
||||
$recipient = $input['recipient'];
|
||||
$type = $input['type'] ?? 'text';
|
||||
|
||||
// Verificar si es mensaje de texto y si el usuario respondió en las últimas 24h
|
||||
if ($type === 'text') {
|
||||
$stmt = $db->query(
|
||||
"SELECT MAX(created_at) as last_message FROM conversations
|
||||
WHERE phone_number = ? AND direction = 'incoming'
|
||||
AND created_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)",
|
||||
[$recipient]
|
||||
);
|
||||
$lastMessage = $stmt->fetch();
|
||||
|
||||
if (!$lastMessage || !$lastMessage['last_message']) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'No se puede enviar mensaje de texto libre. El usuario no ha respondido en las últimas 24 horas.',
|
||||
'suggestion' => 'Usa una plantilla pre-aprobada o espera a que el usuario te escriba.',
|
||||
'error_code' => 'OUTSIDE_24H_WINDOW'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$whatsappService = new WhatsAppService();
|
||||
$response = null;
|
||||
|
||||
@@ -39,36 +118,265 @@ try {
|
||||
echo json_encode(['error' => 'Mensaje requerido']);
|
||||
exit;
|
||||
}
|
||||
// DEBUG: Mostrar datos de envío de mensaje de texto
|
||||
error_log("=== DEBUG WHATSAPP TEXT MESSAGE ===");
|
||||
error_log("Recipient: " . $recipient);
|
||||
error_log("Message: " . $input['message']);
|
||||
error_log("Method: sendTextMessage()");
|
||||
error_log("====================================");
|
||||
|
||||
$response = $whatsappService->sendTextMessage($recipient, $input['message']);
|
||||
break;
|
||||
|
||||
case 'template':
|
||||
if (!isset($input['template']) || empty($input['template'])) {
|
||||
// Formato 1: Simple (actual)
|
||||
if (isset($input['template_name']) || (isset($input['template']) && !is_array($input['template']))) {
|
||||
$template = $input['template'] ?? $input['template_name'] ?? '';
|
||||
if (empty($template)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Plantilla requerida']);
|
||||
echo json_encode(['error' => 'Template requerido']);
|
||||
exit;
|
||||
}
|
||||
$language = $input['language'] ?? 'es';
|
||||
$parameters = $input['parameters'] ?? [];
|
||||
$response = $whatsappService->sendTemplateMessage($recipient, $input['template'], $language, $parameters);
|
||||
|
||||
// Verificar plantilla en base de datos (opcional)
|
||||
$templateRecord = null;
|
||||
try {
|
||||
$stmt = $db->query(
|
||||
"SELECT * FROM message_templates WHERE template_name = ? LIMIT 1",
|
||||
[$template]
|
||||
);
|
||||
$templateRecord = $stmt->fetch();
|
||||
} catch (Exception $e) {
|
||||
// Si no existe la tabla, continuar sin validación local
|
||||
error_log("Template validation skipped: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Solo validar si la tabla existe y encontramos registros
|
||||
if ($templateRecord && $templateRecord['status'] !== 'approved') {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Plantilla no aprobada en el sistema local',
|
||||
'template_name' => $template,
|
||||
'template_status' => $templateRecord['status'],
|
||||
'suggestions' => [
|
||||
'1. Aprueba la plantilla desde la pestaña Plantillas',
|
||||
'2. O verifica que esté aprobada en WhatsApp Business Manager',
|
||||
'3. Usa el nombre exacto de la plantilla'
|
||||
]
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Usar el idioma de la base de datos si no se especifica
|
||||
if ($language === 'es' && $templateRecord['language_code']) {
|
||||
$language = $templateRecord['language_code'];
|
||||
}
|
||||
|
||||
// DEBUG: Mostrar datos de envío de plantilla formato simple
|
||||
error_log("=== DEBUG WHATSAPP TEMPLATE MESSAGE (SIMPLE FORMAT) ===");
|
||||
error_log("Recipient: " . $recipient);
|
||||
error_log("Template Name: " . $template);
|
||||
error_log("Language: " . $language);
|
||||
error_log("Parameters: " . json_encode($parameters));
|
||||
error_log("Template Record Status: " . ($templateRecord ? $templateRecord['status'] : 'not found in DB'));
|
||||
error_log("Method: sendTemplateMessage()");
|
||||
error_log("========================================================");
|
||||
|
||||
$response = $whatsappService->sendTemplateMessage($recipient, $template, $language, $parameters);
|
||||
}
|
||||
// Formato 2: WhatsApp Business API estándar
|
||||
elseif (isset($input['template']) && is_array($input['template'])) {
|
||||
$templateData = $input['template'];
|
||||
$templateName = $templateData['name'] ?? '';
|
||||
$languageCode = $templateData['language']['code'] ?? 'es';
|
||||
$components = $templateData['components'] ?? [];
|
||||
|
||||
if (empty($templateName)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Template name requerido en formato WhatsApp']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Convertir formato WhatsApp a nuestro servicio
|
||||
$parameters = [];
|
||||
foreach ($components as $component) {
|
||||
if ($component['type'] === 'body' && isset($component['parameters'])) {
|
||||
foreach ($component['parameters'] as $param) {
|
||||
$parameters[] = $param['text'] ?? '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DEBUG: Mostrar datos de envío de plantilla formato WhatsApp estándar
|
||||
error_log("=== DEBUG WHATSAPP TEMPLATE MESSAGE (WHATSAPP STANDARD FORMAT) ===");
|
||||
error_log("Recipient: " . $recipient);
|
||||
error_log("Template Name: " . $templateName);
|
||||
error_log("Language Code: " . $languageCode);
|
||||
error_log("Original Components: " . json_encode($components));
|
||||
error_log("Converted Parameters: " . json_encode($parameters));
|
||||
error_log("Full Template Data: " . json_encode($templateData));
|
||||
error_log("Method: sendTemplateMessage()");
|
||||
error_log("=================================================================");
|
||||
|
||||
$response = $whatsappService->sendTemplateMessage($recipient, $templateName, $languageCode, $parameters);
|
||||
}
|
||||
else {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Template requerido (formato simple o WhatsApp estándar)']);
|
||||
exit;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Tipo de mensaje no válido']);
|
||||
echo json_encode(['error' => 'Tipo de mensaje no soportado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($response) {
|
||||
// DEBUG: Mostrar respuesta exitosa de WhatsApp
|
||||
error_log("=== DEBUG WHATSAPP SUCCESS RESPONSE ===");
|
||||
error_log("Response: " . json_encode($response));
|
||||
error_log("======================================");
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Mensaje enviado correctamente',
|
||||
'whatsapp_response' => $response
|
||||
]);
|
||||
} else {
|
||||
// DEBUG: Mostrar error de respuesta de WhatsApp
|
||||
error_log("=== DEBUG WHATSAPP ERROR RESPONSE ===");
|
||||
error_log("Response was NULL or FALSE");
|
||||
error_log("Type: " . $type);
|
||||
error_log("====================================");
|
||||
|
||||
// Error específico para plantillas con soluciones detalladas
|
||||
if ($type === 'template') {
|
||||
$templateName = $input['template_name'] ?? $input['template'] ?? 'unknown';
|
||||
$language = $input['language'] ?? 'es';
|
||||
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Error #132001: Template name does not exist in the translation',
|
||||
'template_name' => $templateName,
|
||||
'language' => $language,
|
||||
'error_analysis' => [
|
||||
'El error #132001 indica que WhatsApp no encuentra la plantilla',
|
||||
'Esto puede suceder por varias razones:'
|
||||
],
|
||||
'solutions' => [
|
||||
'1. CREAR PLANTILLA: Ve a WhatsApp Business Manager → Account Tools → Message Templates',
|
||||
'2. APROBAR PLANTILLA: Asegúrate que Meta haya aprobado tu plantilla',
|
||||
'3. NOMBRE EXACTO: Usa el nombre exacto (case-sensitive)',
|
||||
'4. IDIOMA CORRECTO: Usa el código exacto (en_US, es_ES, etc.)',
|
||||
'5. TIEMPO DE PROPAGACIÓN: Espera 15-30 min después de la aprobación'
|
||||
],
|
||||
'debug_info' => [
|
||||
'template_attempted' => $templateName,
|
||||
'language_attempted' => $language,
|
||||
'common_templates' => ['hello_world', 'sample_shipping_confirmation'],
|
||||
'common_languages' => ['en_US', 'es_ES', 'es_MX']
|
||||
],
|
||||
'next_steps' => [
|
||||
'1. Verifica en WhatsApp Business Manager si la plantilla existe',
|
||||
'2. Crea una plantilla simple primero (ej: "hello_world")',
|
||||
'3. Usa el modo debug para probar sin envío real'
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Error enviando mensaje']);
|
||||
}
|
||||
}
|
||||
|
||||
// Modo 2: Envío a usuario existente (nuevo formato para conversaciones)
|
||||
} elseif (isset($input['user_id']) && isset($input['message'])) {
|
||||
$userId = intval($input['user_id']);
|
||||
$message = trim($input['message']);
|
||||
|
||||
if (!$userId || !$message) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'user_id y message son requeridos']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Obtener información del usuario
|
||||
$stmt = $db->query(
|
||||
"SELECT phone_number, name FROM users WHERE id = ?",
|
||||
[$userId]
|
||||
);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Usuario no encontrado']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Enviar mensaje via WhatsApp API
|
||||
$whatsappService = new WhatsAppService();
|
||||
|
||||
// DEBUG: Mostrar datos de envío a usuario existente
|
||||
error_log("=== DEBUG WHATSAPP MESSAGE TO EXISTING USER ===");
|
||||
error_log("User ID: " . $userId);
|
||||
error_log("User Name: " . ($user['name'] ?? 'N/A'));
|
||||
error_log("Phone Number: " . $user['phone_number']);
|
||||
error_log("Message: " . $message);
|
||||
error_log("Method: sendTextMessage()");
|
||||
error_log("===============================================");
|
||||
|
||||
$response = $whatsappService->sendTextMessage($user['phone_number'], $message);
|
||||
|
||||
if ($response && isset($response['messages']) && !empty($response['messages'])) {
|
||||
// Guardar mensaje en la base de datos
|
||||
$messageData = [
|
||||
'user_id' => $userId,
|
||||
'content' => $message,
|
||||
'direction' => 'outgoing',
|
||||
'message_type' => 'text',
|
||||
'status' => 'sent',
|
||||
'message_id' => $response['messages'][0]['id'] ?? null,
|
||||
'created_at' => date('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
// Intentar insertar en conversations primero
|
||||
try {
|
||||
$stmt = $db->query(
|
||||
"INSERT INTO conversations (user_id, content, direction, message_type, status, message_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
array_values($messageData)
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
// Si falla, intentar en messages
|
||||
try {
|
||||
$stmt = $db->query(
|
||||
"INSERT INTO messages (user_id, message_text, direction, message_type, status, message_id, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
[$userId, $message, 'outgoing', 'text', 'sent', $response['messages'][0]['id'] ?? null, date('Y-m-d H:i:s')]
|
||||
);
|
||||
} catch (Exception $e2) {
|
||||
error_log("Error guardando mensaje: " . $e2->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Mensaje enviado correctamente',
|
||||
'whatsapp_response' => $response
|
||||
]);
|
||||
} else {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Error al enviar mensaje por WhatsApp'
|
||||
]);
|
||||
}
|
||||
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Formato de datos inválido. Se requiere recipient o user_id+message']);
|
||||
exit;
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in send_message.php: " . $e->getMessage());
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* API de prueba para configuraciones (sin autenticación)
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Simular autenticación para pruebas
|
||||
session_start();
|
||||
$_SESSION['admin_logged_in'] = true;
|
||||
$_SESSION['admin_username'] = 'test_admin';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
try {
|
||||
// Respuesta simple para test de conectividad
|
||||
if ($action === 'ping') {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'API funcionando correctamente',
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'method' => $method
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
switch ($action) {
|
||||
case 'status':
|
||||
// Estado general del sistema
|
||||
$allConfigs = getAllConfigsFromDB();
|
||||
$whatsappValidation = validateWhatsAppConfigFromDB();
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'total_configs' => count($allConfigs),
|
||||
'whatsapp_configured' => $whatsappValidation['valid'],
|
||||
'database_migrated' => !empty($allConfigs),
|
||||
'installation_completed' => isInstallationCompleted(),
|
||||
'server_time' => date('Y-m-d H:i:s')
|
||||
]
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'all':
|
||||
// Obtener todas las configuraciones
|
||||
$configs = getAllConfigsFromDB();
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $configs
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'whatsapp':
|
||||
// Obtener configuración de WhatsApp
|
||||
$whatsappConfig = getWhatsAppConfigFromDB();
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => $whatsappConfig
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'test_db':
|
||||
// Test de conexión a BD
|
||||
try {
|
||||
$pdo = createDbConnection();
|
||||
$tableExists = $pdo->query("SHOW TABLES LIKE 'system_config'")->rowCount() > 0;
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'db_connected' => true,
|
||||
'table_exists' => $tableExists,
|
||||
'config_count' => $tableExists ? $pdo->query("SELECT COUNT(*) FROM system_config")->fetchColumn() : 0
|
||||
]
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Error de BD: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Acción GET no reconocida: ' . $action);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
switch ($action) {
|
||||
case 'save_test':
|
||||
// Guardar configuración de prueba
|
||||
$key = 'api_test_' . time();
|
||||
$value = 'Test desde API - ' . date('Y-m-d H:i:s');
|
||||
|
||||
$result = saveConfigToDB($key, $value, 'Prueba desde API');
|
||||
|
||||
echo json_encode([
|
||||
'success' => $result,
|
||||
'message' => $result ? 'Configuración guardada correctamente' : 'Error guardando configuración',
|
||||
'data' => [
|
||||
'key' => $key,
|
||||
'value' => $value
|
||||
]
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'save_whatsapp_test':
|
||||
// Guardar config de WhatsApp de prueba
|
||||
$testConfig = [
|
||||
'token' => 'test_token_' . time(),
|
||||
'phone_number_id' => '123456789',
|
||||
'webhook_verify_token' => 'webhook_' . time()
|
||||
];
|
||||
|
||||
$result = saveWhatsAppConfigToDB($testConfig);
|
||||
|
||||
echo json_encode([
|
||||
'success' => $result,
|
||||
'message' => $result ? 'WhatsApp config guardada' : 'Error guardando WhatsApp config',
|
||||
'data' => $testConfig
|
||||
]);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Acción POST no reconocida: ' . $action);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Método HTTP no permitido: ' . $method);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* API - Actualizar estado de plantilla
|
||||
* Fecha: 3 de enero de 2026
|
||||
*/
|
||||
|
||||
require_once '../config/config.php';
|
||||
|
||||
// Verificar autenticación
|
||||
requireAuthentication();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Access-Control-Allow-Methods: PUT, POST');
|
||||
header('Access-Control-Allow-Headers: Content-Type');
|
||||
|
||||
if (!in_array($_SERVER['REQUEST_METHOD'], ['PUT', 'POST'])) {
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Método no permitido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input || !isset($input['id']) || !isset($input['status'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'ID y estado requeridos']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$templateId = (int) $input['id'];
|
||||
$status = $input['status'];
|
||||
|
||||
// Validar estado
|
||||
if (!in_array($status, ['pending', 'approved', 'rejected'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Estado inválido']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Verificar que la plantilla existe
|
||||
$template = $db->fetch(
|
||||
"SELECT * FROM message_templates WHERE id = :id",
|
||||
['id' => $templateId]
|
||||
);
|
||||
|
||||
if (!$template) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Plantilla no encontrada']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Actualizar estado
|
||||
$updated = $db->update('message_templates',
|
||||
['status' => $status],
|
||||
['id' => $templateId]
|
||||
);
|
||||
|
||||
if ($updated) {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Estado de plantilla actualizado correctamente',
|
||||
'template' => $template['name'],
|
||||
'new_status' => $status
|
||||
]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Error actualizando el estado']);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Error in update_template_status.php: " . $e->getMessage());
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Error interno del servidor: ' . $e->getMessage()]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,213 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Aprobación de Plantillas WhatsApp</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.status-pending { background-color: #fff3cd; }
|
||||
.status-approved { background-color: #d1edff; }
|
||||
.status-rejected { background-color: #f8d7da; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<h1 class="mb-4">🔧 Aprobación de Plantillas WhatsApp</h1>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<h5>📋 Información Importante</h5>
|
||||
<p><strong>Error actual:</strong> "#132001 Template name does not exist in the translation"</p>
|
||||
<p><strong>Causa:</strong> Las plantillas están en estado "pending" en lugar de "approved"</p>
|
||||
<p><strong>Solución:</strong> Aprobar las plantillas que ya están validadas en WhatsApp Business Manager</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>📊 Resumen de Plantillas</h5>
|
||||
</div>
|
||||
<div class="card-body" id="summary">
|
||||
<p>Cargando...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>⚡ Acciones Rápidas</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<button class="btn btn-success w-100 mb-3" onclick="approveAllTemplates()">
|
||||
✅ Aprobar Todas las Plantillas
|
||||
</button>
|
||||
<button class="btn btn-info w-100" onclick="refreshData()">
|
||||
🔄 Actualizar Estado
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h5>📝 Lista de Plantillas</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="templates-list">
|
||||
<p>Cargando plantillas...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h5>🎯 Próximos Pasos</h5>
|
||||
<ol>
|
||||
<li><strong>Verificar en WhatsApp Business Manager:</strong> Asegúrate de que estas plantillas estén realmente aprobadas en tu cuenta de WhatsApp Business</li>
|
||||
<li><strong>Aprobar plantillas:</strong> Usa el botón "Aprobar Todas" si las plantillas están validadas por Meta</li>
|
||||
<li><strong>Probar envío:</strong> Después de aprobar, prueba enviar un mensaje usando las plantillas aprobadas</li>
|
||||
<li><strong>Crear nuevas plantillas:</strong> Si necesitas plantillas específicas, créalas primero en WhatsApp Business Manager</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning mt-4">
|
||||
<h6>⚠️ Advertencia</h6>
|
||||
<p>Solo aprueba plantillas que realmente estén aprobadas en tu WhatsApp Business Manager. Aprobar plantillas que no están validadas por Meta no solucionará el error.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// Cargar datos al inicio
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
refreshData();
|
||||
});
|
||||
|
||||
function refreshData() {
|
||||
fetch('/api/approve_templates.php', {
|
||||
method: 'GET'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
updateSummary(data.summary);
|
||||
updateTemplatesList(data.templates);
|
||||
} else {
|
||||
alert('Error al cargar datos: ' + data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Error de conexión');
|
||||
});
|
||||
}
|
||||
|
||||
function updateSummary(summary) {
|
||||
const summaryDiv = document.getElementById('summary');
|
||||
summaryDiv.innerHTML = `
|
||||
<div class="row text-center">
|
||||
<div class="col-md-4">
|
||||
<h4 class="text-primary">${summary.total}</h4>
|
||||
<p>Total</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h4 class="text-success">${summary.approved}</h4>
|
||||
<p>Aprobadas</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h4 class="text-warning">${summary.pending}</h4>
|
||||
<p>Pendientes</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function updateTemplatesList(templates) {
|
||||
const listDiv = document.getElementById('templates-list');
|
||||
|
||||
if (templates.length === 0) {
|
||||
listDiv.innerHTML = '<p class="text-muted">No hay plantillas registradas</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="table-responsive"><table class="table table-striped"><thead><tr><th>Nombre</th><th>Estado</th><th>Idioma</th><th>Categoría</th><th>Acción</th></tr></thead><tbody>';
|
||||
|
||||
templates.forEach(template => {
|
||||
const statusClass = `status-${template.status}`;
|
||||
const statusText = template.status === 'approved' ? '✅ Aprobada' :
|
||||
template.status === 'pending' ? '⏳ Pendiente' :
|
||||
'❌ Rechazada';
|
||||
|
||||
html += `
|
||||
<tr class="${statusClass}">
|
||||
<td><strong>${template.name}</strong></td>
|
||||
<td>${statusText}</td>
|
||||
<td>${template.language || 'N/A'}</td>
|
||||
<td>${template.category || 'N/A'}</td>
|
||||
<td>
|
||||
${template.status === 'pending' ?
|
||||
`<button class="btn btn-sm btn-success" onclick="approveTemplate('${template.name}')">
|
||||
Aprobar
|
||||
</button>` :
|
||||
'<span class="text-muted">-</span>'
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</tbody></table></div>';
|
||||
listDiv.innerHTML = html;
|
||||
}
|
||||
|
||||
function approveAllTemplates() {
|
||||
if (!confirm('¿Estás seguro de que quieres aprobar todas las plantillas pendientes?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'approve_all');
|
||||
|
||||
fetch('/api/approve_templates.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
alert(data.message);
|
||||
if (data.success) {
|
||||
refreshData();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Error de conexión');
|
||||
});
|
||||
}
|
||||
|
||||
function approveTemplate(templateName) {
|
||||
const formData = new FormData();
|
||||
formData.append('action', 'approve_specific');
|
||||
formData.append('template_name', templateName);
|
||||
|
||||
fetch('/api/approve_templates.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
alert(data.message);
|
||||
if (data.success) {
|
||||
refreshData();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Error de conexión');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,215 @@
|
||||
/* Estilos adicionales para el panel de diagnóstico */
|
||||
|
||||
.diagnostic-results {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.diagnostic-results .list-group-item {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.diagnostic-results .list-group-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.diagnostic-results h6 {
|
||||
color: #495057;
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 0.5rem;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.diagnostic-results .badge {
|
||||
font-size: 0.7em;
|
||||
min-width: 20px;
|
||||
}
|
||||
|
||||
.diagnostic-results .alert {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.diagnostic-results .alert small {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.spinner-border-sm {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
/* Mejoras en tabs */
|
||||
.nav-pills .nav-link {
|
||||
color: #6c757d;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-pills .nav-link.active {
|
||||
background-color: #0d6efd;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-pills .nav-link:hover:not(.active) {
|
||||
background-color: #f8f9fa;
|
||||
color: #0d6efd;
|
||||
}
|
||||
|
||||
/* Mejoras en cards */
|
||||
.card {
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
border: 1px solid rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
|
||||
}
|
||||
|
||||
/* Mejoras en modales */
|
||||
.modal-content {
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Mejoras en alertas */
|
||||
.alert {
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
/* Estilos para mensajes de chat */
|
||||
.message-bubble {
|
||||
max-width: 70%;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-bubble.outgoing {
|
||||
background-color: #dcf8c6;
|
||||
margin-left: auto;
|
||||
border-bottom-right-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.message-bubble.incoming {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e9ecef;
|
||||
margin-right: auto;
|
||||
border-bottom-left-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.message-meta {
|
||||
font-size: 0.75rem;
|
||||
color: #6c757d;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* Tabla responsive mejorada */
|
||||
.table-responsive {
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table th {
|
||||
background-color: #f8f9fa;
|
||||
border-top: none;
|
||||
font-weight: 600;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Botones mejorados */
|
||||
.btn {
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Estados de conexión */
|
||||
.status-indicator {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.status-indicator.online {
|
||||
background-color: #28a745;
|
||||
}
|
||||
|
||||
.status-indicator.offline {
|
||||
background-color: #dc3545;
|
||||
}
|
||||
|
||||
.status-indicator.warning {
|
||||
background-color: #ffc107;
|
||||
}
|
||||
|
||||
/* Loading states */
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Responsive improvements */
|
||||
@media (max-width: 768px) {
|
||||
.nav-pills {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.nav-pills .nav-link {
|
||||
text-align: center;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
max-width: 85%;
|
||||
}
|
||||
|
||||
.diagnostic-results .row {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Animaciones sutiles */
|
||||
.card, .btn, .alert {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Mejoras en formularios */
|
||||
.form-control:focus {
|
||||
border-color: #86b7fe;
|
||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
.form-select:focus {
|
||||
border-color: #86b7fe;
|
||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
@@ -110,6 +110,29 @@ body {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Divisor del sidebar */
|
||||
.sidebar-divider {
|
||||
margin: 10px 20px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* Botón de logout en sidebar */
|
||||
.sidebar-menu .logout-link {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
background: rgba(220, 53, 69, 0.1);
|
||||
border-top: 1px solid rgba(220, 53, 69, 0.3);
|
||||
}
|
||||
|
||||
.sidebar-menu .logout-link:hover {
|
||||
color: white;
|
||||
background: rgba(220, 53, 69, 0.2);
|
||||
border-color: rgba(220, 53, 69, 0.4);
|
||||
}
|
||||
|
||||
.sidebar-menu .logout-link::before {
|
||||
background: #dc3545;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.main-content {
|
||||
margin-left: 280px;
|
||||
@@ -373,6 +396,74 @@ body {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Modal Fallback Styles */
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1055;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
position: relative;
|
||||
width: auto;
|
||||
margin: 1.75rem auto;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
border-radius: 0.375rem;
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 1rem;
|
||||
border-top: 1px solid #dee2e6;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1050;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-size: 1.25rem;
|
||||
cursor: pointer;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
/* Menu Tree */
|
||||
.menu-tree {
|
||||
list-style: none;
|
||||
|
||||
+414
-21
@@ -24,15 +24,22 @@ class WhatsAppBotManager {
|
||||
this.refreshStats();
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
// Verificar sesión cada 5 minutos
|
||||
setInterval(() => {
|
||||
this.checkSession();
|
||||
}, 300000); // 5 minutos
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Navegación de tabs
|
||||
document.querySelectorAll('.nav-link').forEach(link => {
|
||||
// Navegación de tabs - solo enlaces que tengan data-tab
|
||||
document.querySelectorAll('.nav-link[data-tab]').forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const tabName = link.getAttribute('data-tab');
|
||||
if (tabName) {
|
||||
this.showTab(tabName);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -165,8 +172,13 @@ class WhatsAppBotManager {
|
||||
const stats = await this.apiCall('get_stats.php');
|
||||
this.updateStats(stats);
|
||||
|
||||
const recentMessages = await this.apiCall('get_recent_messages.php');
|
||||
this.updateRecentMessages(recentMessages);
|
||||
const recentMessagesResponse = await this.apiCall('get_recent_messages.php');
|
||||
if (recentMessagesResponse && recentMessagesResponse.success && Array.isArray(recentMessagesResponse.data)) {
|
||||
this.updateRecentMessages(recentMessagesResponse.data);
|
||||
} else if (recentMessagesResponse && Array.isArray(recentMessagesResponse)) {
|
||||
// Retrocompatibilidad por si la API devuelve directamente el array
|
||||
this.updateRecentMessages(recentMessagesResponse);
|
||||
}
|
||||
|
||||
this.updateChart();
|
||||
} catch (error) {
|
||||
@@ -275,9 +287,20 @@ class WhatsAppBotManager {
|
||||
|
||||
async loadConversations() {
|
||||
try {
|
||||
const conversations = await this.apiCall('get_conversations.php');
|
||||
this.updateConversationsTable(conversations);
|
||||
const response = await this.apiCall('get_conversations.php');
|
||||
console.log('Respuesta get_conversations:', response); // Debug
|
||||
|
||||
if (response && response.success && Array.isArray(response.data)) {
|
||||
this.updateConversationsTable(response.data);
|
||||
} else if (response && Array.isArray(response)) {
|
||||
// Retrocompatibilidad por si la API devuelve directamente el array
|
||||
this.updateConversationsTable(response);
|
||||
} else {
|
||||
console.error('Formato de respuesta inesperado:', response);
|
||||
this.showError('Error: formato de datos inválido para conversaciones');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error en loadConversations:', error);
|
||||
this.showError('Error cargando conversaciones: ' + error.message);
|
||||
}
|
||||
}
|
||||
@@ -288,6 +311,19 @@ class WhatsAppBotManager {
|
||||
|
||||
tbody.innerHTML = '';
|
||||
|
||||
console.log('Datos para tabla de conversaciones:', conversations); // Debug
|
||||
|
||||
if (!Array.isArray(conversations)) {
|
||||
console.error('conversations no es un array:', typeof conversations, conversations);
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-muted">Error: datos inválidos</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
if (conversations.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-muted">No hay conversaciones</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
conversations.forEach(conv => {
|
||||
const row = this.createConversationRow(conv);
|
||||
tbody.appendChild(row);
|
||||
@@ -327,8 +363,15 @@ class WhatsAppBotManager {
|
||||
|
||||
async loadUsers() {
|
||||
try {
|
||||
const users = await this.apiCall('get_users.php');
|
||||
this.updateUsersTable(users);
|
||||
const response = await this.apiCall('get_users.php');
|
||||
if (response && response.success && Array.isArray(response.data)) {
|
||||
this.updateUsersTable(response.data);
|
||||
} else if (response && Array.isArray(response)) {
|
||||
// Retrocompatibilidad por si la API devuelve directamente el array
|
||||
this.updateUsersTable(response);
|
||||
} else {
|
||||
this.showError('Error: formato de datos inválido para usuarios');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError('Error cargando usuarios: ' + error.message);
|
||||
}
|
||||
@@ -373,7 +416,14 @@ class WhatsAppBotManager {
|
||||
|
||||
async loadMenus() {
|
||||
try {
|
||||
const menus = await this.apiCall('get_menus.php');
|
||||
const response = await this.apiCall('get_menus.php');
|
||||
let menus = response;
|
||||
if (response && response.success && Array.isArray(response.data)) {
|
||||
menus = response.data;
|
||||
} else if (!Array.isArray(response)) {
|
||||
this.showError('Error: formato de datos inválido para menús');
|
||||
return;
|
||||
}
|
||||
this.updateMenusTree(menus);
|
||||
this.loadMenuParents(menus);
|
||||
} catch (error) {
|
||||
@@ -457,8 +507,18 @@ class WhatsAppBotManager {
|
||||
|
||||
async loadMessageUsers() {
|
||||
try {
|
||||
const users = await this.apiCall('get_users.php');
|
||||
const templates = await this.apiCall('get_templates.php');
|
||||
const usersResponse = await this.apiCall('get_users.php');
|
||||
const templatesResponse = await this.apiCall('get_templates.php');
|
||||
|
||||
let users = usersResponse;
|
||||
if (usersResponse && usersResponse.success && Array.isArray(usersResponse.data)) {
|
||||
users = usersResponse.data;
|
||||
}
|
||||
|
||||
let templates = templatesResponse;
|
||||
if (templatesResponse && templatesResponse.success && Array.isArray(templatesResponse.data)) {
|
||||
templates = templatesResponse.data;
|
||||
}
|
||||
|
||||
this.updateMessageUserSelect(users);
|
||||
this.updateTemplateSelect(templates);
|
||||
@@ -512,16 +572,39 @@ class WhatsAppBotManager {
|
||||
}
|
||||
|
||||
async sendMessage() {
|
||||
const recipient = document.getElementById('message-recipient').value;
|
||||
const recipientType = document.getElementById('recipient-type').value;
|
||||
const messageType = document.getElementById('message-type').value;
|
||||
|
||||
let recipient;
|
||||
if (recipientType === 'existing') {
|
||||
recipient = document.getElementById('message-recipient').value;
|
||||
} else {
|
||||
recipient = document.getElementById('manual-recipient').value.trim();
|
||||
|
||||
// Para números manuales, solo permitir plantillas
|
||||
if (messageType !== 'template') {
|
||||
this.showError('Para números manuales solo se pueden enviar plantillas aprobadas');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const messageText = document.getElementById('message-text').value;
|
||||
const template = document.getElementById('message-template').value;
|
||||
|
||||
if (!recipient) {
|
||||
this.showError('Por favor selecciona un destinatario');
|
||||
this.showError('Por favor ingresa un destinatario');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validar formato de número manual
|
||||
if (recipientType === 'manual') {
|
||||
const phoneRegex = /^\+\d{10,15}$/;
|
||||
if (!phoneRegex.test(recipient)) {
|
||||
this.showError('El número debe tener el formato: +57XXXXXXXXX (10-15 dígitos)');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const data = {
|
||||
recipient: recipient,
|
||||
type: messageType
|
||||
@@ -548,6 +631,30 @@ class WhatsAppBotManager {
|
||||
if (result.success) {
|
||||
this.showSuccess('Mensaje enviado correctamente');
|
||||
document.getElementById('send-message-form').reset();
|
||||
|
||||
// Resetear la interfaz
|
||||
this.resetMessageForm();
|
||||
|
||||
// Actualizar conversaciones si es necesario
|
||||
if (this.currentTab === 'conversations') {
|
||||
this.loadConversations();
|
||||
}
|
||||
} else {
|
||||
this.showError(result.error || 'Error enviando mensaje');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError('Error enviando mensaje: ' + error.message);
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
resetMessageForm() {
|
||||
document.getElementById('recipient-type').value = 'existing';
|
||||
document.getElementById('message-type').value = 'text';
|
||||
toggleRecipientType();
|
||||
toggleMessageType();
|
||||
}
|
||||
} else {
|
||||
this.showError(result.error || 'Error enviando mensaje');
|
||||
}
|
||||
@@ -661,8 +768,15 @@ class WhatsAppBotManager {
|
||||
|
||||
async loadLogs() {
|
||||
try {
|
||||
const logs = await this.apiCall('get_logs.php');
|
||||
this.updateLogsTable(logs);
|
||||
const response = await this.apiCall('get_logs.php');
|
||||
if (response && response.success && Array.isArray(response.data)) {
|
||||
this.updateLogsTable(response.data);
|
||||
} else if (response && Array.isArray(response)) {
|
||||
// Retrocompatibilidad por si la API devuelve directamente el array
|
||||
this.updateLogsTable(response);
|
||||
} else {
|
||||
this.showError('Error: formato de datos inválido para logs');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError('Error cargando logs: ' + error.message);
|
||||
}
|
||||
@@ -710,6 +824,7 @@ class WhatsAppBotManager {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest' // Para identificar peticiones AJAX
|
||||
}
|
||||
};
|
||||
|
||||
@@ -717,13 +832,51 @@ class WhatsAppBotManager {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(this.apiBaseUrl + endpoint, options);
|
||||
|
||||
// Verificar si es error de autenticación (401)
|
||||
if (response.status === 401) {
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
if (errorData.redirect) {
|
||||
this.showError(errorData.error || 'Sesión expirada. Redirigiendo al login...');
|
||||
setTimeout(() => {
|
||||
window.location.href = errorData.redirect;
|
||||
}, 2000);
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
// Si no puede parsear JSON, redirigir al login
|
||||
this.showError('Sesión expirada. Redirigiendo al login...');
|
||||
setTimeout(() => {
|
||||
window.location.href = 'login.php';
|
||||
}, 2000);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
const result = await response.json();
|
||||
|
||||
// Verificar si la respuesta indica problemas de autenticación
|
||||
if (result.success === false && result.redirect) {
|
||||
this.showError(result.error || 'Sesión expirada. Redirigiendo al login...');
|
||||
setTimeout(() => {
|
||||
window.location.href = result.redirect;
|
||||
}, 2000);
|
||||
return null;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error en apiCall:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
@@ -832,6 +985,26 @@ class WhatsAppBotManager {
|
||||
}
|
||||
|
||||
// Funciones específicas
|
||||
searchConversations(query) {
|
||||
// Función de búsqueda de conversaciones
|
||||
const tbody = document.getElementById('conversations-table');
|
||||
if (!tbody) return;
|
||||
|
||||
const rows = tbody.getElementsByTagName('tr');
|
||||
const searchTerm = query.toLowerCase();
|
||||
|
||||
for (let row of rows) {
|
||||
const phoneNumber = row.cells[0]?.textContent?.toLowerCase() || '';
|
||||
const lastMessage = row.cells[1]?.textContent?.toLowerCase() || '';
|
||||
|
||||
if (phoneNumber.includes(searchTerm) || lastMessage.includes(searchTerm)) {
|
||||
row.style.display = '';
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
refreshData() {
|
||||
this.loadTabData(this.currentTab);
|
||||
this.showSuccess('Datos actualizados');
|
||||
@@ -900,13 +1073,233 @@ class WhatsAppBotManager {
|
||||
refreshLogs() {
|
||||
this.loadLogs();
|
||||
}
|
||||
|
||||
async loadTemplates() {
|
||||
try {
|
||||
const response = await this.apiCall('get_templates.php');
|
||||
if (response && response.success && Array.isArray(response.data)) {
|
||||
this.updateTemplatesTable(response.data);
|
||||
} else if (response && Array.isArray(response)) {
|
||||
// Retrocompatibilidad por si la API devuelve directamente el array
|
||||
this.updateTemplatesTable(response);
|
||||
} else {
|
||||
this.showError('Error: formato de datos inválido para plantillas');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError('Error cargando plantillas: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
updateTemplatesTable(templates) {
|
||||
const tbody = document.getElementById('templates-table');
|
||||
if (!tbody) return;
|
||||
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (templates && templates.length > 0) {
|
||||
templates.forEach(template => {
|
||||
const row = this.createTemplateRow(template);
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
} else {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-muted">No hay plantillas registradas</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
createTemplateRow(template) {
|
||||
const tr = document.createElement('tr');
|
||||
const statusBadge = this.getStatusBadge(template.status);
|
||||
|
||||
tr.innerHTML = `
|
||||
<td>${template.name}</td>
|
||||
<td><code>${template.template_name}</code></td>
|
||||
<td>${template.language_code.toUpperCase()}</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">${template.category}</span>
|
||||
</td>
|
||||
<td>${statusBadge}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-outline-primary" onclick="app.editTemplate(${template.id})" title="Editar">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-danger" onclick="app.deleteTemplate(${template.id})" title="Eliminar">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
return tr;
|
||||
}
|
||||
|
||||
getStatusBadge(status) {
|
||||
const badges = {
|
||||
'approved': '<span class="badge bg-success">Aprobada</span>',
|
||||
'pending': '<span class="badge bg-warning">Pendiente</span>',
|
||||
'rejected': '<span class="badge bg-danger">Rechazada</span>'
|
||||
};
|
||||
return badges[status] || '<span class="badge bg-secondary">Desconocido</span>';
|
||||
}
|
||||
|
||||
async saveTemplate() {
|
||||
const formData = {
|
||||
name: document.getElementById('template-name').value.trim(),
|
||||
template_name: document.getElementById('template-whatsapp-name').value.trim(),
|
||||
language_code: document.getElementById('template-language').value,
|
||||
category: document.getElementById('template-category').value
|
||||
};
|
||||
|
||||
if (!formData.name || !formData.template_name) {
|
||||
this.showError('Por favor complete todos los campos requeridos');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.showLoading('Guardando plantilla...');
|
||||
const result = await this.apiCall('save_template.php', 'POST', formData);
|
||||
|
||||
if (result.success) {
|
||||
this.showSuccess(result.message);
|
||||
document.getElementById('template-form').reset();
|
||||
|
||||
// Cerrar modal
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('createTemplateModal'));
|
||||
modal.hide();
|
||||
|
||||
// Recargar plantillas
|
||||
this.loadTemplates();
|
||||
} else {
|
||||
this.showError(result.error || 'Error guardando plantilla');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError('Error guardando plantilla: ' + error.message);
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
async editTemplate(templateId) {
|
||||
// Por ahora, solo permitir cambiar el estado
|
||||
const status = prompt('Cambiar estado de la plantilla:\n- pending (Pendiente)\n- approved (Aprobada)\n- rejected (Rechazada)', 'approved');
|
||||
|
||||
if (!status || !['pending', 'approved', 'rejected'].includes(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.showLoading('Actualizando plantilla...');
|
||||
const result = await this.apiCall('update_template_status.php', 'POST', {
|
||||
id: templateId,
|
||||
status: status
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
this.showSuccess(result.message);
|
||||
this.loadTemplates();
|
||||
} else {
|
||||
this.showError(result.error || 'Error actualizando plantilla');
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError('Error actualizando plantilla: ' + error.message);
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
async deleteTemplate(templateId) {
|
||||
if (!confirm('¿Estás seguro de que deseas eliminar esta plantilla?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.showLoading('Eliminando plantilla...');
|
||||
// Por ahora usaremos update_template_status para marcar como eliminada
|
||||
// En el futuro se puede crear delete_template.php
|
||||
this.showError('Función de eliminar aún no implementada');
|
||||
} catch (error) {
|
||||
this.showError('Error eliminando plantilla: ' + error.message);
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
// Método para verificar el estado de la sesión
|
||||
async checkSession() {
|
||||
try {
|
||||
const result = await this.apiCall('get_stats.php');
|
||||
// Si la llamada es exitosa, la sesión está activa
|
||||
if (!result) {
|
||||
// La función apiCall ya manejó la redirección al login
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn('Error verificando sesión:', error);
|
||||
// No mostrar error, solo en caso de problemas de red
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializar la aplicación
|
||||
const app = new WhatsAppBotManager();
|
||||
|
||||
// Funciones globales
|
||||
window.refreshData = () => app.refreshData();
|
||||
window.exportUsers = () => app.exportUsers();
|
||||
window.refreshLogs = () => app.refreshLogs();
|
||||
window.clearLogs = () => app.clearLogs();
|
||||
// Funciones globales - usando function declarations para evitar problemas con SES
|
||||
window.refreshData = function() {
|
||||
if (typeof app !== 'undefined') app.refreshData();
|
||||
};
|
||||
window.exportUsers = function() {
|
||||
if (typeof app !== 'undefined') app.exportUsers();
|
||||
};
|
||||
window.refreshLogs = function() {
|
||||
if (typeof app !== 'undefined') app.refreshLogs();
|
||||
};
|
||||
window.clearLogs = function() {
|
||||
if (typeof app !== 'undefined') app.clearLogs();
|
||||
};
|
||||
|
||||
// Las funciones de modales se definen ahora en el HTML para evitar problemas con SES
|
||||
// window.showCreateTemplateModal y window.showCreateMenuModal están en index.php
|
||||
|
||||
window.toggleRecipientType = () => {
|
||||
const recipientType = document.getElementById('recipient-type').value;
|
||||
const existingGroup = document.getElementById('existing-recipient-group');
|
||||
const manualGroup = document.getElementById('manual-recipient-group');
|
||||
const messageTypeSelect = document.getElementById('message-type');
|
||||
|
||||
if (recipientType === 'existing') {
|
||||
existingGroup.style.display = 'block';
|
||||
manualGroup.style.display = 'none';
|
||||
// Habilitar todas las opciones
|
||||
messageTypeSelect.innerHTML = `
|
||||
<option value="text">Texto</option>
|
||||
<option value="template">Plantilla</option>
|
||||
`;
|
||||
} else {
|
||||
existingGroup.style.display = 'none';
|
||||
manualGroup.style.display = 'block';
|
||||
// Solo permitir plantillas para números manuales
|
||||
messageTypeSelect.innerHTML = `
|
||||
<option value="template">Plantilla (Solo opción disponible)</option>
|
||||
`;
|
||||
messageTypeSelect.value = 'template';
|
||||
toggleMessageType(); // Actualizar la interfaz
|
||||
}
|
||||
};
|
||||
|
||||
// Funciones movidas al HTML inline para evitar problemas con SES
|
||||
// window.showCreateMenuModal y window.saveNewMenu están ahora en index.php
|
||||
|
||||
window.toggleMessageType = function() {
|
||||
const messageType = document.getElementById('message-type').value;
|
||||
const textGroup = document.getElementById('message-text-group');
|
||||
const templateGroup = document.getElementById('template-group');
|
||||
|
||||
if (messageType === 'text') {
|
||||
textGroup.style.display = 'block';
|
||||
templateGroup.style.display = 'none';
|
||||
} else {
|
||||
textGroup.style.display = 'none';
|
||||
templateGroup.style.display = 'block';
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,968 @@
|
||||
/**
|
||||
* WhatsApp Bot Manager - JavaScript Simplificado
|
||||
* Versión sin librerías externas problemáticas
|
||||
*/
|
||||
|
||||
class SimpleWhatsAppManager {
|
||||
constructor() {
|
||||
this.apiBaseUrl = './api/';
|
||||
this.currentTab = 'dashboard';
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.log('Iniciando sistema simplificado');
|
||||
this.setupEventListeners();
|
||||
this.loadDashboard();
|
||||
}
|
||||
|
||||
log(message, type = 'info') {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
console.log(`[${timestamp}] [${type.toUpperCase()}] ${message}`);
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
this.log('Configurando event listeners');
|
||||
|
||||
// Navegación de tabs - más defensiva
|
||||
const navLinks = document.querySelectorAll('.nav-link[data-tab]');
|
||||
if (navLinks) {
|
||||
navLinks.forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const tabName = link.getAttribute('data-tab');
|
||||
if (tabName) {
|
||||
this.showTab(tabName);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Formularios
|
||||
this.setupFormListeners();
|
||||
}
|
||||
|
||||
setupFormListeners() {
|
||||
// Formulario de configuración
|
||||
const settingsForm = document.getElementById('settings-form');
|
||||
if (settingsForm) {
|
||||
settingsForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
this.saveSettings();
|
||||
});
|
||||
}
|
||||
|
||||
// Formulario de envío de mensaje
|
||||
const sendForm = document.getElementById('send-message-form');
|
||||
if (sendForm) {
|
||||
sendForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
this.sendMessage();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showTab(tabName) {
|
||||
this.log(`Cambiando a tab: ${tabName}`);
|
||||
|
||||
// Ocultar todas las pestañas
|
||||
const tabs = document.querySelectorAll('.tab-content');
|
||||
if (tabs) {
|
||||
tabs.forEach(tab => tab.classList.remove('active'));
|
||||
}
|
||||
|
||||
// Remover clase active de navegación
|
||||
const navLinks = document.querySelectorAll('.nav-link');
|
||||
if (navLinks) {
|
||||
navLinks.forEach(link => link.classList.remove('active'));
|
||||
}
|
||||
|
||||
// Mostrar pestaña seleccionada
|
||||
const targetTab = document.getElementById(tabName);
|
||||
if (targetTab) {
|
||||
targetTab.classList.add('active');
|
||||
}
|
||||
|
||||
// Activar enlace de navegación
|
||||
const activeLink = document.querySelector(`[data-tab="${tabName}"]`);
|
||||
if (activeLink) {
|
||||
activeLink.classList.add('active');
|
||||
}
|
||||
|
||||
this.currentTab = tabName;
|
||||
this.loadTabData(tabName);
|
||||
}
|
||||
|
||||
loadTabData(tabName) {
|
||||
this.log(`Cargando datos para tab: ${tabName}`);
|
||||
|
||||
switch (tabName) {
|
||||
case 'dashboard':
|
||||
this.loadDashboard();
|
||||
break;
|
||||
case 'conversations':
|
||||
this.loadConversations();
|
||||
break;
|
||||
case 'users':
|
||||
this.loadUsers();
|
||||
break;
|
||||
case 'messages':
|
||||
this.loadMessages();
|
||||
break;
|
||||
case 'templates':
|
||||
this.loadTemplates();
|
||||
break;
|
||||
case 'settings':
|
||||
this.loadSettings();
|
||||
break;
|
||||
default:
|
||||
this.log(`Tab no reconocido: ${tabName}`, 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
async apiCall(endpoint, options = {}) {
|
||||
const url = `${this.apiBaseUrl}${endpoint}${endpoint.includes('?') ? '&' : '?'}debug=true`;
|
||||
|
||||
this.log(`API Call: ${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
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
this.log(`API Response: ${JSON.stringify(data).substring(0, 100)}...`, 'success');
|
||||
return data;
|
||||
|
||||
} catch (error) {
|
||||
this.log(`API Error: ${error.message}`, 'error');
|
||||
this.showError('Error en petición: ' + error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async apiCallReal(endpoint, options = {}) {
|
||||
const url = `${this.apiBaseUrl}${endpoint}`;
|
||||
|
||||
this.log(`API Call (REAL): ${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
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
this.log(`API Response (REAL): ${JSON.stringify(data).substring(0, 100)}...`, 'success');
|
||||
return data;
|
||||
|
||||
} catch (error) {
|
||||
this.log(`API Error (REAL): ${error.message}`, 'error');
|
||||
this.showError('Error en petición real: ' + error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async loadDashboard() {
|
||||
this.log('Cargando dashboard');
|
||||
|
||||
try {
|
||||
// Cargar estadísticas básicas
|
||||
const stats = await this.apiCall('manage_config.php?action=status');
|
||||
this.updateStats(stats);
|
||||
|
||||
} catch (error) {
|
||||
this.showError('Error cargando dashboard: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
updateStats(response) {
|
||||
if (response && response.success && response.data) {
|
||||
const data = response.data;
|
||||
|
||||
// Actualizar elementos del dashboard si existen
|
||||
const elements = [
|
||||
{ id: 'total-users', value: data.total_configs || 0 },
|
||||
{ id: 'messages-today', value: data.whatsapp_configured ? 'Configurado' : 'Sin configurar' },
|
||||
{ id: 'active-users', value: data.database_migrated ? 'Migrada' : 'Pendiente' },
|
||||
{ id: 'total-messages', value: data.installation_completed ? 'Completada' : 'Pendiente' }
|
||||
];
|
||||
|
||||
elements.forEach(item => {
|
||||
const element = document.getElementById(item.id);
|
||||
if (element) {
|
||||
element.textContent = item.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async loadUsers() {
|
||||
this.log('Cargando usuarios');
|
||||
|
||||
try {
|
||||
const response = await this.apiCall('get_users.php');
|
||||
|
||||
if (response && response.success) {
|
||||
this.updateUsersList(response.data || []);
|
||||
} else {
|
||||
this.showError('No se pudieron cargar los usuarios');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.showError('Error cargando usuarios: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
updateUsersList(users) {
|
||||
const container = document.getElementById('users-table');
|
||||
if (!container) return;
|
||||
|
||||
if (users.length === 0) {
|
||||
container.innerHTML = '<tr><td colspan="7" class="text-center text-muted">No hay usuarios registrados</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
|
||||
users.forEach(user => {
|
||||
const createdDate = new Date(user.created_at);
|
||||
const formatDate = createdDate.toLocaleDateString() + ' ' + createdDate.toLocaleTimeString('es', { hour: '2-digit', minute: '2-digit' });
|
||||
|
||||
html += `
|
||||
<tr>
|
||||
<td>${user.id}</td>
|
||||
<td>${user.phone_number}</td>
|
||||
<td>${user.name || 'Sin nombre'}</td>
|
||||
<td><span class="badge bg-${user.status === 'active' ? 'success' : 'secondary'}">${user.status}</span></td>
|
||||
<td>${user.current_menu || 'Sin menú'}</td>
|
||||
<td>${formatDate}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary me-1" onclick="editUser(${user.id})">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="deleteUser(${user.id})">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
async loadConversations() {
|
||||
this.log('Cargando conversaciones');
|
||||
|
||||
try {
|
||||
const response = await this.apiCall('get_conversations.php');
|
||||
|
||||
if (response && response.success) {
|
||||
this.updateConversationsList(response.data || []);
|
||||
} else {
|
||||
this.showError('No se pudieron cargar las conversaciones');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.showError('Error cargando conversaciones: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
updateConversationsList(conversations) {
|
||||
const container = document.getElementById('conversations-table');
|
||||
if (!container) return;
|
||||
|
||||
if (conversations.length === 0) {
|
||||
container.innerHTML = '<tr><td colspan="6" class="text-center text-muted">No hay conversaciones</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
|
||||
conversations.forEach(conv => {
|
||||
const date = new Date(conv.created_at);
|
||||
const formatDate = date.toLocaleDateString() + ' ' + date.toLocaleTimeString('es', { hour: '2-digit', minute: '2-digit' });
|
||||
|
||||
html += `
|
||||
<tr>
|
||||
<td>
|
||||
<div>
|
||||
<strong>${conv.name || conv.phone_number}</strong><br>
|
||||
<small class="text-muted">${conv.phone_number}</small>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div style="max-width: 200px; overflow: hidden; text-overflow: ellipsis;">
|
||||
${conv.content || 'Sin contenido'}
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="badge bg-info">${conv.message_type || 'text'}</span></td>
|
||||
<td><span class="badge bg-${conv.status === 'sent' ? 'success' : 'warning'}">${conv.status || 'unknown'}</span></td>
|
||||
<td>${formatDate}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="viewConversation(${conv.user_id})">
|
||||
<i class="fas fa-eye"></i> Ver
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.log('Cargando configuraciones');
|
||||
|
||||
try {
|
||||
const response = await this.apiCall('manage_config.php?action=whatsapp');
|
||||
|
||||
if (response && response.success) {
|
||||
this.updateSettingsForm(response.data);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.showError('Error cargando configuraciones: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
updateSettingsForm(data) {
|
||||
if (!data) return;
|
||||
|
||||
const fields = [
|
||||
{ id: 'whatsapp-token', value: data.token },
|
||||
{ id: 'phone-number-id', value: data.phone_number_id },
|
||||
{ id: 'webhook-token', value: data.webhook_verify_token },
|
||||
{ id: 'business-name', value: data.business_name },
|
||||
{ id: 'welcome-message', value: data.welcome_message }
|
||||
];
|
||||
|
||||
fields.forEach(field => {
|
||||
const element = document.getElementById(field.id);
|
||||
if (element && field.value) {
|
||||
element.value = field.value;
|
||||
this.log(`Campo ${field.id} actualizado con valor: ${field.value}`);
|
||||
} else {
|
||||
this.log(`Campo ${field.id} no encontrado o sin valor`, 'warning');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
this.log('Guardando configuraciones');
|
||||
|
||||
const settings = {
|
||||
token: document.getElementById('whatsapp-token').value,
|
||||
phone_number_id: document.getElementById('phone-number-id').value,
|
||||
webhook_verify_token: document.getElementById('webhook-token').value,
|
||||
business_name: document.getElementById('business-name').value,
|
||||
welcome_message: document.getElementById('welcome-message').value
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await this.apiCall('manage_config.php?action=save_whatsapp', {
|
||||
method: 'POST',
|
||||
body: { whatsapp: settings }
|
||||
});
|
||||
|
||||
if (response && response.success) {
|
||||
this.showSuccess('Configuraciones guardadas exitosamente');
|
||||
} else {
|
||||
this.showError('Error guardando configuraciones');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.showError('Error: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
this.log(`Error: ${message}`, 'error');
|
||||
this.showAlert(message, 'danger');
|
||||
}
|
||||
|
||||
showSuccess(message) {
|
||||
this.log(`Success: ${message}`, 'success');
|
||||
this.showAlert(message, 'success');
|
||||
}
|
||||
|
||||
showAlert(message, type) {
|
||||
// Crear alerta temporal
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
|
||||
alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
|
||||
alertDiv.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
|
||||
document.body.appendChild(alertDiv);
|
||||
|
||||
// Auto-remover después de 5 segundos
|
||||
setTimeout(() => {
|
||||
if (alertDiv.parentNode) {
|
||||
alertDiv.parentNode.removeChild(alertDiv);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
async loadMessages() {
|
||||
this.log('Cargando interfaz de mensajes');
|
||||
|
||||
try {
|
||||
// Cargar usuarios para el select
|
||||
const usersResponse = await this.apiCall('get_users.php');
|
||||
|
||||
if (usersResponse && usersResponse.success) {
|
||||
this.populateUsersSelect(usersResponse.data || []);
|
||||
}
|
||||
|
||||
// Cargar plantillas para el select
|
||||
const templatesResponse = await this.apiCall('get_templates.php');
|
||||
|
||||
if (templatesResponse && templatesResponse.success) {
|
||||
this.populateTemplatesSelect(templatesResponse.data || []);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.showError('Error cargando datos para mensajes: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
populateUsersSelect(users) {
|
||||
const select = document.getElementById('message-recipient');
|
||||
if (!select) return;
|
||||
|
||||
// Limpiar opciones existentes (excepto la primera)
|
||||
select.innerHTML = '<option value="">Seleccionar usuario...</option>';
|
||||
|
||||
// Agregar usuarios
|
||||
users.forEach(user => {
|
||||
const option = document.createElement('option');
|
||||
option.value = user.id;
|
||||
option.textContent = `${user.name || user.phone_number} (${user.phone_number})`;
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
populateTemplatesSelect(templates) {
|
||||
const select = document.getElementById('message-template');
|
||||
if (!select) return;
|
||||
|
||||
// Limpiar opciones existentes (excepto la primera)
|
||||
select.innerHTML = '<option value="">Seleccionar plantilla...</option>';
|
||||
|
||||
// Agregar plantillas
|
||||
templates.forEach(template => {
|
||||
const option = document.createElement('option');
|
||||
option.value = template.template_name;
|
||||
option.textContent = `${template.name} - ${template.category || 'Sin descripción'}`;
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
async loadTemplates() {
|
||||
this.log('Cargando plantillas');
|
||||
|
||||
try {
|
||||
const response = await this.apiCall('get_templates.php');
|
||||
|
||||
if (response && response.success) {
|
||||
this.updateTemplatesList(response.data || []);
|
||||
} else {
|
||||
this.showError('Error cargando plantillas');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.showError('Error cargando plantillas: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
updateTemplatesList(templates) {
|
||||
const container = document.getElementById('templates-table');
|
||||
if (!container) return;
|
||||
|
||||
if (templates.length === 0) {
|
||||
container.innerHTML = '<tr><td colspan="6" class="text-center text-muted">No hay plantillas registradas</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
|
||||
templates.forEach(template => {
|
||||
html += `
|
||||
<tr>
|
||||
<td><strong>${template.name}</strong></td>
|
||||
<td><code>${template.template_name}</code></td>
|
||||
<td>${template.language_code || 'es'}</td>
|
||||
<td><span class="badge bg-info">${template.category || 'utility'}</span></td>
|
||||
<td><span class="badge bg-${template.status === 'approved' ? 'success' : 'warning'}">${template.status || 'pending'}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary me-1" onclick="editTemplate(${template.id})">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="deleteTemplate(${template.id})">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
async saveTemplate() {
|
||||
this.log('Guardando plantilla');
|
||||
|
||||
try {
|
||||
const templateData = {
|
||||
name: document.getElementById('template-name').value,
|
||||
whatsapp_name: document.getElementById('template-whatsapp-name').value,
|
||||
language: document.getElementById('template-language').value,
|
||||
category: document.getElementById('template-category').value
|
||||
};
|
||||
|
||||
// Validar campos requeridos
|
||||
if (!templateData.name || !templateData.whatsapp_name) {
|
||||
this.showError('Nombre descriptivo y nombre de WhatsApp son requeridos');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await this.apiCall('save_template.php', {
|
||||
method: 'POST',
|
||||
body: templateData
|
||||
});
|
||||
|
||||
if (response && response.success) {
|
||||
this.showSuccess('Plantilla guardada correctamente');
|
||||
|
||||
// Cerrar modal
|
||||
const modal = document.getElementById('createTemplateModal');
|
||||
if (modal) {
|
||||
const bsModal = bootstrap.Modal.getInstance(modal);
|
||||
if (bsModal) bsModal.hide();
|
||||
}
|
||||
|
||||
// Limpiar formulario
|
||||
document.getElementById('template-form').reset();
|
||||
|
||||
// Recargar plantillas
|
||||
this.loadTemplates();
|
||||
} else {
|
||||
this.showError(`Error guardando plantilla: ${response?.error || 'Error desconocido'}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.showError('Error guardando plantilla: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async sendMessage() {
|
||||
this.log('Enviando mensaje');
|
||||
|
||||
try {
|
||||
const recipientType = document.getElementById('recipient-type').value;
|
||||
const messageType = document.getElementById('message-type').value;
|
||||
|
||||
let recipient;
|
||||
let messageData = {};
|
||||
|
||||
// Obtener destinatario
|
||||
if (recipientType === 'existing') {
|
||||
const userSelect = document.getElementById('message-recipient');
|
||||
if (!userSelect.value) {
|
||||
this.showError('Por favor selecciona un usuario');
|
||||
return;
|
||||
}
|
||||
|
||||
// Obtener datos del usuario seleccionado
|
||||
const selectedOption = userSelect.options[userSelect.selectedIndex];
|
||||
const userId = userSelect.value;
|
||||
|
||||
// Necesitamos obtener el número de teléfono del usuario
|
||||
const usersResponse = await this.apiCall('get_users.php');
|
||||
if (usersResponse && usersResponse.success) {
|
||||
const user = usersResponse.data.find(u => u.id == userId);
|
||||
if (user) {
|
||||
recipient = user.phone_number;
|
||||
} else {
|
||||
this.showError('Usuario no encontrado');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.showError('Error obteniendo datos del usuario');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
recipient = document.getElementById('manual-recipient').value;
|
||||
if (!recipient) {
|
||||
this.showError('Por favor ingresa un número de teléfono');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Preparar mensaje según el tipo
|
||||
if (messageType === 'text') {
|
||||
const messageText = document.getElementById('message-text').value;
|
||||
if (!messageText) {
|
||||
this.showError('Por favor escribe un mensaje');
|
||||
return;
|
||||
}
|
||||
|
||||
messageData = {
|
||||
recipient: recipient,
|
||||
type: 'text',
|
||||
message: messageText
|
||||
};
|
||||
} else {
|
||||
const templateSelect = document.getElementById('message-template');
|
||||
if (!templateSelect.value) {
|
||||
this.showError('Por favor selecciona una plantilla');
|
||||
return;
|
||||
}
|
||||
|
||||
messageData = {
|
||||
recipient: recipient,
|
||||
type: 'template',
|
||||
template_name: templateSelect.value
|
||||
};
|
||||
}
|
||||
|
||||
this.log(`Enviando mensaje a ${recipient}: ${JSON.stringify(messageData)}`);
|
||||
|
||||
// Preguntar al usuario si quiere envío real o simulado
|
||||
const sendReal = confirm('¿Enviar mensaje REAL por WhatsApp?\n\nSí = Envío real\nNo = Envío simulado (debug)');
|
||||
|
||||
// Enviar mensaje
|
||||
const response = sendReal
|
||||
? await this.apiCallReal('send_message.php', {
|
||||
method: 'POST',
|
||||
body: messageData
|
||||
})
|
||||
: await this.apiCall('send_message.php', {
|
||||
method: 'POST',
|
||||
body: messageData
|
||||
});
|
||||
|
||||
if (response && response.success) {
|
||||
this.showSuccess(`Mensaje enviado correctamente a ${recipient}`);
|
||||
|
||||
// Limpiar formulario
|
||||
document.getElementById('send-message-form').reset();
|
||||
document.getElementById('message-recipient').value = '';
|
||||
} else {
|
||||
this.showError(`Error enviando mensaje: ${response?.error || 'Error desconocido'}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.showError('Error enviando mensaje: ' + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializar cuando el DOM esté listo
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('DOM cargado, inicializando WhatsApp Manager...');
|
||||
|
||||
try {
|
||||
window.whatsappManager = new SimpleWhatsAppManager();
|
||||
console.log('WhatsApp Manager inicializado correctamente');
|
||||
} catch (error) {
|
||||
console.error('Error inicializando WhatsApp Manager:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Funciones globales para modales (compatibilidad con el HTML existente)
|
||||
window.openCreateTemplateModal = function() {
|
||||
console.log('🔵 Abriendo modal de plantilla...');
|
||||
|
||||
const modalElement = document.getElementById('createTemplateModal');
|
||||
if (!modalElement) {
|
||||
console.error('❌ Modal de plantilla no encontrado');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const form = document.getElementById('template-form');
|
||||
if (form) form.reset();
|
||||
|
||||
if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
|
||||
const modal = new bootstrap.Modal(modalElement);
|
||||
modal.show();
|
||||
} else {
|
||||
modalElement.style.display = 'block';
|
||||
modalElement.classList.add('show');
|
||||
}
|
||||
console.log('✅ Modal de plantilla abierto');
|
||||
} catch (error) {
|
||||
console.error('❌ Error abriendo modal de plantilla:', error);
|
||||
}
|
||||
};
|
||||
|
||||
window.openCreateMenuModal = function() {
|
||||
console.log('🔵 Abriendo modal de menú...');
|
||||
|
||||
const modalElement = document.getElementById('createMenuModal');
|
||||
if (!modalElement) {
|
||||
console.error('❌ Modal de menú no encontrado');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const form = document.getElementById('menu-form');
|
||||
if (form) form.reset();
|
||||
|
||||
if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
|
||||
const modal = new bootstrap.Modal(modalElement);
|
||||
modal.show();
|
||||
} else {
|
||||
modalElement.style.display = 'block';
|
||||
modalElement.classList.add('show');
|
||||
}
|
||||
console.log('✅ Modal de menú abierto');
|
||||
} catch (error) {
|
||||
console.error('❌ Error abriendo modal de menú:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Función global para debugging
|
||||
window.debugAPI = async function(endpoint) {
|
||||
try {
|
||||
const manager = window.whatsappManager;
|
||||
if (manager) {
|
||||
const result = await manager.apiCall(endpoint);
|
||||
console.log('Debug API Result:', result);
|
||||
return result;
|
||||
} else {
|
||||
console.error('Manager no inicializado');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Debug API Error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Función para ver conversación específica
|
||||
window.viewConversation = function(userId) {
|
||||
console.log('Viendo conversación del usuario:', userId);
|
||||
alert(`Ver conversación del usuario ${userId} - Función en desarrollo`);
|
||||
};
|
||||
|
||||
// Función para editar usuario
|
||||
window.editUser = function(userId) {
|
||||
console.log('Editando usuario:', userId);
|
||||
alert(`Editar usuario ${userId} - Función en desarrollo`);
|
||||
};
|
||||
|
||||
// Función para eliminar usuario
|
||||
window.deleteUser = function(userId) {
|
||||
console.log('Eliminando usuario:', userId);
|
||||
if (confirm(`¿Está seguro que desea eliminar el usuario ${userId}?`)) {
|
||||
alert(`Eliminar usuario ${userId} - Función en desarrollo`);
|
||||
}
|
||||
};
|
||||
|
||||
// Funciones para plantillas
|
||||
window.showCreateTemplateModal = function() {
|
||||
const modal = new bootstrap.Modal(document.getElementById('createTemplateModal'));
|
||||
modal.show();
|
||||
};
|
||||
|
||||
window.editTemplate = function(templateId) {
|
||||
console.log('Editando plantilla:', templateId);
|
||||
alert(`Editar plantilla ${templateId} - Función en desarrollo`);
|
||||
};
|
||||
|
||||
window.deleteTemplate = function(templateId) {
|
||||
console.log('Eliminando plantilla:', templateId);
|
||||
if (confirm(`¿Está seguro que desea eliminar la plantilla ${templateId}?`)) {
|
||||
alert(`Eliminar plantilla ${templateId} - Función en desarrollo`);
|
||||
}
|
||||
};
|
||||
|
||||
// Hacer que app.saveTemplate() funcione
|
||||
window.app = {
|
||||
saveTemplate: function() {
|
||||
if (window.whatsappManager) {
|
||||
window.whatsappManager.saveTemplate();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Funciones para el formulario de mensajes
|
||||
window.toggleRecipientType = function() {
|
||||
const recipientType = document.getElementById('recipient-type').value;
|
||||
const existingGroup = document.getElementById('existing-recipient-group');
|
||||
const manualGroup = document.getElementById('manual-recipient-group');
|
||||
|
||||
if (recipientType === 'manual') {
|
||||
existingGroup.style.display = 'none';
|
||||
manualGroup.style.display = 'block';
|
||||
} else {
|
||||
existingGroup.style.display = 'block';
|
||||
manualGroup.style.display = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
window.toggleMessageType = function() {
|
||||
const messageType = document.getElementById('message-type').value;
|
||||
const textGroup = document.getElementById('message-text-group');
|
||||
const templateGroup = document.getElementById('template-group');
|
||||
|
||||
if (messageType === 'template') {
|
||||
textGroup.style.display = 'none';
|
||||
templateGroup.style.display = 'block';
|
||||
} else {
|
||||
textGroup.style.display = 'block';
|
||||
templateGroup.style.display = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
// Función de diagnóstico
|
||||
window.runDiagnostic = function() {
|
||||
const resultsDiv = document.getElementById('diagnostic-results');
|
||||
|
||||
// Mostrar spinner de carga
|
||||
resultsDiv.innerHTML = `
|
||||
<div class="text-center">
|
||||
<div class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="visually-hidden">Cargando...</span>
|
||||
</div>
|
||||
<p class="mt-2">Ejecutando diagnóstico...</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
fetch('./api/diagnose_whatsapp.php')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
displayDiagnosticResults(data);
|
||||
} else {
|
||||
resultsDiv.innerHTML = `
|
||||
<div class="alert alert-danger">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
Error: ${data.error || 'Error desconocido'}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error en diagnóstico:', error);
|
||||
resultsDiv.innerHTML = `
|
||||
<div class="alert alert-danger">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
Error de conexión: ${error.message}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
};
|
||||
|
||||
function displayDiagnosticResults(data) {
|
||||
const resultsDiv = document.getElementById('diagnostic-results');
|
||||
|
||||
let html = `
|
||||
<div class="diagnostic-results">
|
||||
<small class="text-muted">Última verificación: ${data.timestamp}</small>
|
||||
|
||||
<!-- Estado de Configuración -->
|
||||
<div class="mt-3">
|
||||
<h6><i class="fas fa-cog"></i> Configuración</h6>
|
||||
<div class="list-group list-group-flush">
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center py-2">
|
||||
<small>Phone Number ID</small>
|
||||
<span class="badge bg-${data.config_status.phone_number_id ? 'success' : 'danger'}">
|
||||
${data.config_status.phone_number_id ? '✓' : '✗'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center py-2">
|
||||
<small>Access Token</small>
|
||||
<span class="badge bg-${data.config_status.access_token ? 'success' : 'danger'}">
|
||||
${data.config_status.access_token ? '✓' : '✗'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center py-2">
|
||||
<small>Webhook Token</small>
|
||||
<span class="badge bg-${data.config_status.webhook_verify_token ? 'success' : 'danger'}">
|
||||
${data.config_status.webhook_verify_token ? '✓' : '✗'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Estado de Plantillas -->
|
||||
<div class="mt-3">
|
||||
<h6><i class="fas fa-file-text"></i> Plantillas</h6>
|
||||
<div class="row text-center">
|
||||
<div class="col-4">
|
||||
<div class="text-success">
|
||||
<strong>${data.template_status.approved || 0}</strong><br>
|
||||
<small>Aprobadas</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="text-warning">
|
||||
<strong>${data.template_status.pending || 0}</strong><br>
|
||||
<small>Pendientes</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="text-danger">
|
||||
<strong>${data.template_status.rejected || 0}</strong><br>
|
||||
<small>Rechazadas</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Estado de API -->
|
||||
<div class="mt-3">
|
||||
<h6><i class="fas fa-cloud"></i> API WhatsApp</h6>
|
||||
<div class="alert alert-${data.api_status.reachable ? 'success' : 'danger'} p-2">
|
||||
<small>
|
||||
${data.api_status.reachable ?
|
||||
`✓ Conectado - ${data.api_status.phone_number || 'N/A'}` :
|
||||
`✗ ${data.api_status.error || 'No conectado'}`
|
||||
}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Recomendaciones
|
||||
if (data.recommendations && data.recommendations.length > 0) {
|
||||
html += `
|
||||
<div class="mt-3">
|
||||
<h6><i class="fas fa-lightbulb"></i> Recomendaciones</h6>
|
||||
<ul class="list-unstyled">
|
||||
`;
|
||||
data.recommendations.forEach(rec => {
|
||||
html += `<li><small><i class="fas fa-arrow-right text-muted"></i> ${rec}</small></li>`;
|
||||
});
|
||||
html += `
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
resultsDiv.innerHTML = html;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sistema de Autenticación - WhatsApp 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.0.0/css/all.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<div class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="mb-0"><i class="fas fa-shield-alt"></i> Sistema de Autenticación Implementado</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-success">
|
||||
<h6><i class="fas fa-check-circle"></i> ¡Configuración Completada!</h6>
|
||||
<p class="mb-0">El sistema de autenticación ha sido implementado exitosamente.</p>
|
||||
</div>
|
||||
|
||||
<h6><i class="fas fa-cog"></i> Características Implementadas:</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><i class="fas fa-check text-success"></i> <strong>Inicio de sesión</strong> - Formulario de login con validación</li>
|
||||
<li><i class="fas fa-check text-success"></i> <strong>Cierre de sesión</strong> - Botón de logout en header y sidebar</li>
|
||||
<li><i class="fas fa-check text-success"></i> <strong>Redirección automática</strong> - Al login si no está autenticado</li>
|
||||
<li><i class="fas fa-check text-success"></i> <strong>Timeout de sesión</strong> - Cierre automático por inactividad</li>
|
||||
<li><i class="fas fa-check text-success"></i> <strong>Protección AJAX</strong> - Manejo de sesiones en llamadas API</li>
|
||||
<li><i class="fas fa-check text-success"></i> <strong>Mensajes informativos</strong> - Confirmación de logout y errores</li>
|
||||
<li><i class="fas fa-check text-success"></i> <strong>Verificación automática</strong> - Chequeo periódico de sesión</li>
|
||||
</ul>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card border-primary">
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-sign-in-alt fa-3x text-primary mb-3"></i>
|
||||
<h6>Iniciar Sesión</h6>
|
||||
<p class="small text-muted">Acceder al panel de administración</p>
|
||||
<a href="login.php" class="btn btn-primary btn-sm">
|
||||
<i class="fas fa-sign-in-alt"></i> Ir al Login
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card border-info">
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-tachometer-alt fa-3x text-info mb-3"></i>
|
||||
<h6>Panel de Control</h6>
|
||||
<p class="small text-muted">Administrar WhatsApp Bot</p>
|
||||
<a href="index.php" class="btn btn-info btn-sm">
|
||||
<i class="fas fa-tachometer-alt"></i> Ir al Panel
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 p-3 bg-light rounded">
|
||||
<h6><i class="fas fa-info-circle"></i> Información de Seguridad:</h6>
|
||||
<ul class="small mb-0">
|
||||
<li>Las sesiones expiran automáticamente después de <strong>30 minutos</strong> de inactividad</li>
|
||||
<li>Todas las páginas del admin están protegidas contra acceso no autorizado</li>
|
||||
<li>Las llamadas AJAX manejan automáticamente la expiración de sesiones</li>
|
||||
<li>El sistema verifica la sesión cada 5 minutos en segundo plano</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-code"></i> Desarrollado por <strong>U-Site.app</strong> |
|
||||
Fecha: <?= date('d/m/Y H:i:s') ?>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* Archivo de protección de autenticación
|
||||
* Incluir en páginas que requieren login
|
||||
* Desarrollado por U-Site.app
|
||||
*/
|
||||
|
||||
// Asegurar que config.php esté incluido
|
||||
if (!function_exists('isUserLoggedIn')) {
|
||||
require_once __DIR__ . '/config/config.php';
|
||||
}
|
||||
|
||||
// Verificar si la instalación está completada
|
||||
if (!isInstallationCompleted()) {
|
||||
header('Location: install.php');
|
||||
exit('Sistema no instalado. <a href="install.php">Instalar ahora</a>');
|
||||
}
|
||||
|
||||
// Verificar autenticación - redirigir al login si no está logueado
|
||||
if (!isUserLoggedIn()) {
|
||||
// Si es una petición AJAX, devolver JSON
|
||||
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
|
||||
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
|
||||
http_response_code(401);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Sesión expirada. Por favor, inicie sesión.',
|
||||
'redirect' => 'login.php'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Para páginas normales, redirigir al login
|
||||
header('Location: login.php');
|
||||
exit('Acceso denegado. <a href="login.php">Iniciar sesión</a>');
|
||||
}
|
||||
|
||||
// Verificar timeout de sesión (ya se hace en config.php, pero por seguridad)
|
||||
if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > SESSION_TIMEOUT)) {
|
||||
session_destroy();
|
||||
session_start();
|
||||
|
||||
// Si es AJAX
|
||||
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
|
||||
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
|
||||
http_response_code(401);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'Sesión expirada por inactividad.',
|
||||
'redirect' => 'login.php'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Location: login.php?session_expired=1');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Actualizar última actividad
|
||||
$_SESSION['last_activity'] = time();
|
||||
?>
|
||||
+671
-37
@@ -2,22 +2,337 @@
|
||||
/**
|
||||
* Configuración del sistema WhatsApp Bot Manager
|
||||
* Desarrollado por U-Site.app
|
||||
* Fecha: 13 de noviembre de 2025
|
||||
* Fecha: 4 de enero de 2026 - Sistema de autenticación unificado
|
||||
*/
|
||||
|
||||
// Configuración de la base de datos (compatible con cPanel)
|
||||
define('DB_HOST', 'localhost');
|
||||
define('DB_HOST', '77.37.126.126');
|
||||
define('DB_PORT', '3306');
|
||||
define('DB_NAME', 'whatsapp_bot');
|
||||
define('DB_USER', 'whatsapp_user');
|
||||
define('DB_PASS', ''); // Se generará automáticamente en la instalación
|
||||
define('DB_NAME', 'usite_whatsapp_bot');
|
||||
define('DB_USER', 'usite_whatsapp_user');
|
||||
define('DB_PASS', '6q!Vio0fn@PFmsLyHjH5'); // Se generará automáticamente en la instalación
|
||||
define('DB_CHARSET', 'utf8mb4');
|
||||
|
||||
// WhatsApp Business API (se configurará después de la instalación)
|
||||
define('WHATSAPP_TOKEN', 'TU_TOKEN_DE_WHATSAPP_AQUI');
|
||||
define('WHATSAPP_PHONE_NUMBER_ID', 'TU_PHONE_ID_AQUI');
|
||||
define('WHATSAPP_API_URL', 'https://graph.facebook.com/v22.0/');
|
||||
define('WEBHOOK_VERIFY_TOKEN', 'mi_token_secreto_123');
|
||||
// Configuración de autenticación (LEGACY - migrado a BD)
|
||||
// NOTA: Esta constante se mantiene para retrocompatibilidad durante la migración
|
||||
// El sistema ahora usa autenticación basada en base de datos (admin_users table)
|
||||
define('ADMIN_PASSWORD', '$2y$10$IXCY8Sm1xFkfhC6Y67Ahn.QLHxE.sjWfmTEOKFZdCN2a9s9YLVuGW'); // password123
|
||||
|
||||
// Función para cargar archivo .env
|
||||
function loadEnvFile($path) {
|
||||
if (!file_exists($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, '#') === 0 || strpos($line, '=') === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$name, $value] = explode('=', $line, 2);
|
||||
$name = trim($name);
|
||||
$value = trim($value, " \t\n\r\0\x0B\"'");
|
||||
|
||||
if (!array_key_exists($name, $_ENV)) {
|
||||
$_ENV[$name] = $value;
|
||||
putenv("$name=$value");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cargar .env si existe
|
||||
loadEnvFile(__DIR__ . '/../.env');
|
||||
|
||||
// Función helper para obtener variables de entorno
|
||||
function env($key, $default = null) {
|
||||
return $_ENV[$key] ?? getenv($key) ?: $default;
|
||||
}
|
||||
|
||||
// Función para verificar si la instalación está completada
|
||||
function isInstallationCompleted() {
|
||||
return file_exists(dirname(__DIR__) . '/.installation_completed');
|
||||
}
|
||||
|
||||
// Función para obtener configuración desde base de datos
|
||||
function getConfigFromDB($key, $default = null) {
|
||||
static $configs = null;
|
||||
global $config_cache_cleared;
|
||||
|
||||
if ($configs === null || $config_cache_cleared) {
|
||||
$config_cache_cleared = false;
|
||||
|
||||
if (isInstallationCompleted()) {
|
||||
try {
|
||||
$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,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]
|
||||
);
|
||||
|
||||
// Verificar si existe la tabla system_config
|
||||
$tableExists = $pdo->query("SHOW TABLES LIKE 'system_config'")->rowCount() > 0;
|
||||
|
||||
if ($tableExists) {
|
||||
$stmt = $pdo->query("SELECT config_key, config_value FROM system_config");
|
||||
$configs = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$configs[$row['config_key']] = $row['config_value'];
|
||||
}
|
||||
} else {
|
||||
// Si no existe la tabla, usar valores por defecto
|
||||
$configs = [];
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error loading config: " . $e->getMessage());
|
||||
$configs = [];
|
||||
}
|
||||
} else {
|
||||
$configs = [];
|
||||
}
|
||||
}
|
||||
|
||||
return $configs[$key] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guarda una configuración en la base de datos
|
||||
* @param string $key Clave de configuración
|
||||
* @param mixed $value Valor de configuración
|
||||
* @param string $description Descripción opcional
|
||||
* @return bool True si se guardó correctamente
|
||||
*/
|
||||
function saveConfigToDB($key, $value, $description = null) {
|
||||
try {
|
||||
if (!isInstallationCompleted()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pdo = createDbConnection();
|
||||
|
||||
// Crear tabla si no existe
|
||||
createSystemConfigTable($pdo);
|
||||
|
||||
// Verificar si ya existe la configuración
|
||||
$stmt = $pdo->prepare("SELECT id FROM system_config WHERE config_key = ?");
|
||||
$stmt->execute([$key]);
|
||||
$exists = $stmt->fetch();
|
||||
|
||||
if ($exists) {
|
||||
// Actualizar configuración existente
|
||||
$sql = "UPDATE system_config SET config_value = ?, updated_at = NOW()";
|
||||
$params = [$value];
|
||||
|
||||
if ($description !== null) {
|
||||
$sql .= ", description = ?";
|
||||
$params[] = $description;
|
||||
}
|
||||
|
||||
$sql .= " WHERE config_key = ?";
|
||||
$params[] = $key;
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$result = $stmt->execute($params);
|
||||
} else {
|
||||
// Insertar nueva configuración
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO system_config (config_key, config_value, description, created_at, updated_at)
|
||||
VALUES (?, ?, ?, NOW(), NOW())
|
||||
");
|
||||
$result = $stmt->execute([$key, $value, $description]);
|
||||
}
|
||||
|
||||
// Limpiar cache estático
|
||||
if ($result) {
|
||||
clearConfigCache();
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error guardando configuración '$key': " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guarda múltiples configuraciones en una sola transacción
|
||||
* @param array $configs Array asociativo [key => value]
|
||||
* @return bool True si todas se guardaron correctamente
|
||||
*/
|
||||
function saveMultipleConfigsToDB($configs) {
|
||||
try {
|
||||
if (!isInstallationCompleted() || empty($configs)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pdo = createDbConnection();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Crear tabla si no existe
|
||||
createSystemConfigTable($pdo);
|
||||
|
||||
$success = true;
|
||||
foreach ($configs as $key => $value) {
|
||||
// Verificar si ya existe
|
||||
$stmt = $pdo->prepare("SELECT id FROM system_config WHERE config_key = ?");
|
||||
$stmt->execute([$key]);
|
||||
$exists = $stmt->fetch();
|
||||
|
||||
if ($exists) {
|
||||
// Actualizar
|
||||
$stmt = $pdo->prepare("UPDATE system_config SET config_value = ?, updated_at = NOW() WHERE config_key = ?");
|
||||
$result = $stmt->execute([$value, $key]);
|
||||
} else {
|
||||
// Insertar
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO system_config (config_key, config_value, created_at, updated_at)
|
||||
VALUES (?, ?, NOW(), NOW())
|
||||
");
|
||||
$result = $stmt->execute([$key, $value]);
|
||||
}
|
||||
|
||||
if (!$result) {
|
||||
$success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
$pdo->commit();
|
||||
clearConfigCache();
|
||||
return true;
|
||||
} else {
|
||||
$pdo->rollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
if (isset($pdo) && $pdo->inTransaction()) {
|
||||
$pdo->rollback();
|
||||
}
|
||||
error_log("Error guardando configuraciones múltiples: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene todas las configuraciones de la base de datos
|
||||
* @return array Array asociativo con todas las configuraciones
|
||||
*/
|
||||
function getAllConfigsFromDB() {
|
||||
try {
|
||||
if (!isInstallationCompleted()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$pdo = createDbConnection();
|
||||
|
||||
// Verificar si existe la tabla
|
||||
$tableExists = $pdo->query("SHOW TABLES LIKE 'system_config'")->rowCount() > 0;
|
||||
|
||||
if (!$tableExists) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$stmt = $pdo->query("
|
||||
SELECT config_key, config_value, description, created_at, updated_at
|
||||
FROM system_config
|
||||
ORDER BY config_key
|
||||
");
|
||||
|
||||
$configs = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$configs[$row['config_key']] = [
|
||||
'value' => $row['config_value'],
|
||||
'description' => $row['description'],
|
||||
'created_at' => $row['created_at'],
|
||||
'updated_at' => $row['updated_at']
|
||||
];
|
||||
}
|
||||
|
||||
return $configs;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error obteniendo todas las configuraciones: " . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina una configuración de la base de datos
|
||||
* @param string $key Clave de configuración a eliminar
|
||||
* @return bool True si se eliminó correctamente
|
||||
*/
|
||||
function deleteConfigFromDB($key) {
|
||||
try {
|
||||
if (!isInstallationCompleted()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pdo = createDbConnection();
|
||||
$stmt = $pdo->prepare("DELETE FROM system_config WHERE config_key = ?");
|
||||
$result = $stmt->execute([$key]);
|
||||
|
||||
if ($result) {
|
||||
clearConfigCache();
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error eliminando configuración '$key': " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpia el cache estático de configuraciones
|
||||
*/
|
||||
function clearConfigCache() {
|
||||
// Necesitamos acceder a la variable estática de getConfigFromDB
|
||||
// Como no podemos modificar directamente variables estáticas de otra función,
|
||||
// establecemos una flag para que getConfigFromDB reinicie el cache
|
||||
global $config_cache_cleared;
|
||||
$config_cache_cleared = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crea la tabla system_config si no existe
|
||||
* @param PDO $pdo Conexión a la base de datos
|
||||
* @return bool True si se creó o ya existía
|
||||
*/
|
||||
function createSystemConfigTable($pdo) {
|
||||
try {
|
||||
$sql = "CREATE TABLE IF NOT EXISTS system_config (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
config_key VARCHAR(255) NOT NULL UNIQUE,
|
||||
config_value TEXT,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_config_key (config_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
|
||||
|
||||
return $pdo->exec($sql) !== false;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error creando tabla system_config: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// WhatsApp Business API (prioridad: BD > .env > default)
|
||||
define('WHATSAPP_TOKEN', getConfigFromDB('whatsapp_token', env('WHATSAPP_TOKEN', 'TU_TOKEN_DE_WHATSAPP_AQUI')));
|
||||
define('WHATSAPP_PHONE_NUMBER_ID', getConfigFromDB('whatsapp_phone_number_id', env('WHATSAPP_PHONE_NUMBER_ID', 'TU_PHONE_ID_AQUI')));
|
||||
define('WHATSAPP_API_URL', getConfigFromDB('whatsapp_api_url', env('WHATSAPP_API_URL', 'https://graph.facebook.com/v22.0/')));
|
||||
define('WEBHOOK_VERIFY_TOKEN', getConfigFromDB('webhook_verify_token', env('WEBHOOK_VERIFY_TOKEN', 'mi_token_secreto_123')));
|
||||
|
||||
// Configuración general
|
||||
define('APP_NAME', 'WhatsApp Bot System');
|
||||
@@ -33,8 +348,9 @@ define('DEVELOPER_SUPPORT', 'https://u-site.app/support');
|
||||
|
||||
// Configuración de seguridad
|
||||
define('INSTALLATION_LOCK', '.installation_completed'); // Archivo de bloqueo
|
||||
define('SECRET_KEY', 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
|
||||
define('ADMIN_USERNAME', 'admin'); // Usuario administrador por defecto
|
||||
define('ADMIN_PASSWORD', ''); // Se configurará en instalación
|
||||
// ADMIN_PASSWORD movido arriba como parte del sistema unificado (línea 18)
|
||||
define('SESSION_TIMEOUT', 1800); // 30 minutos
|
||||
define('MAX_LOGIN_ATTEMPTS', 3); // Intentos máximos de login
|
||||
define('LOGIN_LOCKOUT_TIME', 900); // 15 minutos de bloqueo tras fallos
|
||||
@@ -75,9 +391,126 @@ if (!defined('AUTO_DETECTED_URL')) {
|
||||
define('AUTO_DETECTED_URL', $protocol . $host . $path);
|
||||
}
|
||||
|
||||
// Función para verificar si la instalación está completada
|
||||
function isInstallationCompleted() {
|
||||
return file_exists(dirname(__DIR__) . '/.installation_completed');
|
||||
|
||||
// ========================================
|
||||
// SISTEMA DE AUTENTICACIÓN UNIFICADO
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Crea conexión a la base de datos
|
||||
* @return PDO
|
||||
*/
|
||||
function createDbConnection() {
|
||||
try {
|
||||
$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,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]
|
||||
);
|
||||
return $pdo;
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error de conexión BD: " . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Autentica un usuario usando la base de datos
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return array|false Datos del usuario o false si falla
|
||||
*/
|
||||
function authenticateUser($username, $password) {
|
||||
try {
|
||||
$pdo = createDbConnection();
|
||||
|
||||
// Primero verificar si existe la tabla admin_users
|
||||
$checkTable = $pdo->query("SHOW TABLES LIKE 'admin_users'");
|
||||
$tableExists = $checkTable->rowCount() > 0;
|
||||
|
||||
if ($tableExists) {
|
||||
// Sistema moderno: autenticación por BD
|
||||
$stmt = $pdo->prepare("SELECT * FROM admin_users WHERE username = ? AND is_active = 1");
|
||||
$stmt->execute([$username]);
|
||||
$admin = $stmt->fetch();
|
||||
|
||||
if ($admin && password_verify($password, $admin['password_hash'])) {
|
||||
// Actualizar último login
|
||||
$updateLogin = $pdo->prepare("UPDATE admin_users SET last_login = NOW() WHERE id = ?");
|
||||
$updateLogin->execute([$admin['id']]);
|
||||
|
||||
return [
|
||||
'id' => $admin['id'],
|
||||
'username' => $admin['username'],
|
||||
'full_name' => $admin['full_name'],
|
||||
'email' => $admin['email']
|
||||
];
|
||||
}
|
||||
} else {
|
||||
// Sistema legacy: usar constante ADMIN_PASSWORD
|
||||
if ($username === 'admin' && password_verify($password, ADMIN_PASSWORD)) {
|
||||
return [
|
||||
'id' => 1,
|
||||
'username' => 'admin',
|
||||
'full_name' => 'Administrador',
|
||||
'email' => 'admin@sistema.local'
|
||||
];
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Error en autenticación: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si el sistema está migrado a BD
|
||||
* @return bool
|
||||
*/
|
||||
function isAuthSystemMigrated() {
|
||||
try {
|
||||
$pdo = createDbConnection();
|
||||
$checkTable = $pdo->query("SHOW TABLES LIKE 'admin_users'");
|
||||
return $checkTable->rowCount() > 0;
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene todos los administradores del sistema
|
||||
* @return array
|
||||
*/
|
||||
function getAdminUsers() {
|
||||
try {
|
||||
$pdo = createDbConnection();
|
||||
|
||||
if (isAuthSystemMigrated()) {
|
||||
$stmt = $pdo->query("SELECT * FROM admin_users ORDER BY created_at DESC");
|
||||
return $stmt->fetchAll();
|
||||
} else {
|
||||
// Sistema legacy: retornar admin por defecto
|
||||
return [[
|
||||
'id' => 1,
|
||||
'username' => 'admin',
|
||||
'full_name' => 'Administrador',
|
||||
'email' => 'admin@sistema.local',
|
||||
'is_active' => 1,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
'last_login' => null
|
||||
]];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error_log("Error obteniendo usuarios admin: " . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Función para verificar login
|
||||
@@ -160,35 +593,222 @@ spl_autoload_register(function ($class) {
|
||||
}
|
||||
});
|
||||
|
||||
// Función para obtener configuración desde base de datos
|
||||
function getConfigFromDB($key, $default = null) {
|
||||
static $configs = null;
|
||||
|
||||
if ($configs === null && isInstallationCompleted()) {
|
||||
try {
|
||||
$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,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
|
||||
/**
|
||||
* Guarda la configuración completa de WhatsApp
|
||||
* @param array $whatsappConfig Array con las configuraciones de WhatsApp
|
||||
* @return bool True si se guardó correctamente
|
||||
*/
|
||||
function saveWhatsAppConfigToDB($whatsappConfig) {
|
||||
$success = true;
|
||||
|
||||
// Mapear configuraciones de WhatsApp y guardar individualmente
|
||||
if (isset($whatsappConfig['token'])) {
|
||||
$success &= saveConfigToDB('whatsapp_token', $whatsappConfig['token']);
|
||||
}
|
||||
|
||||
if (isset($whatsappConfig['phone_number_id'])) {
|
||||
$success &= saveConfigToDB('whatsapp_phone_number_id', $whatsappConfig['phone_number_id']);
|
||||
}
|
||||
|
||||
if (isset($whatsappConfig['api_url'])) {
|
||||
$success &= saveConfigToDB('whatsapp_api_url', $whatsappConfig['api_url']);
|
||||
}
|
||||
|
||||
if (isset($whatsappConfig['webhook_verify_token'])) {
|
||||
$success &= saveConfigToDB('webhook_verify_token', $whatsappConfig['webhook_verify_token']);
|
||||
}
|
||||
|
||||
if (isset($whatsappConfig['business_name'])) {
|
||||
$success &= saveConfigToDB('business_name', $whatsappConfig['business_name']);
|
||||
}
|
||||
|
||||
if (isset($whatsappConfig['welcome_message'])) {
|
||||
$success &= saveConfigToDB('welcome_message', $whatsappConfig['welcome_message']);
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene toda la configuración de WhatsApp desde BD
|
||||
* @return array Configuración completa de WhatsApp
|
||||
*/
|
||||
function getWhatsAppConfigFromDB() {
|
||||
return [
|
||||
'token' => getConfigFromDB('whatsapp_token', ''),
|
||||
'phone_number_id' => getConfigFromDB('whatsapp_phone_number_id', ''),
|
||||
'api_url' => getConfigFromDB('whatsapp_api_url', 'https://graph.facebook.com/v22.0/'),
|
||||
'webhook_verify_token' => getConfigFromDB('webhook_verify_token', ''),
|
||||
'business_name' => getConfigFromDB('business_name', ''),
|
||||
'welcome_message' => getConfigFromDB('welcome_message', ''),
|
||||
'status' => validateWhatsAppConfigFromDB()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida la configuración de WhatsApp desde BD
|
||||
* @return array Estado de validación
|
||||
*/
|
||||
function validateWhatsAppConfigFromDB() {
|
||||
$token = getConfigFromDB('whatsapp_token', '');
|
||||
$phoneId = getConfigFromDB('whatsapp_phone_number_id', '');
|
||||
$webhookToken = getConfigFromDB('webhook_verify_token', '');
|
||||
|
||||
$errors = [];
|
||||
$warnings = [];
|
||||
|
||||
if (empty($token) || $token === 'TU_TOKEN_DE_WHATSAPP_AQUI') {
|
||||
$errors[] = 'Token de WhatsApp no configurado';
|
||||
}
|
||||
|
||||
if (empty($phoneId) || $phoneId === 'TU_PHONE_ID_AQUI') {
|
||||
$errors[] = 'Phone Number ID no configurado';
|
||||
} elseif ($phoneId === '858157464051987') {
|
||||
$warnings[] = 'Phone Number ID problemático detectado (858157464051987)';
|
||||
}
|
||||
|
||||
if (empty($webhookToken)) {
|
||||
$errors[] = 'Token de verificación del webhook no configurado';
|
||||
}
|
||||
|
||||
return [
|
||||
'valid' => empty($errors),
|
||||
'errors' => $errors,
|
||||
'warnings' => $warnings,
|
||||
'configured_fields' => [
|
||||
'token' => !empty($token) && $token !== 'TU_TOKEN_DE_WHATSAPP_AQUI',
|
||||
'phone_id' => !empty($phoneId) && $phoneId !== 'TU_PHONE_ID_AQUI',
|
||||
'webhook_token' => !empty($webhookToken)
|
||||
]
|
||||
);
|
||||
|
||||
$stmt = $pdo->query("SELECT config_key, config_value FROM system_config");
|
||||
$configs = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$configs[$row['config_key']] = $row['config_value'];
|
||||
];
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
error_log("Error loading config: " . $e->getMessage());
|
||||
$configs = [];
|
||||
|
||||
/**
|
||||
* Inicializa configuraciones por defecto en la base de datos
|
||||
* @return bool True si se inicializaron correctamente
|
||||
*/
|
||||
function initializeDefaultConfigs() {
|
||||
$defaultConfigs = [
|
||||
// Configuración de WhatsApp
|
||||
'whatsapp_token' => env('WHATSAPP_TOKEN', 'TU_TOKEN_DE_WHATSAPP_AQUI'),
|
||||
'whatsapp_phone_number_id' => env('WHATSAPP_PHONE_NUMBER_ID', 'TU_PHONE_ID_AQUI'),
|
||||
'whatsapp_api_url' => env('WHATSAPP_API_URL', 'https://graph.facebook.com/v22.0/'),
|
||||
'webhook_verify_token' => env('WEBHOOK_VERIFY_TOKEN', 'mi_token_secreto_123'),
|
||||
|
||||
// Configuración de la aplicación
|
||||
'app_name' => 'WhatsApp Bot System',
|
||||
'app_version' => '1.0.0',
|
||||
'timezone' => 'America/Bogota',
|
||||
'developer_name' => 'U-Site.app',
|
||||
'developer_url' => 'https://u-site.app',
|
||||
'developer_email' => 'support@u-site.app',
|
||||
|
||||
// Configuración de logs y archivos
|
||||
'enable_logging' => '1',
|
||||
'log_level' => 'INFO',
|
||||
'upload_max_size' => '10485760',
|
||||
'allowed_file_types' => 'jpg,jpeg,png,gif,pdf,doc,docx',
|
||||
|
||||
// Configuración de seguridad
|
||||
'session_timeout' => '1800',
|
||||
'max_login_attempts' => '3',
|
||||
'login_lockout_time' => '900'
|
||||
];
|
||||
|
||||
return saveMultipleConfigsToDB($defaultConfigs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migra configuraciones desde constantes/archivos a la base de datos
|
||||
* @return bool True si la migración fue exitosa
|
||||
*/
|
||||
function migrateConfigsToDatabase() {
|
||||
try {
|
||||
writeLog('INFO', 'Iniciando migración de configuraciones a BD');
|
||||
|
||||
// Crear tabla si no existe
|
||||
$pdo = createDbConnection();
|
||||
if (!createSystemConfigTable($pdo)) {
|
||||
writeLog('ERROR', 'No se pudo crear la tabla system_config');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Migrar configuraciones actuales
|
||||
$success = initializeDefaultConfigs();
|
||||
|
||||
if ($success) {
|
||||
writeLog('INFO', 'Migración de configuraciones completada exitosamente');
|
||||
} else {
|
||||
writeLog('ERROR', 'Error durante la migración de configuraciones');
|
||||
}
|
||||
|
||||
return $success;
|
||||
|
||||
} catch (Exception $e) {
|
||||
writeLog('ERROR', 'Error crítico en migración de configuraciones: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $configs[$key] ?? $default;
|
||||
// Función para validar configuración de WhatsApp
|
||||
function validateWhatsAppConfig() {
|
||||
$errors = [];
|
||||
|
||||
if (WHATSAPP_TOKEN === 'TU_TOKEN_DE_WHATSAPP_AQUI' || empty(WHATSAPP_TOKEN)) {
|
||||
$errors[] = 'Token de WhatsApp no configurado';
|
||||
}
|
||||
|
||||
if (WHATSAPP_PHONE_NUMBER_ID === 'TU_PHONE_ID_AQUI' || empty(WHATSAPP_PHONE_NUMBER_ID)) {
|
||||
$errors[] = 'Phone Number ID de WhatsApp no configurado';
|
||||
}
|
||||
|
||||
// Detectar Phone Number ID problemático conocido
|
||||
if (WHATSAPP_PHONE_NUMBER_ID === '858157464051987') {
|
||||
$errors[] = '⚠️ PHONE NUMBER ID PROBLEMÁTICO: El ID 858157464051987 genera errores. <a href="fix_phone_number_id.php" class="alert-link"><strong>Corregir ahora</strong></a>';
|
||||
}
|
||||
|
||||
if (empty(WEBHOOK_VERIFY_TOKEN)) {
|
||||
$errors[] = 'Token de verificación del webhook no configurado';
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
// Función para logging centralizado
|
||||
function writeLog($level, $message, $context = []) {
|
||||
if (!ENABLE_LOGGING) return;
|
||||
|
||||
$logLevels = ['DEBUG' => 0, 'INFO' => 1, 'WARNING' => 2, 'ERROR' => 3];
|
||||
$currentLevel = $logLevels[LOG_LEVEL] ?? 1;
|
||||
|
||||
if (($logLevels[$level] ?? 1) < $currentLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$contextStr = $context ? ' ' . json_encode($context) : '';
|
||||
$logMessage = "[$timestamp] [$level] $message$contextStr" . PHP_EOL;
|
||||
|
||||
$logDir = dirname(__DIR__) . '/logs';
|
||||
if (!is_dir($logDir)) {
|
||||
mkdir($logDir, 0755, true);
|
||||
}
|
||||
|
||||
file_put_contents($logDir . '/system.log', $logMessage, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
|
||||
// Crear directorios necesarios
|
||||
$requiredDirs = [
|
||||
dirname(__DIR__) . '/logs',
|
||||
dirname(__DIR__) . '/uploads'
|
||||
];
|
||||
|
||||
foreach ($requiredDirs as $dir) {
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializar sesión si no está activa
|
||||
@@ -208,4 +828,18 @@ if (isUserLoggedIn() && isset($_SESSION['last_activity'])) {
|
||||
if (isUserLoggedIn()) {
|
||||
$_SESSION['last_activity'] = time();
|
||||
}
|
||||
|
||||
// Función para verificar autenticación en APIs
|
||||
function requireAuthentication() {
|
||||
if (!isUserLoggedIn()) {
|
||||
http_response_code(401);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => 'No autorizado. Inicie sesión.',
|
||||
'redirect' => 'login.php'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,332 @@
|
||||
<?php
|
||||
/**
|
||||
* Configuración dinámica del sistema WhatsApp Bot Manager
|
||||
* Carga variables de entorno desde .env si existe
|
||||
* Desarrollado por U-Site.app
|
||||
* Fecha: 13 de noviembre de 2025
|
||||
*/
|
||||
|
||||
// Función para cargar archivo .env
|
||||
function loadEnvFile($path) {
|
||||
if (!file_exists($path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, '#') === 0 || strpos($line, '=') === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$name, $value] = explode('=', $line, 2);
|
||||
$name = trim($name);
|
||||
$value = trim($value, " \t\n\r\0\x0B\"'");
|
||||
|
||||
if (!array_key_exists($name, $_ENV)) {
|
||||
$_ENV[$name] = $value;
|
||||
putenv("$name=$value");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Cargar .env si existe
|
||||
$envLoaded = loadEnvFile(__DIR__ . '/../.env');
|
||||
|
||||
// Función helper para obtener variables de entorno con fallback
|
||||
function env($key, $default = null) {
|
||||
return $_ENV[$key] ?? getenv($key) ?: $default;
|
||||
}
|
||||
|
||||
// Configuración de la base de datos
|
||||
define('DB_HOST', env('DB_HOST', 'localhost'));
|
||||
define('DB_PORT', env('DB_PORT', '3306'));
|
||||
define('DB_NAME', env('DB_NAME', 'whatsapp_bot'));
|
||||
define('DB_USER', env('DB_USER', 'whatsapp_user'));
|
||||
define('DB_PASS', env('DB_PASS', '')); // Se generará automáticamente en la instalación
|
||||
define('DB_CHARSET', env('DB_CHARSET', 'utf8mb4'));
|
||||
|
||||
// WhatsApp Business API
|
||||
define('WHATSAPP_TOKEN', env('WHATSAPP_TOKEN', 'TU_TOKEN_DE_WHATSAPP_AQUI'));
|
||||
define('WHATSAPP_PHONE_NUMBER_ID', env('WHATSAPP_PHONE_NUMBER_ID', 'TU_PHONE_ID_AQUI'));
|
||||
define('WHATSAPP_API_URL', env('WHATSAPP_API_URL', 'https://graph.facebook.com/v22.0/'));
|
||||
define('WEBHOOK_VERIFY_TOKEN', env('WEBHOOK_VERIFY_TOKEN', 'mi_token_secreto_123'));
|
||||
|
||||
// Configuración general
|
||||
define('APP_NAME', env('APP_NAME', 'WhatsApp Bot System'));
|
||||
define('APP_VERSION', env('APP_VERSION', '1.0.0'));
|
||||
define('APP_URL', env('APP_URL', 'https://tudominio.com')); // Se auto-detectará
|
||||
define('TIMEZONE', env('TIMEZONE', 'America/Bogota'));
|
||||
|
||||
// Información del desarrollador
|
||||
define('DEVELOPER_NAME', 'U-Site.app');
|
||||
define('DEVELOPER_URL', 'https://u-site.app');
|
||||
define('DEVELOPER_EMAIL', 'support@u-site.app');
|
||||
define('DEVELOPER_SUPPORT', 'https://u-site.app/support');
|
||||
|
||||
// Configuración de seguridad
|
||||
define('INSTALLATION_LOCK', '.installation_completed'); // Archivo de bloqueo
|
||||
define('ADMIN_USERNAME', env('ADMIN_USERNAME', 'admin')); // Usuario administrador por defecto
|
||||
define('ADMIN_PASSWORD', env('ADMIN_PASSWORD', '')); // Se configurará en instalación
|
||||
define('SESSION_TIMEOUT', (int)env('SESSION_TIMEOUT', 1800)); // 30 minutos
|
||||
define('MAX_LOGIN_ATTEMPTS', (int)env('MAX_LOGIN_ATTEMPTS', 3)); // Intentos máximos de login
|
||||
define('LOGIN_LOCKOUT_TIME', (int)env('LOGIN_LOCKOUT_TIME', 900)); // 15 minutos de bloqueo tras fallos
|
||||
|
||||
// Configuración de logs
|
||||
define('ENABLE_LOGGING', filter_var(env('ENABLE_LOGGING', 'true'), FILTER_VALIDATE_BOOLEAN));
|
||||
define('LOG_LEVEL', env('LOG_LEVEL', 'INFO')); // DEBUG, INFO, WARNING, ERROR
|
||||
define('LOG_FILE', env('LOG_FILE', 'logs/system.log'));
|
||||
|
||||
// Configuración de archivos
|
||||
define('UPLOAD_MAX_SIZE', (int)env('UPLOAD_MAX_SIZE', 10485760)); // 10MB
|
||||
define('ALLOWED_FILE_TYPES', ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx', 'mp4', 'mp3', 'wav']);
|
||||
|
||||
// Configurar zona horaria
|
||||
date_default_timezone_set(TIMEZONE);
|
||||
|
||||
// Configurar errores para desarrollo (se desactivará en producción)
|
||||
$isProduction = env('APP_ENV', 'development') === 'production';
|
||||
$installationCompleted = file_exists(dirname(__DIR__) . '/.installation_completed');
|
||||
|
||||
if (!$isProduction && !$installationCompleted) {
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
} else {
|
||||
error_reporting(0);
|
||||
ini_set('display_errors', 0);
|
||||
}
|
||||
|
||||
ini_set('log_errors', 1);
|
||||
if (ENABLE_LOGGING) {
|
||||
ini_set('error_log', dirname(__DIR__) . '/' . LOG_FILE);
|
||||
}
|
||||
|
||||
// Headers de seguridad
|
||||
if (!headers_sent()) {
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
header('X-Frame-Options: SAMEORIGIN');
|
||||
header('X-XSS-Protection: 1; mode=block');
|
||||
header('Referrer-Policy: strict-origin-when-cross-origin');
|
||||
}
|
||||
|
||||
// Auto-detectar URL base si no está configurada
|
||||
if (!defined('AUTO_DETECTED_URL')) {
|
||||
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://';
|
||||
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||||
$path = dirname($_SERVER['SCRIPT_NAME'] ?? '');
|
||||
define('AUTO_DETECTED_URL', $protocol . $host . $path);
|
||||
}
|
||||
|
||||
// Función para logging centralizado
|
||||
function writeLog($level, $message, $context = []) {
|
||||
if (!ENABLE_LOGGING) return;
|
||||
|
||||
$logLevels = ['DEBUG' => 0, 'INFO' => 1, 'WARNING' => 2, 'ERROR' => 3];
|
||||
$currentLevel = $logLevels[LOG_LEVEL] ?? 1;
|
||||
|
||||
if (($logLevels[$level] ?? 1) < $currentLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$contextStr = $context ? ' ' . json_encode($context) : '';
|
||||
$logMessage = "[$timestamp] [$level] $message$contextStr" . PHP_EOL;
|
||||
|
||||
$logDir = dirname(dirname(__FILE__)) . '/logs';
|
||||
if (!is_dir($logDir)) {
|
||||
mkdir($logDir, 0755, true);
|
||||
}
|
||||
|
||||
file_put_contents($logDir . '/system.log', $logMessage, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
|
||||
// Función para verificar si la instalación está completada
|
||||
function isInstallationCompleted() {
|
||||
return file_exists(dirname(__DIR__) . '/.installation_completed');
|
||||
}
|
||||
|
||||
// Función para verificar login
|
||||
function isUserLoggedIn() {
|
||||
return isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true;
|
||||
}
|
||||
|
||||
// Función para verificar intentos de login
|
||||
function checkLoginAttempts($ip) {
|
||||
$attemptsFile = dirname(__DIR__) . '/.login_attempts.json';
|
||||
|
||||
if (!file_exists($attemptsFile)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$attempts = json_decode(file_get_contents($attemptsFile), true);
|
||||
|
||||
if (!isset($attempts[$ip])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$ipData = $attempts[$ip];
|
||||
|
||||
if (time() > $ipData['locked_until']) {
|
||||
unset($attempts[$ip]);
|
||||
file_put_contents($attemptsFile, json_encode($attempts));
|
||||
return true;
|
||||
}
|
||||
|
||||
return $ipData['attempts'] < MAX_LOGIN_ATTEMPTS;
|
||||
}
|
||||
|
||||
// Función para registrar intento de login fallido
|
||||
function recordFailedLogin($ip) {
|
||||
$attemptsFile = dirname(__DIR__) . '/.login_attempts.json';
|
||||
|
||||
$attempts = [];
|
||||
if (file_exists($attemptsFile)) {
|
||||
$attempts = json_decode(file_get_contents($attemptsFile), true) ?: [];
|
||||
}
|
||||
|
||||
if (!isset($attempts[$ip])) {
|
||||
$attempts[$ip] = ['attempts' => 0, 'locked_until' => 0];
|
||||
}
|
||||
|
||||
$attempts[$ip]['attempts']++;
|
||||
|
||||
if ($attempts[$ip]['attempts'] >= MAX_LOGIN_ATTEMPTS) {
|
||||
$attempts[$ip]['locked_until'] = time() + LOGIN_LOCKOUT_TIME;
|
||||
}
|
||||
|
||||
file_put_contents($attemptsFile, json_encode($attempts));
|
||||
writeLog('WARNING', 'Failed login attempt', ['ip' => $ip, 'attempts' => $attempts[$ip]['attempts']]);
|
||||
}
|
||||
|
||||
// Función para limpiar intentos de login exitoso
|
||||
function clearLoginAttempts($ip) {
|
||||
$attemptsFile = dirname(__DIR__) . '/.login_attempts.json';
|
||||
|
||||
if (file_exists($attemptsFile)) {
|
||||
$attempts = json_decode(file_get_contents($attemptsFile), true) ?: [];
|
||||
unset($attempts[$ip]);
|
||||
file_put_contents($attemptsFile, json_encode($attempts));
|
||||
}
|
||||
|
||||
writeLog('INFO', 'Successful login', ['ip' => $ip]);
|
||||
}
|
||||
|
||||
// Autoloader mejorado
|
||||
spl_autoload_register(function ($class) {
|
||||
$directories = [
|
||||
dirname(__DIR__) . '/classes/',
|
||||
dirname(__DIR__) . '/services/',
|
||||
dirname(__DIR__) . '/controllers/'
|
||||
];
|
||||
|
||||
foreach ($directories as $directory) {
|
||||
$file = $directory . $class . '.php';
|
||||
if (file_exists($file)) {
|
||||
require_once $file;
|
||||
writeLog('DEBUG', 'Class loaded', ['class' => $class, 'file' => $file]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
writeLog('ERROR', 'Class not found', ['class' => $class]);
|
||||
});
|
||||
|
||||
// Función para obtener configuración desde base de datos
|
||||
function getConfigFromDB($key, $default = null) {
|
||||
static $configs = null;
|
||||
|
||||
if ($configs === null && isInstallationCompleted()) {
|
||||
try {
|
||||
$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,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]
|
||||
);
|
||||
|
||||
$stmt = $pdo->query("SELECT config_key, config_value FROM system_config");
|
||||
$configs = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$configs[$row['config_key']] = $row['config_value'];
|
||||
}
|
||||
|
||||
writeLog('DEBUG', 'Database configuration loaded');
|
||||
} catch (PDOException $e) {
|
||||
writeLog('ERROR', 'Error loading config from database', ['error' => $e->getMessage()]);
|
||||
$configs = [];
|
||||
}
|
||||
}
|
||||
|
||||
return $configs[$key] ?? $default;
|
||||
}
|
||||
|
||||
// Función para validar configuración de WhatsApp
|
||||
function validateWhatsAppConfig() {
|
||||
$errors = [];
|
||||
|
||||
if (WHATSAPP_TOKEN === 'TU_TOKEN_DE_WHATSAPP_AQUI' || empty(WHATSAPP_TOKEN)) {
|
||||
$errors[] = 'Token de WhatsApp no configurado';
|
||||
}
|
||||
|
||||
if (WHATSAPP_PHONE_NUMBER_ID === 'TU_PHONE_ID_AQUI' || empty(WHATSAPP_PHONE_NUMBER_ID)) {
|
||||
$errors[] = 'Phone Number ID de WhatsApp no configurado';
|
||||
}
|
||||
|
||||
if (empty(WEBHOOK_VERIFY_TOKEN)) {
|
||||
$errors[] = 'Token de verificación del webhook no configurado';
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
// Función para crear directorio si no existe
|
||||
function ensureDirectoryExists($path) {
|
||||
if (!is_dir($path)) {
|
||||
mkdir($path, 0755, true);
|
||||
writeLog('INFO', 'Directory created', ['path' => $path]);
|
||||
}
|
||||
}
|
||||
|
||||
// Crear directorios necesarios
|
||||
ensureDirectoryExists(dirname(__DIR__) . '/logs');
|
||||
ensureDirectoryExists(dirname(__DIR__) . '/uploads');
|
||||
|
||||
// Inicializar sesión si no está activa
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
ini_set('session.cookie_httponly', 1);
|
||||
ini_set('session.cookie_secure', isset($_SERVER['HTTPS']));
|
||||
ini_set('session.use_strict_mode', 1);
|
||||
session_start();
|
||||
}
|
||||
|
||||
// Verificar timeout de sesión
|
||||
if (isUserLoggedIn() && isset($_SESSION['last_activity'])) {
|
||||
if (time() - $_SESSION['last_activity'] > SESSION_TIMEOUT) {
|
||||
writeLog('INFO', 'Session timeout', ['user' => $_SESSION['username'] ?? 'unknown']);
|
||||
session_destroy();
|
||||
session_start();
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar última actividad
|
||||
if (isUserLoggedIn()) {
|
||||
$_SESSION['last_activity'] = time();
|
||||
}
|
||||
|
||||
// Registrar acceso si está habilitado el logging
|
||||
if (ENABLE_LOGGING && isset($_SERVER['REQUEST_URI'])) {
|
||||
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown';
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? 'Unknown';
|
||||
|
||||
writeLog('DEBUG', 'Page access', [
|
||||
'uri' => $_SERVER['REQUEST_URI'],
|
||||
'ip' => $ip,
|
||||
'user_agent' => substr($userAgent, 0, 100)
|
||||
]);
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,115 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>✅ Sistema de Configuraciones - Funcionando</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; background: #f5f5f5; }
|
||||
.success { background: #d4edda; color: #155724; padding: 15px; border-radius: 5px; margin: 10px 0; }
|
||||
.error { background: #f8d7da; color: #721c24; padding: 15px; border-radius: 5px; margin: 10px 0; }
|
||||
.info { background: #d1ecf1; color: #0c5460; padding: 15px; border-radius: 5px; margin: 10px 0; }
|
||||
.code { background: #f8f9fa; border-left: 4px solid #007bff; padding: 10px; margin: 10px 0; font-family: monospace; }
|
||||
h1 { color: #28a745; }
|
||||
h2 { color: #007bff; }
|
||||
.btn { background: #007bff; color: white; padding: 10px 15px; text-decoration: none; border-radius: 5px; display: inline-block; margin: 5px; }
|
||||
.btn:hover { background: #0056b3; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🚀 Sistema de Configuraciones WhatsApp Bot</h1>
|
||||
|
||||
<?php
|
||||
require_once 'config/config.php';
|
||||
|
||||
try {
|
||||
// Verificar conexión
|
||||
$pdo = createDbConnection();
|
||||
echo '<div class="success">✅ <strong>Conexión a BD:</strong> Exitosa</div>';
|
||||
|
||||
// Verificar tabla
|
||||
$tableExists = $pdo->query("SHOW TABLES LIKE 'system_config'")->rowCount() > 0;
|
||||
echo '<div class="success">✅ <strong>Tabla system_config:</strong> ' . ($tableExists ? 'Existe' : 'No existe') . '</div>';
|
||||
|
||||
// Mostrar configuraciones actuales
|
||||
$configs = getAllConfigsFromDB();
|
||||
echo '<div class="info">📊 <strong>Total de configuraciones:</strong> ' . count($configs) . '</div>';
|
||||
|
||||
// Probar guardar una configuración
|
||||
$testResult = saveConfigToDB('ultimo_test', date('Y-m-d H:i:s'), 'Última prueba del sistema');
|
||||
echo '<div class="success">💾 <strong>Guardar configuración:</strong> ' . ($testResult ? 'Funcional' : 'Error') . '</div>';
|
||||
|
||||
// Probar obtener configuración
|
||||
$testValue = getConfigFromDB('ultimo_test');
|
||||
echo '<div class="success">📖 <strong>Obtener configuración:</strong> ' . ($testValue ? 'Funcional' : 'Error') . '</div>';
|
||||
|
||||
// Configuración de WhatsApp
|
||||
$whatsappConfig = getWhatsAppConfigFromDB();
|
||||
$whatsappValidation = validateWhatsAppConfigFromDB();
|
||||
echo '<div class="info">📱 <strong>WhatsApp configurado:</strong> ' . ($whatsappValidation['valid'] ? 'Válido' : 'Necesita configuración') . '</div>';
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo '<div class="error">❌ <strong>Error:</strong> ' . $e->getMessage() . '</div>';
|
||||
}
|
||||
?>
|
||||
|
||||
<h2>🔧 Funciones Disponibles</h2>
|
||||
|
||||
<div class="info">
|
||||
<p><strong>El sistema de configuraciones está completamente funcional:</strong></p>
|
||||
<ul>
|
||||
<li>✅ Conexión a base de datos MySQL</li>
|
||||
<li>✅ Tabla system_config creada automáticamente</li>
|
||||
<li>✅ Funciones de guardar/obtener configuraciones</li>
|
||||
<li>✅ Sistema de cache inteligente</li>
|
||||
<li>✅ Configuraciones específicas de WhatsApp</li>
|
||||
<li>✅ Validación de configuraciones</li>
|
||||
<li>✅ API REST completa</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>📝 Ejemplos de Uso</h2>
|
||||
|
||||
<div class="code">
|
||||
<strong>// Guardar configuración</strong><br>
|
||||
saveConfigToDB('mi_configuracion', 'mi_valor', 'Descripción opcional');<br><br>
|
||||
|
||||
<strong>// Obtener configuración</strong><br>
|
||||
$valor = getConfigFromDB('mi_configuracion', 'valor_por_defecto');<br><br>
|
||||
|
||||
<strong>// Configurar WhatsApp</strong><br>
|
||||
saveWhatsAppConfigToDB([<br>
|
||||
'token' => 'tu_token_aqui',<br>
|
||||
'phone_number_id' => '123456789'<br>
|
||||
]);<br><br>
|
||||
|
||||
<strong>// Obtener configuración de WhatsApp</strong><br>
|
||||
$whatsapp = getWhatsAppConfigFromDB();
|
||||
</div>
|
||||
|
||||
<h2>🌐 Archivos del Sistema</h2>
|
||||
|
||||
<div class="info">
|
||||
<p><strong>Archivos creados/modificados:</strong></p>
|
||||
<ul>
|
||||
<li>📁 <strong>config/config.php</strong> - Sistema completo de configuraciones</li>
|
||||
<li>📁 <strong>api/manage_config.php</strong> - API REST para configuraciones</li>
|
||||
<li>📁 <strong>test_config_system.php</strong> - Interfaz de pruebas completa</li>
|
||||
<li>📁 <strong>test_simple_config.php</strong> - Test básico por consola</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-top: 30px;">
|
||||
<a href="test_config_system.php" class="btn">🧪 Abrir Panel de Pruebas</a>
|
||||
<a href="test_simple_config.php" class="btn">📊 Ver Test Simple</a>
|
||||
<a href="index.php" class="btn">🏠 Ir al Sistema Principal</a>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-top: 20px; padding: 20px; background: white; border-radius: 10px;">
|
||||
<h3>🎉 ¡Sistema Listo!</h3>
|
||||
<p>El sistema de configuraciones está completamente implementado y funcionando.<br>
|
||||
Puedes guardar y obtener configuraciones desde la base de datos de forma segura y eficiente.</p>
|
||||
<p><small>Desarrollado por <strong>U-Site.app</strong> - <?= date('Y-m-d H:i:s') ?></small></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
/**
|
||||
* Configurador de Tokens y Variables - WhatsApp Bot Manager
|
||||
* Herramienta para configurar WEBHOOK_VERIFY_TOKEN y otros valores
|
||||
* 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>🔧 Configurador de Tokens - 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: 900px;
|
||||
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="text"], textarea {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
box-sizing: border-box;
|
||||
font-family: monospace;
|
||||
}
|
||||
input:focus, textarea: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;
|
||||
}
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background: #545b62;
|
||||
}
|
||||
.config-display {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
font-family: monospace;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
.token-generator {
|
||||
background: #e3f2fd;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin: 15px 0;
|
||||
border-left: 5px solid #2196f3;
|
||||
}
|
||||
.copy-btn {
|
||||
background: #2196f3;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 15px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
.copy-btn:hover {
|
||||
background: #1976d2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔧 Configurador de Tokens WhatsApp</h1>
|
||||
|
||||
<?php
|
||||
try {
|
||||
$pdo = createDbConnection();
|
||||
|
||||
echo '<div class="info-box">';
|
||||
echo '<h4>✅ Conexión a Base de Datos: EXITOSA</h4>';
|
||||
echo '</div>';
|
||||
|
||||
// Verificar si existe tabla system_config
|
||||
$configTableExists = $pdo->query("SHOW TABLES LIKE 'system_config'")->rowCount() > 0;
|
||||
|
||||
// Crear tabla si no existe
|
||||
if (!$configTableExists && isset($_POST['create_table'])) {
|
||||
$createTableSQL = "
|
||||
CREATE TABLE system_config (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
config_key VARCHAR(100) UNIQUE NOT NULL,
|
||||
config_value TEXT,
|
||||
description VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
|
||||
|
||||
$pdo->exec($createTableSQL);
|
||||
|
||||
// Insertar valores por defecto
|
||||
$defaultConfigs = [
|
||||
['webhook_verify_token', 'mi_token_secreto_' . bin2hex(random_bytes(8)), 'Token para verificar webhook de WhatsApp'],
|
||||
['whatsapp_token', '', 'Token de acceso de WhatsApp Business API'],
|
||||
['whatsapp_phone_number_id', '', 'ID del número de teléfono de WhatsApp'],
|
||||
['whatsapp_api_url', 'https://graph.facebook.com/v22.0/', 'URL base de la API de WhatsApp']
|
||||
];
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO system_config (config_key, config_value, description) VALUES (?, ?, ?)");
|
||||
foreach ($defaultConfigs as $config) {
|
||||
$stmt->execute($config);
|
||||
}
|
||||
|
||||
echo '<div class="info-box">✅ Tabla system_config creada e inicializada correctamente</div>';
|
||||
$configTableExists = true;
|
||||
}
|
||||
|
||||
// Procesar actualización de configuración
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_config'])) {
|
||||
$webhookToken = trim($_POST['webhook_verify_token']);
|
||||
$whatsappToken = trim($_POST['whatsapp_token']);
|
||||
$phoneNumberId = trim($_POST['whatsapp_phone_number_id']);
|
||||
$apiUrl = trim($_POST['whatsapp_api_url']);
|
||||
|
||||
if ($configTableExists) {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO system_config (config_key, config_value) VALUES (?, ?)
|
||||
ON DUPLICATE KEY UPDATE config_value = VALUES(config_value)
|
||||
");
|
||||
|
||||
$stmt->execute(['webhook_verify_token', $webhookToken]);
|
||||
$stmt->execute(['whatsapp_token', $whatsappToken]);
|
||||
$stmt->execute(['whatsapp_phone_number_id', $phoneNumberId]);
|
||||
$stmt->execute(['whatsapp_api_url', $apiUrl]);
|
||||
|
||||
echo '<div class="info-box">✅ Configuración actualizada exitosamente en la base de datos</div>';
|
||||
} else {
|
||||
echo '<div class="error-box">❌ Tabla system_config no existe. Créala primero.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Mostrar estado actual
|
||||
echo '<h3>📊 Estado Actual de la Configuración</h3>';
|
||||
echo '<div class="config-display">';
|
||||
|
||||
if ($configTableExists) {
|
||||
echo '<h4>🗃️ Configuración desde Base de Datos:</h4>';
|
||||
$stmt = $pdo->query("SELECT config_key, config_value FROM system_config ORDER BY config_key");
|
||||
$dbConfigs = $stmt->fetchAll();
|
||||
|
||||
foreach ($dbConfigs as $config) {
|
||||
$value = $config['config_value'];
|
||||
$displayValue = empty($value) ? '<em style="color: #999;">Sin valor</em>' : htmlspecialchars($value);
|
||||
echo "<strong>{$config['config_key']}:</strong> $displayValue<br>";
|
||||
}
|
||||
} else {
|
||||
echo '<h4>⚠️ Tabla system_config NO EXISTE</h4>';
|
||||
echo '<p>Los valores se toman de constantes PHP por defecto:</p>';
|
||||
}
|
||||
|
||||
echo '<br><h4>📋 Valores Activos (lo que usa el sistema ahora):</h4>';
|
||||
echo '<strong>WEBHOOK_VERIFY_TOKEN:</strong> ' . htmlspecialchars(WEBHOOK_VERIFY_TOKEN) . '<br>';
|
||||
echo '<strong>WHATSAPP_TOKEN:</strong> ' . htmlspecialchars(WHATSAPP_TOKEN) . '<br>';
|
||||
echo '<strong>WHATSAPP_PHONE_NUMBER_ID:</strong> ' . htmlspecialchars(WHATSAPP_PHONE_NUMBER_ID) . '<br>';
|
||||
echo '<strong>WHATSAPP_API_URL:</strong> ' . htmlspecialchars(WHATSAPP_API_URL) . '<br>';
|
||||
|
||||
echo '</div>';
|
||||
|
||||
// Si no existe la tabla, mostrar opción para crearla
|
||||
if (!$configTableExists) {
|
||||
echo '<div class="warning-box">';
|
||||
echo '<h4>🚨 Tabla de Configuración Faltante</h4>';
|
||||
echo '<p>La tabla <code>system_config</code> no existe. Esta tabla es necesaria para guardar la configuración en la base de datos.</p>';
|
||||
echo '<form method="post">';
|
||||
echo '<input type="hidden" name="create_table" value="1">';
|
||||
echo '<button type="submit" class="btn">🏗️ Crear Tabla system_config</button>';
|
||||
echo '</form>';
|
||||
echo '</div>';
|
||||
} else {
|
||||
// Formulario de configuración
|
||||
echo '<h3>⚙️ Actualizar Configuración</h3>';
|
||||
|
||||
// Obtener valores actuales de BD
|
||||
$currentValues = [];
|
||||
$stmt = $pdo->query("SELECT config_key, config_value FROM system_config");
|
||||
while ($row = $stmt->fetch()) {
|
||||
$currentValues[$row['config_key']] = $row['config_value'];
|
||||
}
|
||||
|
||||
echo '<form method="post">';
|
||||
|
||||
echo '<div class="form-group">';
|
||||
echo '<label for="webhook_verify_token">🔐 Webhook Verify Token:</label>';
|
||||
echo '<input type="text" id="webhook_verify_token" name="webhook_verify_token" ';
|
||||
echo 'value="' . htmlspecialchars($currentValues['webhook_verify_token'] ?? '') . '" ';
|
||||
echo 'placeholder="Token para verificar el webhook">';
|
||||
echo '<div class="token-generator">';
|
||||
echo '💡 <strong>Sugerencia:</strong> Usa un token seguro como: <code id="suggested-token">whatsapp_' . bin2hex(random_bytes(12)) . '</code>';
|
||||
echo '<button type="button" class="copy-btn" onclick="copyToField(\'suggested-token\', \'webhook_verify_token\')">📋 Copiar</button>';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="form-group">';
|
||||
echo '<label for="whatsapp_token">📱 WhatsApp Access Token:</label>';
|
||||
echo '<input type="text" id="whatsapp_token" name="whatsapp_token" ';
|
||||
echo 'value="' . htmlspecialchars($currentValues['whatsapp_token'] ?? '') . '" ';
|
||||
echo 'placeholder="Token de acceso de WhatsApp Business API">';
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="form-group">';
|
||||
echo '<label for="whatsapp_phone_number_id">📞 Phone Number ID:</label>';
|
||||
echo '<input type="text" id="whatsapp_phone_number_id" name="whatsapp_phone_number_id" ';
|
||||
echo 'value="' . htmlspecialchars($currentValues['whatsapp_phone_number_id'] ?? '') . '" ';
|
||||
echo 'placeholder="ID del número de teléfono">';
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="form-group">';
|
||||
echo '<label for="whatsapp_api_url">🌐 WhatsApp API URL:</label>';
|
||||
echo '<input type="text" id="whatsapp_api_url" name="whatsapp_api_url" ';
|
||||
echo 'value="' . htmlspecialchars($currentValues['whatsapp_api_url'] ?? 'https://graph.facebook.com/v22.0/') . '" ';
|
||||
echo 'placeholder="URL base de la API">';
|
||||
echo '</div>';
|
||||
|
||||
echo '<input type="hidden" name="update_config" value="1">';
|
||||
echo '<button type="submit" class="btn">💾 Guardar Configuración</button>';
|
||||
echo '</form>';
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo '<div class="error-box">';
|
||||
echo '<h4>❌ Error de Sistema</h4>';
|
||||
echo '<p>Error: ' . htmlspecialchars($e->getMessage()) . '</p>';
|
||||
echo '</div>';
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="info-box" style="margin-top: 30px;">
|
||||
<h4>🌐 Enlaces del Sistema:</h4>
|
||||
<p><strong>Panel principal:</strong> <a href="index.php" target="_blank">index.php</a></p>
|
||||
<p><strong>Login:</strong> <a href="login.php" target="_blank">login.php</a></p>
|
||||
<p><strong>Webhook endpoint:</strong> <a href="api/webhook.php" target="_blank">api/webhook.php</a></p>
|
||||
</div>
|
||||
|
||||
<div class="warning-box" style="margin-top: 30px;">
|
||||
<h4>⚠️ Instrucciones de Uso:</h4>
|
||||
<ul>
|
||||
<li><strong>Webhook Verify Token:</strong> Úsalo en la configuración del webhook de Facebook/Meta</li>
|
||||
<li><strong>WhatsApp Token:</strong> Obténlo desde Meta for Developers</li>
|
||||
<li><strong>Phone Number ID:</strong> Disponible en la consola de WhatsApp Business</li>
|
||||
<li>Los cambios se guardan en la base de datos y tienen prioridad sobre los valores por defecto</li>
|
||||
<li>Elimina esta herramienta en producción por seguridad</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function copyToField(sourceId, targetId) {
|
||||
const sourceText = document.getElementById(sourceId).textContent;
|
||||
document.getElementById(targetId).value = sourceText;
|
||||
|
||||
// Feedback visual
|
||||
const btn = event.target;
|
||||
const originalText = btn.textContent;
|
||||
btn.textContent = '✅ Copiado';
|
||||
btn.style.background = '#4caf50';
|
||||
|
||||
setTimeout(() => {
|
||||
btn.textContent = originalText;
|
||||
btn.style.background = '#2196f3';
|
||||
}, 1500);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
/**
|
||||
* 🔧 CONFIGURADOR TOKEN WHATSAPP
|
||||
* Herramienta para configurar 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>🔧 Configurar 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 "input[type=text]{width:100%;padding:10px;background:#222;color:#fff;border:1px solid #555;margin:5px 0}";
|
||||
echo "button{padding:10px 20px;background:#333;color:#fff;border:1px solid #555;cursor:pointer}";
|
||||
echo "button:hover{background:#555}";
|
||||
echo ".form-group{margin:15px 0}";
|
||||
echo "</style></head><body>";
|
||||
|
||||
echo "<h1>🔧 CONFIGURADOR TOKEN WHATSAPP</h1>";
|
||||
|
||||
// Manejar formulario
|
||||
if ($_POST) {
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>💾 GUARDANDO CONFIGURACIÓN...</h2>";
|
||||
|
||||
$new_token = trim($_POST['whatsapp_token'] ?? '');
|
||||
$new_phone_id = trim($_POST['phone_id'] ?? '');
|
||||
$new_webhook_token = trim($_POST['webhook_token'] ?? '');
|
||||
|
||||
if (empty($new_token)) {
|
||||
echo "<div class='error'>❌ El token de WhatsApp es obligatorio</div>";
|
||||
} elseif (empty($new_phone_id)) {
|
||||
echo "<div class='error'>❌ El Phone Number ID es obligatorio</div>";
|
||||
} else {
|
||||
// Crear archivo .env
|
||||
$env_content = "# WhatsApp Business API Configuration\n";
|
||||
$env_content .= "WHATSAPP_TOKEN=$new_token\n";
|
||||
$env_content .= "WHATSAPP_PHONE_NUMBER_ID=$new_phone_id\n";
|
||||
|
||||
if (!empty($new_webhook_token)) {
|
||||
$env_content .= "WEBHOOK_VERIFY_TOKEN=$new_webhook_token\n";
|
||||
}
|
||||
|
||||
$env_content .= "\n# Generated on: " . date('Y-m-d H:i:s') . "\n";
|
||||
|
||||
if (file_put_contents('.env', $env_content) !== false) {
|
||||
echo "<div class='ok'>✅ Archivo .env creado exitosamente</div>";
|
||||
echo "<div class='ok'>✅ Configuración guardada</div>";
|
||||
|
||||
// Test inmediato
|
||||
echo "<div class='warning'>🔄 Probando configuración...</div>";
|
||||
|
||||
// Simular carga de .env
|
||||
$_ENV['WHATSAPP_TOKEN'] = $new_token;
|
||||
$_ENV['WHATSAPP_PHONE_NUMBER_ID'] = $new_phone_id;
|
||||
if (!empty($new_webhook_token)) {
|
||||
$_ENV['WEBHOOK_VERIFY_TOKEN'] = $new_webhook_token;
|
||||
}
|
||||
|
||||
echo "<div class='warning'>📋 <a href='diagnostico_whatsapp.php' style='color:#ffaa00'>Verificar configuración aquí</a></div>";
|
||||
|
||||
} else {
|
||||
echo "<div class='error'>❌ Error al crear archivo .env</div>";
|
||||
echo "<div class='warning'>Verifica permisos de escritura</div>";
|
||||
}
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
}
|
||||
|
||||
// Cargar configuración actual
|
||||
$current_token = '';
|
||||
$current_phone_id = '';
|
||||
$current_webhook_token = '';
|
||||
|
||||
try {
|
||||
require_once 'config/config.php';
|
||||
$current_token = defined('WHATSAPP_TOKEN') ? WHATSAPP_TOKEN : '';
|
||||
$current_phone_id = defined('WHATSAPP_PHONE_NUMBER_ID') ? WHATSAPP_PHONE_NUMBER_ID : '';
|
||||
$current_webhook_token = defined('WEBHOOK_VERIFY_TOKEN') ? WEBHOOK_VERIFY_TOKEN : '';
|
||||
} catch (Exception $e) {
|
||||
echo "<div class='error'>❌ Error cargando configuración: " . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
}
|
||||
|
||||
// Mostrar configuración actual
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>⚙️ CONFIGURACIÓN ACTUAL</h2>";
|
||||
|
||||
if ($current_token === 'TU_TOKEN_DE_WHATSAPP_AQUI' || empty($current_token)) {
|
||||
echo "<div class='error'>❌ Token no configurado</div>";
|
||||
} else {
|
||||
$token_preview = substr($current_token, 0, 10) . '...' . substr($current_token, -10);
|
||||
echo "<div class='warning'>🔑 Token actual: $token_preview</div>";
|
||||
}
|
||||
|
||||
if ($current_phone_id === 'TU_PHONE_ID_AQUI' || empty($current_phone_id)) {
|
||||
echo "<div class='error'>❌ Phone ID no configurado</div>";
|
||||
} else {
|
||||
echo "<div class='ok'>✅ Phone ID: $current_phone_id</div>";
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
// Formulario de configuración
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>📝 NUEVA CONFIGURACIÓN</h2>";
|
||||
|
||||
echo "<form method='POST'>";
|
||||
|
||||
echo "<div class='form-group'>";
|
||||
echo "<label style='color:#ffaa00'><strong>🔑 Token de WhatsApp Business API:</strong></label>";
|
||||
echo "<input type='text' name='whatsapp_token' placeholder='EAAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' value=''>";
|
||||
echo "<div class='warning'>• Obtenlo de: Facebook Developers > Tu App > WhatsApp > API Setup</div>";
|
||||
echo "<div class='warning'>• Debe comenzar con 'EAA'</div>";
|
||||
echo "<div class='warning'>• Longitud típica: 100-300 caracteres</div>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='form-group'>";
|
||||
echo "<label style='color:#ffaa00'><strong>📱 Phone Number ID:</strong></label>";
|
||||
echo "<input type='text' name='phone_id' placeholder='1234567890123456' value=''>";
|
||||
echo "<div class='warning'>• Obtenlo de: Facebook Developers > Tu App > WhatsApp > API Setup</div>";
|
||||
echo "<div class='warning'>• Solo números, típicamente 15-16 dígitos</div>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='form-group'>";
|
||||
echo "<label style='color:#ffaa00'><strong>🔐 Webhook Verify Token (opcional):</strong></label>";
|
||||
echo "<input type='text' name='webhook_token' placeholder='mi_token_secreto_123' value='$current_webhook_token'>";
|
||||
echo "<div class='warning'>• Token que configuraste en Facebook para verificar webhook</div>";
|
||||
echo "<div class='warning'>• Puede ser cualquier string secreto</div>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='form-group'>";
|
||||
echo "<button type='submit'>💾 Guardar Configuración</button>";
|
||||
echo "</div>";
|
||||
|
||||
echo "</form>";
|
||||
echo "</div>";
|
||||
|
||||
// Instrucciones detalladas
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>📖 INSTRUCCIONES PASO A PASO</h2>";
|
||||
|
||||
echo "<div class='warning'><strong>1. CREAR APLICACIÓN DE WHATSAPP:</strong></div>";
|
||||
echo "<div class='ok'>• Ve a <a href='https://developers.facebook.com/' style='color:#ffaa00'>https://developers.facebook.com/</a></div>";
|
||||
echo "<div class='ok'>• Haz clic en 'Create App'</div>";
|
||||
echo "<div class='ok'>• Selecciona 'Business' como tipo de app</div>";
|
||||
echo "<div class='ok'>• Dale un nombre a tu aplicación</div>";
|
||||
|
||||
echo "<br><div class='warning'><strong>2. CONFIGURAR WHATSAPP BUSINESS API:</strong></div>";
|
||||
echo "<div class='ok'>• En tu app, busca 'WhatsApp' en productos</div>";
|
||||
echo "<div class='ok'>• Haz clic en 'Set up' o 'Configurar'</div>";
|
||||
echo "<div class='ok'>• Ve a la sección 'API Setup'</div>";
|
||||
|
||||
echo "<br><div class='warning'><strong>3. OBTENER CREDENCIALES:</strong></div>";
|
||||
echo "<div class='ok'>• <strong>Temporary access token:</strong> Copia este token completo</div>";
|
||||
echo "<div class='ok'>• <strong>Phone number ID:</strong> Copia el ID del número de prueba</div>";
|
||||
echo "<div class='ok'>• <strong>From phone number:</strong> Anota este número</div>";
|
||||
|
||||
echo "<br><div class='warning'><strong>4. CONFIGURAR WEBHOOK:</strong></div>";
|
||||
echo "<div class='ok'>• URL del webhook: https://tudominio.com/api/webhook.php</div>";
|
||||
echo "<div class='ok'>• Verify token: El mismo que ingreses arriba</div>";
|
||||
echo "<div class='ok'>• Suscríbete a 'messages'</div>";
|
||||
|
||||
echo "<br><div class='error'><strong>⚠️ IMPORTANTE:</strong></div>";
|
||||
echo "<div class='error'>• El token temporal expira en 24 horas</div>";
|
||||
echo "<div class='error'>• Para producción necesitas generar un token permanente</div>";
|
||||
echo "<div class='error'>• Verifica que tu dominio tenga HTTPS</div>";
|
||||
|
||||
echo "</div>";
|
||||
|
||||
// Estado del archivo .env
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>📄 ESTADO ARCHIVO .ENV</h2>";
|
||||
|
||||
if (file_exists('.env')) {
|
||||
echo "<div class='ok'>✅ Archivo .env existe</div>";
|
||||
$env_content = file_get_contents('.env');
|
||||
echo "<div class='warning'>📋 Contenido actual:</div>";
|
||||
echo "<pre style='background:#222;padding:10px;color:#ccc'>" . htmlspecialchars($env_content) . "</pre>";
|
||||
} else {
|
||||
echo "<div class='warning'>⚠️ Archivo .env no existe</div>";
|
||||
echo "<div class='warning'>Se creará automáticamente al guardar</div>";
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
echo "</body></html>";
|
||||
?>
|
||||
@@ -0,0 +1,717 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>💬 Conversaciones - WhatsApp Bot</title>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/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>
|
||||
:root {
|
||||
--whatsapp-green: #25d366;
|
||||
--whatsapp-green-dark: #128c7e;
|
||||
--whatsapp-light: #dcf8c6;
|
||||
--whatsapp-dark: #075e54;
|
||||
--message-bg: #f0f0f0;
|
||||
--sidebar-bg: #f8f9fa;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
|
||||
background: #e5ddd5;
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.chat-sidebar {
|
||||
width: 350px;
|
||||
background: var(--sidebar-bg);
|
||||
border-right: 1px solid #e1e5e9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 20px;
|
||||
background: var(--whatsapp-dark);
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.sidebar-search {
|
||||
padding: 10px 20px;
|
||||
background: white;
|
||||
border-bottom: 1px solid #e1e5e9;
|
||||
}
|
||||
|
||||
.conversation-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.conversation-item {
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.conversation-item:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.conversation-item.active {
|
||||
background: #e3f2fd;
|
||||
border-right: 3px solid var(--whatsapp-green);
|
||||
}
|
||||
|
||||
.conversation-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background: var(--whatsapp-green);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
margin-right: 15px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.conversation-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.conversation-name {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.conversation-preview {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.conversation-time {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.chat-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #e5ddd5;
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
padding: 15px 20px;
|
||||
background: var(--whatsapp-dark);
|
||||
color: white;
|
||||
border-bottom: 1px solid #ddd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chat-header-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: var(--whatsapp-green);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.chat-header-info h6 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.chat-header-info small {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGRlZnM+CjxwYXR0ZXJuIGlkPSJ3aGF0c2FwcC1iZyIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiPgo8cG9seWdvbiBmaWxsPSJyZ2JhKDIyOSwyMjEsMjEzLDAuMSkiIHBvaW50cz0iMCwwIDEwMCwwIDEwMCwxMDAgMCwxMDAiLz4KPGV+';
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 70%;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.message.outgoing {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.message.incoming {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
padding: 8px 12px;
|
||||
border-radius: 18px;
|
||||
position: relative;
|
||||
word-wrap: break-word;
|
||||
box-shadow: 0 1px 1px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.message.outgoing .message-bubble {
|
||||
background: var(--whatsapp-light);
|
||||
color: #333;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
.message.incoming .message-bubble {
|
||||
background: white;
|
||||
color: #333;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
.message-time {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
text-align: right;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.message-status {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.status-sent { color: #999; }
|
||||
.status-delivered { color: #999; }
|
||||
.status-read { color: var(--whatsapp-green); }
|
||||
|
||||
.chat-input {
|
||||
padding: 15px 20px;
|
||||
background: #f0f0f0;
|
||||
border-top: 1px solid #ddd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.chat-input input {
|
||||
flex: 1;
|
||||
padding: 10px 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 25px;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.chat-input input:focus {
|
||||
border-color: var(--whatsapp-green);
|
||||
}
|
||||
|
||||
.chat-input .btn {
|
||||
border-radius: 50%;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--whatsapp-green);
|
||||
border: none;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.chat-input .btn:hover {
|
||||
background: var(--whatsapp-green-dark);
|
||||
}
|
||||
|
||||
.no-conversation {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-conversation i {
|
||||
font-size: 64px;
|
||||
margin-bottom: 20px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.message-template {
|
||||
background: #fffacd;
|
||||
border: 1px solid #f0e68c;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.unread-count {
|
||||
background: var(--whatsapp-green);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.chat-sidebar {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.chat-main {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chat-sidebar.show-chat {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="chat-container">
|
||||
<!-- Sidebar con lista de conversaciones -->
|
||||
<div class="chat-sidebar">
|
||||
<div class="sidebar-header">
|
||||
<div>
|
||||
<h5 class="mb-0">💬 Conversaciones</h5>
|
||||
</div>
|
||||
<div>
|
||||
<a href="index.php" class="text-white text-decoration-none">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-search">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-transparent border-0">
|
||||
<i class="fas fa-search text-muted"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control border-0" placeholder="Buscar conversaciones..." id="search-input">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="conversation-list" id="conversation-list">
|
||||
<div class="loading">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
<div>Cargando conversaciones...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Área principal del chat -->
|
||||
<div class="chat-main">
|
||||
<div id="no-conversation" class="no-conversation">
|
||||
<i class="fab fa-whatsapp"></i>
|
||||
<h4>WhatsApp Bot Manager</h4>
|
||||
<p>Selecciona una conversación para comenzar a chatear</p>
|
||||
</div>
|
||||
|
||||
<div id="chat-area" style="display: none; height: 100%; flex-direction: column;">
|
||||
<div class="chat-header">
|
||||
<div class="chat-header-avatar" id="chat-avatar">
|
||||
<i class="fas fa-user"></i>
|
||||
</div>
|
||||
<div class="chat-header-info">
|
||||
<h6 id="chat-name">Usuario</h6>
|
||||
<small id="chat-phone">+1234567890</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-messages" id="chat-messages">
|
||||
<!-- Los mensajes se cargan aquí -->
|
||||
</div>
|
||||
|
||||
<div class="chat-input">
|
||||
<input type="text" placeholder="Escribe un mensaje..." id="message-input" maxlength="4096">
|
||||
<button class="btn" id="send-btn">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
class WhatsAppChat {
|
||||
constructor() {
|
||||
this.currentConversationId = null;
|
||||
this.currentUserId = null;
|
||||
this.conversations = [];
|
||||
this.messages = [];
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.loadConversations();
|
||||
this.setupEventListeners();
|
||||
this.setupAutoRefresh();
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Búsqueda de conversaciones
|
||||
document.getElementById('search-input').addEventListener('input', (e) => {
|
||||
this.searchConversations(e.target.value);
|
||||
});
|
||||
|
||||
// Envío de mensajes
|
||||
document.getElementById('send-btn').addEventListener('click', () => {
|
||||
this.sendMessage();
|
||||
});
|
||||
|
||||
document.getElementById('message-input').addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.sendMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupAutoRefresh() {
|
||||
// Actualizar conversaciones cada 30 segundos
|
||||
setInterval(() => {
|
||||
this.loadConversations();
|
||||
}, 30000);
|
||||
|
||||
// Actualizar mensajes del chat activo cada 10 segundos
|
||||
setInterval(() => {
|
||||
if (this.currentUserId) {
|
||||
this.loadMessages(this.currentUserId, false);
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
async loadConversations() {
|
||||
try {
|
||||
const response = await fetch('api/get_conversations.php');
|
||||
const data = await response.json();
|
||||
console.log('Respuesta get_conversations:', data); // Debug
|
||||
|
||||
// Manejar nuevo formato de respuesta de API
|
||||
if (data && data.success && Array.isArray(data.data)) {
|
||||
this.conversations = data.data;
|
||||
this.renderConversations();
|
||||
} else if (Array.isArray(data)) {
|
||||
// Retrocompatibilidad con formato anterior
|
||||
this.conversations = data;
|
||||
this.renderConversations();
|
||||
} else {
|
||||
console.error('Error cargando conversaciones: formato de datos inválido', data);
|
||||
alert('Error cargando conversaciones: ' + (data.error || 'formato de datos inválido'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading conversations:', error);
|
||||
alert('Error cargando conversaciones: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
renderConversations() {
|
||||
const container = document.getElementById('conversation-list');
|
||||
|
||||
if (this.conversations.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center p-4">
|
||||
<i class="fas fa-comments fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">No hay conversaciones aún</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Agrupar por usuario
|
||||
const userConversations = {};
|
||||
this.conversations.forEach(conv => {
|
||||
const key = conv.user_id;
|
||||
if (!userConversations[key]) {
|
||||
userConversations[key] = {
|
||||
user_id: conv.user_id,
|
||||
name: conv.name || conv.phone_number,
|
||||
phone_number: conv.phone_number,
|
||||
last_message: conv.content,
|
||||
last_time: conv.created_at,
|
||||
direction: conv.direction,
|
||||
unread: 0
|
||||
};
|
||||
}
|
||||
|
||||
// Mantener el mensaje más reciente
|
||||
if (new Date(conv.created_at) > new Date(userConversations[key].last_time)) {
|
||||
userConversations[key].last_message = conv.content;
|
||||
userConversations[key].last_time = conv.created_at;
|
||||
userConversations[key].direction = conv.direction;
|
||||
}
|
||||
});
|
||||
|
||||
const html = Object.values(userConversations).map(conv => {
|
||||
const initials = this.getInitials(conv.name);
|
||||
const time = this.formatTime(conv.last_time);
|
||||
const preview = this.truncateText(conv.last_message || 'Sin mensajes', 50);
|
||||
const isActive = conv.user_id == this.currentUserId ? 'active' : '';
|
||||
|
||||
return `
|
||||
<div class="conversation-item ${isActive}" data-user-id="${conv.user_id}" onclick="chat.openConversation(${conv.user_id}, '${conv.name}', '${conv.phone_number}')">
|
||||
<div class="conversation-avatar">
|
||||
${initials}
|
||||
</div>
|
||||
<div class="conversation-info">
|
||||
<div class="conversation-name">${conv.name}</div>
|
||||
<div class="conversation-preview">
|
||||
${conv.direction === 'outgoing' ? '✓ ' : ''}${preview}
|
||||
</div>
|
||||
</div>
|
||||
<div class="conversation-time">
|
||||
${time}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
getInitials(name) {
|
||||
if (!name) return '?';
|
||||
const parts = name.split(' ');
|
||||
if (parts.length >= 2) {
|
||||
return (parts[0][0] + parts[1][0]).toUpperCase();
|
||||
}
|
||||
return name.substring(0, 2).toUpperCase();
|
||||
}
|
||||
|
||||
formatTime(dateString) {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffInHours = (now - date) / (1000 * 60 * 60);
|
||||
|
||||
if (diffInHours < 24) {
|
||||
return date.toLocaleTimeString('es-ES', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
} else if (diffInHours < 48) {
|
||||
return 'Ayer';
|
||||
} else {
|
||||
return date.toLocaleDateString('es-ES', {
|
||||
day: '2-digit',
|
||||
month: '2-digit'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
truncateText(text, maxLength) {
|
||||
if (text.length <= maxLength) return text;
|
||||
return text.substring(0, maxLength) + '...';
|
||||
}
|
||||
|
||||
async openConversation(userId, userName, phoneNumber) {
|
||||
this.currentUserId = userId;
|
||||
|
||||
// Actualizar UI
|
||||
document.getElementById('no-conversation').style.display = 'none';
|
||||
document.getElementById('chat-area').style.display = 'flex';
|
||||
|
||||
// Actualizar header
|
||||
document.getElementById('chat-name').textContent = userName;
|
||||
document.getElementById('chat-phone').textContent = phoneNumber;
|
||||
document.getElementById('chat-avatar').textContent = this.getInitials(userName);
|
||||
|
||||
// Marcar conversación como activa
|
||||
document.querySelectorAll('.conversation-item').forEach(item => {
|
||||
item.classList.remove('active');
|
||||
});
|
||||
document.querySelector(`[data-user-id="${userId}"]`)?.classList.add('active');
|
||||
|
||||
// Cargar mensajes
|
||||
await this.loadMessages(userId);
|
||||
}
|
||||
|
||||
async loadMessages(userId, showLoading = true) {
|
||||
if (showLoading) {
|
||||
document.getElementById('chat-messages').innerHTML = `
|
||||
<div class="loading">
|
||||
<i class="fas fa-spinner fa-spin"></i> Cargando mensajes...
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`api/get_user_messages.php?user_id=${userId}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
this.messages = data;
|
||||
this.renderMessages();
|
||||
this.scrollToBottom();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading messages:', error);
|
||||
document.getElementById('chat-messages').innerHTML = `
|
||||
<div class="text-center p-4">
|
||||
<i class="fas fa-exclamation-triangle text-warning"></i>
|
||||
<p>Error al cargar los mensajes</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
renderMessages() {
|
||||
const container = document.getElementById('chat-messages');
|
||||
|
||||
if (this.messages.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="text-center p-4">
|
||||
<i class="far fa-comment fa-3x text-muted mb-3"></i>
|
||||
<p class="text-muted">No hay mensajes en esta conversación</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const html = this.messages.map(msg => {
|
||||
const time = new Date(msg.created_at).toLocaleTimeString('es-ES', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
|
||||
const statusIcon = this.getStatusIcon(msg.status);
|
||||
|
||||
return `
|
||||
<div class="message ${msg.direction}">
|
||||
<div class="message-bubble">
|
||||
${msg.content || msg.message_text || '[Mensaje vacío]'}
|
||||
<div class="message-time">
|
||||
${time}
|
||||
${msg.direction === 'outgoing' ? `<span class="message-status">${statusIcon}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
getStatusIcon(status) {
|
||||
switch (status) {
|
||||
case 'sent': return '<i class="fas fa-check status-sent"></i>';
|
||||
case 'delivered': return '<i class="fas fa-check-double status-delivered"></i>';
|
||||
case 'read': return '<i class="fas fa-check-double status-read"></i>';
|
||||
default: return '<i class="fas fa-clock status-sent"></i>';
|
||||
}
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
const container = document.getElementById('chat-messages');
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
async sendMessage() {
|
||||
const input = document.getElementById('message-input');
|
||||
const message = input.value.trim();
|
||||
|
||||
if (!message || !this.currentUserId) return;
|
||||
|
||||
// Deshabilitar input
|
||||
input.disabled = true;
|
||||
document.getElementById('send-btn').disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch('api/send_message.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: this.currentUserId,
|
||||
message: message
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
input.value = '';
|
||||
// Recargar mensajes
|
||||
await this.loadMessages(this.currentUserId, false);
|
||||
// Actualizar lista de conversaciones
|
||||
this.loadConversations();
|
||||
} else {
|
||||
alert('Error al enviar mensaje: ' + (result.error || 'Error desconocido'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error sending message:', error);
|
||||
alert('Error al enviar mensaje');
|
||||
} finally {
|
||||
input.disabled = false;
|
||||
document.getElementById('send-btn').disabled = false;
|
||||
input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
searchConversations(query) {
|
||||
const items = document.querySelectorAll('.conversation-item');
|
||||
items.forEach(item => {
|
||||
const name = item.querySelector('.conversation-name').textContent.toLowerCase();
|
||||
const preview = item.querySelector('.conversation-preview').textContent.toLowerCase();
|
||||
const matches = name.includes(query.toLowerCase()) || preview.includes(query.toLowerCase());
|
||||
item.style.display = matches ? 'flex' : 'none';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializar cuando la página cargue
|
||||
let chat;
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
chat = new WhatsAppChat();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
/**
|
||||
* 🔧 CORRECTOR AUTOMÁTICO DE RUTAS API
|
||||
* Corrige todas las rutas incorrectas en 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>🔧 Corrector 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>🔧 CORRECTOR AUTOMÁTICO DE RUTAS API</h1>";
|
||||
|
||||
// Verificar que existe la carpeta api
|
||||
if (!is_dir('api')) {
|
||||
echo "<div class='error'>❌ CRÍTICO: Carpeta 'api' no encontrada</div>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Obtener todos los archivos PHP de la API
|
||||
$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 para corregir</div>";
|
||||
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>🔄 CORRIGIENDO ARCHIVOS...</h2>";
|
||||
|
||||
$corrections = [
|
||||
// Ruta incorrecta -> Ruta correcta
|
||||
"require_once 'config/config.php';" => "require_once '../config/config.php';",
|
||||
'require_once "config/config.php";' => 'require_once "../config/config.php";',
|
||||
"include_once 'config/config.php';" => "include_once '../config/config.php';",
|
||||
'include_once "config/config.php";' => 'include_once "../config/config.php";',
|
||||
|
||||
// También corregir rutas a classes si las hay
|
||||
"require_once 'classes/" => "require_once '../classes/",
|
||||
'require_once "classes/' => 'require_once "../classes/',
|
||||
"include_once 'classes/" => "include_once '../classes/",
|
||||
'include_once "classes/' => 'include_once "../classes/',
|
||||
|
||||
// Servicios
|
||||
"require_once 'services/" => "require_once '../services/",
|
||||
'require_once "services/' => 'require_once "../services/',
|
||||
];
|
||||
|
||||
$total_files = 0;
|
||||
$total_corrections = 0;
|
||||
$errors = [];
|
||||
|
||||
foreach ($api_files as $file) {
|
||||
$filename = basename($file);
|
||||
$total_files++;
|
||||
|
||||
echo "<div class='warning'>🔄 Procesando: $filename</div>";
|
||||
|
||||
try {
|
||||
// Leer contenido del archivo
|
||||
$content = file_get_contents($file);
|
||||
if ($content === false) {
|
||||
$errors[] = "No se pudo leer: $filename";
|
||||
continue;
|
||||
}
|
||||
|
||||
$original_content = $content;
|
||||
$file_corrections = 0;
|
||||
|
||||
// Aplicar todas las correcciones
|
||||
foreach ($corrections as $wrong => $correct) {
|
||||
$old_content = $content;
|
||||
$content = str_replace($wrong, $correct, $content);
|
||||
|
||||
if ($content !== $old_content) {
|
||||
$matches = substr_count($old_content, $wrong);
|
||||
$file_corrections += $matches;
|
||||
echo "<div class='ok'>✅ $matches corrección(es): '$wrong' → '$correct'</div>";
|
||||
}
|
||||
}
|
||||
|
||||
// Si hubo cambios, guardar el archivo
|
||||
if ($content !== $original_content) {
|
||||
if (file_put_contents($file, $content) === false) {
|
||||
$errors[] = "No se pudo escribir: $filename";
|
||||
continue;
|
||||
}
|
||||
|
||||
$total_corrections += $file_corrections;
|
||||
echo "<div class='ok'>✅ Archivo guardado: $filename ($file_corrections correcciones)</div>";
|
||||
} else {
|
||||
echo "<div class='warning'>⚠️ Sin cambios necesarios: $filename</div>";
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$errors[] = "Error procesando $filename: " . $e->getMessage();
|
||||
}
|
||||
|
||||
echo "<br>";
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
// Verificación post-corrección
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>✅ VERIFICACIÓN POST-CORRECCIÓN</h2>";
|
||||
|
||||
echo "<div class='ok'>📊 Archivos procesados: $total_files</div>";
|
||||
echo "<div class='ok'>📊 Total correcciones: $total_corrections</div>";
|
||||
|
||||
if (!empty($errors)) {
|
||||
echo "<div class='error'>❌ Errores encontrados:</div>";
|
||||
foreach ($errors as $error) {
|
||||
echo "<div class='error'>• $error</div>";
|
||||
}
|
||||
} else {
|
||||
echo "<div class='ok'>✅ Sin errores durante la corrección</div>";
|
||||
}
|
||||
|
||||
// Verificar que las rutas sean correctas ahora
|
||||
echo "<br><div class='warning'><strong>VERIFICANDO RUTAS CORREGIDAS...</strong></div>";
|
||||
|
||||
$verification_success = true;
|
||||
|
||||
foreach ($api_files as $file) {
|
||||
$filename = basename($file);
|
||||
$content = file_get_contents($file);
|
||||
|
||||
// Verificar que no quedan rutas incorrectas
|
||||
if (preg_match('/require_once\s+[\'"]config\/config\.php[\'"]/', $content) ||
|
||||
preg_match('/include_once\s+[\'"]config\/config\.php[\'"]/', $content)) {
|
||||
echo "<div class='error'>❌ $filename: Aún tiene rutas incorrectas</div>";
|
||||
$verification_success = false;
|
||||
} else {
|
||||
// Verificar que tiene la ruta correcta
|
||||
if (preg_match('/require_once\s+[\'"]\.\.\/config\/config\.php[\'"]/', $content) ||
|
||||
preg_match('/include_once\s+[\'"]\.\.\/config\/config\.php[\'"]/', $content)) {
|
||||
echo "<div class='ok'>✅ $filename: Ruta corregida correctamente</div>";
|
||||
} else {
|
||||
echo "<div class='warning'>⚠️ $filename: No se encontró require de config</div>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
// Test final
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>🔬 TEST FINAL</h2>";
|
||||
|
||||
if ($verification_success && $total_corrections > 0) {
|
||||
echo "<div class='ok'>🎉 ¡CORRECCIÓN COMPLETADA CON ÉXITO!</div>";
|
||||
echo "<div class='ok'>✅ Todas las rutas de la API han sido corregidas</div>";
|
||||
echo "<div class='ok'>✅ Los archivos ahora buscan '../config/config.php'</div>";
|
||||
|
||||
echo "<br><div class='warning'><strong>SIGUIENTE PASO:</strong></div>";
|
||||
echo "<div class='warning'>• Prueba cualquier endpoint de la API</div>";
|
||||
echo "<div class='warning'>• Ejemplo: <a href='api/get_settings.php' style='color:#ffaa00'>api/get_settings.php</a></div>";
|
||||
echo "<div class='warning'>• O prueba el login: <a href='login.php' style='color:#ffaa00'>login.php</a></div>";
|
||||
|
||||
} elseif ($total_corrections === 0) {
|
||||
echo "<div class='warning'>⚠️ No se encontraron correcciones necesarias</div>";
|
||||
echo "<div class='warning'>Puede que las rutas ya estén correctas o hay otro problema</div>";
|
||||
|
||||
echo "<br><div class='warning'><strong>VERIFICAR:</strong></div>";
|
||||
echo "<div class='warning'>• Ejecuta: <a href='diagnostico_api.php' style='color:#ffaa00'>diagnostico_api.php</a></div>";
|
||||
|
||||
} else {
|
||||
echo "<div class='error'>❌ Corrección incompleta</div>";
|
||||
echo "<div class='error'>Hay problemas que requieren revisión manual</div>";
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
echo "</body></html>";
|
||||
?>
|
||||
@@ -0,0 +1,175 @@
|
||||
<?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
|
||||
$sampleMessages = [
|
||||
['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']
|
||||
];
|
||||
|
||||
$messagesCreated = 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($sampleMessages);
|
||||
$message = $sampleMessages[$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']
|
||||
]);
|
||||
$messagesCreated++;
|
||||
} catch (Exception $e) {
|
||||
// Si falla, intentar en messages
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO messages (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']
|
||||
]);
|
||||
$messagesCreated++;
|
||||
} 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: $messagesCreated</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>";
|
||||
?>
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
<?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";
|
||||
?>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?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";
|
||||
?>
|
||||
@@ -0,0 +1,313 @@
|
||||
<!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>
|
||||
@@ -0,0 +1,272 @@
|
||||
<?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', 'messages', 'settings'];
|
||||
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>";
|
||||
?>
|
||||
@@ -0,0 +1,237 @@
|
||||
<?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>";
|
||||
?>
|
||||
@@ -0,0 +1,105 @@
|
||||
<?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', 'messages'];
|
||||
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>";
|
||||
?>
|
||||
@@ -0,0 +1,333 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,301 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,264 @@
|
||||
<?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>";
|
||||
?>
|
||||
@@ -0,0 +1,258 @@
|
||||
<?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#settings' 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>";
|
||||
?>
|
||||
@@ -0,0 +1,432 @@
|
||||
<?php
|
||||
/**
|
||||
* Herramienta para encontrar el Phone Number ID correcto
|
||||
* Fecha: 5 de enero de 2026
|
||||
*/
|
||||
|
||||
require_once 'config/config.php';
|
||||
|
||||
if (!isUserLoggedIn()) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$result = null;
|
||||
$error = null;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
if ($action === 'find_numbers') {
|
||||
$token = $_POST['token'] ?? getConfigFromDB('whatsapp_token', WHATSAPP_TOKEN);
|
||||
|
||||
if (empty($token) || $token === 'TU_TOKEN_DE_WHATSAPP_AQUI') {
|
||||
$error = 'Token de WhatsApp requerido';
|
||||
} else {
|
||||
$result = findAvailablePhoneNumbers($token);
|
||||
}
|
||||
} elseif ($action === 'update_config') {
|
||||
$newPhoneId = $_POST['phone_id'] ?? '';
|
||||
|
||||
if (empty($newPhoneId)) {
|
||||
$error = 'Phone Number ID requerido';
|
||||
} else {
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Actualizar en base de datos
|
||||
$existing = $db->fetch("SELECT id FROM system_config WHERE config_key = 'whatsapp_phone_number_id'");
|
||||
|
||||
if ($existing) {
|
||||
$db->query(
|
||||
"UPDATE system_config SET config_value = ?, updated_at = NOW() WHERE config_key = 'whatsapp_phone_number_id'",
|
||||
[$newPhoneId]
|
||||
);
|
||||
} else {
|
||||
$db->query(
|
||||
"INSERT INTO system_config (config_key, config_value, created_at, updated_at) VALUES ('whatsapp_phone_number_id', ?, NOW(), NOW())",
|
||||
[$newPhoneId]
|
||||
);
|
||||
}
|
||||
|
||||
$result = ['success' => true, 'message' => 'Phone Number ID actualizado correctamente'];
|
||||
|
||||
// Probar nueva configuración
|
||||
$testResult = testNewConfig($newPhoneId);
|
||||
$result['test'] = $testResult;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$error = 'Error actualizando configuración: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findAvailablePhoneNumbers($token) {
|
||||
try {
|
||||
// Método 1: Buscar a través de Business Account
|
||||
$ch = curl_init();
|
||||
$url = 'https://graph.facebook.com/v18.0/me/businesses';
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Content-Type: application/json'
|
||||
],
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_SSL_VERIFYPEER => true
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
return ['success' => false, 'error' => 'Error cURL: ' . $curlError];
|
||||
}
|
||||
|
||||
$decoded = json_decode($response, true);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
// Método 2: Intentar obtener info de la app directamente
|
||||
return findPhoneNumbersFromApp($token);
|
||||
}
|
||||
|
||||
$phoneNumbers = [];
|
||||
|
||||
if (isset($decoded['data']) && is_array($decoded['data'])) {
|
||||
foreach ($decoded['data'] as $business) {
|
||||
$businessId = $business['id'];
|
||||
$phoneNumbersData = getPhoneNumbersFromBusiness($token, $businessId);
|
||||
|
||||
if ($phoneNumbersData['success']) {
|
||||
$phoneNumbers = array_merge($phoneNumbers, $phoneNumbersData['numbers']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'numbers' => $phoneNumbers,
|
||||
'method' => 'business_account'
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return ['success' => false, 'error' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
function findPhoneNumbersFromApp($token) {
|
||||
try {
|
||||
// Intentar con diferentes endpoints
|
||||
$endpoints = [
|
||||
'https://graph.facebook.com/v18.0/me',
|
||||
'https://graph.facebook.com/v18.0/me/accounts',
|
||||
'https://graph.facebook.com/v18.0/me/applications'
|
||||
];
|
||||
|
||||
foreach ($endpoints as $endpoint) {
|
||||
$ch = curl_init();
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $endpoint,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Content-Type: application/json'
|
||||
],
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_SSL_VERIFYPEER => true
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode === 200) {
|
||||
$decoded = json_decode($response, true);
|
||||
// Aquí podrías analizar la respuesta para encontrar números
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'No se pudieron encontrar números automáticamente. Debe obtenerlos manualmente de Facebook for Developers.',
|
||||
'manual_steps' => [
|
||||
'1. Ve a https://developers.facebook.com/apps',
|
||||
'2. Selecciona tu aplicación WhatsApp Business',
|
||||
'3. Ve a Productos > WhatsApp > Configuración',
|
||||
'4. En la sección "Números de teléfono", copia el Phone Number ID',
|
||||
'5. Pega el ID en el formulario de abajo'
|
||||
]
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return ['success' => false, 'error' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
function getPhoneNumbersFromBusiness($token, $businessId) {
|
||||
try {
|
||||
$ch = curl_init();
|
||||
$url = "https://graph.facebook.com/v18.0/{$businessId}/phone_numbers";
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Content-Type: application/json'
|
||||
],
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_SSL_VERIFYPEER => true
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode === 200) {
|
||||
$decoded = json_decode($response, true);
|
||||
|
||||
if (isset($decoded['data'])) {
|
||||
return [
|
||||
'success' => true,
|
||||
'numbers' => $decoded['data']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return ['success' => false, 'numbers' => []];
|
||||
|
||||
} catch (Exception $e) {
|
||||
return ['success' => false, 'numbers' => []];
|
||||
}
|
||||
}
|
||||
|
||||
function testNewConfig($phoneId) {
|
||||
try {
|
||||
$token = getConfigFromDB('whatsapp_token', WHATSAPP_TOKEN);
|
||||
|
||||
$ch = curl_init();
|
||||
$url = WHATSAPP_API_URL . $phoneId;
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Content-Type: application/json'
|
||||
],
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_SSL_VERIFYPEER => true
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($curlError) {
|
||||
return ['success' => false, 'message' => 'Error: ' . $curlError];
|
||||
}
|
||||
|
||||
$decoded = json_decode($response, true);
|
||||
|
||||
if ($httpCode === 200) {
|
||||
$displayName = $decoded['display_phone_number'] ?? 'N/A';
|
||||
return ['success' => true, 'message' => "✅ Configuración válida. Número: $displayName"];
|
||||
} else {
|
||||
$errorMsg = $decoded['error']['message'] ?? 'Error desconocido';
|
||||
return ['success' => false, 'message' => "❌ Error: $errorMsg"];
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
return ['success' => false, 'message' => 'Error: ' . $e->getMessage()];
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Buscar Phone Number ID Correcto</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>
|
||||
.whatsapp-green { background: linear-gradient(135deg, #25D366, #128C7E); }
|
||||
.phone-card { border: 2px solid #ddd; transition: all 0.3s; }
|
||||
.phone-card:hover { border-color: #25D366; box-shadow: 0 4px 15px rgba(37,211,102,0.2); }
|
||||
.phone-card.selected { border-color: #25D366; background: #f0fff4; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<div class="container my-4">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="card whatsapp-green text-white mb-4">
|
||||
<div class="card-body text-center">
|
||||
<h1><i class="fab fa-whatsapp me-2"></i>Buscar Phone Number ID Correcto</h1>
|
||||
<p class="mb-0">Encuentra y configura el Phone Number ID válido para tu cuenta</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Problema Detectado -->
|
||||
<div class="alert alert-danger">
|
||||
<h5><i class="fas fa-exclamation-triangle me-2"></i>Problema Detectado</h5>
|
||||
<p class="mb-1"><strong>Phone Number ID Actual:</strong> <code>858157464051987</code></p>
|
||||
<p class="mb-0"><strong>Error:</strong> Este ID no existe, no tienes permisos, o no soporta la operación solicitada.</p>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger">
|
||||
<i class="fas fa-times-circle me-2"></i><?= htmlspecialchars($error) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($result && isset($result['success']) && $result['success']): ?>
|
||||
<div class="alert alert-success">
|
||||
<i class="fas fa-check-circle me-2"></i><?= htmlspecialchars($result['message'] ?? 'Operación exitosa') ?>
|
||||
<?php if (isset($result['test'])): ?>
|
||||
<br><strong>Prueba:</strong> <?= htmlspecialchars($result['test']['message']) ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="row">
|
||||
<!-- Paso 1: Buscar números automáticamente -->
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5><i class="fas fa-search me-2"></i>Paso 1: Buscar Automáticamente</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Intentar encontrar números de WhatsApp asociados a tu token:</p>
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="find_numbers">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Token de WhatsApp</label>
|
||||
<input type="text" class="form-control" name="token"
|
||||
value="<?= substr(getConfigFromDB('whatsapp_token', WHATSAPP_TOKEN), 0, 30) ?>..." readonly>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-search me-2"></i>Buscar Números
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<?php if ($result && isset($result['numbers'])): ?>
|
||||
<hr>
|
||||
<h6>Números Encontrados:</h6>
|
||||
<?php if (empty($result['numbers'])): ?>
|
||||
<div class="alert alert-warning">
|
||||
<p>No se encontraron números automáticamente.</p>
|
||||
<?php if (isset($result['manual_steps'])): ?>
|
||||
<strong>Pasos manuales:</strong>
|
||||
<ol>
|
||||
<?php foreach ($result['manual_steps'] as $step): ?>
|
||||
<li><?= htmlspecialchars($step) ?></li>
|
||||
<?php endforeach; ?>
|
||||
</ol>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<?php foreach ($result['numbers'] as $number): ?>
|
||||
<div class="phone-card card mb-2 p-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong><?= htmlspecialchars($number['display_phone_number'] ?? $number['id']) ?></strong><br>
|
||||
<small class="text-muted">ID: <?= htmlspecialchars($number['id']) ?></small>
|
||||
</div>
|
||||
<button class="btn btn-success btn-sm"
|
||||
onclick="selectPhoneNumber('<?= htmlspecialchars($number['id']) ?>')">
|
||||
<i class="fas fa-check me-1"></i>Usar Este
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Paso 2: Configuración manual -->
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5><i class="fas fa-edit me-2"></i>Paso 2: Configuración Manual</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Si conoces el Phone Number ID correcto, ingrésalo aquí:</p>
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="update_config">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nuevo Phone Number ID</label>
|
||||
<input type="text" class="form-control" name="phone_id" id="phone_id"
|
||||
placeholder="123456789012345" required>
|
||||
<small class="text-muted">Debe ser numérico, 10-20 dígitos</small>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="fas fa-save me-2"></i>Actualizar y Probar
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<h6>¿Cómo obtener el Phone Number ID?</h6>
|
||||
<ol class="small">
|
||||
<li>Ve a <a href="https://developers.facebook.com/apps" target="_blank">Facebook for Developers</a></li>
|
||||
<li>Selecciona tu aplicación WhatsApp Business</li>
|
||||
<li>Ve a <strong>Productos → WhatsApp → Configuración</strong></li>
|
||||
<li>En "Números de teléfono", encuentra tu número</li>
|
||||
<li>Copia el <strong>Phone Number ID</strong></li>
|
||||
<li>Pégalo en el campo de arriba</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enlaces útiles -->
|
||||
<div class="text-center">
|
||||
<a href="index.php" class="btn btn-outline-primary me-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>Volver al Panel
|
||||
</a>
|
||||
<a href="diagnostico_whatsapp_avanzado.php" class="btn btn-outline-info me-2">
|
||||
<i class="fas fa-stethoscope me-2"></i>Diagnóstico Completo
|
||||
</a>
|
||||
<a href="solucionador_whatsapp.php" class="btn btn-outline-warning">
|
||||
<i class="fas fa-tools me-2"></i>Herramientas de Solución
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function selectPhoneNumber(phoneId) {
|
||||
document.getElementById('phone_id').value = phoneId;
|
||||
|
||||
// Highlight selected
|
||||
document.querySelectorAll('.phone-card').forEach(card => {
|
||||
card.classList.remove('selected');
|
||||
});
|
||||
event.target.closest('.phone-card').classList.add('selected');
|
||||
|
||||
// Scroll to form
|
||||
document.getElementById('phone_id').scrollIntoView({behavior: 'smooth'});
|
||||
document.getElementById('phone_id').focus();
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,70 @@
|
||||
# Descripción del Uso del Permiso whatsapp_business_management
|
||||
|
||||
## WhatsApp Bot Manager by U-Site.app
|
||||
|
||||
---
|
||||
|
||||
**Descripción detallada del uso del permiso:**
|
||||
|
||||
Nuestra aplicación "WhatsApp Bot Manager" utiliza el permiso whatsapp_business_management para proporcionar una plataforma completa de administración y gestión de activos comerciales de WhatsApp Business. La aplicación permite a las empresas configurar, administrar y optimizar todos sus recursos de WhatsApp Business desde un panel centralizado, facilitando la gestión operativa y el análisis de rendimiento de sus comunicaciones empresariales.
|
||||
|
||||
**Uso específico del permiso:**
|
||||
|
||||
1. **Administración de Cuentas WhatsApp Business:** La app gestiona la configuración y administración de múltiples cuentas de WhatsApp Business, permitiendo a empresas con varios puntos de contacto centralizar su gestión desde una sola plataforma.
|
||||
|
||||
2. **Gestión de Números de Teléfono:** Administra la configuración, verificación y asignación de números de teléfono comerciales, incluyendo la configuración de perfiles comerciales, horarios de atención y información de contacto empresarial.
|
||||
|
||||
3. **Administración de Plantillas de Mensajes:** Crea, edita, organiza y gestiona plantillas de mensajes pre-aprobadas por WhatsApp, permitiendo a las empresas mantener comunicaciones consistentes y cumplir con las políticas de mensajería masiva.
|
||||
|
||||
4. **Configuración de Webhooks:** Gestiona las suscripciones a webhooks para recibir notificaciones en tiempo real de mensajes entrantes, cambios de estado de mensajes y eventos importantes, asegurando comunicación bidireccional efectiva.
|
||||
|
||||
5. **Gestión de Códigos QR Comerciales:** Genera, personaliza y administra códigos QR para que los clientes puedan iniciar conversaciones comerciales fácilmente, facilitando la captación de nuevos contactos y leads.
|
||||
|
||||
6. **Panel de Análisis y Estadísticas:** Recopila y presenta métricas agregadas y anónimas sobre el rendimiento de las comunicaciones empresariales, incluyendo tasas de respuesta, horarios de mayor actividad y efectividad de plantillas.
|
||||
|
||||
**Valor para los usuarios finales:**
|
||||
|
||||
- **Gestión Centralizada:** Las empresas pueden administrar todos sus activos de WhatsApp Business desde un solo panel, eliminando la necesidad de gestionar múltiples plataformas por separado.
|
||||
- **Configuración Simplificada:** Interface intuitiva para configurar números comerciales, plantillas y webhooks sin conocimientos técnicos avanzados.
|
||||
- **Optimización Basada en Datos:** Acceso a análisis detallados que ayudan a las empresas a optimizar sus estrategias de comunicación y mejorar la satisfacción del cliente.
|
||||
- **Escalabilidad Empresarial:** Capacidad de gestionar múltiples números y cuentas comerciales desde una sola plataforma, ideal para empresas en crecimiento.
|
||||
- **Cumplimiento Automatizado:** Gestión automática de plantillas y configuraciones que aseguran cumplimiento con las políticas de WhatsApp Business.
|
||||
|
||||
**Necesidad para el funcionamiento de la app:**
|
||||
|
||||
Este permiso es fundamental para el funcionamiento de nuestra aplicación porque:
|
||||
|
||||
1. **Configuración Inicial:** Sin acceso a la gestión de activos comerciales, las empresas no pueden configurar adecuadamente sus cuentas de WhatsApp Business para usar la plataforma de automatización.
|
||||
|
||||
2. **Administración de Plantillas:** La gestión de plantillas es esencial para enviar mensajes estructurados y cumplir con las regulaciones de WhatsApp sobre mensajes comerciales.
|
||||
|
||||
3. **Gestión de Webhooks:** El acceso a la configuración de webhooks es crítico para recibir mensajes entrantes y mantener conversaciones bidireccionales en tiempo real.
|
||||
|
||||
4. **Análisis de Rendimiento:** Los datos de análisis son necesarios para proporcionar insights valiosos a las empresas sobre la efectividad de sus comunicaciones y identificar áreas de mejora.
|
||||
|
||||
5. **Escalabilidad:** La capacidad de gestionar múltiples números y cuentas es esencial para empresas que operan en diferentes mercados o tienen múltiples líneas de negocio.
|
||||
|
||||
**Casos de uso específicos:**
|
||||
|
||||
- **Cadena de Restaurantes:** Gestiona números de WhatsApp para cada sucursal, plantillas de menús y promociones, y analiza el rendimiento de cada ubicación.
|
||||
- **Empresa de E-commerce:** Administra múltiples números para diferentes categorías de productos, plantillas de confirmación de pedidos y seguimiento de entregas.
|
||||
- **Centro Médico:** Gestiona números especializados por departamento, plantillas para recordatorios de citas y análisis de comunicación por especialidad.
|
||||
- **Agencia de Marketing:** Administra cuentas de WhatsApp Business de múltiples clientes desde una plataforma centralizada.
|
||||
|
||||
**Beneficios de análisis agregado:**
|
||||
|
||||
La aplicación utiliza datos agregados y anónimos para:
|
||||
- Identificar patrones de comunicación más efectivos
|
||||
- Optimizar horarios de envío de mensajes
|
||||
- Mejorar tasas de respuesta mediante análisis de plantillas
|
||||
- Proporcionar benchmarks de industria para mejorar estrategias de comunicación
|
||||
|
||||
La aplicación está diseñada para ser una solución integral de gestión de WhatsApp Business, proporcionando a las empresas las herramientas necesarias para administrar eficientemente sus activos comerciales y optimizar sus comunicaciones con clientes, cumpliendo siempre con las políticas y regulaciones de WhatsApp Business.
|
||||
|
||||
---
|
||||
|
||||
**Información de contacto:**
|
||||
- Desarrollador: U-Site.app
|
||||
- Email: support@u-site.app
|
||||
- Sitio web: https://u-site.app
|
||||
- Política de privacidad: https://tudominio.com/privacy-policy.html
|
||||
@@ -0,0 +1,57 @@
|
||||
# Descripción del Uso del Permiso whatsapp_business_messaging
|
||||
|
||||
## WhatsApp Bot Manager by U-Site.app
|
||||
|
||||
---
|
||||
|
||||
**Descripción detallada del uso del permiso:**
|
||||
|
||||
Nuestra aplicación "WhatsApp Bot Manager" utiliza el permiso whatsapp_business_messaging para proporcionar una plataforma integral de automatización y gestión de comunicaciones empresariales a través de WhatsApp Business API. La aplicación funciona como un sistema de gestión de conversaciones que permite a las empresas automatizar respuestas, gestionar múltiples conversaciones de clientes y mejorar su atención al cliente.
|
||||
|
||||
**Uso específico del permiso:**
|
||||
|
||||
1. **Envío de Mensajes Automatizados:** La app envía mensajes de texto automáticos a clientes basados en triggers predefinidos, respuestas a preguntas frecuentes, notificaciones de servicios, confirmaciones de citas y seguimiento post-venta.
|
||||
|
||||
2. **Gestión de Conversaciones:** Administra y almacena historial completo de conversaciones entre empresas y clientes para proporcionar contexto continuo y seguimiento personalizado de cada interacción.
|
||||
|
||||
3. **Respuestas Inteligentes:** Utiliza un sistema de bot automatizado que responde instantáneamente a consultas comunes de clientes, reduciendo tiempos de respuesta y mejorando la experiencia del usuario.
|
||||
|
||||
4. **Notificaciones Empresariales:** Envía alertas importantes como confirmaciones de pedidos, recordatorios de citas, actualizaciones de estado de servicios y comunicaciones transaccionales.
|
||||
|
||||
5. **Gestión de Multimedia:** Procesa, almacena y responde con contenido multimedia (imágenes, documentos, audios) enviado por clientes para brindar soporte técnico y atención personalizada.
|
||||
|
||||
**Valor para los usuarios finales:**
|
||||
|
||||
- **Respuestas Inmediatas:** Los clientes reciben respuestas instantáneas las 24/7, mejorando significativamente la experiencia de atención al cliente.
|
||||
- **Comunicación Personalizada:** Cada conversación mantiene contexto histórico, permitiendo interacciones más relevantes y personalizadas.
|
||||
- **Eficiencia Operativa:** Las empresas pueden manejar múltiples conversaciones simultáneamente sin pérdida de calidad en el servicio.
|
||||
- **Disponibilidad Continua:** El sistema proporciona atención automatizada fuera del horario comercial, capturando leads y resolviendo consultas básicas.
|
||||
|
||||
**Necesidad para el funcionamiento de la app:**
|
||||
|
||||
Este permiso es absolutamente esencial para el funcionamiento de nuestra aplicación porque:
|
||||
|
||||
1. **Funcionalidad Principal:** Sin la capacidad de enviar mensajes, la app no puede cumplir su propósito fundamental de automatización de comunicaciones empresariales.
|
||||
|
||||
2. **Gestión de Conversaciones:** Necesitamos acceder al historial y metadata de mensajes para mantener contexto conversacional y proporcionar respuestas relevantes.
|
||||
|
||||
3. **Experiencia de Usuario Continua:** La gestión de multimedia es crucial para resolver consultas que requieren intercambio de imágenes, documentos o archivos de audio.
|
||||
|
||||
4. **Análisis y Mejora:** Los datos agregados y anónimos nos permiten optimizar las respuestas automáticas y mejorar la precisión del bot, beneficiando directamente a todos los usuarios.
|
||||
|
||||
**Casos de uso específicos:**
|
||||
|
||||
- Empresa de delivery que envía confirmaciones automáticas de pedidos y tracking de entregas
|
||||
- Clínica médica que envía recordatorios de citas y confirmaciones de disponibilidad
|
||||
- Tienda online que responde consultas sobre productos y procesa órdenes de compra
|
||||
- Empresa de servicios que proporciona soporte técnico 24/7 con respuestas automatizadas
|
||||
|
||||
La aplicación está diseñada exclusivamente para mejorar la comunicación empresa-cliente, cumpliendo con todas las políticas de WhatsApp Business y proporcionando valor real tanto a empresas como a sus clientes finales a través de comunicaciones más eficientes y efectivas.
|
||||
|
||||
---
|
||||
|
||||
**Información de contacto:**
|
||||
- Desarrollador: U-Site.app
|
||||
- Email: support@u-site.app
|
||||
- Sitio web: https://u-site.app
|
||||
- Política de privacidad: https://tudominio.com/privacy-policy.html
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* Script para probar y arreglar contraseña
|
||||
* Desarrollado por U-Site.app
|
||||
*/
|
||||
|
||||
echo "🔧 DIAGNÓSTICO Y REPARACIÓN DE CONTRASEÑA\n";
|
||||
echo "========================================\n\n";
|
||||
|
||||
// Contraseña actual del archivo de credenciales
|
||||
$currentPassword = '2fa904eee46f';
|
||||
echo "Contraseña a verificar: " . $currentPassword . "\n";
|
||||
|
||||
// Leer config actual
|
||||
require_once 'config/config.php';
|
||||
$currentHash = ADMIN_PASSWORD;
|
||||
echo "Hash actual en config.php: " . $currentHash . "\n";
|
||||
echo "Longitud del hash: " . strlen($currentHash) . " caracteres\n\n";
|
||||
|
||||
// Verificar si la contraseña funciona con el hash actual
|
||||
echo "Probando contraseña actual...\n";
|
||||
if (password_verify($currentPassword, $currentHash)) {
|
||||
echo "✅ La contraseña SÍ funciona con el hash actual\n";
|
||||
} else {
|
||||
echo "❌ La contraseña NO funciona con el hash actual\n";
|
||||
echo "🔧 Generando nuevo hash...\n";
|
||||
|
||||
$newHash = password_hash($currentPassword, PASSWORD_DEFAULT);
|
||||
echo "Nuevo hash generado: " . $newHash . "\n";
|
||||
echo "Longitud del nuevo hash: " . strlen($newHash) . " caracteres\n";
|
||||
|
||||
// Verificar que el nuevo hash funciona
|
||||
if (password_verify($currentPassword, $newHash)) {
|
||||
echo "✅ El nuevo hash funciona correctamente\n";
|
||||
|
||||
// Actualizar config.php
|
||||
$configFile = 'config/config.php';
|
||||
$config = file_get_contents($configFile);
|
||||
|
||||
// Buscar y reemplazar la línea completa de ADMIN_PASSWORD
|
||||
$pattern = "/define\('ADMIN_PASSWORD',\s*'[^']*'\);/";
|
||||
$replacement = "define('ADMIN_PASSWORD', '" . $newHash . "');";
|
||||
$newConfig = preg_replace($pattern, $replacement, $config);
|
||||
|
||||
if (file_put_contents($configFile, $newConfig)) {
|
||||
echo "✅ Archivo config.php actualizado correctamente\n";
|
||||
} else {
|
||||
echo "❌ Error al actualizar config.php\n";
|
||||
}
|
||||
} else {
|
||||
echo "❌ Error: El nuevo hash no funciona\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n📋 CREDENCIALES FINALES:\n";
|
||||
echo "Usuario: admin\n";
|
||||
echo "Contraseña: " . $currentPassword . "\n";
|
||||
echo "\n🌐 Prueba en: http://localhost:8080/login.php\n";
|
||||
?>
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
/**
|
||||
* Corrección rápida para Phone Number ID problemático
|
||||
* Fecha: 10 de enero de 2026
|
||||
*
|
||||
* Este script corrige específicamente el Phone Number ID problemático '858157464051987'
|
||||
*/
|
||||
|
||||
require_once 'config/config.php';
|
||||
|
||||
// Solo permitir ejecución directa (no via include)
|
||||
if (basename(__FILE__) !== basename($_SERVER['SCRIPT_NAME'])) {
|
||||
die('Acceso no autorizado');
|
||||
}
|
||||
|
||||
$message = '';
|
||||
$error = '';
|
||||
$currentPhoneId = WHATSAPP_PHONE_NUMBER_ID;
|
||||
$isProblematic = ($currentPhoneId === '858157464051987');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
|
||||
if ($_POST['action'] === 'update_phone_id') {
|
||||
$newPhoneId = trim($_POST['new_phone_id'] ?? '');
|
||||
$newToken = trim($_POST['whatsapp_token'] ?? '');
|
||||
|
||||
if (empty($newPhoneId)) {
|
||||
$error = 'El Phone Number ID es obligatorio';
|
||||
} elseif (empty($newToken)) {
|
||||
$error = 'El Token de WhatsApp es obligatorio';
|
||||
} elseif (!preg_match('/^\d{10,20}$/', $newPhoneId)) {
|
||||
$error = 'Phone Number ID debe ser numérico (10-20 dígitos)';
|
||||
} elseif (!preg_match('/^EAA[A-Za-z0-9_-]+$/', $newToken)) {
|
||||
$error = 'Formato de token inválido. Debe empezar con EAA';
|
||||
} else {
|
||||
try {
|
||||
// Probar la nueva configuración antes de guardar
|
||||
$testUrl = "https://graph.facebook.com/v22.0/{$newPhoneId}";
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'GET',
|
||||
'header' => "Authorization: Bearer {$newToken}\r\n"
|
||||
]
|
||||
]);
|
||||
|
||||
$testResponse = @file_get_contents($testUrl, false, $context);
|
||||
$httpCode = 200;
|
||||
|
||||
if (isset($http_response_header)) {
|
||||
foreach ($http_response_header as $header) {
|
||||
if (strpos($header, 'HTTP/') === 0) {
|
||||
$httpCode = (int) substr($header, 9, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
$error = 'No se puede validar el Phone Number ID. Verifique que sea correcto y que tenga permisos.';
|
||||
} else {
|
||||
// Actualizar en base de datos
|
||||
$pdo = createDbConnection();
|
||||
|
||||
// Verificar si existe la tabla system_config
|
||||
$tableExists = $pdo->query("SHOW TABLES LIKE 'system_config'")->rowCount() > 0;
|
||||
|
||||
if ($tableExists) {
|
||||
// Actualizar Phone Number ID
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO system_config (config_key, config_value, updated_at)
|
||||
VALUES (?, ?, NOW())
|
||||
ON DUPLICATE KEY UPDATE config_value = VALUES(config_value), updated_at = NOW()
|
||||
");
|
||||
$stmt->execute(['whatsapp_phone_number_id', $newPhoneId]);
|
||||
|
||||
// Actualizar Token
|
||||
$stmt->execute(['whatsapp_token', $newToken]);
|
||||
|
||||
$message = "✅ Configuración actualizada correctamente. Phone Number ID: {$newPhoneId}";
|
||||
|
||||
// Refrescar la página para mostrar la nueva configuración
|
||||
header("Location: " . $_SERVER['PHP_SELF'] . "?updated=1");
|
||||
exit;
|
||||
} else {
|
||||
$error = 'La tabla system_config no existe. Ejecute la instalación primero.';
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$error = 'Error al actualizar configuración: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Si se redirigió después de actualizar
|
||||
if (isset($_GET['updated'])) {
|
||||
$message = "✅ Configuración actualizada correctamente.";
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Corrección Phone Number ID - WhatsApp Bot</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card shadow">
|
||||
<div class="card-header bg-danger text-white">
|
||||
<h4 class="mb-0">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
Corrección Phone Number ID Problemático
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-success">
|
||||
<i class="fas fa-check-circle"></i> <?= htmlspecialchars($message) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger">
|
||||
<i class="fas fa-exclamation-circle"></i> <?= htmlspecialchars($error) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<h5><i class="fas fa-info-circle"></i> Problema Detectado</h5>
|
||||
<p>El sistema está intentando usar el Phone Number ID: <code><?= htmlspecialchars($currentPhoneId) ?></code></p>
|
||||
|
||||
<?php if ($isProblematic): ?>
|
||||
<p class="text-danger fw-bold">
|
||||
⚠️ Este ID está generando el error: "Object with ID '858157464051987' does not exist, cannot be loaded due to missing permissions, or does not support this operation"
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<h5><i class="fas fa-lightbulb"></i> Cómo obtener el Phone Number ID correcto:</h5>
|
||||
<ol>
|
||||
<li>Ve a <a href="https://developers.facebook.com/apps" target="_blank">Facebook for Developers</a></li>
|
||||
<li>Selecciona tu app de WhatsApp Business</li>
|
||||
<li>En el menú izquierdo, ve a "WhatsApp" → "API Setup"</li>
|
||||
<li>Copia el "Phone Number ID" que aparece ahí</li>
|
||||
<li>También copia tu "Access Token" si ha cambiado</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<form method="POST" class="mt-4">
|
||||
<input type="hidden" name="action" value="update_phone_id">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="whatsapp_token" class="form-label">
|
||||
<i class="fas fa-key"></i> WhatsApp Token *
|
||||
</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="whatsapp_token"
|
||||
name="whatsapp_token"
|
||||
value="<?= htmlspecialchars(WHATSAPP_TOKEN === 'TU_TOKEN_DE_WHATSAPP_AQUI' ? '' : WHATSAPP_TOKEN) ?>"
|
||||
placeholder="EAA..."
|
||||
required>
|
||||
<div class="form-text">Debe empezar con EAA</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="new_phone_id" class="form-label">
|
||||
<i class="fas fa-phone"></i> Nuevo Phone Number ID *
|
||||
</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="new_phone_id"
|
||||
name="new_phone_id"
|
||||
placeholder="123456789012345"
|
||||
pattern="\d{10,20}"
|
||||
required>
|
||||
<div class="form-text">Solo números, 10-20 dígitos</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-primary btn-lg">
|
||||
<i class="fas fa-save"></i>
|
||||
Actualizar Configuración
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="text-center">
|
||||
<a href="index.php" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Volver al Panel
|
||||
</a>
|
||||
<a href="encontrar_phone_id.php" class="btn btn-info">
|
||||
<i class="fas fa-search"></i> Encontrar Phone ID
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,474 @@
|
||||
<?php
|
||||
/**
|
||||
* Gestión Unificada de Administradores - 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>🛠️ Gestión de Administradores - 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: 1200px;
|
||||
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"], input[type="email"] {
|
||||
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;
|
||||
}
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
.btn-warning {
|
||||
background: #ffc107;
|
||||
color: #000;
|
||||
}
|
||||
.btn-warning:hover {
|
||||
background: #e0a800;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.tab-container {
|
||||
display: flex;
|
||||
border-bottom: 2px solid #25D366;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.tab {
|
||||
padding: 15px 30px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: #f8f9fa;
|
||||
margin-right: 5px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
.tab.active {
|
||||
background: #25D366;
|
||||
color: white;
|
||||
}
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
code {
|
||||
background: #f8f9fa;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🛠️ Gestión Unificada de Administradores</h1>
|
||||
|
||||
<div class="tab-container">
|
||||
<button class="tab active" onclick="showTab('list')">📋 Lista de Administradores</button>
|
||||
<button class="tab" onclick="showTab('add')">➕ Agregar Admin</button>
|
||||
<button class="tab" onclick="showTab('password')">🔑 Cambiar Contraseña</button>
|
||||
<button class="tab" onclick="showTab('test')">🔍 Probar Login</button>
|
||||
<button class="tab" onclick="showTab('config')">⚙️ Configuración</button>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
try {
|
||||
$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, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||||
);
|
||||
|
||||
// Verificar si el sistema está migrado
|
||||
$checkTable = $pdo->query("SHOW TABLES LIKE 'admin_users'");
|
||||
$tableExists = $checkTable->rowCount() > 0;
|
||||
|
||||
if (!$tableExists) {
|
||||
echo '<div class="error-box">';
|
||||
echo '<h4>❌ Sistema No Migrado</h4>';
|
||||
echo '<p>El sistema de contraseñas aún no ha sido migrado a la base de datos.</p>';
|
||||
echo '<p><a href="migrar_passwords_bd.php" class="btn">🔄 Migrar Sistema Ahora</a></p>';
|
||||
echo '</div>';
|
||||
exit;
|
||||
}
|
||||
|
||||
// Procesar acciones
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
|
||||
if ($_POST['action'] === 'add_admin') {
|
||||
$username = trim($_POST['username']);
|
||||
$password = $_POST['password'];
|
||||
$fullName = trim($_POST['full_name']);
|
||||
$email = trim($_POST['email']);
|
||||
|
||||
if (strlen($username) < 3) {
|
||||
echo '<div class="error-box">❌ El usuario debe tener al menos 3 caracteres</div>';
|
||||
} elseif (strlen($password) < 6) {
|
||||
echo '<div class="error-box">❌ La contraseña debe tener al menos 6 caracteres</div>';
|
||||
} else {
|
||||
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO admin_users (username, password_hash, full_name, email) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$username, $passwordHash, $fullName, $email]);
|
||||
echo '<div class="info-box">✅ Administrador creado exitosamente: <strong>' . htmlspecialchars($username) . '</strong></div>';
|
||||
} catch (PDOException $e) {
|
||||
echo '<div class="error-box">❌ Error: ' . (strpos($e->getMessage(), 'Duplicate') !== false ? 'El usuario ya existe' : $e->getMessage()) . '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'update_password') {
|
||||
$username = $_POST['username'];
|
||||
$newPassword = $_POST['new_password'];
|
||||
$confirmPassword = $_POST['confirm_password'];
|
||||
|
||||
if ($newPassword !== $confirmPassword) {
|
||||
echo '<div class="error-box">❌ Las contraseñas no coinciden</div>';
|
||||
} elseif (strlen($newPassword) < 6) {
|
||||
echo '<div class="error-box">❌ La contraseña debe tener al menos 6 caracteres</div>';
|
||||
} else {
|
||||
$passwordHash = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||
$stmt = $pdo->prepare("UPDATE admin_users SET password_hash = ?, updated_at = NOW() WHERE username = ?");
|
||||
|
||||
if ($stmt->execute([$passwordHash, $username])) {
|
||||
echo '<div class="info-box">✅ Contraseña actualizada para: <strong>' . htmlspecialchars($username) . '</strong></div>';
|
||||
} else {
|
||||
echo '<div class="error-box">❌ Error actualizando contraseña</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'toggle_status') {
|
||||
$userId = $_POST['user_id'];
|
||||
$stmt = $pdo->prepare("UPDATE admin_users SET is_active = NOT is_active, updated_at = NOW() WHERE id = ?");
|
||||
|
||||
if ($stmt->execute([$userId])) {
|
||||
echo '<div class="info-box">✅ Estado del usuario actualizado</div>';
|
||||
} else {
|
||||
echo '<div class="error-box">❌ Error actualizando estado</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'test_login') {
|
||||
$testUsername = $_POST['test_username'];
|
||||
$testPassword = $_POST['test_password'];
|
||||
|
||||
$stmt = $pdo->prepare("SELECT * FROM admin_users WHERE username = ? AND is_active = 1");
|
||||
$stmt->execute([$testUsername]);
|
||||
$admin = $stmt->fetch();
|
||||
|
||||
if ($admin && password_verify($testPassword, $admin['password_hash'])) {
|
||||
echo '<div class="info-box">✅ <strong>LOGIN EXITOSO</strong><br>';
|
||||
echo 'Usuario: ' . htmlspecialchars($admin['username']) . '<br>';
|
||||
echo 'Nombre: ' . htmlspecialchars($admin['full_name']) . '<br>';
|
||||
echo 'Último login: ' . ($admin['last_login'] ?? 'Nunca') . '</div>';
|
||||
|
||||
// Actualizar último login
|
||||
$updateLogin = $pdo->prepare("UPDATE admin_users SET last_login = NOW() WHERE id = ?");
|
||||
$updateLogin->execute([$admin['id']]);
|
||||
} else {
|
||||
echo '<div class="error-box">❌ <strong>LOGIN FALLIDO</strong><br>';
|
||||
if (!$admin) {
|
||||
echo 'Usuario no encontrado o inactivo';
|
||||
} else {
|
||||
echo 'Contraseña incorrecta';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'clear_login_attempts') {
|
||||
$attemptsFile = '.login_attempts.json';
|
||||
if (file_exists($attemptsFile)) {
|
||||
unlink($attemptsFile);
|
||||
echo '<div class="info-box">✅ Intentos de login bloqueados eliminados</div>';
|
||||
} else {
|
||||
echo '<div class="warning-box">⚠️ No hay bloqueos para eliminar</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo '<div class="error-box">❌ Error de base de datos: ' . $e->getMessage() . '</div>';
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- Tab: Lista de Administradores -->
|
||||
<div id="list" class="tab-content active">
|
||||
<h3>📋 Administradores del Sistema</h3>
|
||||
|
||||
<?php
|
||||
$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>Último Login</th><th>Acciones</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['last_login'] ?? 'Nunca') . '</td>';
|
||||
echo '<td>';
|
||||
echo '<form method="post" style="display: inline-block;">';
|
||||
echo '<input type="hidden" name="action" value="toggle_status">';
|
||||
echo '<input type="hidden" name="user_id" value="' . $admin['id'] . '">';
|
||||
echo '<button type="submit" class="btn btn-warning" style="padding: 5px 10px; font-size: 12px;">';
|
||||
echo $admin['is_active'] ? 'Desactivar' : 'Activar';
|
||||
echo '</button>';
|
||||
echo '</form>';
|
||||
echo '</td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Agregar Admin -->
|
||||
<div id="add" class="tab-content">
|
||||
<h3>➕ Agregar Nuevo Administrador</h3>
|
||||
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label for="username">Usuario:</label>
|
||||
<input type="text" id="username" name="username" required minlength="3">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Contraseña:</label>
|
||||
<input type="password" id="password" name="password" required minlength="6">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="full_name">Nombre completo:</label>
|
||||
<input type="text" id="full_name" name="full_name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email:</label>
|
||||
<input type="email" id="email" name="email">
|
||||
</div>
|
||||
<input type="hidden" name="action" value="add_admin">
|
||||
<button type="submit" class="btn">➕ Crear Administrador</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Cambiar Contraseña -->
|
||||
<div id="password" class="tab-content">
|
||||
<h3>🔑 Cambiar Contraseña de Administrador</h3>
|
||||
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label for="username_pass">Seleccionar usuario:</label>
|
||||
<select id="username_pass" name="username" required style="width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 8px;">
|
||||
<option value="">Seleccionar...</option>
|
||||
<?php
|
||||
foreach ($admins as $admin) {
|
||||
echo '<option value="' . htmlspecialchars($admin['username']) . '">' .
|
||||
htmlspecialchars($admin['username']) . ' - ' . htmlspecialchars($admin['full_name'] ?? 'Sin nombre') .
|
||||
'</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="new_password">Nueva contraseña:</label>
|
||||
<input type="password" id="new_password" name="new_password" required minlength="6">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirm_password">Confirmar contraseña:</label>
|
||||
<input type="password" id="confirm_password" name="confirm_password" required minlength="6">
|
||||
</div>
|
||||
<input type="hidden" name="action" value="update_password">
|
||||
<button type="submit" class="btn">🔑 Actualizar Contraseña</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Probar Login -->
|
||||
<div id="test" class="tab-content">
|
||||
<h3>🔍 Probar Credenciales de Login</h3>
|
||||
|
||||
<form method="post">
|
||||
<div class="form-group">
|
||||
<label for="test_username">Usuario:</label>
|
||||
<input type="text" id="test_username" name="test_username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="test_password">Contraseña:</label>
|
||||
<input type="password" id="test_password" name="test_password" required>
|
||||
</div>
|
||||
<input type="hidden" name="action" value="test_login">
|
||||
<button type="submit" class="btn">🔍 Probar Login</button>
|
||||
</form>
|
||||
|
||||
<div class="info-box" style="margin-top: 20px;">
|
||||
<h4>🌐 Enlaces de Acceso:</h4>
|
||||
<p><strong>Login del sistema:</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Configuración -->
|
||||
<div id="config" class="tab-content">
|
||||
<h3>⚙️ Configuración del Sistema</h3>
|
||||
|
||||
<div class="info-box">
|
||||
<h4>📊 Estado del Sistema</h4>
|
||||
<p><strong>Total de administradores:</strong> <?php echo count($admins); ?></p>
|
||||
<p><strong>Administradores activos:</strong> <?php echo count(array_filter($admins, fn($a) => $a['is_active'])); ?></p>
|
||||
<p><strong>Base de datos:</strong> <?php echo DB_NAME; ?></p>
|
||||
<p><strong>Sistema migrado:</strong> ✅ Sí</p>
|
||||
</div>
|
||||
|
||||
<form method="post" style="margin-top: 20px;">
|
||||
<input type="hidden" name="action" value="clear_login_attempts">
|
||||
<button type="submit" class="btn btn-danger">🗑️ Limpiar Intentos de Login Bloqueados</button>
|
||||
</form>
|
||||
|
||||
<div class="warning-box" style="margin-top: 20px;">
|
||||
<h4>⚠️ Información Importante:</h4>
|
||||
<ul>
|
||||
<li>Las contraseñas se almacenan de forma segura usando hash</li>
|
||||
<li>Los usuarios inactivos no pueden iniciar sesión</li>
|
||||
<li>Se registra la fecha del último login de cada usuario</li>
|
||||
<li>Esta herramienta debe eliminarse en producción</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function showTab(tabName) {
|
||||
// Ocultar todos los tabs
|
||||
var tabs = document.querySelectorAll('.tab-content');
|
||||
tabs.forEach(function(tab) {
|
||||
tab.classList.remove('active');
|
||||
});
|
||||
|
||||
// Remover clase active de todos los botones
|
||||
var buttons = document.querySelectorAll('.tab');
|
||||
buttons.forEach(function(button) {
|
||||
button.classList.remove('active');
|
||||
});
|
||||
|
||||
// Mostrar el tab seleccionado
|
||||
document.getElementById(tabName).classList.add('active');
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
/**
|
||||
* 💾 GUARDADOR DE CONFIGURACIÓN WHATSAPP
|
||||
* Herramienta para guardar configuración en la base de datos
|
||||
*/
|
||||
|
||||
// Headers para mostrar errores
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
echo "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
|
||||
echo "<title>💾 Guardar Configuración 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 "input[type=text]{width:100%;padding:10px;background:#222;color:#fff;border:1px solid #555;margin:5px 0}";
|
||||
echo "button{padding:10px 20px;background:#333;color:#fff;border:1px solid #555;cursor:pointer}";
|
||||
echo "button:hover{background:#555}";
|
||||
echo ".form-group{margin:15px 0}";
|
||||
echo "</style></head><body>";
|
||||
|
||||
echo "<h1>💾 GUARDADOR DE CONFIGURACIÓN 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;
|
||||
}
|
||||
|
||||
// Procesar formulario
|
||||
if ($_POST) {
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>💾 GUARDANDO EN BASE DE DATOS...</h2>";
|
||||
|
||||
$config_updates = [
|
||||
'whatsapp_token' => trim($_POST['whatsapp_token'] ?? ''),
|
||||
'whatsapp_phone_number_id' => trim($_POST['phone_id'] ?? ''),
|
||||
'webhook_verify_token' => trim($_POST['webhook_token'] ?? ''),
|
||||
'whatsapp_api_url' => trim($_POST['api_url'] ?? 'https://graph.facebook.com/v22.0/')
|
||||
];
|
||||
|
||||
try {
|
||||
$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,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
]
|
||||
);
|
||||
|
||||
$updated = 0;
|
||||
$inserted = 0;
|
||||
|
||||
foreach ($config_updates as $key => $value) {
|
||||
if (empty($value)) continue;
|
||||
|
||||
// Verificar si ya existe
|
||||
$stmt = $pdo->prepare("SELECT id FROM system_config WHERE config_key = ?");
|
||||
$stmt->execute([$key]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Actualizar existente
|
||||
$stmt = $pdo->prepare("UPDATE system_config SET config_value = ?, updated_at = NOW() WHERE config_key = ?");
|
||||
$stmt->execute([$value, $key]);
|
||||
$updated++;
|
||||
echo "<div class='ok'>✅ Actualizado: $key</div>";
|
||||
} else {
|
||||
// Insertar nuevo
|
||||
$descriptions = [
|
||||
'whatsapp_token' => 'Token de acceso de WhatsApp Business API',
|
||||
'whatsapp_phone_number_id' => 'ID del número de teléfono de WhatsApp',
|
||||
'webhook_verify_token' => 'Token de verificación para webhook',
|
||||
'whatsapp_api_url' => 'URL base de la API de WhatsApp'
|
||||
];
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO system_config (config_key, config_value, description) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$key, $value, $descriptions[$key] ?? 'Configuración de WhatsApp']);
|
||||
$inserted++;
|
||||
echo "<div class='ok'>✅ Insertado: $key</div>";
|
||||
}
|
||||
}
|
||||
|
||||
echo "<div class='ok'>📊 Total actualizados: $updated</div>";
|
||||
echo "<div class='ok'>📊 Total insertados: $inserted</div>";
|
||||
|
||||
// Verificar que se guardó correctamente
|
||||
echo "<br><div class='warning'>🔄 Verificando configuración guardada...</div>";
|
||||
|
||||
foreach ($config_updates as $key => $value) {
|
||||
if (empty($value)) continue;
|
||||
|
||||
$stmt = $pdo->prepare("SELECT config_value FROM system_config WHERE config_key = ?");
|
||||
$stmt->execute([$key]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
if ($row && $row['config_value'] === $value) {
|
||||
echo "<div class='ok'>✅ $key guardado correctamente</div>";
|
||||
} else {
|
||||
echo "<div class='error'>❌ $key NO se guardó correctamente</div>";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
echo "<div class='error'>❌ Error de base de datos: " . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
}
|
||||
|
||||
// Mostrar configuración actual
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>⚙️ CONFIGURACIÓN ACTUAL</h2>";
|
||||
|
||||
try {
|
||||
$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]
|
||||
);
|
||||
|
||||
$stmt = $pdo->query("SELECT config_key, config_value FROM system_config WHERE config_key IN ('whatsapp_token', 'whatsapp_phone_number_id', 'webhook_verify_token', 'whatsapp_api_url')");
|
||||
$current_config = [];
|
||||
while ($row = $stmt->fetch()) {
|
||||
$current_config[$row['config_key']] = $row['config_value'];
|
||||
}
|
||||
|
||||
// Mostrar configuración
|
||||
$configs_to_show = [
|
||||
'whatsapp_token' => 'Token de WhatsApp',
|
||||
'whatsapp_phone_number_id' => 'Phone Number ID',
|
||||
'webhook_verify_token' => 'Webhook Verify Token',
|
||||
'whatsapp_api_url' => 'API URL'
|
||||
];
|
||||
|
||||
foreach ($configs_to_show as $key => $label) {
|
||||
if (isset($current_config[$key])) {
|
||||
$value = $current_config[$key];
|
||||
if (in_array($key, ['whatsapp_token', 'webhook_verify_token'])) {
|
||||
$display = substr($value, 0, 10) . '...' . substr($value, -10) . " (" . strlen($value) . " chars)";
|
||||
} else {
|
||||
$display = $value;
|
||||
}
|
||||
echo "<div class='ok'>✅ $label: $display</div>";
|
||||
} else {
|
||||
echo "<div class='error'>❌ $label: No configurado en BD</div>";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
echo "<div class='error'>❌ Error consultando BD: " . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
// Formulario
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>📝 ACTUALIZAR CONFIGURACIÓN</h2>";
|
||||
|
||||
echo "<form method='POST'>";
|
||||
|
||||
echo "<div class='form-group'>";
|
||||
echo "<label style='color:#ffaa00'><strong>🔑 Token de WhatsApp Business API:</strong></label>";
|
||||
echo "<input type='text' name='whatsapp_token' placeholder='EAAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'>";
|
||||
echo "<div class='warning'>• Se guardará en la base de datos</div>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='form-group'>";
|
||||
echo "<label style='color:#ffaa00'><strong>📱 Phone Number ID:</strong></label>";
|
||||
echo "<input type='text' name='phone_id' placeholder='1234567890123456'>";
|
||||
echo "<div class='warning'>• ID numérico del teléfono</div>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='form-group'>";
|
||||
echo "<label style='color:#ffaa00'><strong>🔐 Webhook Verify Token:</strong></label>";
|
||||
echo "<input type='text' name='webhook_token' placeholder='mi_token_secreto_123'>";
|
||||
echo "<div class='warning'>• Token para verificar webhook</div>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='form-group'>";
|
||||
echo "<label style='color:#ffaa00'><strong>🌐 API URL:</strong></label>";
|
||||
echo "<input type='text' name='api_url' value='https://graph.facebook.com/v22.0/' placeholder='https://graph.facebook.com/v22.0/'>";
|
||||
echo "<div class='warning'>• URL base de la API de WhatsApp</div>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='form-group'>";
|
||||
echo "<button type='submit'>💾 Guardar en Base de Datos</button>";
|
||||
echo "</div>";
|
||||
|
||||
echo "</form>";
|
||||
echo "</div>";
|
||||
|
||||
// Test de las constantes actuales
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>🔍 VALORES ACTUALES DE LAS CONSTANTES</h2>";
|
||||
|
||||
$constants_to_check = [
|
||||
'WHATSAPP_TOKEN' => 'Token actual en memoria',
|
||||
'WHATSAPP_PHONE_NUMBER_ID' => 'Phone ID actual en memoria',
|
||||
'WEBHOOK_VERIFY_TOKEN' => 'Webhook token actual en memoria',
|
||||
'WHATSAPP_API_URL' => 'API URL actual en memoria'
|
||||
];
|
||||
|
||||
foreach ($constants_to_check as $const => $desc) {
|
||||
if (defined($const)) {
|
||||
$value = constant($const);
|
||||
if (in_array($const, ['WHATSAPP_TOKEN', 'WEBHOOK_VERIFY_TOKEN'])) {
|
||||
if (strlen($value) > 20) {
|
||||
$display = substr($value, 0, 10) . '...' . substr($value, -10);
|
||||
} else {
|
||||
$display = $value;
|
||||
}
|
||||
} else {
|
||||
$display = $value;
|
||||
}
|
||||
echo "<div class='ok'>✅ $desc: $display</div>";
|
||||
} else {
|
||||
echo "<div class='error'>❌ $desc: No definida</div>";
|
||||
}
|
||||
}
|
||||
|
||||
echo "<div class='warning'><strong>NOTA:</strong> Después de guardar, recarga esta página para ver los nuevos valores</div>";
|
||||
|
||||
echo "</div>";
|
||||
|
||||
echo "</body></html>";
|
||||
?>
|
||||
@@ -0,0 +1,292 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Centro de Ayuda - WhatsApp 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>
|
||||
.help-card {
|
||||
transition: transform 0.2s;
|
||||
border: none;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
.help-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
||||
}
|
||||
.hero-section {
|
||||
background: linear-gradient(135deg, #25d366, #128c7e);
|
||||
color: white;
|
||||
padding: 60px 0;
|
||||
}
|
||||
.status-badge {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Hero Section -->
|
||||
<div class="hero-section">
|
||||
<div class="container text-center">
|
||||
<h1><i class="fas fa-robot"></i> WhatsApp Bot Manager</h1>
|
||||
<p class="lead">Centro de Ayuda y Configuración</p>
|
||||
<small>Sistema validado y optimizado - 2 de enero de 2026</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container my-5">
|
||||
<!-- Status Overview -->
|
||||
<div class="row mb-5">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-success text-center">
|
||||
<h4><i class="fas fa-check-circle"></i> Estado del Sistema</h4>
|
||||
<p class="mb-0">Tu WhatsApp Bot Manager ha sido <strong>validado y optimizado</strong> completamente</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Access -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h2 class="text-center mb-4">🚀 Acceso Rápido</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-md-3">
|
||||
<div class="card help-card h-100 text-center">
|
||||
<div class="card-body">
|
||||
<i class="fas fa-check-circle fa-3x text-success mb-3"></i>
|
||||
<h5>Validar Sistema</h5>
|
||||
<p class="card-text">Verifica que todo esté funcionando correctamente</p>
|
||||
<a href="system_validator.php" class="btn btn-success">Validar</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="card help-card h-100 text-center">
|
||||
<div class="card-body">
|
||||
<i class="fab fa-whatsapp fa-3x text-success mb-3"></i>
|
||||
<h5>Test WhatsApp</h5>
|
||||
<p class="card-text">Probar conectividad con WhatsApp API</p>
|
||||
<a href="whatsapp_tester.php" class="btn btn-success">Probar</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="card help-card h-100 text-center">
|
||||
<div class="card-body">
|
||||
<i class="fas fa-cog fa-3x text-primary mb-3"></i>
|
||||
<h5>Configuración</h5>
|
||||
<p class="card-text">Configurar manualmente sin archivos .env</p>
|
||||
<a href="quick_config.php" class="btn btn-primary">Configurar</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<div class="card help-card h-100 text-center">
|
||||
<div class="card-body">
|
||||
<i class="fas fa-tachometer-alt fa-3x text-info mb-3"></i>
|
||||
<h5>Panel Control</h5>
|
||||
<p class="card-text">Acceder al panel de administración</p>
|
||||
<a href="index.php" class="btn btn-info text-white">Acceder</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Installation Options -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h2 class="text-center mb-4">⚙️ Opciones de Instalación</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-md-4">
|
||||
<div class="card help-card h-100">
|
||||
<div class="card-body">
|
||||
<div class="position-relative">
|
||||
<span class="badge bg-success status-badge">Recomendado</span>
|
||||
</div>
|
||||
<i class="fas fa-server fa-2x text-primary mb-3"></i>
|
||||
<h5>Instalador Automático</h5>
|
||||
<p class="card-text">Para VPS y servidores dedicados. Crea automáticamente la base de datos y configura todo.</p>
|
||||
<a href="install.php" class="btn btn-primary">Instalar Automático</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card help-card h-100">
|
||||
<div class="card-body">
|
||||
<i class="fas fa-tools fa-2x text-warning mb-3"></i>
|
||||
<h5>Instalador Manual</h5>
|
||||
<p class="card-text">Para hosting compartido (cPanel). Requiere crear la base de datos manualmente.</p>
|
||||
<a href="install_manual.php" class="btn btn-warning">Instalar Manual</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card help-card h-100">
|
||||
<div class="card-body">
|
||||
<i class="fas fa-feather-alt fa-2x text-info mb-3"></i>
|
||||
<h5>Instalador Básico</h5>
|
||||
<p class="card-text">Instalación mínima sin funciones avanzadas. Ideal para pruebas rápidas.</p>
|
||||
<a href="install_simple.php" class="btn btn-info text-white">Instalar Básico</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Documentation -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h2 class="text-center mb-4">📚 Documentación</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-md-4">
|
||||
<div class="card help-card h-100">
|
||||
<div class="card-body">
|
||||
<i class="fas fa-book-open fa-2x text-success mb-3"></i>
|
||||
<h5>README Principal</h5>
|
||||
<p class="card-text">Descripción completa del proyecto, características y funcionalidades.</p>
|
||||
<a href="README.md" target="_blank" class="btn btn-outline-success">Leer</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card help-card h-100">
|
||||
<div class="card-body">
|
||||
<div class="position-relative">
|
||||
<span class="badge bg-primary status-badge">Nuevo</span>
|
||||
</div>
|
||||
<i class="fas fa-rocket fa-2x text-primary mb-3"></i>
|
||||
<h5>Guía de Setup</h5>
|
||||
<p class="card-text">Instrucciones completas paso a paso para configurar el sistema correctamente.</p>
|
||||
<a href="README_SETUP.md" target="_blank" class="btn btn-outline-primary">Ver Guía</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card help-card h-100">
|
||||
<div class="card-body">
|
||||
<i class="fas fa-cloud-upload-alt fa-2x text-info mb-3"></i>
|
||||
<h5>Guía de Despliegue</h5>
|
||||
<p class="card-text">Instrucciones detalladas para desplegar en diferentes tipos de servidores.</p>
|
||||
<a href="DEPLOYMENT_GUIDE.md" target="_blank" class="btn btn-outline-info">Ver Guía</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Troubleshooting -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h2 class="text-center mb-4">🆘 Solución de Problemas</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-md-4">
|
||||
<div class="card help-card h-100">
|
||||
<div class="card-body">
|
||||
<i class="fas fa-database fa-2x text-danger mb-3"></i>
|
||||
<h5>Problemas de Base de Datos</h5>
|
||||
<p class="card-text">Soluciones para errores de conexión y tablas faltantes.</p>
|
||||
<a href="SOLUCION_ERROR_CONFIG.md" target="_blank" class="btn btn-outline-danger">Ver Solución</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card help-card h-100">
|
||||
<div class="card-body">
|
||||
<i class="fas fa-server fa-2x text-warning mb-3"></i>
|
||||
<h5>Problemas de Hosting</h5>
|
||||
<p class="card-text">Guías específicas para diferentes proveedores de hosting.</p>
|
||||
<a href="SOLUCION_HOSTING.md" target="_blank" class="btn btn-outline-warning">Ver Solución</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card help-card h-100">
|
||||
<div class="card-body">
|
||||
<i class="fas fa-table fa-2x text-info mb-3"></i>
|
||||
<h5>Tablas Faltantes</h5>
|
||||
<p class="card-text">Qué hacer cuando faltan tablas en la base de datos.</p>
|
||||
<a href="SOLUCION_TABLA_NO_EXISTE.md" target="_blank" class="btn btn-outline-info">Ver Solución</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System Status -->
|
||||
<div class="card mb-5">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0"><i class="fas fa-info-circle"></i> Estado del Sistema</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row text-center">
|
||||
<div class="col-md-3">
|
||||
<i class="fas fa-check-circle fa-2x text-success mb-2"></i>
|
||||
<h6>Archivos</h6>
|
||||
<small class="text-muted">Estructura validada</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<i class="fas fa-check-circle fa-2x text-success mb-2"></i>
|
||||
<h6>Configuración</h6>
|
||||
<small class="text-muted">Sistema mejorado</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<i class="fas fa-check-circle fa-2x text-success mb-2"></i>
|
||||
<h6>Seguridad</h6>
|
||||
<small class="text-muted">Protecciones activas</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<i class="fas fa-check-circle fa-2x text-success mb-2"></i>
|
||||
<h6>Logging</h6>
|
||||
<small class="text-muted">Sistema centralizado</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="text-center">
|
||||
<h6>✅ Sistema Completamente Operativo</h6>
|
||||
<p class="text-muted mb-0">
|
||||
Validado el 2 de enero de 2026 -
|
||||
Todas las funcionalidades han sido revisadas y optimizadas
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="text-center">
|
||||
<hr>
|
||||
<p class="text-muted">
|
||||
<i class="fas fa-code"></i> Desarrollado por
|
||||
<a href="https://u-site.app" target="_blank" class="text-decoration-none">U-Site.app</a>
|
||||
- WhatsApp Bot Manager v1.0.0
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,38 @@
|
||||
# WhatsApp Bot Manager - .htaccess SIMPLE para resolución de errores
|
||||
# Si hay error 500, usa este archivo temporalmente
|
||||
|
||||
# Configuración básica
|
||||
Options -Indexes
|
||||
RewriteEngine On
|
||||
|
||||
# Protección básica de archivos
|
||||
<Files "config.php">
|
||||
Require all denied
|
||||
</Files>
|
||||
|
||||
<Files ".env*">
|
||||
Require all denied
|
||||
</Files>
|
||||
|
||||
# Permitir acceso a archivos principales
|
||||
<Files "*.php">
|
||||
Require all granted
|
||||
</Files>
|
||||
|
||||
<Files "*.html">
|
||||
Require all granted
|
||||
</Files>
|
||||
|
||||
<Files "*.css">
|
||||
Require all granted
|
||||
</Files>
|
||||
|
||||
<Files "*.js">
|
||||
Require all granted
|
||||
</Files>
|
||||
|
||||
# Página de inicio
|
||||
DirectoryIndex index.php index.html
|
||||
|
||||
# Charset
|
||||
AddDefaultCharset UTF-8
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Icono WhatsApp Bot Manager</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #f0f0f0;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.icon-container {
|
||||
display: inline-block;
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
.icon-title {
|
||||
margin: 10px 0;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
.download-btn {
|
||||
display: inline-block;
|
||||
padding: 8px 16px;
|
||||
background: #25D366;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
margin: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.icon-preview {
|
||||
margin: 10px 0;
|
||||
}
|
||||
h1 {
|
||||
color: #25D366;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🤖 Iconos WhatsApp Bot Manager</h1>
|
||||
<p>Haz clic derecho en cualquier icono → "Guardar imagen como..." para descargar</p>
|
||||
|
||||
<!-- Icono Principal 512x512 -->
|
||||
<div class="icon-container">
|
||||
<div class="icon-title">Icono Principal (512x512)</div>
|
||||
<div class="icon-preview">
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Fondo circular con gradiente -->
|
||||
<defs>
|
||||
<radialGradient id="bgGradient" cx="50%" cy="30%" r="70%">
|
||||
<stop offset="0%" style="stop-color:#34D366;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#25D366;stop-opacity:1" />
|
||||
</radialGradient>
|
||||
<linearGradient id="botGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#f0f0f0;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Círculo de fondo -->
|
||||
<circle cx="256" cy="256" r="240" fill="url(#bgGradient)" stroke="#1aa347" stroke-width="8"/>
|
||||
|
||||
<!-- Cabeza del robot -->
|
||||
<circle cx="256" cy="200" r="80" fill="url(#botGradient)" stroke="#25D366" stroke-width="4"/>
|
||||
|
||||
<!-- Ojos del robot -->
|
||||
<circle cx="230" cy="180" r="12" fill="#25D366"/>
|
||||
<circle cx="282" cy="180" r="12" fill="#25D366"/>
|
||||
<circle cx="230" cy="180" r="6" fill="white"/>
|
||||
<circle cx="282" cy="180" r="6" fill="white"/>
|
||||
|
||||
<!-- Antenas -->
|
||||
<line x1="220" y1="140" x2="210" y2="110" stroke="#25D366" stroke-width="6" stroke-linecap="round"/>
|
||||
<line x1="292" y1="140" x2="302" y2="110" stroke="#25D366" stroke-width="6" stroke-linecap="round"/>
|
||||
<circle cx="210" cy="110" r="8" fill="#25D366"/>
|
||||
<circle cx="302" cy="110" r="8" fill="#25D366"/>
|
||||
|
||||
<!-- Boca/pantalla -->
|
||||
<rect x="220" y="200" width="72" height="24" rx="12" fill="#25D366"/>
|
||||
<rect x="224" y="204" width="64" height="16" rx="8" fill="white"/>
|
||||
|
||||
<!-- Cuerpo del robot -->
|
||||
<rect x="200" y="280" width="112" height="120" rx="20" fill="url(#botGradient)" stroke="#25D366" stroke-width="4"/>
|
||||
|
||||
<!-- Brazos -->
|
||||
<rect x="150" y="300" width="50" height="20" rx="10" fill="url(#botGradient)" stroke="#25D366" stroke-width="3"/>
|
||||
<rect x="312" y="300" width="50" height="20" rx="10" fill="url(#botGradient)" stroke="#25D366" stroke-width="3"/>
|
||||
|
||||
<!-- Manos con símbolos de mensaje -->
|
||||
<circle cx="125" cy="310" r="18" fill="#25D366"/>
|
||||
<circle cx="387" cy="310" r="18" fill="#25D366"/>
|
||||
|
||||
<!-- Símbolos de chat en las manos -->
|
||||
<path d="M115 305 Q125 300 135 305 Q135 315 125 320 Q115 315 115 305 Z" fill="white"/>
|
||||
<path d="M377 305 Q387 300 397 305 Q397 315 387 320 Q377 315 377 305 Z" fill="white"/>
|
||||
|
||||
<!-- Panel de control en el pecho -->
|
||||
<rect x="220" y="320" width="72" height="40" rx="8" fill="#25D366"/>
|
||||
<circle cx="240" cy="335" r="6" fill="white"/>
|
||||
<circle cx="256" cy="335" r="6" fill="white"/>
|
||||
<circle cx="272" cy="335" r="6" fill="white"/>
|
||||
<rect x="230" y="345" width="52" height="6" rx="3" fill="white"/>
|
||||
|
||||
<!-- Piernas -->
|
||||
<rect x="220" y="400" width="24" height="40" rx="12" fill="url(#botGradient)" stroke="#25D366" stroke-width="3"/>
|
||||
<rect x="268" y="400" width="24" height="40" rx="12" fill="url(#botGradient)" stroke="#25D366" stroke-width="3"/>
|
||||
|
||||
<!-- Pies -->
|
||||
<ellipse cx="232" cy="450" rx="20" ry="12" fill="#25D366"/>
|
||||
<ellipse cx="280" cy="450" rx="20" ry="12" fill="#25D366"/>
|
||||
|
||||
<!-- Burbujas de chat decorativas -->
|
||||
<circle cx="100" cy="150" r="20" fill="white" fill-opacity="0.8" stroke="#25D366" stroke-width="2"/>
|
||||
<path d="M90 145 Q100 140 110 145 Q110 155 100 160 Q90 155 90 145 Z" fill="white" fill-opacity="0.8" stroke="#25D366" stroke-width="2"/>
|
||||
<circle cx="95" cy="150" r="3" fill="#25D366"/>
|
||||
<circle cx="105" cy="150" r="3" fill="#25D366"/>
|
||||
|
||||
<circle cx="420" cy="180" r="25" fill="white" fill-opacity="0.8" stroke="#25D366" stroke-width="2"/>
|
||||
<path d="M405 175 Q420 170 435 175 Q435 185 420 190 Q405 185 405 175 Z" fill="white" fill-opacity="0.8" stroke="#25D366" stroke-width="2"/>
|
||||
<circle cx="415" cy="180" r="3" fill="#25D366"/>
|
||||
<circle cx="425" cy="180" r="3" fill="#25D366"/>
|
||||
|
||||
<!-- Texto W -->
|
||||
<text x="256" y="490" text-anchor="middle" font-family="Arial Black, sans-serif" font-size="24" fill="white" font-weight="bold">BOT</text>
|
||||
</svg>
|
||||
</div>
|
||||
<a href="data:image/svg+xml;charset=utf-8,%3Csvg width='512' height='512' viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3CradialGradient id='bgGradient' cx='50%25' cy='30%25' r='70%25'%3E%3Cstop offset='0%25' style='stop-color:%2334D366;stop-opacity:1' /%3E%3Cstop offset='100%25' style='stop-color:%2325D366;stop-opacity:1' /%3E%3C/radialGradient%3E%3ClinearGradient id='botGradient' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%23ffffff;stop-opacity:1' /%3E%3Cstop offset='100%25' style='stop-color:%23f0f0f0;stop-opacity:1' /%3E%3C/linearGradient%3E%3C/defs%3E%3Ccircle cx='256' cy='256' r='240' fill='url(%23bgGradient)' stroke='%231aa347' stroke-width='8'/%3E%3Ccircle cx='256' cy='200' r='80' fill='url(%23botGradient)' stroke='%2325D366' stroke-width='4'/%3E%3Ccircle cx='230' cy='180' r='12' fill='%2325D366'/%3E%3Ccircle cx='282' cy='180' r='12' fill='%2325D366'/%3E%3Ccircle cx='230' cy='180' r='6' fill='white'/%3E%3Ccircle cx='282' cy='180' r='6' fill='white'/%3E%3Cline x1='220' y1='140' x2='210' y2='110' stroke='%2325D366' stroke-width='6' stroke-linecap='round'/%3E%3Cline x1='292' y1='140' x2='302' y2='110' stroke='%2325D366' stroke-width='6' stroke-linecap='round'/%3E%3Ccircle cx='210' cy='110' r='8' fill='%2325D366'/%3E%3Ccircle cx='302' cy='110' r='8' fill='%2325D366'/%3E%3Crect x='220' y='200' width='72' height='24' rx='12' fill='%2325D366'/%3E%3Crect x='224' y='204' width='64' height='16' rx='8' fill='white'/%3E%3Crect x='200' y='280' width='112' height='120' rx='20' fill='url(%23botGradient)' stroke='%2325D366' stroke-width='4'/%3E%3Crect x='150' y='300' width='50' height='20' rx='10' fill='url(%23botGradient)' stroke='%2325D366' stroke-width='3'/%3E%3Crect x='312' y='300' width='50' height='20' rx='10' fill='url(%23botGradient)' stroke='%2325D366' stroke-width='3'/%3E%3Ccircle cx='125' cy='310' r='18' fill='%2325D366'/%3E%3Ccircle cx='387' cy='310' r='18' fill='%2325D366'/%3E%3Cpath d='M115 305 Q125 300 135 305 Q135 315 125 320 Q115 315 115 305 Z' fill='white'/%3E%3Cpath d='M377 305 Q387 300 397 305 Q397 315 387 320 Q377 315 377 305 Z' fill='white'/%3E%3Crect x='220' y='320' width='72' height='40' rx='8' fill='%2325D366'/%3E%3Ccircle cx='240' cy='335' r='6' fill='white'/%3E%3Ccircle cx='256' cy='335' r='6' fill='white'/%3E%3Ccircle cx='272' cy='335' r='6' fill='white'/%3E%3Crect x='230' y='345' width='52' height='6' rx='3' fill='white'/%3E%3Crect x='220' y='400' width='24' height='40' rx='12' fill='url(%23botGradient)' stroke='%2325D366' stroke-width='3'/%3E%3Crect x='268' y='400' width='24' height='40' rx='12' fill='url(%23botGradient)' stroke='%2325D366' stroke-width='3'/%3E%3Cellipse cx='232' cy='450' rx='20' ry='12' fill='%2325D366'/%3E%3Cellipse cx='280' cy='450' rx='20' ry='12' fill='%2325D366'/%3E%3Ccircle cx='100' cy='150' r='20' fill='white' fill-opacity='0.8' stroke='%2325D366' stroke-width='2'/%3E%3Cpath d='M90 145 Q100 140 110 145 Q110 155 100 160 Q90 155 90 145 Z' fill='white' fill-opacity='0.8' stroke='%2325D366' stroke-width='2'/%3E%3Ccircle cx='95' cy='150' r='3' fill='%2325D366'/%3E%3Ccircle cx='105' cy='150' r='3' fill='%2325D366'/%3E%3Ccircle cx='420' cy='180' r='25' fill='white' fill-opacity='0.8' stroke='%2325D366' stroke-width='2'/%3E%3Cpath d='M405 175 Q420 170 435 175 Q435 185 420 190 Q405 185 405 175 Z' fill='white' fill-opacity='0.8' stroke='%2325D366' stroke-width='2'/%3E%3Ccircle cx='415' cy='180' r='3' fill='%2325D366'/%3E%3Ccircle cx='425' cy='180' r='3' fill='%2325D366'/%3E%3Ctext x='256' y='490' text-anchor='middle' font-family='Arial Black, sans-serif' font-size='24' fill='white' font-weight='bold'%3EBOT%3C/text%3E%3C/svg%3E" download="whatsapp-bot-icon-512.svg" class="download-btn">Descargar SVG</a>
|
||||
</div>
|
||||
|
||||
<!-- Icono Mediano 256x256 -->
|
||||
<div class="icon-container">
|
||||
<div class="icon-title">Icono Mediano (256x256)</div>
|
||||
<div class="icon-preview">
|
||||
<svg width="256" height="256" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="bgGradient2" cx="50%" cy="30%" r="70%">
|
||||
<stop offset="0%" style="stop-color:#34D366;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#25D366;stop-opacity:1" />
|
||||
</radialGradient>
|
||||
<linearGradient id="botGradient2" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#f0f0f0;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle cx="256" cy="256" r="240" fill="url(#bgGradient2)" stroke="#1aa347" stroke-width="8"/>
|
||||
<circle cx="256" cy="200" r="80" fill="url(#botGradient2)" stroke="#25D366" stroke-width="4"/>
|
||||
<circle cx="230" cy="180" r="12" fill="#25D366"/>
|
||||
<circle cx="282" cy="180" r="12" fill="#25D366"/>
|
||||
<circle cx="230" cy="180" r="6" fill="white"/>
|
||||
<circle cx="282" cy="180" r="6" fill="white"/>
|
||||
<line x1="220" y1="140" x2="210" y2="110" stroke="#25D366" stroke-width="6" stroke-linecap="round"/>
|
||||
<line x1="292" y1="140" x2="302" y2="110" stroke="#25D366" stroke-width="6" stroke-linecap="round"/>
|
||||
<circle cx="210" cy="110" r="8" fill="#25D366"/>
|
||||
<circle cx="302" cy="110" r="8" fill="#25D366"/>
|
||||
<rect x="220" y="200" width="72" height="24" rx="12" fill="#25D366"/>
|
||||
<rect x="224" y="204" width="64" height="16" rx="8" fill="white"/>
|
||||
<rect x="200" y="280" width="112" height="120" rx="20" fill="url(#botGradient2)" stroke="#25D366" stroke-width="4"/>
|
||||
<rect x="220" y="320" width="72" height="40" rx="8" fill="#25D366"/>
|
||||
<circle cx="240" cy="335" r="6" fill="white"/>
|
||||
<circle cx="256" cy="335" r="6" fill="white"/>
|
||||
<circle cx="272" cy="335" r="6" fill="white"/>
|
||||
<text x="256" y="490" text-anchor="middle" font-family="Arial Black, sans-serif" font-size="24" fill="white" font-weight="bold">BOT</text>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Icono Pequeño 128x128 -->
|
||||
<div class="icon-container">
|
||||
<div class="icon-title">Icono Pequeño (128x128)</div>
|
||||
<div class="icon-preview">
|
||||
<svg width="128" height="128" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="bgGradient3" cx="50%" cy="30%" r="70%">
|
||||
<stop offset="0%" style="stop-color:#34D366;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#25D366;stop-opacity:1" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<circle cx="256" cy="256" r="240" fill="url(#bgGradient3)" stroke="#1aa347" stroke-width="12"/>
|
||||
<circle cx="256" cy="200" r="80" fill="white" stroke="#25D366" stroke-width="6"/>
|
||||
<circle cx="230" cy="180" r="15" fill="#25D366"/>
|
||||
<circle cx="282" cy="180" r="15" fill="#25D366"/>
|
||||
<circle cx="230" cy="180" r="8" fill="white"/>
|
||||
<circle cx="282" cy="180" r="8" fill="white"/>
|
||||
<rect x="220" y="200" width="72" height="24" rx="12" fill="#25D366"/>
|
||||
<rect x="200" y="280" width="112" height="120" rx="20" fill="white" stroke="#25D366" stroke-width="6"/>
|
||||
<rect x="220" y="320" width="72" height="40" rx="8" fill="#25D366"/>
|
||||
<text x="256" y="480" text-anchor="middle" font-family="Arial Black, sans-serif" font-size="36" fill="white" font-weight="bold">🤖</text>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Favicon 64x64 -->
|
||||
<div class="icon-container">
|
||||
<div class="icon-title">Favicon (64x64)</div>
|
||||
<div class="icon-preview">
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="32" cy="32" r="28" fill="#25D366" stroke="#1aa347" stroke-width="2"/>
|
||||
<circle cx="32" cy="24" r="12" fill="white" stroke="#25D366" stroke-width="1"/>
|
||||
<circle cx="28" cy="22" r="2" fill="#25D366"/>
|
||||
<circle cx="36" cy="22" r="2" fill="#25D366"/>
|
||||
<rect x="26" y="25" width="12" height="4" rx="2" fill="#25D366"/>
|
||||
<rect x="24" y="36" width="16" height="16" rx="3" fill="white" stroke="#25D366" stroke-width="1"/>
|
||||
<rect x="26" y="40" width="12" height="6" rx="2" fill="#25D366"/>
|
||||
<circle cx="29" cy="43" r="1" fill="white"/>
|
||||
<circle cx="32" cy="43" r="1" fill="white"/>
|
||||
<circle cx="35" cy="43" r="1" fill="white"/>
|
||||
</svg>
|
||||
</div>
|
||||
<a href="data:image/svg+xml;charset=utf-8,%3Csvg width='64' height='64' viewBox='0 0 64 64' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='32' cy='32' r='28' fill='%2325D366' stroke='%231aa347' stroke-width='2'/%3E%3Ccircle cx='32' cy='24' r='12' fill='white' stroke='%2325D366' stroke-width='1'/%3E%3Ccircle cx='28' cy='22' r='2' fill='%2325D366'/%3E%3Ccircle cx='36' cy='22' r='2' fill='%2325D366'/%3E%3Crect x='26' y='25' width='12' height='4' rx='2' fill='%2325D366'/%3E%3Crect x='24' y='36' width='16' height='16' rx='3' fill='white' stroke='%2325D366' stroke-width='1'/%3E%3Crect x='26' y='40' width='12' height='6' rx='2' fill='%2325D366'/%3E%3Ccircle cx='29' cy='43' r='1' fill='white'/%3E%3Ccircle cx='32' cy='43' r='1' fill='white'/%3E%3Ccircle cx='35' cy='43' r='1' fill='white'/%3E%3C/svg%3E" download="favicon.svg" class="download-btn">Descargar Favicon</a>
|
||||
</div>
|
||||
|
||||
<!-- Icono Alternativo Circular -->
|
||||
<div class="icon-container">
|
||||
<div class="icon-title">Alternativo Circular (512x512)</div>
|
||||
<div class="icon-preview">
|
||||
<svg width="256" height="256" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="bgGradient4" cx="50%" cy="30%" r="70%">
|
||||
<stop offset="0%" style="stop-color:#34D366;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1aa347;stop-opacity:1" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<circle cx="256" cy="256" r="240" fill="url(#bgGradient4)"/>
|
||||
|
||||
<!-- Chat bubble grande -->
|
||||
<path d="M180 180 Q280 150 380 180 Q380 280 280 310 Q250 310 220 300 L180 320 Q180 280 180 180 Z" fill="white" stroke="#25D366" stroke-width="6"/>
|
||||
|
||||
<!-- Texto "BOT" dentro de la burbuja -->
|
||||
<text x="280" y="250" text-anchor="middle" font-family="Arial Black, sans-serif" font-size="48" fill="#25D366" font-weight="bold">BOT</text>
|
||||
|
||||
<!-- Puntos de chat -->
|
||||
<circle cx="240" cy="280" r="8" fill="#25D366"/>
|
||||
<circle cx="270" cy="280" r="8" fill="#25D366"/>
|
||||
<circle cx="300" cy="280" r="8" fill="#25D366"/>
|
||||
|
||||
<!-- Icono de robot pequeño -->
|
||||
<circle cx="350" cy="200" r="20" fill="#25D366"/>
|
||||
<circle cx="345" cy="195" r="4" fill="white"/>
|
||||
<circle cx="355" cy="195" r="4" fill="white"/>
|
||||
<rect x="342" y="205" width="16" height="8" rx="4" fill="white"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 40px; padding: 20px; background: white; border-radius: 10px; max-width: 600px; margin-left: auto; margin-right: auto;">
|
||||
<h2>📋 Cómo usar estos iconos:</h2>
|
||||
<ul style="text-align: left;">
|
||||
<li><strong>Facebook/Meta App:</strong> Usa el icono de 512x512</li>
|
||||
<li><strong>Favicon del sitio web:</strong> Usa el favicon de 64x64</li>
|
||||
<li><strong>Documentación/Marketing:</strong> Cualquier tamaño según necesites</li>
|
||||
<li><strong>Formato:</strong> Los iconos son SVG (escalables sin pérdida de calidad)</li>
|
||||
<li><strong>Colores:</strong> Verde WhatsApp oficial (#25D366)</li>
|
||||
</ul>
|
||||
|
||||
<h3>💡 Para convertir a PNG:</h3>
|
||||
<p>Puedes usar herramientas online como <strong>convertio.co</strong> o <strong>cloudconvert.com</strong> para convertir SVG a PNG en el tamaño que necesites.</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
-549
@@ -1,549 +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</title>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="./assets/css/styles.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Sidebar -->
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h4><i class="fas fa-robot"></i> WhatsApp Bot</h4>
|
||||
</div>
|
||||
<ul class="sidebar-menu">
|
||||
<li><a href="#dashboard" class="nav-link active" data-tab="dashboard"><i class="fas fa-tachometer-alt"></i> Dashboard</a></li>
|
||||
<li><a href="#conversations" class="nav-link" data-tab="conversations"><i class="fas fa-comments"></i> Conversaciones</a></li>
|
||||
<li><a href="#users" class="nav-link" data-tab="users"><i class="fas fa-users"></i> Usuarios</a></li>
|
||||
<li><a href="#menus" class="nav-link" data-tab="menus"><i class="fas fa-list"></i> Menús</a></li>
|
||||
<li><a href="#messages" class="nav-link" data-tab="messages"><i class="fas fa-paper-plane"></i> Enviar Mensaje</a></li>
|
||||
<li><a href="#templates" class="nav-link" data-tab="templates"><i class="fas fa-file-text"></i> Plantillas</a></li>
|
||||
<li><a href="#autoresponses" class="nav-link" data-tab="autoresponses"><i class="fas fa-robot"></i> Respuestas Auto</a></li>
|
||||
<li><a href="#settings" class="nav-link" data-tab="settings"><i class="fas fa-cog"></i> Configuración</a></li>
|
||||
<li><a href="#logs" class="nav-link" data-tab="logs"><i class="fas fa-file-alt"></i> Logs</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<!-- Header -->
|
||||
<header class="content-header">
|
||||
<h1><i class="fas fa-robot"></i> Sistema de Gestión WhatsApp Bot</h1>
|
||||
<div class="header-actions">
|
||||
<span class="status-indicator online">
|
||||
<i class="fas fa-circle"></i> En línea
|
||||
</span>
|
||||
<button class="btn btn-primary" onclick="refreshData()">
|
||||
<i class="fas fa-sync-alt"></i> Actualizar
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Dashboard Tab -->
|
||||
<div id="dashboard" class="tab-content active">
|
||||
<div class="row mb-4">
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card card-stat bg-primary text-white">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
<h6 class="card-stat-title">Total Usuarios</h6>
|
||||
<h3 class="card-stat-number" id="total-users">-</h3>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-users fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card card-stat bg-success text-white">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
<h6 class="card-stat-title">Mensajes Hoy</h6>
|
||||
<h3 class="card-stat-number" id="messages-today">-</h3>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-comment fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card card-stat bg-info text-white">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
<h6 class="card-stat-title">Usuarios Activos</h6>
|
||||
<h3 class="card-stat-number" id="active-users">-</h3>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-user-check fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card card-stat bg-warning text-white">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
<h6 class="card-stat-title">Total Mensajes</h6>
|
||||
<h3 class="card-stat-number" id="total-messages">-</h3>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-envelope fa-2x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-chart-line"></i> Actividad Reciente</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="messagesChart" height="100"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-clock"></i> Últimos Mensajes</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="recent-messages" class="list-group">
|
||||
<!-- Contenido dinámico -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conversations Tab -->
|
||||
<div id="conversations" class="tab-content">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5><i class="fas fa-comments"></i> Conversaciones</h5>
|
||||
<div>
|
||||
<input type="text" class="form-control" placeholder="Buscar conversaciones..." id="search-conversations">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Usuario</th>
|
||||
<th>Último Mensaje</th>
|
||||
<th>Tipo</th>
|
||||
<th>Estado</th>
|
||||
<th>Fecha</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="conversations-table">
|
||||
<!-- Contenido dinámico -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Users Tab -->
|
||||
<div id="users" class="tab-content">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5><i class="fas fa-users"></i> Gestión de Usuarios</h5>
|
||||
<button class="btn btn-primary" onclick="exportUsers()">
|
||||
<i class="fas fa-download"></i> Exportar
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Teléfono</th>
|
||||
<th>Nombre</th>
|
||||
<th>Estado</th>
|
||||
<th>Menú Actual</th>
|
||||
<th>Registro</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="users-table">
|
||||
<!-- Contenido dinámico -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menus Tab -->
|
||||
<div id="menus" class="tab-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5><i class="fas fa-list"></i> Menús Configurados</h5>
|
||||
<button class="btn btn-primary" onclick="showCreateMenuModal()">
|
||||
<i class="fas fa-plus"></i> Nuevo Menú
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="menus-tree">
|
||||
<!-- Árbol de menús -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-edit"></i> Editor de Menú</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="menu-form">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nombre del Menú</label>
|
||||
<input type="text" class="form-control" id="menu-name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Título</label>
|
||||
<input type="text" class="form-control" id="menu-title" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Descripción</label>
|
||||
<textarea class="form-control" id="menu-description" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Menú Padre</label>
|
||||
<select class="form-control" id="menu-parent">
|
||||
<option value="">Sin padre (Menú raíz)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" class="form-check-input" id="menu-active" checked>
|
||||
<label class="form-check-label">Activo</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> Guardar Menú
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Messages Tab -->
|
||||
<div id="messages" class="tab-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-paper-plane"></i> Enviar Mensaje</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="send-message-form">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Destinatario</label>
|
||||
<select class="form-control" id="message-recipient" required>
|
||||
<option value="">Seleccionar usuario...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Tipo de Mensaje</label>
|
||||
<select class="form-control" id="message-type" required>
|
||||
<option value="text">Texto</option>
|
||||
<option value="template">Plantilla</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3" id="message-text-group">
|
||||
<label class="form-label">Mensaje</label>
|
||||
<textarea class="form-control" id="message-text" rows="4" placeholder="Escribe tu mensaje aquí..."></textarea>
|
||||
</div>
|
||||
<div class="mb-3" id="template-group" style="display: none;">
|
||||
<label class="form-label">Plantilla</label>
|
||||
<select class="form-control" id="message-template">
|
||||
<option value="">Seleccionar plantilla...</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-paper-plane"></i> Enviar Mensaje
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-broadcast-tower"></i> Mensaje Masivo</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="broadcast-form">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Filtro de Usuarios</label>
|
||||
<select class="form-control" id="broadcast-filter">
|
||||
<option value="all">Todos los usuarios</option>
|
||||
<option value="active">Solo usuarios activos</option>
|
||||
<option value="recent">Usuarios recientes (30 días)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Mensaje</label>
|
||||
<textarea class="form-control" id="broadcast-message" rows="4" placeholder="Mensaje para envío masivo..."></textarea>
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Cuidado:</strong> Este mensaje se enviará a múltiples usuarios.
|
||||
</div>
|
||||
<button type="submit" class="btn btn-warning">
|
||||
<i class="fas fa-broadcast-tower"></i> Envío Masivo
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Templates Tab -->
|
||||
<div id="templates" class="tab-content">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5><i class="fas fa-file-text"></i> Plantillas de Mensaje</h5>
|
||||
<button class="btn btn-primary" onclick="showCreateTemplateModal()">
|
||||
<i class="fas fa-plus"></i> Nueva Plantilla
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre</th>
|
||||
<th>Plantilla WhatsApp</th>
|
||||
<th>Idioma</th>
|
||||
<th>Categoría</th>
|
||||
<th>Estado</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="templates-table">
|
||||
<!-- Contenido dinámico -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Auto Responses Tab -->
|
||||
<div id="autoresponses" class="tab-content">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5><i class="fas fa-robot"></i> Respuestas Automáticas</h5>
|
||||
<button class="btn btn-primary" onclick="showCreateAutoResponseModal()">
|
||||
<i class="fas fa-plus"></i> Nueva Respuesta
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tipo</th>
|
||||
<th>Palabra Clave</th>
|
||||
<th>Respuesta</th>
|
||||
<th>Estado</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="autoresponses-table">
|
||||
<!-- Contenido dinámico -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings Tab -->
|
||||
<div id="settings" class="tab-content">
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-cog"></i> Configuración del Sistema</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="settings-form">
|
||||
<div class="mb-4">
|
||||
<h6 class="text-muted">WhatsApp Business API</h6>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Token de Acceso</label>
|
||||
<input type="password" class="form-control" id="whatsapp-token" placeholder="Token de WhatsApp Business API">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Phone Number ID</label>
|
||||
<input type="text" class="form-control" id="phone-number-id" placeholder="ID del número de teléfono">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Webhook Verify Token</label>
|
||||
<input type="text" class="form-control" id="webhook-token" placeholder="Token de verificación del webhook">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6 class="text-muted">Configuración General</h6>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nombre de la Empresa</label>
|
||||
<input type="text" class="form-control" id="business-name" placeholder="Nombre de tu empresa">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Mensaje de Bienvenida</label>
|
||||
<textarea class="form-control" id="welcome-message" rows="3" placeholder="Mensaje que se envía a usuarios nuevos"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> Guardar Configuración
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-info-circle"></i> Información del Sistema</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-sm-6">Versión:</dt>
|
||||
<dd class="col-sm-6">1.0.0</dd>
|
||||
|
||||
<dt class="col-sm-6">PHP:</dt>
|
||||
<dd class="col-sm-6">8.0+</dd>
|
||||
|
||||
<dt class="col-sm-6">Base de Datos:</dt>
|
||||
<dd class="col-sm-6">MySQL</dd>
|
||||
|
||||
<dt class="col-sm-6">Webhook URL:</dt>
|
||||
<dd class="col-sm-6">
|
||||
<code>http://localhost/migrador/api/webhook.php</code>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs Tab -->
|
||||
<div id="logs" class="tab-content">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5><i class="fas fa-file-alt"></i> Logs del Sistema</h5>
|
||||
<div>
|
||||
<button class="btn btn-outline-primary" onclick="refreshLogs()">
|
||||
<i class="fas fa-sync-alt"></i> Actualizar
|
||||
</button>
|
||||
<button class="btn btn-outline-danger" onclick="clearLogs()">
|
||||
<i class="fas fa-trash"></i> Limpiar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Fecha</th>
|
||||
<th>IP</th>
|
||||
<th>Estado</th>
|
||||
<th>Request</th>
|
||||
<th>Response</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="logs-table">
|
||||
<!-- Contenido dinámico -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Modals -->
|
||||
<!-- Create Menu Option Modal -->
|
||||
<div class="modal fade" id="createMenuOptionModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Agregar Opción de Menú</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="menu-option-form">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Número de Opción</label>
|
||||
<input type="number" class="form-control" id="option-number" min="0" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Texto de la Opción</label>
|
||||
<input type="text" class="form-control" id="option-text" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Tipo de Acción</label>
|
||||
<select class="form-control" id="action-type" required>
|
||||
<option value="menu">Navegar a otro menú</option>
|
||||
<option value="message">Enviar mensaje</option>
|
||||
<option value="api_call">Llamada API</option>
|
||||
<option value="end">Finalizar conversación</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Valor de Acción</label>
|
||||
<textarea class="form-control" id="action-value" rows="3" placeholder="Contenido del mensaje o nombre del menú..."></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveMenuOption()">Guardar Opción</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="./assets/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -13,6 +13,12 @@ if (!file_exists($installationLockFile)) {
|
||||
|
||||
require_once 'config/config.php';
|
||||
|
||||
// Verificar autenticación - redirigir al login si no está logueado
|
||||
if (!isUserLoggedIn()) {
|
||||
header('Location: login.php');
|
||||
exit('Acceso denegado. <a href="login.php">Iniciar sesión</a>');
|
||||
}
|
||||
|
||||
// Verificar conexión a la base de datos
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
@@ -30,6 +36,7 @@ try {
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="./assets/css/styles.css" rel="stylesheet">
|
||||
<link href="./assets/css/diagnostic.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Sidebar -->
|
||||
@@ -47,6 +54,8 @@ try {
|
||||
<li><a href="#autoresponses" class="nav-link" data-tab="autoresponses"><i class="fas fa-robot"></i> Respuestas Auto</a></li>
|
||||
<li><a href="#settings" class="nav-link" data-tab="settings"><i class="fas fa-cog"></i> Configuración</a></li>
|
||||
<li><a href="#logs" class="nav-link" data-tab="logs"><i class="fas fa-file-alt"></i> Logs</a></li>
|
||||
<li class="sidebar-divider"></li>
|
||||
<li><a href="logout.php" class="nav-link logout-link" onclick="return confirm('¿Está seguro que desea cerrar sesión?')"><i class="fas fa-sign-out-alt"></i> Cerrar Sesión</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -54,14 +63,23 @@ try {
|
||||
<main class="main-content">
|
||||
<!-- Header -->
|
||||
<header class="content-header">
|
||||
<div>
|
||||
<h1><i class="fas fa-robot"></i> Sistema de Gestión WhatsApp Bot</h1>
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-user"></i> Conectado como <strong><?= htmlspecialchars(ADMIN_USERNAME) ?></strong>
|
||||
| <i class="fas fa-clock"></i> Sesión desde: <?= date('H:i:s', $_SESSION['login_time'] ?? time()) ?>
|
||||
</small>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<span class="status-indicator online">
|
||||
<i class="fas fa-circle"></i> En línea
|
||||
</span>
|
||||
<button class="btn btn-primary" onclick="refreshData()">
|
||||
<button class="btn btn-primary me-2" onclick="refreshData()">
|
||||
<i class="fas fa-sync-alt"></i> Actualizar
|
||||
</button>
|
||||
<a href="logout.php" class="btn btn-outline-danger" onclick="return confirm('¿Está seguro que desea cerrar sesión?')">
|
||||
<i class="fas fa-sign-out-alt"></i> Cerrar Sesión
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -291,14 +309,28 @@ try {
|
||||
<div class="card-body">
|
||||
<form id="send-message-form">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Tipo de Destinatario</label>
|
||||
<select class="form-control" id="recipient-type" onchange="toggleRecipientType()">
|
||||
<option value="existing">Usuario Registrado</option>
|
||||
<option value="manual">Número Manual (Solo Plantillas)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3" id="existing-recipient-group">
|
||||
<label class="form-label">Destinatario</label>
|
||||
<select class="form-control" id="message-recipient" required>
|
||||
<select class="form-control" id="message-recipient">
|
||||
<option value="">Seleccionar usuario...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3" id="manual-recipient-group" style="display: none;">
|
||||
<label class="form-label">Número de WhatsApp</label>
|
||||
<input type="text" class="form-control" id="manual-recipient" placeholder="+57XXXXXXXXX">
|
||||
<small class="form-text text-warning">
|
||||
⚠️ Solo se pueden enviar plantillas aprobadas a números nuevos
|
||||
</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Tipo de Mensaje</label>
|
||||
<select class="form-control" id="message-type" required>
|
||||
<select class="form-control" id="message-type" onchange="toggleMessageType()">
|
||||
<option value="text">Texto</option>
|
||||
<option value="template">Plantilla</option>
|
||||
</select>
|
||||
@@ -358,7 +390,7 @@ try {
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5><i class="fas fa-file-text"></i> Plantillas de Mensaje</h5>
|
||||
<button class="btn btn-primary" onclick="showCreateTemplateModal()">
|
||||
<button class="btn btn-primary" id="btn-new-template" onclick="showCreateTemplateModal()">
|
||||
<i class="fas fa-plus"></i> Nueva Plantilla
|
||||
</button>
|
||||
</div>
|
||||
@@ -460,6 +492,30 @@ try {
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<!-- Panel de Diagnóstico -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5><i class="fas fa-stethoscope"></i> Diagnóstico</h5>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="runDiagnostic()">
|
||||
<i class="fas fa-play"></i> Ejecutar
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="diagnostic-results">
|
||||
<p class="text-muted">Haz clic en "Ejecutar" para verificar el estado del sistema</p>
|
||||
</div>
|
||||
<div class="mt-3" id="diagnostic-actions" style="display: none;">
|
||||
<a href="approve_templates.html" class="btn btn-success btn-sm" target="_blank">
|
||||
🔧 Solucionar Problema de Plantillas
|
||||
</a>
|
||||
<a href="GUIA_ERROR_PLANTILLAS.md" class="btn btn-info btn-sm" target="_blank">
|
||||
📋 Ver Guía Completa
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Información del Sistema -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-info-circle"></i> Información del Sistema</h5>
|
||||
@@ -522,9 +578,164 @@ try {
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Modal - Crear Plantilla -->
|
||||
<div class="modal fade" id="createTemplateModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">📝 Nueva Plantilla de WhatsApp</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="template-form">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nombre descriptivo *</label>
|
||||
<input type="text" class="form-control" id="template-name" required placeholder="ej: Bienvenida Cliente">
|
||||
<small class="form-text text-muted">Nombre interno para identificar la plantilla</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nombre en WhatsApp *</label>
|
||||
<input type="text" class="form-control" id="template-whatsapp-name" required placeholder="ej: welcome_customer">
|
||||
<small class="form-text text-muted">Debe coincidir exactamente con el nombre en WhatsApp Business Manager</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Idioma</label>
|
||||
<select class="form-control" id="template-language">
|
||||
<option value="es">Español</option>
|
||||
<option value="en">English</option>
|
||||
<option value="pt">Português</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Categoría</label>
|
||||
<select class="form-control" id="template-category">
|
||||
<option value="utility">Utilidad</option>
|
||||
<option value="marketing">Marketing</option>
|
||||
<option value="authentication">Autenticación</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="alert alert-info">
|
||||
<strong>⚠️ Importante:</strong> La plantilla debe ser creada y aprobada primero en WhatsApp Business Manager.
|
||||
Este formulario solo registra plantillas ya existentes en WhatsApp.
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button type="button" class="btn btn-primary" onclick="app.saveTemplate()">Guardar Plantilla</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal de Crear Menú -->
|
||||
<div class="modal fade" id="createMenuModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">📋 Nuevo Menú</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="menu-form">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nombre del Menú *</label>
|
||||
<input type="text" class="form-control" id="menu-name" required placeholder="ej: Menú Principal">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Descripción</label>
|
||||
<textarea class="form-control" id="menu-description" rows="3" placeholder="Descripción del menú (opcional)"></textarea>
|
||||
</div>
|
||||
<div class="alert alert-info">
|
||||
<strong>ℹ️ Información:</strong> Después de crear el menú, podrás agregar opciones y configurar las respuestas.
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveNewMenu()">Crear Menú</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="./assets/js/app.js"></script>
|
||||
<script src="./assets/js/app_simple.js"></script>
|
||||
<script>
|
||||
// Función de diagnóstico mejorada
|
||||
function runDiagnostic() {
|
||||
const resultsDiv = document.getElementById('diagnostic-results');
|
||||
const actionsDiv = document.getElementById('diagnostic-actions');
|
||||
resultsDiv.innerHTML = '<p class="text-muted"><i class="fas fa-spinner fa-spin"></i> Ejecutando diagnóstico...</p>';
|
||||
|
||||
fetch('/api/diagnose_whatsapp.php')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
let html = '<div class="diagnostic-report">';
|
||||
|
||||
// Configuración
|
||||
html += `<div class="mb-3">
|
||||
<h6><i class="fas fa-cog"></i> Configuración WhatsApp</h6>
|
||||
<div class="alert alert-${data.config.status === 'complete' ? 'success' : 'warning'}">
|
||||
${data.config.message} (${data.config.completion}% completa)
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// Plantillas
|
||||
const templateIssues = data.templates.approved === 0 && data.templates.pending > 0;
|
||||
html += `<div class="mb-3">
|
||||
<h6><i class="fas fa-file-alt"></i> Estado de Plantillas</h6>
|
||||
<div class="alert alert-${templateIssues ? 'danger' : 'success'}">
|
||||
${data.templates.message}
|
||||
<br><small>Aprobadas: ${data.templates.approved} | Pendientes: ${data.templates.pending}</small>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// API
|
||||
html += `<div class="mb-3">
|
||||
<h6><i class="fas fa-plug"></i> Conectividad API</h6>
|
||||
<div class="alert alert-${data.api.status === 'ready' ? 'success' : 'warning'}">
|
||||
${data.api.message}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// Recomendaciones
|
||||
if (data.recommendations && data.recommendations.length > 0) {
|
||||
html += `<div class="mb-3">
|
||||
<h6><i class="fas fa-lightbulb"></i> Recomendaciones</h6>
|
||||
<ul class="list-unstyled">`;
|
||||
|
||||
data.recommendations.forEach(rec => {
|
||||
html += `<li class="mb-1"><i class="fas fa-arrow-right text-primary"></i> ${rec}</li>`;
|
||||
});
|
||||
|
||||
html += `</ul></div>`;
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
resultsDiv.innerHTML = html;
|
||||
|
||||
// Mostrar botones de acción si hay problemas con plantillas
|
||||
if (templateIssues) {
|
||||
actionsDiv.style.display = 'block';
|
||||
} else {
|
||||
actionsDiv.style.display = 'none';
|
||||
}
|
||||
|
||||
} else {
|
||||
resultsDiv.innerHTML = `<div class="alert alert-danger">Error: ${data.message}</div>`;
|
||||
actionsDiv.style.display = 'none';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
resultsDiv.innerHTML = '<div class="alert alert-danger">Error de conectividad</div>';
|
||||
actionsDiv.style.display = 'none';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,194 @@
|
||||
<?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="settings" 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="messages-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-messages" 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>
|
||||
|
||||
<!-- Settings Tab -->
|
||||
<div id="settings" 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="settings-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>
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
/**
|
||||
* 🗄️ INSTALADOR DE BASE DE DATOS - WhatsApp Bot
|
||||
* Crea todas las tablas necesarias y corrige inconsistencias
|
||||
*/
|
||||
|
||||
// Headers para mostrar errores
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
set_time_limit(120); // 2 minutos máximo
|
||||
|
||||
echo "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
|
||||
echo "<title>🗄️ Instalador BD</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>🗄️ INSTALADOR DE BASE DE DATOS</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;
|
||||
}
|
||||
|
||||
// Verificar que existe el archivo schema.sql
|
||||
if (!file_exists('database/schema.sql')) {
|
||||
echo "<div class='error'>❌ CRÍTICO: database/schema.sql no encontrado</div>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Conectar a la base de datos
|
||||
try {
|
||||
$dsn = "mysql:host=" . DB_HOST . ";port=" . DB_PORT . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
|
||||
$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'>✅ Conectado a la base de datos</div>";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
echo "<div class='error'>❌ Error de conexión: " . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
exit;
|
||||
}
|
||||
|
||||
// Leer archivo SQL
|
||||
$sql_content = file_get_contents('database/schema.sql');
|
||||
if (!$sql_content) {
|
||||
echo "<div class='error'>❌ No se pudo leer database/schema.sql</div>";
|
||||
exit;
|
||||
}
|
||||
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>📋 EJECUTANDO SCHEMA SQL...</h2>";
|
||||
|
||||
// Separar comandos SQL
|
||||
$commands = array_filter(
|
||||
array_map('trim', explode(';', $sql_content)),
|
||||
function($cmd) {
|
||||
return !empty($cmd) && !preg_match('/^\s*--/', $cmd);
|
||||
}
|
||||
);
|
||||
|
||||
$success_count = 0;
|
||||
$error_count = 0;
|
||||
|
||||
foreach ($commands as $i => $command) {
|
||||
if (empty(trim($command))) continue;
|
||||
|
||||
// Mostrar progreso cada 5 comandos
|
||||
if ($i % 5 === 0) {
|
||||
echo "<div class='warning'>Procesando comando " . ($i + 1) . "/" . count($commands) . "...</div>";
|
||||
flush();
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo->exec($command);
|
||||
$success_count++;
|
||||
|
||||
// Solo mostrar detalles de CREATE TABLE y INSERT importantes
|
||||
if (preg_match('/CREATE TABLE (\w+)/', $command, $matches)) {
|
||||
echo "<div class='ok'>✅ Tabla '{$matches[1]}' creada</div>";
|
||||
} elseif (preg_match('/INSERT INTO (\w+)/', $command, $matches) && $i < 10) {
|
||||
echo "<div class='ok'>✅ Datos insertados en '{$matches[1]}'</div>";
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
$error_count++;
|
||||
|
||||
// Si es error de tabla existente, es OK
|
||||
if (strpos($e->getMessage(), 'already exists') !== false) {
|
||||
if (preg_match('/CREATE TABLE (\w+)/', $command, $matches)) {
|
||||
echo "<div class='warning'>⚠️ Tabla '{$matches[1]}' ya existe</div>";
|
||||
}
|
||||
} else {
|
||||
echo "<div class='error'>❌ Error en comando " . ($i + 1) . ": " . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
if (strlen($command) < 200) {
|
||||
echo "<pre>" . htmlspecialchars($command) . "</pre>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo "<div class='ok'>📊 Comandos exitosos: $success_count</div>";
|
||||
echo "<div class='error'>📊 Errores: $error_count</div>";
|
||||
|
||||
echo "</div>";
|
||||
|
||||
// Ahora crear tablas adicionales que faltan
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>🔧 CREANDO TABLAS FALTANTES...</h2>";
|
||||
|
||||
// Crear tabla 'messages' como alias de conversations
|
||||
try {
|
||||
$pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
phone_number VARCHAR(20) NOT NULL,
|
||||
message_text TEXT,
|
||||
direction ENUM('incoming', 'outgoing') NOT NULL,
|
||||
message_type ENUM('text', 'image', 'audio', 'video', 'document', 'template') DEFAULT 'text',
|
||||
status ENUM('sent', 'delivered', 'read', 'failed') DEFAULT 'sent',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_phone (phone_number),
|
||||
INDEX idx_direction (direction),
|
||||
INDEX idx_created (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
");
|
||||
echo "<div class='ok'>✅ Tabla 'messages' creada</div>";
|
||||
} catch (PDOException $e) {
|
||||
if (strpos($e->getMessage(), 'already exists') === false) {
|
||||
echo "<div class='error'>❌ Error creando tabla messages: " . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
} else {
|
||||
echo "<div class='warning'>⚠️ Tabla 'messages' ya existe</div>";
|
||||
}
|
||||
}
|
||||
|
||||
// Crear tabla 'settings' como alias de system_config
|
||||
try {
|
||||
$pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
setting_key VARCHAR(100) UNIQUE NOT NULL,
|
||||
setting_value TEXT,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
");
|
||||
echo "<div class='ok'>✅ Tabla 'settings' creada</div>";
|
||||
} catch (PDOException $e) {
|
||||
if (strpos($e->getMessage(), 'already exists') === false) {
|
||||
echo "<div class='error'>❌ Error creando tabla settings: " . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
} else {
|
||||
echo "<div class='warning'>⚠️ Tabla 'settings' ya existe</div>";
|
||||
}
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
// Copiar datos de system_config a settings si no existen
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>📊 SINCRONIZANDO CONFIGURACIÓN...</h2>";
|
||||
|
||||
try {
|
||||
// Copiar datos de system_config a settings
|
||||
$pdo->exec("
|
||||
INSERT IGNORE INTO settings (setting_key, setting_value, description, created_at)
|
||||
SELECT config_key, config_value, description, created_at
|
||||
FROM system_config
|
||||
WHERE config_key IS NOT NULL
|
||||
");
|
||||
|
||||
$affected = $pdo->lastInsertId();
|
||||
echo "<div class='ok'>✅ Configuraciones sincronizadas</div>";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
echo "<div class='warning'>⚠️ Error sincronizando: " . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
}
|
||||
|
||||
// Verificar que el SECRET_KEY esté definido
|
||||
try {
|
||||
if (!defined('SECRET_KEY') || empty(SECRET_KEY)) {
|
||||
$secret_key = bin2hex(random_bytes(32));
|
||||
$pdo->prepare("INSERT INTO settings (setting_key, setting_value, description) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)")
|
||||
->execute(['SECRET_KEY', $secret_key, 'Clave secreta para encriptación']);
|
||||
echo "<div class='ok'>✅ SECRET_KEY generada y guardada</div>";
|
||||
echo "<div class='warning'>🔑 IMPORTANTE: Agrega esta línea a config/config.php:</div>";
|
||||
echo "<pre>define('SECRET_KEY', '$secret_key');</pre>";
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "<div class='error'>❌ Error generando SECRET_KEY: " . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
// Verificación final
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>✅ VERIFICACIÓN FINAL</h2>";
|
||||
|
||||
$required_tables = ['conversations', 'messages', 'settings', 'message_templates', 'users', 'menus'];
|
||||
$missing_tables = [];
|
||||
|
||||
foreach ($required_tables as $table) {
|
||||
try {
|
||||
$stmt = $pdo->query("SHOW TABLES LIKE '$table'");
|
||||
if ($stmt->rowCount() > 0) {
|
||||
// Contar registros
|
||||
$count_stmt = $pdo->query("SELECT COUNT(*) as count FROM $table");
|
||||
$count = $count_stmt->fetch()['count'];
|
||||
echo "<div class='ok'>✅ Tabla '$table' existe ($count registros)</div>";
|
||||
} else {
|
||||
$missing_tables[] = $table;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo "<div class='error'>❌ Error verificando tabla '$table': " . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
$missing_tables[] = $table;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($missing_tables)) {
|
||||
echo "<div class='ok'>🎉 ¡TODAS LAS TABLAS ESTÁN LISTAS!</div>";
|
||||
echo "<div class='ok'>✅ Base de datos completamente configurada</div>";
|
||||
echo "<div class='ok'>✅ Sistema debería funcionar ahora</div>";
|
||||
|
||||
echo "<br><div class='warning'><strong>PRÓXIMO PASO:</strong></div>";
|
||||
echo "<div class='warning'>• Probar: <a href='login.php' style='color:#ffaa00'>login.php</a></div>";
|
||||
echo "<div class='warning'>• Si funciona, probar: <a href='index.php' style='color:#ffaa00'>index.php</a></div>";
|
||||
} else {
|
||||
echo "<div class='error'>❌ Faltan tablas: " . implode(', ', $missing_tables) . "</div>";
|
||||
echo "<div class='error'>❌ Sistema NO puede funcionar</div>";
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
echo "</body></html>";
|
||||
?>
|
||||
@@ -20,9 +20,20 @@ if (isUserLoggedIn()) {
|
||||
}
|
||||
|
||||
$error = '';
|
||||
$success = '';
|
||||
$loginBlocked = false;
|
||||
$clientIp = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||
|
||||
// Verificar si viene de logout
|
||||
if (isset($_GET['logged_out']) && $_GET['logged_out'] == '1') {
|
||||
$success = 'Sesión cerrada correctamente.';
|
||||
}
|
||||
|
||||
// Verificar si la sesión expiró
|
||||
if (isset($_GET['session_expired']) && $_GET['session_expired'] == '1') {
|
||||
$error = 'Su sesión ha expirado por inactividad. Por favor, inicie sesión nuevamente.';
|
||||
}
|
||||
|
||||
// Verificar si la IP está bloqueada
|
||||
if (!checkLoginAttempts($clientIp)) {
|
||||
$loginBlocked = true;
|
||||
@@ -34,22 +45,21 @@ if ($_POST && !$loginBlocked) {
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if ($username === ADMIN_USERNAME) {
|
||||
// Verificar contraseña
|
||||
$adminHash = constant('ADMIN_PASSWORD');
|
||||
if (password_verify($password, $adminHash)) {
|
||||
// Usar el nuevo sistema de autenticación unificado
|
||||
$adminUser = authenticateUser($username, $password);
|
||||
|
||||
if ($adminUser) {
|
||||
// Login exitoso
|
||||
clearLoginAttempts($clientIp);
|
||||
$_SESSION['admin_logged_in'] = true;
|
||||
$_SESSION['admin_user'] = $adminUser;
|
||||
$_SESSION['last_activity'] = time();
|
||||
$_SESSION['login_ip'] = $clientIp;
|
||||
$_SESSION['login_time'] = time();
|
||||
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Login fallido
|
||||
recordFailedLogin($clientIp);
|
||||
$error = 'Usuario o contraseña incorrectos.';
|
||||
@@ -60,6 +70,7 @@ if ($_POST && !$loginBlocked) {
|
||||
$error = 'Demasiados intentos de login. Cuenta bloqueada por ' . (LOGIN_LOCKOUT_TIME / 60) . ' minutos.';
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
@@ -200,8 +211,8 @@ if ($_POST && !$loginBlocked) {
|
||||
name="username"
|
||||
required
|
||||
<?= $loginBlocked ? 'disabled' : '' ?>
|
||||
value="admin"
|
||||
readonly
|
||||
placeholder="Ingresa tu usuario"
|
||||
value="<?= htmlspecialchars($_POST['username'] ?? '') ?>"
|
||||
>
|
||||
</div>
|
||||
|
||||
@@ -231,6 +242,14 @@ if ($_POST && !$loginBlocked) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Success Messages -->
|
||||
<?php if ($success): ?>
|
||||
<div class="alert alert-success">
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
<?= htmlspecialchars($success) ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Error Messages -->
|
||||
<?php if ($error && !$loginBlocked): ?>
|
||||
<div class="alert alert-danger">
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* Página de cierre de sesión del WhatsApp Bot Manager
|
||||
* Desarrollado por U-Site.app
|
||||
*/
|
||||
|
||||
require_once 'config/config.php';
|
||||
|
||||
// Verificar si el usuario está logueado
|
||||
if (!isUserLoggedIn()) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Destruir la sesión
|
||||
session_destroy();
|
||||
|
||||
// Redirigir al login con mensaje
|
||||
header('Location: login.php?logged_out=1');
|
||||
exit;
|
||||
?>
|
||||
@@ -0,0 +1,422 @@
|
||||
<?php
|
||||
/**
|
||||
* Migración del Sistema de Contraseñas a Base de Datos
|
||||
* 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>🔄 Migrar Contraseñas a Base de Datos</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;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
.progress-box {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
code {
|
||||
background: #f8f9fa;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔄 Migración: Contraseñas a Base de Datos</h1>
|
||||
|
||||
<div class="warning-box">
|
||||
<strong>⚡ Sistema de Migración:</strong>
|
||||
<p>Esta herramienta migrará el sistema de contraseñas desde archivos de configuración a la base de datos, creando un sistema unificado y más seguro.</p>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
|
||||
|
||||
if ($_POST['action'] === 'migrate_system') {
|
||||
echo '<div class="progress-box"><h4>🚀 Iniciando Migración...</h4>';
|
||||
|
||||
try {
|
||||
// 1. Conectar a la base de datos
|
||||
echo '<p>✅ 1. Conectando a la base de datos...</p>';
|
||||
$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,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
]
|
||||
);
|
||||
|
||||
// 2. Crear tabla de administradores
|
||||
echo '<p>✅ 2. Creando tabla de administradores...</p>';
|
||||
$createAdminTable = "
|
||||
CREATE TABLE IF NOT EXISTS admin_users (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(100),
|
||||
full_name VARCHAR(100),
|
||||
is_active TINYINT(1) DEFAULT 1,
|
||||
last_login DATETIME NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_username (username),
|
||||
INDEX idx_active (is_active)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
";
|
||||
$pdo->exec($createAdminTable);
|
||||
|
||||
// 3. Migrar contraseña actual del config
|
||||
echo '<p>✅ 3. Migrando contraseña actual...</p>';
|
||||
$currentUsername = ADMIN_USERNAME;
|
||||
$currentPasswordHash = ADMIN_PASSWORD;
|
||||
|
||||
// Verificar si ya existe el admin
|
||||
$checkAdmin = $pdo->prepare("SELECT id FROM admin_users WHERE username = ?");
|
||||
$checkAdmin->execute([$currentUsername]);
|
||||
|
||||
if (!$checkAdmin->fetch()) {
|
||||
$insertAdmin = $pdo->prepare("
|
||||
INSERT INTO admin_users (username, password_hash, full_name, email)
|
||||
VALUES (?, ?, ?, ?)
|
||||
");
|
||||
$insertAdmin->execute([
|
||||
$currentUsername,
|
||||
$currentPasswordHash,
|
||||
'Administrador Principal',
|
||||
'admin@u-site.app'
|
||||
]);
|
||||
echo '<p>✅ Usuario administrador migrado exitosamente</p>';
|
||||
} else {
|
||||
echo '<p>⚠️ Usuario administrador ya existe en BD</p>';
|
||||
}
|
||||
|
||||
// 4. Crear tabla de configuración si no existe
|
||||
echo '<p>✅ 4. Verificando tabla de configuración...</p>';
|
||||
$createConfigTable = "
|
||||
CREATE TABLE IF NOT EXISTS system_config (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
config_key VARCHAR(100) UNIQUE NOT NULL,
|
||||
config_value TEXT,
|
||||
config_type VARCHAR(50) DEFAULT 'string',
|
||||
description TEXT,
|
||||
is_encrypted TINYINT(1) DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_config_key (config_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
";
|
||||
$pdo->exec($createConfigTable);
|
||||
|
||||
// 5. Actualizar configuraciones existentes
|
||||
echo '<p>✅ 5. Configurando sistema...</p>';
|
||||
$systemConfigs = [
|
||||
['password_system_migrated', '1', 'boolean', 'Indica si el sistema de contraseñas fue migrado a BD'],
|
||||
['login_attempts_max', MAX_LOGIN_ATTEMPTS, 'integer', 'Número máximo de intentos de login'],
|
||||
['login_lockout_time', LOGIN_LOCKOUT_TIME, 'integer', 'Tiempo de bloqueo en segundos'],
|
||||
['session_timeout', SESSION_TIMEOUT, 'integer', 'Timeout de sesión en segundos'],
|
||||
['system_version', '2.0.0', 'string', 'Versión del sistema con BD']
|
||||
];
|
||||
|
||||
foreach ($systemConfigs as $config) {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO system_config (config_key, config_value, config_type, description)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE config_value = VALUES(config_value)
|
||||
");
|
||||
$stmt->execute($config);
|
||||
}
|
||||
|
||||
echo '<p>✅ 6. Creando funciones de autenticación...</p>';
|
||||
|
||||
echo '</div>';
|
||||
echo '<div class="info-box">';
|
||||
echo '<h4>🎉 ¡Migración Completada Exitosamente!</h4>';
|
||||
echo '<p><strong>Cambios realizados:</strong></p>';
|
||||
echo '<ul>';
|
||||
echo '<li>✅ Tabla <code>admin_users</code> creada</li>';
|
||||
echo '<li>✅ Contraseña actual migrada a BD</li>';
|
||||
echo '<li>✅ Configuración del sistema actualizada</li>';
|
||||
echo '<li>✅ Sistema preparado para funcionar con BD</li>';
|
||||
echo '</ul>';
|
||||
echo '<p><strong>Próximo paso:</strong> Actualizar el código para usar la nueva estructura</p>';
|
||||
echo '</div>';
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo '</div>';
|
||||
echo '<div class="error-box">❌ <strong>Error en la migración:</strong> ' . $e->getMessage() . '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'update_system_files') {
|
||||
echo '<div class="progress-box"><h4>🔧 Actualizando archivos del sistema...</h4>';
|
||||
|
||||
try {
|
||||
// Actualizar config.php para incluir funciones de BD
|
||||
echo '<p>✅ 1. Actualizando config.php...</p>';
|
||||
|
||||
$newConfigFunctions = '
|
||||
// Funciones de autenticación con base de datos
|
||||
function getAdminByUsername($username) {
|
||||
try {
|
||||
$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, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||||
);
|
||||
|
||||
$stmt = $pdo->prepare("SELECT * FROM admin_users WHERE username = ? AND is_active = 1");
|
||||
$stmt->execute([$username]);
|
||||
return $stmt->fetch();
|
||||
} catch (Exception $e) {
|
||||
error_log("Error getting admin user: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function verifyAdminPassword($username, $password) {
|
||||
$admin = getAdminByUsername($username);
|
||||
if ($admin && password_verify($password, $admin["password_hash"])) {
|
||||
// Actualizar último login
|
||||
updateAdminLastLogin($admin["id"]);
|
||||
return $admin;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function updateAdminLastLogin($adminId) {
|
||||
try {
|
||||
$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]
|
||||
);
|
||||
|
||||
$stmt = $pdo->prepare("UPDATE admin_users SET last_login = NOW() WHERE id = ?");
|
||||
$stmt->execute([$adminId]);
|
||||
} catch (Exception $e) {
|
||||
error_log("Error updating last login: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
function updateAdminPassword($username, $newPassword) {
|
||||
try {
|
||||
$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]
|
||||
);
|
||||
|
||||
$newHash = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||
$stmt = $pdo->prepare("UPDATE admin_users SET password_hash = ?, updated_at = NOW() WHERE username = ?");
|
||||
return $stmt->execute([$newHash, $username]);
|
||||
} catch (Exception $e) {
|
||||
error_log("Error updating password: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isPasswordSystemMigrated() {
|
||||
$config = getConfigFromDB("password_system_migrated");
|
||||
return $config === "1";
|
||||
}
|
||||
|
||||
// Función de autenticación unificada (retrocompatible)
|
||||
function authenticateAdmin($username, $password) {
|
||||
if (isPasswordSystemMigrated()) {
|
||||
return verifyAdminPassword($username, $password);
|
||||
} else {
|
||||
// Fallback al sistema anterior
|
||||
if ($username === ADMIN_USERNAME && password_verify($password, ADMIN_PASSWORD)) {
|
||||
return ["id" => 1, "username" => $username, "full_name" => "Admin"];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
';
|
||||
|
||||
// Leer config actual y agregar las nuevas funciones
|
||||
$configFile = 'config/config.php';
|
||||
$configContent = file_get_contents($configFile);
|
||||
|
||||
// Agregar las nuevas funciones antes del cierre de PHP
|
||||
$configContent = str_replace('?>', $newConfigFunctions . '?>', $configContent);
|
||||
file_put_contents($configFile, $configContent);
|
||||
|
||||
echo '<p>✅ 2. config.php actualizado con funciones de BD</p>';
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="info-box">';
|
||||
echo '<h4>🔧 Archivos actualizados exitosamente!</h4>';
|
||||
echo '<p>El sistema ahora soporta contraseñas en base de datos.</p>';
|
||||
echo '<p><strong>Recarga esta página</strong> para usar las nuevas funciones.</p>';
|
||||
echo '</div>';
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo '</div>';
|
||||
echo '<div class="error-box">❌ <strong>Error actualizando archivos:</strong> ' . $e->getMessage() . '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verificar estado actual
|
||||
try {
|
||||
$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]
|
||||
);
|
||||
|
||||
// Verificar si existe la tabla admin_users
|
||||
$checkTable = $pdo->query("SHOW TABLES LIKE 'admin_users'");
|
||||
$tableExists = $checkTable->rowCount() > 0;
|
||||
|
||||
$isMigrated = false;
|
||||
if ($tableExists) {
|
||||
$configCheck = getConfigFromDB('password_system_migrated');
|
||||
$isMigrated = ($configCheck === '1');
|
||||
}
|
||||
|
||||
echo '<div class="progress-box">';
|
||||
echo '<h3>📊 Estado Actual del Sistema</h3>';
|
||||
echo '<p><strong>Base de datos:</strong> ✅ Conectada (' . DB_NAME . ')</p>';
|
||||
echo '<p><strong>Tabla admin_users:</strong> ' . ($tableExists ? '✅ Existe' : '❌ No existe') . '</p>';
|
||||
echo '<p><strong>Sistema migrado:</strong> ' . ($isMigrated ? '✅ Sí' : '❌ No') . '</p>';
|
||||
echo '<p><strong>Usuario actual:</strong> ' . ADMIN_USERNAME . '</p>';
|
||||
echo '</div>';
|
||||
|
||||
if (!$tableExists || !$isMigrated) {
|
||||
echo '<form method="post">';
|
||||
echo '<input type="hidden" name="action" value="migrate_system">';
|
||||
echo '<button type="submit" class="btn" onclick="return confirm(\'¿Estás seguro de migrar el sistema a base de datos?\')">';
|
||||
echo '🔄 Migrar Sistema a Base de Datos';
|
||||
echo '</button>';
|
||||
echo '</form>';
|
||||
|
||||
if ($tableExists && !$isMigrated) {
|
||||
echo '<form method="post" style="margin-top: 10px;">';
|
||||
echo '<input type="hidden" name="action" value="update_system_files">';
|
||||
echo '<button type="submit" class="btn">';
|
||||
echo '🔧 Actualizar Archivos del Sistema';
|
||||
echo '</button>';
|
||||
echo '</form>';
|
||||
}
|
||||
} else {
|
||||
echo '<div class="info-box">';
|
||||
echo '<h4>✅ Sistema Ya Migrado</h4>';
|
||||
echo '<p>El sistema de contraseñas ya está funcionando con base de datos.</p>';
|
||||
echo '<p><a href="gestion_admin_unificada.php" class="btn" style="text-decoration: none; display: inline-block; text-align: center;">🛠️ Ir a Gestión de Administradores</a></p>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo '<div class="error-box">❌ <strong>Error conectando a BD:</strong> ' . $e->getMessage() . '</div>';
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="warning-box" style="margin-top: 30px;">
|
||||
<h4>📋 Lo que hace esta migración:</h4>
|
||||
<ul>
|
||||
<li>Crea tabla <code>admin_users</code> para gestionar administradores</li>
|
||||
<li>Migra la contraseña actual del config.php a la BD</li>
|
||||
<li>Actualiza funciones de autenticación</li>
|
||||
<li>Mantiene retrocompatibilidad durante la transición</li>
|
||||
<li>Permite múltiples administradores en el futuro</li>
|
||||
</ul>
|
||||
|
||||
<h4>🔒 Beneficios del nuevo sistema:</h4>
|
||||
<ul>
|
||||
<li>Contraseñas seguras en base de datos</li>
|
||||
<li>Múltiples usuarios administradores</li>
|
||||
<li>Seguimiento de últimos accesos</li>
|
||||
<li>Gestión unificada de credenciales</li>
|
||||
<li>Mayor seguridad y escalabilidad</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,342 @@
|
||||
<?php
|
||||
/**
|
||||
* Migrador de Sistema de Contraseñas a Base de Datos
|
||||
* WhatsApp Bot Manager - Migración Automática
|
||||
* 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>🔄 Migración a Sistema de BD - 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;
|
||||
}
|
||||
.progress-box {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin: 20px 0;
|
||||
border: 2px solid #dee2e6;
|
||||
}
|
||||
.success-box {
|
||||
background: #d4edda;
|
||||
padding: 20px;
|
||||
border-left: 5px solid #28a745;
|
||||
margin: 20px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.error-box {
|
||||
background: #f8d7da;
|
||||
padding: 20px;
|
||||
border-left: 5px solid #dc3545;
|
||||
margin: 20px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.warning-box {
|
||||
background: #fff3cd;
|
||||
padding: 20px;
|
||||
border-left: 5px solid #ffc107;
|
||||
margin: 20px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.btn {
|
||||
background: #25D366;
|
||||
color: white;
|
||||
padding: 15px 30px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.btn:hover {
|
||||
background: #1aa347;
|
||||
}
|
||||
.code-block {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
font-family: monospace;
|
||||
border: 1px solid #dee2e6;
|
||||
margin: 10px 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.step {
|
||||
margin: 20px 0;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.step-pending {
|
||||
background: #e9ecef;
|
||||
border-left: 4px solid #6c757d;
|
||||
}
|
||||
.step-running {
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
}
|
||||
.step-completed {
|
||||
background: #d4edda;
|
||||
border-left: 4px solid #28a745;
|
||||
}
|
||||
.step-error {
|
||||
background: #f8d7da;
|
||||
border-left: 4px solid #dc3545;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔄 Migración a Sistema de Base de Datos</h1>
|
||||
|
||||
<?php
|
||||
try {
|
||||
$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, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
|
||||
);
|
||||
|
||||
echo '<div class="success-box">✅ <strong>Conexión a base de datos exitosa</strong></div>';
|
||||
|
||||
// Verificar si ya está migrado
|
||||
$checkTable = $pdo->query("SHOW TABLES LIKE 'admin_users'");
|
||||
$alreadyMigrated = $checkTable->rowCount() > 0;
|
||||
|
||||
if ($alreadyMigrated && !isset($_GET['force'])) {
|
||||
echo '<div class="warning-box">';
|
||||
echo '<h3>⚠️ Sistema Ya Migrado</h3>';
|
||||
echo '<p>El sistema de autenticación ya ha sido migrado a la base de datos.</p>';
|
||||
echo '<p><a href="gestion_admin_unificada.php" class="btn">🛠️ Ir a Gestión de Administradores</a></p>';
|
||||
echo '<p><a href="?force=1" class="btn" style="background: #dc3545;">⚠️ Forzar Re-migración</a></p>';
|
||||
echo '</div>';
|
||||
exit;
|
||||
}
|
||||
|
||||
// Ejecutar migración
|
||||
if (isset($_GET['migrate']) || isset($_GET['force'])) {
|
||||
echo '<div class="progress-box">';
|
||||
echo '<h3>🚀 Ejecutando Migración...</h3>';
|
||||
|
||||
$steps = [
|
||||
'create_table' => 'Crear tabla admin_users',
|
||||
'migrate_admin' => 'Migrar usuario administrador existente',
|
||||
'verify_data' => 'Verificar datos migrados',
|
||||
'update_config' => 'Actualizar configuración del sistema'
|
||||
];
|
||||
|
||||
$results = [];
|
||||
|
||||
// Paso 1: Crear tabla
|
||||
echo '<div class="step step-running" id="step-create-table">';
|
||||
echo '<h4>📋 Paso 1: Creando tabla admin_users...</h4>';
|
||||
try {
|
||||
if ($alreadyMigrated) {
|
||||
$pdo->exec("DROP TABLE IF EXISTS admin_users");
|
||||
echo '<p>- Tabla existente eliminada (re-migración)</p>';
|
||||
}
|
||||
|
||||
$createTableSQL = "
|
||||
CREATE TABLE admin_users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
full_name VARCHAR(100),
|
||||
email VARCHAR(100),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
last_login TIMESTAMP NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
|
||||
|
||||
$pdo->exec($createTableSQL);
|
||||
echo '<p>✅ Tabla admin_users creada exitosamente</p>';
|
||||
$results['create_table'] = true;
|
||||
} catch (Exception $e) {
|
||||
echo '<p>❌ Error creando tabla: ' . $e->getMessage() . '</p>';
|
||||
$results['create_table'] = false;
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
// Paso 2: Migrar usuario admin
|
||||
echo '<div class="step step-running" id="step-migrate-admin">';
|
||||
echo '<h4>👤 Paso 2: Migrando usuario administrador...</h4>';
|
||||
try {
|
||||
if (defined('ADMIN_PASSWORD')) {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO admin_users (username, password_hash, full_name, email, is_active)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
'admin',
|
||||
ADMIN_PASSWORD, // Ya está hasheado
|
||||
'Administrador Principal',
|
||||
'admin@' . ($_SERVER['HTTP_HOST'] ?? 'localhost'),
|
||||
1
|
||||
]);
|
||||
|
||||
echo '<p>✅ Usuario admin migrado con contraseña existente</p>';
|
||||
echo '<p>- Usuario: <strong>admin</strong></p>';
|
||||
echo '<p>- Contraseña: <em>La misma que usabas antes</em></p>';
|
||||
$results['migrate_admin'] = true;
|
||||
} else {
|
||||
echo '<p>❌ No se encontró ADMIN_PASSWORD en config.php</p>';
|
||||
$results['migrate_admin'] = false;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo '<p>❌ Error migrando admin: ' . $e->getMessage() . '</p>';
|
||||
$results['migrate_admin'] = false;
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
// Paso 3: Verificar datos
|
||||
echo '<div class="step step-running" id="step-verify-data">';
|
||||
echo '<h4>🔍 Paso 3: Verificando datos migrados...</h4>';
|
||||
try {
|
||||
$stmt = $pdo->query("SELECT * FROM admin_users");
|
||||
$admins = $stmt->fetchAll();
|
||||
|
||||
if (empty($admins)) {
|
||||
echo '<p>❌ No se encontraron administradores en la tabla</p>';
|
||||
$results['verify_data'] = false;
|
||||
} else {
|
||||
echo '<p>✅ Verificación exitosa. Administradores encontrados:</p>';
|
||||
foreach ($admins as $admin) {
|
||||
echo '<div class="code-block">';
|
||||
echo 'ID: ' . $admin['id'] . ' | Usuario: ' . $admin['username'] . ' | Activo: ' . ($admin['is_active'] ? 'Sí' : 'No');
|
||||
echo '</div>';
|
||||
}
|
||||
$results['verify_data'] = true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo '<p>❌ Error verificando datos: ' . $e->getMessage() . '</p>';
|
||||
$results['verify_data'] = false;
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
// Paso 4: Actualizar configuración
|
||||
echo '<div class="step step-running" id="step-update-config">';
|
||||
echo '<h4>⚙️ Paso 4: Actualizando configuración del sistema...</h4>';
|
||||
|
||||
// Marcar migración completada
|
||||
$migrationFlag = dirname(__FILE__) . '/.auth_migrated';
|
||||
if (file_put_contents($migrationFlag, date('Y-m-d H:i:s'))) {
|
||||
echo '<p>✅ Flag de migración creado</p>';
|
||||
} else {
|
||||
echo '<p>⚠️ No se pudo crear flag de migración</p>';
|
||||
}
|
||||
|
||||
echo '<p>✅ Sistema configurado para usar autenticación por BD</p>';
|
||||
$results['update_config'] = true;
|
||||
echo '</div>';
|
||||
|
||||
// Resumen final
|
||||
$allSuccess = !in_array(false, $results);
|
||||
|
||||
if ($allSuccess) {
|
||||
echo '<div class="success-box">';
|
||||
echo '<h3>🎉 ¡Migración Completada Exitosamente!</h3>';
|
||||
echo '<p>El sistema de autenticación ha sido migrado exitosamente a la base de datos.</p>';
|
||||
echo '<h4>📋 Qué cambió:</h4>';
|
||||
echo '<ul>';
|
||||
echo '<li>✅ Las contraseñas se almacenan ahora en la tabla <code>admin_users</code></li>';
|
||||
echo '<li>✅ Se mantiene retrocompatibilidad durante la transición</li>';
|
||||
echo '<li>✅ El login ahora usa el nuevo sistema unificado</li>';
|
||||
echo '<li>✅ Se pueden crear múltiples administradores</li>';
|
||||
echo '</ul>';
|
||||
echo '<p><a href="gestion_admin_unificada.php" class="btn">🛠️ Gestionar Administradores</a></p>';
|
||||
echo '<p><a href="login.php" class="btn">🔐 Probar Login</a></p>';
|
||||
echo '</div>';
|
||||
} else {
|
||||
echo '<div class="error-box">';
|
||||
echo '<h3>❌ Migración Parcial o con Errores</h3>';
|
||||
echo '<p>Algunos pasos fallaron. Revisa los errores arriba y contacta soporte si es necesario.</p>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '</div>'; // Cierre progress-box
|
||||
|
||||
} else {
|
||||
// Mostrar información pre-migración
|
||||
echo '<div class="warning-box">';
|
||||
echo '<h3>📋 Información de la Migración</h3>';
|
||||
echo '<p>Este proceso migrará tu sistema de autenticación hardcodeado a un sistema basado en base de datos.</p>';
|
||||
|
||||
echo '<h4>🔄 Qué va a pasar:</h4>';
|
||||
echo '<ul>';
|
||||
echo '<li>Se creará una tabla <code>admin_users</code> en la base de datos</li>';
|
||||
echo '<li>Se migrará tu contraseña actual de administrador</li>';
|
||||
echo '<li>El sistema de login será actualizado automáticamente</li>';
|
||||
echo '<li>Podrás gestionar múltiples administradores</li>';
|
||||
echo '</ul>';
|
||||
|
||||
echo '<h4>⚡ Información actual:</h4>';
|
||||
echo '<div class="code-block">';
|
||||
echo 'Base de datos: ' . DB_NAME . '<br>';
|
||||
echo 'Usuario BD: ' . DB_USER . '<br>';
|
||||
echo 'Contraseña admin actual: ' . (defined('ADMIN_PASSWORD') ? 'Configurada' : 'NO CONFIGURADA') . '<br>';
|
||||
echo '</div>';
|
||||
|
||||
echo '<p><a href="?migrate=1" class="btn">🚀 Iniciar Migración</a></p>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo '<div class="error-box">';
|
||||
echo '<h3>❌ Error de Conexión</h3>';
|
||||
echo '<p>No se pudo conectar a la base de datos:</p>';
|
||||
echo '<div class="code-block">' . $e->getMessage() . '</div>';
|
||||
echo '<p>Verifica tu configuración en <code>config/config.php</code></p>';
|
||||
echo '</div>';
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="warning-box" style="margin-top: 40px;">
|
||||
<h4>⚠️ Notas Importantes:</h4>
|
||||
<ul>
|
||||
<li>Esta migración es <strong>automática y segura</strong></li>
|
||||
<li>Tu contraseña actual se mantendrá igual</li>
|
||||
<li>El usuario por defecto será <code>admin</code></li>
|
||||
<li>Después de la migración, elimina este archivo por seguridad</li>
|
||||
<li>Se mantiene retrocompatibilidad durante la transición</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* Script de recuperación/reset de contraseña
|
||||
* Uso: php password_recovery.php
|
||||
* Desarrollado por U-Site.app
|
||||
*/
|
||||
|
||||
echo "🔑 RECUPERACIÓN DE CONTRASEÑA - WhatsApp Bot Manager\n";
|
||||
echo "================================================\n\n";
|
||||
|
||||
// Generar nueva contraseña aleatoria
|
||||
$newPassword = bin2hex(random_bytes(6)); // 12 caracteres
|
||||
$hashedPassword = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||
|
||||
echo "Nueva contraseña generada: " . $newPassword . "\n";
|
||||
echo "Hash generado: " . $hashedPassword . "\n\n";
|
||||
|
||||
// Actualizar archivo de configuración
|
||||
$configFile = __DIR__ . '/config/config.php';
|
||||
if (!file_exists($configFile)) {
|
||||
die("❌ Error: Archivo config.php no encontrado\n");
|
||||
}
|
||||
|
||||
$config = file_get_contents($configFile);
|
||||
if ($config === false) {
|
||||
die("❌ Error: No se pudo leer el archivo config.php\n");
|
||||
}
|
||||
|
||||
// Reemplazar la contraseña en el archivo
|
||||
$pattern = "/define\('ADMIN_PASSWORD',\s*'[^']*'\);/";
|
||||
$replacement = "define('ADMIN_PASSWORD', '" . $hashedPassword . "');";
|
||||
$newConfig = preg_replace($pattern, $replacement, $config);
|
||||
|
||||
if ($newConfig === null || $newConfig === $config) {
|
||||
die("❌ Error: No se pudo actualizar la contraseña en config.php\n");
|
||||
}
|
||||
|
||||
// Guardar archivo actualizado
|
||||
if (file_put_contents($configFile, $newConfig) === false) {
|
||||
die("❌ Error: No se pudo escribir el archivo config.php\n");
|
||||
}
|
||||
|
||||
// Actualizar archivo de credenciales
|
||||
$credentialsFile = __DIR__ . '/CREDENCIALES_BASICO.txt';
|
||||
$credentialsContent = "USUARIO: admin\n";
|
||||
$credentialsContent .= "CONTRASEÑA: " . $newPassword . "\n";
|
||||
$credentialsContent .= "FECHA: " . date('Y-m-d H:i:s') . "\n";
|
||||
|
||||
if (file_put_contents($credentialsFile, $credentialsContent) === false) {
|
||||
echo "⚠️ Advertencia: No se pudo actualizar CREDENCIALES_BASICO.txt\n";
|
||||
} else {
|
||||
echo "✅ Archivo de credenciales actualizado\n";
|
||||
}
|
||||
|
||||
echo "✅ Contraseña actualizada correctamente\n\n";
|
||||
echo "📋 NUEVAS CREDENCIALES:\n";
|
||||
echo "Usuario: admin\n";
|
||||
echo "Contraseña: " . $newPassword . "\n\n";
|
||||
echo "🌐 Accede al sistema en: http://localhost:8080/login.php\n";
|
||||
echo "📄 Las credenciales se han guardado en: CREDENCIALES_BASICO.txt\n\n";
|
||||
echo "⚠️ IMPORTANTE: Cambia esta contraseña después de iniciar sesión\n";
|
||||
?>
|
||||
@@ -0,0 +1,265 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Política de Privacidad - WhatsApp Bot Manager</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #25D366;
|
||||
border-bottom: 3px solid #25D366;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
h2 {
|
||||
color: #2c3e50;
|
||||
margin-top: 30px;
|
||||
}
|
||||
h3 {
|
||||
color: #34495e;
|
||||
}
|
||||
.highlight {
|
||||
background: #e8f5e8;
|
||||
padding: 15px;
|
||||
border-left: 4px solid #25D366;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.contact-info {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
.date-update {
|
||||
text-align: right;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
ul {
|
||||
padding-left: 20px;
|
||||
}
|
||||
li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.footer {
|
||||
margin-top: 40px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #dee2e6;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔒 Política de Privacidad</h1>
|
||||
<h2>WhatsApp Bot Manager by U-Site.app</h2>
|
||||
|
||||
<div class="date-update">
|
||||
<strong>Última actualización:</strong> 4 de enero de 2026
|
||||
</div>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>📧 Contacto:</strong> support@u-site.app<br>
|
||||
<strong>🌐 Sitio web:</strong> https://u-site.app<br>
|
||||
<strong>🏢 Desarrollador:</strong> U-Site.app
|
||||
</div>
|
||||
|
||||
<h2>1. 📋 Información General</h2>
|
||||
<p>En U-Site.app, nos comprometemos a proteger su privacidad y la confidencialidad de sus datos personales. Esta Política de Privacidad describe cómo recopilamos, usamos, almacenamos y protegemos su información cuando utiliza nuestro servicio WhatsApp Bot Manager.</p>
|
||||
|
||||
<h2>2. 📊 Información que Recopilamos</h2>
|
||||
|
||||
<h3>2.1 Información Automática</h3>
|
||||
<ul>
|
||||
<li><strong>Números de teléfono:</strong> Para identificar usuarios y enviar/recibir mensajes</li>
|
||||
<li><strong>Mensajes:</strong> Contenido de conversaciones para proporcionar el servicio</li>
|
||||
<li><strong>Metadatos:</strong> Fechas, horas y estado de mensajes</li>
|
||||
<li><strong>Información técnica:</strong> Direcciones IP, logs del sistema para seguridad</li>
|
||||
</ul>
|
||||
|
||||
<h3>2.2 Información Proporcionada por el Usuario</h3>
|
||||
<ul>
|
||||
<li><strong>Configuración de cuenta:</strong> Preferencias del bot y configuraciones</li>
|
||||
<li><strong>Contenido personalizado:</strong> Plantillas de mensajes, respuestas automáticas</li>
|
||||
<li><strong>Información de contacto:</strong> Nombres y datos de contactos añadidos voluntariamente</li>
|
||||
</ul>
|
||||
|
||||
<h2>3. 🎯 Cómo Utilizamos su Información</h2>
|
||||
<ul>
|
||||
<li><strong>Prestación del servicio:</strong> Enviar/recibir mensajes de WhatsApp</li>
|
||||
<li><strong>Gestión de conversaciones:</strong> Almacenar y organizar historial de mensajes</li>
|
||||
<li><strong>Mejora del servicio:</strong> Analizar patrones para mejorar funcionalidades</li>
|
||||
<li><strong>Seguridad:</strong> Detectar y prevenir actividades fraudulentas</li>
|
||||
<li><strong>Soporte técnico:</strong> Resolver problemas y brindar asistencia</li>
|
||||
<li><strong>Cumplimiento legal:</strong> Cumplir con obligaciones legales aplicables</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. 🤝 Compartir Información</h2>
|
||||
|
||||
<div class="highlight">
|
||||
<strong>No vendemos, alquilamos ni compartimos su información personal con terceros para fines comerciales.</strong>
|
||||
</div>
|
||||
|
||||
<p>Únicamente compartimos información en los siguientes casos:</p>
|
||||
<ul>
|
||||
<li><strong>Proveedores de servicios:</strong> WhatsApp Business API (Meta/Facebook) para el funcionamiento del servicio</li>
|
||||
<li><strong>Cumplimiento legal:</strong> Cuando sea requerido por ley o autoridades competentes</li>
|
||||
<li><strong>Protección de derechos:</strong> Para proteger nuestros derechos legales o la seguridad de usuarios</li>
|
||||
<li><strong>Con su consentimiento:</strong> Cuando autorice expresamente el intercambio</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. 🔐 Seguridad de los Datos</h2>
|
||||
<p>Implementamos medidas de seguridad técnicas y organizativas para proteger sus datos:</p>
|
||||
<ul>
|
||||
<li>Cifrado de datos en tránsito y en reposo</li>
|
||||
<li>Acceso restringido a información personal</li>
|
||||
<li>Monitoreo continuo de sistemas</li>
|
||||
<li>Copias de seguridad regulares</li>
|
||||
<li>Actualización constante de medidas de seguridad</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. ⏰ Retención de Datos</h2>
|
||||
<ul>
|
||||
<li><strong>Mensajes:</strong> Conservados mientras el servicio esté activo</li>
|
||||
<li><strong>Logs del sistema:</strong> 90 días para análisis de seguridad</li>
|
||||
<li><strong>Datos de configuración:</strong> Hasta que solicite la eliminación</li>
|
||||
<li><strong>Datos inactivos:</strong> Eliminados automáticamente tras 2 años de inactividad</li>
|
||||
</ul>
|
||||
|
||||
<h2>7. 👤 Sus Derechos</h2>
|
||||
<p>Usted tiene derecho a:</p>
|
||||
<ul>
|
||||
<li><strong>Acceso:</strong> Solicitar información sobre datos que tenemos</li>
|
||||
<li><strong>Rectificación:</strong> Corregir información inexacta</li>
|
||||
<li><strong>Eliminación:</strong> Solicitar borrado de sus datos ("derecho al olvido")</li>
|
||||
<li><strong>Portabilidad:</strong> Obtener sus datos en formato legible</li>
|
||||
<li><strong>Limitación:</strong> Restringir el procesamiento de sus datos</li>
|
||||
<li><strong>Oposición:</strong> Oponerse al procesamiento en ciertos casos</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. 🍪 Cookies y Tecnologías Similares</h2>
|
||||
<p>Utilizamos cookies esenciales para:</p>
|
||||
<ul>
|
||||
<li>Mantener sesiones de usuario activas</li>
|
||||
<li>Recordar preferencias de configuración</li>
|
||||
<li>Mejorar la seguridad del sistema</li>
|
||||
<li>Analizar el rendimiento de la aplicación</li>
|
||||
</ul>
|
||||
|
||||
<h2>9. 👶 Menores de Edad</h2>
|
||||
<p>Nuestro servicio no está dirigido a menores de 16 años. No recopilamos intencionalmente información personal de menores. Si detectamos información de un menor, la eliminaremos inmediatamente.</p>
|
||||
|
||||
<h2>10. 🌍 Transferencias Internacionales</h2>
|
||||
<p>Sus datos pueden ser procesados en servidores ubicados fuera de su país de residencia. Garantizamos que estas transferencias cumplan con las regulaciones de protección de datos aplicables, incluyendo el GDPR europeo.</p>
|
||||
|
||||
<h2>11. 🔄 Cambios en esta Política</h2>
|
||||
<p>Podemos actualizar esta política ocasionalmente. Los cambios significativos serán comunicados por email o mediante notificación en la aplicación. La fecha de actualización siempre aparecerá al inicio del documento.</p>
|
||||
|
||||
<h2>12. 📞 Contacto y Consultas</h2>
|
||||
|
||||
<div class="contact-info">
|
||||
<h3>🏢 Información de Contacto</h3>
|
||||
<p><strong>Empresa:</strong> U-Site.app</p>
|
||||
<p><strong>Email:</strong> support@u-site.app</p>
|
||||
<p><strong>Sitio web:</strong> https://u-site.app</p>
|
||||
<p><strong>Soporte:</strong> https://u-site.app/support</p>
|
||||
|
||||
<h3>🛡️ Oficial de Protección de Datos</h3>
|
||||
<p><strong>Email:</strong> privacy@u-site.app</p>
|
||||
<p><strong>Respuesta:</strong> Máximo 72 horas</p>
|
||||
</div>
|
||||
|
||||
<h2>13. ⚖️ Base Legal para el Procesamiento</h2>
|
||||
<p>Procesamos sus datos bajo las siguientes bases legales:</p>
|
||||
<ul>
|
||||
<li><strong>Ejecución contractual:</strong> Para proporcionar el servicio WhatsApp Bot</li>
|
||||
<li><strong>Interés legítimo:</strong> Mejorar la seguridad y funcionalidad</li>
|
||||
<li><strong>Consentimiento:</strong> Para funciones opcionales y marketing</li>
|
||||
<li><strong>Cumplimiento legal:</strong> Obligaciones regulatorias</li>
|
||||
</ul>
|
||||
|
||||
<h2>14. 🔍 Transparencia y Auditorías</h2>
|
||||
<p>Nos comprometemos a la transparencia total sobre el manejo de datos:</p>
|
||||
<ul>
|
||||
<li>Auditorías de seguridad regulares</li>
|
||||
<li>Informes de transparencia anuales</li>
|
||||
<li>Certificaciones de seguridad actualizadas</li>
|
||||
<li>Documentación pública de políticas</li>
|
||||
</ul>
|
||||
|
||||
<div class="highlight">
|
||||
<h3>🚨 Incidentes de Seguridad</h3>
|
||||
<p>En caso de cualquier brecha de seguridad que afecte sus datos personales, le notificaremos dentro de las 72 horas siguientes al descubrimiento, de acuerdo con las regulaciones aplicables.</p>
|
||||
</div>
|
||||
|
||||
<h2>15. 📜 Cumplimiento Regulatorio</h2>
|
||||
<p>Esta política cumple con:</p>
|
||||
<ul>
|
||||
<li><strong>GDPR:</strong> Reglamento General de Protección de Datos (UE)</li>
|
||||
<li><strong>CCPA:</strong> Ley de Privacidad del Consumidor de California</li>
|
||||
<li><strong>LGPD:</strong> Ley General de Protección de Datos (Brasil)</li>
|
||||
<li><strong>Ley 1581:</strong> Protección de Datos Personales (Colombia)</li>
|
||||
<li><strong>Políticas de Meta:</strong> Para uso de WhatsApp Business API</li>
|
||||
</ul>
|
||||
|
||||
<div class="footer">
|
||||
<p><strong>WhatsApp Bot Manager</strong> - Desarrollado por <a href="https://u-site.app" target="_blank">U-Site.app</a></p>
|
||||
<p>© 2026 U-Site.app. Todos los derechos reservados.</p>
|
||||
<p><em>Documento válido y público para revisión de Facebook/Meta</em></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Script para mejorar la accesibilidad y experiencia de usuario
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Smooth scrolling para enlaces internos
|
||||
const links = document.querySelectorAll('a[href^="#"]');
|
||||
links.forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Destacar sección actual en el scroll
|
||||
const sections = document.querySelectorAll('h2');
|
||||
const observerOptions = {
|
||||
threshold: 0.7
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.style.backgroundColor = '#f8f9ff';
|
||||
} else {
|
||||
entry.target.style.backgroundColor = 'transparent';
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
sections.forEach(section => {
|
||||
observer.observe(section);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* Configuración rápida manual - WhatsApp Bot Manager
|
||||
* Para usuarios que prefieren configurar manualmente sin usar .env
|
||||
*
|
||||
* INSTRUCCIONES:
|
||||
* 1. Completa los valores entre comillas
|
||||
* 2. Guarda el archivo
|
||||
* 3. Ejecuta http://tudominio.com/system_validator.php
|
||||
* 4. Si todo está en verde, ya puedes usar el sistema
|
||||
*/
|
||||
|
||||
// ==========================================
|
||||
// CONFIGURACIÓN DE BASE DE DATOS
|
||||
// ==========================================
|
||||
$manual_config = [
|
||||
// Información del servidor de base de datos
|
||||
'DB_HOST' => 'localhost', // Normalmente 'localhost'
|
||||
'DB_PORT' => '3306', // Puerto MySQL (normalmente 3306)
|
||||
'DB_NAME' => 'whatsapp_bot', // Nombre de tu base de datos
|
||||
'DB_USER' => 'tu_usuario_aqui', // Usuario de MySQL
|
||||
'DB_PASS' => 'tu_contraseña_aqui', // Contraseña de MySQL
|
||||
|
||||
// ==========================================
|
||||
// CONFIGURACIÓN DE WHATSAPP BUSINESS API
|
||||
// ==========================================
|
||||
'WHATSAPP_TOKEN' => 'TU_TOKEN_REAL_AQUI', // Token de acceso de Facebook
|
||||
'WHATSAPP_PHONE_NUMBER_ID' => 'TU_PHONE_ID_REAL_AQUI', // ID del número de teléfono
|
||||
'WEBHOOK_VERIFY_TOKEN' => 'mi_token_super_secreto_2026', // Token personalizado para webhook
|
||||
|
||||
// ==========================================
|
||||
// CONFIGURACIÓN GENERAL
|
||||
// ==========================================
|
||||
'APP_URL' => 'https://tudominio.com', // URL de tu sitio web
|
||||
'TIMEZONE' => 'America/Bogota', // Tu zona horaria
|
||||
'ADMIN_USERNAME' => 'admin', // Usuario administrador
|
||||
'ADMIN_PASSWORD' => 'password123', // Contraseña administrador (cámbiala!)
|
||||
|
||||
// ==========================================
|
||||
// CONFIGURACIÓN DE LOGS Y SEGURIDAD
|
||||
// ==========================================
|
||||
'ENABLE_LOGGING' => true, // Habilitar logs (recomendado: true)
|
||||
'LOG_LEVEL' => 'INFO', // Nivel de logs: DEBUG, INFO, WARNING, ERROR
|
||||
'SESSION_TIMEOUT' => 1800, // Timeout de sesión en segundos (30 min)
|
||||
'MAX_LOGIN_ATTEMPTS' => 3, // Intentos máximos de login
|
||||
];
|
||||
|
||||
// ==========================================
|
||||
// NO MODIFICAR DESDE AQUÍ HACIA ABAJO
|
||||
// ==========================================
|
||||
|
||||
// Aplicar configuración manual al entorno
|
||||
foreach ($manual_config as $key => $value) {
|
||||
if (!array_key_exists($key, $_ENV)) {
|
||||
$_ENV[$key] = $value;
|
||||
putenv("$key=$value");
|
||||
}
|
||||
}
|
||||
|
||||
// Incluir configuración principal
|
||||
require_once 'config/config.php';
|
||||
|
||||
// ==========================================
|
||||
// FUNCIONES DE AYUDA
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Función para mostrar el estado de la configuración
|
||||
*/
|
||||
function showConfigStatus() {
|
||||
echo "<h2>Estado de la Configuración Manual</h2>";
|
||||
echo "<table border='1' style='border-collapse: collapse; width: 100%;'>";
|
||||
echo "<tr><th style='padding: 10px;'>Variable</th><th style='padding: 10px;'>Valor</th><th style='padding: 10px;'>Estado</th></tr>";
|
||||
|
||||
$configs = [
|
||||
'DB_HOST' => DB_HOST,
|
||||
'DB_NAME' => DB_NAME,
|
||||
'DB_USER' => DB_USER,
|
||||
'DB_PASS' => DB_PASS ? '***CONFIGURADA***' : 'NO CONFIGURADA',
|
||||
'WHATSAPP_TOKEN' => (WHATSAPP_TOKEN !== 'TU_TOKEN_REAL_AQUI') ? '***CONFIGURADO***' : 'NO CONFIGURADO',
|
||||
'WHATSAPP_PHONE_NUMBER_ID' => (WHATSAPP_PHONE_NUMBER_ID !== 'TU_PHONE_ID_REAL_AQUI') ? '***CONFIGURADO***' : 'NO CONFIGURADO',
|
||||
'WEBHOOK_VERIFY_TOKEN' => WEBHOOK_VERIFY_TOKEN,
|
||||
'APP_URL' => APP_URL,
|
||||
'TIMEZONE' => TIMEZONE,
|
||||
];
|
||||
|
||||
foreach ($configs as $key => $value) {
|
||||
$status = (strpos($value, 'NO CONFIGURAD') !== false) ? '❌ Pendiente' : '✅ OK';
|
||||
$color = (strpos($value, 'NO CONFIGURAD') !== false) ? '#ffebee' : '#e8f5e8';
|
||||
echo "<tr style='background-color: $color;'>";
|
||||
echo "<td style='padding: 10px; font-weight: bold;'>$key</td>";
|
||||
echo "<td style='padding: 10px;'>$value</td>";
|
||||
echo "<td style='padding: 10px;'>$status</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
echo "</table>";
|
||||
|
||||
echo "<br><p><strong>Siguiente paso:</strong> <a href='system_validator.php'>Ejecutar Validador del Sistema</a></p>";
|
||||
}
|
||||
|
||||
// Si se accede directamente a este archivo, mostrar el estado
|
||||
if (basename(__FILE__) === basename($_SERVER['SCRIPT_NAME'])) {
|
||||
echo "<!DOCTYPE html>";
|
||||
echo "<html><head><title>Configuración Manual - WhatsApp Bot</title></head><body>";
|
||||
echo "<h1>🔧 Configuración Manual - WhatsApp Bot Manager</h1>";
|
||||
|
||||
showConfigStatus();
|
||||
|
||||
echo "<hr>";
|
||||
echo "<h3>📝 Instrucciones:</h3>";
|
||||
echo "<ol>";
|
||||
echo "<li>Abre el archivo <code>quick_config.php</code> en un editor de texto</li>";
|
||||
echo "<li>Busca la sección <code>\$manual_config</code> al inicio del archivo</li>";
|
||||
echo "<li>Completa todos los valores que digan 'TU_XXX_AQUI' con tus datos reales</li>";
|
||||
echo "<li>Guarda el archivo</li>";
|
||||
echo "<li>Recarga esta página para ver los cambios</li>";
|
||||
echo "<li>Una vez que todo esté en verde, ejecuta el <a href='system_validator.php'>Validador del Sistema</a></li>";
|
||||
echo "</ol>";
|
||||
|
||||
echo "<hr>";
|
||||
echo "<h3>🔗 Enlaces Útiles:</h3>";
|
||||
echo "<ul>";
|
||||
echo "<li><a href='system_validator.php'>🔍 Validador del Sistema</a></li>";
|
||||
echo "<li><a href='install.php'>⚙️ Instalador Automático</a></li>";
|
||||
echo "<li><a href='index.php'>🏠 Panel de Control</a></li>";
|
||||
echo "<li><a href='README_SETUP.md' target='_blank'>📚 Guía Completa</a></li>";
|
||||
echo "</ul>";
|
||||
|
||||
echo "<hr>";
|
||||
echo "<p><small>WhatsApp Bot Manager - U-Site.app - 2 de enero de 2026</small></p>";
|
||||
echo "</body></html>";
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
/**
|
||||
* Utilidad de Recuperación de Contraseña - WhatsApp Bot Manager
|
||||
* Fecha: 4 de enero de 2026
|
||||
*/
|
||||
|
||||
require_once 'config/config.php';
|
||||
|
||||
// Solo permitir acceso desde localhost o IP específicas para 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>🔑 Recuperar Contraseña - 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: 600px;
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🔑 Recuperar Contraseña de Administrador</h1>
|
||||
|
||||
<div class="warning-box">
|
||||
<strong>⚠️ Advertencia de Seguridad:</strong>
|
||||
<p>Esta utilidad solo debe usarse desde el servidor local y debe eliminarse después del uso. Nunca la dejes accesible públicamente.</p>
|
||||
</div>
|
||||
|
||||
<div class="current-info">
|
||||
<h3>📋 Información Actual del Sistema:</h3>
|
||||
<p><strong>Usuario administrador:</strong> <code><?php echo ADMIN_USERNAME; ?></code></p>
|
||||
<p><strong>Hash actual:</strong> <code><?php echo substr(ADMIN_PASSWORD, 0, 30); ?>...</code></p>
|
||||
<p><strong>IP de acceso:</strong> <code><?php echo $clientIP; ?></code></p>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['action'])) {
|
||||
|
||||
if ($_POST['action'] === 'reset_password') {
|
||||
$newPassword = $_POST['new_password'] ?? '';
|
||||
$confirmPassword = $_POST['confirm_password'] ?? '';
|
||||
|
||||
if (empty($newPassword) || empty($confirmPassword)) {
|
||||
echo '<div class="error-box">❌ <strong>Error:</strong> Todos los campos son requeridos.</div>';
|
||||
} elseif ($newPassword !== $confirmPassword) {
|
||||
echo '<div class="error-box">❌ <strong>Error:</strong> Las contraseñas no coinciden.</div>';
|
||||
} elseif (strlen($newPassword) < 6) {
|
||||
echo '<div class="error-box">❌ <strong>Error:</strong> La contraseña debe tener al menos 6 caracteres.</div>';
|
||||
} else {
|
||||
// Generar nuevo hash
|
||||
$newHash = password_hash($newPassword, PASSWORD_DEFAULT);
|
||||
|
||||
// Leer el archivo config.php
|
||||
$configFile = 'config/config.php';
|
||||
$configContent = file_get_contents($configFile);
|
||||
|
||||
// Reemplazar la línea del password
|
||||
$oldLine = "define('ADMIN_PASSWORD', '" . ADMIN_PASSWORD . "');";
|
||||
$newLine = "define('ADMIN_PASSWORD', '" . $newHash . "');";
|
||||
|
||||
$newConfigContent = str_replace($oldLine, $newLine, $configContent);
|
||||
|
||||
if ($newConfigContent !== $configContent && file_put_contents($configFile, $newConfigContent)) {
|
||||
echo '<div class="info-box">✅ <strong>¡Éxito!</strong> La contraseña ha sido actualizada correctamente.</div>';
|
||||
echo '<div class="info-box">🔄 <strong>Importante:</strong> Recarga la página para ver los cambios reflejados.</div>';
|
||||
|
||||
// Log del cambio
|
||||
$logEntry = "[" . date('Y-m-d H:i:s') . "] Password reset from IP: " . $clientIP . PHP_EOL;
|
||||
file_put_contents('logs/system.log', $logEntry, FILE_APPEND | LOCK_EX);
|
||||
} else {
|
||||
echo '<div class="error-box">❌ <strong>Error:</strong> No se pudo actualizar el archivo de configuración. Verifica permisos de escritura.</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'verify_password') {
|
||||
$testPassword = $_POST['test_password'] ?? '';
|
||||
|
||||
if (password_verify($testPassword, ADMIN_PASSWORD)) {
|
||||
echo '<div class="info-box">✅ <strong>Correcto:</strong> La contraseña ingresada es válida.</div>';
|
||||
} else {
|
||||
echo '<div class="error-box">❌ <strong>Incorrecto:</strong> La contraseña ingresada no es válida.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($_POST['action'] === 'clear_login_attempts') {
|
||||
$attemptsFile = '.login_attempts.json';
|
||||
if (file_exists($attemptsFile)) {
|
||||
unlink($attemptsFile);
|
||||
echo '<div class="info-box">✅ <strong>Éxito:</strong> Intentos de login bloqueados eliminados.</div>';
|
||||
} else {
|
||||
echo '<div class="warning-box">⚠️ <strong>Info:</strong> No hay intentos de login bloqueados.</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- Formulario para verificar contraseña actual -->
|
||||
<form method="post" style="margin-bottom: 30px;">
|
||||
<h3>🔍 Verificar Contraseña Actual</h3>
|
||||
<div class="form-group">
|
||||
<label for="test_password">Contraseña a verificar:</label>
|
||||
<input type="password" id="test_password" name="test_password" required>
|
||||
</div>
|
||||
<input type="hidden" name="action" value="verify_password">
|
||||
<button type="submit" class="btn">🔍 Verificar Contraseña</button>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Formulario para cambiar contraseña -->
|
||||
<form method="post">
|
||||
<h3>🔄 Establecer Nueva Contraseña</h3>
|
||||
<div class="form-group">
|
||||
<label for="new_password">Nueva contraseña:</label>
|
||||
<input type="password" id="new_password" name="new_password" required minlength="6">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirm_password">Confirmar contraseña:</label>
|
||||
<input type="password" id="confirm_password" name="confirm_password" required minlength="6">
|
||||
</div>
|
||||
<input type="hidden" name="action" value="reset_password">
|
||||
<button type="submit" class="btn">🔄 Cambiar Contraseña</button>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Opciones adicionales -->
|
||||
<h3>🛠️ Herramientas Adicionales</h3>
|
||||
|
||||
<form method="post" style="margin-bottom: 20px;">
|
||||
<input type="hidden" name="action" value="clear_login_attempts">
|
||||
<button type="submit" class="btn btn-danger">🗑️ Limpiar Intentos de Login Bloqueados</button>
|
||||
</form>
|
||||
|
||||
<div class="info-box">
|
||||
<h4>📝 Información Útil:</h4>
|
||||
<ul>
|
||||
<li><strong>Usuario por defecto:</strong> <code>admin</code></li>
|
||||
<li><strong>Archivo de configuración:</strong> <code>config/config.php</code></li>
|
||||
<li><strong>Página de login:</strong> <a href="login.php">login.php</a></li>
|
||||
<li><strong>Timeout de sesión:</strong> <?php echo SESSION_TIMEOUT/60; ?> minutos</li>
|
||||
<li><strong>Intentos máximos:</strong> <?php echo MAX_LOGIN_ATTEMPTS; ?> intentos</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="warning-box">
|
||||
<h4>🔒 Recomendaciones de Seguridad:</h4>
|
||||
<ul>
|
||||
<li>Usa una contraseña fuerte con al menos 8 caracteres</li>
|
||||
<li>Incluye mayúsculas, minúsculas, números y símbolos</li>
|
||||
<li>No uses la misma contraseña en otros sitios</li>
|
||||
<li>Elimina este archivo después de usar: <code>rm recuperar_password.php</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="current-info" style="margin-top: 30px;">
|
||||
<h4>🚀 Próximos Pasos:</h4>
|
||||
<ol>
|
||||
<li>Cambia la contraseña usando el formulario de arriba</li>
|
||||
<li>Prueba el login en <a href="login.php" target="_blank">login.php</a></li>
|
||||
<li>Elimina este archivo por seguridad</li>
|
||||
<li>Accede al panel de administración</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?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';
|
||||
?>
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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";
|
||||
?>
|
||||
@@ -0,0 +1,332 @@
|
||||
<?php
|
||||
/**
|
||||
* Solucionador de problemas WhatsApp API
|
||||
* Fecha: 5 de enero de 2026
|
||||
*/
|
||||
|
||||
require_once 'config/config.php';
|
||||
|
||||
// Verificar autenticación
|
||||
if (!isUserLoggedIn()) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
|
||||
$success = '';
|
||||
$error = '';
|
||||
|
||||
// Procesar formulario
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
try {
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
if ($action === 'update_config') {
|
||||
$newToken = trim($_POST['whatsapp_token'] ?? '');
|
||||
$newPhoneId = trim($_POST['phone_number_id'] ?? '');
|
||||
$newApiUrl = trim($_POST['api_url'] ?? WHATSAPP_API_URL);
|
||||
$newWebhookToken = trim($_POST['webhook_token'] ?? WEBHOOK_VERIFY_TOKEN);
|
||||
|
||||
if (empty($newToken) || empty($newPhoneId)) {
|
||||
throw new Exception('Token y Phone Number ID son obligatorios');
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
// Actualizar en base de datos
|
||||
$configs = [
|
||||
'whatsapp_token' => $newToken,
|
||||
'whatsapp_phone_number_id' => $newPhoneId,
|
||||
'whatsapp_api_url' => $newApiUrl,
|
||||
'webhook_verify_token' => $newWebhookToken
|
||||
];
|
||||
|
||||
foreach ($configs as $key => $value) {
|
||||
$existing = $db->fetch("SELECT id FROM system_config WHERE config_key = ?", [$key]);
|
||||
|
||||
if ($existing) {
|
||||
$db->query("UPDATE system_config SET config_value = ?, updated_at = NOW() WHERE config_key = ?", [$value, $key]);
|
||||
} else {
|
||||
$db->query("INSERT INTO system_config (config_key, config_value, created_at, updated_at) VALUES (?, ?, NOW(), NOW())", [$key, $value]);
|
||||
}
|
||||
}
|
||||
|
||||
$success = 'Configuración actualizada correctamente. <a href="#test">Probar conexión</a>';
|
||||
|
||||
} elseif ($action === 'test_connection') {
|
||||
// Probar conexión con los valores actuales
|
||||
$testResult = testWhatsAppConnection();
|
||||
if ($testResult['success']) {
|
||||
$success = 'Conexión exitosa: ' . $testResult['message'];
|
||||
} else {
|
||||
$error = 'Error en conexión: ' . $testResult['message'];
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$error = 'Error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
function testWhatsAppConnection() {
|
||||
try {
|
||||
$token = getConfigFromDB('whatsapp_token', WHATSAPP_TOKEN);
|
||||
$phoneId = getConfigFromDB('whatsapp_phone_number_id', WHATSAPP_PHONE_NUMBER_ID);
|
||||
$apiUrl = getConfigFromDB('whatsapp_api_url', WHATSAPP_API_URL);
|
||||
|
||||
if ($token === 'TU_TOKEN_DE_WHATSAPP_AQUI' || $phoneId === 'TU_PHONE_ID_AQUI') {
|
||||
return ['success' => false, 'message' => 'Configuración incompleta'];
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
$url = $apiUrl . $phoneId;
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Content-Type: application/json'
|
||||
],
|
||||
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) {
|
||||
return ['success' => false, 'message' => 'Error cURL: ' . $error];
|
||||
}
|
||||
|
||||
$decoded = json_decode($response, true);
|
||||
|
||||
if ($httpCode === 200) {
|
||||
$displayName = $decoded['display_phone_number'] ?? 'N/A';
|
||||
return ['success' => true, 'message' => "Número verificado: $displayName"];
|
||||
} else {
|
||||
$errorMsg = $decoded['error']['message'] ?? 'Error desconocido';
|
||||
return ['success' => false, 'message' => $errorMsg];
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
return ['success' => false, 'message' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Solucionador WhatsApp API</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>
|
||||
.status-card { border-left: 5px solid #dc3545; }
|
||||
.status-card.success { border-left-color: #28a745; }
|
||||
.status-card.warning { border-left-color: #ffc107; }
|
||||
.step-card { margin-bottom: 1rem; transition: all 0.3s; }
|
||||
.step-card:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
|
||||
.code-block { background: #f8f9fa; border-radius: 8px; padding: 15px; font-family: monospace; }
|
||||
.whatsapp-green { background: linear-gradient(135deg, #25D366, #128C7E); }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<div class="container my-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="card whatsapp-green text-white mb-4">
|
||||
<div class="card-body text-center">
|
||||
<h1><i class="fab fa-whatsapp me-2"></i>Solucionador WhatsApp API</h1>
|
||||
<p class="mb-0">Diagnóstico y configuración automática</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alertas -->
|
||||
<?php if ($success): ?>
|
||||
<div class="alert alert-success">
|
||||
<i class="fas fa-check-circle me-2"></i><?= $success ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i><?= $error ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Estado Actual -->
|
||||
<div class="card status-card mb-4">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-info-circle me-2"></i>Estado Actual del Sistema</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6>Configuración Actual:</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><strong>Token:</strong>
|
||||
<?php if (WHATSAPP_TOKEN !== 'TU_TOKEN_DE_WHATSAPP_AQUI'): ?>
|
||||
<span class="text-success">✅ Configurado</span>
|
||||
<?php else: ?>
|
||||
<span class="text-danger">❌ No configurado</span>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<li><strong>Phone ID:</strong>
|
||||
<?php if (WHATSAPP_PHONE_NUMBER_ID !== 'TU_PHONE_ID_AQUI'): ?>
|
||||
<span class="text-warning">⚠️ <?= WHATSAPP_PHONE_NUMBER_ID ?></span>
|
||||
<?php if (WHATSAPP_PHONE_NUMBER_ID === '858157464051987'): ?>
|
||||
<br><small class="text-danger">Este ID está causando errores</small>
|
||||
<?php endif; ?>
|
||||
<?php else: ?>
|
||||
<span class="text-danger">❌ No configurado</span>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<li><strong>API URL:</strong> <?= WHATSAPP_API_URL ?></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>Error Reportado:</h6>
|
||||
<div class="code-block">
|
||||
<small>Object with ID '858157464051987' does not exist, cannot be loaded due to missing permissions, or does not support this operation.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pasos de Solución -->
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card step-card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h6><i class="fas fa-cog me-2"></i>Paso 1: Actualizar Configuración</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="update_config">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Token de WhatsApp Business API</label>
|
||||
<input type="text" class="form-control" name="whatsapp_token"
|
||||
value="<?= htmlspecialchars(getConfigFromDB('whatsapp_token', WHATSAPP_TOKEN)) ?>"
|
||||
placeholder="EAAxxxxxxxxx..." required>
|
||||
<small class="text-muted">Obténlo desde Facebook for Developers</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Phone Number ID</label>
|
||||
<input type="text" class="form-control" name="phone_number_id"
|
||||
value="<?= htmlspecialchars(getConfigFromDB('whatsapp_phone_number_id', WHATSAPP_PHONE_NUMBER_ID)) ?>"
|
||||
placeholder="123456789012345" required>
|
||||
<small class="text-muted">ID del número verificado en Meta</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">API URL</label>
|
||||
<input type="text" class="form-control" name="api_url"
|
||||
value="<?= htmlspecialchars(getConfigFromDB('whatsapp_api_url', WHATSAPP_API_URL)) ?>">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Webhook Verify Token</label>
|
||||
<input type="text" class="form-control" name="webhook_token"
|
||||
value="<?= htmlspecialchars(getConfigFromDB('webhook_verify_token', WEBHOOK_VERIFY_TOKEN)) ?>">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save me-2"></i>Guardar Configuración
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card step-card">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h6><i class="fas fa-test-tube me-2"></i>Paso 2: Probar Conexión</h6>
|
||||
</div>
|
||||
<div class="card-body" id="test">
|
||||
<p>Una vez guardada la configuración, prueba la conexión:</p>
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="test_connection">
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="fas fa-plug me-2"></i>Probar Conexión
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<h6>Guía de obtención de credenciales:</h6>
|
||||
<ol class="small">
|
||||
<li>Ve a <a href="https://developers.facebook.com/apps" target="_blank">Facebook for Developers</a></li>
|
||||
<li>Selecciona tu aplicación WhatsApp Business</li>
|
||||
<li>Ve a "WhatsApp" → "Configuración"</li>
|
||||
<li>Copia el "Token de acceso" y el "Phone Number ID"</li>
|
||||
<li>Verifica que el número esté validado y activo</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Información Adicional -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-lightbulb me-2"></i>Posibles Causas del Error</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<h6 class="text-danger">❌ ID Incorrecto</h6>
|
||||
<p class="small">El Phone Number ID no corresponde a ningún número verificado en tu cuenta de Meta Business.</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h6 class="text-warning">⚠️ Sin Permisos</h6>
|
||||
<p class="small">El token de acceso no tiene permisos para acceder a este número específico.</p>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h6 class="text-info">🔄 Número No Verificado</h6>
|
||||
<p class="small">El número de teléfono no está completamente verificado en Meta Business Manager.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info mt-3">
|
||||
<h6><i class="fas fa-info-circle me-2"></i>Solución Recomendada:</h6>
|
||||
<p class="mb-0">
|
||||
1. Verifica en Meta for Developers que el número esté activo<br>
|
||||
2. Copia el Phone Number ID correcto (no uses el del ejemplo)<br>
|
||||
3. Asegúrate de que el token tenga permisos <code>whatsapp_business_messaging</code><br>
|
||||
4. Prueba la conexión antes de enviar mensajes
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enlaces útiles -->
|
||||
<div class="text-center">
|
||||
<a href="index.php" class="btn btn-outline-primary me-2">
|
||||
<i class="fas fa-arrow-left me-2"></i>Volver al Panel
|
||||
</a>
|
||||
<a href="diagnostico_whatsapp_avanzado.php" class="btn btn-outline-info me-2">
|
||||
<i class="fas fa-search me-2"></i>Diagnóstico Completo
|
||||
</a>
|
||||
<a href="test_whatsapp_completo.php" class="btn btn-outline-success">
|
||||
<i class="fas fa-vial me-2"></i>Pruebas Avanzadas
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,321 @@
|
||||
<?php
|
||||
/**
|
||||
* Validador del sistema WhatsApp Bot Manager
|
||||
* Verifica que todos los componentes estén funcionando correctamente
|
||||
* Fecha: 2 de enero de 2026
|
||||
*/
|
||||
|
||||
require_once 'config/config.php';
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Validación del Sistema - 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">
|
||||
<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; }
|
||||
.test-info { background-color: #d1ecf1; border-color: #bee5eb; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h1 class="text-center mb-4">
|
||||
<i class="fas fa-check-circle text-primary"></i>
|
||||
Validación del Sistema WhatsApp Bot
|
||||
</h1>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-info-circle"></i> Información del Sistema</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p><strong>Versión:</strong> <?= APP_VERSION ?></p>
|
||||
<p><strong>Desarrollador:</strong> <?= DEVELOPER_NAME ?></p>
|
||||
<p><strong>URL Base:</strong> <?= AUTO_DETECTED_URL ?></p>
|
||||
<p><strong>Zona Horaria:</strong> <?= TIMEZONE ?></p>
|
||||
<p><strong>Fecha/Hora:</strong> <?= date('Y-m-d H:i:s') ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$tests = [];
|
||||
|
||||
// Test 1: PHP Version
|
||||
$phpVersion = phpversion();
|
||||
$phpOk = version_compare($phpVersion, '8.0.0', '>=');
|
||||
$tests[] = [
|
||||
'name' => 'Versión de PHP',
|
||||
'status' => $phpOk ? 'success' : 'error',
|
||||
'message' => "PHP $phpVersion " . ($phpOk ? '✓' : '✗ (Se requiere PHP 8.0+)'),
|
||||
'details' => $phpOk ? 'Versión compatible' : 'Actualiza a PHP 8.0 o superior'
|
||||
];
|
||||
|
||||
// Test 2: Extensiones PHP
|
||||
$requiredExtensions = ['pdo', 'pdo_mysql', 'curl', 'json', 'mbstring', 'openssl'];
|
||||
$missingExtensions = [];
|
||||
foreach ($requiredExtensions as $ext) {
|
||||
if (!extension_loaded($ext)) {
|
||||
$missingExtensions[] = $ext;
|
||||
}
|
||||
}
|
||||
$extOk = empty($missingExtensions);
|
||||
$tests[] = [
|
||||
'name' => 'Extensiones PHP',
|
||||
'status' => $extOk ? 'success' : 'error',
|
||||
'message' => $extOk ? 'Todas las extensiones están disponibles ✓' : 'Faltan extensiones: ' . implode(', ', $missingExtensions),
|
||||
'details' => $extOk ? 'PDO, cURL, JSON, mbstring, OpenSSL' : 'Instala las extensiones faltantes'
|
||||
];
|
||||
|
||||
// Test 3: Conexión a Base de Datos
|
||||
$dbOk = false;
|
||||
$dbMessage = '';
|
||||
$dbDetails = '';
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
$result = $db->fetch("SELECT 1 as test");
|
||||
$dbOk = ($result['test'] == 1);
|
||||
$dbMessage = 'Conexión exitosa ✓';
|
||||
$dbDetails = 'Host: ' . DB_HOST . ', DB: ' . DB_NAME;
|
||||
} catch (Exception $e) {
|
||||
$dbMessage = 'Error de conexión: ' . $e->getMessage();
|
||||
$dbDetails = 'Verifica la configuración en config.php';
|
||||
}
|
||||
$tests[] = [
|
||||
'name' => 'Base de Datos',
|
||||
'status' => $dbOk ? 'success' : 'error',
|
||||
'message' => $dbMessage,
|
||||
'details' => $dbDetails
|
||||
];
|
||||
|
||||
// Test 4: Estructura de tablas (solo si DB está OK)
|
||||
$tablesOk = false;
|
||||
$tablesMessage = '';
|
||||
$tablesDetails = '';
|
||||
if ($dbOk) {
|
||||
try {
|
||||
$requiredTables = ['users', 'conversations', 'menus', 'menu_options', 'auto_responses', 'system_config'];
|
||||
$db = Database::getInstance();
|
||||
$existingTables = [];
|
||||
$missingTables = [];
|
||||
|
||||
foreach ($requiredTables as $table) {
|
||||
$result = $db->fetch("SHOW TABLES LIKE '$table'");
|
||||
if ($result) {
|
||||
$existingTables[] = $table;
|
||||
} else {
|
||||
$missingTables[] = $table;
|
||||
}
|
||||
}
|
||||
|
||||
$tablesOk = empty($missingTables);
|
||||
$tablesMessage = $tablesOk ? 'Todas las tablas existen ✓' : 'Faltan tablas: ' . implode(', ', $missingTables);
|
||||
$tablesDetails = 'Tablas encontradas: ' . count($existingTables) . '/' . count($requiredTables);
|
||||
} catch (Exception $e) {
|
||||
$tablesMessage = 'Error verificando tablas: ' . $e->getMessage();
|
||||
$tablesDetails = 'Ejecuta el instalador para crear las tablas';
|
||||
}
|
||||
} else {
|
||||
$tablesMessage = 'No se puede verificar (DB no disponible)';
|
||||
$tablesDetails = 'Primero arregla la conexión a la base de datos';
|
||||
}
|
||||
$tests[] = [
|
||||
'name' => 'Estructura de Base de Datos',
|
||||
'status' => $tablesOk ? 'success' : ($dbOk ? 'error' : 'warning'),
|
||||
'message' => $tablesMessage,
|
||||
'details' => $tablesDetails
|
||||
];
|
||||
|
||||
// Test 5: Archivos críticos
|
||||
$criticalFiles = [
|
||||
'classes/Database.php',
|
||||
'services/BotService.php',
|
||||
'services/WhatsAppService.php',
|
||||
'api/webhook.php',
|
||||
'api/send_message.php'
|
||||
];
|
||||
$missingFiles = [];
|
||||
foreach ($criticalFiles as $file) {
|
||||
if (!file_exists($file)) {
|
||||
$missingFiles[] = $file;
|
||||
}
|
||||
}
|
||||
$filesOk = empty($missingFiles);
|
||||
$tests[] = [
|
||||
'name' => 'Archivos Críticos',
|
||||
'status' => $filesOk ? 'success' : 'error',
|
||||
'message' => $filesOk ? 'Todos los archivos críticos están presentes ✓' : 'Faltan archivos: ' . implode(', ', $missingFiles),
|
||||
'details' => 'Classes, Services, APIs'
|
||||
];
|
||||
|
||||
// Test 6: Permisos de archivos
|
||||
$logsDir = 'logs';
|
||||
$logsWritable = is_dir($logsDir) && is_writable($logsDir);
|
||||
$tests[] = [
|
||||
'name' => 'Permisos de Escritura',
|
||||
'status' => $logsWritable ? 'success' : 'warning',
|
||||
'message' => $logsWritable ? 'Directorio de logs escribible ✓' : 'Directorio de logs no escribible',
|
||||
'details' => $logsWritable ? 'Los logs se pueden escribir correctamente' : 'chmod 755 en el directorio logs'
|
||||
];
|
||||
|
||||
// Test 7: Configuración WhatsApp
|
||||
$whatsappErrors = validateWhatsAppConfig();
|
||||
$whatsappOk = empty($whatsappErrors);
|
||||
$tests[] = [
|
||||
'name' => 'Configuración WhatsApp',
|
||||
'status' => $whatsappOk ? 'success' : 'warning',
|
||||
'message' => $whatsappOk ? 'WhatsApp configurado correctamente ✓' : 'Configuración pendiente',
|
||||
'details' => $whatsappOk ? 'Token, Phone ID y Webhook configurados' : implode(', ', $whatsappErrors)
|
||||
];
|
||||
|
||||
// Test 8: Estado de instalación
|
||||
$installationCompleted = isInstallationCompleted();
|
||||
$tests[] = [
|
||||
'name' => 'Estado de Instalación',
|
||||
'status' => $installationCompleted ? 'success' : 'info',
|
||||
'message' => $installationCompleted ? 'Instalación completada ✓' : 'Instalación pendiente',
|
||||
'details' => $installationCompleted ? 'Sistema listo para usar' : 'Ejecuta install.php para completar'
|
||||
];
|
||||
|
||||
// Test 9: Conectividad cURL
|
||||
$curlOk = false;
|
||||
$curlMessage = '';
|
||||
if (function_exists('curl_version')) {
|
||||
$curlInfo = curl_version();
|
||||
$curlOk = true;
|
||||
$curlMessage = 'cURL disponible v' . $curlInfo['version'] . ' ✓';
|
||||
} else {
|
||||
$curlMessage = 'cURL no disponible';
|
||||
}
|
||||
$tests[] = [
|
||||
'name' => 'Conectividad cURL',
|
||||
'status' => $curlOk ? 'success' : 'error',
|
||||
'message' => $curlMessage,
|
||||
'details' => $curlOk ? 'Puede comunicarse con APIs externas' : 'Se requiere cURL para WhatsApp API'
|
||||
];
|
||||
|
||||
// Test 10: Memoria y límites PHP
|
||||
$memoryLimit = ini_get('memory_limit');
|
||||
$maxExecution = ini_get('max_execution_time');
|
||||
$postMaxSize = ini_get('post_max_size');
|
||||
$uploadMaxSize = ini_get('upload_max_filesize');
|
||||
|
||||
$tests[] = [
|
||||
'name' => 'Límites PHP',
|
||||
'status' => 'info',
|
||||
'message' => 'Configuración actual del servidor',
|
||||
'details' => "Memoria: $memoryLimit, Ejecución: {$maxExecution}s, POST: $postMaxSize, Upload: $uploadMaxSize"
|
||||
];
|
||||
|
||||
// Mostrar resultados
|
||||
foreach ($tests as $test) {
|
||||
$statusClass = 'test-' . $test['status'];
|
||||
$iconClass = [
|
||||
'success' => 'fa-check-circle text-success',
|
||||
'error' => 'fa-times-circle text-danger',
|
||||
'warning' => 'fa-exclamation-triangle text-warning',
|
||||
'info' => 'fa-info-circle text-info'
|
||||
][$test['status']];
|
||||
|
||||
echo "<div class='test-item $statusClass'>";
|
||||
echo "<h6><i class='fas $iconClass'></i> {$test['name']}</h6>";
|
||||
echo "<p class='mb-1'><strong>{$test['message']}</strong></p>";
|
||||
echo "<small class='text-muted'>{$test['details']}</small>";
|
||||
echo "</div>";
|
||||
}
|
||||
|
||||
// Resumen
|
||||
$totalTests = count($tests);
|
||||
$successCount = count(array_filter($tests, fn($t) => $t['status'] === 'success'));
|
||||
$errorCount = count(array_filter($tests, fn($t) => $t['status'] === 'error'));
|
||||
$warningCount = count(array_filter($tests, fn($t) => $t['status'] === 'warning'));
|
||||
$infoCount = count(array_filter($tests, fn($t) => $t['status'] === 'info'));
|
||||
?>
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-chart-pie"></i> Resumen de Validación</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row text-center">
|
||||
<div class="col-md-3">
|
||||
<h3 class="text-success"><?= $successCount ?></h3>
|
||||
<small>Exitosos</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h3 class="text-danger"><?= $errorCount ?></h3>
|
||||
<small>Errores</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h3 class="text-warning"><?= $warningCount ?></h3>
|
||||
<small>Advertencias</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h3 class="text-info"><?= $infoCount ?></h3>
|
||||
<small>Información</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="text-center">
|
||||
<?php if ($errorCount === 0): ?>
|
||||
<h4 class="text-success">
|
||||
<i class="fas fa-thumbs-up"></i>
|
||||
Sistema Validado Correctamente
|
||||
</h4>
|
||||
<p class="text-muted">Tu instalación de WhatsApp Bot está lista para funcionar</p>
|
||||
<a href="index.php" class="btn btn-success btn-lg">
|
||||
<i class="fas fa-rocket"></i>
|
||||
Acceder al Sistema
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<h4 class="text-warning">
|
||||
<i class="fas fa-tools"></i>
|
||||
Se Requiere Configuración
|
||||
</h4>
|
||||
<p class="text-muted">Hay <?= $errorCount ?> error(es) que deben solucionarse antes de continuar</p>
|
||||
<a href="instalacion.html" class="btn btn-primary btn-lg">
|
||||
<i class="fas fa-cog"></i>
|
||||
Ir al Instalador
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<small class="text-muted">
|
||||
WhatsApp Bot Manager v<?= APP_VERSION ?> -
|
||||
<a href="<?= DEVELOPER_URL ?>" target="_blank"><?= DEVELOPER_NAME ?></a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// Auto-refresh cada 30 segundos si hay errores
|
||||
<?php if ($errorCount > 0): ?>
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 30000);
|
||||
<?php endif; ?>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -151,11 +151,26 @@ try {
|
||||
echo "<h2>🔗 URLs del Sistema:</h2>";
|
||||
echo "<ul>";
|
||||
echo "<li><strong>Panel Principal:</strong> <a href='index.php'>index.php</a></li>";
|
||||
echo "<li><strong>🧪 SUITE DE TESTS COMPLETA:</strong> <a href='run_tests.php' class='btn btn-primary btn-sm'>run_tests.php</a></li>";
|
||||
echo "<li><strong>Webhook:</strong> <a href='api/webhook.php?hub_mode=subscribe&hub_verify_token=mi_token_secreto_123&hub_challenge=test'>webhook.php (test)</a></li>";
|
||||
echo "<li><strong>API Stats:</strong> <a href='api/get_stats.php'>get_stats.php</a></li>";
|
||||
echo "<li><strong>API Users:</strong> <a href='api/get_users.php'>get_users.php</a></li>";
|
||||
echo "</ul>";
|
||||
|
||||
echo "<div class='alert alert-info mt-4'>";
|
||||
echo "<h5><i class='fas fa-rocket'></i> Nueva Suite de Tests Implementada</h5>";
|
||||
echo "<p>Se ha implementado una <strong>suite completa de tests</strong> que cubre:</p>";
|
||||
echo "<ul>";
|
||||
echo "<li>✅ <strong>50+ Tests</strong> exhaustivos</li>";
|
||||
echo "<li>🔌 <strong>17 APIs</strong> validadas</li>";
|
||||
echo "<li>🔒 <strong>Seguridad</strong> completa</li>";
|
||||
echo "<li>📝 <strong>Sistema de plantillas</strong></li>";
|
||||
echo "<li>🔗 <strong>Webhook y servicios</strong></li>";
|
||||
echo "<li>💾 <strong>Base de datos</strong></li>";
|
||||
echo "</ul>";
|
||||
echo "<p class='mb-0'><a href='run_tests.php' class='btn btn-success'><i class='fas fa-play'></i> Ejecutar Tests Completos</a></p>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<h2>📋 Resumen:</h2>";
|
||||
echo "<p><strong>El sistema WhatsApp Bot está:</strong></p>";
|
||||
|
||||
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
/**
|
||||
* 🧪 TEST API - WhatsApp Bot
|
||||
* Prueba específica de endpoints 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>🧪 Test 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 ".endpoint{background:#333;padding:10px;margin:5px 0;border-left:3px solid #ffaa00}";
|
||||
echo "</style></head><body>";
|
||||
|
||||
echo "<h1>🧪 TEST DE LA API</h1>";
|
||||
|
||||
// Lista de endpoints para probar
|
||||
$endpoints = [
|
||||
'get_settings.php' => 'Obtener configuración del sistema',
|
||||
'get_stats.php' => 'Obtener estadísticas',
|
||||
'get_users.php' => 'Obtener usuarios',
|
||||
'get_menus.php' => 'Obtener menús',
|
||||
'get_templates.php' => 'Obtener plantillas'
|
||||
];
|
||||
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>🚀 PROBANDO ENDPOINTS DE LA API...</h2>";
|
||||
|
||||
foreach ($endpoints as $endpoint => $description) {
|
||||
echo "<div class='endpoint'>";
|
||||
echo "<strong>🔗 $endpoint</strong> - $description<br>";
|
||||
|
||||
$api_url = 'api/' . $endpoint;
|
||||
|
||||
// Verificar que el archivo existe
|
||||
if (!file_exists($api_url)) {
|
||||
echo "<div class='error'>❌ Archivo no encontrado: $api_url</div>";
|
||||
continue;
|
||||
}
|
||||
|
||||
echo "<div class='ok'>✅ Archivo existe</div>";
|
||||
|
||||
// Intentar ejecutar el endpoint usando output buffering
|
||||
ob_start();
|
||||
|
||||
try {
|
||||
// Simular REQUEST_METHOD para endpoints que lo requieran
|
||||
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||
$_SERVER['HTTP_HOST'] = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||||
|
||||
// Capturar cualquier error
|
||||
$error_before = error_get_last();
|
||||
|
||||
// Incluir el archivo de la API
|
||||
include $api_url;
|
||||
|
||||
// Obtener la salida
|
||||
$output = ob_get_contents();
|
||||
|
||||
// Verificar errores
|
||||
$error_after = error_get_last();
|
||||
|
||||
if ($error_after && $error_after !== $error_before) {
|
||||
echo "<div class='error'>❌ Error PHP: " . htmlspecialchars($error_after['message']) . "</div>";
|
||||
echo "<div class='error'>Línea: " . $error_after['line'] . " en " . basename($error_after['file']) . "</div>";
|
||||
} else {
|
||||
// Verificar si la salida parece ser JSON válido
|
||||
if (!empty($output)) {
|
||||
$json_data = json_decode($output, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
echo "<div class='ok'>✅ Respuesta JSON válida (" . strlen($output) . " caracteres)</div>";
|
||||
|
||||
// Mostrar estructura básica
|
||||
if (is_array($json_data)) {
|
||||
$keys = array_keys($json_data);
|
||||
$preview_keys = array_slice($keys, 0, 3);
|
||||
echo "<div class='warning'>📋 Claves: " . implode(', ', $preview_keys);
|
||||
if (count($keys) > 3) echo " (+" . (count($keys) - 3) . " más)";
|
||||
echo "</div>";
|
||||
}
|
||||
} else {
|
||||
echo "<div class='warning'>⚠️ Salida no es JSON válido</div>";
|
||||
if (strlen($output) < 200) {
|
||||
echo "<div class='warning'>Salida: " . htmlspecialchars(substr($output, 0, 100)) . "</div>";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
echo "<div class='warning'>⚠️ Sin salida</div>";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (ParseError $e) {
|
||||
echo "<div class='error'>❌ Error de sintaxis: " . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
echo "<div class='error'>Línea: " . $e->getLine() . "</div>";
|
||||
} catch (Error $e) {
|
||||
echo "<div class='error'>❌ Error fatal: " . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
echo "<div class='error'>Archivo: " . basename($e->getFile()) . " línea " . $e->getLine() . "</div>";
|
||||
} catch (Exception $e) {
|
||||
echo "<div class='error'>❌ Excepción: " . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
} finally {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
// Test directo de carga de configuración desde API
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>⚙️ TEST DIRECTO DE CONFIGURACIÓN...</h2>";
|
||||
|
||||
echo "<div class='warning'>🔄 Simulando carga desde directorio API...</div>";
|
||||
|
||||
// Cambiar al directorio API temporalmente
|
||||
$original_dir = getcwd();
|
||||
|
||||
try {
|
||||
chdir('api');
|
||||
echo "<div class='ok'>✅ Cambiado a directorio API</div>";
|
||||
|
||||
// Intentar cargar la configuración
|
||||
if (file_exists('../config/config.php')) {
|
||||
echo "<div class='ok'>✅ ../config/config.php existe desde API</div>";
|
||||
|
||||
try {
|
||||
require_once '../config/config.php';
|
||||
echo "<div class='ok'>✅ Configuración cargada exitosamente</div>";
|
||||
|
||||
// Verificar algunas constantes
|
||||
$test_constants = ['DB_HOST', 'DB_NAME', 'DB_USER', 'SECRET_KEY'];
|
||||
foreach ($test_constants as $const) {
|
||||
if (defined($const)) {
|
||||
echo "<div class='ok'>✅ $const definida</div>";
|
||||
} else {
|
||||
echo "<div class='error'>❌ $const no definida</div>";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "<div class='error'>❌ Error cargando config: " . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
}
|
||||
|
||||
} else {
|
||||
echo "<div class='error'>❌ ../config/config.php NO existe desde API</div>";
|
||||
}
|
||||
|
||||
} finally {
|
||||
chdir($original_dir);
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
// Resumen final
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>📊 RESUMEN</h2>";
|
||||
|
||||
echo "<div class='ok'>✅ Rutas de API corregidas</div>";
|
||||
echo "<div class='ok'>✅ Todos los archivos usan '../config/config.php'</div>";
|
||||
|
||||
echo "<br><div class='warning'><strong>PRÓXIMOS PASOS:</strong></div>";
|
||||
echo "<div class='warning'>1. Verificar que las tablas existan: <a href='test_config.php' style='color:#ffaa00'>test_config.php</a></div>";
|
||||
echo "<div class='warning'>2. Si faltan tablas, ejecutar: <a href='instalar_bd.php' style='color:#ffaa00'>instalar_bd.php</a></div>";
|
||||
echo "<div class='warning'>3. Probar login: <a href='login.php' style='color:#ffaa00'>login.php</a></div>";
|
||||
echo "<div class='warning'>4. Probar panel: <a href='index.php' style='color:#ffaa00'>index.php</a></div>";
|
||||
|
||||
echo "</div>";
|
||||
|
||||
echo "</body></html>";
|
||||
?>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
session_start();
|
||||
$_SESSION['admin_logged_in'] = true;
|
||||
$_SESSION['admin_username'] = 'test_admin';
|
||||
|
||||
require_once 'api/manage_config.php';
|
||||
?>
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* TEST BÁSICO - Verificar que PHP funciona
|
||||
* Si ves este mensaje, PHP está funcionando correctamente
|
||||
*/
|
||||
|
||||
echo "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
|
||||
echo "<title>✅ Test Básico - WhatsApp Bot</title>";
|
||||
echo "<style>body{font-family:Arial,sans-serif;margin:40px;background:#f0f8ff}";
|
||||
echo ".success{color:#28a745;background:#d4edda;padding:20px;border-radius:10px;text-align:center}";
|
||||
echo ".info{color:#0c5460;background:#d1ecf1;padding:15px;border-radius:5px;margin:20px 0}";
|
||||
echo "h1{color:#333}</style></head><body>";
|
||||
|
||||
echo "<div class='success'>";
|
||||
echo "<h1>✅ ¡PHP FUNCIONANDO CORRECTAMENTE!</h1>";
|
||||
echo "<p><strong>Versión PHP:</strong> " . phpversion() . "</p>";
|
||||
echo "<p><strong>Fecha:</strong> " . date('Y-m-d H:i:s') . "</p>";
|
||||
echo "<p><strong>Servidor:</strong> " . ($_SERVER['SERVER_SOFTWARE'] ?? 'No disponible') . "</p>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='info'>";
|
||||
echo "<h2>📋 Próximos pasos:</h2>";
|
||||
echo "<ol>";
|
||||
echo "<li><strong>Subir archivo diagnóstico:</strong> <a href='diagnostico_500.php'>diagnostico_500.php</a></li>";
|
||||
echo "<li><strong>Verificar configuración:</strong> Revisar config/config.php</li>";
|
||||
echo "<li><strong>Probar login:</strong> <a href='login.php'>login.php</a></li>";
|
||||
echo "<li><strong>Dashboard principal:</strong> <a href='index.php'>index.php</a></li>";
|
||||
echo "</ol>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='info'>";
|
||||
echo "<h2>🔧 Si tienes Error 500:</h2>";
|
||||
echo "<p>1. <strong>Ejecuta diagnóstico:</strong> Ve a diagnostico_500.php</p>";
|
||||
echo "<p>2. <strong>Desactiva .htaccess:</strong> Renómbralo a .htaccess.bak</p>";
|
||||
echo "<p>3. <strong>Verifica permisos:</strong> chmod 777 logs/ uploads/</p>";
|
||||
echo "<p>4. <strong>Revisa logs:</strong> En tu panel de hosting</p>";
|
||||
echo "</div>";
|
||||
|
||||
// Test de escritura
|
||||
$test_file = 'logs/test_write.txt';
|
||||
if (is_dir('logs')) {
|
||||
if (is_writable('logs')) {
|
||||
if (file_put_contents($test_file, "Test: " . date('Y-m-d H:i:s'))) {
|
||||
echo "<div class='success'>✅ Directorio logs/ es escribible</div>";
|
||||
unlink($test_file); // Limpiar archivo de prueba
|
||||
} else {
|
||||
echo "<div class='info' style='color:#dc3545;background:#f8d7da'>❌ No se puede escribir en logs/</div>";
|
||||
}
|
||||
} else {
|
||||
echo "<div class='info' style='color:#dc3545;background:#f8d7da'>❌ Directorio logs/ no tiene permisos de escritura</div>";
|
||||
}
|
||||
} else {
|
||||
echo "<div class='info' style='color:#856404;background:#fff3cd'>⚠️ Directorio logs/ no existe</div>";
|
||||
}
|
||||
|
||||
echo "</body></html>";
|
||||
?>
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
/**
|
||||
* 🔧 TEST CONFIGURACIÓN - WhatsApp Bot
|
||||
* Prueba SOLO la configuración sin cargar todo 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>🔧 Test Configuración</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>🔧 TEST CONFIGURACIÓN</h1>";
|
||||
|
||||
// 1. Verificar que el archivo existe
|
||||
if (!file_exists('config/config.php')) {
|
||||
echo "<div class='error'>❌ CRÍTICO: config/config.php no encontrado</div>";
|
||||
echo "<div class='warning'>Asegúrate de que el archivo esté en: config/config.php</div>";
|
||||
exit;
|
||||
}
|
||||
|
||||
echo "<div class='ok'>✅ config/config.php encontrado</div>";
|
||||
|
||||
// 2. Verificar permisos del archivo
|
||||
if (!is_readable('config/config.php')) {
|
||||
echo "<div class='error'>❌ CRÍTICO: config/config.php no es legible</div>";
|
||||
echo "<div class='warning'>Ejecuta: chmod 644 config/config.php</div>";
|
||||
exit;
|
||||
}
|
||||
|
||||
echo "<div class='ok'>✅ config/config.php es legible</div>";
|
||||
|
||||
// 3. Intentar cargar el archivo
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>📋 CARGANDO CONFIGURACIÓN...</h2>";
|
||||
|
||||
try {
|
||||
// Capturar cualquier output
|
||||
ob_start();
|
||||
|
||||
// Capturar errores
|
||||
$error_before = error_get_last();
|
||||
|
||||
// Incluir el archivo
|
||||
require_once 'config/config.php';
|
||||
|
||||
// Obtener output
|
||||
$output = ob_get_clean();
|
||||
|
||||
// Verificar si hubo errores
|
||||
$error_after = error_get_last();
|
||||
|
||||
if ($error_after && $error_after !== $error_before) {
|
||||
echo "<div class='error'>❌ ERROR al cargar config: " . htmlspecialchars($error_after['message']) . "</div>";
|
||||
echo "<div class='warning'>Línea: " . $error_after['line'] . " en " . $error_after['file'] . "</div>";
|
||||
} elseif (!empty($output)) {
|
||||
echo "<div class='warning'>⚠️ ADVERTENCIA: config.php produce output:</div>";
|
||||
echo "<div class='warning'>" . htmlspecialchars($output) . "</div>";
|
||||
echo "<div class='warning'>Los archivos de configuración NO deben producir output</div>";
|
||||
} else {
|
||||
echo "<div class='ok'>✅ config/config.php cargado sin errores</div>";
|
||||
}
|
||||
|
||||
} catch (ParseError $e) {
|
||||
echo "<div class='error'>❌ ERROR DE SINTAXIS en config.php:</div>";
|
||||
echo "<div class='error'>" . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
echo "<div class='error'>Línea: " . $e->getLine() . "</div>";
|
||||
exit;
|
||||
} catch (Error $e) {
|
||||
echo "<div class='error'>❌ ERROR FATAL en config.php:</div>";
|
||||
echo "<div class='error'>" . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
echo "<div class='error'>❌ EXCEPCIÓN en config.php:</div>";
|
||||
echo "<div class='error'>" . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
exit;
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
// 4. Verificar constantes requeridas
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>🗄️ VERIFICANDO CONSTANTES DE BD...</h2>";
|
||||
|
||||
$required_constants = [
|
||||
'DB_HOST' => 'Servidor de base de datos',
|
||||
'DB_NAME' => 'Nombre de la base de datos',
|
||||
'DB_USER' => 'Usuario de la base de datos',
|
||||
'DB_PASS' => 'Contraseña de la base de datos',
|
||||
'DB_PORT' => 'Puerto de la base de datos',
|
||||
'DB_CHARSET' => 'Charset de la base de datos'
|
||||
];
|
||||
|
||||
$all_defined = true;
|
||||
|
||||
foreach ($required_constants as $const => $desc) {
|
||||
if (defined($const)) {
|
||||
$value = constant($const);
|
||||
$display_value = ($const === 'DB_PASS') ? str_repeat('*', strlen($value)) : $value;
|
||||
echo "<div class='ok'>✅ $const = '$display_value' ($desc)</div>";
|
||||
} else {
|
||||
echo "<div class='error'>❌ $const no definida ($desc)</div>";
|
||||
$all_defined = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$all_defined) {
|
||||
echo "<div class='error'>❌ CRÍTICO: Faltan constantes de base de datos</div>";
|
||||
echo "<div class='warning'>El sistema no puede funcionar sin estas constantes</div>";
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
// 5. Test de conexión a BD (solo si todas las constantes están)
|
||||
if ($all_defined) {
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>🔌 TEST CONEXIÓN BASE DE DATOS...</h2>";
|
||||
|
||||
try {
|
||||
$dsn = "mysql:host=" . DB_HOST . ";port=" . DB_PORT . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
|
||||
|
||||
echo "<div class='ok'>DSN: $dsn</div>";
|
||||
|
||||
$pdo = new PDO($dsn, DB_USER, DB_PASS, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_TIMEOUT => 5
|
||||
]);
|
||||
|
||||
echo "<div class='ok'>✅ CONEXIÓN EXITOSA a la base de datos</div>";
|
||||
|
||||
// Verificar algunas tablas
|
||||
$tables_to_check = ['conversations', 'messages', 'settings', 'message_templates'];
|
||||
foreach ($tables_to_check as $table) {
|
||||
try {
|
||||
$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 (Exception $e) {
|
||||
echo "<div class='error'>❌ Error verificando tabla '$table': " . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
echo "<div class='error'>❌ ERROR DE CONEXIÓN:</div>";
|
||||
echo "<div class='error'>" . htmlspecialchars($e->getMessage()) . "</div>";
|
||||
echo "<div class='warning'>Verifica:</div>";
|
||||
echo "<div class='warning'>• Host: " . DB_HOST . "</div>";
|
||||
echo "<div class='warning'>• Base datos: " . DB_NAME . "</div>";
|
||||
echo "<div class='warning'>• Usuario: " . DB_USER . "</div>";
|
||||
echo "<div class='warning'>• Puerto: " . DB_PORT . "</div>";
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
}
|
||||
|
||||
// 6. Verificar otras constantes importantes
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>🔑 OTRAS CONSTANTES DEL SISTEMA...</h2>";
|
||||
|
||||
$other_constants = [
|
||||
'ADMIN_USERNAME' => 'Usuario administrador',
|
||||
'ADMIN_PASSWORD' => 'Contraseña administrador',
|
||||
'SECRET_KEY' => 'Clave secreta',
|
||||
'WHATSAPP_TOKEN' => 'Token de WhatsApp',
|
||||
'WHATSAPP_PHONE_NUMBER_ID' => 'ID del número de teléfono'
|
||||
];
|
||||
|
||||
foreach ($other_constants as $const => $desc) {
|
||||
if (defined($const)) {
|
||||
$value = constant($const);
|
||||
if (in_array($const, ['ADMIN_PASSWORD', 'SECRET_KEY', 'WHATSAPP_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='warning'>⚠️ $const no definida ($desc)</div>";
|
||||
}
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
// 7. Resumen final
|
||||
echo "<div class='section'>";
|
||||
echo "<h2>📊 RESUMEN</h2>";
|
||||
|
||||
if ($all_defined) {
|
||||
echo "<div class='ok'>✅ Configuración básica completa</div>";
|
||||
echo "<div class='ok'>✅ Archivo config.php funcional</div>";
|
||||
echo "<div class='ok'>✅ Sistema puede intentar funcionar</div>";
|
||||
|
||||
echo "<br><div class='warning'><strong>SIGUIENTE PASO:</strong></div>";
|
||||
echo "<div class='warning'>• Probar: <a href='login.php' style='color:#ffaa00'>login.php</a></div>";
|
||||
echo "<div class='warning'>• Si funciona login, probar: <a href='index.php' style='color:#ffaa00'>index.php</a></div>";
|
||||
} else {
|
||||
echo "<div class='error'>❌ Configuración incompleta</div>";
|
||||
echo "<div class='error'>❌ Sistema NO puede funcionar</div>";
|
||||
echo "<div class='warning'>Corrige las constantes faltantes en config/config.php</div>";
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
echo "</body></html>";
|
||||
?>
|
||||
@@ -0,0 +1,452 @@
|
||||
<?php
|
||||
/**
|
||||
* Script de prueba para el sistema de configuraciones
|
||||
* Permite probar todas las funciones de manejo de configuraciones
|
||||
*/
|
||||
|
||||
require_once 'config/config.php';
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Prueba Sistema de Configuraciones</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
<h1 class="mb-4">🔧 Prueba Sistema de Configuraciones</h1>
|
||||
|
||||
<?php if (!isInstallationCompleted()): ?>
|
||||
<div class="alert alert-warning">
|
||||
<strong>⚠️ Instalación no completada:</strong> Algunas funciones pueden no estar disponibles.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Información del sistema -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5>📊 Estado del Sistema</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p><strong>Instalación completada:</strong> <?= isInstallationCompleted() ? '✅ Sí' : '❌ No' ?></p>
|
||||
<p><strong>Sistema migrado:</strong> <?= isAuthSystemMigrated() ? '✅ Sí' : '❌ No' ?></p>
|
||||
<p><strong>BD conectada:</strong>
|
||||
<?php
|
||||
try {
|
||||
$pdo = createDbConnection();
|
||||
echo '✅ Conectada';
|
||||
} catch (Exception $e) {
|
||||
echo '❌ Error: ' . $e->getMessage();
|
||||
}
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p><strong>Tabla system_config:</strong>
|
||||
<?php
|
||||
try {
|
||||
$pdo = createDbConnection();
|
||||
$exists = $pdo->query("SHOW TABLES LIKE 'system_config'")->rowCount() > 0;
|
||||
if ($exists) {
|
||||
$count = $pdo->query("SELECT COUNT(*) as total FROM system_config")->fetch()['total'];
|
||||
echo "✅ Existe ($count registros)";
|
||||
} else {
|
||||
echo '❌ No existe';
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo '❌ Error: ' . $e->getMessage();
|
||||
}
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pruebas de configuración -->
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<!-- Probar guardar configuración -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h6>💾 Probar Guardar Configuración</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="testSaveForm">
|
||||
<div class="mb-2">
|
||||
<input type="text" class="form-control" name="key" placeholder="Clave" value="test_key_<?= time() ?>" required>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<input type="text" class="form-control" name="value" placeholder="Valor" value="Valor de prueba" required>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<input type="text" class="form-control" name="description" placeholder="Descripción (opcional)" value="Configuración de prueba">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm">Guardar</button>
|
||||
</form>
|
||||
<div id="saveResult" class="mt-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Probar obtener configuración -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h6>📖 Probar Obtener Configuración</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="testGetForm">
|
||||
<div class="mb-2">
|
||||
<input type="text" class="form-control" name="key" placeholder="Clave a buscar" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-info btn-sm">Obtener</button>
|
||||
</form>
|
||||
<div id="getResult" class="mt-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<!-- Probar configuración de WhatsApp -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h6>📱 Configuración WhatsApp</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="testWhatsAppForm">
|
||||
<div class="mb-2">
|
||||
<input type="text" class="form-control" name="token" placeholder="Token de WhatsApp">
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<input type="text" class="form-control" name="phone_number_id" placeholder="Phone Number ID">
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<input type="text" class="form-control" name="webhook_verify_token" placeholder="Webhook Token">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success btn-sm">Guardar WhatsApp</button>
|
||||
</form>
|
||||
<div id="whatsappResult" class="mt-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Acciones del sistema -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6>🔧 Acciones del Sistema</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<button onclick="initializeDefaults()" class="btn btn-warning btn-sm mb-2">Inicializar Defaults</button><br>
|
||||
<button onclick="migrateConfigs()" class="btn btn-secondary btn-sm mb-2">Migrar a BD</button><br>
|
||||
<button onclick="clearCache()" class="btn btn-danger btn-sm mb-2">Limpiar Cache</button><br>
|
||||
<button onclick="getAllConfigs()" class="btn btn-primary btn-sm">Ver Todas</button>
|
||||
<div id="systemResult" class="mt-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Configuraciones actuales -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header d-flex justify-content-between">
|
||||
<h5>📋 Configuraciones Actuales</h5>
|
||||
<button onclick="refreshConfigs()" class="btn btn-outline-primary btn-sm">🔄 Actualizar</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="currentConfigs">
|
||||
<div class="text-center text-muted">
|
||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||
Cargando configuraciones...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Validación de WhatsApp -->
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h5>✅ Validación WhatsApp</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="whatsappValidation">
|
||||
<div class="text-center text-muted">
|
||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||
Validando configuración...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// Funciones para probar la API
|
||||
|
||||
// Guardar configuración individual
|
||||
document.getElementById('testSaveForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
|
||||
try {
|
||||
const response = await fetch('api/manage_config.php?action=save', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
key: formData.get('key'),
|
||||
value: formData.get('value'),
|
||||
description: formData.get('description')
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
document.getElementById('saveResult').innerHTML = result.success ?
|
||||
'<div class="alert alert-success alert-sm">✅ ' + result.message + '</div>' :
|
||||
'<div class="alert alert-danger alert-sm">❌ ' + result.message + '</div>';
|
||||
|
||||
if (result.success) {
|
||||
refreshConfigs();
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById('saveResult').innerHTML =
|
||||
'<div class="alert alert-danger alert-sm">❌ Error: ' + error.message + '</div>';
|
||||
}
|
||||
});
|
||||
|
||||
// Obtener configuración
|
||||
document.getElementById('testGetForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
const key = formData.get('key');
|
||||
|
||||
try {
|
||||
const response = await fetch(`api/manage_config.php?action=get&key=${encodeURIComponent(key)}`);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
const data = result.data;
|
||||
const html = `
|
||||
<div class="alert alert-${data.exists ? 'success' : 'warning'} alert-sm">
|
||||
<strong>Clave:</strong> ${data.key}<br>
|
||||
<strong>Valor:</strong> ${data.value || '(vacío)'}<br>
|
||||
<strong>Existe:</strong> ${data.exists ? 'Sí' : 'No'}
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('getResult').innerHTML = html;
|
||||
} else {
|
||||
document.getElementById('getResult').innerHTML =
|
||||
'<div class="alert alert-danger alert-sm">❌ Error: ' + result.error + '</div>';
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById('getResult').innerHTML =
|
||||
'<div class="alert alert-danger alert-sm">❌ Error: ' + error.message + '</div>';
|
||||
}
|
||||
});
|
||||
|
||||
// Guardar configuración de WhatsApp
|
||||
document.getElementById('testWhatsAppForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.target);
|
||||
|
||||
try {
|
||||
const response = await fetch('api/manage_config.php?action=save_whatsapp', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
whatsapp: {
|
||||
token: formData.get('token'),
|
||||
phone_number_id: formData.get('phone_number_id'),
|
||||
webhook_verify_token: formData.get('webhook_verify_token')
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
let html = result.success ?
|
||||
'<div class="alert alert-success alert-sm">✅ ' + result.message + '</div>' :
|
||||
'<div class="alert alert-danger alert-sm">❌ ' + result.message + '</div>';
|
||||
|
||||
if (result.validation) {
|
||||
html += `
|
||||
<div class="mt-2">
|
||||
<small>
|
||||
<strong>Estado:</strong> ${result.validation.valid ? '✅ Válido' : '❌ Inválido'}<br>
|
||||
<strong>Errores:</strong> ${result.validation.errors.join(', ') || 'Ninguno'}<br>
|
||||
<strong>Advertencias:</strong> ${result.validation.warnings.join(', ') || 'Ninguna'}
|
||||
</small>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
document.getElementById('whatsappResult').innerHTML = html;
|
||||
|
||||
if (result.success) {
|
||||
refreshConfigs();
|
||||
refreshWhatsAppValidation();
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById('whatsappResult').innerHTML =
|
||||
'<div class="alert alert-danger alert-sm">❌ Error: ' + error.message + '</div>';
|
||||
}
|
||||
});
|
||||
|
||||
// Inicializar configuraciones por defecto
|
||||
async function initializeDefaults() {
|
||||
try {
|
||||
const response = await fetch('api/manage_config.php?action=initialize_defaults', {
|
||||
method: 'POST'
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
document.getElementById('systemResult').innerHTML = result.success ?
|
||||
'<div class="alert alert-success alert-sm">✅ ' + result.message + '</div>' :
|
||||
'<div class="alert alert-danger alert-sm">❌ ' + result.message + '</div>';
|
||||
|
||||
if (result.success) {
|
||||
refreshConfigs();
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById('systemResult').innerHTML =
|
||||
'<div class="alert alert-danger alert-sm">❌ Error: ' + error.message + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Migrar configuraciones
|
||||
async function migrateConfigs() {
|
||||
try {
|
||||
const response = await fetch('api/manage_config.php?action=migrate_to_database', {
|
||||
method: 'POST'
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
document.getElementById('systemResult').innerHTML = result.success ?
|
||||
'<div class="alert alert-success alert-sm">✅ ' + result.message + '</div>' :
|
||||
'<div class="alert alert-danger alert-sm">❌ ' + result.message + '</div>';
|
||||
|
||||
if (result.success) {
|
||||
refreshConfigs();
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById('systemResult').innerHTML =
|
||||
'<div class="alert alert-danger alert-sm">❌ Error: ' + error.message + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Limpiar cache
|
||||
async function clearCache() {
|
||||
try {
|
||||
const response = await fetch('api/manage_config.php?action=clear_cache', {
|
||||
method: 'DELETE'
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
document.getElementById('systemResult').innerHTML = result.success ?
|
||||
'<div class="alert alert-success alert-sm">✅ ' + result.message + '</div>' :
|
||||
'<div class="alert alert-danger alert-sm">❌ ' + result.message + '</div>';
|
||||
} catch (error) {
|
||||
document.getElementById('systemResult').innerHTML =
|
||||
'<div class="alert alert-danger alert-sm">❌ Error: ' + error.message + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Obtener todas las configuraciones
|
||||
async function getAllConfigs() {
|
||||
refreshConfigs();
|
||||
}
|
||||
|
||||
// Actualizar lista de configuraciones
|
||||
async function refreshConfigs() {
|
||||
try {
|
||||
const response = await fetch('api/manage_config.php?action=all');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
const configs = result.data;
|
||||
let html = '';
|
||||
|
||||
if (Object.keys(configs).length === 0) {
|
||||
html = '<div class="text-muted text-center">No hay configuraciones guardadas</div>';
|
||||
} else {
|
||||
html = '<div class="table-responsive"><table class="table table-sm table-striped">';
|
||||
html += '<thead><tr><th>Clave</th><th>Valor</th><th>Descripción</th><th>Actualizado</th></tr></thead><tbody>';
|
||||
|
||||
for (const [key, config] of Object.entries(configs)) {
|
||||
const value = config.value ? (config.value.length > 50 ? config.value.substring(0, 50) + '...' : config.value) : '(vacío)';
|
||||
html += `
|
||||
<tr>
|
||||
<td><code>${key}</code></td>
|
||||
<td>${value}</td>
|
||||
<td><small>${config.description || '—'}</small></td>
|
||||
<td><small>${config.updated_at || config.created_at}</small></td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
html += '</tbody></table></div>';
|
||||
}
|
||||
|
||||
document.getElementById('currentConfigs').innerHTML = html;
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById('currentConfigs').innerHTML =
|
||||
'<div class="alert alert-danger">Error cargando configuraciones: ' + error.message + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Actualizar validación de WhatsApp
|
||||
async function refreshWhatsAppValidation() {
|
||||
try {
|
||||
const response = await fetch('api/manage_config.php?action=validate_whatsapp');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
const validation = result.data;
|
||||
let html = `
|
||||
<div class="alert alert-${validation.valid ? 'success' : 'warning'}">
|
||||
<h6>${validation.valid ? '✅ Configuración válida' : '⚠️ Configuración incompleta'}</h6>
|
||||
`;
|
||||
|
||||
if (validation.errors.length > 0) {
|
||||
html += '<p><strong>Errores:</strong></p><ul>';
|
||||
validation.errors.forEach(error => {
|
||||
html += `<li>${error}</li>`;
|
||||
});
|
||||
html += '</ul>';
|
||||
}
|
||||
|
||||
if (validation.warnings.length > 0) {
|
||||
html += '<p><strong>Advertencias:</strong></p><ul>';
|
||||
validation.warnings.forEach(warning => {
|
||||
html += `<li>${warning}</li>`;
|
||||
});
|
||||
html += '</ul>';
|
||||
}
|
||||
|
||||
html += '<p><strong>Campos configurados:</strong></p><ul>';
|
||||
for (const [field, configured] of Object.entries(validation.configured_fields)) {
|
||||
html += `<li>${field}: ${configured ? '✅' : '❌'}</li>`;
|
||||
}
|
||||
html += '</ul></div>';
|
||||
|
||||
document.getElementById('whatsappValidation').innerHTML = html;
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById('whatsappValidation').innerHTML =
|
||||
'<div class="alert alert-danger">Error validando WhatsApp: ' + error.message + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Cargar datos iniciales
|
||||
refreshConfigs();
|
||||
refreshWhatsAppValidation();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,209 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test Funciones Globales - 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.0.0/css/all.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-md-10 mx-auto">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-bug"></i> Test de Funciones Globales</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6>Modales Disponibles:</h6>
|
||||
<button class="btn btn-primary mb-2 d-block" onclick="testShowCreateTemplateModal()">
|
||||
<i class="fas fa-plus"></i> Test Modal Plantilla
|
||||
</button>
|
||||
|
||||
<button class="btn btn-success mb-2 d-block" onclick="testShowCreateMenuModal()">
|
||||
<i class="fas fa-plus"></i> Test Modal Menú
|
||||
</button>
|
||||
|
||||
<button class="btn btn-info mb-2 d-block" onclick="testBootstrap()">
|
||||
<i class="fas fa-check"></i> Test Bootstrap
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>Debug Console:</h6>
|
||||
<div id="console" class="bg-dark text-white p-3 rounded" style="height: 300px; overflow-y: auto; font-family: monospace; font-size: 0.8rem;">
|
||||
<!-- Console output -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal de Plantilla -->
|
||||
<div class="modal fade" id="createTemplateModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">📝 Test Modal Plantilla</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Este es el modal de plantillas funcionando correctamente.</p>
|
||||
<form id="template-form">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nombre de prueba</label>
|
||||
<input type="text" class="form-control" placeholder="Plantilla de prueba">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal de Menú -->
|
||||
<div class="modal fade" id="createMenuModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">📋 Test Modal Menú</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Este es el modal de menús funcionando correctamente.</p>
|
||||
<form id="menu-form">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nombre del menú</label>
|
||||
<input type="text" class="form-control" placeholder="Menú de prueba">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
const consoleDiv = document.getElementById('console');
|
||||
|
||||
function log(message, type = 'info') {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const colors = {
|
||||
info: '#17a2b8',
|
||||
success: '#28a745',
|
||||
error: '#dc3545',
|
||||
warning: '#ffc107'
|
||||
};
|
||||
|
||||
consoleDiv.innerHTML += `<div style="color: ${colors[type]};">[${timestamp}] ${message}</div>`;
|
||||
consoleDiv.scrollTop = consoleDiv.scrollHeight;
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
function testBootstrap() {
|
||||
log('🧪 Testing Bootstrap...', 'info');
|
||||
if (typeof bootstrap !== 'undefined') {
|
||||
log('✅ Bootstrap is available', 'success');
|
||||
if (bootstrap.Modal) {
|
||||
log('✅ Bootstrap.Modal is available', 'success');
|
||||
} else {
|
||||
log('❌ Bootstrap.Modal not found', 'error');
|
||||
}
|
||||
} else {
|
||||
log('❌ Bootstrap not available', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Definir las funciones globales aquí directamente
|
||||
window.showCreateTemplateModal = function() {
|
||||
log('🔵 showCreateTemplateModal called', 'info');
|
||||
|
||||
const modalElement = document.getElementById('createTemplateModal');
|
||||
if (!modalElement) {
|
||||
log('❌ Modal element not found', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
|
||||
log('✅ Using Bootstrap modal', 'success');
|
||||
const modal = new bootstrap.Modal(modalElement);
|
||||
modal.show();
|
||||
log('✅ Modal opened successfully', 'success');
|
||||
} else {
|
||||
log('⚠️ Using manual modal', 'warning');
|
||||
modalElement.style.display = 'block';
|
||||
modalElement.classList.add('show');
|
||||
}
|
||||
} catch (error) {
|
||||
log('❌ Error: ' + error.message, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
window.showCreateMenuModal = function() {
|
||||
log('🔵 showCreateMenuModal called', 'info');
|
||||
|
||||
const modalElement = document.getElementById('createMenuModal');
|
||||
if (!modalElement) {
|
||||
log('❌ Menu modal element not found', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
|
||||
log('✅ Using Bootstrap modal for menu', 'success');
|
||||
const modal = new bootstrap.Modal(modalElement);
|
||||
modal.show();
|
||||
log('✅ Menu modal opened successfully', 'success');
|
||||
} else {
|
||||
log('⚠️ Using manual modal for menu', 'warning');
|
||||
modalElement.style.display = 'block';
|
||||
modalElement.classList.add('show');
|
||||
}
|
||||
} catch (error) {
|
||||
log('❌ Error opening menu modal: ' + error.message, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
function testShowCreateTemplateModal() {
|
||||
log('🔧 Testing showCreateTemplateModal function...', 'info');
|
||||
if (typeof showCreateTemplateModal === 'function') {
|
||||
log('✅ Function exists, calling it...', 'success');
|
||||
showCreateTemplateModal();
|
||||
} else {
|
||||
log('❌ Function not found!', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function testShowCreateMenuModal() {
|
||||
log('🔧 Testing showCreateMenuModal function...', 'info');
|
||||
if (typeof showCreateMenuModal === 'function') {
|
||||
log('✅ Function exists, calling it...', 'success');
|
||||
showCreateMenuModal();
|
||||
} else {
|
||||
log('❌ Function not found!', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
log('📄 Page loaded', 'info');
|
||||
testBootstrap();
|
||||
|
||||
// Test function availability
|
||||
log('🔍 Checking function availability...', 'info');
|
||||
log('showCreateTemplateModal: ' + (typeof showCreateTemplateModal), 'info');
|
||||
log('showCreateMenuModal: ' + (typeof showCreateMenuModal), 'info');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test Modal Plantillas - 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.0.0/css/all.min.css" rel="stylesheet">
|
||||
<link href="./assets/css/styles.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-md-8 mx-auto">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-test-tube"></i> Prueba de Modal de Plantillas</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Haz clic en el botón para probar el modal de crear plantilla:</p>
|
||||
|
||||
<button class="btn btn-primary" onclick="showCreateTemplateModal()">
|
||||
<i class="fas fa-plus"></i> Nueva Plantilla
|
||||
</button>
|
||||
|
||||
<button class="btn btn-info ms-2" onclick="testBootstrap()">
|
||||
<i class="fas fa-check"></i> Test Bootstrap
|
||||
</button>
|
||||
|
||||
<div class="mt-3">
|
||||
<h6>Log de Debug:</h6>
|
||||
<div id="debug-log" class="bg-light p-3 rounded" style="height: 200px; overflow-y: auto; font-family: monospace; font-size: 0.8rem;">
|
||||
<!-- Los mensajes aparecerán aquí -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal de Crear Plantilla -->
|
||||
<div class="modal fade" id="createTemplateModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">📝 Nueva Plantilla de WhatsApp</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="template-form">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nombre descriptivo *</label>
|
||||
<input type="text" class="form-control" id="template-name" required placeholder="ej: Bienvenida Cliente">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nombre en WhatsApp *</label>
|
||||
<input type="text" class="form-control" id="template-whatsapp-name" required placeholder="ej: welcome_customer">
|
||||
</div>
|
||||
<div class="alert alert-info">
|
||||
<strong>⚠️ Importante:</strong> Este es un modal de prueba.
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||
<button type="button" class="btn btn-primary">Guardar Plantilla</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
const debugLog = document.getElementById('debug-log');
|
||||
|
||||
function addLog(message) {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
debugLog.innerHTML += `[${timestamp}] ${message}\n`;
|
||||
debugLog.scrollTop = debugLog.scrollHeight;
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
function testBootstrap() {
|
||||
addLog('🧪 Probando Bootstrap...');
|
||||
if (typeof bootstrap !== 'undefined') {
|
||||
addLog('✅ Bootstrap está disponible');
|
||||
addLog('📦 Versión detectada: ' + (bootstrap.Modal ? 'Modal disponible' : 'Modal no disponible'));
|
||||
} else {
|
||||
addLog('❌ Bootstrap NO está disponible');
|
||||
}
|
||||
}
|
||||
|
||||
function showCreateTemplateModal() {
|
||||
addLog('🔵 Intentando abrir modal de crear plantilla...');
|
||||
|
||||
const modalElement = document.getElementById('createTemplateModal');
|
||||
if (!modalElement) {
|
||||
addLog('❌ No se encontró el modal createTemplateModal');
|
||||
alert('Error: No se encontró el modal de crear plantilla');
|
||||
return;
|
||||
}
|
||||
|
||||
addLog('✅ Modal encontrado');
|
||||
|
||||
try {
|
||||
// Limpiar el formulario
|
||||
const form = document.getElementById('template-form');
|
||||
if (form) {
|
||||
form.reset();
|
||||
addLog('✅ Formulario limpiado');
|
||||
}
|
||||
|
||||
// Verificar si Bootstrap está disponible
|
||||
if (typeof bootstrap !== 'undefined') {
|
||||
addLog('✅ Usando Bootstrap modal');
|
||||
const modal = new bootstrap.Modal(modalElement);
|
||||
modal.show();
|
||||
addLog('✅ Modal abierto con Bootstrap');
|
||||
} else {
|
||||
addLog('⚠️ Bootstrap no disponible, usando modal manual');
|
||||
// Fallback manual
|
||||
modalElement.style.display = 'block';
|
||||
modalElement.classList.add('show');
|
||||
addLog('✅ Modal abierto manualmente');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
addLog('❌ Error al abrir modal: ' + error.message);
|
||||
alert('Error al abrir el modal: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
addLog('📄 Página cargada');
|
||||
testBootstrap();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,85 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test de Navegación - 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.0.0/css/all.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.tab-content { display: none; padding: 20px; }
|
||||
.tab-content.active { display: block; }
|
||||
.nav-link.active { background-color: #007bff; color: white; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-3 bg-dark text-white" style="height: 100vh;">
|
||||
<h4 class="p-3">Menú de Prueba</h4>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="#dashboard" class="nav-link active text-white d-block p-2" data-tab="dashboard"><i class="fas fa-tachometer-alt"></i> Dashboard</a></li>
|
||||
<li><a href="#users" class="nav-link text-white d-block p-2" data-tab="users"><i class="fas fa-users"></i> Usuarios</a></li>
|
||||
<li><a href="#logs" class="nav-link text-white d-block p-2" data-tab="logs"><i class="fas fa-file-alt"></i> Logs</a></li>
|
||||
<li><a href="#menus" class="nav-link text-white d-block p-2" data-tab="menus"><i class="fas fa-list"></i> Menús</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<div id="dashboard" class="tab-content active">
|
||||
<h2>Dashboard</h2>
|
||||
<p>Contenido del dashboard aquí.</p>
|
||||
<div class="alert alert-info">Esta es la pestaña de Dashboard</div>
|
||||
</div>
|
||||
<div id="users" class="tab-content">
|
||||
<h2>Usuarios</h2>
|
||||
<p>Lista de usuarios aquí.</p>
|
||||
<div class="alert alert-success">Esta es la pestaña de Usuarios</div>
|
||||
</div>
|
||||
<div id="logs" class="tab-content">
|
||||
<h2>Logs</h2>
|
||||
<p>Logs del sistema aquí.</p>
|
||||
<div class="alert alert-warning">Esta es la pestaña de Logs</div>
|
||||
</div>
|
||||
<div id="menus" class="tab-content">
|
||||
<h2>Menús</h2>
|
||||
<p>Configuración de menús aquí.</p>
|
||||
<div class="alert alert-primary">Esta es la pestaña de Menús</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('✅ Navegación de prueba inicializada');
|
||||
|
||||
document.querySelectorAll('.nav-link[data-tab]').forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const tabName = this.getAttribute('data-tab');
|
||||
console.log('🔄 Cambiando a:', tabName);
|
||||
|
||||
// Remover active de todos los enlaces
|
||||
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
// Ocultar todos los tabs
|
||||
document.querySelectorAll('.tab-content').forEach(tab => {
|
||||
tab.classList.remove('active');
|
||||
});
|
||||
|
||||
// Mostrar el tab seleccionado
|
||||
const targetTab = document.getElementById(tabName);
|
||||
if (targetTab) {
|
||||
targetTab.classList.add('active');
|
||||
console.log('✅ Tab activado:', tabName);
|
||||
} else {
|
||||
console.error('❌ Tab no encontrado:', tabName);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
echo password_verify('2fa904eee46f', '$2y$10$/ElTmzox8CpwwPaBLOz8JOKPsg7.ml2kn.85XuR7qRl/F4G5fzIxG') ? 'SI FUNCIONA' : 'NO FUNCIONA';
|
||||
?>
|
||||
@@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test SES Fix - 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.0.0/css/all.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container py-5">
|
||||
<div class="row">
|
||||
<div class="col-md-8 mx-auto">
|
||||
<div class="card">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5><i class="fas fa-check-circle"></i> Test SES Fix</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Esta página prueba que las funciones globales funcionen sin errores de SES.</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6>Funciones disponibles:</h6>
|
||||
<button class="btn btn-primary d-block mb-2" onclick="testFunction('showCreateTemplateModal')">
|
||||
<i class="fas fa-file-text"></i> Test showCreateTemplateModal
|
||||
</button>
|
||||
|
||||
<button class="btn btn-success d-block mb-2" onclick="testFunction('showCreateMenuModal')">
|
||||
<i class="fas fa-list"></i> Test showCreateMenuModal
|
||||
</button>
|
||||
|
||||
<button class="btn btn-info d-block mb-2" onclick="clearLog()">
|
||||
<i class="fas fa-eraser"></i> Limpiar Log
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>Status Log:</h6>
|
||||
<div id="status-log" class="bg-light border p-3 rounded" style="height: 200px; overflow-y: auto; font-family: monospace; font-size: 0.8rem;">
|
||||
<div style="color: green;">✅ Página cargada sin errores de SES</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
const logDiv = document.getElementById('status-log');
|
||||
|
||||
function addLog(message, type = 'info') {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const colors = {
|
||||
info: '#17a2b8',
|
||||
success: '#28a745',
|
||||
error: '#dc3545',
|
||||
warning: '#ffc107'
|
||||
};
|
||||
|
||||
logDiv.innerHTML += `<div style="color: ${colors[type]};">[${timestamp}] ${message}</div>`;
|
||||
logDiv.scrollTop = logDiv.scrollHeight;
|
||||
}
|
||||
|
||||
function clearLog() {
|
||||
logDiv.innerHTML = '<div style="color: green;">✅ Log limpiado</div>';
|
||||
}
|
||||
|
||||
// Definir funciones de prueba localmente
|
||||
window.showCreateTemplateModal = function() {
|
||||
addLog('✅ showCreateTemplateModal llamada exitosamente', 'success');
|
||||
alert('Modal de plantilla funcionando correctamente');
|
||||
};
|
||||
|
||||
window.showCreateMenuModal = function() {
|
||||
addLog('✅ showCreateMenuModal llamada exitosamente', 'success');
|
||||
alert('Modal de menú funcionando correctamente');
|
||||
};
|
||||
|
||||
function testFunction(functionName) {
|
||||
addLog(`🧪 Probando función: ${functionName}`, 'info');
|
||||
|
||||
if (typeof window[functionName] === 'function') {
|
||||
try {
|
||||
window[functionName]();
|
||||
addLog(`✅ Función ${functionName} ejecutada sin errores`, 'success');
|
||||
} catch (error) {
|
||||
addLog(`❌ Error ejecutando ${functionName}: ${error.message}`, 'error');
|
||||
}
|
||||
} else {
|
||||
addLog(`❌ Función ${functionName} no encontrada`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
addLog('📄 DOM cargado', 'info');
|
||||
|
||||
// Verificar disponibilidad de funciones
|
||||
addLog('🔍 Verificando funciones globales...', 'info');
|
||||
|
||||
const functions = ['showCreateTemplateModal', 'showCreateMenuModal'];
|
||||
functions.forEach(func => {
|
||||
if (typeof window[func] === 'function') {
|
||||
addLog(`✅ ${func} disponible`, 'success');
|
||||
} else {
|
||||
addLog(`❌ ${func} no disponible`, 'error');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* Prueba simple del sistema de configuraciones
|
||||
*/
|
||||
|
||||
require_once 'config/config.php';
|
||||
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
|
||||
try {
|
||||
// Test básico de conexión a BD
|
||||
echo "=== PRUEBA SISTEMA DE CONFIGURACIONES ===\n\n";
|
||||
|
||||
echo "1. Probando conexión a base de datos...\n";
|
||||
$pdo = createDbConnection();
|
||||
echo "✅ Conexión exitosa\n\n";
|
||||
|
||||
echo "2. Verificando tabla system_config...\n";
|
||||
$tableExists = $pdo->query("SHOW TABLES LIKE 'system_config'")->rowCount() > 0;
|
||||
|
||||
if (!$tableExists) {
|
||||
echo "⚠️ Tabla no existe, creándola...\n";
|
||||
$created = createSystemConfigTable($pdo);
|
||||
if ($created) {
|
||||
echo "✅ Tabla creada exitosamente\n\n";
|
||||
} else {
|
||||
echo "❌ Error creando tabla\n\n";
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
echo "✅ Tabla existe\n\n";
|
||||
}
|
||||
|
||||
echo "3. Probando guardar configuración...\n";
|
||||
$testKey = 'test_config_' . time();
|
||||
$testValue = 'Valor de prueba ' . date('Y-m-d H:i:s');
|
||||
|
||||
$saved = saveConfigToDB($testKey, $testValue, 'Configuración de prueba');
|
||||
if ($saved) {
|
||||
echo "✅ Configuración guardada: $testKey = $testValue\n\n";
|
||||
} else {
|
||||
echo "❌ Error guardando configuración\n\n";
|
||||
}
|
||||
|
||||
echo "4. Probando obtener configuración...\n";
|
||||
$retrieved = getConfigFromDB($testKey, 'no_encontrado');
|
||||
if ($retrieved === $testValue) {
|
||||
echo "✅ Configuración recuperada correctamente: $retrieved\n\n";
|
||||
} else {
|
||||
echo "❌ Error recuperando configuración. Esperado: $testValue, Obtenido: $retrieved\n\n";
|
||||
}
|
||||
|
||||
echo "5. Probando obtener todas las configuraciones...\n";
|
||||
$allConfigs = getAllConfigsFromDB();
|
||||
echo "✅ Total de configuraciones: " . count($allConfigs) . "\n\n";
|
||||
|
||||
echo "6. Probando configuración de WhatsApp...\n";
|
||||
$whatsappTest = [
|
||||
'token' => 'token_de_prueba_' . time(),
|
||||
'phone_number_id' => '123456789',
|
||||
'webhook_verify_token' => 'webhook_test_' . time()
|
||||
];
|
||||
|
||||
$whatsappSaved = saveWhatsAppConfigToDB($whatsappTest);
|
||||
if ($whatsappSaved) {
|
||||
echo "✅ Configuración de WhatsApp guardada\n";
|
||||
|
||||
$whatsappConfig = getWhatsAppConfigFromDB();
|
||||
echo "📱 Token: " . $whatsappConfig['token'] . "\n";
|
||||
echo "📱 Phone ID: " . $whatsappConfig['phone_number_id'] . "\n";
|
||||
echo "📱 Webhook: " . $whatsappConfig['webhook_verify_token'] . "\n\n";
|
||||
} else {
|
||||
echo "❌ Error guardando configuración de WhatsApp\n\n";
|
||||
}
|
||||
|
||||
echo "7. Probando validación de WhatsApp...\n";
|
||||
$validation = validateWhatsAppConfigFromDB();
|
||||
echo "✅ Configuración válida: " . ($validation['valid'] ? 'Sí' : 'No') . "\n";
|
||||
echo " Errores: " . count($validation['errors']) . "\n";
|
||||
echo " Advertencias: " . count($validation['warnings']) . "\n\n";
|
||||
|
||||
echo "8. Limpiando configuración de prueba...\n";
|
||||
$deleted = deleteConfigFromDB($testKey);
|
||||
if ($deleted) {
|
||||
echo "✅ Configuración de prueba eliminada\n\n";
|
||||
} else {
|
||||
echo "❌ Error eliminando configuración de prueba\n\n";
|
||||
}
|
||||
|
||||
echo "=== PRUEBAS COMPLETADAS EXITOSAMENTE ===\n";
|
||||
echo "El sistema de configuraciones funciona correctamente.\n";
|
||||
echo "Puedes usar las funciones:\n";
|
||||
echo "- saveConfigToDB(clave, valor, descripción)\n";
|
||||
echo "- getConfigFromDB(clave, por_defecto)\n";
|
||||
echo "- getAllConfigsFromDB()\n";
|
||||
echo "- saveWhatsAppConfigToDB(array_config)\n";
|
||||
echo "- getWhatsAppConfigFromDB()\n";
|
||||
echo "- validateWhatsAppConfigFromDB()\n";
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ ERROR: " . $e->getMessage() . "\n";
|
||||
echo "Trace: " . $e->getTraceAsString() . "\n";
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/**
|
||||
* Diagnóstico avanzado de WhatsApp API
|
||||
* Fecha: 4 de enero de 2026
|
||||
*/
|
||||
|
||||
require_once 'config/config.php';
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
|
||||
echo "<h2>🔍 Diagnóstico Avanzado de WhatsApp API</h2>";
|
||||
|
||||
// 1. Verificar configuración actual
|
||||
echo "<h3>1. Configuración Actual</h3>";
|
||||
echo "TOKEN: " . substr(WHATSAPP_TOKEN, 0, 20) . "..." . substr(WHATSAPP_TOKEN, -10) . "<br>";
|
||||
echo "PHONE_NUMBER_ID: " . WHATSAPP_PHONE_NUMBER_ID . "<br>";
|
||||
echo "API_URL: " . WHATSAPP_API_URL . "<br>";
|
||||
|
||||
// 2. Test de conectividad con WhatsApp API
|
||||
echo "<h3>2. Test de Conectividad</h3>";
|
||||
|
||||
try {
|
||||
// Test 1: Verificar información del número de teléfono
|
||||
$ch = curl_init();
|
||||
$url = WHATSAPP_API_URL . WHATSAPP_PHONE_NUMBER_ID;
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . WHATSAPP_TOKEN,
|
||||
'Content-Type: application/json'
|
||||
],
|
||||
CURLOPT_TIMEOUT => 30
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($error) {
|
||||
echo "❌ Error de conexión: " . $error . "<br>";
|
||||
} else {
|
||||
echo "📞 Código HTTP: $httpCode<br>";
|
||||
if ($httpCode == 200) {
|
||||
$data = json_decode($response, true);
|
||||
echo "✅ Conexión exitosa con WhatsApp API<br>";
|
||||
echo "<pre>" . json_encode($data, JSON_PRETTY_PRINT) . "</pre>";
|
||||
} else {
|
||||
echo "❌ Error HTTP $httpCode<br>";
|
||||
echo "<pre>$response</pre>";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ Excepción: " . $e->getMessage() . "<br>";
|
||||
}
|
||||
|
||||
// 3. Test de mensaje a número de prueba (tu propio número)
|
||||
echo "<h3>3. Test de Mensaje Real</h3>";
|
||||
|
||||
// Crear un botón para probar envío
|
||||
?>
|
||||
<form method="post" style="margin: 20px 0;">
|
||||
<input type="hidden" name="test_message" value="1">
|
||||
<label>Número de destino:</label>
|
||||
<input type="text" name="phone" value="+573168950803" style="width: 200px; padding: 5px;">
|
||||
<br><br>
|
||||
<label>Mensaje de prueba:</label>
|
||||
<input type="text" name="message" value="🚀 Test desde sistema - <?php echo date('H:i:s'); ?>" style="width: 400px; padding: 5px;">
|
||||
<br><br>
|
||||
<button type="submit" style="padding: 10px 20px; background: #25D366; color: white; border: none; border-radius: 5px;">
|
||||
📱 Enviar Mensaje de Prueba
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<?php
|
||||
if (isset($_POST['test_message'])) {
|
||||
echo "<h4>🚀 Resultado del Test:</h4>";
|
||||
|
||||
try {
|
||||
$whatsapp = new WhatsAppService();
|
||||
$phone = $_POST['phone'];
|
||||
$message = $_POST['message'];
|
||||
|
||||
echo "📞 Enviando a: $phone<br>";
|
||||
echo "💬 Mensaje: $message<br><br>";
|
||||
|
||||
$response = $whatsapp->sendTextMessage($phone, $message);
|
||||
|
||||
echo "<strong>✅ Respuesta de WhatsApp API:</strong><br>";
|
||||
echo "<pre style='background: #e8f5e8; padding: 10px; border-radius: 5px;'>";
|
||||
echo json_encode($response, JSON_PRETTY_PRINT);
|
||||
echo "</pre>";
|
||||
|
||||
if (isset($response['messages'][0]['id'])) {
|
||||
$messageId = $response['messages'][0]['id'];
|
||||
echo "<p><strong>📨 Mensaje enviado con ID: $messageId</strong></p>";
|
||||
echo "<p>⏱️ <strong>Espera 1-2 minutos y verifica tu WhatsApp</strong></p>";
|
||||
|
||||
// Mostrar información adicional
|
||||
echo "<h4>🔍 Información del Envío:</h4>";
|
||||
if (isset($response['contacts'][0])) {
|
||||
$contact = $response['contacts'][0];
|
||||
echo "📱 Número Input: " . $contact['input'] . "<br>";
|
||||
echo "📱 WhatsApp ID: " . $contact['wa_id'] . "<br>";
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ <strong>Error:</strong> " . $e->getMessage() . "<br>";
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Verificar status del mensaje reciente
|
||||
echo "<h3>4. Verificar Logs del Sistema</h3>";
|
||||
$logFile = 'logs/system.log';
|
||||
if (file_exists($logFile)) {
|
||||
$logs = file_get_contents($logFile);
|
||||
$lines = array_filter(array_slice(explode("\n", $logs), -20)); // Últimas 20 líneas
|
||||
|
||||
echo "<pre style='background: #f8f9fa; padding: 15px; border: 1px solid #dee2e6; border-radius: 5px; max-height: 400px; overflow-y: auto;'>";
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, 'ERROR') !== false) {
|
||||
echo "<span style='color: red;'>$line</span>\n";
|
||||
} elseif (strpos($line, 'WARNING') !== false) {
|
||||
echo "<span style='color: orange;'>$line</span>\n";
|
||||
} else {
|
||||
echo "$line\n";
|
||||
}
|
||||
}
|
||||
echo "</pre>";
|
||||
} else {
|
||||
echo "⚠️ No hay archivo de logs disponible<br>";
|
||||
}
|
||||
|
||||
// 5. Posibles soluciones
|
||||
echo "<h3>5. ✅ Posibles Soluciones</h3>";
|
||||
echo "<div style='background: #fff3cd; padding: 15px; border: 1px solid #ffeaa7; border-radius: 5px;'>";
|
||||
echo "<h4>🔧 Si el mensaje no llega, verifica:</h4>";
|
||||
echo "<ol>";
|
||||
echo "<li><strong>Modo de desarrollo:</strong> ¿Tu app está en modo sandbox? Los mensajes solo llegan a números pre-aprobados</li>";
|
||||
echo "<li><strong>Número verificado:</strong> ¿El número desde el que envías está verificado en Facebook Business?</li>";
|
||||
echo "<li><strong>Permisos del token:</strong> ¿Tu token tiene permisos 'whatsapp_business_messaging'?</li>";
|
||||
echo "<li><strong>Número de destino:</strong> ¿El número +573168950803 tiene WhatsApp activo?</li>";
|
||||
echo "<li><strong>Rate limiting:</strong> ¿Has enviado muchos mensajes recientemente?</li>";
|
||||
echo "<li><strong>Webhook URL:</strong> ¿Tu webhook está configurado correctamente para recibir respuestas?</li>";
|
||||
echo "</ol>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div style='background: #d1ecf1; padding: 15px; border: 1px solid #bee5eb; border-radius: 5px; margin-top: 15px;'>";
|
||||
echo "<h4>📋 Pasos para resolver:</h4>";
|
||||
echo "<ol>";
|
||||
echo "<li>Ve a <a href='https://developers.facebook.com/apps' target='_blank'>Facebook Developers</a></li>";
|
||||
echo "<li>Selecciona tu app → WhatsApp → Configuración</li>";
|
||||
echo "<li>Verifica que el número esté <strong>verificado</strong> y <strong>activo</strong></li>";
|
||||
echo "<li>Revisa la sección 'Phone Numbers' y asegúrate que esté en modo <strong>Producción</strong></li>";
|
||||
echo "<li>En 'Webhooks', verifica que tu URL esté configurada y validada</li>";
|
||||
echo "</ol>";
|
||||
echo "</div>";
|
||||
?>
|
||||
@@ -0,0 +1,495 @@
|
||||
<?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_settings.php
|
||||
$this->apiTest("API get_settings existe", function() {
|
||||
if (!file_exists(__DIR__ . '/../api/get_settings.php')) {
|
||||
throw new Exception("get_settings.php no existe");
|
||||
}
|
||||
|
||||
$content = file_get_contents(__DIR__ . '/../api/get_settings.php');
|
||||
|
||||
if (strpos($content, 'requireAuthentication()') === false) {
|
||||
throw new Exception("get_settings no requiere autenticación");
|
||||
}
|
||||
|
||||
return "API get_settings correcta";
|
||||
});
|
||||
|
||||
// Test save_settings.php
|
||||
$this->apiTest("API save_settings existe", function() {
|
||||
if (!file_exists(__DIR__ . '/../api/save_settings.php')) {
|
||||
throw new Exception("save_settings.php no existe");
|
||||
}
|
||||
|
||||
$content = file_get_contents(__DIR__ . '/../api/save_settings.php');
|
||||
|
||||
if (strpos($content, 'requireAuthentication()') === false) {
|
||||
throw new Exception("save_settings 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_settings completa";
|
||||
});
|
||||
|
||||
// Verificar tabla settings
|
||||
$this->apiTest("Verificar tabla configuración", function() {
|
||||
$db = Database::getInstance();
|
||||
|
||||
$result = $db->fetchAll("SHOW TABLES LIKE 'settings'");
|
||||
if (!$result || empty($result)) {
|
||||
throw new Exception("Tabla settings 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', 'messages_today', 'active_users', 'total_messages'];
|
||||
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, 'requireAuthentication()') === false) {
|
||||
throw new Exception("get_recent_messages no requiere autenticación");
|
||||
}
|
||||
|
||||
if (strpos($content, 'messages') === false && 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 = ['messages', '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();
|
||||
}
|
||||
?>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user