diff --git a/ARQUITECTURA_MEJORADA.md b/ARQUITECTURA_MEJORADA.md
deleted file mode 100644
index cc7c301..0000000
--- a/ARQUITECTURA_MEJORADA.md
+++ /dev/null
@@ -1,555 +0,0 @@
-# 🚀 Arquitectura Mejorada - WhatsApp Bot Manager
-
-## 📋 Tabla de Contenidos
-
-- [Introducción](#introducción)
-- [Arquitectura General](#arquitectura-general)
-- [Componentes Clave](#componentes-clave)
-- [Flujo de Mensajes](#flujo-de-mensajes)
-- [Instalación y Configuración](#instalación-y-configuración)
-- [Deployment](#deployment)
-- [Monitoreo](#monitoreo)
-- [Troubleshooting](#troubleshooting)
-
----
-
-## Introducción
-
-Esta arquitectura implementa las **mejores prácticas** para chatbots de WhatsApp Business API en PHP, optimizando:
-
-✅ **Respuesta rápida al webhook** (< 1 segundo)
-✅ **Procesamiento asíncrono** con colas Redis
-✅ **Rate limiting automático** (respeta límites de WhatsApp)
-✅ **Logging estructurado** con Monolog
-✅ **Gestión de estados** en Redis (conversaciones)
-✅ **Escalabilidad horizontal** con workers
-
----
-
-## Arquitectura General
-
-```
-┌─────────────────┐
-│ WhatsApp API │
-└────────┬────────┘
- │ POST
- ▼
-┌─────────────────────┐
-│ webhook_optimized │ ← Responde 200 OK < 1 seg
-│ .php │
-└────────┬────────────┘
- │ push
- ▼
-┌─────────────────────┐
-│ Redis Queue │ ← Cola FIFO con prioridad
-│ (messages, media) │
-└────────┬────────────┘
- │ pop (BRPOP)
- ▼
-┌─────────────────────┐
-│ worker.php │ ← Procesa mensajes async
-│ (3+ procesos) │
-└────────┬────────────┘
- │
- ▼
-┌─────────────────────┐
-│ BotService │ ← Lógica del bot
-│ WhatsAppService │
-└─────────────────────┘
-```
-
-### Ventajas sobre arquitectura anterior
-
-| Antes | Ahora |
-|-------|-------|
-| Webhook procesa todo síncronamente | Webhook solo encola (< 100ms) |
-| Riesgo de timeout si bot tarda > 5s | Worker procesa sin límite de tiempo |
-| Sin control de límites de envío | Rate limiting automático (80 msg/s) |
-| Logs dispersos en archivos | Logging centralizado con Monolog |
-| Estados en BD (lentos) | Estados en Redis (rápido) |
-| Difícil escalar | Múltiples workers en paralelo |
-
----
-
-## Componentes Clave
-
-### 1. **RedisQueue** (`queue/RedisQueue.php`)
-
-Gestiona colas de mensajes con soporte para:
-
-- ✅ Prioridades (alta, normal, baja)
-- ✅ Retry con backoff exponencial
-- ✅ Dead Letter Queue (DLQ) para mensajes fallidos
-- ✅ Rate limiting integrado
-- ✅ Delayed messages (programar envíos)
-
-**Ejemplo de uso:**
-
-```php
-use WhatsApp\Queue\RedisQueue;
-
-$queue = new RedisQueue();
-
-// Encolar mensaje
-$queue->push('messages', [
- 'user' => $user,
- 'messageText' => 'Hola',
- 'messageType' => 'text'
-], 0); // Prioridad alta
-
-// Obtener mensaje (bloqueante)
-$message = $queue->pop(['messages', 'media'], 5); // timeout 5s
-
-// Re-encolar con retry si falla
-if (!$success) {
- $queue->retry('messages', $message, 3); // max 3 intentos
-}
-
-// Estadísticas
-$stats = $queue->getStats();
-// ['messages' => ['pending' => 5, 'delayed' => 2, 'failed' => 1]]
-```
-
-### 2. **ConversationState** (`queue/ConversationState.php`)
-
-Gestiona estados de conversación en Redis (rápido):
-
-```php
-use WhatsApp\Queue\ConversationState;
-
-$state = new ConversationState();
-
-// Establecer estado
-$state->setState($userId, 'waiting_name', [
- 'step' => 1,
- 'retries' => 0
-], 3600); // Expira en 1 hora
-
-// Obtener estado
-$current = $state->getState($userId);
-// ['state' => 'waiting_name', 'context' => [...], 'updated_at' => ...]
-
-// Actualizar contexto
-$state->updateContext($userId, ['name' => 'Juan']);
-
-// Limpiar al finalizar
-$state->clearState($userId);
-
-// Datos temporales (para formularios multi-paso)
-$state->setTemporaryData($userId, 'email', 'juan@example.com');
-$email = $state->getTemporaryData($userId, 'email');
-```
-
-### 3. **Worker** (`worker.php`)
-
-Procesa mensajes de forma asíncrona:
-
-```bash
-# Ejecutar worker manualmente
-php worker.php messages
-
-# Worker daemon (loop infinito)
-php worker.php --daemon
-
-# Worker específico para media
-php worker.php media --daemon
-```
-
-**Características:**
-
-- ✅ Procesa colas: `messages`, `media`, `notifications`
-- ✅ Shutdown graceful (SIGTERM, SIGINT)
-- ✅ Auto-restart si excede memoria (128MB)
-- ✅ Retry automático con backoff
-- ✅ Logging detallado
-
-### 4. **Webhook Optimizado** (`api/webhook_optimized.php`)
-
-Nuevo webhook que **responde inmediatamente**:
-
-```php
-// Flujo optimizado:
-1. Leer payload
-2. Responder 200 OK inmediatamente (< 100ms)
-3. Forzar envío con fastcgi_finish_request()
-4. Procesar y encolar sin presión de tiempo
-```
-
-**Tiempos medidos:**
-
-- ⚡ Respuesta al cliente: **50-100ms**
-- ⏱️ Procesamiento total: **200-500ms**
-- 📊 vs. webhook anterior: **5x más rápido**
-
-### 5. **Rate Limiting** (`services/WhatsAppServiceWithRateLimit.php`)
-
-Respeta límites de WhatsApp automáticamente:
-
-- 🚦 **80 mensajes/segundo**
-- 🚦 **1,000 mensajes/hora**
-- 🚦 **10,000 mensajes/día**
-
-```php
-use WhatsAppServiceWithRateLimit;
-
-$whatsapp = new WhatsAppServiceWithRateLimit();
-
-// Envía si está dentro del límite, encola si no
-$result = $whatsapp->sendTextMessage($phone, 'Hola');
-
-if ($result['queued'] ?? false) {
- // Mensaje encolado por rate limit
-}
-
-// Ver estadísticas
-$stats = $whatsapp->getRateLimitStats();
-/*
-[
- 'per_second' => ['current' => 45, 'limit' => 80, 'remaining' => 35],
- 'per_hour' => ['current' => 320, 'limit' => 1000, 'remaining' => 680],
- 'per_day' => ['current' => 5200, 'limit' => 10000, 'remaining' => 4800]
-]
-*/
-```
-
-### 6. **Logging Estructurado** (`classes/LoggerFactory.php`)
-
-```php
-use LoggerFactory;
-
-// Loggers por canal
-$logger = LoggerFactory::webhook();
-$logger->info("Webhook received", ['entries' => 2]);
-
-// Helper global (compatible con código existente)
-writeLog('INFO', 'Message processed', ['user_id' => 123], 'bot');
-
-// Estadísticas
-$stats = LoggerFactory::getStats();
-// ['total_files' => 12, 'total_size_mb' => 45.2, 'by_channel' => [...]]
-
-// Limpieza de logs antiguos (ejecutar con cron)
-LoggerFactory::cleanup(30); // Elimina logs > 30 días
-```
-
----
-
-## Flujo de Mensajes
-
-### 1. **Mensaje entrante de WhatsApp**
-
-```
-Usuario → WhatsApp API → webhook_optimized.php
- ↓ (< 100ms)
- Responde 200 OK
- ↓
- Encola en Redis (queue:messages)
- ↓
- worker.php (BRPOP)
- ↓
- BotService.processMessage()
- ↓
- WhatsAppService.sendTextMessage()
- ↓ (con rate limit)
- WhatsApp API → Usuario
-```
-
-### 2. **Envío de mensajes salientes**
-
-```php
-// Sin rate limit (envío inmediato)
-WhatsAppService::sendTextMessage($phone, 'Hola');
-
-// Con rate limit (puede encolar automáticamente)
-WhatsAppServiceWithRateLimit::sendTextMessage($phone, 'Hola');
-```
-
-### 3. **Procesamiento de media**
-
-```
-Usuario envía imagen → webhook detecta media
- ↓
- Encola en queue:media
- ↓
- worker.php procesa
- ↓
- MediaService.fetchAndStoreFromGraph()
- ↓
- Descarga y guarda en /uploads/
- ↓
- Actualiza conversations.local_file
-```
-
----
-
-## Instalación y Configuración
-
-### 1. **Instalar dependencias**
-
-```bash
-cd /ruta/a/tu/proyecto
-composer install
-```
-
-### 2. **Configurar .env**
-
-```bash
-cp .env.example .env
-nano .env
-```
-
-Agregar configuración de Redis:
-
-```env
-# Redis
-REDIS_HOST=127.0.0.1
-REDIS_PORT=6379
-REDIS_PASSWORD=
-REDIS_DB=0
-
-# Rate Limiting
-ENABLE_RATE_LIMIT=true
-
-# Logging
-LOG_LEVEL=INFO
-LOG_PATH=logs
-```
-
-### 3. **Instalar y configurar Redis**
-
-```bash
-# Ubuntu/Debian
-sudo apt update
-sudo apt install redis-server -y
-sudo systemctl enable redis-server
-sudo systemctl start redis-server
-
-# Verificar
-redis-cli ping
-# Respuesta: PONG
-```
-
-### 4. **Configurar Webhook en WhatsApp**
-
-Actualizar URL del webhook a `webhook_optimized.php`:
-
-```
-https://tudominio.com/api/webhook_optimized.php
-```
-
-### 5. **Iniciar Workers**
-
-#### Opción A: Manual (desarrollo)
-
-```bash
-php worker.php --daemon &
-```
-
-#### Opción B: Con Supervisor (producción)
-
-```bash
-# Instalar supervisor
-sudo apt install supervisor -y
-
-# Copiar configuración
-sudo cp supervisor-whatsapp-worker.conf /etc/supervisor/conf.d/
-
-# Actualizar rutas en el archivo
-sudo nano /etc/supervisor/conf.d/supervisor-whatsapp-worker.conf
-
-# Recargar supervisor
-sudo supervisorctl reread
-sudo supervisorctl update
-sudo supervisorctl start whatsapp-worker:*
-
-# Ver estado
-sudo supervisorctl status
-```
-
----
-
-## Deployment
-
-### Checklist de producción
-
-- [ ] Redis instalado y funcionando
-- [ ] Composer dependencies instaladas
-- [ ] .env configurado correctamente
-- [ ] Workers iniciados con Supervisor
-- [ ] Webhook actualizado en WhatsApp
-- [ ] Logs con permisos correctos (755 en `/logs`)
-- [ ] Cron configurado para logs cleanup
-
-### Cron recomendado
-
-```bash
-# Editar crontab
-crontab -e
-
-# Agregar estas líneas:
-# Procesar mensajes delayed cada minuto
-* * * * * cd /ruta/proyecto && php -r "require 'vendor/autoload.php'; use WhatsApp\Queue\RedisQueue; \$q = new RedisQueue(); \$q->processDelayed('messages');"
-
-# Limpiar logs antiguos cada día a las 3 AM
-0 3 * * * cd /ruta/proyecto && php -r "require 'classes/LoggerFactory.php'; LoggerFactory::cleanup(30);"
-
-# Monitorear workers (reiniciar si están caídos)
-*/5 * * * * supervisorctl status whatsapp-worker:* | grep -q RUNNING || supervisorctl restart whatsapp-worker:*
-```
-
----
-
-## Monitoreo
-
-### 1. **Ver estadísticas de colas**
-
-```php
-use WhatsApp\Queue\RedisQueue;
-
-$queue = new RedisQueue();
-$stats = $queue->getStats();
-print_r($stats);
-```
-
-### 2. **Ver logs en tiempo real**
-
-```bash
-# Webhook
-tail -f logs/webhook.log
-
-# Worker
-tail -f logs/worker.log
-
-# Bot
-tail -f logs/bot.log
-
-# Todos los errores
-tail -f logs/*.log | grep ERROR
-```
-
-### 3. **Verificar estado de workers**
-
-```bash
-sudo supervisorctl status whatsapp-worker:*
-```
-
-### 4. **Monitorear Redis**
-
-```bash
-# Conexión
-redis-cli
-
-# Ver todas las colas
-KEYS whatsapp:queue:*
-
-# Ver longitud de cola
-LLEN whatsapp:queue:messages
-
-# Ver rate limits
-KEYS whatsapp:ratelimit:*
-```
-
----
-
-## Troubleshooting
-
-### ❌ Worker no procesa mensajes
-
-**Solución:**
-
-```bash
-# Verificar si worker está corriendo
-ps aux | grep worker.php
-
-# Ver logs
-tail -f logs/worker.log
-
-# Reiniciar workers
-sudo supervisorctl restart whatsapp-worker:*
-```
-
-### ❌ Redis no conecta
-
-**Solución:**
-
-```bash
-# Verificar Redis
-sudo systemctl status redis-server
-
-# Reiniciar
-sudo systemctl restart redis-server
-
-# Verificar puerto
-netstat -an | grep 6379
-```
-
-### ❌ Rate limit excedido constantemente
-
-**Solución:**
-
-```php
-// Ver estadísticas actuales
-$whatsapp = new WhatsAppServiceWithRateLimit();
-print_r($whatsapp->getRateLimitStats());
-
-// Resetear límites (solo desarrollo)
-$whatsapp->resetRateLimits();
-
-// Aumentar número de workers
-// Editar supervisor-whatsapp-worker.conf:
-// numprocs=5 (en lugar de 3)
-```
-
-### ❌ Logs muy grandes
-
-**Solución:**
-
-```bash
-# Limpiar manualmente
-cd logs
-find . -name "*.log*" -mtime +7 -delete
-
-# O ejecutar cleanup:
-php -r "require 'classes/LoggerFactory.php'; echo LoggerFactory::cleanup(7) . ' archivos eliminados';"
-```
-
----
-
-## Performance Benchmarks
-
-### Antes vs. Ahora
-
-| Métrica | Antes | Ahora | Mejora |
-|---------|-------|-------|--------|
-| Respuesta webhook | 3-8s | 50-150ms | **50x más rápido** |
-| Mensajes/segundo | ~10 | 80 (límite API) | **8x más rápido** |
-| Timeout en webhook | 5-10% | 0% | **100% confiable** |
-| Memoria worker | N/A | 20-40 MB | **Eficiente** |
-| Reintentos exitosos | Manual | Automático | **100% automatizado** |
-
----
-
-## Próximos pasos
-
-1. ✅ **Implementado:** Arquitectura con colas
-2. ✅ **Implementado:** Rate limiting
-3. ✅ **Implementado:** Logging estructurado
-4. 🔜 **Recomendado:** Monitoreo con Prometheus/Grafana
-5. 🔜 **Recomendado:** Alertas por email/Slack cuando worker cae
-6. 🔜 **Recomendado:** Dashboard web para ver estadísticas en tiempo real
-
----
-
-## Soporte
-
-Para dudas o problemas:
-
-1. Revisar logs en `/logs`
-2. Ver [troubleshooting](#troubleshooting)
-3. Consultar documentación de WhatsApp API
-4. Verificar configuración de Redis y workers
-
----
-
-**Última actualización:** Enero 2026
-**Versión:** 2.0
diff --git a/DEPLOYMENT_GUIDE.md b/DEPLOYMENT_GUIDE.md
deleted file mode 100644
index 6c6fd49..0000000
--- a/DEPLOYMENT_GUIDE.md
+++ /dev/null
@@ -1,359 +0,0 @@
-# 🚀 Guía de Despliegue en Servidor
-
-## 📋 Requisitos del Servidor
-
-### Mínimos
-- **PHP**: 8.0 o superior
-- **MySQL/MariaDB**: 5.7 o superior
-- **Servidor Web**: Apache/Nginx
-- **SSL**: Certificado válido (HTTPS obligatorio para WhatsApp)
-- **Memoria RAM**: Mínimo 512MB
-- **Espacio**: 1GB libre
-
-### Extensiones PHP Requeridas
-```bash
-php-pdo
-php-pdo-mysql
-php-curl
-php-json
-php-mbstring
-php-openssl
-```
-
-## 🌍 Configuración por Tipo de Servidor
-
-### 🔹 **VPS/Servidor Dedicado (Ubuntu/CentOS)**
-
-#### 1. Instalar Dependencias
-```bash
-# Ubuntu/Debian
-sudo apt update
-sudo apt install apache2 mysql-server php8.0 php8.0-mysql php8.0-curl php8.0-json php8.0-mbstring
-
-# CentOS/RHEL
-sudo yum install httpd mysql-server php php-mysql php-curl php-json php-mbstring
-```
-
-#### 2. Configurar Apache
-```apache
-# /etc/apache2/sites-available/whatsapp-bot.conf
-
- ServerName tudominio.com
- DocumentRoot /var/www/html/bot
-
-
- AllowOverride All
- Require all granted
-
-
- # Redirigir HTTP a HTTPS
- RewriteEngine On
- RewriteCond %{HTTPS} off
- RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
-
-
-
- ServerName tudominio.com
- DocumentRoot /var/www/html/migrador
-
- SSLEngine on
- SSLCertificateFile /path/to/your/certificate.crt
- SSLCertificateKeyFile /path/to/your/private.key
-
-
- AllowOverride All
- Require all granted
-
-
-```
-
-#### 3. Subir Archivos
-```bash
-# Crear directorio
-sudo mkdir -p /var/www/html/migrador
-
-# Subir archivos (usando SCP/SFTP)
-scp -r bot/* usuario@servidor:/var/www/html/bot/
-
-# Configurar permisos
-sudo chown -R www-data:www-data /var/www/html/bot
-sudo chmod -R 755 /var/www/html/bot
-```
-
-### 🔹 **Hosting Compartido (cPanel/Plesk)**
-
-#### 1. Subir Archivos
-- Accede a tu **File Manager** en cPanel
-- Ve a la carpeta `public_html`
-- Crea carpeta `migrador` (o sube directamente a raíz)
-- Sube todos los archivos del proyecto
-
-#### 2. Configurar Base de Datos
-- Ve a **MySQL Databases** en cPanel
-- Crea nueva base de datos: `tuusuario_whatsapp`
-- Crea usuario y asigna privilegios
-- Anota: host, usuario, contraseña, base de datos
-
-#### 3. Editar Configuración
-```php
-// config/config.php
-define('DB_HOST', 'localhost'); // O tu host específico
-define('DB_NAME', 'tuusuario_whatsapp');
-define('DB_USER', 'tuusuario_dbuser');
-define('DB_PASS', 'tu_password_db');
-```
-
-### 🔹 **Servicios en la Nube**
-
-#### **AWS EC2**
-```bash
-# 1. Conectar por SSH
-ssh -i tu-clave.pem ec2-user@tu-ip-publica
-
-# 2. Instalar LAMP stack
-sudo yum update -y
-sudo amazon-linux-extras install -y lamp-mariadb10.2-php7.2 php7.2
-
-# 3. Configurar y seguir pasos de VPS
-```
-
-#### **DigitalOcean Droplet**
-```bash
-# 1. Crear droplet con LAMP stack preinstalado
-# 2. Conectar por SSH
-# 3. Subir archivos a /var/www/html/
-```
-
-#### **Google Cloud Platform**
-```bash
-# 1. Crear VM con Ubuntu
-# 2. Instalar LAMP stack
-# 3. Configurar firewall para puertos 80/443
-```
-
-## 🔧 Configuración Específica del Sistema
-
-### 1. Editar config/config.php
-```php
-
-```
-
-### 2. Crear .htaccess (Apache)
-```apache
-# .htaccess en raíz del proyecto
-RewriteEngine On
-
-# Forzar HTTPS
-RewriteCond %{HTTPS} off
-RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
-
-# Proteger archivos sensibles
-
- Require all denied
-
-
-
- Require all denied
-
-
-# Cache headers
-
- ExpiresActive on
- ExpiresByType text/css "access plus 1 year"
- ExpiresByType application/javascript "access plus 1 year"
- ExpiresByType image/png "access plus 1 year"
- ExpiresByType image/jpg "access plus 1 year"
-
-```
-
-### 3. Configurar SSL (Let's Encrypt)
-```bash
-# Instalar Certbot
-sudo apt install certbot python3-certbot-apache
-
-# Obtener certificado
-sudo certbot --apache -d tudominio.com
-
-# Auto-renovación
-sudo crontab -e
-# Agregar: 0 12 * * * /usr/bin/certbot renew --quiet
-```
-
-## 🔗 Configuración de WhatsApp Webhook
-
-### 1. URL del Webhook
-```
-https://tudominio.com/bot/api/webhook.php
-```
-
-### 2. Configurar en Facebook Developers
-1. Ve a [developers.facebook.com](https://developers.facebook.com)
-2. Selecciona tu app de WhatsApp Business
-3. Ve a **WhatsApp > Configuration**
-4. En **Webhooks**:
- - **Callback URL**: `https://tudominio.com/migrador/api/webhook.php`
- - **Verify Token**: `mi_token_secreto_123` (o cambia en config.php)
- - **Webhook fields**: ✅ `conversations`
-
-### 3. Verificar Webhook
-```bash
-# Probar webhook manualmente
-curl -X GET "https://tudominio.com/migrador/api/webhook.php?hub.mode=subscribe&hub.challenge=CHALLENGE_ACCEPTED&hub.verify_token=mi_token_secreto_123"
-```
-
-## ⚙️ Configuración Post-Instalación
-
-### 1. Ejecutar Instalador
-```
-https://tudominio.com/migrador/install.php
-```
-
-### 2. Verificar Sistema
-```
-https://tudominio.com/migrador/test.php
-```
-
-### 3. Configurar Panel
-```
-https://tudominio.com/migrador/index.php
-```
-
-## 🔒 Seguridad en Producción
-
-### 1. Configuraciones PHP
-```ini
-# php.ini ajustes
-expose_php = Off
-display_errors = Off
-log_errors = On
-allow_url_fopen = Off
-```
-
-### 2. Permisos de Archivos
-```bash
-# Archivos: 644
-sudo find /var/www/html/migrador -type f -exec chmod 644 {} \;
-
-# Directorios: 755
-sudo find /var/www/html/migrador -type d -exec chmod 755 {} \;
-
-# Config solo lectura
-sudo chmod 600 /var/www/html/migrador/config/config.php
-```
-
-### 3. Backup Automático
-```bash
-#!/bin/bash
-# backup.sh
-DATE=$(date +%Y%m%d_%H%M%S)
-mysqldump -u usuario -p password whatsapp_db > /backups/whatsapp_$DATE.sql
-tar -czf /backups/files_$DATE.tar.gz /var/www/html/migrador
-```
-
-## 📊 Monitoreo y Logs
-
-### 1. Logs de Apache
-```bash
-tail -f /var/log/apache2/error.log
-tail -f /var/log/apache2/access.log
-```
-
-### 2. Logs de PHP
-```bash
-tail -f /var/log/php_errors.log
-```
-
-### 3. Logs del Sistema
-```bash
-# Ver logs desde el panel
-https://tudominio.com/migrador/index.php#logs
-```
-
-## 🚀 Optimizaciones de Rendimiento
-
-### 1. Cache de PHP (OPcache)
-```ini
-# php.ini
-opcache.enable=1
-opcache.memory_consumption=128
-opcache.max_accelerated_files=1000
-```
-
-### 2. Compresión GZIP
-```apache
-# En .htaccess
-
- AddOutputFilterByType DEFLATE text/plain
- AddOutputFilterByType DEFLATE text/html
- AddOutputFilterByType DEFLATE text/css
- AddOutputFilterByType DEFLATE application/javascript
-
-```
-
-### 3. Optimización MySQL
-```sql
--- my.cnf
-[mysqld]
-innodb_buffer_pool_size = 256M
-query_cache_type = 1
-query_cache_size = 64M
-```
-
-## 📞 URLs de Producción
-
-Una vez configurado:
-
-- **🏠 Panel**: https://tudominio.com/migrador/index.php
-- **🔗 API**: https://tudominio.com/migrador/api/
-- **📱 Webhook**: https://tudominio.com/migrador/api/webhook.php
-- **📊 Stats**: https://tudominio.com/migrador/api/get_stats.php
-
-## ⚠️ Troubleshooting
-
-### Error 500
-- Revisar logs de Apache/PHP
-- Verificar permisos de archivos
-- Comprobar configuración de base de datos
-
-### Webhook no funciona
-- Verificar SSL válido
-- Comprobar URL pública
-- Revisar token de verificación
-
-### Base de datos no conecta
-- Verificar credenciales
-- Comprobar host/puerto
-- Revisar permisos de usuario MySQL
-
-## 📝 Checklist Final
-
-- [ ] ✅ Servidor con PHP 8.0+ y MySQL
-- [ ] ✅ SSL/HTTPS configurado
-- [ ] ✅ Archivos subidos con permisos correctos
-- [ ] ✅ Base de datos creada y configurada
-- [ ] ✅ config.php editado con datos reales
-- [ ] ✅ Instalador ejecutado sin errores
-- [ ] ✅ Webhook configurado en Facebook
-- [ ] ✅ Sistema probado y funcionando
-- [ ] ✅ Backup automático configurado
-
-¡Tu sistema WhatsApp Bot Manager estará listo para producción! 🎉
\ No newline at end of file
diff --git a/DOCKER_DEV_GUIDE.md b/DOCKER_DEV_GUIDE.md
deleted file mode 100644
index 6f0764c..0000000
--- a/DOCKER_DEV_GUIDE.md
+++ /dev/null
@@ -1,245 +0,0 @@
-# Guía de Uso - Docker Compose Development
-
-## 🚀 Inicio Rápido
-
-### Levantar entorno de desarrollo
-```bash
-docker-compose -f docker-compose.dev.yml up -d
-```
-
-### Ver logs en tiempo real
-```bash
-docker-compose -f docker-compose.dev.yml logs -f
-```
-
-### Detener entorno
-```bash
-docker-compose -f docker-compose.dev.yml down
-```
-
-### Reconstruir contenedores
-```bash
-docker-compose -f docker-compose.dev.yml build --no-cache
-docker-compose -f docker-compose.dev.yml up -d
-```
-
----
-
-## 🛠️ Herramientas Disponibles
-
-| Servicio | URL | Descripción |
-|----------|-----|-------------|
-| **Aplicación** | http://localhost:8080 | WhatsApp Bot principal |
-| **XDebug** | Puerto 9003 | Debugger para IDE (VSCode, PHPStorm) |
-| **Redis Commander** | http://localhost:8082 | Interface web para Redis |
-| **MailHog** | http://localhost:8025 | Captura de emails de prueba |
-| **Adminer** | http://localhost:8083 | Gestor de base de datos |
-
----
-
-## 🔧 Características de Desarrollo
-
-### ✅ Hot Reload Activado
-Los cambios en archivos PHP/JS/CSS se reflejan **inmediatamente** sin reiniciar:
-- ✅ Volumen mount: `./:/var/www/html`
-- ✅ OPcache desactivado
-- ✅ No requiere rebuild
-
-### 🐛 Debugging con XDebug
-
-#### VSCode Configuration (`.vscode/launch.json`)
-```json
-{
- "version": "0.2.0",
- "configurations": [
- {
- "name": "Listen for XDebug",
- "type": "php",
- "request": "launch",
- "port": 9003,
- "pathMappings": {
- "/var/www/html": "${workspaceFolder}"
- }
- }
- ]
-}
-```
-
-#### PHPStorm Configuration
-1. Settings → PHP → Debug → XDebug
-2. Port: `9003`
-3. Path mappings: `/var/www/html` → `/Users/lizandro/Documents/GitHub/whatsapp`
-
-### 📊 Logs en Tiempo Real
-```bash
-# Ver logs de todos los servicios
-docker-compose -f docker-compose.dev.yml logs -f
-
-# Ver logs de un servicio específico
-docker-compose -f docker-compose.dev.yml logs -f app
-
-# Ver logs PHP-FPM
-docker-compose -f docker-compose.dev.yml exec app tail -f /var/www/html/logs/php-fpm-error.log
-
-# Ver logs de XDebug
-docker-compose -f docker-compose.dev.yml exec app tail -f /var/www/html/logs/xdebug.log
-```
-
----
-
-## 📧 Testing de Emails con MailHog
-
-MailHog captura todos los emails enviados desde la aplicación:
-
-1. Configura PHP para usar MailHog:
-```php
-ini_set('SMTP', 'mailhog');
-ini_set('smtp_port', 1025);
-```
-
-2. Abre http://localhost:8025 para ver emails capturados
-
----
-
-## 🔍 Inspeccionar Redis
-
-### Opción 1: Redis Commander (Web UI)
-- URL: http://localhost:8082
-- Ver claves, valores, TTL
-- Ejecutar comandos Redis
-
-### Opción 2: Redis CLI
-```bash
-docker-compose -f docker-compose.dev.yml exec redis redis-cli
-
-# Comandos útiles:
-KEYS * # Ver todas las claves
-GET clave # Ver valor de una clave
-TTL clave # Ver tiempo de vida
-MONITOR # Ver comandos en tiempo real
-```
-
----
-
-## 🗄️ Acceso a Base de Datos
-
-### Adminer (http://localhost:8083)
-- **Server**: Tu DB_HOST del .env
-- **Username**: Tu DB_USER del .env
-- **Password**: Tu DB_PASS del .env
-- **Database**: Tu DB_NAME del .env
-
-### MySQL CLI desde contenedor
-```bash
-docker-compose -f docker-compose.dev.yml exec app mysql -h ${DB_HOST} -u ${DB_USER} -p${DB_PASS} ${DB_NAME}
-```
-
----
-
-## 🧪 Comandos Útiles
-
-### Ejecutar comandos en el contenedor
-```bash
-# Bash interactivo
-docker-compose -f docker-compose.dev.yml exec app bash
-
-# PHP CLI
-docker-compose -f docker-compose.dev.yml exec app php -v
-
-# Composer
-docker-compose -f docker-compose.dev.yml exec app composer install
-
-# Verificar configuración PHP
-docker-compose -f docker-compose.dev.yml exec app php -i | grep xdebug
-```
-
-### Reiniciar servicios
-```bash
-# Reiniciar un servicio específico
-docker-compose -f docker-compose.dev.yml restart app
-
-# Reiniciar todos
-docker-compose -f docker-compose.dev.yml restart
-```
-
-### Limpiar todo (cuidado!)
-```bash
-# Detener y eliminar contenedores + volúmenes
-docker-compose -f docker-compose.dev.yml down -v
-
-# Limpiar imágenes no utilizadas
-docker system prune -a
-```
-
----
-
-## 🚨 Troubleshooting
-
-### Problema: Cambios no se reflejan
-```bash
-# 1. Verificar que el volumen esté montado
-docker-compose -f docker-compose.dev.yml exec app ls -la /var/www/html
-
-# 2. Verificar que OPcache esté desactivado
-docker-compose -f docker-compose.dev.yml exec app php -i | grep opcache.enable
-
-# 3. Limpiar caché del navegador (Cmd+Shift+R)
-```
-
-### Problema: XDebug no funciona
-```bash
-# Verificar que XDebug esté instalado
-docker-compose -f docker-compose.dev.yml exec app php -m | grep xdebug
-
-# Ver configuración de XDebug
-docker-compose -f docker-compose.dev.yml exec app php -i | grep xdebug
-
-# Ver logs de XDebug
-docker-compose -f docker-compose.dev.yml exec app tail -f /var/www/html/logs/xdebug.log
-```
-
-### Problema: Redis no conecta
-```bash
-# Verificar que Redis esté corriendo
-docker-compose -f docker-compose.dev.yml ps
-
-# Test de conexión
-docker-compose -f docker-compose.dev.yml exec app php -r "try { \$r = new Redis(); \$r->connect('redis', 6379); echo 'OK'; } catch (Exception \$e) { echo \$e->getMessage(); }"
-```
-
----
-
-## ⚠️ Diferencias con Producción
-
-| Característica | Desarrollo | Producción |
-|----------------|-----------|------------|
-| OPcache | ❌ Desactivado | ✅ Activado |
-| Error Display | ✅ Activado | ❌ Desactivado |
-| XDebug | ✅ Activado | ❌ Desactivado |
-| Log Level | DEBUG | INFO/WARNING |
-| Workers | 1 | 2+ |
-| Rate Limiting | ❌ Desactivado | ✅ Activado |
-
----
-
-## 🔄 Migrar de Dev a Producción
-
-```bash
-# 1. Detener entorno de desarrollo
-docker-compose -f docker-compose.dev.yml down
-
-# 2. Levantar entorno de producción
-docker-compose up -d
-
-# 3. Verificar
-docker-compose ps
-```
-
----
-
-## 📝 Notas
-
-- **No commitear** `docker-compose.dev.yml` en producción
-- Usar `.env` para configuración sensible
-- Los volúmenes de desarrollo son locales (no se comparten)
-- MailHog solo atrapa emails locales, no los envía realmente
diff --git a/DOCKER_README.md b/DOCKER_README.md
deleted file mode 100644
index e79cc42..0000000
--- a/DOCKER_README.md
+++ /dev/null
@@ -1,462 +0,0 @@
-# 🐳 WhatsApp Bot Manager - Docker
-
-## Guía Rápida de Instalación
-
-Esta es la forma **más fácil y rápida** de desplegar el WhatsApp Bot Manager con todas sus funcionalidades.
-
----
-
-## 📦 ¿Qué incluye?
-
-La arquitectura Docker incluye:
-
-- ✅ **PHP 8.2 + Nginx** (aplicación web)
-- ✅ **MySQL 8.0** (base de datos)
-- ✅ **Redis 7** (colas y caché)
-- ✅ **3 Workers** automáticos (procesamiento asíncrono)
-- ✅ **Supervisor** (gestor de procesos)
-- ✅ **PHPMyAdmin** (opcional, para desarrollo)
-- ✅ **Redis Commander** (opcional, para debugging)
-- ✅ **Health checks** automáticos
-- ✅ **Logs centralizados**
-- ✅ **Auto-restart** en caso de fallos
-
----
-
-## 🚀 Instalación en 3 Pasos
-
-### 1. Clonar y configurar
-
-```bash
-cd /ruta/a/tu/proyecto
-
-# Copiar variables de entorno
-cp .env.docker .env
-
-# Editar .env con tus credenciales de WhatsApp
-nano .env
-```
-
-**Mínimo requerido en `.env`:**
-
-```env
-# WhatsApp API
-WHATSAPP_TOKEN=tu_token_aqui
-WHATSAPP_PHONE_NUMBER_ID=tu_phone_number_id
-WEBHOOK_VERIFY_TOKEN=tu_token_verificacion
-
-# Base de datos (puedes dejar estos valores)
-DB_PASS=password_seguro
-DB_ROOT_PASSWORD=root_password_seguro
-```
-
-### 2. Construir contenedores
-
-```bash
-docker-compose build
-```
-
-### 3. Iniciar servicios
-
-```bash
-docker-compose up -d
-```
-
-**¡Listo!** 🎉 Tu bot está corriendo en: `http://localhost:8080`
-
----
-
-## 📋 Comandos Útiles
-
-### Ver logs en tiempo real
-
-```bash
-# Todos los servicios
-docker-compose logs -f
-
-# Solo la aplicación
-docker-compose logs -f app
-
-# Solo workers
-docker-compose logs -f worker
-
-# Solo webhook
-docker-compose logs -f app | grep webhook
-```
-
-### Ver estado de servicios
-
-```bash
-docker-compose ps
-```
-
-### Reiniciar servicios
-
-```bash
-# Reiniciar todo
-docker-compose restart
-
-# Reiniciar solo app
-docker-compose restart app
-
-# Reiniciar workers
-docker-compose restart worker
-```
-
-### Ejecutar comandos dentro del contenedor
-
-```bash
-# Shell interactivo
-docker-compose exec app sh
-
-# Ejecutar script PHP
-docker-compose exec app php test_architecture.php
-
-# Ver cola de Redis
-docker-compose exec redis redis-cli KEYS "whatsapp:queue:*"
-
-# Ver estadísticas de workers
-docker-compose exec app supervisorctl status
-```
-
-### Detener y eliminar todo
-
-```bash
-# Detener servicios
-docker-compose down
-
-# Detener y eliminar volúmenes (⚠️ borra datos)
-docker-compose down -v
-```
-
----
-
-## 🔧 Configuración Avanzada
-
-### Cambiar puerto de la aplicación
-
-```bash
-# En .env
-APP_PORT=3000
-
-# Reiniciar
-docker-compose up -d
-```
-
-### Aumentar número de workers
-
-```bash
-# En .env
-WORKER_REPLICAS=5
-
-# En docker-compose.yml, en la sección app > environment
-# Ajustar numprocs en supervisor/programs.conf
-
-# Rebuild
-docker-compose up -d --build
-```
-
-### Habilitar herramientas de desarrollo
-
-```bash
-# PHPMyAdmin en http://localhost:8081
-# Redis Commander en http://localhost:8082
-docker-compose --profile dev up -d
-```
-
----
-
-## 🏗️ Estructura de Contenedores
-
-```
-┌─────────────────────────────────────────┐
-│ app (PHP + Nginx + Workers) │
-│ - Puerto: 8080 → 80 │
-│ - Supervisor: Nginx + PHP-FPM + 3 Workers│
-│ - Health check: /health.php │
-└─────────┬───────────────────────────────┘
- │
- ├─────────┐
- │ │
-┌─────────▼──┐ ┌───▼────────┐
-│ db │ │ redis │
-│ MySQL 8.0 │ │ Redis 7 │
-│ Puerto:3306│ │ Puerto:6379│
-└────────────┘ └────────────┘
-
-┌─────────────────────┐
-│ worker (opcional) │
-│ 2 workers extra │
-└─────────────────────┘
-
-┌──────────────────────────┐
-│ phpmyadmin (opcional) │
-│ Puerto: 8081 │
-└──────────────────────────┘
-
-┌──────────────────────────┐
-│ redis-commander (opcional)│
-│ Puerto: 8082 │
-└──────────────────────────┘
-```
-
----
-
-## 📊 Monitoreo
-
-### Health Check
-
-```bash
-curl http://localhost:8080/health.php
-```
-
-Respuesta:
-
-```json
-{
- "status": "healthy",
- "timestamp": "2026-01-27 10:30:00",
- "checks": {
- "php": {"status": "ok", "version": "8.2.15"},
- "database": {"status": "ok", "host": "db"},
- "redis": {"status": "ok", "host": "redis"},
- "logs": {"status": "ok", "writable": true},
- "uploads": {"status": "ok", "writable": true},
- "queue": {
- "status": "ok",
- "stats": {
- "messages": {"pending": 0, "delayed": 0, "failed": 0},
- "media": {"pending": 0, "delayed": 0, "failed": 0}
- }
- }
- }
-}
-```
-
-### Estado de Workers
-
-```bash
-docker-compose exec app supervisorctl status
-```
-
-### Estadísticas de Redis
-
-```bash
-docker-compose exec redis redis-cli INFO stats
-```
-
-### Uso de recursos
-
-```bash
-docker stats
-```
-
----
-
-## 🔐 Actualizar Webhook en WhatsApp
-
-Una vez desplegado, actualiza la URL del webhook en WhatsApp Business Dashboard:
-
-```
-https://tudominio.com/api/webhook_optimized.php
-```
-
-Para desarrollo local con ngrok:
-
-```bash
-ngrok http 8080
-
-# Usar la URL generada:
-# https://xxxxx.ngrok.io/api/webhook_optimized.php
-```
-
----
-
-## 🐛 Troubleshooting
-
-### ❌ Puerto 8080 ya está en uso
-
-```bash
-# Cambiar puerto en .env
-APP_PORT=3000
-
-# O detener servicio que usa 8080
-sudo lsof -ti:8080 | xargs kill -9
-```
-
-### ❌ Error de permisos en logs/uploads
-
-```bash
-docker-compose exec app chown -R www:www /var/www/html/logs
-docker-compose exec app chown -R www:www /var/www/html/uploads
-docker-compose exec app chmod -R 755 /var/www/html/logs
-docker-compose exec app chmod -R 755 /var/www/html/uploads
-```
-
-### ❌ Workers no procesan mensajes
-
-```bash
-# Ver logs de workers
-docker-compose logs worker
-
-# Reiniciar workers
-docker-compose exec app supervisorctl restart whatsapp-worker:*
-
-# Ver estado
-docker-compose exec app supervisorctl status
-```
-
-### ❌ Redis no conecta
-
-```bash
-# Verificar que Redis está corriendo
-docker-compose ps redis
-
-# Ver logs
-docker-compose logs redis
-
-# Reiniciar
-docker-compose restart redis
-```
-
-### ❌ MySQL no inicia
-
-```bash
-# Ver logs
-docker-compose logs db
-
-# Eliminar volumen y recrear (⚠️ borra datos)
-docker-compose down -v
-docker-compose up -d
-```
-
-### ❌ "Connection refused" al webhook
-
-```bash
-# Verificar que Nginx está corriendo
-docker-compose exec app ps aux | grep nginx
-
-# Reiniciar Nginx
-docker-compose exec app supervisorctl restart nginx
-
-# Ver logs de Nginx
-docker-compose exec app tail -f /var/log/nginx/error.log
-```
-
----
-
-## 🚢 Deployment en Producción
-
-### 1. Servidor con Docker
-
-```bash
-# Clonar repositorio
-git clone https://github.com/tu-repo/whatsapp-bot.git
-cd whatsapp-bot
-
-# Configurar .env
-cp .env.docker .env
-nano .env
-
-# Construir y ejecutar
-docker-compose up -d --build
-
-# Ver logs
-docker-compose logs -f
-```
-
-### 2. Con Docker Swarm (alta disponibilidad)
-
-```bash
-# Inicializar swarm
-docker swarm init
-
-# Deploy
-docker stack deploy -c docker-compose.yml whatsapp
-
-# Ver servicios
-docker service ls
-
-# Escalar workers
-docker service scale whatsapp_worker=5
-```
-
-### 3. Con Kubernetes (opcional)
-
-Archivos de configuración disponibles en `/k8s` (crear según necesidad).
-
----
-
-## 📈 Performance
-
-### Configuración optimizada para producción
-
-La configuración Docker incluye:
-
-- ✅ **OPcache** activado (PHP)
-- ✅ **FastCGI cache** en Nginx
-- ✅ **Redis** para sesiones PHP
-- ✅ **Connection pooling** MySQL
-- ✅ **Gzip** compresión
-- ✅ **pm.dynamic** para PHP-FPM (auto-scaling)
-
-### Benchmarks
-
-| Métrica | Valor |
-|---------|-------|
-| Respuesta webhook | 50-100ms |
-| Throughput | 80 msg/s |
-| Memoria por worker | 30-50 MB |
-| CPU por worker | 5-10% |
-| Uptime | 99.9% |
-
----
-
-## 🔄 Actualización
-
-```bash
-# Pull últimos cambios
-git pull
-
-# Rebuild contenedores
-docker-compose build --no-cache
-
-# Reiniciar con nuevas imágenes
-docker-compose up -d
-
-# Ver logs para verificar
-docker-compose logs -f
-```
-
----
-
-## 🎯 Próximos Pasos
-
-1. ✅ Configurar SSL/HTTPS (usar reverse proxy como Traefik o Caddy)
-2. ✅ Configurar backups automáticos de MySQL
-3. ✅ Implementar monitoreo con Prometheus + Grafana
-4. ✅ Configurar alertas (email/Slack) para fallos críticos
-5. ✅ Implementar CI/CD con GitHub Actions
-
----
-
-## 📚 Documentación Adicional
-
-- [Arquitectura Completa](ARQUITECTURA_MEJORADA.md)
-- [Documentación Original](README.md)
-- [WhatsApp API Docs](https://developers.facebook.com/docs/whatsapp)
-
----
-
-## 🆘 Soporte
-
-Si encuentras problemas:
-
-1. Revisar logs: `docker-compose logs -f`
-2. Verificar health: `curl http://localhost:8080/health.php`
-3. Ejecutar tests: `docker-compose exec app php test_architecture.php`
-4. Consultar troubleshooting arriba
-
----
-
-**¡Todo listo para producción!** 🚀
diff --git a/DOCUMENTACION_COMPLETA.txt b/DOCUMENTACION_COMPLETA.txt
new file mode 100644
index 0000000..d1034e6
--- /dev/null
+++ b/DOCUMENTACION_COMPLETA.txt
@@ -0,0 +1,2253 @@
+================================================================================
+ DOCUMENTACIÓN COMPLETA
+ WhatsApp Bot Manager
+================================================================================
+
+Versión: 1.2.0
+PHP: 8.2+
+Estado: Producción
+
+Sistema completo de gestión de conversaciones de WhatsApp Business
+Desarrollado por U-Site.app
+
+================================================================================
+ÍNDICE
+================================================================================
+
+1. Introducción
+2. Manual de Usuario
+3. Documentación Técnica
+4. API REST Completa
+5. Guía de Implementación
+6. Casos de Uso
+7. Entregables del Proyecto
+8. Mantenimiento y Soporte
+
+================================================================================
+1. INTRODUCCIÓN
+================================================================================
+
+¿Qué es WhatsApp Bot Manager?
+-------------------------------
+
+WhatsApp Bot Manager es una plataforma web completa para gestionar
+comunicaciones de WhatsApp Business. Permite a empresas automatizar
+respuestas, gestionar conversaciones en tiempo real, enviar mensajes
+masivos y mantener un control total sobre las interacciones con clientes.
+
+
+Características Principales
+-----------------------------
+
+GESTIÓN DE CONVERSACIONES
+- Interfaz tipo WhatsApp Web responsive
+- Notificaciones en tiempo real con SSE (Server-Sent Events)
+- Soporte completo para multimedia (imágenes, videos, audio, documentos)
+- Búsqueda y filtrado de conversaciones
+- Indicadores de estado de mensajes (enviado, entregado, leído)
+
+AUTOMATIZACIÓN INTELIGENTE
+- Bot con respuestas automáticas configurables
+- Plantillas de mensaje aprobadas por WhatsApp
+- Menús interactivos con botones
+- Mensajes masivos con segmentación
+- Quick replies para respuestas rápidas
+
+SEGURIDAD Y CONTROL
+- Autenticación basada en base de datos
+- Protección contra ataques de fuerza bruta
+- Registro completo de actividades (logs)
+- Sistema de sesiones seguras
+- Configuración granular de permisos
+
+ANÁLISIS Y REPORTES
+- Dashboard con estadísticas en tiempo real
+- Gráficos de mensajes por día/hora
+- Estado de conversaciones (activas, resueltas, pendientes)
+- Exportación de datos a CSV
+- Métricas de rendimiento del bot
+
+
+Tecnologías Utilizadas
+------------------------
+
+Tecnología Versión Uso
+------------------------------------------------------------------------
+PHP 8.2+ Backend y lógica de negocio
+MariaDB 10.11+ Base de datos principal
+Redis 7.0+ Cache y gestión de colas
+JavaScript ES6+ Frontend interactivo
+Bootstrap 5.3.0 UI/UX responsive
+Docker 24.0+ Contenedorización
+Nginx 1.25+ Servidor web
+WhatsApp API v22.0 Integración con WhatsApp
+
+
+================================================================================
+2. MANUAL DE USUARIO
+================================================================================
+
+2.1. Acceso al Sistema
+------------------------
+
+Inicio de Sesión
+----------------
+1. Accede a la URL de tu instalación (ej: https://tudominio.com)
+2. Haz clic en "Iniciar Sesión"
+3. Ingresa tus credenciales:
+ - Usuario: admin (por defecto)
+ - Contraseña: password123 (cámbiala tras el primer acceso)
+
+Recuperación de Contraseña
+---------------------------
+1. Haz clic en "¿Olvidaste tu contraseña?"
+2. Ingresa tu correo electrónico registrado
+3. Recibirás un enlace para restablecer tu contraseña
+4. Crea una nueva contraseña segura (mínimo 8 caracteres)
+
+
+2.2. Panel Principal (Dashboard)
+---------------------------------
+
+Vista General
+-------------
+Al iniciar sesión verás el dashboard con:
+
+ESTADÍSTICAS GENERALES
+- Total de usuarios
+- Mensajes recibidos (24h)
+- Mensajes enviados (24h)
+- Conversaciones activas
+
+GRÁFICO DE MENSAJES
+- Visualización por hora/día
+- Comparativa entrada vs salida
+- Tendencias de uso
+
+MENSAJES RECIENTES
+- Últimas 10 conversaciones
+- Vista previa del mensaje
+- Timestamp relativo
+
+
+2.3. Gestión de Conversaciones
+--------------------------------
+
+Interfaz de Chat
+----------------
+La interfaz principal replica WhatsApp Web con:
+
+PANEL IZQUIERDO (Lista de Conversaciones)
+- Buscar: Campo de búsqueda por nombre o número
+- Filtros:
+ * Todos los mensajes
+ * Solo no leídos
+- Lista ordenada por mensaje más reciente
+- Indicadores:
+ * Punto azul = conversación no leída
+ * Timestamp del último mensaje
+ * Vista previa del contenido
+
+PANEL PRINCIPAL (Área de Chat)
+- Cabecera:
+ * Avatar y nombre del usuario
+ * Número de teléfono
+ * Botones de acción (editar, eliminar)
+- Área de mensajes:
+ * Mensajes propios (derecha, fondo verde claro)
+ * Mensajes recibidos (izquierda, fondo blanco)
+ * Timestamps en cada mensaje
+ * Estados: checkmark enviado, doble checkmark entregado/leído
+- Barra de entrada:
+ * Campo de texto para escribir
+ * Botón de adjuntar archivos
+ * Botón de respuestas rápidas
+ * Botón de enviar
+
+Enviar Mensajes de Texto
+-------------------------
+1. Selecciona una conversación de la lista
+2. Escribe tu mensaje en el campo inferior
+3. Presiona Enter o haz clic en Enviar
+4. El mensaje aparecerá inmediatamente en el chat
+
+Características de texto:
+- Soporte para emojis
+- Formato WhatsApp:
+ * *texto* = negrita
+ * _texto_ = cursiva
+ * ~texto~ = tachado
+ * `código` = monoespaciado
+- Saltos de línea con Shift + Enter
+- Envío con Enter
+
+
+Enviar Archivos Multimedia
+---------------------------
+
+IMÁGENES
+1. Haz clic en el botón Adjuntar
+2. Selecciona "Imagen"
+3. Elige una imagen desde tu dispositivo
+4. Opcionalmente agrega un caption (texto)
+5. Haz clic en "Enviar"
+
+Formatos soportados: JPG, PNG, GIF, WebP
+Tamaño máximo: 5 MB
+
+VIDEOS
+1. Clic en Adjuntar > "Video"
+2. Selecciona un video (máx 16MB)
+3. Agrega caption opcional
+4. Envía
+
+Formatos soportados: MP4, AVI, MOV, 3GP
+Tamaño máximo: 16 MB
+
+AUDIO
+Opción A - Grabar nota de voz:
+- Mantén presionado el botón del micrófono
+- Habla al micrófono
+- Suelta para enviar automáticamente
+- Desliza para cancelar
+
+Opción B - Subir archivo de audio:
+- Clic en Adjuntar > "Audio"
+- Selecciona archivo MP3, WAV, AAC, OGG
+- Envía directamente
+
+Tamaño máximo audio: 16 MB
+
+DOCUMENTOS
+1. Clic en Adjuntar > "Documento"
+2. Selecciona archivo (PDF, DOC, XLS, etc.)
+3. El sistema detecta el tipo automáticamente
+4. Envía
+
+Formatos soportados: PDF, DOC, DOCX, XLS, XLSX, PPT, TXT, ZIP
+Tamaño máximo: 100 MB
+
+
+Respuestas Rápidas (Quick Replies)
+-----------------------------------
+Las respuestas rápidas te permiten enviar mensajes predefinidos con un
+solo clic:
+
+1. Haz clic en el botón Respuestas Rápidas
+2. Se desplegará un panel con dos pestañas:
+ - Texto: Respuestas automáticas simples
+ - Plantillas: Templates aprobados de WhatsApp
+3. Selecciona la respuesta deseada
+4. Se enviará automáticamente
+
+Editar respuestas rápidas:
+1. Ve a Configuración > Respuestas Automáticas
+2. Haz clic en "Nueva Respuesta"
+3. Ingresa:
+ - Palabra clave (trigger)
+ - Mensaje de respuesta
+ - Activa/Desactiva
+4. Guarda los cambios
+
+
+Plantillas de WhatsApp
+-----------------------
+Las plantillas son mensajes pre-aprobados por Meta que puedes usar:
+
+ENVIAR UNA PLANTILLA
+1. En el chat, despliega Respuestas Rápidas
+2. Ve a la pestaña "Plantillas"
+3. Selecciona una plantilla aprobada
+4. Si la plantilla tiene variables, se rellenarán automáticamente
+5. La plantilla se envía instantáneamente
+
+GESTIONAR PLANTILLAS
+1. Ve a Configuración > Plantillas de Mensaje
+2. Verás una lista con:
+ - Nombre de la plantilla
+ - Estado: Aprobada | Pendiente | Rechazada
+ - Idioma
+ - Fecha de creación
+3. Crear nueva plantilla:
+ - Clic en "Nueva Plantilla"
+ - Completa el formulario:
+ * Nombre: identificador único (sin espacios)
+ * Categoría: MARKETING, UTILITY, AUTHENTICATION
+ * Idioma: es (Español)
+ * Encabezado: Título opcional
+ * Cuerpo: Mensaje principal (puede incluir variables {{1}}, {{2}})
+ * Pie: Texto al final opcional
+ * Botones: Opcionales (CALL_TO_ACTION, QUICK_REPLY)
+ - Enviar para aprobación
+ - Meta revisará la plantilla (puede tardar 24-48h)
+
+IMPORTANTE: Solo puedes enviar plantillas APROBADAS por WhatsApp.
+
+
+Notificaciones en Tiempo Real
+-------------------------------
+El sistema usa SSE (Server-Sent Events) para actualizaciones en tiempo real:
+
+- Nuevos mensajes: Aparecen automáticamente sin recargar
+- Notificaciones de escritura: "Usuario está escribiendo..."
+- Estados de mensaje: Cambios en tiempo real (entregado -> leído)
+- Toasts informativos: Alertas visuales en la esquina superior derecha
+
+Estados de conexión:
+- Verde: Conectado
+- Amarillo: Reconectando
+- Rojo: Desconectado
+
+Si pierdes conexión, el sistema intentará reconectar automáticamente
+cada 5 segundos.
+
+
+2.4. Gestión de Usuarios
+--------------------------
+
+Listar Usuarios
+---------------
+1. Ve a "Usuarios" en el menú principal
+2. Verás una tabla con:
+ - ID del usuario
+ - Nombre
+ - Número de teléfono
+ - Estado (in_service, on_hold, completed)
+ - Última interacción
+ - Acciones (editar, eliminar, ver chat)
+
+Crear/Editar Usuario
+---------------------
+1. Haz clic en "Nuevo Usuario"
+2. Completa el formulario:
+ - Nombre: Nombre completo o identificador
+ - Teléfono: Número con código de país (ej: 573001234567)
+ - Email: Opcional
+ - Notas: Información adicional
+3. Guarda
+
+Editar: Haz clic en el icono de edición en la fila del usuario.
+
+Eliminar Usuario
+----------------
+1. Haz clic en el icono de eliminar
+2. Confirma la acción
+3. ADVERTENCIA: Esto eliminará también todo el historial de conversaciones
+
+Exportar Usuarios
+-----------------
+1. Haz clic en "Exportar CSV"
+2. Se descargará un archivo con todos los usuarios y sus datos
+
+
+2.5. Mensajes Masivos (Broadcast)
+-----------------------------------
+
+Enviar Mensaje Masivo
+----------------------
+1. Ve a "Envío Masivo" en el menú
+2. Configura el mensaje:
+ - Tipo: Texto o Plantilla
+ - Contenido: Escribe el mensaje o selecciona plantilla
+ - Filtro de destinatarios:
+ * Todos los usuarios
+ * Solo usuarios activos (últimas 24h)
+ * Lista personalizada (importar CSV)
+3. Vista previa: Revisa cómo se verá el mensaje
+4. Haz clic en "Enviar a todos"
+5. El sistema procesará los envíos en segundo plano
+
+Limitaciones de WhatsApp:
+- Solo puedes enviar plantillas aprobadas a usuarios que no te han
+ escrito en 24h
+- Respeta la ventana de 24 horas para mensajes libres
+- El sistema controlará automáticamente estos límites
+
+Seguimiento de Envíos
+----------------------
+En la sección "Historial de Envíos" puedes ver:
+- Fecha y hora del envío
+- Total de destinatarios
+- Enviados exitosos
+- Fallidos (con motivo)
+- Estado general
+
+
+2.6. Bot Automatizado
+----------------------
+
+Configurar el Bot
+-----------------
+1. Ve a Configuración > Bot Automático
+2. Activa/Desactiva el bot con el interruptor
+3. Configura:
+ - Mensaje de bienvenida: Primer mensaje al nuevo usuario
+ - Mensaje de ausencia: Respuesta fuera de horario
+ - Horario de atención: Define días y horas
+ - Palabras clave: Triggers que activan respuestas
+
+Respuestas Automáticas
+-----------------------
+
+CREAR RESPUESTA AUTOMÁTICA
+1. Ve a Configuración > Respuestas Automáticas
+2. Clic en "Nueva Respuesta"
+3. Configura:
+ - Trigger: Palabra o frase que activa la respuesta
+ - Mensaje: Respuesta automática
+ - Activo: On/Off
+ - Prioridad: Orden de evaluación (1 = más alta)
+4. Guarda
+
+Ejemplos de uso:
+- Trigger: hola, buenos días, buenas tardes
+ Respuesta: ¡Hola! Bienvenido a [Empresa]. ¿En qué puedo ayudarte?
+
+- Trigger: horario, horarios, cuando abren
+ Respuesta: Nuestro horario de atención es: Lunes a Viernes 9am-6pm
+
+- Trigger: precio, cuanto cuesta, costo
+ Respuesta: Para información sobre precios, por favor visita [URL]
+
+TIPS PARA RESPUESTAS AUTOMÁTICAS EFECTIVAS
+- Usa múltiples triggers: hola,buenos días,buenas tardes,hey
+- Sé claro y conciso: Respuestas cortas son más efectivas
+- Incluye opciones: "Responde 1 para ventas, 2 para soporte"
+- Usa emojis: Hacen la conversación más amigable
+- Escalamiento: Indica cuándo un humano tomará la conversación
+
+
+Menús Interactivos
+------------------
+Los menús interactivos permiten crear flujos de conversación estructurados:
+
+CREAR MENÚ
+1. Ve a Configuración > Menús Interactivos
+2. Clic en "Nuevo Menú"
+3. Configura:
+ - Nombre del menú: Identificador interno
+ - Mensaje principal: Texto que verá el usuario
+ - Opciones (hasta 10):
+ * ID: Número o letra
+ * Texto: Descripción de la opción
+ * Acción: Respuesta o submenu
+4. Guarda
+
+Ejemplo de menú principal:
+---------------------------
+Bienvenido a [Empresa]
+
+Por favor selecciona una opción:
+
+1. Ventas
+2. Soporte técnico
+3. Información general
+4. Hablar con un asesor
+
+Responde con el número de tu opción.
+
+
+2.7. Configuración del Sistema
+--------------------------------
+
+Configuración de WhatsApp
+--------------------------
+1. Ve a Configuración > WhatsApp API
+2. Completa los campos:
+
+ Datos de Meta Business:
+ - App ID: ID de tu aplicación de Facebook
+ - App Secret: Secreto de la aplicación
+ - Business Account ID: ID de tu cuenta de negocio
+ - Phone Number ID: ID del número de teléfono de WhatsApp
+ - Access Token: Token de acceso permanente
+ - Webhook Verify Token: Token para verificar el webhook
+
+ URLs:
+ - API URL: https://graph.facebook.com/v22.0/
+ - Webhook URL: https://tudominio.com/api/webhook.php
+
+3. Haz clic en "Verificar Configuración"
+4. Si todo está correcto, verás checkmarks en todos los checks
+5. Guarda los cambios
+
+Configuración General
+---------------------
+1. Ve a Configuración > General
+2. Ajusta:
+ - Nombre de la empresa: Aparece en mensajes automáticos
+ - Zona horaria: América/Bogota (por defecto)
+ - Idioma: Español (es)
+ - Límite de mensajes por minuto: Control de rate limiting
+ - Retención de logs: Días que se guardan los registros
+ - Activar notificaciones sonoras: Sonido al recibir mensaje
+3. Guarda
+
+Gestión de Administradores
+---------------------------
+1. Ve a Configuración > Administradores
+2. Lista de usuarios admin actuales
+3. Crear nuevo admin:
+ - Clic en "Nuevo Administrador"
+ - Ingresa:
+ * Nombre de usuario
+ * Email
+ * Contraseña (mínimo 8 caracteres)
+ * Permisos:
+ - Admin completo: Acceso total
+ - Operador: Solo gestión de conversaciones
+ - Visualizador: Solo lectura
+ - Guarda
+4. Eliminar admin: Clic en icono eliminar junto al usuario
+
+
+2.8. Logs y Auditoría
+----------------------
+
+Ver Logs del Sistema
+--------------------
+1. Ve a Logs en el menú principal
+2. Filtros disponibles:
+ - Nivel: Error, Warning, Info, Debug
+ - Fecha: Rango de fechas
+ - Módulo: API, Bot, Webhook, Sistema
+3. Tabla de logs muestra:
+ - Timestamp
+ - Nivel de severidad
+ - Mensaje
+ - Contexto (datos adicionales)
+
+Logs de Webhook
+----------------
+Los logs de webhook muestran cada interacción con WhatsApp:
+
+1. Ve a Logs > Webhook
+2. Cada entrada muestra:
+ - Timestamp de recepción
+ - Tipo de evento (message, status, etc.)
+ - Datos del mensaje (JSON)
+ - Respuesta del sistema
+ - Tiempo de procesamiento
+
+Útil para:
+- Depurar problemas de recepción de mensajes
+- Ver mensajes duplicados
+- Auditar interacciones
+
+Exportar Logs
+-------------
+1. Selecciona los filtros deseados
+2. Clic en "Exportar"
+3. Descarga archivo CSV con los logs
+
+Limpiar Logs
+------------
+Para liberar espacio:
+1. Ve a Logs > Limpieza
+2. Selecciona:
+ - Logs más antiguos de X días
+ - Tipo específico
+3. Confirma la eliminación
+
+
+================================================================================
+3. DOCUMENTACIÓN TÉCNICA
+================================================================================
+
+3.1. Arquitectura del Sistema
+-------------------------------
+
+DIAGRAMA DE COMPONENTES
+------------------------
+
+ FRONTEND
+ +----------+ +-------------+ +----------------+
+ |conversations| chat_window | inicio.html |
+ | .php | | .php | (landing) |
+ +----------+ +-------------+ +----------------+
+ |
+ v
+ API REST (42 Endpoints)
+ +-------------+ +-------------+ +----------------+
+ |send_message | |get_messages | | upload_media |
+ |send_template| | get_users | | webhook |
+ |send_broadcast| | get_stats | | sse_events |
+ +-------------+ +-------------+ +----------------+
+ |
+ v
+ CAPA DE SERVICIOS
+ +-----------------+ +-------------------+
+ | WhatsAppService | | BotService |
+ | (comunicación | | (automatización) |
+ | con WhatsApp) | | |
+ +-----------------+ +-------------------+
+ |
+ v
+ CAPA DE DATOS
+ +------------+ +------------+ +---------------+
+ | Database | | Redis | | Filesystem |
+ | (MariaDB) | | (cache) | | (uploads/) |
+ +------------+ +------------+ +---------------+
+ |
+ v
+ INTEGRACIONES EXTERNAS
+ +------------------------------------------------------+
+ | WhatsApp Business API (Meta Graph API) |
+ | https://graph.facebook.com/v22.0/ |
+ +------------------------------------------------------+
+
+
+FLUJO DE MENSAJES
+-----------------
+
+Mensaje Entrante (Incoming):
+1. Usuario envía mensaje por WhatsApp
+2. WhatsApp envía webhook POST a /api/webhook.php
+3. Webhook valida firma de Meta
+4. Procesa evento (message, status, etc.)
+5. Guarda mensaje en BD (tabla: conversations)
+6. BotService evalúa si debe responder automáticamente
+7. Si hay respuesta automática -> Envía por WhatsAppService
+8. SSE notifica a frontend conectado
+9. Frontend actualiza UI en tiempo real
+
+Mensaje Saliente (Outgoing):
+1. Usuario escribe mensaje en interfaz web
+2. Frontend llama a /api/send_message.php (POST)
+3. API valida sesión y datos
+4. WhatsAppService envía mensaje a Graph API
+5. Graph API retorna ID del mensaje
+6. Se guarda mensaje en BD con estado 'sent'
+7. Respuesta JSON al frontend
+8. Frontend muestra mensaje instantáneamente
+9. Webhook recibe confirmación de entrega/lectura
+10. Actualiza estado en BD y notifica por SSE
+
+
+3.2. Estructura de Archivos
+-----------------------------
+
+whatsapp/
+|-- api/ (API REST Endpoints)
+| |-- webhook.php (Recibe eventos de WhatsApp)
+| |-- send_message.php (Enviar mensaje individual)
+| |-- send_media_message.php (Enviar multimedia)
+| |-- send_template.php (Enviar plantilla)
+| |-- send_broadcast.php (Envío masivo)
+| |-- get_conversations.php (Listar conversaciones)
+| |-- get_user_messages.php (Mensajes de un usuario)
+| |-- get_users.php (Listar usuarios)
+| |-- update_user.php (Actualizar usuario)
+| |-- delete_user.php (Eliminar usuario)
+| |-- upload_media.php (Subir archivos)
+| |-- get_media.php (Obtener URL temporal de media)
+| |-- get_templates.php (Listar plantillas)
+| |-- save_template.php (Guardar plantilla)
+| |-- get_autoresponses.php (Respuestas automáticas)
+| |-- save_autoresponse.php (Guardar respuesta automática)
+| |-- get_stats.php (Estadísticas dashboard)
+| |-- sse_events.php (Server-Sent Events)
+| +-- ... (42 endpoints totales)
+|
+|-- assets/ (Recursos estáticos)
+| |-- css/
+| | +-- main.css
+| |-- js/
+| | |-- chat-common.js (Funciones compartidas de chat)
+| | |-- dashboard.js (Dashboard stats)
+| | +-- admin.js
+| +-- images/
+|
+|-- classes/ (Clases PHP)
+| +-- Database.php (Clase singleton de DB)
+|
+|-- config/ (Configuración)
+| |-- config.php (Configuración principal)
+| +-- .env.example (Plantilla variables de entorno)
+|
+|-- database/ (Scripts SQL)
+| |-- migrations/
+| | |-- 001_initial_schema.sql
+| | |-- 002_add_media_fields.sql
+| | +-- ...
+| +-- backups/ (Backups automáticos)
+|
+|-- docker/ (Configuración Docker)
+| |-- nginx/
+| | +-- default.conf
+| |-- php/
+| | +-- Dockerfile
+| +-- supervisor/
+| +-- supervisord.conf
+|
+|-- services/ (Servicios)
+| |-- WhatsAppService.php (Cliente WhatsApp Business API)
+| |-- BotService.php (Lógica del bot automático)
+| +-- MediaService.php (Gestión multimedia)
+|
+|-- uploads/ (Archivos subidos)
+| |-- images/
+| |-- videos/
+| |-- audio/
+| +-- documents/
+|
+|-- logs/ (Logs del sistema)
+| |-- app.log
+| |-- webhook.log
+| +-- error.log
+|
+|-- tests/ (Tests)
+| |-- ApiTest.php
+| |-- WhatsAppServiceTest.php
+| +-- ...
+|
+|-- conversations.php (Interfaz principal de chat)
+|-- chat_window.php (Ventana de chat)
+|-- inicio.html (Landing page)
+|-- index.php (Dashboard / Redirect)
+|-- login.php (Login)
+|-- logout.php (Logout)
+|-- worker.php (Worker de colas)
+|-- composer.json (Dependencias PHP)
+|-- docker-compose.yml (Orquestación containers)
+|-- .env (Variables de entorno)
++-- README.md (Documentación básica)
+
+
+3.3. Base de Datos
+-------------------
+
+TABLA: users
+Almacena información de usuarios/contactos de WhatsApp.
+
+Campo Tipo Descripción
+------------------------------------------------------------------------
+id INT ID único autoincremental
+phone_number VARCHAR(50) Número con código de país
+name VARCHAR(255) Nombre del contacto
+email VARCHAR(255) Email (opcional)
+profile_pic_url TEXT URL foto de perfil
+in_service TINYINT(1) 1 si está en conversación activa
+advisor_requested TINYINT(1) 1 si solicitó asesor humano
+on_hold TINYINT(1) 1 si está en espera
+attended_by INT ID del admin que atiende
+last_message_at TIMESTAMP Timestamp último mensaje
+created_at TIMESTAMP Fecha de registro
+updated_at TIMESTAMP Última actualización
+
+Índices:
+- idx_phone (phone_number)
+- idx_last_message (last_message_at)
+- idx_in_service (in_service)
+
+
+TABLA: conversations
+Almacena todos los mensajes (entrantes y salientes).
+
+Campo Tipo Descripción
+------------------------------------------------------------------------
+id INT ID único autoincremental
+user_id INT ID del usuario
+message_id VARCHAR(255) ID único de WhatsApp
+content TEXT Contenido del mensaje
+direction ENUM 'incoming' o 'outgoing'
+message_type VARCHAR(50) text, image, video, audio, etc.
+status VARCHAR(50) sent, delivered, read, failed
+media_url TEXT URL original de WhatsApp
+whatsapp_media_id VARCHAR(255) ID del media en WhatsApp
+local_file VARCHAR(500) Ruta local del archivo
+local_thumb VARCHAR(500) Ruta de thumbnail
+filename VARCHAR(255) Nombre del archivo
+mime_type VARCHAR(100) Tipo MIME
+media_url_external TEXT URL proxy persistente
+reply_to_message_id VARCHAR(255) ID mensaje al que responde
+reaction_emoji VARCHAR(10) Emoji de reacción
+reaction_to_message_id VARCHAR(255) ID mensaje al que reacciona
+created_at TIMESTAMP Fecha de creación
+
+Índices:
+- idx_user_id (user_id)
+- idx_message_id (message_id)
+- idx_direction (direction)
+- idx_created_at (created_at)
+- idx_user_created (user_id, created_at)
+
+
+TABLA: message_templates
+Plantillas de WhatsApp aprobadas por Meta.
+
+Campo Tipo Descripción
+------------------------------------------------------------------------
+id INT ID único autoincremental
+template_name VARCHAR(255) Nombre de la plantilla
+language_code VARCHAR(10) Código de idioma (es, en)
+category VARCHAR(50) MARKETING, UTILITY, AUTHENTICATION
+header_text TEXT Texto del encabezado
+body_text TEXT Texto del cuerpo
+footer_text TEXT Texto del pie
+buttons TEXT Botones (JSON)
+status VARCHAR(50) pending, approved, rejected
+created_at TIMESTAMP Fecha de creación
+updated_at TIMESTAMP Última actualización
+
+Índices:
+- idx_status (status)
+
+
+TABLA: autoresponses
+Respuestas automáticas del bot.
+
+Campo Tipo Descripción
+------------------------------------------------------------------------
+id INT ID único autoincremental
+trigger_keywords TEXT Palabras clave (separadas por comas)
+response_message TEXT Mensaje de respuesta
+is_active TINYINT(1) 1 si está activa
+priority INT Orden de evaluación
+created_at TIMESTAMP Fecha de creación
+updated_at TIMESTAMP Última actualización
+
+Índices:
+- idx_active (is_active)
+
+
+TABLA: system_config
+Configuración dinámica del sistema.
+
+Campo Tipo Descripción
+------------------------------------------------------------------------
+id INT ID único autoincremental
+config_key VARCHAR(255) Clave de configuración
+config_value TEXT Valor
+config_type VARCHAR(50) string, int, boolean, json
+description TEXT Descripción
+created_at TIMESTAMP Fecha de creación
+updated_at TIMESTAMP Última actualización
+
+Índices:
+- idx_key (config_key)
+
+
+TABLA: admin_users
+Usuarios administradores del panel.
+
+Campo Tipo Descripción
+------------------------------------------------------------------------
+id INT ID único autoincremental
+username VARCHAR(100) Nombre de usuario
+password VARCHAR(255) Contraseña hash
+email VARCHAR(255) Email
+role VARCHAR(50) super_admin, admin, operator, viewer
+is_active TINYINT(1) 1 si está activo
+last_login TIMESTAMP Último inicio de sesión
+created_at TIMESTAMP Fecha de creación
+updated_at TIMESTAMP Última actualización
+
+Índices:
+- idx_username (username)
+
+
+TABLA: webhook_logs
+Auditoría de webhooks recibidos.
+
+Campo Tipo Descripción
+------------------------------------------------------------------------
+id INT ID único autoincremental
+event_type VARCHAR(100) Tipo de evento
+payload TEXT Datos del evento (JSON)
+processed TINYINT(1) 1 si fue procesado
+processing_time_ms INT Tiempo de procesamiento
+error_message TEXT Mensaje de error (si aplica)
+created_at TIMESTAMP Fecha de recepción
+
+Índices:
+- idx_event_type (event_type)
+- idx_processed (processed)
+- idx_created_at (created_at)
+
+
+3.4. Servicios Principales
+----------------------------
+
+WhatsAppService.php
+Clase que encapsula toda la comunicación con WhatsApp Business API.
+
+MÉTODOS PRINCIPALES:
+
+1. sendTextMessage($to, $message)
+ Envía un mensaje de texto simple.
+
+ Parámetros:
+ - $to: Número de destino con código de país
+ - $message: Contenido del mensaje (máx 4096 caracteres)
+
+ Retorna:
+ [
+ 'success' => true|false,
+ 'message_id' => 'wamid.XXX...',
+ 'error' => null|string
+ ]
+
+2. sendTemplateMessage($to, $templateName, $language, $bodyParameters)
+ Envía una plantilla de mensaje aprobada.
+
+ Parámetros:
+ - $to: Número destino
+ - $templateName: Nombre de la plantilla
+ - $language: Código de idioma (es)
+ - $bodyParameters: Array de variables
+
+3. sendImageMessage($to, $imageUrl, $caption, $isLocalFile)
+ Envía una imagen.
+
+ Formatos soportados: JPG, JPEG, PNG
+ Tamaño máximo: 5 MB
+
+4. sendVideoMessage($to, $videoUrl, $caption, $isLocalFile)
+ Envía un video.
+
+ Formatos soportados: MP4, 3GPP
+ Tamaño máximo: 16 MB
+
+5. sendAudioMessage($to, $audioUrl, $isLocalFile)
+ Envía audio.
+
+ Formatos soportados: AAC, MP3, AMR, OGG
+ Tamaño máximo: 16 MB
+
+6. sendDocumentMessage($to, $documentUrl, $filename, $caption, $isLocalFile)
+ Envía un documento.
+
+ Formatos soportados: PDF, DOC, DOCX, XLS, XLSX, PPT, TXT, ZIP
+ Tamaño máximo: 100 MB
+
+7. uploadMedia($filePath, $mimeType)
+ Sube un archivo a los servidores de WhatsApp.
+
+ Retorna: media_id para usar en envíos
+
+8. downloadMedia($mediaId)
+ Descarga un archivo multimedia de WhatsApp.
+
+ Retorna: ruta local del archivo descargado
+
+9. sendInteractiveMessage($to, $bodyText, $buttons, $header, $footer)
+ Envía un mensaje con botones interactivos (hasta 3 botones).
+
+10. sendListMessage($to, $bodyText, $buttonText, $sections)
+ Envía un mensaje con lista interactiva (hasta 10 opciones).
+
+
+BotService.php
+Servicio de automatización de respuestas.
+
+MÉTODOS PRINCIPALES:
+
+1. processIncomingMessage($userId, $message)
+ Procesa un mensaje entrante y decide si el bot debe responder.
+
+ Lógica:
+ - Verifica si el bot está activado
+ - Comprueba si el usuario está siendo atendido por humano
+ - Busca respuesta automática que coincida
+ - Ejecuta lógica de menús si aplica
+ - Envía respuesta si corresponde
+
+2. findAutoResponse($message)
+ Busca una respuesta automática que coincida con el mensaje.
+
+ Retorna: Objeto con response_message o null
+
+3. sendWelcomeMessage($userId)
+ Envía el mensaje de bienvenida configurado.
+
+4. isBusinessHours()
+ Verifica si está dentro del horario de atención.
+
+ Retorna: true si está en horario, false fuera de horario
+
+
+MediaService.php
+Servicio para gestión de archivos multimedia.
+
+MÉTODOS PRINCIPALES:
+
+1. downloadAndSaveMedia($mediaId, $userId)
+ Descarga un media de WhatsApp y lo guarda localmente.
+
+ Proceso:
+ - Obtiene URL temporal del media
+ - Descarga el contenido
+ - Detecta tipo MIME
+ - Genera nombre único
+ - Guarda en carpeta correspondiente
+ - Genera thumbnail para imágenes/videos
+ - Actualiza registro en BD
+
+2. generateThumbnail($imagePath, $maxWidth, $maxHeight)
+ Genera un thumbnail para imágenes.
+
+3. getMediaProxy($messageId)
+ Obtiene URL proxy persistente para un media.
+
+
+3.5. Server-Sent Events (SSE)
+-------------------------------
+
+El sistema usa SSE para notificaciones en tiempo real sin WebSockets.
+
+FLUJO SSE:
+1. Frontend se conecta a /api/sse_events.php
+2. El backend mantiene la conexión HTTP abierta
+3. Cuando hay un nuevo evento:
+ a. Se guarda en Redis (canal: sse_notifications)
+ b. worker.php o webhook.php publica evento en canal
+4. sse_events.php lee de Redis y envía al cliente
+5. Frontend recibe evento y actualiza UI
+6. Conexión se mantiene viva con heartbeat cada 15s
+
+TIPOS DE EVENTOS SSE:
+
+1. new_message
+ Nuevo mensaje recibido.
+
+ Datos:
+ {
+ "type": "new_message",
+ "user_id": 123,
+ "message_id": "wamid.XXX",
+ "content": "Hola",
+ "direction": "incoming",
+ "created_at": "2024-01-27 14:30:45"
+ }
+
+2. message_status
+ Cambio de estado de mensaje.
+
+ Datos:
+ {
+ "type": "message_status",
+ "message_id": "wamid.XXX",
+ "status": "read",
+ "timestamp": "2024-01-27 14:31:00"
+ }
+
+3. conversation_update
+ Actualización de conversación en lista.
+
+ Datos:
+ {
+ "type": "conversation_update",
+ "user_id": 123,
+ "last_message": "Hola",
+ "unread_count": 1
+ }
+
+4. notification
+ Notificación general.
+
+ Datos:
+ {
+ "type": "notification",
+ "title": "Nuevo mensaje",
+ "message": "Juan te ha enviado un mensaje",
+ "severity": "info"
+ }
+
+
+================================================================================
+4. API REST COMPLETA
+================================================================================
+
+AUTENTICACIÓN
+-------------
+Todos los endpoints (excepto webhook) requieren autenticación mediante
+sesión PHP.
+
+Headers requeridos:
+Cookie: PHPSESSID=abc123...
+
+Respuesta de error de autenticación:
+{
+ "error": "Acceso denegado. Inicia sesión.",
+ "code": 401
+}
+
+
+ENDPOINTS DE MENSAJERÍA
+------------------------
+
+POST /api/send_message.php
+Envía un mensaje de texto individual.
+
+Request Body:
+{
+ "recipient": "573001234567",
+ "message": "Hola, este es un mensaje de prueba",
+ "reply_to": "wamid.XXX" // Opcional
+}
+
+Response Success:
+{
+ "success": true,
+ "message_id": "wamid.HBgNNTczMDA...",
+ "timestamp": "2024-01-27 14:35:20"
+}
+
+Response Error:
+{
+ "success": false,
+ "error": "Recipient not on WhatsApp"
+}
+
+
+POST /api/send_template.php
+Envía una plantilla de mensaje.
+
+Request Body:
+{
+ "recipient": "573001234567",
+ "template_name": "welcome_message",
+ "language": "es",
+ "parameters": {
+ "body": ["Juan Pérez", "2024-01-27"],
+ "header": []
+ }
+}
+
+Response:
+{
+ "success": true,
+ "message_id": "wamid.XXX..."
+}
+
+
+POST /api/send_media_message.php
+Envía un mensaje multimedia.
+
+Request Body:
+{
+ "recipient": "573001234567",
+ "media_type": "image",
+ "media_url": "https://ejemplo.com/imagen.jpg",
+ "caption": "Mira esta imagen",
+ "filename": "imagen.jpg" // Requerido para documentos
+}
+
+Tipos de media: image, video, audio, document
+
+Response:
+{
+ "success": true,
+ "message_id": "wamid.XXX...",
+ "media_id": "123456789"
+}
+
+
+POST /api/send_broadcast.php
+Envía mensaje masivo.
+
+Request Body:
+{
+ "message": "Hola a todos, este es un anuncio importante",
+ "filter": "active",
+ "template_name": null
+}
+
+Filtros disponibles:
+- all: Todos los usuarios
+- active: Usuarios activos (< 24h)
+- inactive: Usuarios inactivos
+- custom: Lista personalizada (requiere user_ids array)
+
+Response:
+{
+ "success": true,
+ "total_sent": 150,
+ "failed": 2,
+ "details": [
+ {"phone": "573001234567", "status": "sent"},
+ {"phone": "573009876543", "status": "failed", "error": "Not on WhatsApp"}
+ ]
+}
+
+
+ENDPOINTS DE CONVERSACIONES
+----------------------------
+
+GET /api/get_conversations.php
+Lista todas las conversaciones.
+
+Query Params:
+- page (int): Número de página (default: 1)
+- limit (int): Conversaciones por página (default: 50, max: 100)
+- filter (string): all | unread | active
+
+Ejemplo:
+GET /api/get_conversations.php?page=1&limit=20&filter=unread
+
+Response:
+{
+ "success": true,
+ "data": [
+ {
+ "user_id": 123,
+ "name": "Juan Pérez",
+ "phone_number": "573001234567",
+ "last_message": "Hola, necesito ayuda",
+ "last_message_time": "2024-01-27 14:30:45",
+ "last_message_direction": "incoming",
+ "unread_count": 2,
+ "in_service": 1,
+ "attended_by": null
+ }
+ ],
+ "pagination": {
+ "current_page": 1,
+ "total_pages": 5,
+ "total_items": 98,
+ "per_page": 20
+ }
+}
+
+
+GET /api/get_user_messages.php
+Obtiene mensajes de un usuario específico.
+
+Query Params:
+- user_id (int): ID del usuario (requerido)
+- limit (int): Número de mensajes (default: 50)
+- before (timestamp): Cargar mensajes anteriores (scroll infinito)
+
+Ejemplo:
+GET /api/get_user_messages.php?user_id=123&limit=50
+
+Response:
+{
+ "success": true,
+ "data": [
+ {
+ "id": 456,
+ "message_id": "wamid.XXX",
+ "user_id": 123,
+ "content": "Hola, necesito ayuda",
+ "direction": "incoming",
+ "message_type": "text",
+ "status": "read",
+ "media_url": null,
+ "local_file": null,
+ "reply_to_message_id": null,
+ "reaction_emoji": null,
+ "created_at": "2024-01-27 14:30:45"
+ }
+ ],
+ "has_more": true
+}
+
+
+POST /api/mark_conversation_read.php
+Marca una conversación como leída.
+
+Request Body:
+{
+ "user_id": 123
+}
+
+Response:
+{
+ "success": true,
+ "message": "Conversación marcada como leída"
+}
+
+
+ENDPOINTS DE USUARIOS
+----------------------
+
+GET /api/get_users.php
+Lista todos los usuarios.
+
+Query Params:
+- page (int): Número de página
+- limit (int): Usuarios por página
+- search (string): Buscar por nombre o teléfono
+
+Response:
+{
+ "success": true,
+ "data": [
+ {
+ "id": 123,
+ "phone_number": "573001234567",
+ "name": "Juan Pérez",
+ "email": "juan@example.com",
+ "in_service": 1,
+ "advisor_requested": 0,
+ "last_message_at": "2024-01-27 14:30:45",
+ "created_at": "2024-01-20 10:00:00"
+ }
+ ],
+ "pagination": {...}
+}
+
+
+POST /api/update_user.php
+Actualiza información de un usuario.
+
+Request Body:
+{
+ "user_id": 123,
+ "name": "Juan Pérez García",
+ "email": "juan.perez@example.com"
+}
+
+Response:
+{
+ "success": true,
+ "message": "Usuario actualizado correctamente"
+}
+
+
+DELETE /api/delete_user.php
+Elimina un usuario y todo su historial.
+
+Request Body:
+{
+ "user_id": 123
+}
+
+Response:
+{
+ "success": true,
+ "message": "Usuario eliminado correctamente"
+}
+
+
+GET /api/export_users.php
+Exporta usuarios a CSV.
+
+Response: Archivo CSV descargable
+
+
+ENDPOINTS DE MULTIMEDIA
+------------------------
+
+POST /api/upload_media.php
+Sube un archivo multimedia.
+
+Request: multipart/form-data
+- file (file): Archivo a subir
+
+Response:
+{
+ "success": true,
+ "media_id": "123456789",
+ "media_url": "https://graph.facebook.com/v22.0/123456789",
+ "local_file": "uploads/images/20240127_143520_abc123.jpg",
+ "mime_type": "image/jpeg",
+ "file_size": 245678
+}
+
+
+GET /api/get_media.php
+Obtiene URL temporal de un media de WhatsApp.
+
+Query Params:
+- id (string): Media ID de WhatsApp
+
+Response:
+{
+ "success": true,
+ "url": "https://lookaside.fbsbx.com/whatsapp_business/...",
+ "mime_type": "image/jpeg",
+ "file_size": 245678,
+ "sha256": "abc123...",
+ "expires_at": "2024-01-27 15:30:00"
+}
+
+
+ENDPOINTS DE PLANTILLAS
+------------------------
+
+GET /api/get_templates.php
+Lista plantillas de mensaje.
+
+Query Params:
+- approved_only (0|1): Solo plantillas aprobadas
+- limit (int): Límite de resultados
+
+Response:
+{
+ "success": true,
+ "data": [
+ {
+ "id": 1,
+ "template_name": "welcome_message",
+ "language_code": "es",
+ "category": "MARKETING",
+ "body_text": "Hola {{1}}, bienvenido...",
+ "status": "approved",
+ "created_at": "2024-01-20 10:00:00"
+ }
+ ]
+}
+
+
+POST /api/save_template.php
+Crea o actualiza una plantilla.
+
+Request Body:
+{
+ "template_name": "new_template",
+ "language_code": "es",
+ "category": "MARKETING",
+ "header_text": "Oferta Especial",
+ "body_text": "Hola {{1}}, tenemos una oferta...",
+ "footer_text": "Válido hasta fin de mes",
+ "buttons": [
+ {"type": "QUICK_REPLY", "text": "Me interesa"}
+ ]
+}
+
+Response:
+{
+ "success": true,
+ "template_id": 5,
+ "message": "Plantilla creada. Pendiente de aprobación."
+}
+
+
+ENDPOINTS DE RESPUESTAS AUTOMÁTICAS
+-------------------------------------
+
+GET /api/get_autoresponses.php
+Lista respuestas automáticas.
+
+Response:
+{
+ "success": true,
+ "data": [
+ {
+ "id": 1,
+ "trigger_keywords": "hola,buenos días,hey",
+ "response_message": "¡Hola! Bienvenido...",
+ "is_active": 1,
+ "priority": 1
+ }
+ ]
+}
+
+
+POST /api/save_autoresponse.php
+Crea o actualiza respuesta automática.
+
+Request Body:
+{
+ "id": null,
+ "trigger_keywords": "precio,costo,cuanto cuesta",
+ "response_message": "Nuestros precios varían...",
+ "is_active": 1,
+ "priority": 5
+}
+
+Response:
+{
+ "success": true,
+ "id": 3,
+ "message": "Respuesta automática guardada"
+}
+
+
+ENDPOINTS DE ESTADÍSTICAS
+---------------------------
+
+GET /api/get_stats.php
+Obtiene estadísticas del dashboard.
+
+Response:
+{
+ "success": true,
+ "data": {
+ "total_users": 1250,
+ "messages_received_24h": 340,
+ "messages_sent_24h": 285,
+ "active_conversations": 45,
+ "messages_by_hour": [
+ {"hour": "00:00", "incoming": 5, "outgoing": 2},
+ ...
+ ],
+ "messages_by_day": [
+ {"date": "2024-01-21", "incoming": 120, "outgoing": 95},
+ ...
+ ],
+ "top_users": [
+ {"user_id": 123, "name": "Juan Pérez", "message_count": 45}
+ ]
+ }
+}
+
+
+ENDPOINT DE WEBHOOK
+-------------------
+
+POST /api/webhook.php
+Recibe eventos de WhatsApp Business API.
+
+Verificación (GET):
+GET /api/webhook.php?hub.mode=subscribe&hub.challenge=123456&hub.verify_token=token
+
+Response: 123456 (echo del challenge)
+
+Eventos recibidos (POST):
+
+Nuevo mensaje:
+{
+ "object": "whatsapp_business_account",
+ "entry": [{
+ "changes": [{
+ "value": {
+ "messaging_product": "whatsapp",
+ "messages": [{
+ "from": "573001234567",
+ "id": "wamid.XXX",
+ "timestamp": "1706367000",
+ "type": "text",
+ "text": {
+ "body": "Hola, necesito ayuda"
+ }
+ }]
+ }
+ }]
+ }]
+}
+
+Estado de mensaje:
+{
+ "entry": [{
+ "changes": [{
+ "value": {
+ "statuses": [{
+ "id": "wamid.XXX",
+ "status": "read",
+ "timestamp": "1706367100",
+ "recipient_id": "573001234567"
+ }]
+ }
+ }]
+ }]
+}
+
+Tipos de status:
+- sent: Enviado
+- delivered: Entregado
+- read: Leído
+- failed: Fallido
+
+
+ENDPOINTS SSE
+-------------
+
+GET /api/sse_events.php
+Conexión de Server-Sent Events para notificaciones en tiempo real.
+
+Headers:
+Content-Type: text/event-stream
+Cache-Control: no-cache
+Connection: keep-alive
+
+Eventos enviados:
+event: message
+data: {"type":"new_message","user_id":123,"content":"Hola"}
+
+event: heartbeat
+data: {"timestamp":"2024-01-27 14:35:00"}
+
+
+CÓDIGOS DE ESTADO HTTP
+-----------------------
+
+Código Significado
+------------------------------------------------------------------------
+200 Éxito
+201 Creado
+400 Petición inválida
+401 No autenticado
+403 Sin permisos
+404 No encontrado
+405 Método no permitido
+429 Demasiadas peticiones (rate limit)
+500 Error del servidor
+503 Servicio no disponible
+
+
+================================================================================
+5. GUÍA DE IMPLEMENTACIÓN
+================================================================================
+
+5.1. Instalación desde Cero
+-----------------------------
+
+REQUISITOS DEL SERVIDOR
+------------------------
+
+Mínimos:
+- CPU: 2 cores
+- RAM: 4 GB
+- Disco: 20 GB SSD
+- SO: Ubuntu 22.04 LTS, Debian 11+, CentOS 8+
+
+Recomendados para producción:
+- CPU: 4+ cores
+- RAM: 8+ GB
+- Disco: 50+ GB SSD
+- SO: Ubuntu 22.04 LTS
+
+
+OPCIÓN 1: Docker (Recomendado)
+-------------------------------
+
+1. Instalar Docker y Docker Compose
+
+Ubuntu/Debian:
+sudo apt update
+sudo apt install -y docker.io docker-compose
+
+Verificar instalación:
+docker --version
+docker-compose --version
+
+2. Clonar Repositorio
+
+git clone https://github.com/tu-usuario/whatsapp.git
+cd whatsapp
+
+3. Configurar Variables de Entorno
+
+cp .env.example .env
+nano .env
+
+Editar .env:
+# Base de datos
+DB_HOST=db
+DB_PORT=3306
+DB_NAME=whatsapp_bot
+DB_USER=whatsapp_user
+DB_PASS=tu_password_seguro
+
+# WhatsApp Business API
+WHATSAPP_TOKEN=tu_access_token
+WHATSAPP_PHONE_ID=tu_phone_id
+WHATSAPP_BUSINESS_ACCOUNT_ID=tu_business_account_id
+WHATSAPP_APP_ID=tu_app_id
+WHATSAPP_APP_SECRET=tu_app_secret
+
+# Webhook
+WEBHOOK_VERIFY_TOKEN=tu_token_verificacion_webhook
+
+# Sistema
+APP_ENV=production
+APP_DEBUG=false
+APP_TIMEZONE=America/Bogota
+
+4. Levantar Contenedores
+
+Desarrollo:
+docker-compose -f docker-compose.dev.yml up -d
+
+Producción:
+docker-compose -f docker-compose.prod.yml up -d
+
+5. Verificar Estado
+
+docker-compose ps
+
+Deberías ver:
+NAME STATUS
+whatsapp-app Up
+whatsapp-db Up
+whatsapp-redis Up
+whatsapp-worker Up
+whatsapp-nginx Up
+
+6. Acceder a la Aplicación
+
+App: http://tu-servidor:8080 (dev) o http://tu-dominio (prod)
+phpMyAdmin: http://tu-servidor:8084 (dev)
+
+7. Configurar en el Panel
+
+1. Accede con las credenciales por defecto:
+ Usuario: admin
+ Password: password123
+2. Cambia la contraseña inmediatamente
+3. Ve a Configuración > WhatsApp API y completa los datos
+4. Verifica la configuración
+
+
+OPCIÓN 2: Instalación Manual
+------------------------------
+
+1. Instalar Stack LAMP
+
+Ubuntu 22.04:
+sudo apt update
+sudo apt install -y apache2 mariadb-server php8.2 php8.2-mysql \
+ php8.2-curl php8.2-mbstring php8.2-xml php8.2-gd php8.2-zip \
+ php8.2-redis redis-server
+
+Verificar PHP:
+php -v
+
+2. Configurar MariaDB
+
+sudo mysql_secure_installation
+
+Crear base de datos:
+sudo mysql -u root -p
+
+SQL:
+CREATE DATABASE whatsapp_bot CHARACTER SET utf8mb4
+ COLLATE utf8mb4_unicode_ci;
+CREATE USER 'whatsapp_user'@'localhost'
+ IDENTIFIED BY 'tu_password_seguro';
+GRANT ALL PRIVILEGES ON whatsapp_bot.*
+ TO 'whatsapp_user'@'localhost';
+FLUSH PRIVILEGES;
+EXIT;
+
+3. Clonar y Configurar Aplicación
+
+cd /var/www/html
+sudo git clone https://github.com/tu-usuario/whatsapp.git
+cd whatsapp
+
+Permisos:
+sudo chown -R www-data:www-data /var/www/html/whatsapp
+sudo chmod -R 755 /var/www/html/whatsapp
+sudo chmod -R 777 /var/www/html/whatsapp/uploads
+sudo chmod -R 777 /var/www/html/whatsapp/logs
+
+Configurar .env:
+cp .env.example .env
+sudo nano .env
+
+4. Importar Base de Datos
+
+mysql -u whatsapp_user -p whatsapp_bot < \
+ database/migrations/001_initial_schema.sql
+mysql -u whatsapp_user -p whatsapp_bot < \
+ database/migrations/002_add_media_fields.sql
+
+5. Configurar Apache
+
+sudo nano /etc/apache2/sites-available/whatsapp.conf
+
+Contenido:
+
+ ServerName tudominio.com
+ DocumentRoot /var/www/html/whatsapp
+
+
+ Options -Indexes +FollowSymLinks
+ AllowOverride All
+ Require all granted
+
+
+ ErrorLog ${APACHE_LOG_DIR}/whatsapp_error.log
+ CustomLog ${APACHE_LOG_DIR}/whatsapp_access.log combined
+
+
+Habilitar sitio:
+sudo a2ensite whatsapp.conf
+sudo a2enmod rewrite
+sudo systemctl restart apache2
+
+6. Configurar SSL con Let's Encrypt
+
+sudo apt install -y certbot python3-certbot-apache
+sudo certbot --apache -d tudominio.com
+
+7. Configurar Worker (Supervisor)
+
+sudo apt install -y supervisor
+sudo cp supervisor-whatsapp-worker.conf \
+ /etc/supervisor/conf.d/whatsapp-worker.conf
+sudo supervisorctl reread
+sudo supervisorctl update
+sudo supervisorctl start whatsapp-worker:*
+
+8. Configurar Cron Jobs
+
+sudo crontab -e
+
+Agregar:
+# Limpiar logs antiguos (diario a las 3am)
+0 3 * * * cd /var/www/html/whatsapp && \
+ php scripts/clean_old_logs.php
+
+# Backup de base de datos (diario a las 2am)
+0 2 * * * /usr/bin/mysqldump -u whatsapp_user \
+ -p'tu_password' whatsapp_bot > \
+ /var/www/html/whatsapp/database/backups/backup_$(date +\%Y\%m\%d_\%H\%M\%S).sql
+
+# Limpiar backups antiguos (mantener últimos 7 días)
+0 4 * * * find /var/www/html/whatsapp/database/backups \
+ -name "backup_*.sql" -mtime +7 -delete
+
+
+5.2. Configuración de Producción
+----------------------------------
+
+OPTIMIZACIÓN DE PHP
+-------------------
+
+Editar /etc/php/8.2/apache2/php.ini:
+
+memory_limit = 256M
+post_max_size = 100M
+upload_max_filesize = 100M
+max_execution_time = 300
+max_input_time = 300
+
+; Para SSE (Server-Sent Events)
+output_buffering = Off
+implicit_flush = On
+
+; Seguridad
+expose_php = Off
+display_errors = Off
+log_errors = On
+error_log = /var/log/php/error.log
+
+; Zona horaria
+date.timezone = America/Bogota
+
+
+OPTIMIZACIÓN DE MariaDB
+------------------------
+
+Editar /etc/mysql/mariadb.conf.d/50-server.cnf:
+
+[mysqld]
+# Memoria
+innodb_buffer_pool_size = 2G
+innodb_log_file_size = 256M
+
+# Conexiones
+max_connections = 200
+
+# Charset
+character-set-server = utf8mb4
+collation-server = utf8mb4_unicode_ci
+
+# Logs
+slow_query_log = 1
+slow_query_log_file = /var/log/mysql/slow-query.log
+long_query_time = 2
+
+# Índices
+innodb_flush_log_at_trx_commit = 2
+innodb_flush_method = O_DIRECT
+
+Reiniciar:
+sudo systemctl restart mariadb
+
+
+FIREWALL
+--------
+
+UFW (Ubuntu):
+sudo ufw allow 22/tcp
+sudo ufw allow 80/tcp
+sudo ufw allow 443/tcp
+sudo ufw enable
+sudo ufw status
+
+
+MONITOREO
+---------
+
+Instalar Monit:
+sudo apt install -y monit
+sudo nano /etc/monit/monitrc
+
+Configurar:
+set daemon 60
+set logfile /var/log/monit.log
+
+check process apache2 with pidfile /var/run/apache2/apache2.pid
+ start program = "/usr/sbin/service apache2 start"
+ stop program = "/usr/sbin/service apache2 stop"
+ if failed host localhost port 80 protocol http then restart
+ if 5 restarts within 5 cycles then timeout
+
+check process mysql with pidfile /var/run/mysqld/mysqld.pid
+ start program = "/usr/sbin/service mysql start"
+ stop program = "/usr/sbin/service mysql stop"
+ if failed host localhost port 3306 protocol mysql then restart
+ if 5 restarts within 5 cycles then timeout
+
+check process redis with pidfile /var/run/redis/redis-server.pid
+ start program = "/usr/sbin/service redis-server start"
+ stop program = "/usr/sbin/service redis-server stop"
+ if failed host localhost port 6379 protocol redis then restart
+
+Iniciar:
+sudo systemctl enable monit
+sudo systemctl start monit
+
+
+5.3. Actualización del Sistema
+--------------------------------
+
+PROCESO DE ACTUALIZACIÓN
+-------------------------
+
+1. Backup completo
+./scripts/backup_all.sh
+
+2. Modo mantenimiento
+touch maintenance.lock
+
+3. Actualizar código
+git pull origin main
+
+4. Ejecutar migraciones
+php scripts/run_migrations.php
+
+5. Limpiar cache
+php scripts/clear_cache.php
+
+6. Reiniciar servicios
+sudo systemctl restart apache2
+sudo supervisorctl restart whatsapp-worker:*
+
+7. Verificar funcionamiento
+php scripts/check_health.php
+
+8. Desactivar modo mantenimiento
+rm maintenance.lock
+
+
+================================================================================
+6. CASOS DE USO
+================================================================================
+
+CASO 1: Atención al Cliente Automatizada
+------------------------------------------
+
+Escenario: Una empresa de e-commerce quiere automatizar respuestas a
+preguntas frecuentes.
+
+Implementación:
+1. Configurar respuestas automáticas con triggers específicos
+2. Crear menú interactivo de opciones principales
+3. Configurar horario de atención para transferencia a humano
+4. Resultado: Reducción de 70% en consultas repetitivas
+
+
+CASO 2: Campañas de Marketing
+-------------------------------
+
+Escenario: Lanzamiento de nueva colección, enviar anuncio a clientes activos.
+
+Implementación:
+1. Crear plantilla en Meta con variables dinámicas
+2. Enviar broadcast con filtro de usuarios activos
+3. Seguimiento de métricas (enviados, leídos, interacciones)
+4. Resultado: 300% ROI, 36% tasa de apertura, 9% conversión
+
+
+CASO 3: Soporte Técnico con Tickets
+-------------------------------------
+
+Escenario: Empresa de software que recibe consultas técnicas.
+
+Implementación:
+1. Crear sistema de tickets automático
+2. Escalamiento automático según palabras clave
+3. Vinculación de respuestas a tickets
+4. Sistema de calificación de servicio
+5. Resultado: Tiempo de respuesta 15 minutos, satisfacción 4.5/5
+
+
+CASO 4: Reservas y Citas
+--------------------------
+
+Escenario: Clínica médica permite agendar citas por WhatsApp.
+
+Implementación:
+1. Menú de especialidades
+2. Sistema de calendario con disponibilidad
+3. Confirmación automática de citas
+4. Recordatorios 24h antes
+5. Resultado: 80% de citas agendadas fuera de horario
+
+
+CASO 5: Encuestas y Feedback
+------------------------------
+
+Escenario: Recopilar feedback post-compra.
+
+Implementación:
+1. Envío automático de encuesta 24h después de compra
+2. Procesamiento inteligente de respuestas
+3. Escalamiento de casos negativos
+4. Dashboard de resultados y análisis
+5. Resultado: 350 respuestas en 1 semana, NPS de 72
+
+
+================================================================================
+7. ENTREGABLES DEL PROYECTO
+================================================================================
+
+1. CÓDIGO FUENTE COMPLETO
+ - Código PHP backend (42 APIs, servicios, clases)
+ - Frontend (HTML, CSS, JavaScript)
+ - Configuración Docker (dev y prod)
+ - Scripts SQL (esquema, migraciones)
+ - Tests automatizados
+
+2. DOCUMENTACIÓN COMPLETA
+ - README.md
+ - DOCUMENTACION_COMPLETA.md (este documento)
+ - REPORTE_SISTEMA_COMPLETO.md
+ - DEPLOYMENT_GUIDE.md
+ - DOCKER_README.md
+ - SSE_REALTIME_DOCS.md
+ - API_REFERENCE.md
+
+3. BASE DE DATOS
+ - Esquema completo (schema_complete.sql)
+ - Migraciones ordenadas
+ - Scripts de mantenimiento
+
+4. CONFIGURACIÓN DE SERVIDOR
+ - Nginx configuration
+ - Apache vhost configuration
+ - Supervisor configuration
+ - Cron jobs
+
+5. SCRIPTS DE UTILIDAD
+ - install.php (Instalador interactivo)
+ - run_migrations.php
+ - clean_old_logs.php
+ - backup_database.sh
+ - restore_backup.sh
+ - check_health.php
+ - test_whatsapp_connection.php
+ - generate_report.php
+
+6. TESTS AUTOMATIZADOS
+ - Tests unitarios
+ - Tests de integración
+ - Coverage reports
+
+7. DOCKER COMPOSE
+ - docker-compose.dev.yml (Desarrollo)
+ - docker-compose.prod.yml (Producción)
+
+8. MANUALES DE USUARIO
+ - Manual de usuario final (PDF)
+ - Manual de administrador (PDF)
+
+9. VIDEO TUTORIALES
+ - Instalación y configuración inicial (15 min)
+ - Configurar WhatsApp Business API (10 min)
+ - Gestión de conversaciones (8 min)
+ - Crear respuestas automáticas (7 min)
+ - Enviar mensajes masivos (6 min)
+ - Crear plantillas de WhatsApp (12 min)
+ - Administración de usuarios (5 min)
+ - Interpretar estadísticas (8 min)
+ - Solución de problemas comunes (10 min)
+ - Actualización del sistema (7 min)
+
+10. LICENCIA Y GARANTÍA
+ - 30 días de soporte post-entrega
+ - Corrección de bugs sin costo
+ - Actualizaciones de seguridad durante 1 año
+ - Consultoría técnica (10 horas incluidas)
+
+11. ACCESOS Y CREDENCIALES
+ - Credenciales de admin por defecto
+ - Accesos a servidor (SSH)
+ - Credenciales de base de datos
+ - Tokens de WhatsApp Business API
+
+12. CHECKLIST DE ENTREGA
+ - Pre-Entrega
+ - Entrega
+ - Post-Entrega
+
+
+================================================================================
+8. MANTENIMIENTO Y SOPORTE
+================================================================================
+
+8.1. Tareas de Mantenimiento
+------------------------------
+
+DIARIO (Automatizado)
+----------------------
+1. Backup de base de datos (2:00 AM, retención 7 días)
+2. Limpieza de logs (3:00 AM, retención 30 días)
+3. Verificación de salud (cada hora)
+
+SEMANAL
+-------
+1. Optimización de base de datos
+2. Revisión de logs de error
+3. Actualización de dependencias
+
+MENSUAL
+-------
+1. Auditoría de seguridad
+2. Análisis de uso y optimización
+3. Backup completo
+
+
+8.2. Monitoreo
+---------------
+
+MÉTRICAS CLAVE
+--------------
+Métrica Umbral Acción
+------------------------------------------------------------------------
+CPU > 80% 5 min Escalar recursos
+RAM > 90% 5 min Revisar memory leaks
+Disco > 85% - Limpiar archivos antiguos
+Respuesta API > 2s - Optimizar queries
+Errores > 10/min - Investigar logs
+
+
+8.3. Solución de Problemas Comunes
+------------------------------------
+
+1. MENSAJES NO SE ENVÍAN
+Síntomas: API retorna error 401 o 403
+Solución:
+- Verificar token de acceso
+- Regenerar token si expiró
+- Verificar permisos del token en Meta
+
+2. WEBHOOK NO RECIBE EVENTOS
+Síntomas: No llegan mensajes nuevos
+Solución:
+- Verificar URL del webhook en Meta
+- Revisar logs de webhook
+- Verificar suscripción a eventos
+- Probar manualmente con curl
+
+3. SSE NO FUNCIONA
+Síntomas: No hay notificaciones en tiempo real
+Solución:
+- Verificar que Redis esté corriendo
+- Revisar configuración PHP (output_buffering=Off)
+- Verificar soporte SSE en navegador
+- Revisar logs del navegador
+
+4. MULTIMEDIA NO SE DESCARGA
+Síntomas: Imágenes/videos no se muestran
+Solución:
+- Verificar permisos de carpeta uploads/
+- Verificar espacio en disco
+- Revisar logs de descarga
+- Verificar permisos del token
+
+5. ALTO USO DE CPU/RAM
+Síntomas: Sistema lento, timeouts
+Solución:
+- Identificar procesos pesados con top
+- Optimizar consultas lentas
+- Aumentar recursos del servidor
+- Activar cache (Redis/Memcached)
+- Optimizar tablas de base de datos
+
+
+8.4. Contacto de Soporte
+--------------------------
+
+Desarrollado por: U-Site.app
+
+Canales de soporte:
+- Email: soporte@u-site.app
+- WhatsApp: +57 XXX XXX XXXX
+- Portal: https://u-site.app/soporte
+- Telegram: @usiteapp_soporte
+
+Horario de soporte:
+- Lunes a Viernes: 9:00 AM - 6:00 PM (GMT-5)
+- Emergencias críticas: 24/7 (solo clientes premium)
+
+Niveles de soporte:
+- Básico (incluido): Email, respuesta en 24-48h
+- Estándar: Email + WhatsApp, respuesta en 12h
+- Premium: Email + WhatsApp + Teléfono, respuesta en 2h, atención 24/7
+
+
+================================================================================
+LICENCIA
+================================================================================
+
+© 2024-2026 U-Site.app. Todos los derechos reservados.
+
+Este software es propiedad de U-Site.app y está protegido por las leyes
+de derechos de autor.
+
+Permisos:
+- Uso en producción para el cliente que adquirió la licencia
+- Modificaciones para uso interno
+- Despliegue en múltiples instancias del mismo cliente
+
+Prohibido:
+- Redistribución del código fuente
+- Reventa o sublicencia
+- Uso en proyectos de terceros sin autorización
+
+Para consultas sobre licenciamiento, contacta: licencias@u-site.app
+
+
+================================================================================
+CONCLUSIÓN
+================================================================================
+
+Este sistema de WhatsApp Bot Manager representa una solución completa y
+profesional para gestión de comunicaciones empresariales vía WhatsApp Business.
+
+CARACTERÍSTICAS DESTACADAS:
+- 42 endpoints API REST documentados
+- Interfaz moderna y responsive
+- Tiempo real con SSE
+- Bot inteligente y configurable
+- Soporte multimedia completo
+- Seguridad robusta
+- Docker ready
+- Escalable y mantenible
+
+CASOS DE USO VALIDADOS:
+- Atención al cliente automatizada
+- Campañas de marketing masivas
+- Soporte técnico con tickets
+- Reservas y citas
+- Encuestas y feedback
+
+PRÓXIMOS PASOS SUGERIDOS:
+1. Revisar la documentación completa
+2. Seguir la guía de instalación
+3. Configurar WhatsApp Business API
+4. Personalizar respuestas automáticas
+5. Capacitar al equipo
+6. Lanzar en producción
+7. Monitorear y optimizar
+
+¿Necesitas ayuda? Contacta al equipo de U-Site.app:
+
+Website: u-site.app
+Email: contacto@u-site.app
+WhatsApp: +57 XXX XXX XXXX
+
+
+================================================================================
+Desarrollado con amor por U-Site.app
+Simplificando la comunicación empresarial
+================================================================================
diff --git a/Dockerfile.maintenance b/Dockerfile.maintenance
new file mode 100644
index 0000000..de53c99
--- /dev/null
+++ b/Dockerfile.maintenance
@@ -0,0 +1,44 @@
+# Dockerfile para contenedor de mantenimiento
+FROM alpine:3.19
+
+# Instalar herramientas necesarias incluyendo PHP
+RUN apk add --no-cache \
+ bash \
+ php82 \
+ php82-pdo \
+ php82-pdo_mysql \
+ php82-mysqli \
+ php82-curl \
+ php82-json \
+ php82-mbstring \
+ php82-openssl \
+ php82-redis \
+ mariadb-client \
+ curl \
+ tzdata \
+ dcron \
+ logrotate \
+ gzip && \
+ ln -sf /usr/bin/php82 /usr/bin/php
+
+# Configurar zona horaria
+ENV TZ=America/Bogota
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+# Crear directorios de trabajo
+RUN mkdir -p /backups /scripts /var/log/cron
+
+# Copiar scripts de mantenimiento
+COPY docker/maintenance/scripts/ /scripts/
+RUN chmod +x /scripts/*.sh
+
+# Configurar crontab
+COPY docker/maintenance/crontab /etc/crontabs/root
+
+# Script de inicio
+COPY docker/maintenance/entrypoint.sh /entrypoint.sh
+RUN chmod +x /entrypoint.sh
+
+WORKDIR /scripts
+
+ENTRYPOINT ["/entrypoint.sh"]
diff --git a/EMERGENCIA_TABLA_NO_EXISTE.md b/EMERGENCIA_TABLA_NO_EXISTE.md
deleted file mode 100644
index dc87930..0000000
--- a/EMERGENCIA_TABLA_NO_EXISTE.md
+++ /dev/null
@@ -1,146 +0,0 @@
-# 🚨 EMERGENCIA: "Table doesn't exist" - SOLUCIÓN INMEDIATA
-
-## ⚠️ Tu Error Específico
-```
-❌ Table 'usite_whatsapp_bot.menus' doesn't exist
-❌ Table 'usite_whatsapp_bot.users' doesn't exist
-⚠️ 0 tablas, 0 elementos
-```
-
-**CAUSA:** Las tablas NO se están creando. Todos los instaladores anteriores fallan en tu hosting.
-
----
-
-## 🚀 SOLUCIÓN DEFINITIVA
-
-### 🎯 **OPCIÓN 1: Instalador Ultra Básico (GARANTIZADO)**
-**👉 [install_ultra_basic.php](install_ultra_basic.php)**
-
-**¿Por qué funcionará?**
-- ✅ Crea SOLO 2 tablas esenciales: `users` y `system_config`
-- ✅ Verifica CADA tabla después de crearla
-- ✅ NO intenta insertar datos si las tablas fallan
-- ✅ Proceso paso a paso con verificación completa
-
-### 🛠️ **OPCIÓN 2: Crear Tablas Manualmente**
-Si incluso el ultra básico falla, hazlo manual:
-
-1. **Ve a phpMyAdmin en tu HestiaCP**
-2. **Selecciona tu BD:** `usite_whatsapp_bot`
-3. **Ejecuta este SQL:**
-
-```sql
-CREATE TABLE IF NOT EXISTS users (
- id INT AUTO_INCREMENT PRIMARY KEY,
- phone_number VARCHAR(20) UNIQUE NOT NULL,
- name VARCHAR(100),
- status ENUM('active', 'inactive') DEFAULT 'active',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE TABLE IF NOT EXISTS system_config (
- id INT AUTO_INCREMENT PRIMARY KEY,
- config_key VARCHAR(100) UNIQUE NOT NULL,
- config_value TEXT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-);
-
-INSERT INTO system_config (config_key, config_value) VALUES
-('app_installed', '1'),
-('install_date', NOW()),
-('business_name', 'Mi Empresa');
-```
-
-4. **Luego actualiza config/config.php manualmente:**
-```php
-define('DB_HOST', 'localhost');
-define('DB_NAME', 'usite_whatsapp_bot');
-define('DB_USER', 'tu_usuario');
-define('DB_PASS', 'tu_contraseña');
-define('ADMIN_PASSWORD', password_hash('admin123', PASSWORD_DEFAULT));
-```
-
-5. **Crear archivo:** `.installation_completed` (contenido cualquiera)
-
----
-
-## 📋 Diferencias de los Instaladores
-
-| Instalador | Tablas que crea | Tu resultado anterior |
-|------------|------------------|----------------------|
-| `install.php` | Intenta crear BD | ❌ No tienes privilegios |
-| `install_manual.php` | 7+ tablas complejas | ❌ Falla en parsing |
-| `install_integrated.php` | 7+ tablas, schema interno | ❌ Aún falla creación |
-| `install_simple.php` | 3 tablas básicas | ❌ Probablemente falla |
-| **`install_ultra_basic.php`** | **SOLO 2 tablas** | ✅ **DEBERÍA FUNCIONAR** |
-
----
-
-## 🔍 ¿Por qué fallan los otros?
-
-### Problemas identificados en tu hosting:
-1. **Permisos restrictivos:** Puede que no tengas CREATE TABLE completo
-2. **Claves foráneas:** Tu MySQL puede rechazar FOREIGN KEY
-3. **Tipos de datos:** Algunos ENUM o JSON no soportados
-4. **Transacciones:** Problemas con múltiples operaciones
-
-### El Ultra Básico evita esto:
-- ✅ Solo 2 tablas simples, sin dependencias
-- ✅ Sin claves foráneas complejas
-- ✅ Sin tipos de datos exóticos
-- ✅ Verifica cada paso antes de continuar
-
----
-
-## 🎯 PASOS EXACTOS PARA TI
-
-### 1. Ir al Instalador Ultra Básico
-```
-URL: tudominio.com/install_ultra_basic.php
-```
-
-### 2. Usar las mismas credenciales
-- **Host:** localhost
-- **BD:** usite_whatsapp_bot
-- **Usuario:** [el que creaste en HestiaCP]
-- **Contraseña:** [la que configuraste]
-
-### 3. Si funciona verás:
-```
-✅ Conectado a 'usite_whatsapp_bot'
-✅ Tabla 'users' creada y funcional
-✅ Tabla 'system_config' creada y funcional
-✅ Configuración inicial insertada
-✅ config.php actualizado
-✅ Verificación final: 0 usuarios, 4 configs
-🎉 Instalación básica completada
-```
-
-### 4. Después podrás:
-- Entrar al panel con: `admin` / `[contraseña generada]`
-- Probar con: `test.php`
-- Agregar funciones desde el panel
-
----
-
-## 📞 Si AÚN No Funciona
-
-### Información para soporte:
-1. **Resultado exacto del ultra básico**
-2. **Versión PHP de tu hosting**
-3. **Versión MySQL de tu hosting**
-4. **Permisos exactos del usuario de BD**
-
-### Alternativa extrema:
-Si ningún instalador funciona, el problema es de permisos de tu hosting. Contacta soporte de HestiaCP para revisar permisos de CREATE TABLE.
-
----
-
-## 🎉 Una vez que funcione:
-
-✅ **Tendrás:** Sistema básico funcional
-✅ **Podrás:** Acceder al panel de admin
-✅ **Siguiente:** Configurar WhatsApp desde el panel
-✅ **Expandir:** Agregar más funciones gradualmente
-
-**👉 [IR AL INSTALADOR ULTRA BÁSICO AHORA](install_ultra_basic.php)** 🚀
\ No newline at end of file
diff --git a/FIX_PASTE_IMAGE_500.md b/FIX_PASTE_IMAGE_500.md
deleted file mode 100644
index dbcd7f8..0000000
--- a/FIX_PASTE_IMAGE_500.md
+++ /dev/null
@@ -1,57 +0,0 @@
-## ✅ Problema del Error 500 Corregido
-
-### 🐛 Causa del Error:
-Al pegar una imagen desde el portapapeles y enviarla, las peticiones `fetch` a los endpoints de API **NO estaban enviando las cookies de sesión**, causando que:
-1. `upload_media.php` rechazara la petición → Error 401
-2. Nginx devolviera error 500 por fallo en PHP
-
-### 🔧 Solución Aplicada:
-
-Agregué `credentials: 'same-origin'` a ambos fetch en `sendMediaMessage()`:
-
-```javascript
-// 1. Upload del archivo
-const uploadResponse = await fetch('api/upload_media.php', {
- method: 'POST',
- credentials: 'same-origin', // ✅ Enviar cookies de sesión
- body: formData
-});
-
-// 2. Envío del mensaje
-const sendResponse = await fetch('api/send_media_message.php', {
- method: 'POST',
- credentials: 'same-origin', // ✅ Enviar cookies de sesión
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({...})
-});
-```
-
-### 📋 Cómo Funciona Ahora:
-
-1. **Copias una imagen** (Ctrl+C o Cmd+C)
-2. **Haces click en el input de mensaje**
-3. **Pegas** (Ctrl+V o Cmd+V)
-4. ✅ Aparece el preview de la imagen
-5. ✅ Haces click en "Enviar"
-6. ✅ Se sube el archivo con autenticación correcta
-7. ✅ Se envía por WhatsApp
-8. ✅ Aparece en la conversación
-
-### 🧪 Probar Ahora:
-
-1. Recarga la página: http://localhost:8080/conversations.php
-2. Abre una conversación
-3. Copia una imagen desde cualquier lugar
-4. Haz click en el input de texto
-5. Presiona Ctrl+V (o Cmd+V en Mac)
-6. Debe aparecer el preview sin errores
-7. Haz click en enviar
-8. Debe enviarse correctamente
-
-### 📝 Nota Técnica:
-
-Por defecto, `fetch()` **NO envía cookies** a menos que especifiques:
-- `credentials: 'same-origin'` → Envía cookies al mismo dominio
-- `credentials: 'include'` → Envía cookies incluso en CORS
-
-El endpoint `upload_media.php` usa `requireAuthentication()` que verifica la sesión en cookies, por eso fallaba antes.
diff --git a/GUIA_ERROR_PLANTILLAS.md b/GUIA_ERROR_PLANTILLAS.md
deleted file mode 100644
index e586f67..0000000
--- a/GUIA_ERROR_PLANTILLAS.md
+++ /dev/null
@@ -1,167 +0,0 @@
-# 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*
\ No newline at end of file
diff --git a/GUIA_HESTIACP.md b/GUIA_HESTIACP.md
deleted file mode 100644
index 0b6bcfe..0000000
--- a/GUIA_HESTIACP.md
+++ /dev/null
@@ -1,158 +0,0 @@
-# 🛠️ Guía de Instalación en HestiaCP
-
-**Desarrollado por U-Site.app** - [https://u-site.app](https://u-site.app)
-
-## ⚠️ Problema Común en Hostings Compartidos
-
-El instalador automático (`install.php`) **NO FUNCIONA** en hostings compartidos como HestiaCP, cPanel, etc., porque requiere privilegios de administrador para crear bases de datos y usuarios.
-
-## ✅ Solución: Instalación Manual
-
-Usa el archivo `install_manual.php` que he creado específicamente para hostings compartidos.
-
----
-
-## 📋 Pasos en HestiaCP
-
-### 1. Crear Base de Datos
-1. Entra al panel de HestiaCP
-2. Ve a **"Databases"** o **"Base de datos"**
-3. Haz clic en **"Add Database"**
-4. Configura:
- - **Database Name:** `whatsapp_bot` (o el nombre que prefieras)
- - **Database User:** Crea un usuario (ej: `whatsapp_user`)
- - **Password:** Usa una contraseña segura
- - **Privileges:** Selecciona **ALL** o todos los permisos
-
-### 2. Anotar Credenciales
-Guarda esta información que necesitarás:
-
-```
-Host: localhost
-Puerto: 3306 (generalmente)
-Base de datos: [el nombre que creaste]
-Usuario: [el usuario que creaste]
-Contraseña: [la contraseña que configuraste]
-```
-
-### 3. Ejecutar Instalación Manual
-1. Abre tu navegador web
-2. Ve a: `https://tudominio.com/ruta-del-bot/install_manual.php`
-3. Completa el formulario con las credenciales del paso anterior
-4. Haz clic en **"Configurar Sistema"**
-
----
-
-## 🔧 Características del Instalador Manual
-
-### ✅ Lo que SÍ hace:
-- ✅ Conecta con tu BD existente
-- ✅ Crea todas las tablas necesarias
-- ✅ Inserta datos de configuración inicial
-- ✅ Genera contraseña de admin aleatoria
-- ✅ Configura el archivo `config.php`
-- ✅ Detecta automáticamente la URL del servidor
-- ✅ Protege contra reinstalaciones accidentales
-- ✅ Crea archivo de credenciales
-
-### ❌ Lo que NO necesita:
-- ❌ Privilegios de root/administrador
-- ❌ Crear la base de datos (ya la creaste)
-- ❌ Crear el usuario (ya lo creaste)
-- ❌ Configuración especial del servidor
-
----
-
-## 📁 Estructura Típica en HestiaCP
-
-```
-/public_html/
-├── tu-bot/
-│ ├── install_manual.php ← Usa este archivo
-│ ├── install.php ← NO uses este (no funciona)
-│ ├── config/
-│ ├── api/
-│ └── ...resto de archivos
-```
-
----
-
-## 🚨 Errores Comunes y Soluciones
-
-### Error: "se queda cargando"
-**Problema:** El instalador automático intenta crear BD con privilegios que no tienes.
-**Solución:** Usa `install_manual.php` en lugar de `install.php`
-
-### Error: "Access denied for user"
-**Problema:** Credenciales incorrectas o permisos insuficientes.
-**Solución:**
-- Verifica que el usuario tenga ALL PRIVILEGES en la BD
-- Confirma host, usuario y contraseña
-- Asegúrate de que la BD existe
-
-### Error: "Connection refused"
-**Problema:** Host o puerto incorrectos.
-**Solución:**
-- Usa `localhost` como host
-- Confirma que el puerto sea 3306
-- Verifica que MySQL esté corriendo
-
-### Error: "Database doesn't exist"
-**Problema:** Nombre de BD incorrecto o no existe.
-**Solución:**
-- Verifica el nombre exacto en el panel de HestiaCP
-- Asegúrate de que la BD fue creada correctamente
-
----
-
-## 🎯 Después de la Instalación
-
-1. **Guarda las credenciales:** Se generará un archivo `CREDENCIALES_SISTEMA.txt`
-2. **Prueba el sistema:** Ve a `test.php` para verificar
-3. **Configura WhatsApp:** Ve a `server_setup.php`
-4. **Accede al panel:** Ve a `index.php` con usuario `admin`
-
----
-
-## 📞 Soporte Técnico
-
-- **Email:** support@u-site.app
-- **Web:** https://u-site.app/support
-- **Documentación:** Este archivo y los comentarios en el código
-
----
-
-## 🔐 Credenciales por Defecto
-
-### Base de Datos
-- **Host:** localhost
-- **Usuario/Contraseña:** Los que TÚ configuraste en HestiaCP
-
-### Panel Admin
-- **Usuario:** admin
-- **Contraseña:** Se genera automáticamente (se muestra durante instalación)
-
----
-
-## ⚡ Comandos Útiles para Debugging
-
-### Verificar conexión desde terminal:
-```bash
-mysql -h localhost -u tu_usuario -p tu_basedatos
-```
-
-### Ver tablas después de instalar:
-```sql
-USE tu_basedatos;
-SHOW TABLES;
-```
-
-### Ver configuración de usuario:
-```sql
-SELECT User, Host FROM mysql.user WHERE User='tu_usuario';
-SHOW GRANTS FOR 'tu_usuario'@'localhost';
-```
-
----
-
-**¡Listo! Con esta guía deberías poder instalar sin problemas en HestiaCP.** 🚀
\ No newline at end of file
diff --git a/GUIA_HESTIACP_COMPLETA.md b/GUIA_HESTIACP_COMPLETA.md
deleted file mode 100644
index 474a650..0000000
--- a/GUIA_HESTIACP_COMPLETA.md
+++ /dev/null
@@ -1,214 +0,0 @@
-# 🚀 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: conversations, 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 system_config
-2. Configurar Redis/Memcached si disponible
-3. Optimizar MySQL en HestiaCP system_config
-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! 🚀
\ No newline at end of file
diff --git a/GUIA_HESTIA_SSE.md b/GUIA_HESTIA_SSE.md
deleted file mode 100644
index 5cb41b1..0000000
--- a/GUIA_HESTIA_SSE.md
+++ /dev/null
@@ -1,270 +0,0 @@
-# 🚀 Configuración SSE para HestiaCP
-
-## ✅ Pasos para Activar SSE en tu Servidor HestiaCP
-
-### 1. Verificar que el código esté en el servidor
-```bash
-# Subir por FTP/SFTP o Git
-cd /home/tu-usuario/web/tu-dominio.com/public_html/
-ls -la api/sse_events.php # Debe existir
-```
-
-### 2. Configurar Apache para SSE (si usas Apache)
-
-**Opción A: Editar .htaccess en la raíz**
-```apache
-# Agregar al final de .htaccess
-
- # SSE Headers
-
- Header set Cache-Control "no-cache, no-store, must-revalidate"
- Header set X-Accel-Buffering "no"
- SetEnv no-gzip 1
-
-
-```
-
-**Opción B: Crear .htaccess específico en /api/**
-```bash
-cd /home/tu-usuario/web/tu-dominio.com/public_html/api/
-nano .htaccess
-```
-
-Contenido:
-```apache
-
- Header set Cache-Control "no-cache, no-store, must-revalidate"
- Header set X-Accel-Buffering "no"
- SetEnv no-gzip 1
-
-
-# Prevenir timeout en SSE
-
- FcgidIOTimeout 300
-
-```
-
-### 3. Ajustar PHP-FPM (Recomendado)
-
-Editar configuración PHP-FPM en HestiaCP:
-
-1. **Panel HestiaCP** → **Web** → **tu dominio** → **Edit**
-2. **Backend Template**: PHP-FPM
-3. **PHP Version**: 8.x
-
-Luego editar el archivo de configuración (vía SSH):
-```bash
-# Ubicación típica en HestiaCP
-sudo nano /etc/php/8.2/fpm/pool.d/tu-usuario.conf
-
-# Agregar o modificar:
-request_terminate_timeout = 300s
-pm.max_requests = 500
-```
-
-Reiniciar PHP-FPM:
-```bash
-sudo systemctl restart php8.2-fpm
-```
-
-### 4. Verificar Nginx (si usas Nginx)
-
-Si tu dominio usa Nginx como proxy, editar la configuración:
-
-```bash
-sudo nano /etc/nginx/conf.d/domains/tu-dominio.com.conf
-```
-
-Agregar dentro del bloque `location ~ \.php$`:
-```nginx
-# Configuración especial para SSE
-location ~ ^/api/sse_events\.php$ {
- fastcgi_buffering off;
- proxy_buffering off;
-
- fastcgi_read_timeout 300s;
- fastcgi_send_timeout 300s;
-
- fastcgi_pass unix:/run/php/php8.2-fpm.sock;
- include fastcgi_params;
-}
-```
-
-Reiniciar Nginx:
-```bash
-sudo systemctl restart nginx
-```
-
-### 5. Probar SSE desde el servidor
-
-```bash
-# Conectar por SSH a tu servidor
-ssh usuario@tu-dominio.com
-
-# Probar SSE
-curl -N -m 5 'https://tu-dominio.com/api/sse_events.php?token=demo_token'
-```
-
-**Respuesta esperada:**
-```
-event: connected
-data: {"timestamp":1706300000,"user_id":"token_12345678","mode":"authenticated","status":"ready"}
-
-: heartbeat
-```
-
-Si ves esto, **SSE está funcionando** ✅
-
-### 6. Probar desde el navegador
-
-1. Abrir tu sitio: `https://tu-dominio.com/conversations.php`
-2. Abrir Consola del navegador (F12 → Console)
-3. Buscar:
- ```
- Conectando a SSE para eventos en tiempo real...
- SSE URL: https://tu-dominio.com/api/sse_events.php?token=...
- ✅ SSE conectado (🔐 autenticado): {...}
- ```
-
-### 7. Solución de Problemas
-
-#### Error: "Failed to fetch" o timeout
-
-**Causa**: Firewall o mod_security bloqueando
-**Solución**:
-```bash
-# Desactivar mod_security para SSE (temporal)
-sudo a2dismod security2
-sudo systemctl restart apache2
-
-# O agregar excepción en .htaccess:
-
- SecRuleEngine Off
-
-```
-
-#### Error: "EventSource's response has a MIME type"
-
-**Causa**: Headers incorrectos
-**Solución**: Verificar que sse_events.php tenga:
-```php
-header('Content-Type: text/event-stream');
-```
-
-#### Error: Conexión se cierra cada 30 segundos
-
-**Causa**: Timeout de proxy
-**Solución**: Aumentar timeouts en nginx.conf o apache2.conf:
-```
-# Apache
-Timeout 300
-KeepAliveTimeout 300
-
-# Nginx
-proxy_read_timeout 300s;
-```
-
-### 8. Configuración de Seguridad (Producción)
-
-#### A. Token seguro en vez de demo_token
-
-Editar `api/sse_events.php`:
-```php
-// Generar token en login
-$_SESSION['sse_token'] = bin2hex(random_bytes(32));
-
-// Validar token en SSE
-$token = $_GET['token'] ?? null;
-if ($token && isset($_SESSION['sse_token']) && $token === $_SESSION['sse_token']) {
- $authenticated = true;
-}
-```
-
-En `conversations.php`:
-```javascript
-// Usar token de sesión
-const token = '';
-const sseUrl = `${baseUrl}/api/sse_events.php?token=${token}&t=${Date.now()}`;
-```
-
-#### B. Restringir acceso por IP (opcional)
-
-En `.htaccess`:
-```apache
-
- # Solo permitir desde localhost o IPs específicas
- Require ip 127.0.0.1
- Require ip tu.ip.publica
-
-```
-
-### 9. Monitoreo
-
-Ver conexiones SSE activas:
-```bash
-# Ver procesos PHP activos
-ps aux | grep sse_events
-
-# Ver logs de errores
-tail -f /var/log/php8.2-fpm.log
-tail -f /var/log/apache2/error.log
-tail -f /var/log/nginx/error.log
-```
-
-### 10. Performance
-
-Para muchos usuarios simultáneos:
-
-```bash
-# Aumentar límites PHP-FPM
-sudo nano /etc/php/8.2/fpm/pool.d/tu-usuario.conf
-
-pm = dynamic
-pm.max_children = 50
-pm.start_servers = 10
-pm.min_spare_servers = 5
-pm.max_spare_servers = 20
-pm.max_requests = 500
-```
-
----
-
-## 🎯 Checklist de Verificación
-
-- [ ] Archivo `api/sse_events.php` subido al servidor
-- [ ] `.htaccess` configurado con headers SSE
-- [ ] PHP-FPM timeout aumentado a 300s
-- [ ] Nginx/Apache configurado (si aplica)
-- [ ] Test con curl funciona (muestra "event: connected")
-- [ ] Test desde navegador funciona (console.log muestra "✅ SSE conectado")
-- [ ] Token de seguridad configurado (no usar demo_token en producción)
-- [ ] Firewall/mod_security permite conexiones largas
-
----
-
-## 📞 Soporte HestiaCP
-
-Si tienes problemas:
-
-1. **Panel HestiaCP**: https://tu-dominio.com:8083
-2. **Logs**: Panel → Log Files
-3. **SSH**: Usuario y contraseña en el panel
-4. **Documentación**: https://docs.hestiacp.com/
-
----
-
-## ✅ Resultado Esperado
-
-Una vez configurado correctamente:
-
-```
-ANTES (desarrollo local):
-http://localhost:8000/api/sse_events.php
-❌ Timeout (servidor PHP built-in no soporta SSE)
-
-AHORA (producción HestiaCP):
-https://tu-dominio.com/api/sse_events.php
-✅ Conectado - eventos en tiempo real < 1 segundo
-```
-
-**¡Tu sistema de notificaciones en tiempo real estará funcionando!** 🚀
diff --git a/GUIA_SOLUCION_SSE.md b/GUIA_SOLUCION_SSE.md
deleted file mode 100644
index 3ffe609..0000000
--- a/GUIA_SOLUCION_SSE.md
+++ /dev/null
@@ -1,260 +0,0 @@
-# 🔧 Guía de Solución de Errores SSE
-
-## Error: "❌ Error en SSE" sin detalles
-
-### Diagnóstico Rápido
-
-1. **Revisar la consola del navegador**
- - Buscar el nuevo log con `readyState: X (STATE_NAME)`
- - Estados posibles:
- - `0 (CONNECTING)`: El navegador está intentando conectar
- - `1 (OPEN)`: Conexión establecida correctamente
- - `2 (CLOSED)`: Conexión cerrada (error)
-
-2. **Verificar la pestaña Network**
- - Buscar la petición a `sse_events.php`
- - Ver el status code:
- - `200`: OK (pero puede haber error en el stream)
- - `401`: No autenticado
- - `500`: Error del servidor
- - `502/504`: Error de proxy/timeout
-
-### Solución por Estado
-
-#### Estado CLOSED (readyState: 2)
-
-**Causa común**: El servidor cierra la conexión inmediatamente
-
-**Solución**:
-```bash
-# 1. Verificar logs del servidor
-tail -f /var/log/apache2/error.log
-# o
-tail -f /var/log/nginx/error.log
-
-# 2. Probar el endpoint directamente
-curl -N http://localhost:8000/api/sse_events.php?token=demo_token
-
-# 3. Ejecutar el diagnóstico completo
-php test_sse_complete.php
-```
-
-**Posibles problemas**:
-- PHP cierra la conexión por timeout → Verificar `set_time_limit(0)` en sse_events.php
-- Buffer de salida activo → Verificar que no hay `ob_start()` antes
-- Nginx/Apache cortando la conexión → Configurar proxy buffering off
-
-#### Estado CONNECTING (readyState: 0)
-
-**Causa común**: El navegador no puede conectar al servidor
-
-**Solución**:
-```bash
-# 1. Verificar que el servidor está corriendo
-lsof -i :8000
-
-# 2. Verificar firewall
-# macOS
-sudo pfctl -s rules
-
-# 3. Probar desde curl
-curl -I http://localhost:8000/api/sse_events.php?token=demo_token
-```
-
-### Configuración del Servidor
-
-#### Apache (.htaccess)
-```apache
-# Deshabilitar compresión para SSE
-
-
- Header set Cache-Control "no-cache, no-store, must-revalidate"
- Header set X-Accel-Buffering "no"
- SetEnv no-gzip 1
-
-
-```
-
-#### Nginx (nginx.conf)
-```nginx
-location /api/sse_events.php {
- fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
- include fastcgi_params;
-
- # Deshabilitar buffering para SSE
- fastcgi_buffering off;
- proxy_buffering off;
-
- # Timeouts largos
- fastcgi_read_timeout 300s;
- fastcgi_send_timeout 300s;
-}
-```
-
-#### PHP-FPM (www.conf)
-```ini
-; Aumentar timeout de ejecución
-request_terminate_timeout = 300s
-
-; Permitir más procesos para SSE
-pm.max_children = 50
-pm.start_servers = 10
-pm.min_spare_servers = 5
-pm.max_spare_servers = 20
-```
-
-### Pruebas Manuales
-
-#### Test 1: Verificar headers
-```bash
-curl -I http://localhost:8000/api/sse_events.php?token=demo_token
-```
-
-**Esperado**:
-```
-HTTP/1.1 200 OK
-Content-Type: text/event-stream
-Cache-Control: no-cache, no-store, must-revalidate
-Connection: keep-alive
-X-Accel-Buffering: no
-```
-
-#### Test 2: Recibir eventos
-```bash
-curl -N http://localhost:8000/api/sse_events.php?token=demo_token
-```
-
-**Esperado**:
-```
-event: connected
-data: {"timestamp":1706300000,"user_id":"token_12345678","mode":"authenticated"}
-
-: heartbeat
-```
-
-#### Test 3: Diagnóstico completo
-```bash
-php test_sse_complete.php
-```
-
-**Esperado**:
-```
-✅ Headers SSE correctos
-[1] 📨 Evento: connected
- 📦 Data: {
- "timestamp": 1706300000,
- "user_id": "token_12345678",
- "mode": "authenticated"
- }
-```
-
-### Solución de Problemas Comunes
-
-#### Error: "net::ERR_INCOMPLETE_CHUNKED_ENCODING"
-
-**Causa**: El servidor termina el stream abruptamente
-
-**Solución**:
-1. Verificar que no hay errores PHP en el stream
-2. Aumentar `max_execution_time` en php.ini
-3. Verificar que no hay `exit()` o `die()` en el loop SSE
-
-#### Error: "Failed to fetch"
-
-**Causa**: CORS o credenciales
-
-**Solución**:
-```javascript
-// En conversations.php, verificar que se usa:
-this.eventSource = new EventSource(sseUrl, {
- withCredentials: true
-});
-```
-
-```php
-// En sse_events.php, agregar headers CORS:
-header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']);
-header('Access-Control-Allow-Credentials: true');
-```
-
-#### Error: Connection timeout después de 30s
-
-**Causa**: Servidor proxy cortando conexión
-
-**Solución Apache**:
-```apache
-Timeout 300
-KeepAliveTimeout 300
-```
-
-**Solución Nginx**:
-```nginx
-proxy_read_timeout 300s;
-proxy_connect_timeout 300s;
-proxy_send_timeout 300s;
-```
-
-### Debugging Avanzado
-
-#### Habilitar logs detallados PHP
-```php
-// Agregar al inicio de sse_events.php
-error_reporting(E_ALL);
-ini_set('display_errors', 0); // No mostrar en stream
-ini_set('log_errors', 1);
-ini_set('error_log', '/tmp/sse_debug.log');
-```
-
-#### Monitorear eventos en tiempo real
-```bash
-# Terminal 1: Ver logs
-tail -f /tmp/sse_debug.log
-
-# Terminal 2: Conectar SSE
-php test_sse_complete.php
-
-# Terminal 3: Enviar evento de prueba
-curl -X POST http://localhost:8000/api/push_event.php \
- -H "Content-Type: application/json" \
- -d '{"event":"test","data":{"message":"Prueba"},"target":"all"}'
-```
-
-### Checklist de Verificación
-
-- [ ] El servidor está corriendo en el puerto esperado
-- [ ] Los headers SSE están correctos (Content-Type: text/event-stream)
-- [ ] No hay buffering de salida activo
-- [ ] El endpoint responde con HTTP 200
-- [ ] Se recibe el evento 'connected' al conectar
-- [ ] Los eventos tienen el formato correcto (event: XXX\ndata: {}\n\n)
-- [ ] El archivo de eventos temporal es escribible (/uploads/)
-- [ ] Los logs no muestran errores PHP
-- [ ] El navegador no está bloqueando la conexión (CORS)
-- [ ] La autenticación está funcionando (sesión o token)
-
-### Contacto y Soporte
-
-Si después de seguir esta guía el problema persiste:
-
-1. Ejecutar `php test_sse_complete.php` y copiar la salida completa
-2. Verificar `/tmp/sse_debug.log` y copiar los últimos 50 líneas
-3. Revisar la consola del navegador y copiar todos los logs relacionados con SSE
-4. Verificar Network tab y copiar headers/response de sse_events.php
-
-## Mejoras Implementadas
-
-### 1. Mejor Logging en Cliente (conversations.php)
-- ✅ Log detallado del readyState con nombre del estado
-- ✅ Limpieza de timeout antes de reconectar
-- ✅ Try-catch al cerrar EventSource
-
-### 2. Mejor Manejo en Servidor (sse_events.php)
-- ✅ Límite de tiempo de conexión (5 minutos)
-- ✅ Detección de cliente desconectado
-- ✅ Logs más detallados con trace completo
-- ✅ Headers optimizados para diferentes servidores
-
-### 3. Herramientas de Diagnóstico
-- ✅ test_sse_complete.php: Prueba completa de conexión
-- ✅ Parseo de eventos en tiempo real
-- ✅ Medición de tiempos de conexión
diff --git a/INSTRUCCIONES_SERVIDOR.md b/INSTRUCCIONES_SERVIDOR.md
deleted file mode 100644
index d9f9fdc..0000000
--- a/INSTRUCCIONES_SERVIDOR.md
+++ /dev/null
@@ -1,91 +0,0 @@
-# 🚀 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
\ No newline at end of file
diff --git a/NOTIFICACIONES_SSE.md b/NOTIFICACIONES_SSE.md
deleted file mode 100644
index 58fa37c..0000000
--- a/NOTIFICACIONES_SSE.md
+++ /dev/null
@@ -1,337 +0,0 @@
-# ✅ Notificaciones por SSE - Implementación Completa
-
-## 📋 Resumen de Cambios
-
-### 🎯 Objetivo
-Eliminar el polling constante de notificaciones (cada 7-15 segundos) y reemplazarlo con push en tiempo real mediante SSE.
-
----
-
-## 🔧 Archivos Modificados
-
-### 1. **conversations.php**
-**Líneas 1058-1065**: Polling de notificaciones reducido
-```javascript
-// ANTES: setInterval(() => this.loadNotifications(), 7000);
-// AHORA: setInterval(() => this.loadNotifications(), 60000);
-```
-- ✅ Cambio de 7s → 60s (solo como backup)
-- ✅ Comentario: "SSE se encargará de las nuevas en tiempo real"
-
-**Líneas 1112-1124**: Nuevo listener SSE para notificaciones
-```javascript
-this.eventSource.addEventListener('notification', (e) => {
- const notification = JSON.parse(e.data);
- this.showNotificationToast(notification);
-});
-```
-- ✅ Escucha evento 'notification' de SSE
-- ✅ Muestra el toast automáticamente
-
----
-
-### 2. **api/sse_events.php**
-**Líneas 125-142**: Envío de notificaciones en el loop SSE
-```php
-$notifications = $db->fetchAll(
- "SELECT id, user_id, type, message, data, is_read, created_at
- FROM notifications
- WHERE is_read = 0
- ORDER BY created_at DESC
- LIMIT 10"
-);
-
-foreach ($notifications as $notification) {
- sendSSEEvent('notification', $notification);
-}
-```
-- ✅ Lee notificaciones no leídas cada 3 segundos
-- ✅ Las envía por SSE a todos los clientes conectados
-- ✅ Manejo de errores con try-catch
-
----
-
-## 📦 Archivos Nuevos
-
-### 3. **classes/NotificationHelper.php** ⭐
-Helper para crear y enviar notificaciones automáticamente por SSE.
-
-**Método principal: `create()`**
-```php
-NotificationHelper::create(
- $userId, // ID del usuario
- 'document', // Tipo: message, document, status, urgent, etc.
- 'Usuario subió 3 documentos', // Mensaje
- ['count' => 3] // Datos adicionales (opcional)
-);
-```
-
-**Método auxiliar: `pushSSE()`**
-```php
-NotificationHelper::pushSSE($notification);
-```
-
-**Características:**
-- ✅ Inserta en BD (tabla `notifications`)
-- ✅ Envía automáticamente por SSE vía `push_event.php`
-- ✅ Broadcast a todos los operadores conectados
-- ✅ Fire-and-forget (no bloquea ejecución)
-- ✅ Manejo de errores con logs
-
----
-
-### 4. **examples/notification_helper_usage.php**
-Ejemplos de uso del helper:
-- Notificación cuando usuario sube documento
-- Notificación de mensaje importante
-- Notificación de cambio de estado
-- Notificación personalizada
-
----
-
-## 📊 Comparación Antes vs Ahora
-
-| Aspecto | ANTES | AHORA | Mejora |
-|---------|-------|-------|--------|
-| **Notificaciones** | Cada 7s | Cada 60s (backup) | **88% menos** |
-| **Conversaciones** | Cada 30s | ❌ ELIMINADO (solo SSE) | **100% menos** |
-| **Latencia** | 3-7 segundos | < 1 segundo | **~85% más rápido** |
-| **Carga servidor** | ~10 req/min | ~1 req/min | **90% reducción** |
-| **Método** | HTTP GET repetido | SSE push | Push en tiempo real |
-
-### Detalles por Endpoint
-
-| Endpoint | Frecuencia ANTES | Frecuencia AHORA | Reducción |
-|----------|------------------|------------------|-----------|
-| `get_notifications.php` | Cada 7s (~8.5/min) | Cada 60s (1/min) | **88%** |
-| `get_conversations.php` | Cada 30s (2/min) | ❌ ELIMINADO | **100%** |
-| `get_messages.php` | Cada 10s (6/min) | Cada 10s (solo activa) | 0% (ya optimizado) |
-
-**Total del sistema: 90% menos peticiones HTTP** 🚀
-
----
-
-## 🚀 Cómo Usar
-
-### Crear notificación desde cualquier archivo PHP:
-
-```php
-// 1. Incluir el helper
-require_once __DIR__ . '/classes/NotificationHelper.php';
-
-// 2. Crear y enviar notificación (automáticamente por SSE)
-NotificationHelper::create(
- $userId,
- 'document',
- 'Usuario subió documento importante',
- ['file_name' => 'factura.pdf', 'file_size' => 2048000]
-);
-```
-
-### El cliente NO necesita hacer nada:
-- ✅ El evento SSE llega automáticamente
-- ✅ Se muestra el toast de notificación
-- ✅ Se reproduce sonido (si está habilitado)
-- ✅ El usuario puede hacer clic para ver detalles
-
----
-
-## 🔍 Flujo Completo
-
-```
-1. Evento ocurre (usuario sube archivo, nuevo mensaje, etc.)
- ↓
-2. Tu código llama: NotificationHelper::create(...)
- ↓
-3. Helper inserta en BD (tabla notifications)
- ↓
-4. Helper envía POST a push_event.php
- ↓
-5. push_event.php escribe evento en archivo temporal
- ↓
-6. sse_events.php lee eventos pendientes
- ↓
-7. sse_events.php envía evento 'notification' al cliente
- ↓
-8. Cliente recibe evento y muestra toast
- ↓
-9. Usuario ve notificación en < 1 segundo
-```
-
----
-
-## 🎨 Tipos de Notificaciones Soportados
-
-| Tipo | Descripción | Uso |
-|------|-------------|-----|
-| `message` | Mensaje nuevo o importante | Mensajes con palabras clave |
-| `document` | Usuario subió documento | Upload de archivos |
-| `status` | Cambio de estado | Estado del ticket/caso |
-| `urgent` | Notificación urgente | Mensajes prioritarios |
-| `info` | Información general | Avisos del sistema |
-| `warning` | Advertencia | Límites, errores menores |
-| `success` | Acción exitosa | Confirmaciones |
-| `custom` | Personalizado | Cualquier otro tipo |
-
----
-
-## 📝 Ejemplos Prácticos
-
-### Ejemplo 1: Notificar cuando usuario sube documentos
-```php
-// En api/upload_media.php
-if ($uploadSuccess && $fileType === 'document') {
- require_once __DIR__ . '/../classes/NotificationHelper.php';
-
- NotificationHelper::create(
- $userId,
- 'document',
- "Usuario {$userName} subió: {$fileName}",
- [
- 'file_name' => $fileName,
- 'file_type' => $fileType,
- 'file_size' => $fileSize
- ]
- );
-}
-```
-
-### Ejemplo 2: Notificar mensaje con palabra clave
-```php
-// En api/webhook.php
-if (preg_match('/\b(urgente|importante|ayuda)\b/i', $messageText)) {
- require_once __DIR__ . '/../classes/NotificationHelper.php';
-
- NotificationHelper::create(
- $userId,
- 'urgent',
- "Mensaje urgente de {$userName}",
- [
- 'message_preview' => substr($messageText, 0, 100),
- 'phone' => $userPhone
- ]
- );
-}
-```
-
-### Ejemplo 3: Notificar cambio de estado
-```php
-// En cualquier script de gestión
-if ($statusChanged) {
- require_once __DIR__ . '/classes/NotificationHelper.php';
-
- NotificationHelper::create(
- $userId,
- 'status',
- "Estado cambiado: {$oldStatus} → {$newStatus}",
- [
- 'old_status' => $oldStatus,
- 'new_status' => $newStatus,
- 'changed_by' => $_SESSION['admin_name'] ?? 'Sistema'
- ]
- );
-}
-```
-
----
-
-## ✅ Ventajas de Esta Implementación
-
-1. **Sin Cambios en Cliente**: El frontend sigue igual, solo agregamos un listener
-2. **Automático**: Solo llamas a `create()` y todo sucede automáticamente
-3. **Sin Bloqueos**: Fire-and-forget, no afecta el rendimiento
-4. **Escalable**: Funciona con 1 o 1000 operadores conectados
-5. **Fallback**: Si SSE falla, el polling de 60s lo cubre
-6. **Flexible**: Acepta cualquier tipo y datos personalizados
-7. **Debugging**: Logs detallados en error_log
-
----
-
-## 🧪 Cómo Probar
-
-### 1. Verificar que SSE está conectado:
-```bash
-# Abrir consola del navegador en conversations.php
-# Deberías ver: "✅ SSE conectado (🔐 autenticado)"
-```
-
-### 2. Crear notificación de prueba:
-```bash
-php -r "
-require_once 'classes/NotificationHelper.php';
-NotificationHelper::create(1, 'test', 'Prueba de notificación SSE', ['test' => true]);
-echo 'Notificación enviada\n';
-"
-```
-
-### 3. Verificar en el navegador:
-- Deberías ver el toast aparecer en < 1 segundo
-- En la consola: "🔔 Nueva notificación (SSE): ..."
-
----
-
-## 📈 Impacto en Performance
-
-### Servidor
-- **Antes**: ~8-9 peticiones GET /api/get_notifications.php por minuto por cliente
-- **Ahora**: ~1 petición por minuto (solo backup)
-- **Reducción**: 88% menos carga HTTP
-
-### Cliente
-- **Antes**: Latencia de 3-7 segundos para ver notificación
-- **Ahora**: Latencia < 1 segundo
-- **Mejora**: 6x más rápido
-
-### Base de Datos
-- **Antes**: Query cada 7s → ~8.5 queries/min
-- **Ahora**: Query cada 60s (backup) + SSE cada 3s (compartido con otros eventos)
-- **Reducción**: ~85% menos queries independientes
-
----
-
-## 🔐 Seguridad
-
-- ✅ Solo localhost puede llamar a `push_event.php` (protegido por IP)
-- ✅ Autenticación por sesión o token en SSE
-- ✅ Notificaciones solo visibles para operadores autenticados
-- ✅ Datos sensibles en campo `data` (no en mensaje)
-
----
-
-## 🐛 Troubleshooting
-
-### Notificaciones no aparecen:
-1. Verificar que SSE está conectado (consola del navegador)
-2. Revisar logs: `tail -f /var/log/apache2/error.log`
-3. Verificar que `push_event.php` es accesible
-4. Ejecutar: `php test_sse_complete.php`
-
-### Notificaciones duplicadas:
-- Normal si hay múltiples operadores conectados
-- Cada operador recibe su propia notificación
-
-### Polling aún muy frecuente:
-- Verificar `setupNotificationPolling()` en conversations.php
-- Debe ser 60000ms (60 segundos), no 15000ms
-
----
-
-## 📚 Referencias
-
-- [SSE_REALTIME_DOCS.md](SSE_REALTIME_DOCS.md) - Documentación completa del sistema SSE
-- [GUIA_SOLUCION_SSE.md](GUIA_SOLUCION_SSE.md) - Guía de solución de problemas
-- [examples/notification_helper_usage.php](examples/notification_helper_usage.php) - Ejemplos de uso
-
----
-
-## 🎉 Resumen Final
-
-Con esta implementación:
-- ✅ Eliminamos 88% de las peticiones de notificaciones
-- ✅ Notificaciones llegan 6x más rápido (< 1s vs 3-7s)
-- ✅ Sistema más escalable y eficiente
-- ✅ API simple y fácil de usar: `NotificationHelper::create()`
-- ✅ Sin cambios en el frontend (solo agregamos listener)
-- ✅ Fallback automático si SSE falla
-
-**Total de reducción en peticiones HTTP: ~85% en todo el sistema** 🚀
diff --git a/OPTIMIZACION_COMPLETA.md b/OPTIMIZACION_COMPLETA.md
deleted file mode 100644
index a347aaf..0000000
--- a/OPTIMIZACION_COMPLETA.md
+++ /dev/null
@@ -1,381 +0,0 @@
-# 🎯 Optimización Completa - Eliminación de Polling
-
-## 📊 Resumen de Cambios
-
-### ❌ ANTES: Sistema de Polling Constante
-```
-┌─────────────────────────────────────────────────────────┐
-│ Cliente (Navegador) │
-├─────────────────────────────────────────────────────────┤
-│ │
-│ ⏰ Cada 7 segundos: │
-│ GET /api/get_notifications.php │
-│ → 8.5 peticiones/minuto │
-│ │
-│ ⏰ Cada 30 segundos: │
-│ GET /api/get_conversations.php │
-│ → 2 peticiones/minuto │
-│ │
-│ ⏰ Cada 10 segundos (si hay conversación abierta): │
-│ GET /api/get_user_messages.php (delta) │
-│ → 6 peticiones/minuto │
-│ │
-│ 📊 TOTAL: ~16.5 peticiones/minuto por cliente │
-│ │
-└─────────────────────────────────────────────────────────┘
-```
-
-**Problemas:**
-- 🔴 Alta latencia (3-7 segundos para ver cambios)
-- 🔴 Carga innecesaria en servidor
-- 🔴 Peticiones redundantes (aunque no haya cambios)
-- 🔴 No escala bien con muchos usuarios
-
----
-
-### ✅ AHORA: Sistema SSE Puro (Sin Polling)
-```
-┌─────────────────────────────────────────────────────────┐
-│ Cliente (Navegador) │
-├─────────────────────────────────────────────────────────┤
-│ │
-│ 🔌 SSE Conectado (1 conexión persistente): │
-│ ← Evento 'new_message' (< 1s) │
-│ → Recarga mensajes de conversación activa │
-│ ← Evento 'new_conversation' (< 1s) │
-│ → Agrega conversación a lista │
-│ ← Evento 'notification' (< 1s) │
-│ → Muestra toast de notificación │
-│ ← Evento 'heartbeat' (cada 30s) │
-│ → Mantiene conexión viva │
-│ │
-│ 🔄 Backup (solo si SSE falla): │
-│ GET /api/get_notifications.php (cada 60s) │
-│ → 1 petición/minuto │
-│ │
-│ 📊 TOTAL: ~1 petición/minuto por cliente │
-│ (95% reducción) 🎉 │
-│ │
-└─────────────────────────────────────────────────────────┘
-```
-
-**Ventajas:**
-- 🟢 Latencia < 1 segundo (10x más rápido)
-- 🟢 95% menos carga en servidor
-- 🟢 Solo transmite cuando hay cambios reales
-- 🟢 Escala perfectamente con muchos usuarios
-- 🟢 Sin polling = Sin queries innecesarias a BD
-
----
-
-## 📁 Archivos Modificados
-
-### 1. `conversations.php`
-
-#### Cambio 1: Notificaciones (Línea 1058-1065)
-```javascript
-// ANTES
-setupNotificationPolling() {
- this.loadNotifications();
- setInterval(() => this.loadNotifications(), 7000); // ❌ Cada 7s
-}
-
-// AHORA
-setupNotificationPolling() {
- // Carga inicial de notificaciones pendientes
- // SSE se encargará de las nuevas en tiempo real
- this.loadNotifications();
-
- // Backup: verificar cada 60 segundos (solo por si SSE falla)
- setInterval(() => this.loadNotifications(), 60000); // ✅ Cada 60s
-}
-```
-
-#### Cambio 2: Conversaciones (Línea 2132-2138)
-```javascript
-// ANTES
-setupAutoRefresh() {
- // Actualizar conversaciones cada 30 segundos
- setInterval(() => {
- this.loadConversations(); // ❌ Cada 30s
- }, 30000);
-
- // ... más código
-}
-
-// AHORA
-setupAutoRefresh() {
- // NOTA: La recarga automática de conversaciones fue ELIMINADA
- // SSE se encarga de actualizar en tiempo real con eventos:
- // - 'new_message': actualiza la conversación existente
- // - 'new_conversation': agrega nueva conversación a la lista
- // Esto elimina el polling constante y reduce la carga en ~90%
-
- // ✅ Sin setInterval de conversaciones
-
- // ... resto del código (solo polling de mensajes)
-}
-```
-
-#### Cambio 3: Listeners SSE (Línea 1094-1125)
-```javascript
-// Evento: nuevo mensaje entrante
-this.eventSource.addEventListener('new_message', (e) => {
- const data = JSON.parse(e.data);
-
- // Actualizar la lista de conversaciones (sin recargar)
- this.updateConversationInList(data); // ✅ Actualización inteligente
-
- // Si es la conversación activa, recargar mensajes
- if (this.currentUserId === data.user_id) {
- this.loadMessages(this.currentUserId, false, true);
- }
-
- this.playNotificationSound();
-});
-
-// Evento: nueva conversación detectada
-this.eventSource.addEventListener('new_conversation', (e) => {
- const data = JSON.parse(e.data);
-
- // Agregar a la lista (sin recargar todo)
- this.addConversationToList(data); // ✅ Solo agrega una
-
- this.playNotificationSound();
-});
-
-// Evento: notificación del sistema
-this.eventSource.addEventListener('notification', (e) => {
- const notification = JSON.parse(e.data);
- this.showNotificationToast(notification); // ✅ Push instantáneo
-});
-```
-
----
-
-### 2. `api/sse_events.php`
-
-#### Cambio: Envío de notificaciones (Línea 125-142)
-```php
-// Verificar notificaciones no leídas cada 3 segundos
-try {
- $notifications = $db->fetchAll(
- "SELECT id, user_id, type, message, data, is_read, created_at
- FROM notifications
- WHERE is_read = 0
- ORDER BY created_at DESC
- LIMIT 10"
- );
-
- if ($notifications && count($notifications) > 0) {
- foreach ($notifications as $notification) {
- sendSSEEvent('notification', $notification); // ✅ Push automático
- }
- }
-} catch (Exception $e) {
- error_log('SSE: Error leyendo notificaciones: ' . $e->getMessage());
-}
-```
-
----
-
-### 3. `classes/NotificationHelper.php` (NUEVO)
-
-```php
-class NotificationHelper {
-
- // Crear y enviar notificación automáticamente
- public static function create($userId, $type, $message, $data = []) {
- // 1. Insertar en BD
- $notificationId = $db->insert('notifications', [...]);
-
- // 2. Enviar por SSE automáticamente
- self::pushSSE($notification);
-
- return $notificationId;
- }
-
- // Enviar notificación por SSE
- public static function pushSSE($notification) {
- // Fire-and-forget a push_event.php
- $ch = curl_init('http://localhost:8000/api/push_event.php');
- curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
- 'event' => 'notification',
- 'data' => $notification,
- 'target' => 'all'
- ]));
- curl_exec($ch);
- curl_close($ch);
- }
-}
-```
-
-**Uso simple:**
-```php
-// En cualquier archivo PHP
-NotificationHelper::create(
- $userId,
- 'document',
- 'Usuario subió 3 documentos',
- ['count' => 3]
-);
-// ¡Eso es todo! Aparece en el navegador en < 1 segundo
-```
-
----
-
-## 📈 Impacto Medible
-
-### Por Endpoint
-user_messages.php` | 6 | 0 | **100%** ⬇️ |
-| **TOTAL** | **16.5** | **~1** | **~95%** ⬇️ |
-
-### Por Usuario
-
-- **1 usuario conectado**: De 16.5 req/min → ~1 req/min
-- **10 usuarios**: De 165 req/min → ~10 req/min
-- **100 usuarios**: De 1650 req/min → ~100 req/min
-
-**Ahorro de recursos**: 15
-- **1 usuario conectado**: De 16.5 req/min → 1-2 req/min
-- **10 usuarios**: De 165 req/min → 10-20 req/min
-- **100 usuarios**: De 1650 req/min → 100-200 req/min
-
-**Ahorro de recursos**: 1450 peticiones HTTP menos por minuto con 100 usuarios 🚀
-
-### Latencia
-
-| Evento | Antes | Ahora | Mejora |
-|--------|-------|-------|--------|
-| **Nueva notificación** | 3-7s | < 1s | **6x más rápido** |
-| **Nueva conversación** | 15-30s | < 1s | **30x más rápido** |
-| **Nuevo mensaje** | 5-10s | < 1s | **10x más rápido** |
-
----
-
-## 🎯 Flujo Completo de un Evento
-
-### Ejemplo: Usuario sube un documento
-
-```
-┌─────────────────────────────────────────────────────────┐
-│ 1. Usuario sube archivo │
-│ → POST /api/upload_media.php │
-└─────────────────┬───────────────────────────────────────┘
- │
- ▼
-┌─────────────────────────────────────────────────────────┐
-│ 2. Script PHP llama a NotificationHelper │
-│ NotificationHelper::create( │
-│ $userId, 'document', │
-│ 'Usuario subió documento.pdf', │
-│ ['file_name' => 'documento.pdf'] │
-│ ) │
-└─────────────────┬───────────────────────────────────────┘
- │
- ├─► Inserta en tabla notifications
- │
- └─► POST interno a push_event.php
- │
- ▼
-┌─────────────────────────────────────────────────────────┐
-│ 3. push_event.php escribe evento en archivo temporal │
-│ /uploads/events_all.json │
-└─────────────────┬───────────────────────────────────────┘
- │
- ▼
-┌─────────────────────────────────────────────────────────┐
-│ 4. sse_events.php lee el archivo (cada 2-3s) │
-│ → sendSSEEvent('notification', [...]) │
-└─────────────────┬───────────────────────────────────────┘
- │
- │ (Conexión SSE persistente)
- │
- ▼
-┌─────────────────────────────────────────────────────────┐
-│ 5. Navegador recibe evento en < 1 segundo │
-│ eventSource.addEventListener('notification', ...) │
-│ → showNotificationToast(notification) │
-└─────────────────┬───────────────────────────────────────┘
- │
- ▼
-┌─────────────────────────────────────────────────────────┐
-│ 6. Usuario ve el toast de notificación │
-│ 🔔 "Usuario subió documento.pdf" │
-└─────────────────────────────────────────────────────────┘
-
-⏱️ Tiempo total: < 1 segundo
-📉 Peticiones HTTP: 0 (solo la subida inicial)
-```
-
----
-
-## ✅ Checklist de Optimización
-
-- [x] Notificaciones: 7s → 60s (backup)
-- [x] Conversaciones: 30s → **ELIMINADO**
-- [x] Mensajes delta: 10s → **ELIMINADO**
-- [x] SSE listener para `notification`
-- [x] SSE listener para `new_message` (recarga mensajes automáticamente)
-- [x] SSE listener para `new_conversation`
-- [x] SSE envía notificaciones automáticamente
-- [x] NotificationHelper creado
-- [x] Ejemplos de uso documentados
-- [x] Métodos `updateConversationInList()` y `addConversationToList()` funcionando
-- [x] Documentación actualizada
-
-**Todo el polling HTTP ha sido eliminado. Sistema 100% push con SSE.**
-
----
-
-## 🚀 Resultado Final
-
-### Antes vs Ahora - Visualización
-
-```
-ANTES (Polling):
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-GET notifications ⏰⏰⏰⏰⏰⏰⏰⏰ (cada 7s)
-GET conversations ⏰ ⏰ ⏰ ⏰ (cada 30s)
-GET messages ⏰⏰⏰⏰⏰⏰ (cada 10s)
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-Total: ~16 peticiones/minuto 🔴
-
-AHORA (SSE + Backup):
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-SSE push 🔌 ────────────────── (persistente)
- new_message ▶ (cuando ocurre, < 1s)
- new_conversation ▶ (cuando ocurre, < 1s)
- notification ▶ (cuando ocurre, < 1s)
- heartbeat ♥ (cada 30s)
-
-GET notifications ⏰ (cada 60s, backup)
-GET messages ⏰⏰⏰⏰⏰⏰ (cada 10s, activa)
-━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
-Total: ~1-2 peticiones/minuto 🟢
-
-📊 Reducción: 90%
-⚡ Velocidad: 6-30x más rápido
-```
-
----
-
-## 🎉 Conclusión
-
-Con esta optimización completa:
-
-✅ **95% menos peticiones HTTP** (16.5/min → ~1/min)
-✅ **10x más rápido** en tiempo de respuesta (< 1s vs 3-30s)
-✅ **Escalabilidad mejorada** exponencialmente
-✅ **Mejor experiencia de usuario** (tiempo real verdadero)
-✅ **Menor carga en servidor** (CPU, RAM, BD)
-✅ **Menor consumo de batería** en dispositivos móviles
-✅ **API simple** para crear notificaciones: `NotificationHelper::create()`
-✅ **Sin polling** = Solo eventos cuando hay cambios reales
-
-### Sistema Verdaderamente En Tiempo Real
-
-**Antes**: Polling cada X segundos (cliente pregunta: "¿hay algo nuevo?")
-**Ahora**: SSE push instantáneo (servidor dice: "¡ESTO es nuevo!")
-
-**El sistema ahora es verdaderamente en tiempo real con 0 polling innecesario.** 🚀
diff --git a/README.md b/README.md
deleted file mode 100644
index c8d3cfe..0000000
--- a/README.md
+++ /dev/null
@@ -1,177 +0,0 @@
-# WhatsApp Bot Manager 🚀
-
-
-
-
-
-
-
-
-
- Panel administrativo para gestionar bots y envíos de WhatsApp Business
-
-
-
- Desarrollado con 💚 por U-Site.app
-
-
----
-
-## 🏢 Sobre U-Site.app
-
-**U-Site.app** es una casa de desarrollo de software especializada en soluciones empresariales, automatización y aplicaciones web modernas. Creamos herramientas que simplifican procesos y potencian negocios.
-
-🌐 **Website:** [u-site.app](https://u-site.app)
-
----
-
-## ✨ Características
-
-- 📱 **Gestión de conversaciones** - Interfaz tipo WhatsApp Web
-- 📨 **Plantillas de mensajes** - Envíos masivos con templates aprobados
-- 🤖 **Bot automatizado** - Respuestas automáticas configurables
-- 🔐 **Autenticación segura** - Protección contra fuerza bruta
-- 📎 **Multimedia** - Soporte para imágenes, documentos, audio y video
-- 🔔 **Notificaciones SSE** - Tiempo real sin polling
-- 🐳 **Docker Ready** - Despliegue en contenedores
-- 🇨🇴 **Zona horaria Colombia** - Configurado para América/Bogotá
-
----
-
-## 🛠️ Requisitos
-
-| Requisito | Versión |
-|-----------|---------|
-| PHP | 8.0+ |
-| MariaDB/MySQL | 10.11+ / 8.0+ |
-| Redis | 7.0+ |
-| Docker | 24.0+ (opcional) |
-
-**Extensiones PHP requeridas:** PDO, MySQL, mbstring, curl, openssl, json, gd
-
----
-
-## 🚀 Instalación
-
-### Opción 1: Docker (Recomendado)
-
-```bash
-# Clonar repositorio
-git clone https://github.com/tu-usuario/whatsapp.git
-cd whatsapp
-
-# Desarrollo local
-docker-compose -f docker-compose.dev.yml up -d
-
-# Producción
-docker-compose -f docker-compose.prod.yml up -d
-```
-
-**URLs de acceso (desarrollo):**
-- 🌐 App: http://localhost:8080
-- 🗄️ phpMyAdmin: http://localhost:8084
-
-### Opción 2: Instalación Manual
-
-1. Clonar y configurar:
- ```bash
- git clone https://github.com/tu-usuario/whatsapp.git
- cd whatsapp
- cp .env.example .env
- ```
-
-2. Editar `.env` con tus credenciales
-
-3. Ejecutar instalador: `http://tu-servidor/install.php`
-
----
-
-## 📁 Estructura del Proyecto
-
-```
-whatsapp/
-├── api/ # Endpoints API REST
-├── assets/ # CSS, JS, imágenes
-├── classes/ # Clases PHP (Database, Services)
-├── config/ # Configuración
-├── database/ # Scripts SQL y backups
-├── docker/ # Configuración Docker (nginx, php, supervisor)
-├── services/ # Servicios (WhatsApp, Bot)
-├── uploads/ # Archivos subidos
-├── Dockerfile # Imagen principal
-├── Dockerfile.worker # Imagen para workers
-├── docker-compose.dev.yml # Desarrollo
-├── docker-compose.prod.yml # Producción
-└── deploy-vps.sh # Script de despliegue
-```
-
----
-
-## ⚙️ Configuración
-
-### Variables de Entorno (.env)
-
-```env
-# Base de datos
-DB_HOST=mysql
-DB_PORT=3306
-DB_NAME=usite_whatsapp_bot
-DB_USER=tu_usuario
-DB_PASS=tu_password
-
-# WhatsApp Business API
-WHATSAPP_TOKEN=tu_token_aqui
-WHATSAPP_PHONE_NUMBER_ID=tu_phone_id
-WEBHOOK_VERIFY_TOKEN=tu_webhook_token
-
-# Redis
-REDIS_HOST=redis
-REDIS_PORT=6379
-```
-
-### Webhook de WhatsApp
-
-Configura el webhook en Meta Developer Console:
-- **URL:** `https://tu-dominio.com/api/webhook.php`
-- **Verify Token:** El mismo que `WEBHOOK_VERIFY_TOKEN`
-
----
-
-## 🐳 Contenedores Docker
-
-| Contenedor | Descripción | Puerto |
-|------------|-------------|--------|
-| `whatsapp-app` | PHP + Nginx | 80, 443 |
-| `whatsapp-mysql` | MariaDB 10.11 | 3306 |
-| `whatsapp-redis` | Redis 7 | 6379 |
-| `whatsapp-worker` | Procesos background | - |
-
----
-
-## 📖 Documentación Adicional
-
-- [Guía de Instalación](INSTALACION.md)
-- [Guía de Despliegue](DEPLOYMENT_GUIDE.md)
-- [Configuración SSE](README_SSE.md)
-- [Solución de Problemas](SOLUCION_HOSTING.md)
-
----
-
-## 🔒 Seguridad
-
-- Los directorios `dev/`, `tests/`, `scripts/` contienen herramientas de desarrollo
-- **NO exponer** estos directorios en producción
-- Proteger con `.htaccess` o configuración de nginx
-- Rotar credenciales regularmente
-
----
-
-## 📄 Licencia
-
-Propietario - © 2026 [U-Site.app](https://u-site.app)
-
----
-
-
- Desarrollado con 💚 por U-site.app
-
diff --git a/README_LABORATORIO.md b/README_LABORATORIO.md
deleted file mode 100644
index e00cda2..0000000
--- a/README_LABORATORIO.md
+++ /dev/null
@@ -1,298 +0,0 @@
-# 🏥 Sistema de Chatbot WhatsApp - Laboratorio Ximena Caicedo
-
-## 📋 Descripción
-
-Sistema completo de chatbot para WhatsApp diseñado específicamente para el Laboratorio Ximena Caicedo en Cúcuta, Colombia. Incluye flujos conversacionales inteligentes, detección de horarios, gestión de estados, respuestas automáticas y mucho más.
-
-## ✨ Características Principales
-
-### 🤖 Bot Inteligente
-- ✅ Procesamiento de Lenguaje Natural (NLP) básico
-- ✅ Detección de palabras clave y sinónimos
-- ✅ Respuestas automáticas contextuales
-- ✅ Detección de sentimientos y frustración
-- ✅ Escalamiento automático a asesores humanos
-
-### 📅 Gestión de Horarios
-- ✅ Detección automática de horario de atención
-- ✅ Mensajes diferentes dentro/fuera de horario
-- ✅ Configuración por día de la semana
-- ✅ Soporte para horarios partidos (7-12, 14-17)
-
-### 🗂️ Sistema de Menús
-- ✅ Menú principal con 5 opciones
-- ✅ Submenú de exámenes con información detallada
-- ✅ Navegación intuitiva con números
-- ✅ Opción de volver al menú anterior
-
-### 🔄 Gestión de Estados
-- ✅ Tracking del estado de cada conversación
-- ✅ Flujos de agendamiento paso a paso
-- ✅ Validación de datos del paciente
-- ✅ Timeout automático de estados inactivos
-
-### 💬 Respuestas Automáticas
-- ✅ Más de 15 triggers configurados
-- ✅ Respuestas por palabras clave
-- ✅ Información de precios, horarios, ubicación
-- ✅ Instrucciones para exámenes específicos
-
-### 📸 Procesamiento de Multimedia
-- ✅ Recepción de órdenes médicas
-- ✅ Fotos de documentos de identidad
-- ✅ Validación de imágenes
-- ✅ Almacenamiento seguro
-
-## 🚀 Instalación
-
-### Requisitos Previos
-- PHP 8.0 o superior
-- MySQL 5.7 o superior
-- Servidor web (Apache/Nginx)
-- Cuenta de WhatsApp Business API
-
-### Paso 1: Clonar/Descargar Archivos
-Asegúrate de tener todos los archivos en tu servidor.
-
-### Paso 2: Configurar Base de Datos
-1. Crea una base de datos MySQL
-2. Importa las tablas base si no existen:
-```sql
--- Ejecutar los scripts SQL en este orden:
-1. create_tables.sql (tablas base)
-2. migrations/add_conversation_states.sql (tabla de estados)
-3. setup_laboratorio_ximena.sql (datos del laboratorio)
-```
-
-### Paso 3: Configurar Credenciales
-Edita `config/config.php` con tus credenciales:
-```php
-define('DB_HOST', 'localhost');
-define('DB_NAME', 'whatsapp_bot');
-define('DB_USER', 'tu_usuario');
-define('DB_PASS', 'tu_password');
-```
-
-### Paso 4: Ejecutar Instalación Automática
-Visita en tu navegador:
-```
-http://tu-dominio.com/install_laboratorio.php
-```
-
-Este script:
-- ✅ Verifica la conexión a la base de datos
-- ✅ Crea la tabla de estados
-- ✅ Importa menús y respuestas automáticas
-- ✅ Verifica los servicios del sistema
-- ✅ Muestra estadísticas finales
-
-### Paso 5: Configurar WhatsApp API
-1. Ve al panel de administración: `http://tu-dominio.com/index.php`
-2. Navega a la pestaña "Configuración"
-3. Ingresa:
- - Token de Acceso de WhatsApp Business API
- - Phone Number ID
- - Webhook Verify Token
- - API URL (por defecto: https://graph.facebook.com/v22.0/)
-
-### Paso 6: Configurar Webhook
-1. En Meta for Developers, configura el webhook:
- - URL: `https://tu-dominio.com/api/webhook.php`
- - Verify Token: El mismo que configuraste en el paso anterior
- - Suscripciones: `conversations`, `message_status`
-
-## 📚 Estructura de Archivos
-
-```
-whatsapp/
-├── services/
-│ ├── BusinessHoursService.php # Gestión de horarios
-│ ├── ConversationStateService.php # Estados de conversación
-│ ├── NLPService.php # Procesamiento de lenguaje
-│ ├── MenuService.php # Gestión de menús
-│ ├── PatientDataValidator.php # Validación de datos
-│ ├── BotServiceLab.php # Servicio principal del bot
-│ └── WhatsAppService.php # Integración con WhatsApp API
-├── api/
-│ └── webhook.php # Recepción de mensajes
-├── migrations/
-│ └── add_conversation_states.sql # Migración de tabla de estados
-├── setup_laboratorio_ximena.sql # Setup completo del laboratorio
-├── install_laboratorio.php # Instalador automático
-└── README_LABORATORIO.md # Este archivo
-```
-
-## 🎯 Flujos Implementados
-
-### 1. Menú Principal
-```
-1️⃣ Agendar toma a domicilio
-2️⃣ Consultar resultados
-3️⃣ Información sobre exámenes
-4️⃣ Ubicación y horarios
-5️⃣ Hablar con un asesor
-```
-
-### 2. Submenú de Exámenes
-```
-1️⃣ Pruebas de embarazo
-2️⃣ Prueba de paternidad
-3️⃣ Helicobacter pylori (en aliento)
-4️⃣ Orina de 24 horas
-5️⃣ Solicitar cotización
-0️⃣ Volver al menú principal
-```
-
-### 3. Palabras Clave Automáticas
-- **"precio", "costo", "cuánto"** → Información de precios
-- **"resultado", "resultados"** → Instrucciones para descargar
-- **"horario", "cuando"** → Horarios de atención
-- **"domicilio", "casa"** → Servicio a domicilio
-- **"embarazo"** → Pruebas de embarazo
-- **"paternidad", "ADN"** → Prueba de paternidad
-- **"ubicación", "donde"** → Dirección y mapa
-- **"asesor", "ayuda", "persona"** → Transferir a humano
-- **"gracias"** → Mensaje de despedida
-- **"menú", "inicio"** → Mostrar menú principal
-
-## ⚙️ Configuración Avanzada
-
-### Horarios de Atención
-Edita en la base de datos o desde el panel:
-```sql
-UPDATE system_config
-SET config_value = '7:00-12:00,14:00-17:00'
-WHERE config_key = 'business_hours_weekday';
-
-UPDATE system_config
-SET config_value = '8:00-11:00'
-WHERE config_key = 'business_hours_saturday';
-```
-
-### Agregar Nueva Respuesta Automática
-Desde el panel de administración:
-1. Ve a "Respuestas Automáticas"
-2. Click en "Nueva Respuesta"
-3. Configura:
- - Tipo de trigger (keyword, contains, exact, etc.)
- - Palabras clave (separadas por comas)
- - Texto de respuesta
- - Prioridad (1-100, mayor = más prioridad)
-
-### Personalizar Menús
-1. Ve a "Menús Configurados"
-2. Click en el menú que quieres editar
-3. Modifica el mensaje de bienvenida o las opciones
-4. Guarda los cambios
-
-## 🔧 Personalización
-
-### Cambiar Nombre del Laboratorio
-Edita `setup_laboratorio_ximena.sql` antes de instalar, reemplaza:
-```sql
-'Laboratorio Ximena Caicedo' → 'Tu Laboratorio'
-```
-
-### Modificar Valores de Exámenes
-Edita los mensajes en la tabla `menu_options` o `autoresponses`:
-```sql
-UPDATE menu_options
-SET response_text = REPLACE(response_text, '$28.000', '$30.000')
-WHERE response_text LIKE '%$28.000%';
-```
-
-### Agregar Nuevo Examen al Submenú
-```sql
-INSERT INTO menu_options (menu_id, option_key, option_order, option_text, response_type, response_text, is_active)
-VALUES
-(
- (SELECT id FROM menus WHERE menu_key = 'lab_menu_examenes'),
- '6',
- 6,
- 'Nuevo Examen',
- 'text',
- 'Información del nuevo examen...',
- 1
-);
-```
-
-## 📊 Monitoreo y Logs
-
-### Ver Conversaciones Recientes
-```sql
-SELECT phone_number, message_text, direction, created_at
-FROM conversations
-ORDER BY created_at DESC
-LIMIT 50;
-```
-
-### Ver Estados Activos
-```sql
-SELECT phone_number, state, state_data, updated_at
-FROM user_states
-WHERE updated_at > DATE_SUB(NOW(), INTERVAL 1 HOUR);
-```
-
-### Ver Logs de Webhook
-```sql
-SELECT * FROM webhook_logs
-ORDER BY created_at DESC
-LIMIT 20;
-```
-
-## 🐛 Solución de Problemas
-
-### El bot no responde
-1. Verifica que el webhook esté configurado correctamente
-2. Revisa los logs de webhook: `SELECT * FROM webhook_logs ORDER BY created_at DESC LIMIT 1`
-3. Verifica las credenciales de WhatsApp API en "Configuración"
-
-### Mensajes fuera de horario no funcionan
-1. Verifica la zona horaria del servidor: `date_default_timezone_get()`
-2. Revisa la configuración de horarios en `system_config`
-3. Asegúrate de que `BusinessHoursService.php` existe
-
-### Estados no se guardan
-1. Verifica que la tabla `user_states` existe:
-```sql
-SHOW TABLES LIKE 'user_states';
-```
-2. Si no existe, ejecuta: `migrations/add_conversation_states.sql`
-
-### NLP no detecta palabras clave
-1. Revisa que las palabras estén en minúsculas en la BD
-2. Verifica que `autoresponses` tiene registros activos:
-```sql
-SELECT COUNT(*) FROM autoresponses WHERE is_active = 1;
-```
-3. Aumenta la prioridad de la respuesta automática
-
-## 📞 Soporte
-
-Para soporte técnico o consultas:
-- 📧 Email: soporte@ejemplo.com
-- 📱 WhatsApp: +57 XXX XXX XXXX
-- 💻 GitHub: [repositorio]
-
-## 📝 Changelog
-
-### Versión 1.0.0 (13 de enero de 2026)
-- ✅ Implementación completa del sistema
-- ✅ Menús del laboratorio
-- ✅ 15+ respuestas automáticas
-- ✅ Detección de horarios
-- ✅ Gestión de estados
-- ✅ NLP básico
-- ✅ Validación de datos de pacientes
-- ✅ Instalador automático
-
-## 📜 Licencia
-
-Este sistema fue desarrollado específicamente para Laboratorio Ximena Caicedo.
-Todos los derechos reservados © 2026
-
----
-
-**¡Sistema listo para producción! 🚀**
-
-Para comenzar, ejecuta `install_laboratorio.php` y sigue las instrucciones.
diff --git a/README_SETUP.md b/README_SETUP.md
deleted file mode 100644
index a8c7d37..0000000
--- a/README_SETUP.md
+++ /dev/null
@@ -1,230 +0,0 @@
-# 🚀 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: `conversations`
-
-### 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
\ No newline at end of file
diff --git a/README_SSE.md b/README_SSE.md
deleted file mode 100644
index 8996da9..0000000
--- a/README_SSE.md
+++ /dev/null
@@ -1,206 +0,0 @@
-# 🚀 Sistema de Notificaciones en Tiempo Real - Guía Rápida
-
-## ✨ ¿Qué se implementó?
-
-Reemplazamos el **polling constante** (peticiones cada 7-8 segundos) por **Server-Sent Events (SSE)**, que empuja notificaciones en tiempo real cuando llega un mensaje de WhatsApp.
-
-## 📊 Comparación
-
-| Aspecto | Antes (Polling) | Ahora (SSE) |
-|---------|----------------|-------------|
-| **Latencia** | 7-15 segundos | < 1 segundo |
-| **Peticiones/min** | 15-20 | 2-3 |
-| **Carga servidor** | Alta | Baja (85% menos) |
-| **Actualización UI** | Recarga todo | Solo conversación afectada |
-| **Experiencia** | Lenta | Instantánea |
-
-## 📁 Archivos nuevos
-
-```
-api/
-├── sse_events.php # Endpoint SSE (conexión persistente)
-└── push_event.php # API interna para empujar eventos
-
-SSE_REALTIME_DOCS.md # Documentación completa
-test_sse.sh # Script de prueba
-README_SSE.md # Este archivo
-```
-
-## 🔧 Archivos modificados
-
-- ✅ `api/webhook.php` - Empuja evento cuando llega mensaje
-- ✅ `conversations.php` - Se conecta a SSE y actualiza UI incrementalmente
-
-## 🎯 Cómo funciona
-
-```
-1. WhatsApp envía mensaje
- ↓
-2. webhook.php recibe y guarda en BD
- ↓
-3. webhook.php empuja evento → push_event.php
- ↓
-4. push_event.php escribe en archivo temporal
- ↓
-5. sse_events.php lee archivo y envía al navegador
- ↓
-6. conversations.php recibe evento y actualiza UI
- ✅ Solo actualiza la conversación específica (NO recarga todo)
-```
-
-## 🚀 Cómo probar
-
-### 1. Ejecutar script de prueba
-```bash
-cd /Users/lizandro/Documents/GitHub/whatsapp
-./test_sse.sh
-```
-
-Deberías ver:
-```
-🧪 Testing SSE Real-time System
-✓ SSE endpoint responde
-✓ Push endpoint funciona
-✓ Evento enviado exitosamente
-```
-
-### 2. Probar en navegador
-
-1. Abre `conversations.php`
-2. Abre **DevTools → Console** (F12)
-3. Deberías ver:
- ```
- Conectando a SSE para eventos en tiempo real...
- ✅ SSE conectado: {timestamp: 1738000000}
- ```
-
-### 3. Enviar mensaje de prueba desde WhatsApp
-
-Cuando llegue un mensaje:
-```javascript
-📨 Nuevo mensaje (SSE): {user_id: 123, message: "Hola", ...}
-Recargando mensajes de conversación activa...
-```
-
-**Resultado:** El mensaje aparece en < 1 segundo, sin recargar toda la página.
-
-## 🔍 Verificar que funciona
-
-### Método 1: Ver eventos en tiempo real
-```bash
-# Terminal 1: Ver eventos guardados
-watch -n 1 cat /Users/lizandro/Documents/GitHub/whatsapp/uploads/events_global.json
-
-# Terminal 2: Conectar manualmente a SSE (con token)
-curl -N http://localhost/api/sse_events.php?token=demo_token
-```
-
-### Método 2: Simular mensaje nuevo
-```bash
-curl -X POST http://localhost/api/push_event.php \
- -H "Content-Type: application/json" \
- -H "X-Push-Token: internal_push_secret_2026" \
- -d '{
- "event_type": "new_message",
- "data": {
- "user_id": 123,
- "phone_number": "573168950803",
- "message": "Hola desde terminal",
- "timestamp": "'$(date '+%Y-%m-%d %H:%M:%S')'"
- }
- }'
-```
-
-Si tienes `conversations.php` abierto, verás el mensaje aparecer instantáneamente.
-
-## 🐛 Solución de problemas
-
-### ❌ "Usuario no autenticado" en SSE
-
-**Causa:** EventSource no envía cookies de sesión automáticamente en algunos navegadores
-
-**Solución aplicada:**
-- ✅ SSE ahora acepta token en URL: `api/sse_events.php?token=demo_token`
-- ✅ Modo global: Si no hay autenticación específica, usa eventos broadcast
-- ✅ Fallback automático: El sistema sigue funcionando
-
-**No requiere acción manual** - Ya está corregido en el código.
-
-### ❌ "SSE no conecta"
-
-**Causa:** Sesión no iniciada o permisos
-
-**Solución:**
-```bash
-# Verificar que estás logueado en conversations.php
-# Verificar permisos del directorio uploads
-chmod 755 /Users/lizandro/Documents/GitHub/whatsapp/uploads
-```
-
-### ❌ "Eventos no llegan"
-
-**Causa:** Archivo temporal no se crea
-
-**Solución:**
-```bash
-# Verificar que se pueden crear archivos
-touch /Users/lizandro/Documents/GitHub/whatsapp/uploads/test.txt
-# Si falla, ajustar permisos
-```
-
-### ❌ "Push falla desde webhook"
-
-**Causa:** Token incorrecto o URL no resuelve
-
-**Solución:**
-```php
-// En webhook.php, verificar que la URL sea correcta:
-$pushUrl = 'http://127.0.0.1' . dirname($_SERVER['SCRIPT_NAME']) . '/push_event.php';
-
-// O cambiar a localhost:
-$pushUrl = 'http://localhost/api/push_event.php';
-```
-
-## ⚙️ Configuración
-
-### Cambiar token de seguridad
-```php
-// En push_event.php y webhook.php
-$validToken = 'tu_token_super_secreto_aqui';
-```
-
-### Ajustar frecuencia de revisión
-```php
-// En sse_events.php, línea ~95
-sleep(2); // Cambiar a 1-5 segundos según necesidad
-```
-
-### Deshabilitar polling (opcional)
-```javascript
-// En conversations.php, comentar:
-// setInterval(() => this.loadNotifications(), 15000);
-```
-
-## 📈 Beneficios inmediatos
-
-1. ✅ **Notificaciones instantáneas** - Mensajes aparecen en < 1 segundo
-2. ✅ **Menos carga** - 85% menos peticiones HTTP
-3. ✅ **UI más fluida** - No recarga toda la página
-4. ✅ **Mejor UX** - Experiencia similar a WhatsApp Web
-5. ✅ **Compatible** - Si SSE falla, polling sigue funcionando
-
-## 🎉 ¡Listo!
-
-El sistema está funcionando. Cada vez que llegue un mensaje de WhatsApp:
-- Se guarda en BD (como antes)
-- Se empuja evento en tiempo real
-- Aparece instantáneamente en `conversations.php`
-- **Solo se actualiza la conversación específica**
-
-No más esperas de 7-15 segundos ni recargas completas.
-
----
-
-**Documentación completa:** Ver [SSE_REALTIME_DOCS.md](SSE_REALTIME_DOCS.md)
-
-**Soporte:** Si algo no funciona, ejecuta `./test_sse.sh` y revisa el output.
diff --git a/REPORTE_SISTEMA_COMPLETO.md b/REPORTE_SISTEMA_COMPLETO.md
deleted file mode 100644
index f36ffe1..0000000
--- a/REPORTE_SISTEMA_COMPLETO.md
+++ /dev/null
@@ -1,430 +0,0 @@
-# 📋 REPORTE COMPLETO DEL SISTEMA WHATSAPP BOT
-**Fecha de Análisis:** 13 de enero de 2026
-**Estado:** ✅ SISTEMA COMPLETO Y FUNCIONAL
-
----
-
-## 🎯 RESUMEN EJECUTIVO
-
-El sistema WhatsApp Bot está **100% funcional** con todas las características principales implementadas:
-
-✅ **Autenticación y Seguridad**
-✅ **Gestión de Usuarios**
-✅ **Sistema de Mensajería**
-✅ **Multimedia (Imágenes, Videos, Audios, Documentos)**
-✅ **Grabación de Audio en Tiempo Real**
-✅ **Menús Interactivos**
-✅ **Plantillas de WhatsApp**
-✅ **Auto-respuestas**
-✅ **Logs y Diagnósticos**
-✅ **APIs REST Completas**
-
----
-
-## 📊 ESTADÍSTICAS DEL PROYECTO
-
-- **Total archivos PHP:** 130+
-- **APIs REST:** 42 endpoints
-- **Clases:** Database.php (Singleton Pattern)
-- **Servicios:** WhatsAppService (11 métodos de envío)
-- **Interfaces:** 3 (index.php, conversations.php, chat_window.php)
-
----
-
-## 🔒 1. SEGURIDAD Y AUTENTICACIÓN
-
-### ✅ IMPLEMENTADO:
-- **Sistema de Login:** `login.php` con protección brute force
-- **Gestión de Sesiones:** Timeout 30 minutos
-- **Autenticación de APIs:** `requireAuthentication()` en todos los endpoints
-- **Migración a BD:** Sistema `admin_users` tabla
-- **Password Recovery:** `password_recovery.php`
-- **Gestión Admins:** `gestion_admin_unificada.php`
-
-### 📁 Archivos Clave:
-- ✅ `login.php` - Sistema de autenticación
-- ✅ `logout.php` - Cierre de sesión
-- ✅ `config/config.php` - Funciones de seguridad
-- ✅ `auth_protect.php` - Protección de páginas
-- ✅ `migrate_to_database_auth.php` - Migración
-
-### 🔑 Credenciales:
-```
-Usuario: admin
-Password: 2fa904eee46f
-```
-
----
-
-## 💬 2. SISTEMA DE MENSAJERÍA
-
-### ✅ WhatsAppService.php - Métodos Disponibles:
-
-#### Mensajes de Texto:
-```php
-sendTextMessage($to, $message)
-```
-
-#### Plantillas:
-```php
-sendTemplateMessage($to, $templateName, $language, $bodyParameters, $headerParameters)
-```
-
-#### Mensajes Interactivos:
-```php
-sendInteractiveMessage($to, $bodyText, $buttons, $header, $footer)
-sendListMessage($to, $bodyText, $buttonText, $sections, $header, $footer)
-```
-
-#### Multimedia:
-```php
-sendImageMessage($to, $imageUrl, $caption)
-sendVideoMessage($to, $videoUrl, $caption)
-sendAudioMessage($to, $audioUrl)
-sendDocumentMessage($to, $documentUrl, $filename, $caption)
-uploadMedia($filePath, $mimeType)
-downloadMedia($mediaId)
-```
-
-### 📂 Formatos Soportados:
-- **Imágenes:** JPG, PNG, WEBP (máx 5MB)
-- **Videos:** MP4 (máx 16MB)
-- **Audios:** MP3, AAC, OGG, WEBM (máx 16MB)
-- **Documentos:** PDF, DOC, DOCX, XLS, XLSX (máx 100MB)
-
----
-
-## 🎤 3. FUNCIONALIDAD MULTIMEDIA
-
-### ✅ Características Implementadas:
-
-#### En conversations.php:
-- ✅ Botón adjuntar archivos (📎)
-- ✅ Preview de multimedia antes de enviar
-- ✅ Caption opcional para imágenes/videos/documentos
-- ✅ Visualización inline de multimedia
-- ✅ Validación de tamaños y tipos
-
-#### En chat_window.php:
-- ✅ Todas las características de conversations.php
-- ✅ **Grabación de audio en tiempo real**
-- ✅ Botón de micrófono (🎤)
-- ✅ Indicador visual de grabación
-- ✅ Timer de grabación
-- ✅ Preview de audio antes de enviar
-
-### 📁 APIs Multimedia:
-- ✅ `api/upload_media.php` - Subir archivos
-- ✅ `api/send_media_message.php` - Enviar multimedia
-- ✅ Tabla `media_files` en base de datos
-
----
-
-## 🗄️ 4. BASE DE DATOS
-
-### ✅ Tablas Principales:
-
-1. **users** - Gestión de usuarios WhatsApp
-2. **conversations** - Historial de mensajes
-3. **menus** - Menús interactivos
-4. **menu_options** - Opciones de menús
-5. **autoresponses** - Respuestas automáticas
-6. **templates** - Plantillas de WhatsApp
-7. **system_config** - Configuración del sistema
-8. **admin_users** - Administradores del sistema
-9. **webhook_logs** - Logs de webhook
-10. **media_files** - Archivos multimedia ⭐ NUEVA
-
-### 📊 Clase Database:
-- ✅ Patrón Singleton
-- ✅ Métodos: `query()`, `fetch()`, `fetchAll()`, `insert()`, `update()`, `delete()`
-- ✅ Manejo de errores y transacciones
-
----
-
-## 🔌 5. APIs REST (42 ENDPOINTS)
-
-### 📤 Mensajería:
-- ✅ `send_message.php` - Enviar texto
-- ✅ `send_template.php` - Enviar plantilla
-- ✅ `send_media_message.php` - Enviar multimedia
-
-### 👥 Usuarios:
-- ✅ `get_users.php` - Listar usuarios
-- ✅ `update_user.php` - Actualizar usuario
-- ✅ `delete_user.php` - Eliminar usuario
-- ✅ `export_users.php` - Exportar CSV
-
-### 💬 Conversaciones:
-- ✅ `get_conversations.php` - Lista de conversaciones
-- ✅ `get_conversation_list.php` - Lista simplificada
-- ✅ `get_conversation_detail.php` - Detalle de conversación
-- ✅ `get_conversation_stats.php` - Estadísticas
-- ✅ `get_recent_conversations.php` - Mensajes recientes
-
-### 🍽️ Menús:
-- ✅ `get_menus.php` - Listar menús
-- ✅ `save_menu.php` - Guardar menú
-- ✅ `delete_menu.php` - Eliminar menú
-- ✅ `save_menu_option.php` - Guardar opción
-- ✅ `update_menu_option.php` - Actualizar opción
-- ✅ `delete_menu_option.php` - Eliminar opción
-
-### 🤖 Auto-respuestas:
-- ✅ `get_autoresponses.php` - Listar
-- ✅ `save_autoresponse.php` - Guardar
-- ✅ `delete_autoresponse.php` - Eliminar
-
-### 📋 Plantillas:
-- ✅ `get_templates.php` - Listar plantillas
-- ✅ `delete_template.php` - Eliminar plantilla
-
-### 📊 Dashboard:
-- ✅ `get_stats.php` - Estadísticas generales
-- ✅ `get_chart_data.php` - Datos para gráficos
-
-### 📁 Multimedia:
-- ✅ `upload_media.php` - Subir archivos
-- ✅ `send_media_message.php` - Enviar multimedia
-
-### ⚙️ Configuración:
-- ✅ `get_system_config.php` - Obtener configuración
-- ✅ `save_system_config.php` - Guardar configuración
-- ✅ `manage_config.php` - Gestión completa
-
-### 📝 Logs:
-- ✅ `get_logs.php` - Obtener logs
-- ✅ `clear_logs.php` - Limpiar logs
-
-### 🔧 Webhook:
-- ✅ `webhook.php` - Recibir mensajes de WhatsApp
-
----
-
-## 🖥️ 6. INTERFACES DE USUARIO
-
-### ✅ Panel Principal - index.php
-**Características:**
-- Dashboard con estadísticas en tiempo real
-- Gestión de usuarios
-- Plantillas de WhatsApp
-- Menús interactivos
-- Auto-respuestas
-- Logs del sistema
-- Configuración de WhatsApp
-- Gráficos de actividad
-
-### ✅ Conversaciones - conversations.php
-**Características:**
-- Lista de conversaciones con búsqueda
-- Chat en tiempo real
-- Envío de texto y multimedia
-- Preview de archivos
-- Caption para multimedia
-- Visualización inline
-- Botón adjuntar (📎)
-
-### ✅ Chat Window - chat_window.php
-**Características:**
-- Ventana de chat individual
-- Todas las funciones de conversations.php
-- **Grabación de audio (🎤)**
-- Indicador de grabación con timer
-- Preview de audio
-
-### ✅ Sistema de Login - login.php
-**Características:**
-- Diseño moderno con gradientes
-- Protección brute force
-- Recuperación de contraseña
-- Mensajes informativos
-- Badges de seguridad
-
----
-
-## 🔧 7. HERRAMIENTAS DE DIAGNÓSTICO
-
-### ✅ Instalación:
-- `instalacion.html` - Wizard de instalación
-- `install.php`, `install_simple.php`, `install_ultra_basic.php`
-- `instalar_bd.php` - Crear tablas
-
-### ✅ Diagnóstico:
-- `test_webhook.php` - Verificar webhook (7 tests)
-- `diagnostico_whatsapp.php` - Estado de WhatsApp API
-- `diagnostico_api.php` - Verificar APIs
-- `diagnostico_login.php` - Verificar autenticación
-- `verificar_sistema.php` - Estado general
-- `system_validator.php` - Validación completa
-
-### ✅ Tests:
-- `test.php` - Suite de tests
-- `run_tests.php` - Ejecutar todos los tests
-- `test_api.php` - Tests de API
-- `test_whatsapp_completo.php` - Tests WhatsApp
-
----
-
-## 📚 8. DOCUMENTACIÓN
-
-### ✅ Guías Disponibles:
-- `README.md` - Documentación principal
-- `DEPLOYMENT_GUIDE.md` - Guía de despliegue
-- `INSTALACION.md` - Guía de instalación
-- `INSTRUCCIONES_SERVIDOR.md` - Configuración servidor
-- `GUIA_HESTIACP.md` - Guía para HestiaCP
-- `SOLUCION_SEGURIDAD.md` - Soluciones de seguridad
-- `SOLUCION_TABLA_NO_EXISTE.md` - Soluciones BD
-- `GUIA_ERROR_PLANTILLAS.md` - Troubleshooting plantillas
-
----
-
-## ⚠️ 9. ÁREAS QUE NECESITAN ATENCIÓN
-
-### 🟡 RECOMENDACIONES:
-
-#### 1. **Limpieza de Archivos de Debug**
-Eliminar antes de producción:
-- `debug_*.php` (8 archivos)
-- `diagnostico_*.php` (7 archivos)
-- `test_*.php` (20+ archivos)
-- `verificar_*.php` (4 archivos)
-- `fix_*.php` (4 archivos)
-
-#### 2. **Archivos de Instalación**
-Eliminar después de instalar:
-- `install*.php` (5 archivos)
-- `instalacion.html`
-- `gestion_admin_unificada.php` (solo para setup)
-- `migrate_to_database_auth.php` (ya migrado)
-
-#### 3. **Seguridad**
-- ✅ Cambiar credenciales por defecto
-- ✅ Eliminar modo debug de APIs
-- ✅ Activar HTTPS en producción
-- ✅ Revisar permisos de directorios
-- ✅ Configurar .htaccess para proteger config/
-
-#### 4. **Optimizaciones**
-- Agregar cache para configuración
-- Implementar rate limiting en APIs
-- Comprimir archivos CSS/JS
-- Agregar logging más detallado
-
-#### 5. **Monitoreo**
-- Sistema de alertas para errores
-- Dashboard de métricas en tiempo real
-- Backup automático de BD
-
----
-
-## ✅ 10. CHECKLIST DE PRODUCCIÓN
-
-### Antes de Deploy:
-
-- [ ] Cambiar credenciales de admin
-- [ ] Actualizar tokens de WhatsApp
-- [ ] Configurar webhook URL en Facebook
-- [ ] Eliminar archivos de debug/test
-- [ ] Configurar HTTPS
-- [ ] Establecer permisos correctos (755 directorios, 644 archivos)
-- [ ] Crear .htaccess de seguridad
-- [ ] Configurar backups automáticos
-- [ ] Verificar logs de errores
-- [ ] Probar envío de mensajes
-- [ ] Probar recepción de mensajes
-- [ ] Probar multimedia
-- [ ] Verificar grabación de audio
-
----
-
-## 🎉 11. CARACTERÍSTICAS DESTACADAS
-
-### ⭐ LO MEJOR DEL SISTEMA:
-
-1. **Grabación de Audio en Tiempo Real** 🎤
- - Primera implementación en un bot de WhatsApp
- - Interfaz tipo WhatsApp Web
- - Preview antes de enviar
- - Timer de grabación
-
-2. **Multimedia Completo** 📷
- - Subida de archivos
- - Preview integrado
- - Caption opcional
- - Visualización inline
-
-3. **Seguridad Robusta** 🔒
- - Protección brute force
- - Sesiones seguras
- - Autenticación en todas las APIs
- - Migración a BD para admins
-
-4. **Interfaces Modernas** 🎨
- - Diseño tipo WhatsApp
- - Responsive
- - Animaciones suaves
- - UX intuitiva
-
-5. **APIs REST Completas** 🔌
- - 42 endpoints
- - Documentación clara
- - Manejo de errores
- - Debug mode
-
----
-
-## 🚀 12. PRÓXIMOS PASOS RECOMENDADOS
-
-### Características Opcionales:
-
-1. **Mensajes Programados**
- - Envío diferido
- - Campañas automáticas
- - Recordatorios
-
-2. **Analytics Avanzado**
- - Tasa de respuesta
- - Tiempos de respuesta
- - Conversiones
-
-3. **Integración CRM**
- - Sincronización con Salesforce
- - Webhooks salientes
- - API de terceros
-
-4. **IA y Chatbots**
- - Respuestas inteligentes
- - NLP para intenciones
- - Aprendizaje automático
-
-5. **Multi-agente**
- - Asignación de conversaciones
- - Chat en grupo interno
- - Notas privadas
-
----
-
-## 💯 CONCLUSIÓN
-
-**El sistema está 100% funcional y listo para producción.**
-
-### Puntuación por Módulo:
-- ✅ Autenticación: **10/10**
-- ✅ Mensajería: **10/10**
-- ✅ Multimedia: **10/10**
-- ✅ APIs: **10/10**
-- ✅ Interfaces: **10/10**
-- ✅ Base de Datos: **10/10**
-- ✅ Seguridad: **9/10** (eliminar archivos de debug)
-- ✅ Documentación: **10/10**
-
-### **PUNTUACIÓN TOTAL: 99/100** ⭐⭐⭐⭐⭐
-
-**Único pendiente:** Limpieza de archivos de desarrollo antes de producción.
-
----
-
-**Desarrollado por U-Site.app**
-**Fecha:** 13 de enero de 2026
-**Versión:** 2.0 - Sistema Completo con Multimedia
diff --git a/SOLUCION_ERROR_CONFIG.md b/SOLUCION_ERROR_CONFIG.md
deleted file mode 100644
index bf053de..0000000
--- a/SOLUCION_ERROR_CONFIG.md
+++ /dev/null
@@ -1,142 +0,0 @@
-# 🚨 SOLUCIÓN: "Error probando la nueva configuración"
-
-## 📋 El Problema
-Te aparece este error durante la instalación:
-```
-❌ Error probando la nueva configuración: Error en la consulta a la base de datos
-```
-
-## 🔍 Posibles Causas
-
-### 1. **Tabla 'users' no existe aún**
-- El schema no se aplicó correctamente
-- Hay errores en el SQL
-
-### 2. **Archivo config.php no se actualizó**
-- Permisos de escritura insuficientes
-- Error al escribir el archivo
-
-### 3. **Credenciales incorrectas**
-- Host, usuario o contraseña incorrectos
-- Base de datos no existe
-
----
-
-## ✅ SOLUCIONES (En orden de dificultad)
-
-### 🚀 OPCIÓN 1: Usa el Instalador Simplificado
-**¡La más fácil!** Creé un instalador específicamente para estos problemas:
-
-1. Ve a: `install_simple.php`
-2. Completa tus datos de BD
-3. ¡Listo! Solo instala lo esencial
-
-### 🔧 OPCIÓN 2: Diagnóstico Completo
-Identifica exactamente qué está mal:
-
-1. Ve a: `debug_install.php`
-2. Ingresa tus credenciales de BD
-3. Ejecuta el diagnóstico completo
-4. Sigue las recomendaciones específicas
-
-### 🛠️ OPCIÓN 3: Instalación Manual Paso a Paso
-
-#### Paso 1: Verifica tu Base de Datos
-```sql
--- En tu panel MySQL (phpMyAdmin, etc.)
-USE tu_basedatos;
-SHOW TABLES;
-```
-
-#### Paso 2: Crea las tablas manualmente
-Si no existen, ejecuta este SQL:
-
-```sql
-CREATE TABLE IF NOT EXISTS users (
- id INT AUTO_INCREMENT PRIMARY KEY,
- phone_number VARCHAR(20) UNIQUE NOT NULL,
- name VARCHAR(100),
- status ENUM('active', 'inactive') DEFAULT 'active',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-);
-
-CREATE TABLE IF NOT EXISTS system_config (
- id INT AUTO_INCREMENT PRIMARY KEY,
- config_key VARCHAR(100) UNIQUE NOT NULL,
- config_value TEXT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-);
-
-INSERT IGNORE INTO system_config (config_key, config_value) VALUES
-('app_installed', '1'),
-('install_date', NOW());
-```
-
-#### Paso 3: Actualiza config.php manualmente
-Edita `config/config.php` y cambia:
-
-```php
-define('DB_HOST', 'localhost'); // Tu host
-define('DB_PORT', '3306'); // Tu puerto
-define('DB_NAME', 'tu_basedatos'); // Tu BD
-define('DB_USER', 'tu_usuario'); // Tu usuario
-define('DB_PASS', 'tu_contraseña'); // Tu contraseña
-define('ADMIN_PASSWORD', password_hash('admin123', PASSWORD_DEFAULT));
-```
-
-#### Paso 4: Crear archivo de instalación completada
-Crea el archivo `.installation_completed` con cualquier contenido.
-
----
-
-## 🎯 RECOMENDACIÓN ESPECÍFICA
-
-### Para HestiaCP/cPanel:
-1. **USA:** `install_simple.php` ← **MÁS FÁCIL**
-2. **SI FALLA:** `debug_install.php` para diagnóstico
-3. **ÚLTIMA OPCIÓN:** Instalación manual paso a paso
-
-### Credenciales Típicas en HestiaCP:
-- **Host:** `localhost`
-- **Puerto:** `3306`
-- **BD:** `tuusuario_nombrebd`
-- **Usuario:** `tuusuario_nombreusuario`
-- **Contraseña:** La que configuraste
-
----
-
-## 🔐 Después de Solucionar
-
-### Credenciales de Admin:
-- **Usuario:** `admin`
-- **Contraseña:** `admin123` (o la que configuraste)
-
-### Próximos pasos:
-1. Ve a `index.php` para acceder al panel
-2. Prueba con `test.php` que todo funcione
-3. Configura WhatsApp en `server_setup.php`
-
----
-
-## 📞 ¿Sigues con problemas?
-
-### Información necesaria para soporte:
-1. **Tipo de hosting:** (HestiaCP, cPanel, etc.)
-2. **Error exacto:** Copia el mensaje completo
-3. **Resultado de:** `debug_install.php`
-4. **Versión PHP:** Desde tu panel de control
-
-### Contacto:
-- **Email:** support@u-site.app
-- **Con:** Toda la información anterior
-
----
-
-## ✨ Archivos de Ayuda Creados
-
-1. **`install_simple.php`** - Instalador minimalista ✅
-2. **`debug_install.php`** - Diagnóstico completo ✅
-3. **`install_manual.php`** - Instalador manual completo ✅
-4. **`GUIA_HESTIACP.md`** - Guía específica para HestiaCP ✅
-
-**¡Con estas herramientas deberías poder instalar sin problemas!** 🚀
\ No newline at end of file
diff --git a/SOLUCION_HOSTING.md b/SOLUCION_HOSTING.md
deleted file mode 100644
index d44f90a..0000000
--- a/SOLUCION_HOSTING.md
+++ /dev/null
@@ -1,98 +0,0 @@
-# ✅ SOLUCIÓN PARA HOSTING COMPARTIDO (HestiaCP)
-
-**¡Problema solucionado!** He creado una solución completa para tu problema con la instalación en hosting compartido.
-
-## 🚨 El Problema
-- El instalador automático (`install.php`) **NO FUNCIONA** en hosting compartido
-- Se queda "cargando" porque intenta crear BD con privilegios de administrador
-- En HestiaCP/cPanel no tienes esos permisos
-
-## ✅ La Solución
-He creado un **instalador manual** específico para hosting compartido.
-
----
-
-## 🔧 PASOS PARA INSTALAR EN HESTIACP
-
-### 1. Crear Base de Datos Manualmente
-En tu panel de HestiaCP:
-1. Ve a **"Databases"**
-2. Clic en **"Add Database"**
-3. Configura:
- - **Database Name:** (ej: `tuusuario_whatsapp`)
- - **Database User:** (ej: `tuusuario_bot`)
- - **Password:** (una contraseña segura)
- - **Privileges:** **ALL** (todos los permisos)
-
-### 2. Anotar las Credenciales
-Guarda esta información:
-- **Host:** `localhost`
-- **Base de datos:** El nombre que creaste
-- **Usuario:** El usuario que creaste
-- **Contraseña:** La contraseña que configuraste
-
-### 3. Usar el Instalador Manual
-1. Ve a: `https://tudominio.com/ruta-del-bot/install_manual.php`
-2. Completa el formulario con las credenciales del paso anterior
-3. Haz clic en **"Configurar Sistema"**
-
----
-
-## 📁 Archivos Creados
-
-He creado estos archivos nuevos para solucionar tu problema:
-
-1. **`install_manual.php`** - Instalador para hosting compartido ✅
-2. **`GUIA_HESTIACP.md`** - Guía detallada paso a paso ✅
-3. **`instalacion.html`** - Página para elegir tipo de instalación ✅
-4. **Mejorado `install.php`** - Ahora detecta hosting compartido ✅
-
----
-
-## 🎯 URL de Inicio
-
-**👉 EMPIEZA AQUÍ:** `https://tudominio.com/ruta-del-bot/instalacion.html`
-
-Esta página te permite elegir entre:
-- **Instalación Automática** (para desarrollo local)
-- **Instalación Manual** (para hosting compartido) ← **USA ESTA**
-
----
-
-## 🔐 Credenciales Generadas
-
-### Para la Base de Datos
-- Usarás las credenciales que **TÚ** creaste en HestiaCP
-
-### Para el Panel Admin
-- **Usuario:** `admin`
-- **Contraseña:** Se genera automáticamente y se muestra durante la instalación
-
----
-
-## 📞 Si Tienes Problemas
-
-### Errores Comunes:
-- **"Access denied"** → Verifica credenciales de BD
-- **"Database doesn't exist"** → Confirma el nombre exacto de la BD
-- **"Connection refused"** → Asegúrate que MySQL esté activo
-
-### Soporte:
-- **Email:** support@u-site.app
-- **Documentación:** `GUIA_HESTIACP.md`
-- **Pruebas:** `test.php` después de instalar
-
----
-
-## 🚀 Próximos Pasos Después de Instalar
-
-1. **Prueba el sistema:** Ve a `test.php`
-2. **Accede al panel:** Ve a `index.php`
-3. **Configura WhatsApp:** Ve a `server_setup.php`
-4. **Lee las credenciales:** Archivo `CREDENCIALES_SISTEMA.txt`
-
----
-
-**¡Ya no tendrás problemas con la instalación en HestiaCP!** 🎉
-
-La solución está probada y funciona específicamente para hostings compartidos que no permiten crear bases de datos por código.
\ No newline at end of file
diff --git a/SOLUCION_SEGURIDAD.md b/SOLUCION_SEGURIDAD.md
deleted file mode 100644
index c75b2c8..0000000
--- a/SOLUCION_SEGURIDAD.md
+++ /dev/null
@@ -1,94 +0,0 @@
-# 🔐 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. Iniciar sesión');
-}
-```
-
-### 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_system_config.php`
-- ✅ `api/get_users.php`
-- ✅ `api/send_message.php`
-- ✅ `api/get_system_config.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.
\ No newline at end of file
diff --git a/SOLUCION_TABLA_NO_EXISTE.md b/SOLUCION_TABLA_NO_EXISTE.md
deleted file mode 100644
index 1b916fa..0000000
--- a/SOLUCION_TABLA_NO_EXISTE.md
+++ /dev/null
@@ -1,143 +0,0 @@
-# 🚨 SOLUCIÓN PARA ERROR: "Table doesn't exist"
-
-## Tu Situación Específica
-Has reportado estos errores:
-```
-⚠️ Esquema procesado con advertencias: 0 tablas, 0 elementos
-⚠️ SQL Error: Table 'usite_whatsapp_bot.menus' doesn't exist
-```
-
-**CAUSA:** El archivo `schema.sql` no se está procesando correctamente. El instalador intenta insertar datos en tablas que aún no se han creado.
-
----
-
-## ✅ SOLUCIÓN INMEDIATA
-
-### 🎯 **OPCIÓN 1: Instalador Ultra Básico (RECOMENDADO)**
-**👉 Usa:** `install_ultra_basic.php`
-
-### 🔧 **OPCIÓN 2: Instalador Integrado (COMPLETO)**
-**👉 Usa:** `install_integrated.php`
-
-**¿Por qué funcionan?**
-- ✅ Schema SQL integrado en el código PHP
-- ✅ No depende del archivo `schema.sql` externo
-- ✅ Ejecución controlada paso a paso
-- ✅ Primero crea tablas, luego inserta datos
-
-**Nota:** La tabla `users` NO incluye campo `password` porque es para usuarios de WhatsApp que se identifican por número de teléfono. La contraseña de admin se guarda en `config.php`.
-
-### 🔧 **OPCIÓN 2: Reparar Manualmente**
-Si quieres usar el instalador manual:
-
-1. **Ejecuta este SQL directamente en phpMyAdmin:**
-```sql
-USE usite_whatsapp_bot;
-
-CREATE TABLE IF NOT EXISTS menus (
- id INT AUTO_INCREMENT PRIMARY KEY,
- name VARCHAR(100) NOT NULL,
- title VARCHAR(200) NOT NULL,
- description TEXT,
- parent_id INT NULL,
- is_root BOOLEAN DEFAULT FALSE,
- is_active BOOLEAN DEFAULT TRUE,
- order_position INT DEFAULT 0,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- INDEX idx_parent (parent_id),
- INDEX idx_active (is_active)
-);
-```
-
-2. **Luego usa cualquier instalador**
-
----
-
-## 🔍 ¿Por qué falló el archivo schema.sql?
-
-### Problemas comunes:
-1. **Parsing incorrecto:** El archivo se divide mal por `;`
-2. **Variables no definidas:** `SET @variable` no funciona en algunos contextos
-3. **Dependencias:** Se intenta insertar en tablas antes de crearlas
-4. **Encoding:** Caracteres especiales en el archivo
-
-### El instalador integrado evita estos problemas porque:
-- ✅ Usa arrays PHP en lugar de parsing de archivo
-- ✅ Ejecuta CREATE TABLE primero, INSERT después
-- ✅ Manejo de errores específico para cada paso
-
----
-
-## 📋 Pasos Específicos Para Ti
-
-### 1. **Usar el Instalador Integrado:**
-```
-1. Ve a: tudominio.com/install_integrated.php
-2. Ingresa tus credenciales:
- - Host: localhost
- - Puerto: 3306
- - BD: usite_whatsapp_bot
- - Usuario: [tu usuario]
- - Contraseña: [tu contraseña]
-3. Clic en "Instalar con Schema Integrado"
-```
-
-### 2. **Credenciales que se generarán:**
-- **Usuario admin:** `admin`
-- **Contraseña:** Se muestra durante la instalación
-- **Se guarda en:** `CREDENCIALES.txt`
-
-### 3. **Verificar que funcionó:**
-```
-- Ve a: test.php
-- Deberías ver: "✅ Sistema funcionando"
-- Login en: index.php
-```
-
----
-
-## 🎯 Archivos Disponibles
-
-| Instalador | Recomendado Para | Tu Situación |
-|------------|------------------|--------------|
-| `install_integrated.php` | **Problemas con schema.sql** | ✅ **PERFECTO** |
-| `install_simple.php` | Solo funciones básicas | ✅ Alternativa |
-| `install_manual.php` | Instalación completa | ❌ Tiene el problema |
-| `debug_install.php` | Diagnóstico de problemas | ℹ️ Para análisis |
-
----
-
-## 🚀 Después de Instalar
-
-1. **Verifica las tablas creadas:**
- - Ve a phpMyAdmin
- - Deberías ver: users, conversations, menus, menu_options, system_config, etc.
-
-2. **Prueba el sistema:**
- - `test.php` - Para verificar funcionamiento
- - `index.php` - Panel de administración
- - `login.php` - Sistema de login
-
-3. **Configura WhatsApp:**
- - Entra al panel con las credenciales generadas
- - Ve a Configuración
- - Agrega tu token de WhatsApp
-
----
-
-## 📞 Si Aún Tienes Problemas
-
-### Información para soporte:
-- **Hosting:** HestiaCP (confirmado)
-- **BD:** usite_whatsapp_bot (confirmado)
-- **Error anterior:** Schema parsing falló
-- **Solución aplicada:** Instalador integrado
-
-### Contacto:
-- **Email:** support@u-site.app
-- **Incluye:** Resultado del instalador integrado
-
----
-
-**💡 RESUMEN:** Tu problema se debe a que el archivo `schema.sql` no se procesa correctamente. El instalador integrado resuelve esto completamente al tener todo el SQL dentro del código PHP. ¡Úsalo y debería funcionar perfectamente! 🚀
\ No newline at end of file
diff --git a/SSE_REALTIME_DOCS.md b/SSE_REALTIME_DOCS.md
deleted file mode 100644
index 0ed74d3..0000000
--- a/SSE_REALTIME_DOCS.md
+++ /dev/null
@@ -1,349 +0,0 @@
-# Sistema de Notificaciones en Tiempo Real (SSE)
-
-## 📡 Descripción
-
-Sistema de notificaciones push en tiempo real usando **Server-Sent Events (SSE)** para reemplazar el polling constante y mejorar la eficiencia.
-
-## 🔄 Flujo de funcionamiento
-
-### 1. **Cliente se conecta a SSE**
-```javascript
-// En conversations.php
-connectSSE() {
- this.eventSource = new EventSource('api/sse_events.php');
- // Escucha eventos: new_message, new_conversation, heartbeat
-}
-```
-
-### 2. **Webhook recibe mensaje de WhatsApp**
-```
-WhatsApp → webhook.php → Guarda en BD → pushSSEEvent()
-```
-
-### 3. **Push notifica a operadores conectados**
-```php
-webhook.php → push_event.php → Escribe evento en archivo temporal
-```
-
-### 4. **SSE envía evento a clientes**
-```php
-sse_events.php → Lee eventos pendientes → Envía a navegador
-```
-
-### 5. **Cliente recibe y procesa evento**
-```javascript
-// Solo actualiza la conversación afectada, NO recarga todo
-eventSource.addEventListener('new_message', (e) => {
- // Actualizar conversación en lista
- // Si es la activa, recargar mensajes
- // Reproducir sonido
-});
-```
-
-## 📁 Archivos creados
-
-### `api/sse_events.php`
-- Endpoint SSE que mantiene conexión abierta
-- Lee eventos de archivos temporales
-- Envía eventos al navegador en tiempo real
-- **NUEVO**: Envía notificaciones no leídas automáticamente
-- Heartbeat cada 30 segundos para mantener conexión viva
-
-### `api/push_event.php`
-- API interna para empujar eventos
-- Solo accesible desde localhost o con token secreto
-- Escribe eventos en archivos temporales por usuario
-- Broadcast a todos los operadores conectados
-
-### `classes/NotificationHelper.php` ⭐ NUEVO
-- Helper para crear y enviar notificaciones
-- **Método create()**: Inserta en BD y envía automáticamente por SSE
-- **Método pushSSE()**: Envía notificación existente por SSE
-- Uso: `NotificationHelper::create($userId, 'document', 'Usuario subió documento', ['count' => 3])`
-
-## 🔧 Modificaciones en archivos existentes
-
-### `api/webhook.php`
-- ✅ Agregado método `pushSSEEvent()`
-- ✅ Llama a `push_event.php` cuando llega mensaje nuevo
-- ✅ Fire-and-forget (no bloquea el webhook)
-
-### `conversations.php`
-- ✅ Agregado método `connectSSE()`
-- ✅ Agregado método `updateConversationInList()`
-- ✅ Agregado método `addConversationToList()`
-- ✅ **Agregado listener para evento 'notification'**
-- ✅ Polling de notificaciones: 7s → 60s (backup)
-- ✅ Polling de conversaciones: 7s → 30s (backup)
-- ✅ Auto-refresh reducido de 8s → 20s (backup)
-
-## 🎯 Ventajas del sistema SSE
-
-### **Antes (Polling):**
-```
-Cliente → GET messages cada 7-8s
-Cliente → GET conversations cada 15s
-Cliente → GET notifications cada 7s
-
-
-### **Ahora (SSE):**
-```
-Cliente ← SSE conectado (1 conexión persistente)
-Servidor → Push cuando hay nuevo mensaje (< 1s)
- → Recarga automáticamente: loadMessages(userId, false, true)
-Servidor → Push cuando hay nueva conversación (< 1s)
- → Agrega a lista: addConversationToList(data)
-Servidor → Push cuando hay notificación (< 1s)
- → Muestra toast: showNotificationToast(notification)
-Servidor → Heartbeat cada 30s
-
-Backup polling (solo por si falla SSE):
-Cliente → GET notifications cada 60s
-
-Total: ~1 petición por minuto por cliente
-```
-
-**Reducción: 95% menos peticiones HTTP** 🎉
-
-### ¿Por qué NO necesitamos ningún polling ahora?
-
-#### 1. **Mensajes**: SSE evento `new_message`
-```javascript
-// En conversations.php línea ~1103
-this.eventSource.addEventListener('new_message', (e) => {
- const data = JSON.parse(e.data);
-
- // Si es la conversación activa, recarga mensajes
- if (this.currentUserId === data.user_id) {
- this.loadMessages(this.currentUserId, false, true); // ✅ Push
- }
-
- // Actualiza la conversación en la lista
- this.updateConversationInList(data);
-});
-```
-
-#### 2. **Conversaciones**: SSE eventos `new_message` + `new_conversation`
-```javascript
-// Actualización inteligente sin recargar todo
-this.eventSource.addEventListener('new_conversation', (e) => {
- this.addConversationToList(JSON.parse(e.data)); // ✅ Solo agrega una
-});
-
-this.eventSource.addEventListener('new_message', (e) => {
- this.updateConversationInList(JSON.parse(e.data)); // ✅ Solo actualiza una
-});
-```
-
-#### 3. **Notificaciones**: SSE evento `notification`
-```javascript
-this.eventSource.addEventListener('notification', (e) => {
- this.showNotificationToast(JSON.parse(e.data)); // ✅ Toast instantáneo
-});
-```
-
-**Resultado**: Sistema 100% push, 0 polling innecesario 🚀
-Total: ~15-20 peticiones por minuto por cliente
-```
-
-### **Ahora (SSE):**
-```
-Cliente ← SSE conexión permanente
-Cliente → GET backup cada 15-20s
-
-Total: ~2-3 peticiones por minuto por cliente
-Eventos llegan en < 1 segundo después del webhook
-```
-
-### **Beneficios:**
-- ✅ **Latencia ultra-baja:** Mensajes llegan en ~500ms
-- ✅ **Menos carga servidor:** 85% menos peticiones HTTP
-- ✅ **Actualizaciones incrementales:** Solo afecta conversación específica
-- ✅ **No recarga todo:** UI más fluida y rápida
-- ✅ **Conexión persistente:** Un solo canal para todos los eventos
-- ✅ **Fallback automático:** Si SSE falla, polling sigue funcionando
-
-## 🔐 Seguridad
-
-### **Token secreto interno:**
-```php
-// En push_event.php
-X-Push-Token: internal_push_secret_2026
-```
-
-### **Validación de IP:**
-```php
-// Solo permite localhost por defecto
-$allowedIPs = ['127.0.0.1', '::1', 'localhost'];
-```
-
-### **Autenticación SSE:**
-```php
-// En sse_events.php
-requireAuthentication(); // Usa sesión PHP
-```
-
-## 📊 Eventos disponibles
-
-### `connected`
-- Se envía cuando cliente se conecta exitosamente
-- Data: `{ timestamp: unix_timestamp }`
-
-### `new_message`
-- Se envía cuando llega mensaje nuevo de WhatsApp
-- Data:
- ```json
- {
- "user_id": 123,
- "phone_number": "573168950803",
- "name": "Juan Pérez",
- "message": "Hola, necesito ayuda",
- "message_type": "text",
- "timestamp": "2026-01-27 10:30:00"
- }
- ```
-
-### `new_conversation`
-- Se envía cuando se detecta nueva conversación
-- Data:
- ```json
- {
- "user_id": 124,
- "phone_number": "573168950804",
- "name": "María García",
- "message_count": 1,
- "timestamp": "2026-01-27 10:35:00"
- }
- ```
-
-### `heartbeat`
-- Se envía cada 30 segundos
-- Data: `{ timestamp: unix_timestamp }`
-- Mantiene conexión viva
-
-## 🚀 Cómo probar
-
-### 1. **Verificar que SSE funciona:**
-```bash
-curl -N http://localhost/api/sse_events.php
-```
-
-Deberías ver:
-```
-event: connected
-data: {"timestamp":1738000000}
-
-event: heartbeat
-data: {"timestamp":1738000030}
-```
-
-### 2. **Simular un push interno:**
-```bash
-curl -X POST http://localhost/api/push_event.php \
- -H "Content-Type: application/json" \
- -H "X-Push-Token: internal_push_secret_2026" \
- -d '{
- "event_type": "new_message",
- "data": {
- "user_id": 123,
- "phone_number": "573168950803",
- "message": "Test message"
- },
- "target_user_id": "all"
- }'
-```
-
-### 3. **Ver eventos en navegador:**
-- Abrir `conversations.php`
-- Abrir DevTools → Console
-- Deberías ver: `✅ SSE conectado: {...}`
-- Enviar mensaje de prueba desde WhatsApp
-- Verás: `📨 Nuevo mensaje (SSE): {...}`
-
-## 🐛 Troubleshooting
-
-### **SSE no conecta:**
-1. Verificar que `api/sse_events.php` es accesible
-2. Verificar que sesión está activa (login requerido)
-3. Revisar logs del servidor
-
-### **Eventos no llegan:**
-1. Verificar permisos de escritura en `/uploads/`
-2. Verificar que `push_event.php` es accesible desde localhost
-3. Revisar logs de webhook
-
-### **Reconexión constante:**
-1. Verificar timeout del servidor (debe ser > 60s)
-2. Verificar que nginx/apache no tiene buffer activo
-3. Revisar límites de conexiones persistentes
-
-## ⚙️ Configuración avanzada
-
-### **Cambiar token de seguridad:**
-```bash
-# .env o config
-PUSH_TOKEN=tu_token_super_secreto_aqui
-```
-
-### **Aumentar timeout SSE:**
-```php
-// En sse_events.php
-set_time_limit(300); // 5 minutos
-```
-
-### **Cambiar frecuencia de revisión:**
-```php
-// En sse_events.php, cambiar:
-sleep(2); // A valor deseado (1-5 segundos)
-```
-
-## 📈 Monitoreo
-
-### **Ver eventos en tiempo real:**
-```bash
-tail -f /ruta/uploads/events_global.json
-```
-
-### **Contar conexiones activas:**
-```bash
-netstat -an | grep :80 | grep ESTABLISHED | wc -l
-```
-
-### **Ver logs de push:**
-```bash
-tail -f /var/log/apache2/error.log | grep pushSSEEvent
-```
-
-## 🔄 Migración desde polling
-
-El sistema es compatible hacia atrás. Si SSE falla:
-- Polling sigue funcionando como backup
-- No se pierden mensajes
-- Usuario no nota diferencia (solo latencia mayor)
-
-Para deshabilitar polling completamente:
-```javascript
-// En conversations.php, comentar:
-// setInterval(() => this.loadNotifications(), 15000);
-```
-
-## 📝 Notas importantes
-
-1. **Archivos temporales:** Los eventos se guardan en `/uploads/events_{user_id}.json`
-2. **Limpieza automática:** Solo se mantienen últimos 50 eventos por usuario
-3. **Broadcast:** Eventos se envían a todos los operadores conectados
-4. **No duplicación:** Verificación de IDs previene mensajes duplicados
-5. **Graceful degradation:** Sistema funciona con o sin SSE
-
-## 🎉 Resultado final
-
-**Antes:**
-- Mensajes aparecen después de 7-15 segundos
-- Múltiples peticiones constantes
-- Recarga completa de lista y conversaciones
-
-**Ahora:**
-- Mensajes aparecen en < 1 segundo
-- Una conexión persistente + backup ligero
-- Solo actualiza la conversación afectada
-- UI más fluida y responsive
diff --git a/api/change_admin_password.php b/api/change_admin_password.php
new file mode 100644
index 0000000..7afb681
--- /dev/null
+++ b/api/change_admin_password.php
@@ -0,0 +1,52 @@
+execute(
+ "UPDATE admin_users SET password_hash = ? WHERE id = ?",
+ [$hashedPassword, $userId]
+ );
+
+ echo json_encode([
+ 'success' => true,
+ 'message' => 'Contraseña actualizada correctamente'
+ ]);
+
+} catch (Exception $e) {
+ error_log('Error changing admin password: ' . $e->getMessage());
+ http_response_code(500);
+ echo json_encode([
+ 'success' => false,
+ 'error' => $e->getMessage()
+ ]);
+}
diff --git a/api/create_admin_user.php b/api/create_admin_user.php
new file mode 100644
index 0000000..e26c096
--- /dev/null
+++ b/api/create_admin_user.php
@@ -0,0 +1,65 @@
+fetch(
+ "SELECT id FROM admin_users WHERE username = ?",
+ [$username]
+ );
+
+ if ($existing) {
+ throw new Exception('El nombre de usuario ya está en uso');
+ }
+
+ // Encriptar contraseña
+ $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
+
+ // Insertar usuario
+ $db->execute(
+ "INSERT INTO admin_users (username, password_hash, full_name, email, is_active, created_at)
+ VALUES (?, ?, ?, ?, 1, NOW())",
+ [$username, $hashedPassword, $fullName, $email]
+ );
+
+ echo json_encode([
+ 'success' => true,
+ 'message' => 'Usuario creado correctamente'
+ ]);
+
+} catch (Exception $e) {
+ error_log('Error creating admin user: ' . $e->getMessage());
+ http_response_code(500);
+ echo json_encode([
+ 'success' => false,
+ 'error' => $e->getMessage()
+ ]);
+}
diff --git a/api/delete_message.php b/api/delete_message.php
new file mode 100644
index 0000000..428b528
--- /dev/null
+++ b/api/delete_message.php
@@ -0,0 +1,193 @@
+ false, 'error' => 'Método no permitido. Use POST o DELETE.']);
+ exit;
+}
+
+try {
+ $input = json_decode(file_get_contents('php://input'), true);
+
+ // También soportar query params para DELETE
+ if (!$input && $_SERVER['REQUEST_METHOD'] === 'DELETE') {
+ $input = [
+ 'message_id' => $_GET['message_id'] ?? $_GET['id'] ?? null,
+ 'delete_for_everyone' => $_GET['delete_for_everyone'] ?? false
+ ];
+ }
+
+ if (!$input) {
+ http_response_code(400);
+ echo json_encode(['success' => false, 'error' => 'JSON inválido']);
+ exit;
+ }
+
+ $messageId = $input['message_id'] ?? $input['id'] ?? null;
+ $deleteForEveryone = $input['delete_for_everyone'] ?? false;
+ $softDelete = $input['soft_delete'] ?? true; // Por defecto, soft delete
+
+ if (!$messageId) {
+ http_response_code(400);
+ echo json_encode(['success' => false, 'error' => 'message_id es requerido']);
+ exit;
+ }
+
+ $db = Database::getInstance();
+
+ // Buscar el mensaje
+ $sql = "SELECT id, user_id, direction, message_type, content, message_id as wa_message_id,
+ local_file, local_thumb, media_url
+ FROM conversations
+ WHERE id = :mid1 OR message_id = :mid2
+ LIMIT 1";
+ $message = $db->fetch($sql, ['mid1' => $messageId, 'mid2' => $messageId]);
+
+ if (!$message) {
+ http_response_code(404);
+ echo json_encode(['success' => false, 'error' => 'Mensaje no encontrado']);
+ exit;
+ }
+
+ // Solo permitir eliminar mensajes salientes (outgoing) a menos que sea admin
+ $isAdmin = isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'];
+ if ($message['direction'] !== 'outgoing' && !$isAdmin) {
+ http_response_code(403);
+ echo json_encode(['success' => false, 'error' => 'Solo puedes eliminar tus propios mensajes']);
+ exit;
+ }
+
+ $deletedMessageId = $message['id'];
+ $userId = $message['user_id'];
+ $waMessageId = $message['wa_message_id'];
+
+ if ($softDelete) {
+ // Soft delete: marcar como eliminado pero mantener registro
+ // Verificar si existen las columnas deleted_at y original_content
+ $hasDeletedAt = false;
+ $hasOriginalContent = false;
+ try {
+ $columns = $db->fetchAll("SHOW COLUMNS FROM conversations WHERE Field IN ('deleted_at', 'original_content')");
+ foreach ($columns as $col) {
+ if ($col['Field'] === 'deleted_at') $hasDeletedAt = true;
+ if ($col['Field'] === 'original_content') $hasOriginalContent = true;
+ }
+ } catch (Exception $e) {
+ // Ignorar
+ }
+
+ // Construir la query según las columnas disponibles
+ // IMPORTANTE: Guardar original_content ANTES de modificar content
+ if ($hasDeletedAt && $hasOriginalContent) {
+ // Primero guardamos el contenido original
+ $db->execute("UPDATE conversations SET original_content = content WHERE id = :id AND original_content IS NULL", ['id' => $deletedMessageId]);
+ // Luego marcamos como eliminado
+ $updateSql = "UPDATE conversations SET content = '[Mensaje eliminado]', deleted_at = NOW() WHERE id = :id";
+ } elseif ($hasDeletedAt) {
+ $updateSql = "UPDATE conversations SET content = '[Mensaje eliminado]', deleted_at = NOW() WHERE id = :id";
+ } else {
+ $updateSql = "UPDATE conversations SET content = '[Mensaje eliminado]' WHERE id = :id";
+ }
+
+ $db->execute($updateSql, ['id' => $deletedMessageId]);
+
+ error_log("Message soft-deleted: ID={$deletedMessageId}, User={$userId}");
+ } else {
+ // Hard delete: eliminar completamente
+
+ // Primero, eliminar archivos multimedia asociados si existen
+ if (!empty($message['local_file'])) {
+ $filePath = dirname(__DIR__) . '/' . $message['local_file'];
+ if (file_exists($filePath)) {
+ @unlink($filePath);
+ error_log("Deleted media file: {$filePath}");
+ }
+ }
+
+ if (!empty($message['local_thumb'])) {
+ $thumbPath = dirname(__DIR__) . '/' . $message['local_thumb'];
+ if (file_exists($thumbPath)) {
+ @unlink($thumbPath);
+ error_log("Deleted thumbnail: {$thumbPath}");
+ }
+ }
+
+ // Eliminar el mensaje de la base de datos
+ $deleteSql = "DELETE FROM conversations WHERE id = :id";
+ $db->execute($deleteSql, ['id' => $deletedMessageId]);
+
+ error_log("Message hard-deleted: ID={$deletedMessageId}, User={$userId}");
+ }
+
+ // Intentar notificar por SSE si está disponible
+ try {
+ $ssePayload = [
+ 'type' => 'message_deleted',
+ 'user_id' => $userId,
+ 'message_id' => $deletedMessageId,
+ 'wa_message_id' => $waMessageId,
+ 'soft_delete' => $softDelete,
+ 'deleted_at' => date('Y-m-d H:i:s')
+ ];
+
+ // Llamar al endpoint push_event interno
+ $pushUrl = 'http://127.0.0.1' . dirname($_SERVER['SCRIPT_NAME']) . '/push_event.php';
+ $ch = curl_init($pushUrl);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
+ 'event_type' => 'message_deleted',
+ 'data' => $ssePayload,
+ 'target_user_id' => 'all'
+ ]));
+ curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 2);
+ curl_exec($ch);
+ curl_close($ch);
+ } catch (Exception $sseErr) {
+ error_log("delete_message: SSE notification failed: " . $sseErr->getMessage());
+ }
+
+ echo json_encode([
+ 'success' => true,
+ 'message' => $softDelete ? 'Mensaje eliminado' : 'Mensaje eliminado permanentemente',
+ 'data' => [
+ 'id' => $deletedMessageId,
+ 'message_id' => $waMessageId,
+ 'soft_delete' => $softDelete,
+ 'deleted_at' => date('Y-m-d H:i:s')
+ ]
+ ]);
+
+} catch (Exception $e) {
+ error_log('delete_message.php error: ' . $e->getMessage() . ' | Trace: ' . $e->getTraceAsString());
+ http_response_code(500);
+ echo json_encode(['success' => false, 'error' => 'Error interno del servidor', 'debug' => $e->getMessage()]);
+}
diff --git a/api/delete_scheduled_message.php b/api/delete_scheduled_message.php
new file mode 100644
index 0000000..9b7b2b0
--- /dev/null
+++ b/api/delete_scheduled_message.php
@@ -0,0 +1,55 @@
+fetch("SELECT * FROM scheduled_messages WHERE id = ?", [$id]);
+
+ if (!$existing) {
+ throw new Exception('Mensaje programado no encontrado');
+ }
+
+ // Eliminar
+ $db->execute("DELETE FROM scheduled_messages WHERE id = ?", [$id]);
+
+ writeLog('INFO', "Mensaje programado eliminado: ID={$id}");
+
+ echo json_encode([
+ 'success' => true,
+ 'message' => 'Mensaje programado eliminado exitosamente'
+ ]);
+
+} catch (Exception $e) {
+ error_log("Error in delete_scheduled_message.php: " . $e->getMessage());
+ http_response_code(500);
+ echo json_encode([
+ 'success' => false,
+ 'error' => $e->getMessage()
+ ]);
+}
diff --git a/api/edit_message.php b/api/edit_message.php
new file mode 100644
index 0000000..5220e4c
--- /dev/null
+++ b/api/edit_message.php
@@ -0,0 +1,160 @@
+ false, 'error' => 'Método no permitido. Use POST.']);
+ exit;
+}
+
+try {
+ $input = json_decode(file_get_contents('php://input'), true);
+
+ if (!$input) {
+ http_response_code(400);
+ echo json_encode(['success' => false, 'error' => 'JSON inválido']);
+ exit;
+ }
+
+ $messageId = $input['message_id'] ?? $input['id'] ?? null;
+ $newContent = $input['content'] ?? $input['new_content'] ?? null;
+
+ if (!$messageId) {
+ http_response_code(400);
+ echo json_encode(['success' => false, 'error' => 'message_id es requerido']);
+ exit;
+ }
+
+ if ($newContent === null || trim($newContent) === '') {
+ http_response_code(400);
+ echo json_encode(['success' => false, 'error' => 'El contenido no puede estar vacío']);
+ exit;
+ }
+
+ $db = Database::getInstance();
+
+ // Buscar el mensaje
+ $sql = "SELECT id, user_id, direction, message_type, content, message_id as wa_message_id
+ FROM conversations
+ WHERE id = :mid1 OR message_id = :mid2
+ LIMIT 1";
+ $message = $db->fetch($sql, ['mid1' => $messageId, 'mid2' => $messageId]);
+
+ if (!$message) {
+ http_response_code(404);
+ echo json_encode(['success' => false, 'error' => 'Mensaje no encontrado']);
+ exit;
+ }
+
+ // Solo permitir editar mensajes salientes (outgoing)
+ if ($message['direction'] !== 'outgoing') {
+ http_response_code(403);
+ echo json_encode(['success' => false, 'error' => 'Solo puedes editar tus propios mensajes']);
+ exit;
+ }
+
+ // Solo permitir editar mensajes de texto
+ if ($message['message_type'] !== 'text' && $message['message_type'] !== null) {
+ http_response_code(400);
+ echo json_encode(['success' => false, 'error' => 'Solo se pueden editar mensajes de texto']);
+ exit;
+ }
+
+ // Guardar contenido original para historial (opcional)
+ $originalContent = $message['content'];
+
+ // Verificar si existe la columna edited_at
+ $hasEditedAt = false;
+ try {
+ $columns = $db->fetchAll("SHOW COLUMNS FROM conversations WHERE Field = 'edited_at'");
+ $hasEditedAt = !empty($columns);
+ } catch (Exception $e) {
+ // Ignorar
+ }
+
+ // Actualizar el mensaje en la base de datos
+ if ($hasEditedAt) {
+ $updateSql = "UPDATE conversations SET content = :content, edited_at = NOW() WHERE id = :id";
+ } else {
+ $updateSql = "UPDATE conversations SET content = :content WHERE id = :id";
+ }
+
+ $db->execute($updateSql, [
+ 'content' => trim($newContent),
+ 'id' => $message['id']
+ ]);
+
+ // Registrar la edición en el log
+ error_log("Message edited: ID={$message['id']}, User={$message['user_id']}, Original='{$originalContent}', New='{$newContent}'");
+
+ // Intentar notificar por SSE si está disponible
+ try {
+ $ssePayload = [
+ 'type' => 'message_edited',
+ 'user_id' => $message['user_id'],
+ 'message_id' => $message['id'],
+ 'wa_message_id' => $message['wa_message_id'],
+ 'new_content' => trim($newContent),
+ 'edited_at' => date('Y-m-d H:i:s')
+ ];
+
+ // Llamar al endpoint push_event interno
+ $pushUrl = 'http://127.0.0.1' . dirname($_SERVER['SCRIPT_NAME']) . '/push_event.php';
+ $ch = curl_init($pushUrl);
+ curl_setopt($ch, CURLOPT_POST, true);
+ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
+ 'event_type' => 'message_edited',
+ 'data' => $ssePayload,
+ 'target_user_id' => 'all'
+ ]));
+ curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 2);
+ curl_exec($ch);
+ curl_close($ch);
+ } catch (Exception $sseErr) {
+ error_log("edit_message: SSE notification failed: " . $sseErr->getMessage());
+ }
+
+ echo json_encode([
+ 'success' => true,
+ 'message' => 'Mensaje editado correctamente',
+ 'data' => [
+ 'id' => $message['id'],
+ 'message_id' => $message['wa_message_id'],
+ 'new_content' => trim($newContent),
+ 'edited_at' => date('Y-m-d H:i:s')
+ ]
+ ]);
+
+} catch (Exception $e) {
+ error_log('edit_message.php error: ' . $e->getMessage() . ' | Trace: ' . $e->getTraceAsString());
+ http_response_code(500);
+ echo json_encode(['success' => false, 'error' => 'Error interno del servidor', 'debug' => $e->getMessage()]);
+}
diff --git a/api/get_conversations.php b/api/get_conversations.php
index bfd4930..e3e0944 100644
--- a/api/get_conversations.php
+++ b/api/get_conversations.php
@@ -25,6 +25,9 @@ try {
$limit = max(1, min(200, $limit)); // máximo 200 por página
$page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
$offset = ($page - 1) * $limit;
+
+ // Parámetro de búsqueda
+ $search = isset($_GET['search']) ? trim($_GET['search']) : '';
// Obtener conversaciones agregadas por usuario: último mensaje + conteo de no leídos + avatar
$filter = isset($_GET['filter']) ? strtolower(trim($_GET['filter'])) : 'all';
@@ -47,7 +50,19 @@ try {
lm.local_file AS last_local_file,
lm.local_thumb AS last_local_thumb,
lm.created_at AS last_time,
- IFNULL(SUM(CASE WHEN c.direction = 'incoming' AND (c.is_read IS NULL OR c.is_read = 0) THEN 1 ELSE 0 END), 0) AS unread_count
+ IFNULL(SUM(CASE WHEN c.direction = 'incoming' AND (c.is_read IS NULL OR c.is_read = 0) THEN 1 ELSE 0 END), 0) AS unread_count";
+
+ // Si hay búsqueda, agregar campo para ordenar por relevancia
+ if (!empty($search)) {
+ $sql .= ",
+ (CASE
+ WHEN u.name LIKE ? THEN 3
+ WHEN u.phone_number LIKE ? THEN 2
+ ELSE 1
+ END) AS relevance";
+ }
+
+ $sql .= "
FROM users u
LEFT JOIN conversations c ON c.user_id = u.id
LEFT JOIN (
@@ -55,7 +70,23 @@ try {
JOIN (
SELECT user_id, MAX(created_at) AS last_time FROM conversations GROUP BY user_id
) t2 ON t1.user_id = t2.user_id AND t1.created_at = t2.last_time
- ) lm ON lm.user_id = u.id
+ ) lm ON lm.user_id = u.id";
+
+ // Agregar condición de búsqueda si existe
+ if (!empty($search)) {
+ $sql .= "
+ WHERE (
+ u.name LIKE ?
+ OR u.phone_number LIKE ?
+ OR EXISTS (
+ SELECT 1 FROM conversations c2
+ WHERE c2.user_id = u.id
+ AND c2.content LIKE ?
+ )
+ )";
+ }
+
+ $sql .= "
GROUP BY u.id";
// Aplicar filtro 'unread' si se solicita
@@ -63,15 +94,63 @@ try {
$sql .= " HAVING IFNULL(SUM(CASE WHEN c.direction = 'incoming' AND (c.is_read IS NULL OR c.is_read = 0) THEN 1 ELSE 0 END), 0) > 0";
}
- $sql .= "\n ORDER BY lm.created_at DESC\n LIMIT %d OFFSET %d";
+ // Ordenar por relevancia si hay búsqueda, sino por fecha
+ if (!empty($search)) {
+ $sql .= "\n ORDER BY relevance DESC, lm.created_at DESC";
+ } else {
+ $sql .= "\n ORDER BY lm.created_at DESC";
+ }
+
+ $sql .= "\n LIMIT %d OFFSET %d";
- $conversations = $db->fetchAll(sprintf($sql, $limit, $offset));
+ // Preparar parámetros para la consulta
+ $params = [];
+ if (!empty($search)) {
+ $searchParam = '%' . $search . '%';
+ // Para el campo de relevancia
+ $params[] = $searchParam; // name LIKE
+ $params[] = $searchParam; // phone_number LIKE
+ // Para el WHERE
+ $params[] = $searchParam; // name LIKE
+ $params[] = $searchParam; // phone_number LIKE
+ $params[] = $searchParam; // content LIKE
+ }
+
+ $finalSql = sprintf($sql, $limit, $offset);
+
+ if (!empty($params)) {
+ $conversations = $db->fetchAll($finalSql, $params);
+ } else {
+ $conversations = $db->fetchAll($finalSql);
+ }
// Conteo total de usuarios con al menos una conversación (útil para paginar)
- if ($filter === 'unread') {
- $totalRow = $db->fetch("SELECT COUNT(DISTINCT user_id) AS count FROM conversations c WHERE c.direction = 'incoming' AND (c.is_read IS NULL OR c.is_read = 0)");
+ if (!empty($search)) {
+ // Conteo con búsqueda
+ $searchParam = '%' . $search . '%';
+ $countSql = "SELECT COUNT(DISTINCT u.id) AS count
+ FROM users u
+ LEFT JOIN conversations c ON c.user_id = u.id
+ WHERE (
+ u.name LIKE ?
+ OR u.phone_number LIKE ?
+ OR EXISTS (
+ SELECT 1 FROM conversations c2
+ WHERE c2.user_id = u.id
+ AND c2.content LIKE ?
+ )
+ )";
+ if ($filter === 'unread') {
+ $countSql .= " AND c.direction = 'incoming' AND (c.is_read IS NULL OR c.is_read = 0)";
+ }
+ $totalRow = $db->fetch($countSql, [$searchParam, $searchParam, $searchParam]);
} else {
- $totalRow = $db->fetch("SELECT COUNT(DISTINCT user_id) AS count FROM conversations");
+ // Conteo normal sin búsqueda
+ if ($filter === 'unread') {
+ $totalRow = $db->fetch("SELECT COUNT(DISTINCT user_id) AS count FROM conversations c WHERE c.direction = 'incoming' AND (c.is_read IS NULL OR c.is_read = 0)");
+ } else {
+ $totalRow = $db->fetch("SELECT COUNT(DISTINCT user_id) AS count FROM conversations");
+ }
}
$total = isset($totalRow['count']) ? intval($totalRow['count']) : 0;
diff --git a/api/get_scheduled_messages.php b/api/get_scheduled_messages.php
new file mode 100644
index 0000000..74f9fa1
--- /dev/null
+++ b/api/get_scheduled_messages.php
@@ -0,0 +1,113 @@
+= ?";
+ $params[] = $dateFrom;
+ }
+
+ if ($dateTo) {
+ $sql .= " AND scheduled_date <= ?";
+ $params[] = $dateTo;
+ }
+
+ // Ordenar por fecha más cercana primero
+ $sql .= " ORDER BY scheduled_date ASC, scheduled_time ASC";
+
+ // Paginación
+ $sql .= " LIMIT ? OFFSET ?";
+ $params[] = $limit;
+ $params[] = $offset;
+
+ $messages = $db->fetchAll($sql, $params);
+
+ error_log('📋 get_scheduled_messages.php - Encontrados: ' . count($messages) . ' mensajes');
+ error_log('🔍 SQL ejecutado: ' . $sql);
+ error_log('📊 Parámetros: ' . json_encode($params));
+
+ // Decodificar parámetros JSON
+ foreach ($messages as &$message) {
+ if ($message['template_parameters']) {
+ $message['template_parameters'] = json_decode($message['template_parameters'], true);
+ }
+ }
+
+ // Contar total
+ $countSql = "SELECT COUNT(*) as total FROM scheduled_messages WHERE 1=1";
+ $countParams = [];
+
+ if ($userId) {
+ $countSql .= " AND user_id = ?";
+ $countParams[] = $userId;
+ }
+
+ if ($status) {
+ $countSql .= " AND status = ?";
+ $countParams[] = $status;
+ }
+
+ if ($dateFrom) {
+ $countSql .= " AND scheduled_date >= ?";
+ $countParams[] = $dateFrom;
+ }
+
+ if ($dateTo) {
+ $countSql .= " AND scheduled_date <= ?";
+ $countParams[] = $dateTo;
+ }
+
+ $countResult = $db->fetch($countSql, $countParams);
+ $total = $countResult['total'] ?? 0;
+
+ echo json_encode([
+ 'success' => true,
+ 'data' => $messages,
+ 'total' => $total,
+ 'limit' => $limit,
+ 'offset' => $offset
+ ]);
+
+} catch (Exception $e) {
+ error_log("Error in get_scheduled_messages.php: " . $e->getMessage());
+ http_response_code(500);
+ echo json_encode([
+ 'success' => false,
+ 'error' => 'Error obteniendo mensajes programados'
+ ]);
+}
diff --git a/api/get_template_details.php b/api/get_template_details.php
new file mode 100644
index 0000000..9620fe3
--- /dev/null
+++ b/api/get_template_details.php
@@ -0,0 +1,111 @@
+fetch(
+ "SELECT * FROM message_templates WHERE id = ?",
+ [$templateId]
+ );
+ } else {
+ $template = $db->fetch(
+ "SELECT * FROM message_templates WHERE template_name = ? AND language_code = ?",
+ [$templateName, $language]
+ );
+ }
+
+ if (!$template) {
+ throw new Exception('Plantilla no encontrada');
+ }
+
+ // Extraer variables del body_text o de example_parameters
+ $bodyText = $template['body_text'] ?? '';
+ $variables = [];
+
+ // Primero intentar usar las variables guardadas en example_parameters
+ if (!empty($template['example_parameters'])) {
+ $examples = json_decode($template['example_parameters'], true);
+ if (isset($examples['variables']) && is_array($examples['variables'])) {
+ // Usar las variables ya procesadas durante la sincronización
+ foreach ($examples['variables'] as $var) {
+ $varName = $var['name'] ?? $var['index'];
+ $variables[] = [
+ 'index' => $var['index'],
+ 'placeholder' => $var['placeholder'],
+ 'label' => is_numeric($varName) ? 'Variable ' . $varName : ucfirst(str_replace('_', ' ', $varName)),
+ 'required' => true,
+ 'example' => $var['example'] ?? null
+ ];
+ }
+ }
+ }
+
+ // Si no hay variables guardadas, extraerlas del body_text (fallback)
+ if (empty($variables) && preg_match_all('/\{\{([^\}]+)\}\}/', $bodyText, $matches)) {
+ $uniqueVars = array_unique($matches[1]);
+
+ $index = 1;
+ foreach ($uniqueVars as $varName) {
+ $varIndex = is_numeric($varName) ? intval($varName) : $index++;
+ $variables[] = [
+ 'index' => $varIndex,
+ 'placeholder' => '{{' . $varName . '}}',
+ 'label' => is_numeric($varName) ? 'Variable ' . $varName : ucfirst(str_replace('_', ' ', $varName)),
+ 'required' => true,
+ 'example' => null
+ ];
+ }
+ }
+
+ // Preparar respuesta
+ $response = [
+ 'success' => true,
+ 'template' => [
+ 'id' => $template['id'],
+ 'name' => $template['name'],
+ 'template_name' => $template['template_name'],
+ 'language_code' => $template['language_code'],
+ 'category' => $template['category'],
+ 'status' => $template['status'],
+ 'body_text' => $bodyText,
+ 'header_text' => $template['header_text'] ?? null,
+ 'header_type' => $template['header_type'] ?? null,
+ 'footer_text' => $template['footer_text'] ?? null,
+ 'has_variables' => count($variables) > 0,
+ 'variables_count' => count($variables),
+ 'variables' => $variables
+ ]
+ ];
+
+ echo json_encode($response, JSON_UNESCAPED_UNICODE);
+
+} catch (Exception $e) {
+ error_log("Error in get_template_details.php: " . $e->getMessage());
+ http_response_code(500);
+ echo json_encode([
+ 'success' => false,
+ 'error' => $e->getMessage()
+ ]);
+}
diff --git a/api/get_templates.php b/api/get_templates.php
index 4ba62a3..06c1f98 100644
--- a/api/get_templates.php
+++ b/api/get_templates.php
@@ -27,8 +27,22 @@ try {
$approvedOnly = isset($_GET['approved_only']) && $_GET['approved_only'] == '1';
$limit = isset($_GET['limit']) ? intval($_GET['limit']) : null;
- // Ajuste: some DBs may not have body_text column; omit it to avoid SQL errors
- $sql = "SELECT id, name, template_name, language_code, category, status, created_at FROM message_templates";
+ // Incluir todos los campos necesarios para la UI
+ $sql = "SELECT
+ id,
+ name,
+ template_name,
+ language_code,
+ category,
+ status,
+ body_text,
+ header_text,
+ header_type,
+ footer_text,
+ components,
+ example_parameters,
+ created_at
+ FROM message_templates";
$params = [];
if ($approvedOnly) {
diff --git a/api/get_users.php b/api/get_users.php
index 8c2b604..85d5ccb 100644
--- a/api/get_users.php
+++ b/api/get_users.php
@@ -24,8 +24,30 @@ header('Access-Control-Allow-Headers: Content-Type');
try {
$db = Database::getInstance();
- $users = $db->fetchAll(
- "SELECT
+ // Parámetros de paginación
+ $page = isset($_GET['page']) ? max(1, intval($_GET['page'])) : 1;
+ $limit = isset($_GET['limit']) ? max(1, min(100, intval($_GET['limit']))) : 20;
+ $offset = ($page - 1) * $limit;
+
+ // Búsqueda opcional
+ $search = isset($_GET['search']) ? trim($_GET['search']) : '';
+ $whereClause = '';
+ $params = [];
+
+ if (!empty($search)) {
+ $whereClause = "WHERE u.phone_number LIKE ? OR u.name LIKE ? OR u.email LIKE ?";
+ $searchParam = "%{$search}%";
+ $params = [$searchParam, $searchParam, $searchParam];
+ }
+
+ // Contar total de registros
+ $countQuery = "SELECT COUNT(*) as total FROM users u {$whereClause}";
+ $totalResult = $db->fetch($countQuery, $params);
+ $total = $totalResult['total'];
+ $totalPages = ceil($total / $limit);
+
+ // Obtener usuarios paginados
+ $query = "SELECT
u.id,
u.phone_number,
u.name,
@@ -38,12 +60,26 @@ try {
m.name as current_menu
FROM users u
LEFT JOIN menus m ON u.current_menu_id = m.id
- ORDER BY u.created_at DESC"
- );
+ {$whereClause}
+ ORDER BY u.created_at DESC
+ LIMIT ? OFFSET ?";
+
+ $params[] = $limit;
+ $params[] = $offset;
+
+ $users = $db->fetchAll($query, $params);
echo json_encode([
'success' => true,
- 'data' => $users
+ 'data' => $users,
+ 'pagination' => [
+ 'page' => $page,
+ 'limit' => $limit,
+ 'total' => $total,
+ 'totalPages' => $totalPages,
+ 'hasNext' => $page < $totalPages,
+ 'hasPrev' => $page > 1
+ ]
]);
} catch (Exception $e) {
diff --git a/api/list_admin_users.php b/api/list_admin_users.php
new file mode 100644
index 0000000..9e672e9
--- /dev/null
+++ b/api/list_admin_users.php
@@ -0,0 +1,44 @@
+fetchAll("
+ SELECT
+ id,
+ username,
+ full_name,
+ email,
+ is_active,
+ last_login,
+ created_at
+ FROM admin_users
+ ORDER BY created_at DESC
+ ");
+
+ echo json_encode([
+ 'success' => true,
+ 'data' => $users
+ ]);
+
+} catch (Exception $e) {
+ error_log('Error listing admin users: ' . $e->getMessage());
+ http_response_code(500);
+ echo json_encode([
+ 'success' => false,
+ 'error' => 'Error al obtener usuarios: ' . $e->getMessage()
+ ]);
+}
diff --git a/api/preview_template.php b/api/preview_template.php
new file mode 100644
index 0000000..17196fd
--- /dev/null
+++ b/api/preview_template.php
@@ -0,0 +1,130 @@
+fetch(
+ "SELECT * FROM message_templates WHERE id = ?",
+ [$templateId]
+ );
+
+ if (!$template) {
+ throw new Exception('Plantilla no encontrada');
+ }
+
+ // Obtener textos de la plantilla
+ $bodyText = $template['body_text'] ?? '';
+ $headerText = $template['header_text'] ?? null;
+ $footerText = $template['footer_text'] ?? null;
+
+ // Obtener variables guardadas para saber sus placeholders
+ $exampleParams = json_decode($template['example_parameters'] ?? '{}', true);
+ $savedVariables = $exampleParams['variables'] ?? [];
+
+ // Reemplazar variables en el body
+ $bodyPreview = $bodyText;
+ if (is_array($parameters)) {
+ // Si tenemos variables guardadas, usar sus placeholders
+ if (!empty($savedVariables)) {
+ foreach ($savedVariables as $var) {
+ $index = $var['index'] - 1; // Convertir a índice de array (0-based)
+ if (isset($parameters[$index])) {
+ $placeholder = $var['placeholder']; // Ejemplo: {{nombre_tema}} o {{1}}
+ $bodyPreview = str_replace($placeholder, $parameters[$index], $bodyPreview);
+ }
+ }
+ } else {
+ // Fallback: reemplazar variables numéricas estándar
+ foreach ($parameters as $index => $value) {
+ $placeholder = '{{' . ($index + 1) . '}}';
+ $bodyPreview = str_replace($placeholder, $value, $bodyPreview);
+ }
+ }
+ }
+
+ // Reemplazar variables en el header si existen
+ $headerPreview = $headerText;
+ if ($headerText && is_array($parameters)) {
+ if (!empty($savedVariables)) {
+ foreach ($savedVariables as $var) {
+ $index = $var['index'] - 1;
+ if (isset($parameters[$index])) {
+ $placeholder = $var['placeholder'];
+ $headerPreview = str_replace($placeholder, $parameters[$index], $headerPreview);
+ }
+ }
+ } else {
+ foreach ($parameters as $index => $value) {
+ $placeholder = '{{' . ($index + 1) . '}}';
+ $headerPreview = str_replace($placeholder, $value, $headerPreview);
+ }
+ }
+ }
+
+ // Verificar si quedan variables sin reemplazar (tanto numéricas como con nombres)
+ $hasUnreplacedVars = preg_match('/\{\{[^\}]+\}\}/', $bodyPreview);
+
+ // Generar vista previa HTML
+ $htmlPreview = '';
+
+ if ($headerPreview) {
+ $htmlPreview .= '';
+ }
+
+ $htmlPreview .= '';
+ $htmlPreview .= nl2br(htmlspecialchars($bodyPreview));
+ $htmlPreview .= '
';
+
+ if ($footerText) {
+ $htmlPreview .= '';
+ }
+
+ echo json_encode([
+ 'success' => true,
+ 'preview' => [
+ 'body' => $bodyPreview,
+ 'header' => $headerPreview,
+ 'footer' => $footerText,
+ 'html' => $htmlPreview,
+ 'has_unreplaced_variables' => $hasUnreplacedVars,
+ 'is_complete' => !$hasUnreplacedVars
+ ]
+ ], JSON_UNESCAPED_UNICODE);
+
+} catch (Exception $e) {
+ error_log("Error in preview_template.php: " . $e->getMessage());
+ http_response_code(500);
+ echo json_encode([
+ 'success' => false,
+ 'error' => $e->getMessage()
+ ]);
+}
diff --git a/api/schedule_message.php b/api/schedule_message.php
new file mode 100644
index 0000000..af53894
--- /dev/null
+++ b/api/schedule_message.php
@@ -0,0 +1,150 @@
+fetch("SELECT * FROM users WHERE id = ?", [$userId]);
+ if (!$user) {
+ throw new Exception('Usuario no encontrado');
+ }
+
+ // Preparar datos según el tipo de mensaje
+ $templateId = null;
+ $templateName = null;
+ $templateLanguage = 'es';
+ $templateParameters = null;
+ $messageContent = null;
+
+ if ($messageType === 'template') {
+ $templateId = isset($input['template_id']) ? intval($input['template_id']) : null;
+ $templateName = $input['template_name'] ?? null;
+ $templateLanguage = $input['template_language'] ?? 'es';
+ $templateParameters = $input['template_parameters'] ?? null;
+
+ error_log('🔍 template_parameters recibido: ' . json_encode($templateParameters));
+ error_log('🔍 Es array? ' . (is_array($templateParameters) ? 'Sí' : 'No'));
+
+ if (!$templateId && !$templateName) {
+ throw new Exception('Se requiere ID o nombre de plantilla');
+ }
+
+ // Si se proporcionaron parámetros, convertir a JSON
+ if ($templateParameters && is_array($templateParameters)) {
+ $templateParameters = json_encode($templateParameters, JSON_UNESCAPED_UNICODE);
+ error_log('✅ Convertido a JSON: ' . $templateParameters);
+ } else {
+ error_log('⚠️ No se convirtió - es null o no es array');
+ }
+
+ } else if ($messageType === 'text') {
+ $messageContent = $input['message_content'] ?? null;
+
+ if (empty($messageContent)) {
+ throw new Exception('Contenido del mensaje requerido');
+ }
+ } else {
+ throw new Exception('Tipo de mensaje no soportado');
+ }
+
+ // Obtener ID del admin actual
+ $createdBy = $_SESSION['admin_user']['id'] ?? $_SESSION['user_id'] ?? null;
+
+ // Insertar mensaje programado
+ $db->execute(
+ "INSERT INTO scheduled_messages (
+ user_id,
+ template_id,
+ template_name,
+ template_language,
+ template_parameters,
+ message_type,
+ message_content,
+ scheduled_date,
+ scheduled_time,
+ created_by,
+ status
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending')",
+ [
+ $userId,
+ $templateId,
+ $templateName,
+ $templateLanguage,
+ $templateParameters,
+ $messageType,
+ $messageContent,
+ $scheduledDate,
+ $scheduledTime,
+ $createdBy
+ ]
+ );
+
+ $scheduledId = $db->lastInsertId();
+
+ error_log('✅ Mensaje programado guardado con ID: ' . $scheduledId);
+
+ // Log
+ writeLog('INFO', "Mensaje programado creado: ID={$scheduledId}, Usuario={$userId}, Fecha={$scheduledDate} {$scheduledTime}");
+
+ echo json_encode([
+ 'success' => true,
+ 'message' => 'Recordatorio programado exitosamente',
+ 'data' => [
+ 'id' => $scheduledId,
+ 'scheduled_date' => $scheduledDate,
+ 'scheduled_time' => $scheduledTime,
+ 'user_name' => $user['name'],
+ 'phone_number' => $user['phone_number']
+ ]
+ ]);
+
+} catch (Exception $e) {
+ error_log("Error in schedule_message.php: " . $e->getMessage());
+ http_response_code(500);
+ echo json_encode([
+ 'success' => false,
+ 'error' => $e->getMessage()
+ ]);
+}
diff --git a/api/send_broadcast.php b/api/send_broadcast.php
index f5fb2e6..6af18ab 100644
--- a/api/send_broadcast.php
+++ b/api/send_broadcast.php
@@ -23,55 +23,214 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
try {
$input = json_decode(file_get_contents('php://input'), true);
- if (!$input || !isset($input['message'])) {
+ if (!$input) {
http_response_code(400);
- echo json_encode(['error' => 'Mensaje requerido']);
+ echo json_encode(['error' => 'Datos requeridos']);
exit;
}
- $filter = $input['filter'] ?? 'all';
- $message = $input['message'];
+ $selectionType = $input['selection_type'] ?? 'filter';
+ $messageType = $input['message_type'] ?? 'text';
$db = Database::getInstance();
$whatsappService = new WhatsAppService();
- // Construir query según filtro
- $whereClause = "WHERE u.status = 'active'";
+ // Obtener usuarios según tipo de selección
+ $users = [];
- switch ($filter) {
- case 'active':
- $whereClause .= " AND EXISTS (
- SELECT 1 FROM conversations c
- WHERE c.user_id = u.id
- AND c.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
- )";
- break;
-
- case 'recent':
- $whereClause .= " AND u.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)";
- break;
+ if ($selectionType === 'specific') {
+ // Usuarios específicos seleccionados
+ if (!isset($input['user_ids']) || !is_array($input['user_ids']) || empty($input['user_ids'])) {
+ http_response_code(400);
+ echo json_encode(['error' => 'Debes seleccionar al menos un usuario']);
+ exit;
+ }
+
+ $userIds = array_map('intval', $input['user_ids']);
+ $placeholders = implode(',', array_fill(0, count($userIds), '?'));
+ $query = "SELECT id, phone_number, name FROM users WHERE id IN ($placeholders) ORDER BY id ASC";
+ $users = $db->fetchAll($query, $userIds);
+
+ } else {
+ // Por filtro
+ $filter = $input['filter'] ?? 'all';
+ $whereClause = "1=1";
+
+ switch ($filter) {
+ case 'active':
+ $whereClause .= " AND EXISTS (
+ SELECT 1 FROM conversations c
+ WHERE c.user_id = u.id
+ AND c.created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
+ )";
+ break;
+
+ case 'recent':
+ $whereClause .= " AND u.created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)";
+ break;
+
+ case 'all':
+ // Incluir todos los usuarios
+ break;
+ }
+
+ $query = "SELECT id, phone_number, name FROM users u WHERE " . $whereClause . " ORDER BY id ASC";
+ $users = $db->fetchAll($query);
}
- // Obtener usuarios según filtro
- $users = $db->fetchAll(
- "SELECT phone_number FROM users u " . $whereClause
- );
+ if (empty($users)) {
+ http_response_code(404);
+ echo json_encode([
+ 'error' => 'No se encontraron usuarios para enviar',
+ 'selection_type' => $selectionType
+ ]);
+ exit;
+ }
$sentCount = 0;
$errorCount = 0;
+ $errors = [];
foreach ($users as $user) {
try {
- $response = $whatsappService->sendTextMessage($user['phone_number'], $message);
- if ($response) {
- $sentCount++;
+ $response = null;
+ $messageContent = '';
+
+ // Enviar según tipo de mensaje
+ if ($messageType === 'template') {
+ // Enviar plantilla
+ if (!isset($input['template_id'])) {
+ throw new Exception('ID de plantilla requerido');
+ }
+
+ $templateId = intval($input['template_id']);
+ $template = $db->fetch("SELECT * FROM message_templates WHERE id = ?", [$templateId]);
+
+ if (!$template) {
+ throw new Exception('Plantilla no encontrada');
+ }
+
+ $status = strtoupper($template['status']);
+ if ($status !== 'APPROVED') {
+ throw new Exception('La plantilla no está aprobada. Status: ' . $template['status']);
+ }
+
+ // Preparar variables
+ $templateVariables = $input['template_variables'] ?? [];
+
+ error_log('📝 Variables recibidas: ' . json_encode($templateVariables));
+ error_log('🔍 Tipo: ' . gettype($templateVariables));
+
+ // Detectar si son variables numéricas o con nombres
+ $isNumericVars = false;
+ $isNamedVars = false;
+
+ if (is_array($templateVariables) && !empty($templateVariables)) {
+ $firstKey = array_key_first($templateVariables);
+ if (is_int($firstKey) && isset($templateVariables[0])) {
+ // Array indexado numéricamente [0 => "val1", 1 => "val2"]
+ $isNumericVars = true;
+ error_log('✅ Variables tipo: Array numérico indexado');
+ } elseif (is_string($firstKey) && !is_numeric($firstKey)) {
+ // Array asociativo con nombres ["nombre_tema" => "val1", "fecha" => "val2"]
+ $isNamedVars = true;
+ error_log('✅ Variables tipo: Objeto con nombres (asociativo)');
+ } elseif (is_numeric($firstKey)) {
+ // Array con claves numéricas [1 => "val1", 2 => "val2"]
+ $isNumericVars = true;
+ error_log('✅ Variables tipo: Array con claves numéricas');
+ }
+ }
+
+ // Procesar según el tipo
+ $processedVariables = [];
+
+ if ($isNamedVars) {
+ // Variables con nombres: pasar como objeto asociativo
+ $processedVariables = $templateVariables;
+ error_log('📦 Enviando como objeto con nombres: ' . json_encode($processedVariables));
+ } elseif ($isNumericVars) {
+ // Variables numéricas: ordenar y convertir a array indexado
+ ksort($templateVariables, SORT_NUMERIC);
+ foreach ($templateVariables as $value) {
+ $processedVariables[] = $value;
+ }
+ error_log('📊 Enviando como array ordenado: ' . json_encode($processedVariables));
+ } else {
+ error_log('⚠️ Sin variables o formato desconocido');
+ }
+
+ error_log('📤 Variables procesadas: ' . json_encode($processedVariables));
+
+ // Verificar variables si la plantilla usa formato numérico
+ $expectedVars = preg_match_all('/\{\{(\d+)\}\}/', $template['body_text'] ?? '', $matches);
+ if ($expectedVars > 0 && $isNumericVars && count($processedVariables) !== $expectedVars) {
+ throw new Exception("La plantilla requiere {$expectedVars} variable(s) numéricas, pero se enviaron " . count($processedVariables));
+ }
+
+ $response = $whatsappService->sendTemplateMessage(
+ $user['phone_number'],
+ $template['template_name'] ?? $template['name'],
+ $template['language_code'] ?? 'es',
+ $processedVariables
+ );
+
+ // Construir contenido para BD (reemplazar variables en body_text)
+ $messageContent = $template['body_text'] ?? $template['name'];
+ if ($isNamedVars) {
+ // Reemplazar por nombres
+ foreach ($templateVariables as $key => $value) {
+ $messageContent = str_replace('{{' . $key . '}}', $value, $messageContent);
+ }
+ } else {
+ // Reemplazar por índice numérico
+ foreach ($processedVariables as $index => $value) {
+ $messageContent = str_replace('{{' . ($index + 1) . '}}', $value, $messageContent);
+ }
+ }
+
+ } else {
+ // Enviar texto simple
+ $message = trim($input['message'] ?? '');
+ if (empty($message)) {
+ throw new Exception('Mensaje vacío');
+ }
+
+ $response = $whatsappService->sendTextMessage($user['phone_number'], $message);
+ $messageContent = $message;
}
- // Pequeña pausa para no saturar la API
- usleep(100000); // 0.1 segundo
+ if ($response && isset($response['success']) && $response['success']) {
+ $sentCount++;
+
+ // Guardar mensaje en BD
+ $db->insert('conversations', [
+ 'user_id' => $user['id'],
+ 'message_id' => $response['message_id'] ?? null,
+ 'content' => $messageContent,
+ 'direction' => 'outgoing',
+ 'message_type' => $messageType === 'template' ? 'template' : 'text',
+ 'status' => 'sent',
+ 'created_at' => date('Y-m-d H:i:s')
+ ]);
+ } else {
+ $errorCount++;
+ $errors[] = [
+ 'phone' => $user['phone_number'],
+ 'error' => 'Respuesta inválida de WhatsApp'
+ ];
+ }
+
+ // Pausa para no saturar la API (250ms entre mensajes)
+ usleep(250000);
} catch (Exception $e) {
$errorCount++;
+ $errors[] = [
+ 'phone' => $user['phone_number'],
+ 'name' => $user['name'] ?? 'Sin nombre',
+ 'error' => $e->getMessage()
+ ];
error_log("Error sending broadcast to {$user['phone_number']}: " . $e->getMessage());
}
}
@@ -80,7 +239,10 @@ try {
'success' => true,
'sent_count' => $sentCount,
'error_count' => $errorCount,
- 'total_users' => count($users)
+ 'total_users' => count($users),
+ 'selection_type' => $selectionType,
+ 'message_type' => $messageType,
+ 'errors' => $errorCount > 0 ? $errors : null
]);
} catch (Exception $e) {
diff --git a/api/send_media_message_debug.log b/api/send_media_message_debug.log
index 19861fb..6b0350f 100644
--- a/api/send_media_message_debug.log
+++ b/api/send_media_message_debug.log
@@ -239,3 +239,27 @@
[2026-01-29 11:39:41] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSNzAyNEVCRkRGNUYwM0IzQURGAA=="}]}
[2026-01-29 11:39:41] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSNzAyNEVCRkRGNUYwM0IzQURGAA=="}]}
[2026-01-29 11:39:41] Saving to DB - content: record_1769704778262.webm, media_url: 906365935662402, local_file: uploads/media_697b8d4b88a6c2.66006027.ogg, local_thumb: null
+[2026-02-02 21:43:43] Raw input: {"recipient":"573168950803","media_url":"https://bot.u-s.app/uploads/media_698160df42ea78.64430762.ogg","media_type":"audio","caption":null,"filename":"record_1770086620567.webm","is_voice":true}
+[2026-02-02 21:43:43] Input decoded: {"recipient":"573168950803","media_url":"https:\/\/bot.u-s.app\/uploads\/media_698160df42ea78.64430762.ogg","media_type":"audio","caption":null,"filename":"record_1770086620567.webm","is_voice":true}
+[2026-02-02 21:43:43] is_voice: true
+[2026-02-02 21:43:44] Upload result: {"id":"1591364868775705"}
+[2026-02-02 21:43:44] Media ID obtained: 1591364868775705
+[2026-02-02 21:43:45] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSMDhGRTNDMUQ5NEFGQTRBQ0FDAA=="}]}
+[2026-02-02 21:43:45] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSMDhGRTNDMUQ5NEFGQTRBQ0FDAA=="}]}
+[2026-02-02 21:43:45] Saving to DB - content: record_1770086620567.webm, media_url: 1591364868775705, local_file: uploads/media_698160df42ea78.64430762.ogg, local_thumb: null
+[2026-02-03 08:52:11] Raw input: {"recipient":"573168950803","media_url":"https://bot.u-s.app/uploads/media_6981fd8bb34da0.05106104.xlsx","media_type":"document","caption":null,"filename":"20260122213820_CONSOLIDADO FERTILIZACION DESDE EL 2025-01-22 HASTA EL 2026-01-22.xlsx","is_voice":false}
+[2026-02-03 08:52:11] Input decoded: {"recipient":"573168950803","media_url":"https:\/\/bot.u-s.app\/uploads\/media_6981fd8bb34da0.05106104.xlsx","media_type":"document","caption":null,"filename":"20260122213820_CONSOLIDADO FERTILIZACION DESDE EL 2025-01-22 HASTA EL 2026-01-22.xlsx","is_voice":false}
+[2026-02-03 08:52:11] is_voice: false
+[2026-02-03 08:52:12] Upload result: {"id":"817602961329813"}
+[2026-02-03 08:52:12] Media ID obtained: 817602961329813
+[2026-02-03 08:52:13] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSMUVGMkUzOTNCNjBENTE4OEI1AA=="}]}
+[2026-02-03 08:52:13] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSMUVGMkUzOTNCNjBENTE4OEI1AA=="}]}
+[2026-02-03 08:52:13] Saving to DB - content: 20260122213820_CONSOLIDADO FERTILIZACION DESDE EL 2025-01-22 HASTA EL 2026-01-22.xlsx, media_url: 817602961329813, local_file: uploads/media_6981fd8bb34da0.05106104.xlsx, local_thumb: null
+[2026-02-03 16:20:01] Raw input: {"recipient":"573022548060","media_url":"https://bot.u-s.app/uploads/media_69826681178109.48762271.xlsx","media_type":"document","caption":null,"filename":"20260122213820_CONSOLIDADO FERTILIZACION DESDE EL 2025-01-22 HASTA EL 2026-01-22.xlsx","is_voice":false}
+[2026-02-03 16:20:01] Input decoded: {"recipient":"573022548060","media_url":"https:\/\/bot.u-s.app\/uploads\/media_69826681178109.48762271.xlsx","media_type":"document","caption":null,"filename":"20260122213820_CONSOLIDADO FERTILIZACION DESDE EL 2025-01-22 HASTA EL 2026-01-22.xlsx","is_voice":false}
+[2026-02-03 16:20:01] is_voice: false
+[2026-02-03 16:20:02] Upload result: {"id":"876532491851564"}
+[2026-02-03 16:20:02] Media ID obtained: 876532491851564
+[2026-02-03 16:20:02] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSMjAzNDlFQTVENzA2OEQ5QzkwAA=="}]}
+[2026-02-03 16:20:02] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573022548060","wa_id":"573022548060"}],"messages":[{"id":"wamid.HBgMNTczMDIyNTQ4MDYwFQIAERgSMjAzNDlFQTVENzA2OEQ5QzkwAA=="}]}
+[2026-02-03 16:20:02] Saving to DB - content: 20260122213820_CONSOLIDADO FERTILIZACION DESDE EL 2025-01-22 HASTA EL 2026-01-22.xlsx, media_url: 876532491851564, local_file: uploads/media_69826681178109.48762271.xlsx, local_thumb: null
diff --git a/api/send_message.php b/api/send_message.php
index e13ab18..2c4da67 100644
--- a/api/send_message.php
+++ b/api/send_message.php
@@ -163,10 +163,17 @@ try {
echo json_encode(['error' => 'Mensaje requerido']);
exit;
}
+
+ // Procesar saltos de línea escapados (\\n o \n) -> convertir a saltos reales
+ $message = $input['message'];
+ $message = str_replace('\\n', "\n", $message); // \n escapado literal
+ $message = preg_replace('/(?sendTextReply($recipient, $replyTo, $input['message'], $operatorId ? ['operator_id' => $operatorId] : null);
+ $response = $whatsappService->sendTextReply($recipient, $replyTo, $message, $operatorId ? ['operator_id' => $operatorId] : null);
} else {
- $response = $whatsappService->sendTextMessage($recipient, $input['message'], $operatorId ? ['operator_id' => $operatorId] : null);
+ $response = $whatsappService->sendTextMessage($recipient, $message, $operatorId ? ['operator_id' => $operatorId] : null);
}
break;
diff --git a/api/sync_templates_from_facebook.php b/api/sync_templates_from_facebook.php
new file mode 100644
index 0000000..cbd4b84
--- /dev/null
+++ b/api/sync_templates_from_facebook.php
@@ -0,0 +1,266 @@
+ $varIndex,
+ 'placeholder' => "{{" . $varName . "}}",
+ 'name' => $varName,
+ 'example' => $example
+ ];
+ }
+
+ error_log("✅ Variables procesadas para '{$templateName}': " . json_encode($variables, JSON_UNESCAPED_UNICODE));
+ }
+ }
+
+ // Si encontramos variables, agregarlas a example_parameters
+ if (!empty($variables)) {
+ $exampleParameters['variables'] = $variables;
+ error_log("💾 example_parameters final para '{$templateName}': " . json_encode($exampleParameters, JSON_UNESCAPED_UNICODE));
+ }
+
+ // Preparar datos para guardar
+ $componentsJson = !empty($components) ? json_encode($components, JSON_UNESCAPED_UNICODE) : null;
+ $exampleJson = !empty($exampleParameters) ? json_encode($exampleParameters, JSON_UNESCAPED_UNICODE) : null;
+
+ // Verificar si la plantilla ya existe
+ $existing = $db->fetch(
+ "SELECT id, status FROM message_templates WHERE template_name = ? AND language_code = ?",
+ [$templateName, $language]
+ );
+
+ if ($existing) {
+ // Actualizar plantilla existente con nuevos campos
+ $db->execute(
+ "UPDATE message_templates SET
+ status = ?,
+ category = ?,
+ body_text = ?,
+ header_text = ?,
+ header_type = ?,
+ footer_text = ?,
+ components = ?,
+ example_parameters = ?,
+ updated_at = NOW()
+ WHERE id = ?",
+ [
+ strtolower($status),
+ strtolower($category),
+ $bodyText,
+ $headerText,
+ $headerType,
+ $footerText,
+ $componentsJson,
+ $exampleJson,
+ $existing['id']
+ ]
+ );
+ $updatedCount++;
+ } else {
+ // Insertar nueva plantilla con componentes
+ $db->execute(
+ "INSERT INTO message_templates (
+ name,
+ template_name,
+ language_code,
+ category,
+ status,
+ body_text,
+ header_text,
+ header_type,
+ footer_text,
+ components,
+ example_parameters,
+ created_at
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())",
+ [
+ $templateName, // nombre descriptivo igual al nombre técnico
+ $templateName,
+ $language,
+ strtolower($category),
+ strtolower($status),
+ $bodyText,
+ $headerText,
+ $headerType,
+ $footerText,
+ $componentsJson,
+ $exampleJson
+ ]
+ );
+ $syncedCount++;
+ }
+ } catch (Exception $e) {
+ $errors[] = "Error procesando plantilla '{$templateName}': " . $e->getMessage();
+ }
+ }
+
+ // Log de la sincronización
+ writeLog('INFO', "Plantillas sincronizadas desde Facebook: {$syncedCount} nuevas, {$updatedCount} actualizadas, {$skippedCount} sin cambios");
+
+ echo json_encode([
+ 'success' => true,
+ 'message' => 'Plantillas sincronizadas correctamente',
+ 'data' => [
+ 'total' => count($templates),
+ 'synced' => $syncedCount,
+ 'updated' => $updatedCount,
+ 'skipped' => $skippedCount,
+ 'errors' => $errors
+ ]
+ ]);
+
+} catch (Exception $e) {
+ error_log("Error in sync_templates_from_facebook.php: " . $e->getMessage());
+ http_response_code(500);
+ echo json_encode([
+ 'success' => false,
+ 'error' => $e->getMessage()
+ ]);
+}
diff --git a/api/toggle_admin_user_status.php b/api/toggle_admin_user_status.php
new file mode 100644
index 0000000..100fb72
--- /dev/null
+++ b/api/toggle_admin_user_status.php
@@ -0,0 +1,50 @@
+execute(
+ "UPDATE admin_users SET is_active = ? WHERE id = ?",
+ [$isActive ? 1 : 0, $userId]
+ );
+
+ echo json_encode([
+ 'success' => true,
+ 'message' => 'Estado actualizado correctamente'
+ ]);
+
+} catch (Exception $e) {
+ error_log('Error toggling admin user status: ' . $e->getMessage());
+ http_response_code(500);
+ echo json_encode([
+ 'success' => false,
+ 'error' => $e->getMessage()
+ ]);
+}
diff --git a/api/update_admin_user.php b/api/update_admin_user.php
new file mode 100644
index 0000000..49540fd
--- /dev/null
+++ b/api/update_admin_user.php
@@ -0,0 +1,61 @@
+fetch(
+ "SELECT id FROM admin_users WHERE username = ? AND id != ?",
+ [$username, $userId]
+ );
+
+ if ($existing) {
+ throw new Exception('El nombre de usuario ya está en uso');
+ }
+
+ // Actualizar usuario
+ $db->execute(
+ "UPDATE admin_users SET username = ?, full_name = ?, email = ? WHERE id = ?",
+ [$username, $fullName, $email, $userId]
+ );
+
+ echo json_encode([
+ 'success' => true,
+ 'message' => 'Usuario actualizado correctamente'
+ ]);
+
+} catch (Exception $e) {
+ error_log('Error updating admin user: ' . $e->getMessage());
+ http_response_code(500);
+ echo json_encode([
+ 'success' => false,
+ 'error' => $e->getMessage()
+ ]);
+}
diff --git a/api/update_scheduled_message.php b/api/update_scheduled_message.php
new file mode 100644
index 0000000..3132362
--- /dev/null
+++ b/api/update_scheduled_message.php
@@ -0,0 +1,101 @@
+fetch("SELECT * FROM scheduled_messages WHERE id = ?", [$id]);
+
+ if (!$existing) {
+ throw new Exception('Mensaje programado no encontrado');
+ }
+
+ // No permitir editar mensajes ya enviados
+ if ($existing['status'] === 'sent') {
+ throw new Exception('No se puede editar un mensaje ya enviado');
+ }
+
+ // Preparar campos a actualizar
+ $updates = [];
+ $params = [];
+
+ if (isset($input['scheduled_date'])) {
+ $updates[] = "scheduled_date = ?";
+ $params[] = $input['scheduled_date'];
+ }
+
+ if (isset($input['scheduled_time'])) {
+ $updates[] = "scheduled_time = ?";
+ $params[] = $input['scheduled_time'];
+ }
+
+ if (isset($input['template_parameters'])) {
+ $updates[] = "template_parameters = ?";
+ $params[] = is_array($input['template_parameters'])
+ ? json_encode($input['template_parameters'], JSON_UNESCAPED_UNICODE)
+ : $input['template_parameters'];
+ }
+
+ if (isset($input['message_content'])) {
+ $updates[] = "message_content = ?";
+ $params[] = $input['message_content'];
+ }
+
+ if (isset($input['status'])) {
+ $updates[] = "status = ?";
+ $params[] = $input['status'];
+ }
+
+ if (empty($updates)) {
+ throw new Exception('No hay campos para actualizar');
+ }
+
+ // Agregar updated_at
+ $updates[] = "updated_at = NOW()";
+
+ // Agregar ID al final de params
+ $params[] = $id;
+
+ // Ejecutar actualización
+ $sql = "UPDATE scheduled_messages SET " . implode(', ', $updates) . " WHERE id = ?";
+ $db->execute($sql, $params);
+
+ writeLog('INFO', "Mensaje programado actualizado: ID={$id}");
+
+ echo json_encode([
+ 'success' => true,
+ 'message' => 'Mensaje programado actualizado exitosamente'
+ ]);
+
+} catch (Exception $e) {
+ error_log("Error in update_scheduled_message.php: " . $e->getMessage());
+ http_response_code(500);
+ echo json_encode([
+ 'success' => false,
+ 'error' => $e->getMessage()
+ ]);
+}
diff --git a/api/upload_media.php b/api/upload_media.php
index 2218f5f..a524507 100644
--- a/api/upload_media.php
+++ b/api/upload_media.php
@@ -163,10 +163,18 @@ try {
}
}
- // Construir URL pública
- $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
- $host = $_SERVER['HTTP_HOST'];
- $publicUrl = $protocol . '://' . $host . '/uploads/' . $uniqueName;
+ // Construir URL pública usando APP_URL del .env si está disponible
+ // Esto asegura que los archivos sean accesibles públicamente desde WhatsApp (datos móviles)
+ $appUrl = getenv('APP_URL');
+ if ($appUrl && $appUrl !== '') {
+ // Usar APP_URL del .env (ej: https://bot.u-s.app/)
+ $publicUrl = rtrim($appUrl, '/') . '/uploads/' . $uniqueName;
+ } else {
+ // Fallback: auto-detectar desde HTTP_HOST (desarrollo local)
+ $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
+ $host = $_SERVER['HTTP_HOST'];
+ $publicUrl = $protocol . '://' . $host . '/uploads/' . $uniqueName;
+ }
// Determinar tipo de medio
$mediaType = 'document';
diff --git a/api/version/media-url_debug.log b/api/version/media-url_debug.log
index 48796dd..1e19b5e 100644
--- a/api/version/media-url_debug.log
+++ b/api/version/media-url_debug.log
@@ -279,3 +279,138 @@
[2026-01-28 03:19:02] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
[2026-01-28 03:19:02] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1769588642&hash=ARkCJbkmByU2tdLOv2EHpSH0F12MJjM_Oqj-hiGgNVSYAg
[2026-01-28 03:19:02] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1769588642&hash=ARl75SVpu4vAnsH1AXoBGSv5sjftMr7JPocagoN5kIdMWQ
+[2026-02-03 08:52:36] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 08:52:36] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 08:52:36] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 08:52:36] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770127056&hash=ARmn_DQsFhxynN1KXT-Ge4Xf8WLnjXtDlWoDsUomB4lLKg
+[2026-02-03 08:52:36] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770127056&hash=ARnKjD4rWdn1Lf97robC2d7AyEHpxH6a2_oPLhmgZ0I9pA
+[2026-02-03 08:52:57] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 08:52:57] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 08:52:57] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 08:52:57] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770127077&hash=ARldzoeUm09JzYXDKy0ypZS4VLSt5A541gqPHgUu-u9geA
+[2026-02-03 08:52:57] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770127077&hash=ARmAMwa-nPL80hEmwrHHiSHiMhllm6JUl3UG413ZbMkpnA
+[2026-02-03 08:52:59] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 08:52:59] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 08:52:59] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 08:52:59] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770127079&hash=ARmB9s1otyY0Zp-uU577Ezen_iMY_waheMG8EMOMTf4yvA
+[2026-02-03 08:52:59] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770127079&hash=ARlMcDHlgltZRpheK41ar-GLM_sSPXV_8yudSsLXa4IOJw
+[2026-02-03 08:53:52] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 08:53:52] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 08:53:52] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 08:53:53] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770127133&hash=ARmVgexderjai88OImGBkEGRonyefGBrm1-ow633oVjF5w
+[2026-02-03 08:53:53] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770127133&hash=ARleR9CvdH_ZXgcEKan0BggNv6osT2pUwtC3qumta4gttA
+[2026-02-03 08:53:53] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 08:53:53] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 08:53:53] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 08:53:53] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770127133&hash=ARmVgexderjai88OImGBkEGRonyefGBrm1-ow633oVjF5w
+[2026-02-03 08:53:53] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770127133&hash=ARleR9CvdH_ZXgcEKan0BggNv6osT2pUwtC3qumta4gttA
+[2026-02-03 11:15:58] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 11:15:58] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 11:15:58] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 11:15:59] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770135659&hash=ARlKSkH_tfyiy1Ctx7mwxi3aZ2JrsWdWcK9I73EldYmKeg
+[2026-02-03 11:15:59] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770135659&hash=ARmOinKL4W0exQVUyOxwpLCkmPghiI0rzBv22kDEYwbePA
+[2026-02-03 12:00:15] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 12:00:15] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 12:00:15] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 12:00:15] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770138315&hash=ARnn0qsPekd9pOK3dw85bzArN5oMXws99mc_zs6cdK0wtQ
+[2026-02-03 12:00:15] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770138315&hash=ARlAM-vxY5Y6ST1Lwp8ox6SBOAefCdZ9yWVZKM-04kcgqQ
+[2026-02-03 12:45:14] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 12:45:14] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 12:45:14] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 12:45:14] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770141014&hash=ARn79fTfe65Rz38MXO0hhmyLSuVJsGuIzM30zAngMKVSeA
+[2026-02-03 12:45:14] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770141014&hash=ARknEbp3Lf9RYQ2daDdcExwkmvWlMb1F_DLBhGQYJhqXtQ
+[2026-02-03 14:22:03] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 14:22:03] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 14:22:03] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 14:22:03] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770146823&hash=ARmkpGGK9nwQCAqwRqTNu8hZdxaLvFe_QqKCZDa60vjQMw
+[2026-02-03 14:22:03] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770146823&hash=ARkmJ5x9KoRtE0e7E2j3ao1uDWPpEzVsJ3mSCUza8UjbKQ
+[2026-02-03 14:23:56] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 14:23:56] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 14:23:56] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 14:23:57] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770146937&hash=ARlFlA7KVAre979qNou-DdifbKg_QVenRMQ-ocl3p7vqjg
+[2026-02-03 14:23:57] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770146937&hash=ARnJ0xX1NUbsvypvvoZA6UwXnFioUbFugAFUqwrBpQh23Q
+[2026-02-03 14:28:39] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 14:28:39] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 14:28:39] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 14:28:39] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770147219&hash=ARkHFZc5AuDMwNI48w_-THsJM1CoGx3G8CtP-N7fcdBBtQ
+[2026-02-03 14:28:39] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770147219&hash=ARmjcb4sF70-VmiQiXeLl3p8XSaAjjGF9zbh339aUlnFOg
+[2026-02-03 14:34:25] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 14:34:25] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 14:34:25] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 14:34:25] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770147565&hash=ARk8Q6oLaHEvxv7WNOGtOwkgB8RDWPs7xk672ACpSuC-3A
+[2026-02-03 14:34:25] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770147565&hash=ARkNv8rIgcXUHoj2i9ZwZQBW88W7p1yab6a_arnqGzbJtw
+[2026-02-03 14:39:57] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 14:39:57] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 14:39:57] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 14:39:58] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770147898&hash=ARnRWSW5FuBObUTXyIRf_5mEV90eLmf4Kx4hft5MAQH7fw
+[2026-02-03 14:39:58] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770147898&hash=ARlVjspTqGVhZzOq1D-cMbpj7AXP3bNSacmtV3ZIg_ALqA
+[2026-02-03 14:49:54] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 14:49:54] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 14:49:54] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 14:49:55] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770148494&hash=ARka8CBIIFFl2uNVBdAnHVJldB8vBjgU0NbvGVDabWBfsw
+[2026-02-03 14:49:55] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770148494&hash=ARnz6olwrZgd3SpTw-AbpdOo1lcbFr72hX3T6CcVDX9yzw
+[2026-02-03 14:50:00] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 14:50:00] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 14:50:00] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 14:50:00] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770148500&hash=ARkBigqY86ltDKoAFz_5B1g5uDgpegUSHtL4OGJeQME8rw
+[2026-02-03 14:50:00] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770148500&hash=ARmtsjCTyftZSmwlVDnK0QCoYEzA8-NtYX6avOCtr-belQ
+[2026-02-03 15:43:01] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 15:43:01] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 15:43:01] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 15:43:02] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770151682&hash=ARnTTvbQWWCa7LYWf9S55Dg6MSLjjFKtI7nWR0bN2zvDQQ
+[2026-02-03 15:43:02] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770151682&hash=ARlo5HOK4ztNMJITAnXP2SkjsasuE9VDkgnxXYFUC-t4ng
+[2026-02-03 15:46:16] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 15:46:16] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 15:46:16] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 15:46:16] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770151876&hash=ARkTtYYdsUFAiZwrYv2rgdGGiEc-D59t4NMElrHDHtA_Vw
+[2026-02-03 15:46:16] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770151876&hash=ARlQUE5zvswz5tBhKIWtAYtD-E7qr9nTCcpuEoYi3nzDLA
+[2026-02-03 15:46:33] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 15:46:33] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 15:46:33] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 15:46:34] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770151894&hash=ARm8Ao4ZAndoLc5sPn6u44DIOtYTW9ck-en0zxlbgOCIRA
+[2026-02-03 15:46:34] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770151894&hash=ARlLvqyHLBLf1wJwmDZZTlqQT0BpMvqj_hqmhijSVdCbxw
+[2026-02-03 15:47:11] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 15:47:11] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 15:47:11] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 15:47:11] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770151931&hash=ARmC5M11laF_p8bRXwSW0aUa6BZ5p6HLJ4xFt5i_rhGelg
+[2026-02-03 15:47:11] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770151931&hash=ARklCUN4q5XJ2DcsvgXtl1clURKnFJGZ9FB1VMXZ4JUTlA
+[2026-02-03 15:52:33] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 15:52:33] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 15:52:33] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 15:52:34] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770152254&hash=ARkc8acjikfeqPK2EEA-lsA6To-kLYCbC0G1N5I-sgDklw
+[2026-02-03 15:52:34] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770152254&hash=ARkq5Bv1Yx0I8YZVnRPl1GY3mRSjiTZsTeKecOm3WWP6fA
+[2026-02-03 15:54:38] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 15:54:38] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 15:54:38] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 15:54:38] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770152378&hash=ARkd0Smk6IQrA7Z-TtMBMGK080-7tiill2lA697bnJdzQQ
+[2026-02-03 15:54:38] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770152378&hash=ARmCvtVqy3cbEBhreSiBlGMz_k0H0C1q3FglE1cAzOMtsA
+[2026-02-03 15:54:55] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 15:54:55] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 15:54:56] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 15:54:56] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770152396&hash=ARm4I-TH2FgoFVdYU6vyaEldYprMRcQMSUZD7-mteyE2bQ
+[2026-02-03 15:54:56] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770152396&hash=ARmySCPJWQXfIqv5WakKH2es6o2rlrkmd_QeRWbDRDhMjA
+[2026-02-03 16:17:56] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 16:17:56] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 16:17:56] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 16:17:56] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770153776&hash=ARkpL17QRsFrIZ9gL6jP87dZLx9WL80OCkUG2QOBFeE6Iw
+[2026-02-03 16:17:56] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770153776&hash=ARnVTurshL8o0fva9f7ThLQBCNVvOwYex9n-d0Guelq_3w
+[2026-02-03 16:18:30] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 16:18:30] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 16:18:30] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 16:18:30] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770153810&hash=ARkFailq-_F7K1gQIj0Tyqh1KJ_mD8HHlC79mPeupj6g7A
+[2026-02-03 16:18:30] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770153810&hash=ARmcNab_e2NsTTxNcuj_VUmj1_G1h2pdSPXQmEF_T7IZ7A
+[2026-02-03 16:20:02] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 16:20:02] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 16:20:02] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 16:20:03] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770153902&hash=ARmkQUNq3ncSrBHcO337pHLH9_Mrv89BMga1M52iHJjAAA
+[2026-02-03 16:20:03] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770153903&hash=ARkcsEFiF-VZtOHz8UIqPbUlftOVB3pxOmlyOhHWvHwNUA
+[2026-02-03 16:20:10] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 16:20:10] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 16:20:10] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 16:20:11] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770153911&hash=ARlsB6UeRRokyVUn_K3GY56WCvw4M7IiTI-0YYDgWKtDXA
+[2026-02-03 16:20:11] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770153911&hash=ARnhrJuYCnG-1FpfxhUU638YidJDdCp_9Pusp4nmWCCepQ
+[2026-02-03 20:31:47] Request: GET /api/version/media-url.php?url=https%3A%2F%2Flookaside.fbsbx.com%2Fwhatsapp_business%2Fattachments%2F%3Fmid%3D2371342336638440%26source%3Dwebhook%26ext%3D1769575796%26hash%3DARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA GET:{"url":"https:\/\/lookaside.fbsbx.com\/whatsapp_business\/attachments\/?mid=2371342336638440&source=webhook&ext=1769575796&hash=ARndVKdw1p0CCx3_DpTblR2aOVkb_W5mbTqGFPSOK77cjA"} POST:[]
+[2026-02-03 20:31:47] Request: GET /api/version/media-url.php?id=1217156256734654 GET:{"id":"1217156256734654"} POST:[]
+[2026-02-03 20:31:47] Graph API request to https://graph.facebook.com/v22.0/1217156256734654
+[2026-02-03 20:31:48] Proxying Facebook media URL with Authorization to https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=1217156256734654&source=getMedia&ext=1770169008&hash=ARmSF2NK4GqmW53yaJd3NCLYub_AGbau7EHf5WOOETVxbw
+[2026-02-03 20:31:48] Resolved media URL from Graph for mid 2371342336638440: https://lookaside.fbsbx.com/whatsapp_business/attachments/?mid=2371342336638440&source=getMedia&ext=1770169008&hash=ARlAZuDk9UsmFsMvn4qiAOOAk45RwETVe04h9vkJdDruXg
diff --git a/assets/js/app.js b/assets/js/app.js
index 6194c83..50b2d58 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -87,6 +87,15 @@ class WhatsAppBotManager {
});
}
+ // Contador de caracteres para broadcast
+ const broadcastMessage = document.getElementById('broadcast-message');
+ const charCount = document.getElementById('char-count');
+ if (broadcastMessage && charCount) {
+ broadcastMessage.addEventListener('input', (e) => {
+ charCount.textContent = e.target.value.length;
+ });
+ }
+
// Cambio de tipo de mensaje
const messageType = document.getElementById('message-type');
if (messageType) {
@@ -695,9 +704,79 @@ class WhatsAppBotManager {
}
async sendBroadcast() {
- // Funcionalidad de envío masivo deshabilitada temporalmente desde UI
- this.showError('Envío masivo deshabilitado temporalmente');
- return;
+ const selectionType = document.getElementById('broadcast-selection-type').value;
+ const messageType = document.getElementById('broadcast-message-type').value;
+
+ let payload = {
+ selection_type: selectionType,
+ message_type: messageType
+ };
+
+ // Validar según tipo de selección
+ if (selectionType === 'filter') {
+ payload.filter = document.getElementById('broadcast-filter').value;
+ } else {
+ const selectedUsers = $('#broadcast-users').val();
+ if (!selectedUsers || selectedUsers.length === 0) {
+ this.showError('Debes seleccionar al menos un usuario');
+ return;
+ }
+ payload.user_ids = selectedUsers;
+ }
+
+ // Validar según tipo de mensaje
+ if (messageType === 'text') {
+ const message = document.getElementById('broadcast-message').value;
+ if (!message.trim()) {
+ this.showError('El mensaje no puede estar vacío');
+ return;
+ }
+ payload.message = message;
+ } else {
+ const templateId = document.getElementById('broadcast-template').value;
+ if (!templateId) {
+ this.showError('Debes seleccionar una plantilla');
+ return;
+ }
+
+ payload.template_id = templateId;
+
+ // Recoger variables
+ const variables = {};
+ document.querySelectorAll('.broadcast-template-var').forEach(input => {
+ variables[input.dataset.var] = input.value;
+ });
+ payload.template_variables = variables;
+ }
+
+ // Confirmar envío
+ const targetText = selectionType === 'filter'
+ ? `usuarios filtrados por "${payload.filter}"`
+ : `${payload.user_ids.length} usuario(s) seleccionado(s)`;
+ const messageText = messageType === 'text' ? 'mensaje de texto' : 'plantilla';
+
+ if (!confirm(`¿Enviar este ${messageText} a ${targetText}?`)) {
+ return;
+ }
+
+ try {
+ this.showLoading('Enviando mensajes masivos...');
+ const result = await this.apiCall('send_broadcast.php', 'POST', payload);
+
+ if (result.success) {
+ this.showSuccess(`✅ Mensajes enviados exitosamente!\nEnviados: ${result.sent_count}\nErrores: ${result.error_count}\nTotal usuarios: ${result.total_users}`);
+ document.getElementById('broadcast-form').reset();
+ $('#broadcast-users').val(null).trigger('change');
+ document.getElementById('broadcast-template-variables').style.display = 'none';
+ document.getElementById('broadcast-template-preview').style.display = 'none';
+ } else {
+ this.showError(result.error || 'Error enviando broadcast');
+ }
+ } catch (error) {
+ this.showError('Error enviando broadcast: ' + error.message);
+ } finally {
+ this.hideLoading();
+ }
}
async saveMenu() {
diff --git a/assets/js/app_simple.js b/assets/js/app_simple.js
index 53b484b..3238e62 100644
--- a/assets/js/app_simple.js
+++ b/assets/js/app_simple.js
@@ -112,12 +112,21 @@ class SimpleWhatsAppManager {
case 'users':
this.loadUsers();
break;
+ case 'admin_users':
+ this.loadAdminUsers();
+ break;
case 'send_message':
this.loadconversations();
break;
case 'templates':
this.loadTemplates();
break;
+ case 'broadcast':
+ // Recargar plantillas cuando se abre el tab de broadcast
+ if (typeof loadBroadcastTemplates === 'function') {
+ loadBroadcastTemplates();
+ }
+ break;
case 'menus':
this.loadMenus();
break;
@@ -403,14 +412,19 @@ class SimpleWhatsAppManager {
}
}
- async loadUsers() {
- this.log('Cargando usuarios');
+ async loadUsers(page = 1, search = '') {
+ this.log('Cargando usuarios - página ' + page);
try {
- const response = await this.apiCall('get_users.php');
+ let url = `get_users.php?page=${page}&limit=20`;
+ if (search) {
+ url += `&search=${encodeURIComponent(search)}`;
+ }
+
+ const response = await this.apiCall(url);
if (response && response.success) {
- this.updateUsersList(response.data || []);
+ this.updateUsersList(response.data || [], response.pagination || {});
} else {
this.showError('No se pudieron cargar los usuarios');
}
@@ -420,15 +434,17 @@ class SimpleWhatsAppManager {
}
}
- updateUsersList(users) {
+ updateUsersList(users, pagination = {}) {
const container = document.getElementById('users-table');
if (!container) return;
// Guardar datos para uso posterior
this.usersData = users;
+ this.usersPagination = pagination;
if (users.length === 0) {
container.innerHTML = '| No hay usuarios registrados |
';
+ this.updateUsersPagination({});
return;
}
@@ -459,6 +475,75 @@ class SimpleWhatsAppManager {
});
container.innerHTML = html;
+ this.updateUsersPagination(pagination);
+ }
+
+ updateUsersPagination(pagination) {
+ const paginationContainer = document.getElementById('users-pagination');
+ if (!paginationContainer) return;
+
+ if (!pagination.totalPages || pagination.totalPages <= 1) {
+ paginationContainer.innerHTML = '';
+ return;
+ }
+
+ const currentPage = pagination.page || 1;
+ const totalPages = pagination.totalPages;
+ const total = pagination.total || 0;
+
+ let html = `
+
+
+ Mostrando página ${currentPage} de ${totalPages} (${total} usuarios en total)
+
+
+
+ `;
+
+ paginationContainer.innerHTML = html;
}
async loadConversations() {
@@ -716,6 +801,7 @@ class SimpleWhatsAppManager {
const fields = [
{ id: 'whatsapp-token', value: data.whatsapp_token },
{ id: 'phone-number-id', value: data.phone_number_id },
+ { id: 'business-account-id', value: data.business_account_id },
{ id: 'webhook-token', value: data.webhook_verify_token },
{ id: 'api-url', value: data.api_url },
{ id: 'business-name', value: data.business_name },
@@ -739,6 +825,7 @@ class SimpleWhatsAppManager {
const system_config = {
token: document.getElementById('whatsapp-token').value,
phone_number_id: document.getElementById('phone-number-id').value,
+ business_account_id: document.getElementById('business-account-id').value,
webhook_verify_token: document.getElementById('webhook-token').value,
api_url: document.getElementById('api-url').value,
business_name: document.getElementById('business-name').value,
@@ -831,6 +918,9 @@ class SimpleWhatsAppManager {
}
this.log(`Usuarios cargados: ${users.length}, Plantillas cargadas: ${templates.length}`);
+
+ // Guardar plantillas para uso posterior
+ this.templatesData = templates;
if (users.length > 0) {
this.populateUsersSelect(users);
@@ -875,13 +965,15 @@ class SimpleWhatsAppManager {
// Limpiar opciones existentes (excepto la primera)
select.innerHTML = '';
- // 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);
- });
+ // Agregar plantillas (solo las aprobadas)
+ templates
+ .filter(template => template.status && template.status.toUpperCase() === 'APPROVED')
+ .forEach(template => {
+ const option = document.createElement('option');
+ option.value = template.id; // Usar ID en lugar de template_name
+ option.textContent = `${template.name} - ${template.category || 'Sin descripción'}`;
+ select.appendChild(option);
+ });
}
async loadTemplates() {
@@ -1367,38 +1459,90 @@ class SimpleWhatsAppManager {
return;
}
- // Prepare template payload; support named parameters (e.g. { name: 'Juan' })
- const tplName = templateSelect.value;
- let tplParams = null;
+ // Obtener ID y nombre de la plantilla
+ const templateId = templateSelect.value;
+ const selectedOption = templateSelect.options[templateSelect.selectedIndex];
+
+ // Buscar la plantilla en templatesData para obtener el template_name
+ let templateName = null;
+ if (this.templatesData) {
+ const template = this.templatesData.find(t => t.id == templateId);
+ if (template) {
+ templateName = template.template_name;
+ }
+ }
+
+ if (!templateName) {
+ this.showError('No se pudo obtener el nombre de la plantilla');
+ return;
+ }
- // If there's a hidden input with JSON parameters (index.php), use it
+ // Recopilar variables de los inputs
+ let tplParams = {};
+ const varInputs = document.querySelectorAll('.message-template-var');
+
+ if (varInputs.length > 0) {
+ console.log(`🔍 Recopilando ${varInputs.length} variables de la plantilla`);
+
+ // Detectar si las variables son numéricas o con nombres
+ let hasNumericVars = false;
+ let hasNamedVars = false;
+
+ varInputs.forEach(input => {
+ const placeholder = input.dataset.placeholder || '';
+ if (/^\{\{\d+\}\}$/.test(placeholder)) {
+ hasNumericVars = true;
+ } else {
+ hasNamedVars = true;
+ }
+ });
+
+ console.log(`📊 Tipo de variables detectado: ${hasNumericVars ? 'numéricas' : 'con nombres'}`);
+
+ if (hasNumericVars && !hasNamedVars) {
+ // Variables numéricas: crear array ordenado por índice
+ const tempObj = {};
+ varInputs.forEach(input => {
+ const index = parseInt(input.dataset.var);
+ tempObj[index] = input.value || '';
+ });
+ // Convertir a array ordenado
+ const sortedKeys = Object.keys(tempObj).map(Number).sort((a, b) => a - b);
+ tplParams = sortedKeys.map(key => tempObj[key]);
+ console.log('📊 Enviando como array ordenado:', tplParams);
+ } else {
+ // Variables con nombres: crear objeto
+ varInputs.forEach(input => {
+ const placeholder = input.dataset.placeholder || '';
+ // Extraer nombre de la variable: {{nombre_tema}} -> nombre_tema
+ const varName = placeholder.replace(/\{\{|\}\}/g, '');
+ tplParams[varName] = input.value || '';
+ });
+ console.log('📦 Enviando como objeto con nombres:', tplParams);
+ }
+ } else {
+ console.log('ℹ️ Plantilla sin variables');
+ }
+
+ // Si hay input oculto con parámetros (para compatibilidad), usarlo
const paramsInput = document.getElementById('template-params');
if (paramsInput && paramsInput.value) {
try {
- tplParams = JSON.parse(paramsInput.value);
+ const hiddenParams = JSON.parse(paramsInput.value);
+ if (Object.keys(hiddenParams).length > 0) {
+ tplParams = hiddenParams;
+ console.log('📝 Usando parámetros del input oculto:', tplParams);
+ }
} catch (e) {
console.warn('Invalid JSON in #template-params, ignoring', e);
- tplParams = null;
}
}
- // If no params provided and template is contacto_nuevo, prompt operator for name
- if ((!tplParams || (typeof tplParams === 'object' && Object.keys(tplParams).length === 0)) && tplName === 'contacto_nuevo') {
- const defaultName = '';
- const inputName = prompt('Ingrese el nombre para la plantilla contacto_nuevo (dejar vacío para usar el número):', defaultName);
- if (inputName === null) {
- // user cancelled
- return;
- }
- const clean = (typeof inputName === 'string') ? inputName.trim() : '';
- tplParams = { name: clean || recipient };
- }
-
messageData = {
recipient: recipient,
type: 'template',
- template_name: tplName,
- parameters: tplParams || {}
+ template_name: templateName, // Usar template_name en lugar de ID
+ parameters: tplParams
};
}
@@ -1651,6 +1795,164 @@ class SimpleWhatsAppManager {
this.showError('Error eliminando plantilla: ' + error.message);
}
}
+
+ async sendBroadcast() {
+ const selectionType = document.getElementById('broadcast-selection-type').value;
+ const messageType = document.getElementById('broadcast-message-type').value;
+
+ let payload = {
+ selection_type: selectionType,
+ message_type: messageType
+ };
+
+ // Validar según tipo de selección
+ if (selectionType === 'filter') {
+ payload.filter = document.getElementById('broadcast-filter').value;
+ } else {
+ const selectedUsers = $('#broadcast-users').val();
+ if (!selectedUsers || selectedUsers.length === 0) {
+ this.showError('Debes seleccionar al menos un usuario');
+ return;
+ }
+ payload.user_ids = selectedUsers;
+ }
+
+ // Validar según tipo de mensaje
+ if (messageType === 'text') {
+ const message = document.getElementById('broadcast-message').value;
+ if (!message.trim()) {
+ this.showError('El mensaje no puede estar vacío');
+ return;
+ }
+ payload.message = message;
+ } else {
+ const templateId = document.getElementById('broadcast-template').value;
+ if (!templateId) {
+ this.showError('Debes seleccionar una plantilla');
+ return;
+ }
+
+ payload.template_id = templateId;
+
+ // Recoger y validar variables
+ const variablesInputs = document.querySelectorAll('.broadcast-template-var');
+ let hasEmpty = false;
+
+ // Detectar si las variables son numéricas o con nombres usando el placeholder
+ const firstInput = variablesInputs[0];
+ const placeholder = firstInput?.dataset?.placeholder || '';
+ // Si el placeholder es {{1}}, {{2}}, etc. -> numéricas
+ // Si es {{nombre_tema}}, {{fecha}}, etc. -> con nombres
+ const isNumericVar = /^\{\{\d+\}\}$/.test(placeholder);
+
+ console.log('🔍 Detectando tipo de variables:');
+ console.log(' - Placeholder ejemplo:', placeholder);
+ console.log(' - Es numérica?', isNumericVar);
+
+ let variables;
+ if (isNumericVar) {
+ // Variables numéricas {{1}}, {{2}} - usar array ordenado
+ const tempVars = {};
+ variablesInputs.forEach(input => {
+ const varIndex = parseInt(input.dataset.var);
+ if (!input.value.trim()) {
+ input.classList.add('is-invalid');
+ hasEmpty = true;
+ } else {
+ input.classList.remove('is-invalid');
+ tempVars[varIndex] = input.value;
+ }
+ });
+
+ if (hasEmpty) {
+ this.showError('Por favor completa todos los campos de variables de la plantilla');
+ return;
+ }
+
+ // Convertir a array ordenado por índice
+ variables = [];
+ const sortedKeys = Object.keys(tempVars).map(k => parseInt(k)).sort((a, b) => a - b);
+ sortedKeys.forEach(key => {
+ variables.push(tempVars[key]);
+ });
+
+ console.log('📝 Variables numéricas recopiladas:', tempVars);
+ console.log('📊 Array ordenado:', variables);
+ } else {
+ // Variables con nombres {{fecha}}, {{motivo}} - usar objeto
+ variables = {};
+ variablesInputs.forEach(input => {
+ const placeholder = input.dataset.placeholder || '';
+ const varName = placeholder.replace(/\{\{|\}\}/g, ''); // Extraer nombre de {{fecha}}
+
+ if (!input.value.trim()) {
+ input.classList.add('is-invalid');
+ hasEmpty = true;
+ } else {
+ input.classList.remove('is-invalid');
+ variables[varName] = input.value;
+ }
+ });
+
+ if (hasEmpty) {
+ this.showError('Por favor completa todos los campos de variables de la plantilla');
+ return;
+ }
+
+ console.log('📝 Variables con nombres recopiladas:', variables);
+ }
+
+ payload.template_variables = variables;
+ }
+
+ // Confirmar envío
+ const targetText = selectionType === 'filter'
+ ? `usuarios filtrados por "${payload.filter}"`
+ : `${payload.user_ids.length} usuario(s) seleccionado(s)`;
+ const messageText = messageType === 'text' ? 'mensaje de texto' : 'plantilla';
+
+ if (!confirm(`¿Enviar este ${messageText} a ${targetText}?`)) {
+ return;
+ }
+
+ try {
+ this.showInfo('Enviando mensajes masivos...');
+ const result = await this.apiCall('send_broadcast.php', {
+ method: 'POST',
+ body: payload
+ });
+
+ if (result.success) {
+ this.showSuccess(`✅ Mensajes enviados exitosamente!\nEnviados: ${result.sent_count}\nErrores: ${result.error_count}\nTotal usuarios: ${result.total_users}`);
+ document.getElementById('broadcast-form').reset();
+ $('#broadcast-users').val(null).trigger('change');
+ document.getElementById('broadcast-template-variables').style.display = 'none';
+ document.getElementById('broadcast-template-preview').style.display = 'none';
+ } else {
+ this.showError(result.error || 'Error enviando broadcast');
+ }
+ } catch (error) {
+ this.showError('Error enviando broadcast: ' + error.message);
+ }
+ }
+
+ // Cargar usuarios administradores del sistema
+ async loadAdminUsers() {
+ this.log('Cargando usuarios administradores...');
+
+ try {
+ const response = await this.apiCall('list_admin_users.php');
+
+ if (response && response.success && response.data) {
+ renderAdminUsersTable(response.data);
+ } else {
+ this.showError('Error cargando usuarios administradores');
+ }
+ } catch (error) {
+ this.log('Error cargando usuarios administradores: ' + error.message, 'error');
+ this.showError('Error cargando usuarios: ' + error.message);
+ }
+ }
}
// Inicializar cuando el DOM esté listo
@@ -2839,9 +3141,121 @@ window.toggleMessageType = function () {
} else {
textGroup.style.display = 'block';
templateGroup.style.display = 'none';
+ // Ocultar variables y preview cuando no es plantilla
+ const varsContainer = document.getElementById('message-template-variables');
+ const previewContainer = document.getElementById('message-template-preview');
+ if (varsContainer) varsContainer.style.display = 'none';
+ if (previewContainer) previewContainer.style.display = 'none';
}
};
+// Cargar detalles de plantilla para send_message
+window.loadMessageTemplateDetails = async function() {
+ console.log('🔵 loadMessageTemplateDetails llamado');
+ const select = document.getElementById('message-template');
+ const templateId = select.value;
+ console.log('Template ID seleccionado:', templateId);
+
+ if (!templateId) {
+ document.getElementById('message-template-variables').style.display = 'none';
+ document.getElementById('message-template-preview').style.display = 'none';
+ return;
+ }
+
+ try {
+ const response = await window.whatsappManager.apiCall(`get_template_details.php?id=${templateId}`);
+ console.log('✅ Detalles de plantilla:', response);
+
+ if (response && response.success) {
+ const template = response.template || response.data;
+ if (!template) {
+ console.error('❌ No se encontró template en la respuesta');
+ return;
+ }
+
+ console.log('📋 Template data:', template);
+ console.log('📋 Variables:', template.variables);
+
+ const variablesContainer = document.getElementById('message-template-variables');
+
+ if (template.variables && template.variables.length > 0) {
+ console.log(`✅ Se encontraron ${template.variables.length} variables`);
+ let html = '';
+
+ template.variables.forEach((variable, index) => {
+ const varLabel = variable.label || variable.name || `Variable ${variable.index || index + 1}`;
+ const placeholder = variable.placeholder || `{{${variable.index || index + 1}}}`;
+ const example = variable.example || '';
+
+ html += `
+
+
+
+
+ `;
+ });
+
+ console.log('📝 HTML generado para variables:', html);
+ variablesContainer.innerHTML = html;
+ variablesContainer.style.display = 'block';
+ console.log('✅ Variables container mostrado');
+ } else {
+ console.log('⚠️ Template sin variables');
+ variablesContainer.innerHTML = 'Esta plantilla no tiene variables
';
+ variablesContainer.style.display = 'block';
+ }
+
+ updateMessagePreview();
+ } else {
+ console.error('❌ Respuesta sin success:', response);
+ }
+ } catch (error) {
+ console.error('❌ Error cargando detalles de plantilla:', error);
+ window.whatsappManager.showError('Error cargando plantilla');
+ }
+};
+
+// Actualizar preview de plantilla para send_message
+window.updateMessagePreview = async function() {
+ const templateId = document.getElementById('message-template').value;
+ if (!templateId) return;
+
+ // Recolectar variables como array (por índice)
+ const variablesArray = [];
+ document.querySelectorAll('.message-template-var').forEach(input => {
+ const index = parseInt(input.dataset.var) - 1; // Convertir a índice de array (0-based)
+ variablesArray[index] = input.value || `{{${input.dataset.var}}}`;
+ });
+
+ try {
+ const response = await window.whatsappManager.apiCall('preview_template.php', {
+ method: 'POST',
+ body: { template_id: templateId, parameters: variablesArray }
+ });
+
+ if (response && response.success && response.preview) {
+ const previewContainer = document.getElementById('message-template-preview');
+ const previewContent = document.getElementById('message-preview-content');
+
+ // Usar el HTML pre-formateado del servidor
+ previewContent.innerHTML = response.preview.html || response.preview.body.replace(/\\n/g, '
');
+ previewContainer.style.display = 'block';
+ } else {
+ console.error('Error en preview:', response);
+ }
+ } catch (error) {
+ console.error('Error generando vista previa:', error);
+ }
+};
+
+
// Función de diagnóstico
window.runDiagnostic = function () {
const resultsDiv = document.getElementById('diagnostic-results');
@@ -4113,6 +4527,90 @@ window.exportUsers = function() {
}
};
+// Función para buscar usuarios con debounce
+let searchUsersTimeout;
+window.searchUsers = function() {
+ clearTimeout(searchUsersTimeout);
+ searchUsersTimeout = setTimeout(() => {
+ const searchInput = document.getElementById('users-search');
+ const searchTerm = searchInput ? searchInput.value.trim() : '';
+
+ if (window.whatsappManager && window.whatsappManager.loadUsers) {
+ window.whatsappManager.loadUsers(1, searchTerm);
+ }
+ }, 500);
+};
+
+// Función para sincronizar plantillas desde Facebook
+window.syncTemplatesFromFacebook = async function() {
+ console.log('Sincronizando plantillas desde Facebook...');
+
+ if (!window.whatsappManager) {
+ alert('Error: Sistema no inicializado');
+ return;
+ }
+
+ const btn = document.getElementById('btn-sync-templates');
+ if (btn) {
+ btn.disabled = true;
+ btn.innerHTML = ' Sincronizando...';
+ }
+
+ try {
+ window.whatsappManager.showInfo('Sincronizando plantillas desde Facebook/WhatsApp Business...');
+
+ const response = await window.whatsappManager.apiCall('sync_templates_from_facebook.php', {
+ method: 'POST'
+ });
+
+ if (response && response.success) {
+ const data = response.data || {};
+ const message = `
+ ✅ Sincronización completada exitosamente:
+ • ${data.synced || 0} plantillas nuevas
+ • ${data.updated || 0} plantillas actualizadas
+ • ${data.skipped || 0} sin cambios
+ ${data.errors && data.errors.length > 0 ? '\n⚠️ ' + data.errors.length + ' errores encontrados' : ''}
+ `;
+
+ window.whatsappManager.showSuccess(message);
+
+ // Recargar la lista de plantillas
+ if (window.whatsappManager.loadTemplates) {
+ window.whatsappManager.loadTemplates();
+ }
+
+ // Mostrar errores si existen
+ if (data.errors && data.errors.length > 0) {
+ console.warn('Errores durante la sincronización:', data.errors);
+ }
+ } else {
+ const errorMsg = response?.error || 'Error desconocido al sincronizar';
+ window.whatsappManager.showError(`Error al sincronizar plantillas: ${errorMsg}`);
+
+ // Si el error es por falta de WABA ID, dar instrucciones
+ if (errorMsg.includes('Business Account ID')) {
+ setTimeout(() => {
+ alert('💡 Para sincronizar plantillas necesitas configurar el Business Account ID (WABA ID).\n\n' +
+ '1. Ve a la pestaña "Configuración"\n' +
+ '2. Ingresa tu WABA ID en el campo correspondiente\n' +
+ '3. Guarda la configuración\n' +
+ '4. Vuelve a intentar la sincronización\n\n' +
+ 'Puedes encontrar tu WABA ID en Meta Business Manager.');
+ }, 500);
+ }
+ }
+ } catch (error) {
+ console.error('Error sincronizando plantillas:', error);
+ window.whatsappManager.showError('Error de conexión al sincronizar plantillas: ' + error.message);
+ } finally {
+ if (btn) {
+ btn.disabled = false;
+ btn.innerHTML = ' Sincronizar desde Facebook';
+ }
+ }
+};
+
// Función para refrescar logs
window.refreshLogs = function() {
console.log('Refrescando logs...');
@@ -4189,4 +4687,226 @@ window.clearLogs = async function() {
} else {
alert('Error: Sistema no inicializado');
}
+};
+
+// ============================================
+// GESTIÓN DE USUARIOS ADMINISTRADORES
+// ============================================
+
+// Renderizar tabla de usuarios administradores
+function renderAdminUsersTable(users) {
+ const tbody = document.getElementById('admin-users-table');
+ if (!tbody) return;
+
+ if (!users || users.length === 0) {
+ tbody.innerHTML = `
+
+ |
+ No hay usuarios registrados
+ |
+
+ `;
+ return;
+ }
+
+ tbody.innerHTML = users.map(user => {
+ const isActive = parseInt(user.is_active) === 1;
+ const lastLogin = user.last_login ?
+ new Date(user.last_login).toLocaleString('es-CO') :
+ 'Nunca';
+ const createdAt = new Date(user.created_at).toLocaleDateString('es-CO');
+
+ return `
+
+ | ${escapeHtml(user.username)} |
+ ${escapeHtml(user.full_name || '-')} |
+ ${escapeHtml(user.email || '-')} |
+
+
+ ${isActive ? 'Activo' : 'Inactivo'}
+
+ |
+ ${lastLogin} |
+ ${createdAt} |
+
+
+
+
+ |
+
+ `;
+ }).join('');
+}
+
+// Mostrar modal para crear usuario
+window.showCreateAdminUserModal = function() {
+ const modal = new bootstrap.Modal(document.getElementById('adminUserModal'));
+ document.getElementById('admin-user-modal-title').textContent = 'Nuevo Usuario';
+ document.getElementById('admin-user-form').reset();
+ document.getElementById('admin-user-id').value = '';
+ document.getElementById('password-group').style.display = 'block';
+ modal.show();
+};
+
+// Editar usuario existente
+window.editAdminUser = async function(userId) {
+ try {
+ const response = await window.whatsappManager.apiCall('list_admin_users.php');
+
+ if (response && response.success && response.data) {
+ const user = response.data.find(u => parseInt(u.id) === parseInt(userId));
+
+ if (user) {
+ document.getElementById('admin-user-modal-title').textContent = 'Editar Usuario';
+ document.getElementById('admin-user-id').value = user.id;
+ document.getElementById('admin-username').value = user.username;
+ document.getElementById('admin-fullname').value = user.full_name || '';
+ document.getElementById('admin-email').value = user.email || '';
+ document.getElementById('password-group').style.display = 'none';
+
+ const modal = new bootstrap.Modal(document.getElementById('adminUserModal'));
+ modal.show();
+ }
+ }
+ } catch (error) {
+ console.error('Error cargando usuario:', error);
+ alert('Error al cargar datos del usuario');
+ }
+};
+
+// Guardar usuario (crear o actualizar)
+window.saveAdminUser = async function() {
+ const userId = document.getElementById('admin-user-id').value;
+ const username = document.getElementById('admin-username').value.trim();
+ const fullName = document.getElementById('admin-fullname').value.trim();
+ const email = document.getElementById('admin-email').value.trim();
+ const password = document.getElementById('admin-password').value;
+
+ if (!username) {
+ alert('El nombre de usuario es requerido');
+ return;
+ }
+
+ // Si es nuevo usuario, validar contraseña
+ if (!userId && !password) {
+ alert('La contraseña es requerida para usuarios nuevos');
+ return;
+ }
+
+ if (!userId && password.length < 6) {
+ alert('La contraseña debe tener al menos 6 caracteres');
+ return;
+ }
+
+ try {
+ let response;
+
+ if (userId) {
+ // Actualizar usuario existente
+ response = await window.whatsappManager.apiCall('update_admin_user.php', {
+ method: 'POST',
+ body: { user_id: userId, username, full_name: fullName, email }
+ });
+ } else {
+ // Crear nuevo usuario
+ response = await window.whatsappManager.apiCall('create_admin_user.php', {
+ method: 'POST',
+ body: { username, full_name: fullName, email, password }
+ });
+ }
+
+ if (response && response.success) {
+ window.whatsappManager.showSuccess(response.message || 'Usuario guardado correctamente');
+ bootstrap.Modal.getInstance(document.getElementById('adminUserModal')).hide();
+ window.whatsappManager.loadAdminUsers();
+ } else {
+ alert(response?.error || 'Error al guardar usuario');
+ }
+ } catch (error) {
+ console.error('Error guardando usuario:', error);
+ alert('Error al guardar usuario: ' + error.message);
+ }
+};
+
+// Cambiar contraseña de usuario
+window.changeAdminPassword = function(userId, username) {
+ document.getElementById('change-password-user-id').value = userId;
+ document.getElementById('change-password-username').textContent = username;
+ document.getElementById('new-password').value = '';
+ document.getElementById('confirm-password').value = '';
+
+ const modal = new bootstrap.Modal(document.getElementById('changePasswordModal'));
+ modal.show();
+};
+
+// Guardar nueva contraseña
+window.saveNewPassword = async function() {
+ const userId = document.getElementById('change-password-user-id').value;
+ const newPassword = document.getElementById('new-password').value;
+ const confirmPassword = document.getElementById('confirm-password').value;
+
+ if (!newPassword || newPassword.length < 6) {
+ alert('La contraseña debe tener al menos 6 caracteres');
+ return;
+ }
+
+ if (newPassword !== confirmPassword) {
+ alert('Las contraseñas no coinciden');
+ return;
+ }
+
+ try {
+ const response = await window.whatsappManager.apiCall('change_admin_password.php', {
+ method: 'POST',
+ body: { user_id: userId, new_password: newPassword }
+ });
+
+ if (response && response.success) {
+ window.whatsappManager.showSuccess('Contraseña actualizada correctamente');
+ bootstrap.Modal.getInstance(document.getElementById('changePasswordModal')).hide();
+ } else {
+ alert(response?.error || 'Error al cambiar contraseña');
+ }
+ } catch (error) {
+ console.error('Error cambiando contraseña:', error);
+ alert('Error al cambiar contraseña: ' + error.message);
+ }
+};
+
+// Activar/Desactivar usuario
+window.toggleAdminUserStatus = async function(userId, isActive) {
+ const action = isActive ? 'activar' : 'desactivar';
+
+ if (!confirm(`¿Está seguro de ${action} este usuario?`)) {
+ return;
+ }
+
+ try {
+ const response = await window.whatsappManager.apiCall('toggle_admin_user_status.php', {
+ method: 'POST',
+ body: { user_id: userId, is_active: isActive }
+ });
+
+ if (response && response.success) {
+ window.whatsappManager.showSuccess(`Usuario ${isActive ? 'activado' : 'desactivado'} correctamente`);
+ window.whatsappManager.loadAdminUsers();
+ } else {
+ alert(response?.error || `Error al ${action} usuario`);
+ }
+ } catch (error) {
+ console.error(`Error ${action}ando usuario:`, error);
+ alert(`Error al ${action} usuario: ` + error.message);
+ }
};
\ No newline at end of file
diff --git a/chat_window.php b/chat_window.php
index eb6e3ba..01ac1e5 100644
--- a/chat_window.php
+++ b/chat_window.php
@@ -1119,21 +1119,25 @@ if (empty($user_id)) {
option.value = template.name;
option.textContent = `${display} (${lang})`;
option.dataset.language = lang;
+ option.dataset.templateId = template.id || '';
templateSelect.appendChild(option);
});
// Valor por defecto global para la plantilla seleccionada
window.language = 'en_US';
+ window.currentTemplateId = null;
// Actualizar el language global cuando cambie la plantilla seleccionada
templateSelect.addEventListener('change', function() {
const sel = this.selectedOptions[0];
window.language = sel && sel.dataset && sel.dataset.language ? sel.dataset.language : 'en_US';
+ window.currentTemplateId = sel && sel.dataset && sel.dataset.templateId ? sel.dataset.templateId : null;
});
// Si hay una plantilla seleccionada por defecto, establecer language acorde
if (templateSelect.selectedOptions.length && templateSelect.selectedOptions[0].dataset.language) {
window.language = templateSelect.selectedOptions[0].dataset.language;
+ window.currentTemplateId = templateSelect.selectedOptions[0].dataset.templateId;
}
}
} catch (error) {
@@ -1167,7 +1171,13 @@ if (empty($user_id)) {
return;
}
- await sendTemplateMessage(template,language, message);
+ // Verificar si la plantilla tiene variables
+ if (window.currentTemplateId) {
+ await checkTemplateVariables(window.currentTemplateId, template, language);
+ } else {
+ // Plantilla sin variables, enviar directo
+ await sendTemplateMessage(template, language, message);
+ }
} else {
await sendTextMessage(message);
}
@@ -1216,22 +1226,33 @@ if (empty($user_id)) {
}
// Enviar mensaje de plantilla
- async function sendTemplateMessage(template,language, parameters) {
+ async function sendTemplateMessage(template, language, parameters) {
try {
showTyping();
console.log('Enviando plantilla:', {
template,
+ language,
parameters,
user: currentUser
});
+ // Preparar parámetros según el formato
+ let paramArray = [];
+ if (Array.isArray(parameters)) {
+ paramArray = parameters;
+ } else if (typeof parameters === 'string' && parameters) {
+ paramArray = parameters.split(',').map(p => p.trim());
+ } else {
+ paramArray = [];
+ }
+
const requestBody = {
recipient: currentUser.phone_number,
type: 'template',
template: template,
language: language || 'en_US',
- parameters: parameters ? parameters.split(',') : []
+ parameters: paramArray
};
console.log('Request body:', requestBody);
@@ -1245,7 +1266,10 @@ if (empty($user_id)) {
hideTyping();
if (response && response.success) {
- addMessageToView(`Plantilla: ${template}`, 'outgoing');
+ const displayMsg = paramArray.length > 0 ?
+ `Plantilla: ${template} (con ${paramArray.length} variable(s))` :
+ `Plantilla: ${template}`;
+ addMessageToView(displayMsg, 'outgoing');
showAlert('Mensaje de plantilla enviado correctamente', 'success');
} else {
throw new Error(response.error || 'Error enviando plantilla');
@@ -1925,6 +1949,197 @@ if (empty($user_id)) {
const bsModal = new bootstrap.Modal(modal);
bsModal.show();
}
+
+ // Variables globales para el modal de plantillas
+ let currentTemplateData = null;
+
+ // Verificar si la plantilla tiene variables
+ async function checkTemplateVariables(templateId, templateName, language) {
+ try {
+ const response = await whatsappManager.apiCall(`get_template_details.php?id=${templateId}`);
+
+ if (response && response.success && response.template) {
+ currentTemplateData = response.template;
+
+ if (response.template.has_variables) {
+ // Mostrar modal para llenar variables
+ showTemplateVariablesModal(response.template);
+ } else {
+ // Enviar directo sin variables
+ await sendTemplateMessage(templateName, language, []);
+ }
+ } else {
+ throw new Error('No se pudo obtener detalles de la plantilla');
+ }
+ } catch (error) {
+ console.error('Error checking template variables:', error);
+ showAlert('Error al cargar detalles de plantilla: ' + error.message, 'danger');
+ }
+ }
+
+ // Mostrar modal con campos para variables
+ function showTemplateVariablesModal(template) {
+ // Actualizar información del encabezado
+ document.getElementById('templateNameDisplay').textContent = template.name;
+ document.getElementById('templateLanguageDisplay').textContent = `Idioma: ${template.language_code}`;
+ document.getElementById('templateVariablesCount').textContent = `${template.variables_count} variable(s) requerida(s)`;
+
+ // Generar formulario de variables
+ const formContainer = document.getElementById('variablesForm');
+ formContainer.innerHTML = '';
+
+ template.variables.forEach((variable, index) => {
+ const fieldHtml = `
+
+
+
+
+ `;
+ formContainer.insertAdjacentHTML('beforeend', fieldHtml);
+ });
+
+ // Event listeners para actualizar preview en tiempo real
+ const inputs = formContainer.querySelectorAll('.template-variable-input');
+ inputs.forEach(input => {
+ input.addEventListener('input', updateTemplatePreview);
+ });
+
+ // Inicializar preview vacío
+ updateTemplatePreview();
+
+ // Mostrar modal
+ const modal = new bootstrap.Modal(document.getElementById('templateVariablesModal'));
+ modal.show();
+
+ // Event listener para botón de envío
+ document.getElementById('btnSendTemplateWithVariables').onclick = sendTemplateWithVariables;
+ }
+
+ // Actualizar vista previa de la plantilla
+ async function updateTemplatePreview() {
+ if (!currentTemplateData) return;
+
+ const inputs = document.querySelectorAll('.template-variable-input');
+ const parameters = [];
+
+ inputs.forEach(input => {
+ parameters.push(input.value || '');
+ });
+
+ try {
+ const response = await whatsappManager.apiCall('preview_template.php', {
+ body: {
+ template_id: currentTemplateData.id,
+ parameters: parameters
+ }
+ });
+
+ if (response && response.success && response.preview) {
+ const previewContainer = document.getElementById('templatePreview');
+
+ if (response.preview.is_complete) {
+ previewContainer.innerHTML = response.preview.html;
+ previewContainer.className = 'p-3 bg-light rounded border border-success';
+ } else {
+ previewContainer.innerHTML = response.preview.html +
+ ' Faltan variables por completar
';
+ previewContainer.className = 'p-3 bg-light rounded border border-warning';
+ }
+ }
+ } catch (error) {
+ console.error('Error updating preview:', error);
+ }
+ }
+
+ // Enviar plantilla con variables
+ async function sendTemplateWithVariables() {
+ if (!currentTemplateData) return;
+
+ const inputs = document.querySelectorAll('.template-variable-input');
+ const parameters = [];
+ let allFilled = true;
+
+ inputs.forEach(input => {
+ const value = input.value.trim();
+ if (!value) {
+ allFilled = false;
+ input.classList.add('is-invalid');
+ } else {
+ input.classList.remove('is-invalid');
+ }
+ parameters.push(value);
+ });
+
+ if (!allFilled) {
+ showAlert('Por favor complete todas las variables requeridas', 'warning');
+ return;
+ }
+
+ // Cerrar modal
+ const modal = bootstrap.Modal.getInstance(document.getElementById('templateVariablesModal'));
+ modal.hide();
+
+ // Enviar plantilla con parámetros
+ await sendTemplateMessage(
+ currentTemplateData.template_name,
+ currentTemplateData.language_code,
+ parameters
+ );
+
+ // Limpiar datos
+ currentTemplateData = null;
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Vista Previa
+
+
+ Complete los campos para ver la vista previa...
+
+
+
+
+
+
+
+