This commit is contained in:
lizandrogd
2025-11-18 20:26:36 -05:00
commit ce12ac2cce
45 changed files with 10517 additions and 0 deletions
+359
View File
@@ -0,0 +1,359 @@
# 🚀 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
<VirtualHost *:80>
ServerName tudominio.com
DocumentRoot /var/www/html/bot
<Directory /var/www/html/bot>
AllowOverride All
Require all granted
</Directory>
# Redirigir HTTP a HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</VirtualHost>
<VirtualHost *:443>
ServerName tudominio.com
DocumentRoot /var/www/html/migrador
SSLEngine on
SSLCertificateFile /path/to/your/certificate.crt
SSLCertificateKeyFile /path/to/your/private.key
<Directory /var/www/html/migrador>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
```
#### 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
<?php
// === CONFIGURACIÓN DE PRODUCCIÓN ===
// Base de datos de producción
define('DB_HOST', 'tu-servidor-mysql');
define('DB_NAME', 'whatsapp_production');
define('DB_USER', 'tu_usuario_db');
define('DB_PASS', 'password_super_seguro');
// URL de producción
define('APP_URL', 'https://tudominio.com/migrador');
// Deshabilitar errores en producción
error_reporting(0);
ini_set('display_errors', 0);
ini_set('log_errors', 1);
// Configuración de seguridad
define('SECURE_MODE', true);
?>
```
### 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
<Files "config.php">
Require all denied
</Files>
<Files "*.sql">
Require all denied
</Files>
# Cache headers
<IfModule mod_expires.c>
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"
</IfModule>
```
### 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**: ✅ `messages`
### 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
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/javascript
</IfModule>
```
### 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! 🎉
+146
View File
@@ -0,0 +1,146 @@
# 🚨 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)** 🚀
+158
View File
@@ -0,0 +1,158 @@
# 🛠️ 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.** 🚀
+151
View File
@@ -0,0 +1,151 @@
# 🤖 WhatsApp Bot Manager - Guía de Instalación Rápida
## 🚀 Pasos para poner en funcionamiento:
### 1. 📊 Instalar Base de Datos
- Abrir en navegador: `http://localhost/migrador/install.php`
- Hacer clic en "Ejecutar Instalación"
- Verificar que todas las tablas se crearon correctamente
### 2. 🔧 Verificar Sistema
- Abrir: `http://localhost/migrador/test.php`
- Revisar que todos los componentes estén en ✅
### 3. 🌐 Acceder al Panel
- Abrir: `http://localhost/migrador/index.php`
- Navegar por las diferentes secciones
### 4. ⚙️ Configurar WhatsApp (IMPORTANTE)
1. Ir a **Configuración** en el panel
2. Completar:
- **Token de WhatsApp**: EAAWTKu1iZAGABP46c7GZAFvHremu9uvTLOlgZCAa4eKUlaCqszzsQjjLcgnqORzo2EBhB7LZAcAqJ7clpGvNCk3oXKnqo8Fl0K2o7eHYoeaqZBesqjBPRfsd4ZBOdY4kN1YBJnmRznegTD9wtJZByVrRcMXF8YXaAJjLCuIFZCK7QFUpId7i4eVkFvbqUYnUKA91rgPvaeGRNZCLHa5ZC1ZB2Skn0gFNgPTBx7bedAMULYZD
- **Phone Number ID**: 938177732702718
- **Webhook Token**: mi_token_secreto_123
3. Guardar configuración
### 5. 🔗 Configurar Webhook en Facebook
1. Ir a [Facebook for Developers](https://developers.facebook.com)
2. Crear/configurar aplicación WhatsApp Business
3. Webhook URL: `https://tudominio.com/migrador/api/webhook.php`
4. Token de verificación: `mi_token_secreto_123`
5. Suscribirse a eventos: `messages`
## 🎯 Prueba Rápida del Bot
Envía estos mensajes por WhatsApp para probar:
```
👤 Usuario: "Hola"
🤖 Bot: Mensaje de bienvenida
👤 Usuario: "menu"
🤖 Bot: Muestra menú principal
👤 Usuario: "1"
🤖 Bot: Muestra submenú de servicios
👤 Usuario: "ayuda"
🤖 Bot: Muestra información de ayuda
```
## 📋 Funcionalidades Principales
### 🤖 Bot Automático
- ✅ Menús navegables por números
- ✅ Respuestas automáticas
- ✅ Comandos especiales (menu, ayuda, salir)
- ✅ Gestión de estados de usuario
### 🎛️ Panel de Control
- ✅ Dashboard con estadísticas
- ✅ Gestión de conversaciones
- ✅ Administración de usuarios
- ✅ Editor de menús
- ✅ Envío de mensajes
- ✅ Configuración del sistema
### 🔧 APIs Disponibles
- ✅ Webhook para recibir mensajes
- ✅ Envío de mensajes individuales
- ✅ Envío masivo (broadcast)
- ✅ Gestión de menús
- ✅ Estadísticas y reportes
## 🛠️ Estructura de Archivos
```
bot/
├── 🏠 index.php # Panel principal
├── 🔧 install.php # Instalador automático
├── 🧪 test.php # Script de pruebas
├── 📖 README.md # Documentación completa
├── config/
│ └── config.php # Configuración
├── database/
│ └── schema.sql # Esquema de BD
├── api/ # APIs REST
│ ├── webhook.php # Webhook principal
│ ├── send_message.php # Envío de mensajes
│ ├── get_stats.php # Estadísticas
│ └── [más APIs...]
├── classes/
│ └── Database.php # Conexión BD
├── services/
│ ├── WhatsAppService.php # Servicio WhatsApp
│ └── BotService.php # Lógica del bot
└── assets/
├── css/styles.css # Estilos
└── js/app.js # JavaScript
```
## 🎨 Personalización
### Cambiar Menús
1. Ir a **Menús** en el panel
2. Editar menús existentes o crear nuevos
3. Configurar opciones y acciones
### Agregar Respuestas Automáticas
1. Ir a **Respuestas Auto**
2. Agregar nuevas palabras clave
3. Definir respuestas personalizadas
### Configurar Mensajes Masivos
1. Ir a **Enviar Mensaje**
2. Usar la sección "Mensaje Masivo"
3. Seleccionar filtros de usuarios
## 🚨 Solución de Problemas
### ❌ Error de conexión BD
- Verificar credenciales en `config/config.php`
- Comprobar que MySQL esté funcionando
- Revisar permisos del usuario
### ❌ Webhook no funciona
- Verificar que la URL sea accesible públicamente
- Comprobar SSL (debe ser HTTPS)
- Revisar logs en el panel
### ❌ Bot no responde
- Verificar token en configuración
- Comprobar webhook en Facebook
- Revisar logs de PHP
## 📞 URLs de Prueba
- **Panel**: http://localhost/bot/index.php
- **Test**: http://localhost/bot/test.php
- **API Stats**: http://localhost/bot/api/get_stats.php
- **Webhook**: http://localhost/bot/api/webhook.php
## 🎯 ¡Sistema Listo!
Una vez completados todos los pasos, tendrás:
- 🤖 **Bot funcional** respondiendo mensajes automáticamente
- 🎛️ **Panel web** para gestión completa
- 📊 **Dashboard** con métricas en tiempo real
- 🔧 **APIs** para integraciones personalizadas
¡Disfruta de tu nuevo sistema de WhatsApp Bot! 🚀
+295
View File
@@ -0,0 +1,295 @@
# 🤖 Sistema de WhatsApp Bot Manager
Un sistema completo de gestión de chatbot para WhatsApp Business API con interfaz web moderna, menús parametrizables y gestión avanzada de conversaciones.
## ✨ Características
### 🔧 Funcionalidades Principales
- **Bot Inteligente** con menús parametrizables por números
- **Interfaz Web Moderna** para gestión completa
- **API REST** para integración con sistemas externos
- **Respuestas Automáticas** configurables
- **Envío de Mensajes** individuales y masivos
- **Gestión de Usuarios** con estados y seguimiento
- **Dashboard Analytics** con gráficos en tiempo real
- **Logs Detallados** de todas las interacciones
- **Sistema de Plantillas** para mensajes predefinidos
### 🎯 Sistema de Menús
- Menús jerárquicos con navegación por números (1, 2, 3, etc.)
- Acciones configurables: navegar, mensaje, API call, finalizar
- Soporte para servicios, pagos, soporte técnico
- Completamente parametrizable desde la interfaz
### 📱 Compatibilidad WhatsApp
- Integración completa con WhatsApp Business API
- Soporte para mensajes de texto, imágenes, audio, video
- Plantillas de WhatsApp Business
- Estados de entrega y lectura
- Webhook seguro con verificación
## 🚀 Instalación
### 1. Prerrequisitos
```bash
- PHP 8.0 o superior
- MySQL 5.7 o superior
- Servidor web (Apache/Nginx)
- WhatsApp Business API Token
- Dominio con SSL (para webhook)
```
### 2. Configuración de Base de Datos
```bash
# Importar el esquema de la base de datos
mysql -u pym -p'Nicolas2796*+' -h 46.202.93.92 whatsapp < database/schema.sql
```
### 3. Configuración del Proyecto
```bash
# Subir archivos al servidor
# Ajustar permisos
chmod 755 -R bot/
chmod 666 config/config.php
```
### 4. Configurar WhatsApp Business API
1. **Acceder a Facebook Developers**
- Crear una aplicación en developers.facebook.com
- Agregar el producto "WhatsApp Business API"
2. **Configurar Webhook**
- URL del Webhook: `https://tudominio.com/migrador/api/webhook.php`
- Token de Verificación: `mi_token_secreto_123`
- Suscribirse a: `messages`
3. **Obtener Tokens**
- Token de Acceso (ya proporcionado)
- Phone Number ID: `938177732702718`
### 5. Configuración en la Interfaz
1. Abrir: `https://tudominio.com/migrador/`
2. Ir a **Configuración**
3. Completar:
- Token de WhatsApp
- Phone Number ID
- Token de Verificación
- Nombre de la empresa
- Mensaje de bienvenida
## 📖 Uso del Sistema
### 🎮 Interfaz Web
- **Dashboard**: Estadísticas y actividad en tiempo real
- **Conversaciones**: Ver y gestionar todas las conversaciones
- **Usuarios**: Administrar usuarios y estados
- **Menús**: Configurar y editar el flujo del bot
- **Mensajes**: Enviar mensajes individuales o masivos
- **Configuración**: Ajustar parámetros del sistema
### 🤖 Bot de WhatsApp
#### Comandos Disponibles
- `menu` - Mostrar menú principal
- `ayuda` - Mostrar ayuda
- `salir` - Salir del menú actual
#### Flujo de Menús (Ejemplo)
```
📋 Menú Principal
Selecciona una opción:
1. 💼 Ver servicios disponibles
2. 💳 Realizar pago
3. 🆘 Contactar soporte
0. ❌ Salir
💬 Responde con el número de la opción que deseas
```
### 🔧 API Endpoints
#### Webhook
```bash
POST /api/webhook.php
# Recibe mensajes de WhatsApp
```
#### Enviar Mensaje
```bash
POST /api/send_message.php
Content-Type: application/json
{
"recipient": "573168950803",
"type": "text",
"message": "Hola, este es un mensaje de prueba"
}
```
#### Obtener Estadísticas
```bash
GET /api/get_stats.php
```
## 🏗️ Estructura del Proyecto
```
migrador/
├── api/ # Endpoints de la API
│ ├── webhook.php # Webhook principal
│ ├── send_message.php # Envío de mensajes
│ ├── get_stats.php # Estadísticas
│ └── ... # Otras APIs
├── assets/ # Recursos estáticos
│ ├── css/ # Estilos CSS
│ └── js/ # JavaScript
├── classes/ # Clases principales
│ └── Database.php # Conexión a BD
├── config/ # Configuración
│ └── config.php # Configuración principal
├── database/ # Base de datos
│ └── schema.sql # Esquema de BD
├── services/ # Servicios del sistema
│ ├── WhatsAppService.php # Servicio WhatsApp
│ └── BotService.php # Lógica del bot
└── index.php # Página principal
```
## 📊 Base de Datos
### Tablas Principales
- `users` - Usuarios del sistema
- `conversations` - Historial de mensajes
- `menus` - Configuración de menús
- `menu_options` - Opciones de cada menú
- `auto_responses` - Respuestas automáticas
- `system_config` - Configuración del sistema
- `webhook_logs` - Logs de webhooks
## 🔧 Configuración Avanzada
### Menús Personalizados
```sql
-- Crear menú personalizado
INSERT INTO menus (name, title, description, is_root) VALUES
('mi_menu', '🏪 Mi Tienda', 'Menú de productos', TRUE);
-- Agregar opciones
INSERT INTO menu_options (menu_id, option_number, text, action_type, action_value) VALUES
(1, 1, '📱 Ver catálogo', 'message', 'Aquí tienes nuestro catálogo: www.mitienda.com'),
(1, 2, '🛒 Hacer pedido', 'message', 'Para hacer un pedido, envía el código del producto'),
(1, 0, '🔙 Volver', 'menu', 'main_menu');
```
### Respuestas Automáticas
```sql
-- Agregar respuesta automática
INSERT INTO auto_responses (trigger_type, trigger_value, response_text) VALUES
('keyword', 'precio', 'Para consultar precios, escribe *catalogo* o visita www.mitienda.com'),
('keyword', 'horarios', 'Atendemos de Lunes a Viernes de 8am a 6pm');
```
## 🔐 Seguridad
- Validación de tokens en webhook
- Escape de datos SQL
- Headers de seguridad HTTP
- Logs de todas las transacciones
- Autenticación de API
## 🐛 Solución de Problemas
### Error de Conexión a BD
```bash
# Verificar credenciales en config/config.php
# Verificar que el servidor MySQL esté activo
# Comprobar permisos del usuario de BD
```
### Webhook No Funciona
```bash
# Verificar que la URL sea accesible públicamente
# Comprobar que tenga SSL (HTTPS)
# Revisar logs en /api/get_logs.php
```
### Bot No Responde
```bash
# Verificar token de WhatsApp en configuración
# Comprobar que el webhook esté configurado
# Revisar logs de errores PHP
```
## 📞 Ejemplo de Uso Completo
### 1. Configuración Inicial
```bash
# Bot configurado para empresa de servicios
# Menús: Servicios, Pagos, Soporte
# Respuestas automáticas para palabras clave
```
### 2. Flujo de Usuario
```
Usuario: "Hola"
Bot: "¡Hola! 👋 Bienvenido a nuestro servicio automatizado. Escribe *menu* para ver las opciones disponibles."
Usuario: "menu"
Bot: "📋 Menú Principal
1. 💼 Ver servicios disponibles
2. 💳 Realizar pago
3. 🆘 Contactar soporte
0. ❌ Salir"
Usuario: "1"
Bot: "💼 Servicios
1. 📋 Consulta de información
2. 📞 Agendar cita
3. 💰 Cotizar servicio
0. 🔙 Volver al menú principal"
Usuario: "2"
Bot: "Para agendar una cita, por favor proporciona tu nombre completo y tu disponibilidad horaria."
```
## 🎨 Personalización
### Colores y Estilos
```css
/* Modificar en assets/css/styles.css */
:root {
--primary-color: #25d366; /* Verde WhatsApp */
--secondary-color: #128c7e;
--tertiary-color: #075e54;
}
```
### Mensajes del Bot
```sql
-- Modificar mensaje de bienvenida
UPDATE system_config
SET config_value = 'Tu mensaje personalizado'
WHERE config_key = 'welcome_message';
```
## 📈 Monitoreo
- Dashboard en tiempo real con métricas
- Gráficos de actividad por día
- Estados de entrega de mensajes
- Logs detallados de todas las operaciones
## 🤝 Soporte
Para soporte técnico o consultas:
- Email: soporte@tuempresa.com
- WhatsApp: +57 1 234 5678
- Web: www.tuempresa.com
---
**Desarrollado con ❤️ para facilitar la comunicación empresarial a través de WhatsApp**
© 2025 - Sistema WhatsApp Bot Manager v1.0.0
+142
View File
@@ -0,0 +1,142 @@
# 🚨 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!** 🚀
+98
View File
@@ -0,0 +1,98 @@
# ✅ 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.
+143
View File
@@ -0,0 +1,143 @@
# 🚨 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! 🚀
+79
View File
@@ -0,0 +1,79 @@
<?php
/**
* API - Exportar usuarios a CSV
* Fecha: 13 de noviembre de 2025
*/
require_once '../config/config.php';
// Headers para descarga de archivo
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename="usuarios_whatsapp_' . date('Y-m-d') . '.csv"');
header('Cache-Control: no-cache, must-revalidate');
try {
$db = Database::getInstance();
// Obtener todos los usuarios con información adicional
$users = $db->fetchAll(
"SELECT
u.id,
u.phone_number,
u.name,
u.email,
u.status,
u.created_at,
u.updated_at,
COUNT(c.id) as total_messages,
MAX(c.created_at) as last_activity
FROM users u
LEFT JOIN conversations c ON u.id = c.user_id
GROUP BY u.id
ORDER BY u.created_at DESC"
);
// Crear el archivo CSV
$output = fopen('php://output', 'w');
// BOM para UTF-8
fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF));
// Encabezados
fputcsv($output, [
'ID',
'Teléfono',
'Nombre',
'Email',
'Estado',
'Total Mensajes',
'Última Actividad',
'Fecha Registro'
], ';');
// Datos
foreach ($users as $user) {
fputcsv($output, [
$user['id'],
$user['phone_number'],
$user['name'] ?? 'Sin nombre',
$user['email'] ?? 'Sin email',
$user['status'],
$user['total_messages'],
$user['last_activity'] ? date('d/m/Y H:i', strtotime($user['last_activity'])) : 'Nunca',
date('d/m/Y H:i', strtotime($user['created_at']))
], ';');
}
fclose($output);
} catch (Exception $e) {
error_log("Error in export_users.php: " . $e->getMessage());
// Cambiar headers para error
header('Content-Type: application/json; charset=utf-8');
header('Content-Disposition: inline');
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor']);
}
?>
+47
View File
@@ -0,0 +1,47 @@
<?php
/**
* API - Obtener datos para gráficos
* Fecha: 13 de noviembre de 2025
*/
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
header('Access-Control-Allow-Headers: Content-Type');
try {
$db = Database::getInstance();
// Obtener datos de los últimos 7 días
$chartData = $db->fetchAll(
"SELECT
DATE(created_at) as date,
COUNT(*) as message_count
FROM conversations
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY DATE(created_at)
ORDER BY date ASC"
);
// Formatear datos para Chart.js
$labels = [];
$data = [];
foreach ($chartData as $row) {
$labels[] = date('d/m', strtotime($row['date']));
$data[] = (int)$row['message_count'];
}
echo json_encode([
'labels' => $labels,
'data' => $data
]);
} catch (Exception $e) {
error_log("Error in get_chart_data.php: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor']);
}
?>
+41
View File
@@ -0,0 +1,41 @@
<?php
/**
* API - Obtener conversaciones recientes
* Fecha: 13 de noviembre de 2025
*/
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
header('Access-Control-Allow-Headers: Content-Type');
try {
$db = Database::getInstance();
$conversations = $db->fetchAll(
"SELECT
c.id,
c.user_id,
c.content,
c.direction,
c.message_type,
c.status,
c.created_at,
u.phone_number,
u.name
FROM conversations c
JOIN users u ON c.user_id = u.id
ORDER BY c.created_at DESC
LIMIT 50"
);
echo json_encode($conversations);
} catch (Exception $e) {
error_log("Error in get_conversations.php: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor']);
}
?>
+44
View File
@@ -0,0 +1,44 @@
<?php
/**
* API - Obtener logs del webhook
* Fecha: 13 de noviembre de 2025
*/
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
header('Access-Control-Allow-Headers: Content-Type');
try {
$db = Database::getInstance();
$limit = $_GET['limit'] ?? 50;
$offset = $_GET['offset'] ?? 0;
$logs = $db->fetchAll(
"SELECT
id,
status_code,
ip_address,
created_at,
SUBSTRING(request_body, 1, 100) as request_preview,
SUBSTRING(response_body, 1, 100) as response_preview
FROM webhook_logs
ORDER BY created_at DESC
LIMIT :limit OFFSET :offset",
[
'limit' => (int)$limit,
'offset' => (int)$offset
]
);
echo json_encode($logs);
} catch (Exception $e) {
error_log("Error in get_logs.php: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor']);
}
?>
+51
View File
@@ -0,0 +1,51 @@
<?php
/**
* API - Obtener menús
* Fecha: 13 de noviembre de 2025
*/
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
header('Access-Control-Allow-Headers: Content-Type');
try {
$db = Database::getInstance();
$menus = $db->fetchAll(
"SELECT
id,
name,
title,
description,
parent_id,
is_root,
is_active,
order_position,
created_at,
updated_at
FROM menus
ORDER BY order_position ASC, title ASC"
);
// Obtener opciones de cada menú
foreach ($menus as &$menu) {
$options = $db->fetchAll(
"SELECT * FROM menu_options
WHERE menu_id = :menu_id
ORDER BY option_number ASC",
['menu_id' => $menu['id']]
);
$menu['options'] = $options;
}
echo json_encode($menus);
} catch (Exception $e) {
error_log("Error in get_menus.php: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor']);
}
?>
+39
View File
@@ -0,0 +1,39 @@
<?php
/**
* API - Obtener mensajes recientes para dashboard
* Fecha: 13 de noviembre de 2025
*/
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
header('Access-Control-Allow-Headers: Content-Type');
try {
$db = Database::getInstance();
$recentMessages = $db->fetchAll(
"SELECT
c.content,
c.direction,
c.message_type,
c.created_at,
u.phone_number,
u.name
FROM conversations c
JOIN users u ON c.user_id = u.id
WHERE c.content IS NOT NULL AND c.content != ''
ORDER BY c.created_at DESC
LIMIT 10"
);
echo json_encode($recentMessages);
} catch (Exception $e) {
error_log("Error in get_recent_messages.php: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor']);
}
?>
+33
View File
@@ -0,0 +1,33 @@
<?php
/**
* API - Obtener configuración del sistema
* Fecha: 13 de noviembre de 2025
*/
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
header('Access-Control-Allow-Headers: Content-Type');
try {
$db = Database::getInstance();
$configs = $db->fetchAll(
"SELECT config_key, config_value FROM system_config"
);
$settings = [];
foreach ($configs as $config) {
$settings[$config['config_key']] = $config['config_value'];
}
echo json_encode($settings);
} catch (Exception $e) {
error_log("Error in get_settings.php: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor']);
}
?>
+48
View File
@@ -0,0 +1,48 @@
<?php
/**
* API - Obtener estadísticas del dashboard
* Fecha: 13 de noviembre de 2025
*/
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
header('Access-Control-Allow-Headers: Content-Type');
try {
$db = Database::getInstance();
// Total de usuarios
$totalUsers = $db->fetch("SELECT COUNT(*) as count FROM users")['count'];
// Mensajes hoy
$messagesToday = $db->fetch(
"SELECT COUNT(*) as count FROM conversations WHERE DATE(created_at) = CURDATE()"
)['count'];
// Usuarios activos (último mes)
$activeUsers = $db->fetch(
"SELECT COUNT(DISTINCT user_id) as count FROM conversations
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)"
)['count'];
// Total de mensajes
$totalMessages = $db->fetch("SELECT COUNT(*) as count FROM conversations")['count'];
$stats = [
'total_users' => (int)$totalUsers,
'messages_today' => (int)$messagesToday,
'active_users' => (int)$activeUsers,
'total_messages' => (int)$totalMessages
];
echo json_encode($stats);
} catch (Exception $e) {
error_log("Error in get_stats.php: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor']);
}
?>
+37
View File
@@ -0,0 +1,37 @@
<?php
/**
* API - Obtener plantillas de mensaje
* Fecha: 13 de noviembre de 2025
*/
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
header('Access-Control-Allow-Headers: Content-Type');
try {
$db = Database::getInstance();
$templates = $db->fetchAll(
"SELECT
id,
name,
template_name,
language_code,
category,
status,
created_at
FROM message_templates
ORDER BY name ASC"
);
echo json_encode($templates);
} catch (Exception $e) {
error_log("Error in get_templates.php: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor']);
}
?>
+41
View File
@@ -0,0 +1,41 @@
<?php
/**
* API - Obtener usuarios
* Fecha: 13 de noviembre de 2025
*/
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
header('Access-Control-Allow-Headers: Content-Type');
try {
$db = Database::getInstance();
$users = $db->fetchAll(
"SELECT
u.id,
u.phone_number,
u.name,
u.email,
u.status,
u.current_menu_id,
u.current_step,
u.created_at,
u.updated_at,
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"
);
echo json_encode($users);
} catch (Exception $e) {
error_log("Error in get_users.php: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor']);
}
?>
+64
View File
@@ -0,0 +1,64 @@
<?php
/**
* API - Guardar menú
* Fecha: 13 de noviembre de 2025
*/
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Método no permitido']);
exit;
}
try {
$input = json_decode(file_get_contents('php://input'), true);
if (!$input || !isset($input['name']) || !isset($input['title'])) {
http_response_code(400);
echo json_encode(['error' => 'Nombre y título son requeridos']);
exit;
}
$db = Database::getInstance();
// Verificar si es menú raíz
$isRoot = empty($input['parent_id']) ? 1 : 0;
// Obtener próximo order_position
$maxOrder = $db->fetch(
"SELECT COALESCE(MAX(order_position), 0) as max_order FROM menus"
);
$menuData = [
'name' => $input['name'],
'title' => $input['title'],
'description' => $input['description'] ?? '',
'parent_id' => empty($input['parent_id']) ? null : $input['parent_id'],
'is_root' => $isRoot,
'is_active' => $input['is_active'] ?? 1,
'order_position' => $maxOrder['max_order'] + 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
];
$menuId = $db->insert('menus', $menuData);
echo json_encode([
'success' => true,
'message' => 'Menú guardado correctamente',
'menu_id' => $menuId
]);
} catch (Exception $e) {
error_log("Error in save_menu.php: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor: ' . $e->getMessage()]);
}
?>
+89
View File
@@ -0,0 +1,89 @@
<?php
/**
* API - Guardar configuración del sistema
* Fecha: 13 de noviembre de 2025
*/
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Método no permitido']);
exit;
}
try {
$input = json_decode(file_get_contents('php://input'), true);
if (!$input) {
http_response_code(400);
echo json_encode(['error' => 'Datos inválidos']);
exit;
}
$db = Database::getInstance();
// Mapear configuraciones
$configMap = [
'whatsapp_token' => $input['whatsapp_token'] ?? '',
'phone_number_id' => $input['phone_number_id'] ?? '',
'webhook_verify_token' => $input['webhook_token'] ?? '',
'business_name' => $input['business_name'] ?? '',
'welcome_message' => $input['welcome_message'] ?? ''
];
$db->beginTransaction();
try {
foreach ($configMap as $key => $value) {
if (!empty($value)) {
// Verificar si la configuración existe
$existing = $db->fetch(
"SELECT id FROM system_config WHERE config_key = :key",
['key' => $key]
);
if ($existing) {
// Actualizar
$db->update(
'system_config',
['config_value' => $value, 'updated_at' => date('Y-m-d H:i:s')],
'config_key = :key',
['key' => $key]
);
} else {
// Insertar
$db->insert('system_config', [
'config_key' => $key,
'config_value' => $value,
'description' => 'Configurado desde la interfaz web',
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
]);
}
}
}
$db->commit();
echo json_encode([
'success' => true,
'message' => 'Configuración guardada correctamente'
]);
} catch (Exception $e) {
$db->rollback();
throw $e;
}
} catch (Exception $e) {
error_log("Error in save_settings.php: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor: ' . $e->getMessage()]);
}
?>
+88
View File
@@ -0,0 +1,88 @@
<?php
/**
* API - Envío masivo de mensajes
* Fecha: 13 de noviembre de 2025
*/
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Método no permitido']);
exit;
}
try {
$input = json_decode(file_get_contents('php://input'), true);
if (!$input || !isset($input['message'])) {
http_response_code(400);
echo json_encode(['error' => 'Mensaje requerido']);
exit;
}
$filter = $input['filter'] ?? 'all';
$message = $input['message'];
$db = Database::getInstance();
$whatsappService = new WhatsAppService();
// Construir query según filtro
$whereClause = "WHERE u.status = 'active'";
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;
}
// Obtener usuarios según filtro
$users = $db->fetchAll(
"SELECT phone_number FROM users u " . $whereClause
);
$sentCount = 0;
$errorCount = 0;
foreach ($users as $user) {
try {
$response = $whatsappService->sendTextMessage($user['phone_number'], $message);
if ($response) {
$sentCount++;
}
// Pequeña pausa para no saturar la API
usleep(100000); // 0.1 segundo
} catch (Exception $e) {
$errorCount++;
error_log("Error sending broadcast to {$user['phone_number']}: " . $e->getMessage());
}
}
echo json_encode([
'success' => true,
'sent_count' => $sentCount,
'error_count' => $errorCount,
'total_users' => count($users)
]);
} catch (Exception $e) {
error_log("Error in send_broadcast.php: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor: ' . $e->getMessage()]);
}
?>
+78
View File
@@ -0,0 +1,78 @@
<?php
/**
* API - Enviar mensaje individual
* Fecha: 13 de noviembre de 2025
*/
require_once '../config/config.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['error' => 'Método no permitido']);
exit;
}
try {
$input = json_decode(file_get_contents('php://input'), true);
if (!$input || !isset($input['recipient'])) {
http_response_code(400);
echo json_encode(['error' => 'Datos inválidos']);
exit;
}
$recipient = $input['recipient'];
$type = $input['type'] ?? 'text';
$whatsappService = new WhatsAppService();
$response = null;
switch ($type) {
case 'text':
if (!isset($input['message']) || empty($input['message'])) {
http_response_code(400);
echo json_encode(['error' => 'Mensaje requerido']);
exit;
}
$response = $whatsappService->sendTextMessage($recipient, $input['message']);
break;
case 'template':
if (!isset($input['template']) || empty($input['template'])) {
http_response_code(400);
echo json_encode(['error' => 'Plantilla requerida']);
exit;
}
$language = $input['language'] ?? 'es';
$parameters = $input['parameters'] ?? [];
$response = $whatsappService->sendTemplateMessage($recipient, $input['template'], $language, $parameters);
break;
default:
http_response_code(400);
echo json_encode(['error' => 'Tipo de mensaje no válido']);
exit;
}
if ($response) {
echo json_encode([
'success' => true,
'message' => 'Mensaje enviado correctamente',
'whatsapp_response' => $response
]);
} else {
http_response_code(500);
echo json_encode(['error' => 'Error enviando mensaje']);
}
} catch (Exception $e) {
error_log("Error in send_message.php: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor: ' . $e->getMessage()]);
}
?>
+221
View File
@@ -0,0 +1,221 @@
<?php
/**
* Webhook para recibir mensajes de WhatsApp
* Fecha: 13 de noviembre de 2025
*/
require_once '../config/config.php';
// Headers para API
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
header('Access-Control-Allow-Headers: Content-Type');
class WhatsAppWebhook {
private $db;
private $whatsappService;
private $botService;
public function __construct() {
$this->db = Database::getInstance();
$this->whatsappService = new WhatsAppService();
$this->botService = new BotService();
}
public function handleRequest() {
$method = $_SERVER['REQUEST_METHOD'];
if ($method === 'GET') {
$this->verifyWebhook();
} elseif ($method === 'POST') {
$this->processIncomingMessage();
} else {
http_response_code(405);
echo json_encode(['error' => 'Método no permitido']);
}
}
private function verifyWebhook() {
$verifyToken = $_GET['hub_verify_token'] ?? '';
$challenge = $_GET['hub_challenge'] ?? '';
$mode = $_GET['hub_mode'] ?? '';
if ($mode === 'subscribe' && $verifyToken === WEBHOOK_VERIFY_TOKEN) {
echo $challenge;
exit;
} else {
http_response_code(403);
echo json_encode(['error' => 'Token de verificación inválido']);
}
}
private function processIncomingMessage() {
$input = file_get_contents('php://input');
$data = json_decode($input, true);
// Registrar webhook en logs
$this->logWebhook($input, json_encode(['status' => 'received']), 200);
if (!$data || !isset($data['entry'])) {
http_response_code(400);
echo json_encode(['error' => 'Datos inválidos']);
return;
}
try {
foreach ($data['entry'] as $entry) {
if (isset($entry['changes'])) {
foreach ($entry['changes'] as $change) {
if ($change['field'] === 'messages') {
$this->processMessages($change['value']);
}
}
}
}
echo json_encode(['status' => 'success']);
} catch (Exception $e) {
error_log("Error processing webhook: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error interno del servidor']);
}
}
private function processMessages($value) {
if (!isset($value['messages'])) {
return;
}
foreach ($value['messages'] as $message) {
$phoneNumber = $message['from'];
$messageId = $message['id'];
$timestamp = $message['timestamp'];
// Verificar si ya procesamos este mensaje
$existing = $this->db->fetch(
"SELECT id FROM conversations WHERE message_id = :message_id",
['message_id' => $messageId]
);
if ($existing) {
continue; // Ya procesamos este mensaje
}
// Obtener o crear usuario
$user = $this->getUserByPhone($phoneNumber);
if (!$user) {
$userId = $this->createUser($phoneNumber);
$user = $this->getUserById($userId);
}
// Procesar diferentes tipos de mensaje
$messageText = '';
$messageType = 'text';
$mediaUrl = null;
if (isset($message['text'])) {
$messageText = $message['text']['body'];
$messageType = 'text';
} elseif (isset($message['image'])) {
$messageText = $message['image']['caption'] ?? '';
$messageType = 'image';
$mediaUrl = $message['image']['id'];
} elseif (isset($message['audio'])) {
$messageType = 'audio';
$mediaUrl = $message['audio']['id'];
} elseif (isset($message['video'])) {
$messageText = $message['video']['caption'] ?? '';
$messageType = 'video';
$mediaUrl = $message['video']['id'];
} elseif (isset($message['document'])) {
$messageText = $message['document']['filename'] ?? '';
$messageType = 'document';
$mediaUrl = $message['document']['id'];
}
// Guardar mensaje en base de datos
$this->saveMessage([
'user_id' => $user['id'],
'message_id' => $messageId,
'direction' => 'incoming',
'message_type' => $messageType,
'content' => $messageText,
'media_url' => $mediaUrl,
'status' => 'received'
]);
// Procesar con bot
$this->botService->processMessage($user, $messageText, $messageType);
}
// Procesar estados de mensajes (entregado, leído, etc.)
if (isset($value['statuses'])) {
$this->processMessageStatuses($value['statuses']);
}
}
private function processMessageStatuses($statuses) {
foreach ($statuses as $status) {
$messageId = $status['id'];
$newStatus = $status['status']; // sent, delivered, read, failed
$this->db->update(
'conversations',
['status' => $newStatus],
'message_id = :message_id',
['message_id' => $messageId]
);
}
}
private function getUserByPhone($phoneNumber) {
return $this->db->fetch(
"SELECT * FROM users WHERE phone_number = :phone",
['phone' => $phoneNumber]
);
}
private function getUserById($userId) {
return $this->db->fetch(
"SELECT * FROM users WHERE id = :id",
['id' => $userId]
);
}
private function createUser($phoneNumber) {
return $this->db->insert('users', [
'phone_number' => $phoneNumber,
'status' => 'active',
'created_at' => date('Y-m-d H:i:s')
]);
}
private function saveMessage($messageData) {
return $this->db->insert('conversations', $messageData);
}
private function logWebhook($requestBody, $responseBody, $statusCode) {
if (ENABLE_LOGGING) {
$this->db->insert('webhook_logs', [
'request_body' => $requestBody,
'response_body' => $responseBody,
'status_code' => $statusCode,
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'created_at' => date('Y-m-d H:i:s')
]);
}
}
}
// Procesar la solicitud
try {
$webhook = new WhatsAppWebhook();
$webhook->handleRequest();
} catch (Exception $e) {
error_log("Fatal error in webhook: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Error fatal del servidor']);
}
?>
+694
View File
@@ -0,0 +1,694 @@
/*
* WhatsApp Bot Manager - Estilos CSS
* Fecha: 13 de noviembre de 2025
*/
:root {
--primary-color: #25d366;
--secondary-color: #128c7e;
--tertiary-color: #075e54;
--accent-color: #dcf8c6;
--dark-bg: #1f2937;
--light-bg: #f9fafb;
--border-color: #e5e7eb;
--text-primary: #1f2937;
--text-secondary: #6b7280;
--success-color: #10b981;
--warning-color: #f59e0b;
--error-color: #ef4444;
--info-color: #3b82f6;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: var(--light-bg);
color: var(--text-primary);
line-height: 1.6;
}
/* Sidebar */
.sidebar {
position: fixed;
left: 0;
top: 0;
width: 280px;
height: 100vh;
background: linear-gradient(135deg, var(--tertiary-color), var(--secondary-color));
color: white;
z-index: 1000;
overflow-y: auto;
transition: all 0.3s ease;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
}
.sidebar-header {
padding: 20px;
text-align: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.05);
}
.sidebar-header h4 {
font-weight: 600;
margin: 0;
font-size: 1.2rem;
}
.sidebar-menu {
list-style: none;
padding: 0;
margin: 0;
}
.sidebar-menu li {
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.sidebar-menu .nav-link {
display: block;
padding: 15px 20px;
color: rgba(255, 255, 255, 0.8);
text-decoration: none;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.sidebar-menu .nav-link::before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 4px;
height: 100%;
background: var(--primary-color);
transform: translateX(-100%);
transition: transform 0.3s ease;
}
.sidebar-menu .nav-link:hover,
.sidebar-menu .nav-link.active {
color: white;
background: rgba(255, 255, 255, 0.1);
transform: translateX(5px);
}
.sidebar-menu .nav-link.active::before,
.sidebar-menu .nav-link:hover::before {
transform: translateX(0);
}
.sidebar-menu .nav-link i {
margin-right: 10px;
width: 20px;
text-align: center;
}
/* Main Content */
.main-content {
margin-left: 280px;
min-height: 100vh;
transition: margin-left 0.3s ease;
}
.content-header {
background: white;
padding: 20px 30px;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
position: sticky;
top: 0;
z-index: 100;
}
.content-header h1 {
color: var(--text-primary);
font-size: 1.5rem;
font-weight: 600;
margin: 0;
}
.header-actions {
display: flex;
align-items: center;
gap: 15px;
}
.status-indicator {
display: flex;
align-items: center;
gap: 5px;
font-size: 0.9rem;
font-weight: 500;
}
.status-indicator.online {
color: var(--success-color);
}
.status-indicator i {
font-size: 0.7rem;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
/* Tab Content */
.tab-content {
display: none;
padding: 30px;
animation: fadeIn 0.3s ease;
}
.tab-content.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Cards */
.card {
background: white;
border-radius: 12px;
border: none;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.07);
margin-bottom: 20px;
transition: all 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}
.card-header {
background: var(--light-bg);
border-bottom: 1px solid var(--border-color);
padding: 20px;
border-radius: 12px 12px 0 0;
}
.card-header h5 {
margin: 0;
color: var(--text-primary);
font-weight: 600;
}
.card-body {
padding: 20px;
}
/* Stat Cards */
.card-stat {
border-radius: 12px;
border: none;
transition: all 0.3s ease;
}
.card-stat:hover {
transform: translateY(-3px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.card-stat-title {
font-size: 0.85rem;
font-weight: 500;
opacity: 0.9;
margin-bottom: 5px;
}
.card-stat-number {
font-size: 2.5rem;
font-weight: 700;
margin: 0;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* Buttons */
.btn {
border-radius: 8px;
font-weight: 500;
padding: 10px 20px;
transition: all 0.3s ease;
border: none;
position: relative;
overflow: hidden;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.btn:hover::before {
left: 100%;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
color: white;
}
.btn-primary:hover {
background: linear-gradient(135deg, var(--secondary-color), var(--tertiary-color));
color: white;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(37, 211, 102, 0.4);
}
.btn-success {
background: var(--success-color);
}
.btn-warning {
background: var(--warning-color);
}
.btn-danger {
background: var(--error-color);
}
.btn-info {
background: var(--info-color);
}
/* Forms */
.form-control {
border: 2px solid var(--border-color);
border-radius: 8px;
padding: 12px 15px;
transition: all 0.3s ease;
font-size: 0.95rem;
}
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.2rem rgba(37, 211, 102, 0.25);
outline: none;
}
.form-label {
font-weight: 600;
color: var(--text-primary);
margin-bottom: 8px;
}
/* Tables */
.table {
border-collapse: separate;
border-spacing: 0;
}
.table th {
background: var(--light-bg);
color: var(--text-primary);
font-weight: 600;
border: none;
padding: 15px;
font-size: 0.9rem;
}
.table td {
padding: 15px;
border-top: 1px solid var(--border-color);
vertical-align: middle;
}
.table-hover tbody tr:hover {
background-color: rgba(37, 211, 102, 0.05);
}
/* Badges */
.badge {
padding: 6px 12px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 500;
}
.badge-success {
background: var(--success-color);
color: white;
}
.badge-warning {
background: var(--warning-color);
color: white;
}
.badge-danger {
background: var(--error-color);
color: white;
}
.badge-info {
background: var(--info-color);
color: white;
}
.badge-secondary {
background: var(--text-secondary);
color: white;
}
/* Menu Tree */
.menu-tree {
list-style: none;
padding-left: 0;
}
.menu-tree li {
margin-bottom: 10px;
}
.menu-item {
background: white;
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 15px;
display: flex;
justify-content: between;
align-items: center;
transition: all 0.3s ease;
}
.menu-item:hover {
border-color: var(--primary-color);
background: rgba(37, 211, 102, 0.05);
}
.menu-item.root {
border-left: 4px solid var(--primary-color);
}
.menu-item.child {
margin-left: 20px;
border-left: 4px solid var(--info-color);
}
/* Recent Messages */
.recent-message {
padding: 10px 15px;
border-radius: 8px;
margin-bottom: 10px;
border-left: 4px solid var(--primary-color);
background: rgba(37, 211, 102, 0.05);
}
.recent-message .time {
font-size: 0.8rem;
color: var(--text-secondary);
}
.recent-message .content {
font-size: 0.9rem;
margin-top: 5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Loading Spinner */
.spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Alerts */
.alert {
border: none;
border-radius: 8px;
padding: 15px 20px;
}
.alert-success {
background: rgba(16, 185, 129, 0.1);
border-left: 4px solid var(--success-color);
color: var(--success-color);
}
.alert-warning {
background: rgba(245, 158, 11, 0.1);
border-left: 4px solid var(--warning-color);
color: var(--warning-color);
}
.alert-danger {
background: rgba(239, 68, 68, 0.1);
border-left: 4px solid var(--error-color);
color: var(--error-color);
}
.alert-info {
background: rgba(59, 130, 246, 0.1);
border-left: 4px solid var(--info-color);
color: var(--info-color);
}
/* Chat Interface */
.chat-container {
height: 400px;
overflow-y: auto;
background: #f0f0f0;
border-radius: 8px;
padding: 15px;
}
.message {
margin-bottom: 15px;
display: flex;
align-items: flex-start;
}
.message.incoming {
justify-content: flex-start;
}
.message.outgoing {
justify-content: flex-end;
}
.message-bubble {
max-width: 70%;
padding: 10px 15px;
border-radius: 18px;
position: relative;
}
.message.incoming .message-bubble {
background: white;
border-bottom-left-radius: 4px;
}
.message.outgoing .message-bubble {
background: var(--primary-color);
color: white;
border-bottom-right-radius: 4px;
}
.message-time {
font-size: 0.7rem;
opacity: 0.7;
margin-top: 5px;
}
/* Responsive Design */
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
}
.sidebar.show {
transform: translateX(0);
}
.main-content {
margin-left: 0;
}
.content-header {
padding: 15px 20px;
}
.tab-content {
padding: 20px 15px;
}
.card-stat-number {
font-size: 2rem;
}
}
/* Dark Mode Support */
@media (prefers-color-scheme: dark) {
:root {
--light-bg: #1f2937;
--text-primary: #f9fafb;
--text-secondary: #d1d5db;
--border-color: #374151;
}
body {
background-color: var(--dark-bg);
color: var(--text-primary);
}
.card {
background: #374151;
}
.card-header {
background: #4b5563;
}
.table th {
background: #4b5563;
}
}
/* Utility Classes */
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-left { text-align: left; }
.mb-0 { margin-bottom: 0; }
.mb-1 { margin-bottom: 0.25rem; }
.mb-2 { margin-bottom: 0.5rem; }
.mb-3 { margin-bottom: 1rem; }
.mb-4 { margin-bottom: 1.5rem; }
.mb-5 { margin-bottom: 3rem; }
.mt-0 { margin-top: 0; }
.mt-1 { margin-top: 0.25rem; }
.mt-2 { margin-top: 0.5rem; }
.mt-3 { margin-top: 1rem; }
.mt-4 { margin-top: 1.5rem; }
.mt-5 { margin-top: 3rem; }
.d-flex { display: flex; }
.d-block { display: block; }
.d-none { display: none; }
.justify-content-between { justify-content: space-between; }
.justify-content-center { justify-content: center; }
.justify-content-end { justify-content: flex-end; }
.align-items-center { align-items: center; }
.align-items-start { align-items: flex-start; }
.align-items-end { align-items: flex-end; }
.w-100 { width: 100%; }
.h-100 { height: 100%; }
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--border-color);
}
::-webkit-scrollbar-thumb {
background: var(--text-secondary);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--primary-color);
}
/* Animation Classes */
.fade-in {
animation: fadeIn 0.3s ease;
}
.slide-up {
animation: slideUp 0.3s ease;
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.bounce {
animation: bounce 0.5s ease;
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
40% { transform: translateY(-10px); }
60% { transform: translateY(-5px); }
}
/* Custom Components */
.progress-ring {
transform: rotate(-90deg);
}
.progress-ring__circle {
stroke: var(--primary-color);
stroke-linecap: round;
transition: stroke-dasharray 0.35s;
}
.notification-dot {
position: absolute;
top: -5px;
right: -5px;
width: 10px;
height: 10px;
background: var(--error-color);
border-radius: 50%;
animation: pulse 2s infinite;
}
/* Print Styles */
@media print {
.sidebar,
.header-actions,
.btn {
display: none !important;
}
.main-content {
margin-left: 0;
}
.card {
box-shadow: none;
border: 1px solid #ddd;
}
}
+912
View File
@@ -0,0 +1,912 @@
/**
* WhatsApp Bot Manager - JavaScript
* Fecha: 13 de noviembre de 2025
*/
class WhatsAppBotManager {
constructor() {
this.apiBaseUrl = './api/';
this.currentTab = 'dashboard';
this.currentMenuId = null;
this.charts = {};
this.init();
}
init() {
this.setupEventListeners();
this.loadDashboard();
this.setupCharts();
// Auto-refresh cada 30 segundos
setInterval(() => {
if (this.currentTab === 'dashboard') {
this.refreshStats();
}
}, 30000);
}
setupEventListeners() {
// Navegación de tabs
document.querySelectorAll('.nav-link').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const tabName = link.getAttribute('data-tab');
this.showTab(tabName);
});
});
// Formularios
this.setupFormListeners();
// Búsquedas
this.setupSearchListeners();
}
setupFormListeners() {
// Formulario de configuración
const settingsForm = document.getElementById('settings-form');
if (settingsForm) {
settingsForm.addEventListener('submit', (e) => {
e.preventDefault();
this.saveSettings();
});
}
// Formulario de menú
const menuForm = document.getElementById('menu-form');
if (menuForm) {
menuForm.addEventListener('submit', (e) => {
e.preventDefault();
this.saveMenu();
});
}
// Formulario de envío de mensaje
const sendMessageForm = document.getElementById('send-message-form');
if (sendMessageForm) {
sendMessageForm.addEventListener('submit', (e) => {
e.preventDefault();
this.sendMessage();
});
}
// Formulario de mensaje masivo
const broadcastForm = document.getElementById('broadcast-form');
if (broadcastForm) {
broadcastForm.addEventListener('submit', (e) => {
e.preventDefault();
this.sendBroadcast();
});
}
// Cambio de tipo de mensaje
const messageType = document.getElementById('message-type');
if (messageType) {
messageType.addEventListener('change', () => {
this.toggleMessageFields();
});
}
}
setupSearchListeners() {
// Búsqueda de conversaciones
const searchConversations = document.getElementById('search-conversations');
if (searchConversations) {
searchConversations.addEventListener('input', (e) => {
this.searchConversations(e.target.value);
});
}
}
showTab(tabName) {
// Ocultar todas las pestañas
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
// Remover clase active de navegación
document.querySelectorAll('.nav-link').forEach(link => {
link.classList.remove('active');
});
// Mostrar pestaña seleccionada
const targetTab = document.getElementById(tabName);
if (targetTab) {
targetTab.classList.add('active');
}
// Activar enlace de navegación
const activeLink = document.querySelector(`[data-tab="${tabName}"]`);
if (activeLink) {
activeLink.classList.add('active');
}
this.currentTab = tabName;
// Cargar datos específicos del tab
this.loadTabData(tabName);
}
loadTabData(tabName) {
switch (tabName) {
case 'dashboard':
this.loadDashboard();
break;
case 'conversations':
this.loadConversations();
break;
case 'users':
this.loadUsers();
break;
case 'menus':
this.loadMenus();
break;
case 'messages':
this.loadMessageUsers();
break;
case 'templates':
this.loadTemplates();
break;
case 'autoresponses':
this.loadAutoResponses();
break;
case 'settings':
this.loadSettings();
break;
case 'logs':
this.loadLogs();
break;
}
}
async loadDashboard() {
try {
const stats = await this.apiCall('get_stats.php');
this.updateStats(stats);
const recentMessages = await this.apiCall('get_recent_messages.php');
this.updateRecentMessages(recentMessages);
this.updateChart();
} catch (error) {
this.showError('Error cargando dashboard: ' + error.message);
}
}
updateStats(stats) {
if (stats) {
document.getElementById('total-users').textContent = stats.total_users || 0;
document.getElementById('messages-today').textContent = stats.messages_today || 0;
document.getElementById('active-users').textContent = stats.active_users || 0;
document.getElementById('total-messages').textContent = stats.total_messages || 0;
}
}
updateRecentMessages(messages) {
const container = document.getElementById('recent-messages');
if (!container) return;
container.innerHTML = '';
if (messages && messages.length > 0) {
messages.forEach(message => {
const messageElement = this.createRecentMessageElement(message);
container.appendChild(messageElement);
});
} else {
container.innerHTML = '<div class="text-center text-muted">No hay mensajes recientes</div>';
}
}
createRecentMessageElement(message) {
const div = document.createElement('div');
div.className = 'recent-message';
const direction = message.direction === 'incoming' ? '📥' : '📤';
const time = new Date(message.created_at).toLocaleString();
div.innerHTML = `
<div class="d-flex justify-content-between">
<span class="fw-bold">${direction} ${message.phone_number}</span>
<span class="time">${time}</span>
</div>
<div class="content">${this.truncateText(message.content, 50)}</div>
`;
return div;
}
setupCharts() {
const ctx = document.getElementById('messagesChart');
if (ctx) {
this.charts.messages = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Mensajes',
data: [],
borderColor: '#25d366',
backgroundColor: 'rgba(37, 211, 102, 0.1)',
borderWidth: 3,
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
grid: {
color: 'rgba(0,0,0,0.1)'
}
},
x: {
grid: {
color: 'rgba(0,0,0,0.1)'
}
}
},
plugins: {
legend: {
display: false
}
}
}
});
}
}
async updateChart() {
try {
const chartData = await this.apiCall('get_chart_data.php');
if (chartData && this.charts.messages) {
this.charts.messages.data.labels = chartData.labels;
this.charts.messages.data.datasets[0].data = chartData.data;
this.charts.messages.update();
}
} catch (error) {
console.error('Error updating chart:', error);
}
}
async loadConversations() {
try {
const conversations = await this.apiCall('get_conversations.php');
this.updateConversationsTable(conversations);
} catch (error) {
this.showError('Error cargando conversaciones: ' + error.message);
}
}
updateConversationsTable(conversations) {
const tbody = document.getElementById('conversations-table');
if (!tbody) return;
tbody.innerHTML = '';
conversations.forEach(conv => {
const row = this.createConversationRow(conv);
tbody.appendChild(row);
});
}
createConversationRow(conv) {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>
<div class="fw-bold">${conv.phone_number}</div>
<small class="text-muted">${conv.name || 'Sin nombre'}</small>
</td>
<td>${this.truncateText(conv.last_message, 50)}</td>
<td>
<span class="badge badge-${this.getMessageTypeColor(conv.message_type)}">
${conv.message_type}
</span>
</td>
<td>
<span class="badge badge-${this.getStatusColor(conv.status)}">
${conv.status}
</span>
</td>
<td>${new Date(conv.last_activity).toLocaleString()}</td>
<td>
<button class="btn btn-sm btn-primary" onclick="app.viewConversation('${conv.user_id}')">
<i class="fas fa-eye"></i>
</button>
<button class="btn btn-sm btn-success" onclick="app.replyToUser('${conv.phone_number}')">
<i class="fas fa-reply"></i>
</button>
</td>
`;
return tr;
}
async loadUsers() {
try {
const users = await this.apiCall('get_users.php');
this.updateUsersTable(users);
} catch (error) {
this.showError('Error cargando usuarios: ' + error.message);
}
}
updateUsersTable(users) {
const tbody = document.getElementById('users-table');
if (!tbody) return;
tbody.innerHTML = '';
users.forEach(user => {
const row = this.createUserRow(user);
tbody.appendChild(row);
});
}
createUserRow(user) {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${user.id}</td>
<td>${user.phone_number}</td>
<td>${user.name || 'Sin nombre'}</td>
<td>
<span class="badge badge-${this.getStatusColor(user.status)}">
${user.status}
</span>
</td>
<td>${user.current_menu || 'Ninguno'}</td>
<td>${new Date(user.created_at).toLocaleString()}</td>
<td>
<button class="btn btn-sm btn-primary" onclick="app.editUser(${user.id})">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-sm btn-warning" onclick="app.blockUser(${user.id})">
<i class="fas fa-ban"></i>
</button>
</td>
`;
return tr;
}
async loadMenus() {
try {
const menus = await this.apiCall('get_menus.php');
this.updateMenusTree(menus);
this.loadMenuParents(menus);
} catch (error) {
this.showError('Error cargando menús: ' + error.message);
}
}
updateMenusTree(menus) {
const container = document.getElementById('menus-tree');
if (!container) return;
container.innerHTML = '';
// Organizar menús por jerarquía
const rootMenus = menus.filter(menu => menu.is_root);
rootMenus.forEach(menu => {
const menuElement = this.createMenuElement(menu, menus);
container.appendChild(menuElement);
});
}
createMenuElement(menu, allMenus) {
const div = document.createElement('div');
div.className = 'menu-item ' + (menu.is_root ? 'root' : 'child');
div.innerHTML = `
<div>
<h6 class="mb-1">${menu.title}</h6>
<small class="text-muted">${menu.description || 'Sin descripción'}</small>
<div class="mt-2">
<span class="badge badge-${menu.is_active ? 'success' : 'secondary'}">
${menu.is_active ? 'Activo' : 'Inactivo'}
</span>
</div>
</div>
<div>
<button class="btn btn-sm btn-primary me-2" onclick="app.editMenu(${menu.id})">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-sm btn-info me-2" onclick="app.manageMenuOptions(${menu.id})">
<i class="fas fa-list"></i>
</button>
<button class="btn btn-sm btn-danger" onclick="app.deleteMenu(${menu.id})">
<i class="fas fa-trash"></i>
</button>
</div>
`;
// Agregar submenús
const childMenus = allMenus.filter(child => child.parent_id === menu.id);
if (childMenus.length > 0) {
const childContainer = document.createElement('div');
childContainer.style.marginLeft = '20px';
childContainer.style.marginTop = '10px';
childMenus.forEach(child => {
const childElement = this.createMenuElement(child, allMenus);
childContainer.appendChild(childElement);
});
div.appendChild(childContainer);
}
return div;
}
loadMenuParents(menus) {
const select = document.getElementById('menu-parent');
if (!select) return;
select.innerHTML = '<option value="">Sin padre (Menú raíz)</option>';
menus.forEach(menu => {
const option = document.createElement('option');
option.value = menu.id;
option.textContent = menu.title;
select.appendChild(option);
});
}
async loadMessageUsers() {
try {
const users = await this.apiCall('get_users.php');
const templates = await this.apiCall('get_templates.php');
this.updateMessageUserSelect(users);
this.updateTemplateSelect(templates);
} catch (error) {
this.showError('Error cargando datos de mensaje: ' + error.message);
}
}
updateMessageUserSelect(users) {
const select = document.getElementById('message-recipient');
if (!select) return;
select.innerHTML = '<option value="">Seleccionar usuario...</option>';
users.forEach(user => {
const option = document.createElement('option');
option.value = user.phone_number;
option.textContent = `${user.phone_number} ${user.name ? '(' + user.name + ')' : ''}`;
select.appendChild(option);
});
}
updateTemplateSelect(templates) {
const select = document.getElementById('message-template');
if (!select) return;
select.innerHTML = '<option value="">Seleccionar plantilla...</option>';
templates.forEach(template => {
if (template.status === 'approved') {
const option = document.createElement('option');
option.value = template.template_name;
option.textContent = template.name;
select.appendChild(option);
}
});
}
toggleMessageFields() {
const messageType = document.getElementById('message-type').value;
const textGroup = document.getElementById('message-text-group');
const templateGroup = document.getElementById('template-group');
if (messageType === 'template') {
textGroup.style.display = 'none';
templateGroup.style.display = 'block';
} else {
textGroup.style.display = 'block';
templateGroup.style.display = 'none';
}
}
async sendMessage() {
const recipient = document.getElementById('message-recipient').value;
const messageType = document.getElementById('message-type').value;
const messageText = document.getElementById('message-text').value;
const template = document.getElementById('message-template').value;
if (!recipient) {
this.showError('Por favor selecciona un destinatario');
return;
}
const data = {
recipient: recipient,
type: messageType
};
if (messageType === 'text') {
if (!messageText) {
this.showError('Por favor escribe un mensaje');
return;
}
data.message = messageText;
} else if (messageType === 'template') {
if (!template) {
this.showError('Por favor selecciona una plantilla');
return;
}
data.template = template;
}
try {
this.showLoading('Enviando mensaje...');
const result = await this.apiCall('send_message.php', 'POST', data);
if (result.success) {
this.showSuccess('Mensaje enviado correctamente');
document.getElementById('send-message-form').reset();
} else {
this.showError(result.error || 'Error enviando mensaje');
}
} catch (error) {
this.showError('Error enviando mensaje: ' + error.message);
} finally {
this.hideLoading();
}
}
async sendBroadcast() {
const filter = document.getElementById('broadcast-filter').value;
const message = document.getElementById('broadcast-message').value;
if (!message) {
this.showError('Por favor escribe un mensaje');
return;
}
if (!confirm('¿Estás seguro de enviar este mensaje a múltiples usuarios?')) {
return;
}
try {
this.showLoading('Enviando mensajes...');
const result = await this.apiCall('send_broadcast.php', 'POST', {
filter: filter,
message: message
});
if (result.success) {
this.showSuccess(`Mensajes enviados a ${result.sent_count} usuarios`);
document.getElementById('broadcast-form').reset();
} else {
this.showError(result.error || 'Error en envío masivo');
}
} catch (error) {
this.showError('Error en envío masivo: ' + error.message);
} finally {
this.hideLoading();
}
}
async saveMenu() {
const formData = {
name: document.getElementById('menu-name').value,
title: document.getElementById('menu-title').value,
description: document.getElementById('menu-description').value,
parent_id: document.getElementById('menu-parent').value || null,
is_active: document.getElementById('menu-active').checked ? 1 : 0
};
try {
this.showLoading('Guardando menú...');
const result = await this.apiCall('save_menu.php', 'POST', formData);
if (result.success) {
this.showSuccess('Menú guardado correctamente');
document.getElementById('menu-form').reset();
this.loadMenus();
} else {
this.showError(result.error || 'Error guardando menú');
}
} catch (error) {
this.showError('Error guardando menú: ' + error.message);
} finally {
this.hideLoading();
}
}
async saveSettings() {
const formData = {
whatsapp_token: document.getElementById('whatsapp-token').value,
phone_number_id: document.getElementById('phone-number-id').value,
webhook_token: document.getElementById('webhook-token').value,
business_name: document.getElementById('business-name').value,
welcome_message: document.getElementById('welcome-message').value
};
try {
this.showLoading('Guardando configuración...');
const result = await this.apiCall('save_settings.php', 'POST', formData);
if (result.success) {
this.showSuccess('Configuración guardada correctamente');
} else {
this.showError(result.error || 'Error guardando configuración');
}
} catch (error) {
this.showError('Error guardando configuración: ' + error.message);
} finally {
this.hideLoading();
}
}
async loadSettings() {
try {
const settings = await this.apiCall('get_settings.php');
if (settings) {
document.getElementById('whatsapp-token').value = settings.whatsapp_token || '';
document.getElementById('phone-number-id').value = settings.phone_number_id || '';
document.getElementById('webhook-token').value = settings.webhook_token || '';
document.getElementById('business-name').value = settings.business_name || '';
document.getElementById('welcome-message').value = settings.welcome_message || '';
}
} catch (error) {
this.showError('Error cargando configuración: ' + error.message);
}
}
async loadLogs() {
try {
const logs = await this.apiCall('get_logs.php');
this.updateLogsTable(logs);
} catch (error) {
this.showError('Error cargando logs: ' + error.message);
}
}
updateLogsTable(logs) {
const tbody = document.getElementById('logs-table');
if (!tbody) return;
tbody.innerHTML = '';
logs.forEach(log => {
const row = this.createLogRow(log);
tbody.appendChild(row);
});
}
createLogRow(log) {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${new Date(log.created_at).toLocaleString()}</td>
<td>${log.ip_address}</td>
<td>
<span class="badge badge-${log.status_code === 200 ? 'success' : 'danger'}">
${log.status_code}
</span>
</td>
<td>
<button class="btn btn-sm btn-outline-primary" onclick="app.viewLogDetails('${log.id}', 'request')">
Ver Request
</button>
</td>
<td>
<button class="btn btn-sm btn-outline-primary" onclick="app.viewLogDetails('${log.id}', 'response')">
Ver Response
</button>
</td>
`;
return tr;
}
// Utility methods
async apiCall(endpoint, method = 'GET', data = null) {
const options = {
method: method,
headers: {
'Content-Type': 'application/json',
}
};
if (data && method !== 'GET') {
options.body = JSON.stringify(data);
}
const response = await fetch(this.apiBaseUrl + endpoint, options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
showError(message) {
this.showNotification(message, 'error');
}
showSuccess(message) {
this.showNotification(message, 'success');
}
showWarning(message) {
this.showNotification(message, 'warning');
}
showInfo(message) {
this.showNotification(message, 'info');
}
showNotification(message, type = 'info') {
// Crear notificación toast
const toast = document.createElement('div');
toast.className = `alert alert-${type} position-fixed`;
toast.style.top = '20px';
toast.style.right = '20px';
toast.style.zIndex = '9999';
toast.style.minWidth = '300px';
toast.innerHTML = `
<i class="fas fa-${this.getNotificationIcon(type)}"></i>
${message}
<button type="button" class="btn-close" onclick="this.parentElement.remove()"></button>
`;
document.body.appendChild(toast);
// Auto-remove después de 5 segundos
setTimeout(() => {
if (toast.parentElement) {
toast.remove();
}
}, 5000);
}
getNotificationIcon(type) {
const icons = {
success: 'check-circle',
error: 'exclamation-circle',
warning: 'exclamation-triangle',
info: 'info-circle'
};
return icons[type] || 'info-circle';
}
showLoading(message = 'Cargando...') {
const loading = document.createElement('div');
loading.id = 'loading-overlay';
loading.className = 'position-fixed w-100 h-100 d-flex align-items-center justify-content-center';
loading.style.top = '0';
loading.style.left = '0';
loading.style.backgroundColor = 'rgba(0,0,0,0.5)';
loading.style.zIndex = '9999';
loading.innerHTML = `
<div class="bg-white p-4 rounded text-center">
<div class="spinner mb-3"></div>
<div>${message}</div>
</div>
`;
document.body.appendChild(loading);
}
hideLoading() {
const loading = document.getElementById('loading-overlay');
if (loading) {
loading.remove();
}
}
truncateText(text, length) {
if (!text) return '';
return text.length > length ? text.substring(0, length) + '...' : text;
}
getStatusColor(status) {
const colors = {
'active': 'success',
'inactive': 'secondary',
'blocked': 'danger',
'sent': 'info',
'delivered': 'success',
'read': 'success',
'failed': 'danger'
};
return colors[status] || 'secondary';
}
getMessageTypeColor(type) {
const colors = {
'text': 'primary',
'image': 'info',
'audio': 'warning',
'video': 'danger',
'document': 'secondary',
'template': 'success'
};
return colors[type] || 'primary';
}
// Funciones específicas
refreshData() {
this.loadTabData(this.currentTab);
this.showSuccess('Datos actualizados');
}
refreshStats() {
this.loadDashboard();
}
exportUsers() {
window.open(this.apiBaseUrl + 'export_users.php', '_blank');
}
viewConversation(userId) {
// Implementar modal o página de conversación
console.log('Ver conversación del usuario:', userId);
}
replyToUser(phoneNumber) {
document.getElementById('message-recipient').value = phoneNumber;
this.showTab('messages');
}
editUser(userId) {
// Implementar modal de edición de usuario
console.log('Editar usuario:', userId);
}
blockUser(userId) {
if (confirm('¿Estás seguro de bloquear este usuario?')) {
// Implementar bloqueo de usuario
console.log('Bloquear usuario:', userId);
}
}
editMenu(menuId) {
this.currentMenuId = menuId;
// Cargar datos del menú en el formulario
console.log('Editar menú:', menuId);
}
deleteMenu(menuId) {
if (confirm('¿Estás seguro de eliminar este menú?')) {
// Implementar eliminación
console.log('Eliminar menú:', menuId);
}
}
manageMenuOptions(menuId) {
// Mostrar modal de opciones de menú
console.log('Gestionar opciones del menú:', menuId);
}
viewLogDetails(logId, type) {
// Mostrar detalles del log en modal
console.log('Ver detalles del log:', logId, type);
}
clearLogs() {
if (confirm('¿Estás seguro de eliminar todos los logs?')) {
// Implementar limpieza de logs
console.log('Limpiar logs');
}
}
refreshLogs() {
this.loadLogs();
}
}
// Inicializar la aplicación
const app = new WhatsAppBotManager();
// Funciones globales
window.refreshData = () => app.refreshData();
window.exportUsers = () => app.exportUsers();
window.refreshLogs = () => app.refreshLogs();
window.clearLogs = () => app.clearLogs();
+114
View File
@@ -0,0 +1,114 @@
<?php
/**
* Clase Database - Manejo de conexión a base de datos
* Fecha: 13 de noviembre de 2025
*/
class Database {
private static $instance = null;
private $pdo;
private function __construct() {
try {
$dsn = "mysql:host=" . DB_HOST . ";port=" . DB_PORT . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_PERSISTENT => true,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES " . DB_CHARSET . " COLLATE utf8mb4_unicode_ci"
];
$this->pdo = new PDO($dsn, DB_USER, DB_PASS, $options);
} catch (PDOException $e) {
error_log("Database connection failed: " . $e->getMessage());
throw new Exception("Error de conexión a la base de datos");
}
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function getConnection() {
return $this->pdo;
}
public function query($sql, $params = []) {
try {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt;
} catch (PDOException $e) {
error_log("Query failed: " . $e->getMessage() . " SQL: " . $sql);
throw new Exception("Error en la consulta a la base de datos");
}
}
public function fetch($sql, $params = []) {
$stmt = $this->query($sql, $params);
return $stmt->fetch();
}
public function fetchAll($sql, $params = []) {
$stmt = $this->query($sql, $params);
return $stmt->fetchAll();
}
public function insert($table, $data) {
$keys = array_keys($data);
$fields = implode(',', $keys);
$placeholders = ':' . implode(', :', $keys);
$sql = "INSERT INTO {$table} ({$fields}) VALUES ({$placeholders})";
$stmt = $this->query($sql, $data);
return $this->pdo->lastInsertId();
}
public function update($table, $data, $where, $whereParams = []) {
$fields = [];
foreach (array_keys($data) as $key) {
$fields[] = "{$key} = :{$key}";
}
$fieldsStr = implode(', ', $fields);
$sql = "UPDATE {$table} SET {$fieldsStr} WHERE {$where}";
$params = array_merge($data, $whereParams);
return $this->query($sql, $params);
}
public function delete($table, $where, $params = []) {
$sql = "DELETE FROM {$table} WHERE {$where}";
return $this->query($sql, $params);
}
public function beginTransaction() {
return $this->pdo->beginTransaction();
}
public function commit() {
return $this->pdo->commit();
}
public function rollback() {
return $this->pdo->rollback();
}
public function lastInsertId() {
return $this->pdo->lastInsertId();
}
// Prevenir clonación
private function __clone() {}
// Prevenir deserialización
public function __wakeup() {
throw new Exception("Cannot unserialize singleton");
}
}
?>
+211
View File
@@ -0,0 +1,211 @@
<?php
/**
* Configuración del sistema WhatsApp Bot Manager
* Desarrollado por U-Site.app
* Fecha: 13 de noviembre de 2025
*/
// Configuración de la base de datos (compatible con cPanel)
define('DB_HOST', 'localhost');
define('DB_PORT', '3306');
define('DB_NAME', 'whatsapp_bot');
define('DB_USER', 'whatsapp_user');
define('DB_PASS', ''); // Se generará automáticamente en la instalación
define('DB_CHARSET', 'utf8mb4');
// WhatsApp Business API (se configurará después de la instalación)
define('WHATSAPP_TOKEN', 'TU_TOKEN_DE_WHATSAPP_AQUI');
define('WHATSAPP_PHONE_NUMBER_ID', 'TU_PHONE_ID_AQUI');
define('WHATSAPP_API_URL', 'https://graph.facebook.com/v22.0/');
define('WEBHOOK_VERIFY_TOKEN', 'mi_token_secreto_123');
// Configuración general
define('APP_NAME', 'WhatsApp Bot System');
define('APP_VERSION', '1.0.0');
define('APP_URL', 'https://tudominio.com'); // Se auto-detectará
define('TIMEZONE', 'America/Bogota');
// Información del desarrollador
define('DEVELOPER_NAME', 'U-Site.app');
define('DEVELOPER_URL', 'https://u-site.app');
define('DEVELOPER_EMAIL', 'support@u-site.app');
define('DEVELOPER_SUPPORT', 'https://u-site.app/support');
// Configuración de seguridad
define('INSTALLATION_LOCK', '.installation_completed'); // Archivo de bloqueo
define('ADMIN_USERNAME', 'admin'); // Usuario administrador por defecto
define('ADMIN_PASSWORD', ''); // Se configurará en instalación
define('SESSION_TIMEOUT', 1800); // 30 minutos
define('MAX_LOGIN_ATTEMPTS', 3); // Intentos máximos de login
define('LOGIN_LOCKOUT_TIME', 900); // 15 minutos de bloqueo tras fallos
// Configuración de logs
define('ENABLE_LOGGING', true);
define('LOG_LEVEL', 'INFO'); // DEBUG, INFO, WARNING, ERROR
define('LOG_FILE', 'logs/system.log');
// Configuración de archivos
define('UPLOAD_MAX_SIZE', 10485760); // 10MB
define('ALLOWED_FILE_TYPES', ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx']);
// Configurar zona horaria
date_default_timezone_set(TIMEZONE);
// Configurar errores para desarrollo (se desactivará en producción)
if (!file_exists(dirname(__DIR__) . '/' . 'installation_completed')) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
} else {
error_reporting(0);
ini_set('display_errors', 0);
}
ini_set('log_errors', 1);
// Headers de seguridad
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('X-XSS-Protection: 1; mode=block');
// Auto-detectar URL base si no está configurada
if (!defined('AUTO_DETECTED_URL')) {
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://';
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
$path = dirname($_SERVER['SCRIPT_NAME'] ?? '');
define('AUTO_DETECTED_URL', $protocol . $host . $path);
}
// Función para verificar si la instalación está completada
function isInstallationCompleted() {
return file_exists(dirname(__DIR__) . '/.installation_completed');
}
// Función para verificar login
function isUserLoggedIn() {
return isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true;
}
// Función para verificar intentos de login
function checkLoginAttempts($ip) {
$attemptsFile = dirname(__DIR__) . '/.login_attempts.json';
if (!file_exists($attemptsFile)) {
return true;
}
$attempts = json_decode(file_get_contents($attemptsFile), true);
if (!isset($attempts[$ip])) {
return true;
}
$ipData = $attempts[$ip];
if (time() > $ipData['locked_until']) {
unset($attempts[$ip]);
file_put_contents($attemptsFile, json_encode($attempts));
return true;
}
return $ipData['attempts'] < MAX_LOGIN_ATTEMPTS;
}
// Función para registrar intento de login fallido
function recordFailedLogin($ip) {
$attemptsFile = dirname(__DIR__) . '/.login_attempts.json';
$attempts = [];
if (file_exists($attemptsFile)) {
$attempts = json_decode(file_get_contents($attemptsFile), true);
}
if (!isset($attempts[$ip])) {
$attempts[$ip] = ['attempts' => 0, 'locked_until' => 0];
}
$attempts[$ip]['attempts']++;
if ($attempts[$ip]['attempts'] >= MAX_LOGIN_ATTEMPTS) {
$attempts[$ip]['locked_until'] = time() + LOGIN_LOCKOUT_TIME;
}
file_put_contents($attemptsFile, json_encode($attempts));
}
// Función para limpiar intentos de login exitoso
function clearLoginAttempts($ip) {
$attemptsFile = dirname(__DIR__) . '/.login_attempts.json';
if (file_exists($attemptsFile)) {
$attempts = json_decode(file_get_contents($attemptsFile), true);
unset($attempts[$ip]);
file_put_contents($attemptsFile, json_encode($attempts));
}
}
// Autoloader
spl_autoload_register(function ($class) {
$directories = [
dirname(__DIR__) . '/classes/',
dirname(__DIR__) . '/services/',
dirname(__DIR__) . '/controllers/'
];
foreach ($directories as $directory) {
$file = $directory . $class . '.php';
if (file_exists($file)) {
require_once $file;
return;
}
}
});
// Función para obtener configuración desde base de datos
function getConfigFromDB($key, $default = null) {
static $configs = null;
if ($configs === null && isInstallationCompleted()) {
try {
$pdo = new PDO(
"mysql:host=" . DB_HOST . ";port=" . DB_PORT . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET,
DB_USER,
DB_PASS,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
$stmt = $pdo->query("SELECT config_key, config_value FROM system_config");
$configs = [];
while ($row = $stmt->fetch()) {
$configs[$row['config_key']] = $row['config_value'];
}
} catch (PDOException $e) {
error_log("Error loading config: " . $e->getMessage());
$configs = [];
}
}
return $configs[$key] ?? $default;
}
// Inicializar sesión si no está activa
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Verificar timeout de sesión
if (isUserLoggedIn() && isset($_SESSION['last_activity'])) {
if (time() - $_SESSION['last_activity'] > SESSION_TIMEOUT) {
session_destroy();
session_start();
}
}
// Actualizar última actividad
if (isUserLoggedIn()) {
$_SESSION['last_activity'] = time();
}
?>
+179
View File
@@ -0,0 +1,179 @@
-- Base de datos para sistema de WhatsApp Bot
-- Fecha: 13 de noviembre de 2025
-- Tabla de usuarios
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
phone_number VARCHAR(20) UNIQUE NOT NULL,
name VARCHAR(100),
email VARCHAR(100),
status ENUM('active', 'inactive', 'blocked') DEFAULT 'active',
current_menu_id INT NULL,
current_step INT DEFAULT 0,
session_data JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_phone (phone_number),
INDEX idx_status (status)
);
-- Tabla de conversaciones
CREATE TABLE conversations (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
message_id VARCHAR(100),
direction ENUM('incoming', 'outgoing') NOT NULL,
message_type ENUM('text', 'image', 'audio', 'video', 'document', 'template') DEFAULT 'text',
content TEXT,
media_url VARCHAR(500),
status ENUM('sent', 'delivered', 'read', 'failed') DEFAULT 'sent',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_user (user_id),
INDEX idx_created (created_at),
INDEX idx_direction (direction)
);
-- Tabla de menús (sistema parametrizable)
CREATE TABLE 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,
FOREIGN KEY (parent_id) REFERENCES menus(id) ON DELETE CASCADE,
INDEX idx_parent (parent_id),
INDEX idx_active (is_active),
INDEX idx_root (is_root)
);
-- Tabla de opciones de menú
CREATE TABLE menu_options (
id INT AUTO_INCREMENT PRIMARY KEY,
menu_id INT NOT NULL,
option_number INT NOT NULL,
text VARCHAR(200) NOT NULL,
action_type ENUM('menu', 'message', 'api_call', 'end') NOT NULL,
action_value VARCHAR(500),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (menu_id) REFERENCES menus(id) ON DELETE CASCADE,
INDEX idx_menu (menu_id),
INDEX idx_option (option_number),
UNIQUE KEY unique_menu_option (menu_id, option_number)
);
-- Tabla de respuestas automáticas
CREATE TABLE auto_responses (
id INT AUTO_INCREMENT PRIMARY KEY,
trigger_type ENUM('keyword', 'menu_selection', 'welcome') NOT NULL,
trigger_value VARCHAR(200),
response_text TEXT NOT NULL,
response_type ENUM('text', 'template') DEFAULT 'text',
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_trigger (trigger_type),
INDEX idx_active (is_active)
);
-- Tabla de configuración del sistema
CREATE TABLE system_config (
id INT AUTO_INCREMENT PRIMARY KEY,
config_key VARCHAR(100) UNIQUE NOT NULL,
config_value TEXT,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Tabla de webhooks logs
CREATE TABLE webhook_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
request_body TEXT,
response_body TEXT,
status_code INT,
ip_address VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_created (created_at)
);
-- Tabla de plantillas de mensajes
CREATE TABLE message_templates (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
template_name VARCHAR(100) NOT NULL, -- Nombre en WhatsApp Business API
language_code VARCHAR(10) DEFAULT 'es',
category ENUM('marketing', 'utility', 'authentication') DEFAULT 'utility',
status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_status (status)
);
-- Insertar configuraciones iniciales
INSERT INTO system_config (config_key, config_value, description) VALUES
('whatsapp_token', 'EAAWTKu1iZAGABP46c7GZAFvHremu9uvTLOlgZCAa4eKUlaCqszzsQjjLcgnqORzo2EBhB7LZAcAqJ7clpGvNCk3oXKnqo8Fl0K2o7eHYoeaqZBesqjBPRfsd4ZBOdY4kN1YBJnmRznegTD9wtJZByVrRcMXF8YXaAJjLCuIFZCK7QFUpId7i4eVkFvbqUYnUKA91rgPvaeGRNZCLHa5ZC1ZB2Skn0gFNgPTBx7bedAMULYZD', 'Token de acceso de WhatsApp Business API'),
('whatsapp_phone_number_id', '938177732702718', 'ID del número de teléfono de WhatsApp'),
('webhook_verify_token', 'mi_token_secreto_123', 'Token de verificación para webhook'),
('welcome_message', '¡Hola! 👋 Bienvenido a nuestro servicio automatizado. Escribe *menu* para ver las opciones disponibles.', 'Mensaje de bienvenida para nuevos usuarios'),
('business_name', 'Mi Empresa', 'Nombre de la empresa');
-- Insertar menús de ejemplo
INSERT INTO menus (name, title, description, is_root, order_position) VALUES
('main_menu', '🏠 Menú Principal', 'Menú principal del sistema', TRUE, 1),
('services_menu', '💼 Servicios', 'Menú de servicios disponibles', FALSE, 2),
('payments_menu', '💳 Pagos', 'Opciones de pago', FALSE, 3),
('support_menu', '🆘 Soporte', 'Menú de soporte técnico', FALSE, 4);
-- Obtener IDs de menús para las opciones
SET @main_menu_id = (SELECT id FROM menus WHERE name = 'main_menu');
SET @services_menu_id = (SELECT id FROM menus WHERE name = 'services_menu');
SET @payments_menu_id = (SELECT id FROM menus WHERE name = 'payments_menu');
SET @support_menu_id = (SELECT id FROM menus WHERE name = 'support_menu');
-- Actualizar parent_id para menús secundarios
UPDATE menus SET parent_id = @main_menu_id WHERE name IN ('services_menu', 'payments_menu', 'support_menu');
-- Insertar opciones del menú principal
INSERT INTO menu_options (menu_id, option_number, text, action_type, action_value) VALUES
(@main_menu_id, 1, '💼 Ver servicios disponibles', 'menu', 'services_menu'),
(@main_menu_id, 2, '💳 Realizar pago', 'menu', 'payments_menu'),
(@main_menu_id, 3, '🆘 Contactar soporte', 'menu', 'support_menu'),
(@main_menu_id, 0, '❌ Salir', 'end', 'Gracias por usar nuestro servicio. ¡Hasta pronto!');
-- Insertar opciones del menú de servicios
INSERT INTO menu_options (menu_id, option_number, text, action_type, action_value) VALUES
(@services_menu_id, 1, '📋 Consulta de información', 'message', 'Un representante se contactará contigo para brindarte información detallada. Por favor, indica qué información necesitas.'),
(@services_menu_id, 2, '📞 Agendar cita', 'message', 'Para agendar una cita, por favor proporciona tu nombre completo y tu disponibilidad horaria.'),
(@services_menu_id, 3, '💰 Cotizar servicio', 'message', 'Para realizar una cotización, describe detalladamente el servicio que necesitas.'),
(@services_menu_id, 0, '🔙 Volver al menú principal', 'menu', 'main_menu');
-- Insertar opciones del menú de pagos
INSERT INTO menu_options (menu_id, option_number, text, action_type, action_value) VALUES
(@payments_menu_id, 1, '🧾 Consultar factura', 'message', 'Por favor, proporciona tu número de factura o documento de identidad para consultar tu factura pendiente.'),
(@payments_menu_id, 2, '💸 Pagar factura', 'message', 'Para procesar tu pago, necesitamos el número de factura y el método de pago preferido (transferencia, tarjeta, etc).'),
(@payments_menu_id, 3, '📄 Solicitar factura', 'message', 'Indica el período o servicio para el cual necesitas la factura.'),
(@payments_menu_id, 0, '🔙 Volver al menú principal', 'menu', 'main_menu');
-- Insertar opciones del menú de soporte
INSERT INTO menu_options (menu_id, option_number, text, action_type, action_value) VALUES
(@support_menu_id, 1, '🔧 Reporte técnico', 'message', 'Describe detalladamente el problema técnico que estás experimentando. Un técnico se contactará contigo pronto.'),
(@support_menu_id, 2, '❓ Preguntas frecuentes', 'message', 'Puedes consultar nuestras preguntas frecuentes en: www.miempresa.com/faq o describe tu pregunta específica.'),
(@support_menu_id, 3, '👤 Hablar con agente', 'message', 'Te estamos conectando con un agente humano. Por favor, espera un momento...'),
(@support_menu_id, 0, '🔙 Volver al menú principal', 'menu', 'main_menu');
-- Insertar respuestas automáticas
INSERT INTO auto_responses (trigger_type, trigger_value, response_text) VALUES
('keyword', 'menu', 'Aquí tienes nuestro menú principal:'),
('keyword', 'hola', '¡Hola! 👋 Escribe *menu* para ver nuestras opciones.'),
('keyword', 'ayuda', 'Estoy aquí para ayudarte. Escribe *menu* para ver las opciones disponibles.'),
('welcome', '', '¡Bienvenido! 👋 Soy tu asistente virtual. Escribe *menu* para comenzar.');
-- Insertar plantillas de mensaje
INSERT INTO message_templates (name, template_name, language_code, category, status) VALUES
('saludo_inicial', 'hello_world', 'es', 'utility', 'approved'),
('confirmacion_pago', 'payment_confirmation', 'es', 'utility', 'pending');
+269
View File
@@ -0,0 +1,269 @@
<?php
/**
* Instalador de depuración para hosting compartido
* Diagnóstica problemas específicos de conexión y configuración
* Desarrollado por U-Site.app
* Fecha: 13 de noviembre de 2025
*/
error_reporting(E_ALL);
ini_set('display_errors', 1);
$step = $_GET['step'] ?? 'test';
$dbHost = $_POST['db_host'] ?? 'localhost';
$dbPort = $_POST['db_port'] ?? '3306';
$dbName = $_POST['db_name'] ?? '';
$dbUser = $_POST['db_user'] ?? '';
$dbPass = $_POST['db_pass'] ?? '';
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🔍 Diagnóstico de Instalación</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
.console-output {
background: #1e1e1e;
color: #00ff00;
border-radius: 10px;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
max-height: 500px;
overflow-y: auto;
padding: 15px;
}
.success { color: #00ff00; }
.error { color: #ff4444; }
.warning { color: #ffaa00; }
.info { color: #00aaff; }
</style>
</head>
<body class="bg-dark text-light">
<div class="container mt-4">
<div class="row">
<div class="col-12">
<h2><i class="fas fa-bug me-2"></i>Diagnóstico de Instalación WhatsApp Bot</h2>
<p class="text-muted">Herramienta de depuración para identificar problemas</p>
</div>
</div>
<?php if ($step === 'test'): ?>
<!-- Formulario de prueba -->
<form method="POST" action="?step=debug">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Host de BD</label>
<input type="text" class="form-control" name="db_host" value="<?= htmlspecialchars($dbHost) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label">Puerto</label>
<input type="text" class="form-control" name="db_port" value="<?= htmlspecialchars($dbPort) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label">Nombre de BD</label>
<input type="text" class="form-control" name="db_name" value="<?= htmlspecialchars($dbName) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label">Usuario de BD</label>
<input type="text" class="form-control" name="db_user" value="<?= htmlspecialchars($dbUser) ?>" required>
</div>
<div class="col-12">
<label class="form-label">Contraseña de BD</label>
<input type="password" class="form-control" name="db_pass" value="<?= htmlspecialchars($dbPass) ?>" required>
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">
<i class="fas fa-search me-2"></i>Ejecutar Diagnóstico
</button>
</div>
</div>
</form>
<?php elseif ($step === 'debug'): ?>
<!-- Ejecutar diagnóstico -->
<div class="console-output">
<div class="info">WhatsApp Bot - Diagnóstico de Instalación</div>
<div class="info">Fecha: <?= date('Y-m-d H:i:s') ?></div>
<div class="info">═══════════════════════════════════════════</div>
<?php
// Test 1: Verificar extensiones PHP
echo "<div class='info'>📋 Test 1: Verificando extensiones PHP...</div>";
$requiredExtensions = ['pdo', 'pdo_mysql', 'json', 'curl', 'mbstring'];
foreach ($requiredExtensions as $ext) {
if (extension_loaded($ext)) {
echo "<div class='success'>✅ Extensión '$ext': Disponible</div>";
} else {
echo "<div class='error'>❌ Extensión '$ext': NO disponible</div>";
}
}
// Test 2: Verificar archivos del sistema
echo "<div class='info'>📁 Test 2: Verificando archivos del sistema...</div>";
$requiredFiles = [
'config/config.php' => 'Configuración principal',
'classes/Database.php' => 'Clase Database',
'database/schema.sql' => 'Esquema de BD',
'api/webhook.php' => 'Webhook API'
];
foreach ($requiredFiles as $file => $desc) {
if (file_exists($file)) {
$size = filesize($file);
echo "<div class='success'>✅ $desc ($file): Existe ($size bytes)</div>";
} else {
echo "<div class='error'>❌ $desc ($file): NO existe</div>";
}
}
// Test 3: Probar conexión a servidor MySQL
echo "<div class='info'>🔌 Test 3: Probando conexión a servidor MySQL...</div>";
try {
$dsn = "mysql:host={$dbHost};port={$dbPort};charset=utf8mb4";
$pdo_test = new PDO($dsn, $dbUser, $dbPass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 5,
]);
echo "<div class='success'>✅ Conexión al servidor MySQL: EXITOSA</div>";
// Obtener información del servidor
$version = $pdo_test->query("SELECT VERSION()")->fetchColumn();
echo "<div class='success'>✅ Versión MySQL: $version</div>";
} catch (PDOException $e) {
echo "<div class='error'>❌ Error de conexión: " . $e->getMessage() . "</div>";
echo "<div class='warning'>💡 Posibles causas:</div>";
echo "<div class='warning'> - Credenciales incorrectas</div>";
echo "<div class='warning'> - MySQL no está corriendo</div>";
echo "<div class='warning'> - Firewall bloqueando conexión</div>";
}
// Test 4: Verificar acceso a base de datos específica
echo "<div class='info'>💾 Test 4: Verificando acceso a base de datos '$dbName'...</div>";
try {
$dsn = "mysql:host={$dbHost};port={$dbPort};dbname={$dbName};charset=utf8mb4";
$pdo_db = new PDO($dsn, $dbUser, $dbPass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 5,
]);
echo "<div class='success'>✅ Acceso a base de datos '$dbName': EXITOSO</div>";
// Verificar permisos
try {
$pdo_db->exec("CREATE TABLE test_permissions (id INT PRIMARY KEY)");
$pdo_db->exec("DROP TABLE test_permissions");
echo "<div class='success'>✅ Permisos CREATE/DROP: Disponibles</div>";
} catch (PDOException $e) {
echo "<div class='warning'>⚠️ Permisos limitados: " . $e->getMessage() . "</div>";
}
// Listar tablas existentes
$stmt = $pdo_db->query("SHOW TABLES");
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
if (count($tables) > 0) {
echo "<div class='info'>📋 Tablas existentes (" . count($tables) . "):</div>";
foreach ($tables as $table) {
echo "<div class='info'> - $table</div>";
}
} else {
echo "<div class='warning'>⚠️ Base de datos vacía (sin tablas)</div>";
}
} catch (PDOException $e) {
echo "<div class='error'>❌ Error accediendo a BD '$dbName': " . $e->getMessage() . "</div>";
echo "<div class='warning'>💡 Posibles causas:</div>";
echo "<div class='warning'> - Base de datos no existe</div>";
echo "<div class='warning'> - Usuario sin permisos en esta BD</div>";
echo "<div class='warning'> - Nombre de BD incorrecto</div>";
}
// Test 5: Probar carga del archivo config.php
echo "<div class='info'>⚙️ Test 5: Probando carga de configuración...</div>";
try {
if (file_exists('config/config.php')) {
$configContent = file_get_contents('config/config.php');
// Verificar constantes principales
$constants = ['DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS'];
foreach ($constants as $constant) {
if (strpos($configContent, $constant) !== false) {
echo "<div class='success'>✅ Constante '$constant': Definida en config</div>";
} else {
echo "<div class='error'>❌ Constante '$constant': NO encontrada</div>";
}
}
// Intentar incluir config (sin ejecutar)
echo "<div class='success'>✅ Archivo config.php: Legible</div>";
} else {
echo "<div class='error'>❌ Archivo config.php: NO existe</div>";
}
} catch (Exception $e) {
echo "<div class='error'>❌ Error leyendo config: " . $e->getMessage() . "</div>";
}
// Test 6: Verificar schema.sql
echo "<div class='info'>📄 Test 6: Verificando archivo schema.sql...</div>";
try {
if (file_exists('database/schema.sql')) {
$schema = file_get_contents('database/schema.sql');
$statements = explode(';', $schema);
$createTables = 0;
$insertData = 0;
foreach ($statements as $statement) {
if (preg_match('/CREATE TABLE/i', $statement)) $createTables++;
if (preg_match('/INSERT INTO/i', $statement)) $insertData++;
}
echo "<div class='success'>✅ Schema SQL: $createTables tablas, $insertData inserts</div>";
// Verificar tablas principales
$mainTables = ['users', 'conversations', 'menus', 'system_config'];
foreach ($mainTables as $table) {
if (strpos($schema, "CREATE TABLE $table") !== false) {
echo "<div class='success'>✅ Tabla '$table': Definida en schema</div>";
} else {
echo "<div class='warning'>⚠️ Tabla '$table': NO definida en schema</div>";
}
}
} else {
echo "<div class='error'>❌ Archivo schema.sql: NO existe</div>";
}
} catch (Exception $e) {
echo "<div class='error'>❌ Error leyendo schema: " . $e->getMessage() . "</div>";
}
echo "<div class='info'>═══════════════════════════════════════════</div>";
echo "<div class='info'>Diagnóstico completado: " . date('H:i:s') . "</div>";
?>
</div>
<div class="mt-4">
<h5>🚀 Acciones sugeridas:</h5>
<div class="row g-3">
<div class="col-md-4">
<a href="install_manual.php" class="btn btn-success w-100">
<i class="fas fa-tools me-2"></i>Instalar Manual
</a>
</div>
<div class="col-md-4">
<a href="?step=test" class="btn btn-secondary w-100">
<i class="fas fa-redo me-2"></i>Ejecutar Otro Test
</a>
</div>
<div class="col-md-4">
<a href="test.php" class="btn btn-info w-100">
<i class="fas fa-vial me-2"></i>Test Sistema
</a>
</div>
</div>
</div>
<?php endif; ?>
</div>
</body>
</html>
+261
View File
@@ -0,0 +1,261 @@
#!/bin/bash
# Script de despliegue automático para WhatsApp Bot Manager
# Uso: ./deploy.sh [dominio] [usuario_db] [password_db]
echo "🚀 Iniciando despliegue de WhatsApp Bot Manager..."
# Validar parámetros
if [ $# -ne 3 ]; then
echo "❌ Error: Faltan parámetros"
echo "Uso: $0 <dominio> <usuario_db> <password_db>"
echo "Ejemplo: $0 midominio.com db_user mi_password"
exit 1
fi
DOMAIN=$1
DB_USER=$2
DB_PASS=$3
PROJECT_PATH="/var/www/html/bot"
echo "📋 Configuración:"
echo " Dominio: $DOMAIN"
echo " Usuario DB: $DB_USER"
echo " Ruta: $PROJECT_PATH"
# Crear directorio del proyecto
echo "📁 Creando directorios..."
sudo mkdir -p $PROJECT_PATH
cd $PROJECT_PATH
# Configurar archivo de configuración
echo "⚙️ Configurando config.php..."
cat > config/config.php << EOF
<?php
/**
* Configuración de producción - WhatsApp Bot Manager
* Generado automáticamente: $(date)
*/
// Base de datos de producción
define('DB_HOST', 'localhost');
define('DB_NAME', 'whatsapp');
define('DB_USER', '$DB_USER');
define('DB_PASS', '$DB_PASS');
define('DB_CHARSET', 'utf8mb4');
// WhatsApp Business API (ACTUALIZAR CON TUS TOKENS)
define('WHATSAPP_TOKEN', 'TU_TOKEN_AQUI');
define('WHATSAPP_PHONE_NUMBER_ID', 'TU_PHONE_ID_AQUI');
define('WHATSAPP_API_URL', 'https://graph.facebook.com/v22.0/');
define('WEBHOOK_VERIFY_TOKEN', 'mi_token_secreto_123');
// Configuración de producción
define('APP_NAME', 'WhatsApp Bot Manager');
define('APP_VERSION', '1.0.0');
define('APP_URL', 'https://$DOMAIN/bot');
define('TIMEZONE', 'America/Bogota');
// Seguridad en producción
error_reporting(0);
ini_set('display_errors', 0);
ini_set('log_errors', 1);
// Headers de seguridad
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');
// Autoloader
spl_autoload_register(function (\$class) {
\$directories = [
dirname(__DIR__) . '/classes/',
dirname(__DIR__) . '/services/'
];
foreach (\$directories as \$directory) {
\$file = \$directory . \$class . '.php';
if (file_exists(\$file)) {
require_once \$file;
return;
}
}
});
?>
EOF
# Crear .htaccess de seguridad
echo "🔒 Configurando seguridad..."
cat > .htaccess << EOF
RewriteEngine On
# Forzar HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Proteger archivos sensibles
<Files "config.php">
Require all denied
</Files>
<Files "*.sql">
Require all denied
</Files>
<Files "*.log">
Require all denied
</Files>
# Cache headers
<IfModule mod_expires.c>
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"
</IfModule>
# Compresión GZIP
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/json
</IfModule>
EOF
# Configurar permisos
echo "📝 Configurando permisos..."
sudo chown -R www-data:www-data $PROJECT_PATH
sudo find $PROJECT_PATH -type f -exec chmod 644 {} \;
sudo find $PROJECT_PATH -type d -exec chmod 755 {} \;
sudo chmod 600 $PROJECT_PATH/config/config.php
# Configurar Virtual Host de Apache
echo "🌐 Configurando Apache Virtual Host..."
sudo tee /etc/apache2/sites-available/whatsapp-bot.conf > /dev/null << EOF
<VirtualHost *:80>
ServerName $DOMAIN
ServerAlias www.$DOMAIN
DocumentRoot $PROJECT_PATH
<Directory $PROJECT_PATH>
AllowOverride All
Require all granted
DirectoryIndex index.php
</Directory>
ErrorLog \${APACHE_LOG_DIR}/whatsapp-bot_error.log
CustomLog \${APACHE_LOG_DIR}/whatsapp-bot_access.log combined
# Redirigir HTTP a HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</VirtualHost>
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName $DOMAIN
ServerAlias www.$DOMAIN
DocumentRoot $PROJECT_PATH
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/$DOMAIN/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/$DOMAIN/privkey.pem
<Directory $PROJECT_PATH>
AllowOverride All
Require all granted
DirectoryIndex index.php
</Directory>
ErrorLog \${APACHE_LOG_DIR}/whatsapp-bot_ssl_error.log
CustomLog \${APACHE_LOG_DIR}/whatsapp-bot_ssl_access.log combined
</VirtualHost>
</IfModule>
EOF
# Habilitar sitio
echo "✅ Habilitando sitio..."
sudo a2ensite whatsapp-bot.conf
sudo a2enmod rewrite ssl
sudo systemctl reload apache2
# Configurar SSL con Let's Encrypt
echo "🔐 Configurando SSL..."
if command -v certbot &> /dev/null; then
sudo certbot --apache -d $DOMAIN -d www.$DOMAIN --non-interactive --agree-tos --email admin@$DOMAIN
else
echo "⚠️ Certbot no instalado. Instala SSL manualmente:"
echo " sudo apt install certbot python3-certbot-apache"
echo " sudo certbot --apache -d $DOMAIN"
fi
# Crear base de datos
echo "🗄️ Configurando base de datos..."
sudo mysql -e "CREATE DATABASE IF NOT EXISTS whatsapp DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
sudo mysql -e "CREATE USER IF NOT EXISTS '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';"
sudo mysql -e "GRANT ALL PRIVILEGES ON whatsapp.* TO '$DB_USER'@'localhost';"
sudo mysql -e "FLUSH PRIVILEGES;"
# Crear script de backup
echo "💾 Configurando backup automático..."
sudo tee /usr/local/bin/backup-whatsapp.sh > /dev/null << EOF
#!/bin/bash
DATE=\$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/var/backups/whatsapp"
mkdir -p \$BACKUP_DIR
# Backup de base de datos
mysqldump -u $DB_USER -p'$DB_PASS' whatsapp > \$BACKUP_DIR/whatsapp_\$DATE.sql
# Backup de archivos
tar -czf \$BACKUP_DIR/files_\$DATE.tar.gz $PROJECT_PATH
# Eliminar backups antiguos (más de 7 días)
find \$BACKUP_DIR -name "*.sql" -mtime +7 -delete
find \$BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete
echo "Backup completado: \$DATE"
EOF
sudo chmod +x /usr/local/bin/backup-whatsapp.sh
# Configurar cron para backup diario
echo "⏰ Configurando backup automático diario..."
(crontab -l 2>/dev/null; echo "0 2 * * * /usr/local/bin/backup-whatsapp.sh") | crontab -
# Mostrar información final
echo ""
echo "🎉 ¡Despliegue completado!"
echo ""
echo "📋 Información importante:"
echo " 🌐 Sitio web: https://$DOMAIN/migrador"
echo " ⚙️ Instalador: https://$DOMAIN/migrador/install.php"
echo " 🧪 Pruebas: https://$DOMAIN/migrador/test.php"
echo " 📱 Webhook: https://$DOMAIN/migrador/api/webhook.php"
echo ""
echo "🔧 Próximos pasos:"
echo " 1. Edita config/config.php con tus tokens de WhatsApp"
echo " 2. Ejecuta el instalador: https://$DOMAIN/migrador/install.php"
echo " 3. Configura webhook en Facebook Developers"
echo " 4. Prueba el sistema: https://$DOMAIN/migrador/test.php"
echo ""
echo "📁 Rutas importantes:"
echo " Config: $PROJECT_PATH/config/config.php"
echo " Logs: /var/log/apache2/whatsapp-bot_*.log"
echo " Backups: /var/backups/whatsapp/"
echo ""
echo "🔒 Webhook URL para Facebook:"
echo " https://$DOMAIN/migrador/api/webhook.php"
echo " Token de verificación: mi_token_secreto_123"
echo ""
# Verificar estado de servicios
echo "📊 Estado de servicios:"
sudo systemctl is-active apache2 && echo " ✅ Apache: Funcionando" || echo " ❌ Apache: Error"
sudo systemctl is-active mysql && echo " ✅ MySQL: Funcionando" || echo " ❌ MySQL: Error"
echo ""
echo "¡Tu WhatsApp Bot Manager está listo! 🚀"
+549
View File
@@ -0,0 +1,549 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WhatsApp Bot Manager</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="./assets/css/styles.css" rel="stylesheet">
</head>
<body>
<!-- Sidebar -->
<nav class="sidebar">
<div class="sidebar-header">
<h4><i class="fas fa-robot"></i> WhatsApp Bot</h4>
</div>
<ul class="sidebar-menu">
<li><a href="#dashboard" class="nav-link active" data-tab="dashboard"><i class="fas fa-tachometer-alt"></i> Dashboard</a></li>
<li><a href="#conversations" class="nav-link" data-tab="conversations"><i class="fas fa-comments"></i> Conversaciones</a></li>
<li><a href="#users" class="nav-link" data-tab="users"><i class="fas fa-users"></i> Usuarios</a></li>
<li><a href="#menus" class="nav-link" data-tab="menus"><i class="fas fa-list"></i> Menús</a></li>
<li><a href="#messages" class="nav-link" data-tab="messages"><i class="fas fa-paper-plane"></i> Enviar Mensaje</a></li>
<li><a href="#templates" class="nav-link" data-tab="templates"><i class="fas fa-file-text"></i> Plantillas</a></li>
<li><a href="#autoresponses" class="nav-link" data-tab="autoresponses"><i class="fas fa-robot"></i> Respuestas Auto</a></li>
<li><a href="#settings" class="nav-link" data-tab="settings"><i class="fas fa-cog"></i> Configuración</a></li>
<li><a href="#logs" class="nav-link" data-tab="logs"><i class="fas fa-file-alt"></i> Logs</a></li>
</ul>
</nav>
<!-- Main Content -->
<main class="main-content">
<!-- Header -->
<header class="content-header">
<h1><i class="fas fa-robot"></i> Sistema de Gestión WhatsApp Bot</h1>
<div class="header-actions">
<span class="status-indicator online">
<i class="fas fa-circle"></i> En línea
</span>
<button class="btn btn-primary" onclick="refreshData()">
<i class="fas fa-sync-alt"></i> Actualizar
</button>
</div>
</header>
<!-- Dashboard Tab -->
<div id="dashboard" class="tab-content active">
<div class="row mb-4">
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stat bg-primary text-white">
<div class="card-body">
<div class="row align-items-center">
<div class="col">
<h6 class="card-stat-title">Total Usuarios</h6>
<h3 class="card-stat-number" id="total-users">-</h3>
</div>
<div class="col-auto">
<i class="fas fa-users fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stat bg-success text-white">
<div class="card-body">
<div class="row align-items-center">
<div class="col">
<h6 class="card-stat-title">Mensajes Hoy</h6>
<h3 class="card-stat-number" id="messages-today">-</h3>
</div>
<div class="col-auto">
<i class="fas fa-comment fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stat bg-info text-white">
<div class="card-body">
<div class="row align-items-center">
<div class="col">
<h6 class="card-stat-title">Usuarios Activos</h6>
<h3 class="card-stat-number" id="active-users">-</h3>
</div>
<div class="col-auto">
<i class="fas fa-user-check fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stat bg-warning text-white">
<div class="card-body">
<div class="row align-items-center">
<div class="col">
<h6 class="card-stat-title">Total Mensajes</h6>
<h3 class="card-stat-number" id="total-messages">-</h3>
</div>
<div class="col-auto">
<i class="fas fa-envelope fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Recent Activity -->
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-chart-line"></i> Actividad Reciente</h5>
</div>
<div class="card-body">
<canvas id="messagesChart" height="100"></canvas>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-clock"></i> Últimos Mensajes</h5>
</div>
<div class="card-body">
<div id="recent-messages" class="list-group">
<!-- Contenido dinámico -->
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Conversations Tab -->
<div id="conversations" class="tab-content">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-comments"></i> Conversaciones</h5>
<div>
<input type="text" class="form-control" placeholder="Buscar conversaciones..." id="search-conversations">
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Usuario</th>
<th>Último Mensaje</th>
<th>Tipo</th>
<th>Estado</th>
<th>Fecha</th>
<th>Acciones</th>
</tr>
</thead>
<tbody id="conversations-table">
<!-- Contenido dinámico -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Users Tab -->
<div id="users" class="tab-content">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-users"></i> Gestión de Usuarios</h5>
<button class="btn btn-primary" onclick="exportUsers()">
<i class="fas fa-download"></i> Exportar
</button>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Teléfono</th>
<th>Nombre</th>
<th>Estado</th>
<th>Menú Actual</th>
<th>Registro</th>
<th>Acciones</th>
</tr>
</thead>
<tbody id="users-table">
<!-- Contenido dinámico -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Menus Tab -->
<div id="menus" class="tab-content">
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-list"></i> Menús Configurados</h5>
<button class="btn btn-primary" onclick="showCreateMenuModal()">
<i class="fas fa-plus"></i> Nuevo Menú
</button>
</div>
<div class="card-body">
<div id="menus-tree">
<!-- Árbol de menús -->
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-edit"></i> Editor de Menú</h5>
</div>
<div class="card-body">
<form id="menu-form">
<div class="mb-3">
<label class="form-label">Nombre del Menú</label>
<input type="text" class="form-control" id="menu-name" required>
</div>
<div class="mb-3">
<label class="form-label">Título</label>
<input type="text" class="form-control" id="menu-title" required>
</div>
<div class="mb-3">
<label class="form-label">Descripción</label>
<textarea class="form-control" id="menu-description" rows="3"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Menú Padre</label>
<select class="form-control" id="menu-parent">
<option value="">Sin padre (Menú raíz)</option>
</select>
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="menu-active" checked>
<label class="form-check-label">Activo</label>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Guardar Menú
</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Messages Tab -->
<div id="messages" class="tab-content">
<div class="row">
<div class="col-lg-6">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-paper-plane"></i> Enviar Mensaje</h5>
</div>
<div class="card-body">
<form id="send-message-form">
<div class="mb-3">
<label class="form-label">Destinatario</label>
<select class="form-control" id="message-recipient" required>
<option value="">Seleccionar usuario...</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Tipo de Mensaje</label>
<select class="form-control" id="message-type" required>
<option value="text">Texto</option>
<option value="template">Plantilla</option>
</select>
</div>
<div class="mb-3" id="message-text-group">
<label class="form-label">Mensaje</label>
<textarea class="form-control" id="message-text" rows="4" placeholder="Escribe tu mensaje aquí..."></textarea>
</div>
<div class="mb-3" id="template-group" style="display: none;">
<label class="form-label">Plantilla</label>
<select class="form-control" id="message-template">
<option value="">Seleccionar plantilla...</option>
</select>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-paper-plane"></i> Enviar Mensaje
</button>
</form>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-broadcast-tower"></i> Mensaje Masivo</h5>
</div>
<div class="card-body">
<form id="broadcast-form">
<div class="mb-3">
<label class="form-label">Filtro de Usuarios</label>
<select class="form-control" id="broadcast-filter">
<option value="all">Todos los usuarios</option>
<option value="active">Solo usuarios activos</option>
<option value="recent">Usuarios recientes (30 días)</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Mensaje</label>
<textarea class="form-control" id="broadcast-message" rows="4" placeholder="Mensaje para envío masivo..."></textarea>
</div>
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle"></i>
<strong>Cuidado:</strong> Este mensaje se enviará a múltiples usuarios.
</div>
<button type="submit" class="btn btn-warning">
<i class="fas fa-broadcast-tower"></i> Envío Masivo
</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Templates Tab -->
<div id="templates" class="tab-content">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-file-text"></i> Plantillas de Mensaje</h5>
<button class="btn btn-primary" onclick="showCreateTemplateModal()">
<i class="fas fa-plus"></i> Nueva Plantilla
</button>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Nombre</th>
<th>Plantilla WhatsApp</th>
<th>Idioma</th>
<th>Categoría</th>
<th>Estado</th>
<th>Acciones</th>
</tr>
</thead>
<tbody id="templates-table">
<!-- Contenido dinámico -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Auto Responses Tab -->
<div id="autoresponses" class="tab-content">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-robot"></i> Respuestas Automáticas</h5>
<button class="btn btn-primary" onclick="showCreateAutoResponseModal()">
<i class="fas fa-plus"></i> Nueva Respuesta
</button>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Tipo</th>
<th>Palabra Clave</th>
<th>Respuesta</th>
<th>Estado</th>
<th>Acciones</th>
</tr>
</thead>
<tbody id="autoresponses-table">
<!-- Contenido dinámico -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Settings Tab -->
<div id="settings" class="tab-content">
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-cog"></i> Configuración del Sistema</h5>
</div>
<div class="card-body">
<form id="settings-form">
<div class="mb-4">
<h6 class="text-muted">WhatsApp Business API</h6>
<div class="mb-3">
<label class="form-label">Token de Acceso</label>
<input type="password" class="form-control" id="whatsapp-token" placeholder="Token de WhatsApp Business API">
</div>
<div class="mb-3">
<label class="form-label">Phone Number ID</label>
<input type="text" class="form-control" id="phone-number-id" placeholder="ID del número de teléfono">
</div>
<div class="mb-3">
<label class="form-label">Webhook Verify Token</label>
<input type="text" class="form-control" id="webhook-token" placeholder="Token de verificación del webhook">
</div>
</div>
<div class="mb-4">
<h6 class="text-muted">Configuración General</h6>
<div class="mb-3">
<label class="form-label">Nombre de la Empresa</label>
<input type="text" class="form-control" id="business-name" placeholder="Nombre de tu empresa">
</div>
<div class="mb-3">
<label class="form-label">Mensaje de Bienvenida</label>
<textarea class="form-control" id="welcome-message" rows="3" placeholder="Mensaje que se envía a usuarios nuevos"></textarea>
</div>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Guardar Configuración
</button>
</form>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-info-circle"></i> Información del Sistema</h5>
</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-6">Versión:</dt>
<dd class="col-sm-6">1.0.0</dd>
<dt class="col-sm-6">PHP:</dt>
<dd class="col-sm-6">8.0+</dd>
<dt class="col-sm-6">Base de Datos:</dt>
<dd class="col-sm-6">MySQL</dd>
<dt class="col-sm-6">Webhook URL:</dt>
<dd class="col-sm-6">
<code>http://localhost/migrador/api/webhook.php</code>
</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
<!-- Logs Tab -->
<div id="logs" class="tab-content">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-file-alt"></i> Logs del Sistema</h5>
<div>
<button class="btn btn-outline-primary" onclick="refreshLogs()">
<i class="fas fa-sync-alt"></i> Actualizar
</button>
<button class="btn btn-outline-danger" onclick="clearLogs()">
<i class="fas fa-trash"></i> Limpiar
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Fecha</th>
<th>IP</th>
<th>Estado</th>
<th>Request</th>
<th>Response</th>
</tr>
</thead>
<tbody id="logs-table">
<!-- Contenido dinámico -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>
<!-- Modals -->
<!-- Create Menu Option Modal -->
<div class="modal fade" id="createMenuOptionModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Agregar Opción de Menú</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="menu-option-form">
<div class="mb-3">
<label class="form-label">Número de Opción</label>
<input type="number" class="form-control" id="option-number" min="0" required>
</div>
<div class="mb-3">
<label class="form-label">Texto de la Opción</label>
<input type="text" class="form-control" id="option-text" required>
</div>
<div class="mb-3">
<label class="form-label">Tipo de Acción</label>
<select class="form-control" id="action-type" required>
<option value="menu">Navegar a otro menú</option>
<option value="message">Enviar mensaje</option>
<option value="api_call">Llamada API</option>
<option value="end">Finalizar conversación</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Valor de Acción</label>
<textarea class="form-control" id="action-value" rows="3" placeholder="Contenido del mensaje o nombre del menú..."></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="button" class="btn btn-primary" onclick="saveMenuOption()">Guardar Opción</button>
</div>
</div>
</div>
</div>
<!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="./assets/js/app.js"></script>
</body>
</html>
+530
View File
@@ -0,0 +1,530 @@
<?php
/**
* Página principal del sistema WhatsApp Bot
* Fecha: 13 de noviembre de 2025
*/
// Verificar si la instalación está completada
$installationLockFile = '.installation_completed';
if (!file_exists($installationLockFile)) {
header('Location: instalacion.html');
exit();
}
require_once 'config/config.php';
// Verificar conexión a la base de datos
try {
$db = Database::getInstance();
$dbStatus = 'Conectado';
} catch (Exception $e) {
$dbStatus = 'Error: ' . $e->getMessage();
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WhatsApp Bot Manager</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="./assets/css/styles.css" rel="stylesheet">
</head>
<body>
<!-- Sidebar -->
<nav class="sidebar">
<div class="sidebar-header">
<h4><i class="fas fa-robot"></i> WhatsApp Bot</h4>
</div>
<ul class="sidebar-menu">
<li><a href="#dashboard" class="nav-link active" data-tab="dashboard"><i class="fas fa-tachometer-alt"></i> Dashboard</a></li>
<li><a href="#conversations" class="nav-link" data-tab="conversations"><i class="fas fa-comments"></i> Conversaciones</a></li>
<li><a href="#users" class="nav-link" data-tab="users"><i class="fas fa-users"></i> Usuarios</a></li>
<li><a href="#menus" class="nav-link" data-tab="menus"><i class="fas fa-list"></i> Menús</a></li>
<li><a href="#messages" class="nav-link" data-tab="messages"><i class="fas fa-paper-plane"></i> Enviar Mensaje</a></li>
<li><a href="#templates" class="nav-link" data-tab="templates"><i class="fas fa-file-text"></i> Plantillas</a></li>
<li><a href="#autoresponses" class="nav-link" data-tab="autoresponses"><i class="fas fa-robot"></i> Respuestas Auto</a></li>
<li><a href="#settings" class="nav-link" data-tab="settings"><i class="fas fa-cog"></i> Configuración</a></li>
<li><a href="#logs" class="nav-link" data-tab="logs"><i class="fas fa-file-alt"></i> Logs</a></li>
</ul>
</nav>
<!-- Main Content -->
<main class="main-content">
<!-- Header -->
<header class="content-header">
<h1><i class="fas fa-robot"></i> Sistema de Gestión WhatsApp Bot</h1>
<div class="header-actions">
<span class="status-indicator online">
<i class="fas fa-circle"></i> En línea
</span>
<button class="btn btn-primary" onclick="refreshData()">
<i class="fas fa-sync-alt"></i> Actualizar
</button>
</div>
</header>
<!-- Dashboard Tab -->
<div id="dashboard" class="tab-content active">
<div class="row mb-4">
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stat bg-primary text-white">
<div class="card-body">
<div class="row align-items-center">
<div class="col">
<h6 class="card-stat-title">Total Usuarios</h6>
<h3 class="card-stat-number" id="total-users">-</h3>
</div>
<div class="col-auto">
<i class="fas fa-users fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stat bg-success text-white">
<div class="card-body">
<div class="row align-items-center">
<div class="col">
<h6 class="card-stat-title">Mensajes Hoy</h6>
<h3 class="card-stat-number" id="messages-today">-</h3>
</div>
<div class="col-auto">
<i class="fas fa-comment fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stat bg-info text-white">
<div class="card-body">
<div class="row align-items-center">
<div class="col">
<h6 class="card-stat-title">Usuarios Activos</h6>
<h3 class="card-stat-number" id="active-users">-</h3>
</div>
<div class="col-auto">
<i class="fas fa-user-check fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stat bg-warning text-white">
<div class="card-body">
<div class="row align-items-center">
<div class="col">
<h6 class="card-stat-title">Total Mensajes</h6>
<h3 class="card-stat-number" id="total-messages">-</h3>
</div>
<div class="col-auto">
<i class="fas fa-envelope fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Recent Activity -->
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-chart-line"></i> Actividad Reciente</h5>
</div>
<div class="card-body">
<canvas id="messagesChart" height="100"></canvas>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-clock"></i> Últimos Mensajes</h5>
</div>
<div class="card-body">
<div id="recent-messages" class="list-group">
<!-- Contenido dinámico -->
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Conversations Tab -->
<div id="conversations" class="tab-content">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-comments"></i> Conversaciones</h5>
<div>
<input type="text" class="form-control" placeholder="Buscar conversaciones..." id="search-conversations">
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Usuario</th>
<th>Último Mensaje</th>
<th>Tipo</th>
<th>Estado</th>
<th>Fecha</th>
<th>Acciones</th>
</tr>
</thead>
<tbody id="conversations-table">
<!-- Contenido dinámico -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Users Tab -->
<div id="users" class="tab-content">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-users"></i> Gestión de Usuarios</h5>
<button class="btn btn-primary" onclick="exportUsers()">
<i class="fas fa-download"></i> Exportar
</button>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Teléfono</th>
<th>Nombre</th>
<th>Estado</th>
<th>Menú Actual</th>
<th>Registro</th>
<th>Acciones</th>
</tr>
</thead>
<tbody id="users-table">
<!-- Contenido dinámico -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Menus Tab -->
<div id="menus" class="tab-content">
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-list"></i> Menús Configurados</h5>
<button class="btn btn-primary" onclick="showCreateMenuModal()">
<i class="fas fa-plus"></i> Nuevo Menú
</button>
</div>
<div class="card-body">
<div id="menus-tree">
<!-- Árbol de menús -->
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-edit"></i> Editor de Menú</h5>
</div>
<div class="card-body">
<form id="menu-form">
<div class="mb-3">
<label class="form-label">Nombre del Menú</label>
<input type="text" class="form-control" id="menu-name" required>
</div>
<div class="mb-3">
<label class="form-label">Título</label>
<input type="text" class="form-control" id="menu-title" required>
</div>
<div class="mb-3">
<label class="form-label">Descripción</label>
<textarea class="form-control" id="menu-description" rows="3"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Menú Padre</label>
<select class="form-control" id="menu-parent">
<option value="">Sin padre (Menú raíz)</option>
</select>
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="menu-active" checked>
<label class="form-check-label">Activo</label>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Guardar Menú
</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Messages Tab -->
<div id="messages" class="tab-content">
<div class="row">
<div class="col-lg-6">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-paper-plane"></i> Enviar Mensaje</h5>
</div>
<div class="card-body">
<form id="send-message-form">
<div class="mb-3">
<label class="form-label">Destinatario</label>
<select class="form-control" id="message-recipient" required>
<option value="">Seleccionar usuario...</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Tipo de Mensaje</label>
<select class="form-control" id="message-type" required>
<option value="text">Texto</option>
<option value="template">Plantilla</option>
</select>
</div>
<div class="mb-3" id="message-text-group">
<label class="form-label">Mensaje</label>
<textarea class="form-control" id="message-text" rows="4" placeholder="Escribe tu mensaje aquí..."></textarea>
</div>
<div class="mb-3" id="template-group" style="display: none;">
<label class="form-label">Plantilla</label>
<select class="form-control" id="message-template">
<option value="">Seleccionar plantilla...</option>
</select>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-paper-plane"></i> Enviar Mensaje
</button>
</form>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-broadcast-tower"></i> Mensaje Masivo</h5>
</div>
<div class="card-body">
<form id="broadcast-form">
<div class="mb-3">
<label class="form-label">Filtro de Usuarios</label>
<select class="form-control" id="broadcast-filter">
<option value="all">Todos los usuarios</option>
<option value="active">Solo usuarios activos</option>
<option value="recent">Usuarios recientes (30 días)</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Mensaje</label>
<textarea class="form-control" id="broadcast-message" rows="4" placeholder="Mensaje para envío masivo..."></textarea>
</div>
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle"></i>
<strong>Cuidado:</strong> Este mensaje se enviará a múltiples usuarios.
</div>
<button type="submit" class="btn btn-warning">
<i class="fas fa-broadcast-tower"></i> Envío Masivo
</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Templates Tab -->
<div id="templates" class="tab-content">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-file-text"></i> Plantillas de Mensaje</h5>
<button class="btn btn-primary" onclick="showCreateTemplateModal()">
<i class="fas fa-plus"></i> Nueva Plantilla
</button>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Nombre</th>
<th>Plantilla WhatsApp</th>
<th>Idioma</th>
<th>Categoría</th>
<th>Estado</th>
<th>Acciones</th>
</tr>
</thead>
<tbody id="templates-table">
<!-- Contenido dinámico -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Auto Responses Tab -->
<div id="autoresponses" class="tab-content">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-robot"></i> Respuestas Automáticas</h5>
<button class="btn btn-primary" onclick="showCreateAutoResponseModal()">
<i class="fas fa-plus"></i> Nueva Respuesta
</button>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Tipo</th>
<th>Palabra Clave</th>
<th>Respuesta</th>
<th>Estado</th>
<th>Acciones</th>
</tr>
</thead>
<tbody id="autoresponses-table">
<!-- Contenido dinámico -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Settings Tab -->
<div id="settings" class="tab-content">
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-cog"></i> Configuración del Sistema</h5>
</div>
<div class="card-body">
<form id="settings-form">
<div class="mb-4">
<h6 class="text-muted">WhatsApp Business API</h6>
<div class="mb-3">
<label class="form-label">Token de Acceso</label>
<input type="password" class="form-control" id="whatsapp-token" placeholder="Token de WhatsApp Business API">
</div>
<div class="mb-3">
<label class="form-label">Phone Number ID</label>
<input type="text" class="form-control" id="phone-number-id" placeholder="ID del número de teléfono">
</div>
<div class="mb-3">
<label class="form-label">Webhook Verify Token</label>
<input type="text" class="form-control" id="webhook-token" placeholder="Token de verificación del webhook">
</div>
</div>
<div class="mb-4">
<h6 class="text-muted">Configuración General</h6>
<div class="mb-3">
<label class="form-label">Nombre de la Empresa</label>
<input type="text" class="form-control" id="business-name" placeholder="Nombre de tu empresa">
</div>
<div class="mb-3">
<label class="form-label">Mensaje de Bienvenida</label>
<textarea class="form-control" id="welcome-message" rows="3" placeholder="Mensaje que se envía a usuarios nuevos"></textarea>
</div>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Guardar Configuración
</button>
</form>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<h5><i class="fas fa-info-circle"></i> Información del Sistema</h5>
</div>
<div class="card-body">
<dl class="row">
<dt class="col-sm-6">Versión:</dt>
<dd class="col-sm-6">1.0.0</dd>
<dt class="col-sm-6">PHP:</dt>
<dd class="col-sm-6"><?php echo PHP_VERSION; ?></dd>
<dt class="col-sm-6">Base de Datos:</dt>
<dd class="col-sm-6"><?php echo $dbStatus; ?></dd>
<dt class="col-sm-6">Webhook URL:</dt>
<dd class="col-sm-6">
<code><?php echo APP_URL; ?>/api/webhook.php</code>
</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
<!-- Logs Tab -->
<div id="logs" class="tab-content">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-file-alt"></i> Logs del Sistema</h5>
<div>
<button class="btn btn-outline-primary" onclick="refreshLogs()">
<i class="fas fa-sync-alt"></i> Actualizar
</button>
<button class="btn btn-outline-danger" onclick="clearLogs()">
<i class="fas fa-trash"></i> Limpiar
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Fecha</th>
<th>IP</th>
<th>Estado</th>
<th>Request</th>
<th>Response</th>
</tr>
</thead>
<tbody id="logs-table">
<!-- Contenido dinámico -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>
<!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="./assets/js/app.js"></script>
</body>
</html>
+406
View File
@@ -0,0 +1,406 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🚀 WhatsApp Bot Manager - Inicio Rápido</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
:root {
--primary-color: #25d366;
--secondary-color: #075e54;
--accent-color: #128c7e;
--bg-gradient: linear-gradient(135deg, #25d366 0%, #075e54 100%);
}
body {
background: var(--bg-gradient);
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.main-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.icon-xl {
font-size: 4rem;
background: var(--bg-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.step-card {
background: linear-gradient(145deg, #ffffff, #f8f9fa);
border: none;
border-radius: 15px;
transition: all 0.3s ease;
height: 100%;
}
.step-card:hover {
transform: translateY(-10px);
box-shadow: 0 15px 30px rgba(37, 211, 102, 0.2);
}
.step-number {
width: 60px;
height: 60px;
background: var(--bg-gradient);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 1.5rem;
margin: 0 auto 1rem auto;
}
.btn-custom {
background: var(--bg-gradient);
border: none;
color: white;
padding: 15px 30px;
border-radius: 10px;
font-weight: 600;
text-decoration: none;
display: inline-block;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(37, 211, 102, 0.3);
}
.btn-custom:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(37, 211, 102, 0.4);
color: white;
}
.feature-icon {
font-size: 2.5rem;
color: var(--primary-color);
margin-bottom: 1rem;
}
.badge-custom {
background: var(--bg-gradient);
color: white;
padding: 5px 15px;
border-radius: 20px;
font-size: 0.9rem;
margin: 2px;
}
.status-indicator {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
}
.status-success { background-color: #28a745; }
.status-warning { background-color: #ffc107; }
.status-danger { background-color: #dc3545; }
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.tech-stack {
background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 15px;
padding: 2rem;
margin: 2rem 0;
}
.api-endpoint {
background: #f8f9fa;
border-left: 4px solid var(--primary-color);
padding: 1rem;
margin: 0.5rem 0;
border-radius: 0 10px 10px 0;
font-family: 'Courier New', monospace;
}
</style>
</head>
<body>
<div class="container my-5">
<!-- Header -->
<div class="row justify-content-center mb-5">
<div class="col-lg-10">
<div class="main-card p-5 text-center">
<div class="mb-4">
<i class="fab fa-whatsapp icon-xl pulse"></i>
</div>
<h1 class="display-4 fw-bold mb-3">WhatsApp Bot Manager</h1>
<p class="lead text-muted mb-4">Sistema completo de chatbot para WhatsApp con panel de administración</p>
<div class="d-flex flex-wrap justify-content-center gap-2">
<span class="badge-custom">PHP 8.0+</span>
<span class="badge-custom">MySQL</span>
<span class="badge-custom">WhatsApp Business API</span>
<span class="badge-custom">Bootstrap 5</span>
<span class="badge-custom">Chart.js</span>
</div>
</div>
</div>
</div>
<!-- Quick Start Steps -->
<div class="row justify-content-center mb-5">
<div class="col-lg-10">
<h2 class="text-center mb-5 text-white fw-bold">
<i class="fas fa-rocket me-2"></i>Guía de Inicio Rápido
</h2>
<div class="row g-4">
<!-- Paso 1 -->
<div class="col-lg-3 col-md-6">
<div class="card step-card p-4 text-center">
<div class="step-number">1</div>
<h5 class="fw-bold">Instalar BD</h5>
<p class="text-muted small mb-3">Ejecuta el instalador automático</p>
<a href="install.php" class="btn btn-custom btn-sm">
<i class="fas fa-database me-2"></i>Instalar
</a>
</div>
</div>
<!-- Paso 2 -->
<div class="col-lg-3 col-md-6">
<div class="card step-card p-4 text-center">
<div class="step-number">2</div>
<h5 class="fw-bold">Verificar</h5>
<p class="text-muted small mb-3">Prueba todos los componentes</p>
<a href="test.php" class="btn btn-custom btn-sm">
<i class="fas fa-check-circle me-2"></i>Probar
</a>
</div>
</div>
<!-- Paso 3 -->
<div class="col-lg-3 col-md-6">
<div class="card step-card p-4 text-center">
<div class="step-number">3</div>
<h5 class="fw-bold">Configurar</h5>
<p class="text-muted small mb-3">Ajusta WhatsApp y webhooks</p>
<a href="index.php" class="btn btn-custom btn-sm">
<i class="fas fa-cog me-2"></i>Panel
</a>
</div>
</div>
<!-- Paso 4 -->
<div class="col-lg-3 col-md-6">
<div class="card step-card p-4 text-center">
<div class="step-number">4</div>
<h5 class="fw-bold">¡Listo!</h5>
<p class="text-muted small mb-3">Inicia conversaciones</p>
<a href="#features" class="btn btn-custom btn-sm">
<i class="fas fa-comments me-2"></i>Ver Más
</a>
</div>
</div>
</div>
</div>
</div>
<!-- Features -->
<div class="row justify-content-center mb-5" id="features">
<div class="col-lg-10">
<div class="main-card p-5">
<h2 class="text-center mb-5 fw-bold">
<i class="fas fa-star me-2"></i>Características Principales
</h2>
<div class="row g-4">
<div class="col-lg-4 col-md-6 text-center">
<i class="fas fa-robot feature-icon"></i>
<h5 class="fw-bold">Bot Inteligente</h5>
<p class="text-muted">Sistema de menús navegables por números con respuestas automáticas</p>
</div>
<div class="col-lg-4 col-md-6 text-center">
<i class="fas fa-chart-line feature-icon"></i>
<h5 class="fw-bold">Dashboard Analytics</h5>
<p class="text-muted">Estadísticas en tiempo real con gráficos interactivos</p>
</div>
<div class="col-lg-4 col-md-6 text-center">
<i class="fas fa-users feature-icon"></i>
<h5 class="fw-bold">Gestión de Usuarios</h5>
<p class="text-muted">Administra contactos y segmenta audiencias fácilmente</p>
</div>
<div class="col-lg-4 col-md-6 text-center">
<i class="fas fa-broadcast-tower feature-icon"></i>
<h5 class="fw-bold">Mensajes Masivos</h5>
<p class="text-muted">Envía campañas a múltiples usuarios con filtros avanzados</p>
</div>
<div class="col-lg-4 col-md-6 text-center">
<i class="fas fa-code feature-icon"></i>
<h5 class="fw-bold">API Completa</h5>
<p class="text-muted">Integra con otros sistemas usando nuestras APIs REST</p>
</div>
<div class="col-lg-4 col-md-6 text-center">
<i class="fas fa-mobile-alt feature-icon"></i>
<h5 class="fw-bold">Responsive</h5>
<p class="text-muted">Interfaz adaptable para desktop, tablet y móvil</p>
</div>
</div>
</div>
</div>
</div>
<!-- Tech Stack -->
<div class="row justify-content-center mb-5">
<div class="col-lg-10">
<div class="tech-stack text-center">
<h3 class="fw-bold mb-3">
<i class="fas fa-layer-group me-2"></i>Stack Tecnológico
</h3>
<div class="row g-4">
<div class="col-lg-3 col-md-6">
<i class="fab fa-php fa-3x mb-2"></i>
<h6>PHP 8.0+</h6>
<small>Backend robusto y moderno</small>
</div>
<div class="col-lg-3 col-md-6">
<i class="fas fa-database fa-3x mb-2"></i>
<h6>MySQL</h6>
<small>Base de datos relacional</small>
</div>
<div class="col-lg-3 col-md-6">
<i class="fab fa-bootstrap fa-3x mb-2"></i>
<h6>Bootstrap 5</h6>
<small>UI components modernos</small>
</div>
<div class="col-lg-3 col-md-6">
<i class="fas fa-chart-bar fa-3x mb-2"></i>
<h6>Chart.js</h6>
<small>Visualización de datos</small>
</div>
</div>
</div>
</div>
</div>
<!-- API Endpoints -->
<div class="row justify-content-center mb-5">
<div class="col-lg-10">
<div class="main-card p-5">
<h2 class="text-center mb-4 fw-bold">
<i class="fas fa-plug me-2"></i>APIs Disponibles
</h2>
<div class="row">
<div class="col-lg-6">
<h5 class="fw-bold text-success mb-3">
<i class="fas fa-arrow-down me-2"></i>Entrada
</h5>
<div class="api-endpoint">
<strong>POST</strong> /bot/api/webhook.php<br>
<small>Recibe mensajes de WhatsApp</small>
</div>
</div>
<div class="col-lg-6">
<h5 class="fw-bold text-primary mb-3">
<i class="fas fa-arrow-up me-2"></i>Salida
</h5>
<div class="api-endpoint">
<strong>POST</strong> /bot/api/send_message.php<br>
<small>Envía mensajes individuales</small>
</div>
<div class="api-endpoint">
<strong>POST</strong> /bot/api/send_broadcast.php<br>
<small>Envía mensajes masivos</small>
</div>
<div class="api-endpoint">
<strong>GET</strong> /bot/api/get_stats.php<br>
<small>Obtiene estadísticas</small>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- System Status -->
<div class="row justify-content-center mb-5">
<div class="col-lg-10">
<div class="main-card p-4">
<h5 class="fw-bold mb-3">
<i class="fas fa-heartbeat me-2"></i>Estado del Sistema
</h5>
<div class="row g-3">
<div class="col-lg-3 col-md-6">
<span class="status-indicator status-success"></span>
<small>PHP Configurado</small>
</div>
<div class="col-lg-3 col-md-6">
<span class="status-indicator status-warning"></span>
<small>BD Pendiente</small>
</div>
<div class="col-lg-3 col-md-6">
<span class="status-indicator status-warning"></span>
<small>WhatsApp Config</small>
</div>
<div class="col-lg-3 col-md-6">
<span class="status-indicator status-warning"></span>
<small>Webhook URL</small>
</div>
</div>
</div>
</div>
</div>
<!-- Footer -->
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="main-card p-4 text-center">
<p class="text-muted mb-0">
<i class="fas fa-heart text-danger me-2"></i>
Desarrollado con amor para gestionar WhatsApp como un profesional
</p>
<small class="text-muted">
Versión 1.0 | Documentación completa en
<a href="INSTALACION.md" class="text-decoration-none">INSTALACION.md</a>
</small>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Animación suave para los enlaces
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
});
});
});
// Efecto de carga para las tarjetas
const cards = document.querySelectorAll('.step-card');
cards.forEach((card, index) => {
card.style.opacity = '0';
card.style.transform = 'translateY(20px)';
setTimeout(() => {
card.style.transition = 'all 0.6s ease';
card.style.opacity = '1';
card.style.transform = 'translateY(0)';
}, index * 200);
});
</script>
</body>
</html>
+282
View File
@@ -0,0 +1,282 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🚀 WhatsApp Bot - Instalación</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
:root {
--primary-color: #25d366;
--secondary-color: #075e54;
--bg-gradient: linear-gradient(135deg, #25d366 0%, #075e54 100%);
}
body {
background: var(--bg-gradient);
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.install-card {
background: rgba(255,255,255,0.95);
border-radius: 20px;
backdrop-filter: blur(10px);
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
}
.option-card {
border: 2px solid transparent;
border-radius: 15px;
transition: all 0.3s ease;
height: 100%;
}
.option-card:hover {
border-color: var(--primary-color);
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
}
.btn-install {
background: var(--bg-gradient);
border: none;
color: white;
padding: 12px 25px;
border-radius: 10px;
font-weight: 600;
width: 100%;
}
.btn-install:hover {
color: white;
transform: translateY(-2px);
}
.hosting-badge {
position: absolute;
top: -10px;
right: 15px;
font-size: 0.8rem;
padding: 5px 10px;
}
</style>
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="install-card p-5">
<div class="text-center mb-5">
<i class="fab fa-whatsapp text-success" style="font-size: 4rem;"></i>
<h1 class="mt-3 fw-bold">WhatsApp Bot Manager</h1>
<p class="text-muted">Elige el método de instalación según tu tipo de hosting</p>
<div class="mt-3">
<small class="text-muted">
Desarrollado por <strong><a href="https://u-site.app" target="_blank" class="text-decoration-none">U-Site.app</a></strong>
</small>
</div>
</div>
<div class="row g-3">
<!-- Instalación Automática -->
<div class="col-lg-2">
<div class="option-card bg-light p-3 position-relative">
<span class="hosting-badge badge bg-success">Local</span>
<div class="text-center mb-2">
<i class="fas fa-magic text-primary" style="font-size: 2rem;"></i>
</div>
<h6 class="text-center mb-2">🪄 Automática</h6>
<div class="mb-3">
<ul class="list-unstyled small">
<li>• Laragon/XAMPP</li>
<li>• VPS con root</li>
</ul>
</div>
<a href="install.php" class="btn btn-install btn-sm w-100">
<i class="fas fa-play me-1"></i>Usar
</a>
</div>
</div>
<!-- Instalación Manual -->
<div class="col-lg-2">
<div class="option-card bg-light p-3 position-relative">
<span class="hosting-badge badge bg-warning">Hosting</span>
<div class="text-center mb-2">
<i class="fas fa-tools text-warning" style="font-size: 2rem;"></i>
</div>
<h6 class="text-center mb-2">🛠️ Manual</h6>
<div class="mb-3">
<ul class="list-unstyled small">
<li>• HestiaCP</li>
<li>• Schema archivo</li>
</ul>
</div>
<a href="install_manual.php" class="btn btn-install bg-warning text-dark btn-sm w-100">
<i class="fas fa-wrench me-1"></i>Usar
</a>
</div>
</div>
<!-- Instalación Integrada -->
<div class="col-lg-2">
<div class="option-card bg-light p-3 position-relative">
<span class="hosting-badge badge bg-primary">Robusto</span>
<div class="text-center mb-2">
<i class="fas fa-cogs text-primary" style="font-size: 2rem;"></i>
</div>
<h6 class="text-center mb-2">🔧 Integrada</h6>
<div class="mb-3">
<ul class="list-unstyled small">
<li>• Schema interno</li>
<li>• Muy confiable</li>
</ul>
</div>
<a href="install_integrated.php" class="btn btn-install btn-sm w-100">
<i class="fas fa-cog me-1"></i>Usar
</a>
</div>
</div>
<!-- Instalación Ultra Básica -->
<div class="col-lg-3">
<div class="option-card bg-light p-3 position-relative border-danger">
<span class="hosting-badge badge bg-danger">SOLUCIÓN</span>
<div class="text-center mb-2">
<i class="fas fa-medkit text-danger" style="font-size: 2rem;"></i>
</div>
<h6 class="text-center mb-2">⚡ Ultra Básico</h6>
<div class="mb-3">
<ul class="list-unstyled small">
<li><strong>• Para errores "Table doesn't exist"</strong></li>
<li>• Solo tablas esenciales</li>
<li>• Paso a paso verificado</li>
</ul>
</div>
<a href="install_ultra_basic.php" class="btn btn-danger btn-sm w-100">
<i class="fas fa-medkit me-1"></i>SOLUCIONAR
</a>
</div>
</div>
<!-- Instalación Simplificada -->
<div class="col-lg-3">
<div class="option-card bg-light p-3 position-relative">
<span class="hosting-badge badge bg-info">Rápido</span>
<div class="text-center mb-2">
<i class="fas fa-rocket text-info" style="font-size: 2rem;"></i>
</div>
<h6 class="text-center mb-2">📦 Simple</h6>
<div class="mb-3">
<ul class="list-unstyled small">
<li>• Solo lo esencial</li>
<li>• Proceso rápido</li>
<li>• Principiantes</li>
</ul>
</div>
<a href="install_simple.php" class="btn btn-install btn-sm w-100" style="background: #17a2b8;">
<i class="fas fa-bolt me-1"></i>Usar
</a>
</div>
</div>
</div>
<div class="mt-5">
<div class="alert alert-danger">
<h5><i class="fas fa-exclamation-triangle me-2"></i>🚨 ¿Tienes errores de "Table doesn't exist"?</h5>
<div class="row">
<div class="col-md-8">
<p><strong>Si ves errores como:</strong></p>
<ul class="mb-2">
<li>❌ "Table 'usite_whatsapp_bot.menus' doesn't exist"</li>
<li>❌ "Table 'usite_whatsapp_bot.users' doesn't exist"</li>
<li>❌ "0 tablas, 0 elementos procesados"</li>
</ul>
</div>
<div class="col-md-4 text-center">
<a href="install_ultra_basic.php" class="btn btn-danger btn-lg">
<i class="fas fa-medkit me-2"></i>SOLUCIÓN AQUÍ
</a>
<br><small class="text-muted">Instalador Ultra Básico</small>
</div>
</div>
</div>
<div class="alert alert-warning">
<h5><i class="fas fa-question-circle me-2"></i>¿Cuál elegir según tu situación?</h5>
<div class="row">
<div class="col-md-6">
<h6>🔥 <strong>Si tuviste errores con schema.sql:</strong></h6>
<ul class="mb-2">
<li>❌ "Table doesn't exist"</li>
<li>❌ "0 tablas, 0 elementos"</li>
<li>❌ Errores de parsing SQL</li>
</ul>
<p><strong>👉 USA:</strong> <span class="badge bg-danger">Ultra Básico</span> - Resuelve el problema</p>
</div>
<div class="col-md-6">
<h6>💡 <strong>Recomendaciones generales:</strong></h6>
<ul class="mb-2">
<li><strong>Errores de tablas:</strong> Ultra Básico</li>
<li><strong>Primera vez:</strong> Integrada</li>
<li><strong>HestiaCP:</strong> Ultra Básico o Simple</li>
<li><strong>Desarrollo:</strong> Automática</li>
</ul>
</div>
</div>
</div>
</div>
<div class="row g-3 mt-4">
<div class="col-md-3">
<div class="text-center">
<a href="GUIA_HESTIACP.md" target="_blank" class="btn btn-outline-primary">
<i class="fas fa-book me-2"></i>Guía HestiaCP
</a>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<a href="debug_install.php" class="btn btn-outline-warning">
<i class="fas fa-bug me-2"></i>Diagnóstico
</a>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<a href="INSTALACION.md" target="_blank" class="btn btn-outline-success">
<i class="fas fa-file-alt me-2"></i>Documentación
</a>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<a href="test.php" class="btn btn-outline-info">
<i class="fas fa-vial me-2"></i>Probar Sistema
</a>
</div>
</div>
</div>
<!-- Footer -->
<div class="mt-5 pt-4 border-top text-center">
<small class="text-muted">
WhatsApp Bot Manager v1.0 |
Desarrollado por <strong>U-Site.app</strong> |
<a href="https://u-site.app/support" target="_blank">Soporte técnico</a>
</small>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
+493
View File
@@ -0,0 +1,493 @@
<?php
/**
* Instalador automático del sistema WhatsApp Bot
* Desarrollado por U-Site.app - https://u-site.app
* Genera contraseña aleatoria y configura la base de datos
* Fecha: 13 de noviembre de 2025
*/
// Verificar si la instalación ya está completada
$installationLockFile = '.installation_completed';
if (file_exists($installationLockFile)) {
header('Location: login.php');
exit('La instalación ya está completada. <a href="login.php">Iniciar sesión</a>');
}
// Función para generar contraseña aleatoria segura
function generateRandomPassword($length = 20) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*-_=+';
$password = '';
for ($i = 0; $i < $length; $i++) {
$password .= $chars[random_int(0, strlen($chars) - 1)];
}
return $password;
}
// Configuración de la instalación (compatible con cPanel)
$dbHost = 'localhost';
$dbName = 'whatsapp_bot';
$dbUser = 'whatsapp_user';
$dbPass = generateRandomPassword(20);
$dbRootUser = 'root';
$dbRootPass = ''; // En cPanel puede requerir contraseña
// Generar contraseña para admin
$adminPassword = generateRandomPassword(12);
$installStep = $_GET['step'] ?? 'welcome';
$errors = [];
$success = [];
// Procesar instalación
if ($installStep === 'install' && $_POST) {
try {
// 1. Intentar conectar como root para crear base de datos y usuario
try {
$rootPdo = new PDO("mysql:host=$dbHost;charset=utf8mb4", $dbRootUser, $dbRootPass);
$rootPdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
// Si no se puede conectar como root, es probable que sea hosting compartido
throw new Exception("⚠️ HOSTING COMPARTIDO DETECTADO ⚠️<br><br>" .
"No se pueden crear bases de datos automáticamente en este tipo de hosting.<br><br>" .
"<strong>SOLUCIÓN:</strong><br>" .
"1. Crea manualmente la base de datos y usuario en tu panel de control<br>" .
"2. Usa <a href='install_manual.php' class='btn btn-warning btn-sm'>install_manual.php</a> en su lugar<br>" .
"3. Lee la guía: <a href='GUIA_HESTIACP.md' target='_blank'>GUIA_HESTIACP.md</a><br><br>" .
"Error técnico: " . $e->getMessage());
}
// 2. Crear base de datos
$rootPdo->exec("CREATE DATABASE IF NOT EXISTS `$dbName` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
$success[] = "✅ Base de datos '$dbName' creada correctamente";
// 3. Crear usuario con permisos
$rootPdo->exec("DROP USER IF EXISTS '$dbUser'@'localhost'");
$rootPdo->exec("CREATE USER '$dbUser'@'localhost' IDENTIFIED BY '$dbPass'");
$rootPdo->exec("GRANT ALL PRIVILEGES ON `$dbName`.* TO '$dbUser'@'localhost'");
$rootPdo->exec("FLUSH PRIVILEGES");
$success[] = "✅ Usuario '$dbUser' creado con contraseña aleatoria";
// 4. Conectar con nuevo usuario
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 5. Crear tablas desde schema.sql
$schema = file_get_contents('database/schema.sql');
if (!$schema) {
throw new Exception("No se pudo leer el archivo schema.sql");
}
// Ejecutar comandos SQL uno por uno
$statements = array_filter(array_map('trim', explode(';', $schema)));
$tablesCreated = 0;
foreach ($statements as $statement) {
if (!empty($statement) && !preg_match('/^--/', $statement)) {
$pdo->exec($statement);
if (preg_match('/CREATE TABLE|INSERT INTO/', $statement)) {
$tablesCreated++;
}
}
}
$success[] = "$tablesCreated elementos de base de datos procesados";
// 6. Actualizar archivo de configuración
$configFile = 'config/config.php';
$configContent = file_get_contents($configFile);
$configContent = preg_replace("/define\('DB_HOST',\s*'[^']*'\);/", "define('DB_HOST', '$dbHost');", $configContent);
$configContent = preg_replace("/define\('DB_NAME',\s*'[^']*'\);/", "define('DB_NAME', '$dbName');", $configContent);
$configContent = preg_replace("/define\('DB_USER',\s*'[^']*'\);/", "define('DB_USER', '$dbUser');", $configContent);
$configContent = preg_replace("/define\('DB_PASS',\s*'[^']*'\);/", "define('DB_PASS', '$dbPass');", $configContent);
$configContent = preg_replace("/define\('ADMIN_PASSWORD',\s*'[^']*'\);/", "define('ADMIN_PASSWORD', '" . password_hash($adminPassword, PASSWORD_DEFAULT) . "');", $configContent);
// Auto-detectar URL del servidor
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://';
$host = $_SERVER['HTTP_HOST'];
$path = dirname($_SERVER['SCRIPT_NAME']);
$autoUrl = $protocol . $host . $path;
$configContent = preg_replace("/define\('APP_URL',\s*'[^']*'\);/", "define('APP_URL', '$autoUrl');", $configContent);
file_put_contents($configFile, $configContent);
$success[] = "✅ Archivo config.php actualizado con nuevas credenciales";
// 7. Crear archivo de bloqueo de instalación
$lockContent = "Instalación completada: " . date('Y-m-d H:i:s') . "\n";
$lockContent .= "Desarrollado por: U-Site.app\n";
$lockContent .= "URL: https://u-site.app\n";
file_put_contents($installationLockFile, $lockContent);
$success[] = "✅ Sistema bloqueado contra reinstalaciones";
// 8. Crear archivo de credenciales para referencia
$credentialsFile = "CREDENCIALES_SISTEMA.txt";
$credentialsContent = "=== CREDENCIALES DEL SISTEMA WHATSAPP BOT ===\n";
$credentialsContent .= "Desarrollado por: U-Site.app (https://u-site.app)\n";
$credentialsContent .= "Generado automáticamente: " . date('Y-m-d H:i:s') . "\n\n";
$credentialsContent .= "=== BASE DE DATOS ===\n";
$credentialsContent .= "Host: $dbHost\n";
$credentialsContent .= "Base de datos: $dbName\n";
$credentialsContent .= "Usuario: $dbUser\n";
$credentialsContent .= "Contraseña: $dbPass\n\n";
$credentialsContent .= "=== PANEL ADMINISTRATIVO ===\n";
$credentialsContent .= "Usuario: admin\n";
$credentialsContent .= "Contraseña: $adminPassword\n";
$credentialsContent .= "URL Login: $autoUrl/login.php\n\n";
$credentialsContent .= "⚠️ IMPORTANTE: Guarda estas credenciales en un lugar seguro.\n";
$credentialsContent .= "⚠️ Elimina este archivo después de guardar las credenciales.\n";
$credentialsContent .= "⚠️ El sistema está protegido contra reinstalaciones.\n\n";
$credentialsContent .= "📞 Soporte técnico: support@u-site.app\n";
file_put_contents($credentialsFile, $credentialsContent);
$success[] = "✅ Archivo de credenciales creado: $credentialsFile";
$installStep = 'completed';
} catch (PDOException $e) {
$errors[] = "❌ Error de base de datos: " . $e->getMessage();
} catch (Exception $e) {
$errors[] = "❌ Error general: " . $e->getMessage();
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🚀 Instalador WhatsApp Bot</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
:root {
--primary-color: #25d366;
--secondary-color: #075e54;
--bg-gradient: linear-gradient(135deg, #25d366 0%, #075e54 100%);
}
body {
background: var(--bg-gradient);
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.install-card {
background: rgba(255,255,255,0.95);
border-radius: 20px;
backdrop-filter: blur(10px);
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
}
.btn-install {
background: var(--bg-gradient);
border: none;
color: white;
padding: 15px 30px;
border-radius: 10px;
font-weight: 600;
}
.btn-install:hover {
transform: translateY(-2px);
color: white;
}
.password-display {
background: #f8f9fa;
border: 2px dashed #28a745;
border-radius: 10px;
font-family: 'Courier New', monospace;
font-size: 1.1rem;
font-weight: bold;
color: #155724;
word-break: break-all;
padding: 15px;
}
.step-indicator {
width: 40px;
height: 40px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
margin-right: 15px;
font-weight: bold;
}
.step-active { background: var(--primary-color); color: white; }
.step-completed { background: #28a745; color: white; }
.step-pending { background: #e9ecef; color: #6c757d; }
.console-output {
background: #1e1e1e;
color: #00ff00;
border-radius: 10px;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
max-height: 300px;
overflow-y: auto;
}
.blink {
animation: blink 1s infinite;
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
</style>
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="install-card p-5">
<?php if ($installStep === 'welcome'): ?>
<!-- Pantalla de Bienvenida -->
<div class="text-center mb-4">
<i class="fas fa-database text-primary" style="font-size: 4rem;"></i>
<h1 class="mt-3 fw-bold">Instalador WhatsApp Bot</h1>
<p class="text-muted">Configuración automática con credenciales aleatorias</p>
<div class="mt-3">
<small class="text-muted">
Desarrollado por <strong><a href="https://u-site.app" target="_blank" class="text-decoration-none">U-Site.app</a></strong> |
<a href="https://u-site.app/support" target="_blank" class="text-decoration-none">Soporte técnico</a>
</small>
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-md-4">
<div class="d-flex align-items-center">
<span class="step-indicator step-active">1</span>
<div>
<h6 class="mb-1">Crear BD</h6>
<small class="text-muted">Base de datos: <strong>whatsapp_bot</strong></small>
</div>
</div>
</div>
<div class="col-md-4">
<div class="d-flex align-items-center">
<span class="step-indicator step-pending">2</span>
<div>
<h6 class="mb-1">Crear Usuario</h6>
<small class="text-muted">Usuario: <strong>whatsapp_user</strong></small>
</div>
</div>
</div>
<div class="col-md-4">
<div class="d-flex align-items-center">
<span class="step-indicator step-pending">3</span>
<div>
<h6 class="mb-1">Configurar</h6>
<small class="text-muted">Aplicar configuración</small>
</div>
</div>
</div>
</div>
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
<strong>Configuración automática para cPanel/hosting compartido:</strong>
<ul class="mb-0 mt-2">
<li><strong>Host:</strong> localhost (compatible con cualquier hosting)</li>
<li><strong>Base de datos:</strong> whatsapp_bot (se creará automáticamente)</li>
<li><strong>Usuario DB:</strong> whatsapp_user (se creará automáticamente)</li>
<li><strong>Contraseña DB:</strong> Se generará aleatoriamente (20 caracteres)</li>
<li><strong>Usuario Admin:</strong> admin</li>
<li><strong>Contraseña Admin:</strong> Se generará aleatoriamente (12 caracteres)</li>
</ul>
</div>
<div class="alert alert-success">
<i class="fas fa-shield-alt me-2"></i>
<strong>Características de seguridad:</strong>
<ul class="mb-0 mt-2">
<li>✅ Protección contra reinstalaciones</li>
<li>✅ Sistema de login con bloqueo por intentos</li>
<li>✅ Contraseñas encriptadas</li>
<li>✅ Auto-detección de URL del servidor</li>
<li>✅ Compatible con cualquier hosting cPanel</li>
</ul>
</div>
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Requisitos:</strong> Asegúrate de que MySQL esté funcionando en Laragon
</div>
<div class="alert alert-danger">
<i class="fas fa-server me-2"></i>
<strong>⚠️ ¿Usas hosting compartido? (HestiaCP, cPanel, etc.)</strong><br>
Este instalador automático <strong>NO funcionará</strong> en hosting compartido porque requiere privilegios de administrador.<br><br>
<strong>👉 SOLUCIÓN:</strong>
<a href="install_manual.php" class="btn btn-warning btn-sm me-2">
<i class="fas fa-tools me-1"></i>Usar Instalador Manual
</a>
<a href="GUIA_HESTIACP.md" target="_blank" class="btn btn-info btn-sm">
<i class="fas fa-book me-1"></i>Ver Guía
</a>
</div>
<div class="text-center mb-4">
<div class="row g-3">
<div class="col-md-6">
<div class="password-display">
🔐 CONTRASEÑA BD:<br>
<strong><?= htmlspecialchars($dbPass) ?></strong>
</div>
</div>
<div class="col-md-6">
<div class="password-display">
👤 CONTRASEÑA ADMIN:<br>
<strong><?= htmlspecialchars($adminPassword) ?></strong>
</div>
</div>
</div>
</div>
<div class="text-center">
<a href="?step=install" class="btn btn-install btn-lg">
<i class="fas fa-play me-2"></i>Iniciar Instalación
</a>
</div>
<?php elseif ($installStep === 'install'): ?>
<!-- Pantalla de Instalación -->
<div class="text-center mb-4">
<i class="fas fa-cog fa-spin text-warning" style="font-size: 3rem;"></i>
<h2 class="mt-3">Instalando Sistema...</h2>
<p class="text-muted">Configurando base de datos automáticamente</p>
</div>
<form method="POST" id="installForm">
<div class="console-output p-4 mb-4">
<div>WhatsApp Bot Installer v1.0</div>
<div>Connecting to MySQL server (localhost)...</div>
<div>Generating secure random password...</div>
<div class="mt-3">
<strong style="color: #ffff00;">🔐 CONTRASEÑAS GENERADAS:</strong><br>
<span style="color: #00ffff; font-size: 1.0rem;">DB: <?= htmlspecialchars($dbPass) ?></span><br>
<span style="color: #00ff00; font-size: 1.0rem;">Admin: <?= htmlspecialchars($adminPassword) ?></span>
</div>
<div class="mt-2">Creating database 'whatsapp_bot'...</div>
<div>Creating user 'whatsapp_user'@'localhost'...</div>
<div>Granting privileges...</div>
<div>Installing database schema...</div>
<div>Updating configuration files...</div>
<div class="text-success mt-2">Ready to execute installation <span class="blink">_</span></div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-install btn-lg">
<i class="fas fa-database me-2"></i>Ejecutar Instalación
</button>
</div>
</form>
<script>
// Auto-submit después de mostrar la información
setTimeout(function() {
document.getElementById('installForm').submit();
}, 4000);
</script>
<?php elseif ($installStep === 'completed'): ?>
<!-- Pantalla de Completado -->
<div class="text-center mb-4">
<i class="fas fa-check-circle text-success" style="font-size: 4rem;"></i>
<h1 class="mt-3 text-success fw-bold">¡Instalación Completada!</h1>
<p class="text-muted">Tu sistema WhatsApp Bot está listo para usar</p>
</div>
<!-- Mostrar errores si los hay -->
<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<h5><i class="fas fa-exclamation-triangle me-2"></i>Errores encontrados:</h5>
<?php foreach ($errors as $error): ?>
<div><?= htmlspecialchars($error) ?></div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<!-- Mostrar éxitos -->
<?php if (!empty($success)): ?>
<div class="alert alert-success">
<h5><i class="fas fa-check-circle me-2"></i>Instalación exitosa:</h5>
<?php foreach ($success as $msg): ?>
<div><?= htmlspecialchars($msg) ?></div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<!-- Mostrar credenciales -->
<div class="row g-4 mb-4">
<div class="col-md-6">
<h5><i class="fas fa-database me-2 text-primary"></i>Credenciales de BD</h5>
<div class="bg-light p-3 rounded">
<div><strong>Host:</strong> <?= $dbHost ?></div>
<div><strong>Base de datos:</strong> <?= $dbName ?></div>
<div><strong>Usuario:</strong> <?= $dbUser ?></div>
<div class="mt-2">
<strong>Contraseña:</strong><br>
<div class="password-display mt-1">
<?= htmlspecialchars($dbPass) ?>
</div>
</div>
</div>
<h5 class="mt-3"><i class="fas fa-user-shield me-2 text-success"></i>Credenciales Admin</h5>
<div class="bg-light p-3 rounded">
<div><strong>Usuario:</strong> admin</div>
<div class="mt-2">
<strong>Contraseña:</strong><br>
<div class="password-display mt-1">
<?= htmlspecialchars($adminPassword) ?>
</div>
</div>
<div class="mt-2"><strong>URL Login:</strong><br>
<a href="login.php" target="_blank"><?= AUTO_DETECTED_URL ?? $autoUrl ?? '' ?>/login.php</a>
</div>
</div>
</div>
<div class="col-md-6">
<h5><i class="fas fa-link me-2 text-success"></i>Próximos Pasos</h5>
<div class="d-grid gap-2">
<a href="login.php" class="btn btn-install">
<i class="fas fa-sign-in-alt me-2"></i>Iniciar Sesión
</a>
<a href="test.php" class="btn btn-outline-primary">
<i class="fas fa-check-circle me-2"></i>Probar Sistema
</a>
<a href="server_setup.php" class="btn btn-outline-warning">
<i class="fab fa-whatsapp me-2"></i>Configurar WhatsApp
</a>
</div>
</div>
</div>
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>¡IMPORTANTE!</strong>
<ul class="mb-0 mt-2">
<li>Guarda las credenciales en un lugar seguro</li>
<li>El archivo <code>CREDENCIALES_SISTEMA.txt</code> contiene toda la información</li>
<li>Elimina este archivo después de guardar las credenciales</li>
<li><strong>El sistema está protegido contra reinstalaciones</strong></li>
<li>Configura tu token de WhatsApp después del login</li>
<li>Soporte técnico: <a href="mailto:support@u-site.app">support@u-site.app</a></li>
</ul>
</div>
<?php endif; ?>
<!-- Footer -->
<div class="mt-5 pt-4 border-top text-center">
<small class="text-muted">
WhatsApp Bot Manager v1.0 by <strong>U-Site.app</strong> |
Instalación automática con credenciales seguras |
<a href="https://u-site.app/support" target="_blank">Soporte</a>
</small>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
+438
View File
@@ -0,0 +1,438 @@
<?php
/**
* Instalador con Schema Integrado
* Evita problemas de parsing del archivo schema.sql
* Desarrollado por U-Site.app
* Fecha: 14 de noviembre de 2025
*/
// Verificar si ya está instalado
if (file_exists('.installation_completed')) {
header('Location: login.php');
exit('Sistema ya instalado');
}
$step = $_GET['step'] ?? 'form';
$errors = [];
$success = [];
function generatePassword($length = 12) {
return bin2hex(random_bytes($length / 2));
}
$adminPassword = generatePassword(12);
// Schema SQL integrado (evita problemas de parsing)
$integratedSchema = [
// Crear tablas principales
"CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
phone_number VARCHAR(20) UNIQUE NOT NULL,
name VARCHAR(100),
email VARCHAR(100),
status ENUM('active', 'inactive', 'blocked') DEFAULT 'active',
current_menu_id INT NULL,
current_step INT DEFAULT 0,
session_data JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_phone (phone_number),
INDEX idx_status (status)
)",
"CREATE TABLE IF NOT EXISTS conversations (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
message_id VARCHAR(100),
direction ENUM('incoming', 'outgoing') NOT NULL,
message_type ENUM('text', 'image', 'audio', 'video', 'document', 'template') DEFAULT 'text',
content TEXT,
media_url VARCHAR(500),
status ENUM('sent', 'delivered', 'read', 'failed') DEFAULT 'sent',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_user (user_id),
INDEX idx_created (created_at)
)",
"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,
FOREIGN KEY (parent_id) REFERENCES menus(id) ON DELETE CASCADE,
INDEX idx_parent (parent_id),
INDEX idx_active (is_active)
)",
"CREATE TABLE IF NOT EXISTS menu_options (
id INT AUTO_INCREMENT PRIMARY KEY,
menu_id INT NOT NULL,
option_number INT NOT NULL,
text VARCHAR(200) NOT NULL,
action_type ENUM('menu', 'message', 'api_call', 'end') NOT NULL,
action_value VARCHAR(500),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (menu_id) REFERENCES menus(id) ON DELETE CASCADE,
UNIQUE KEY unique_menu_option (menu_id, option_number)
)",
"CREATE TABLE IF NOT EXISTS system_config (
id INT AUTO_INCREMENT PRIMARY KEY,
config_key VARCHAR(100) UNIQUE NOT NULL,
config_value TEXT,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)",
"CREATE TABLE IF NOT EXISTS auto_responses (
id INT AUTO_INCREMENT PRIMARY KEY,
trigger_type ENUM('keyword', 'menu_selection', 'welcome') NOT NULL,
trigger_value VARCHAR(200),
response_text TEXT NOT NULL,
response_type ENUM('text', 'template') DEFAULT 'text',
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)",
"CREATE TABLE IF NOT EXISTS webhook_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
request_body TEXT,
response_body TEXT,
status_code INT,
ip_address VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_created (created_at)
)"
];
$initialData = [
// Configuración inicial
"INSERT IGNORE INTO system_config (config_key, config_value, description) VALUES
('whatsapp_token', 'TU_TOKEN_AQUI', 'Token de WhatsApp Business API'),
('whatsapp_phone_number_id', 'TU_PHONE_ID_AQUI', 'ID del número de teléfono'),
('webhook_verify_token', 'mi_token_secreto_123', 'Token de verificación del webhook'),
('welcome_message', '¡Hola! 👋 Bienvenido. Escribe *menu* para ver opciones.', 'Mensaje de bienvenida'),
('business_name', 'Mi Empresa', 'Nombre de la empresa'),
('app_installed', '1', 'Marca de instalación completada'),
('install_date', NOW(), 'Fecha de instalación')",
// Menú principal
"INSERT IGNORE INTO menus (id, name, title, description, is_root, order_position) VALUES
(1, 'main_menu', '🏠 Menú Principal', 'Menú principal del sistema', TRUE, 1)",
// Opciones del menú principal
"INSERT IGNORE INTO menu_options (menu_id, option_number, text, action_type, action_value) VALUES
(1, 1, '📋 Información', 'message', 'Gracias por contactarnos. Un representante te atenderá pronto.'),
(1, 2, '📞 Soporte', 'message', 'Para soporte técnico, describe tu consulta y te ayudaremos.'),
(1, 0, '❌ Salir', 'end', 'Gracias por contactarnos. ¡Hasta pronto!')",
// Respuestas automáticas
"INSERT IGNORE INTO auto_responses (trigger_type, trigger_value, response_text) VALUES
('keyword', 'menu', 'Aquí tienes nuestro menú principal:'),
('keyword', 'hola', '¡Hola! 👋 Escribe *menu* para ver opciones.'),
('welcome', '', '¡Bienvenido! 👋 Escribe *menu* para comenzar.')"
];
if ($step === 'install' && $_POST) {
$dbHost = trim($_POST['db_host'] ?? 'localhost');
$dbPort = trim($_POST['db_port'] ?? '3306');
$dbName = trim($_POST['db_name'] ?? '');
$dbUser = trim($_POST['db_user'] ?? '');
$dbPass = $_POST['db_pass'] ?? '';
// Validar
if (empty($dbName)) $errors[] = "Nombre de BD requerido";
if (empty($dbUser)) $errors[] = "Usuario requerido";
if (empty($dbPass)) $errors[] = "Contraseña requerida";
if (empty($errors)) {
try {
// Conectar
$dsn = "mysql:host={$dbHost};port={$dbPort};dbname={$dbName};charset=utf8mb4";
$pdo = new PDO($dsn, $dbUser, $dbPass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
$success[] = "✅ Conexión exitosa a '{$dbName}'";
// Ejecutar schema - PASO 1: Crear solo las tablas
$tablesCreated = 0;
$creationErrors = [];
echo "<script>console.log('Iniciando creación de tablas...');</script>";
foreach ($integratedSchema as $index => $sql) {
try {
echo "<script>console.log('Ejecutando SQL: " . substr($sql, 0, 50) . "...');</script>";
$result = $pdo->exec($sql);
$tablesCreated++;
// Extraer nombre de tabla para log
if (preg_match('/CREATE TABLE IF NOT EXISTS (\w+)/', $sql, $matches)) {
$tableName = $matches[1];
$success[] = "✅ Tabla '{$tableName}' creada/verificada";
// Verificar que la tabla realmente existe
try {
$checkQuery = "DESCRIBE `{$tableName}`";
$pdo->query($checkQuery);
} catch (PDOException $e) {
$creationErrors[] = "❌ Error verificando tabla '{$tableName}': " . $e->getMessage();
}
}
// Pequeña pausa para evitar problemas de timing
usleep(100000); // 0.1 segundos
} catch (PDOException $e) {
$errorMsg = $e->getMessage();
if (strpos($errorMsg, 'already exists') === false) {
$creationErrors[] = "❌ Error en tabla " . ($index + 1) . ": " . $errorMsg;
} else {
$tablesCreated++; // Contar como creada si ya existe
}
}
}
// Mostrar errores de creación si los hay
if (!empty($creationErrors)) {
foreach ($creationErrors as $error) {
$errors[] = $error;
}
}
// Solo continuar si se crearon las tablas principales
$essentialTables = ['users', 'conversations', 'menus', 'system_config'];
$missingTables = [];
foreach ($essentialTables as $table) {
try {
$pdo->query("DESCRIBE `{$table}`");
} catch (PDOException $e) {
$missingTables[] = $table;
}
}
if (!empty($missingTables)) {
$errors[] = "❌ Tablas esenciales no creadas: " . implode(', ', $missingTables);
$errors[] = "️ Intenta ejecutar el SQL manualmente en phpMyAdmin primero";
} else {
$success[] = "✅ Todas las tablas esenciales verificadas";
// PASO 2: Insertar datos solo si las tablas existen
$dataInserted = 0;
foreach ($initialData as $sql) {
try {
$pdo->exec($sql);
$dataInserted++;
} catch (PDOException $e) {
$errorMsg = $e->getMessage();
if (strpos($errorMsg, 'Duplicate entry') === false) {
$errors[] = "⚠️ Error insertando datos: " . $errorMsg;
}
}
}
$success[] = "{$tablesCreated} tablas procesadas, {$dataInserted} conjuntos de datos insertados";
}
// Actualizar config.php
$configFile = 'config/config.php';
if (file_exists($configFile)) {
$config = file_get_contents($configFile);
$config = preg_replace("/define\('DB_HOST',\s*'[^']*'\);/", "define('DB_HOST', '$dbHost');", $config);
$config = preg_replace("/define\('DB_PORT',\s*'[^']*'\);/", "define('DB_PORT', '$dbPort');", $config);
$config = preg_replace("/define\('DB_NAME',\s*'[^']*'\);/", "define('DB_NAME', '$dbName');", $config);
$config = preg_replace("/define\('DB_USER',\s*'[^']*'\);/", "define('DB_USER', '$dbUser');", $config);
$config = preg_replace("/define\('DB_PASS',\s*'[^']*'\);/", "define('DB_PASS', '$dbPass');", $config);
$config = preg_replace("/define\('ADMIN_PASSWORD',\s*'[^']*'\);/", "define('ADMIN_PASSWORD', '" . password_hash($adminPassword, PASSWORD_DEFAULT) . "');", $config);
// Auto-detectar URL
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://';
$host = $_SERVER['HTTP_HOST'];
$path = dirname($_SERVER['SCRIPT_NAME']);
$autoUrl = $protocol . $host . $path;
$config = preg_replace("/define\('APP_URL',\s*'[^']*'\);/", "define('APP_URL', '$autoUrl');", $config);
if (file_put_contents($configFile, $config)) {
$success[] = "✅ Configuración actualizada";
} else {
$errors[] = "❌ No se pudo actualizar config.php";
}
}
// Verificar instalación
if (empty($errors)) {
try {
$userCount = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
$configCount = $pdo->query("SELECT COUNT(*) FROM system_config")->fetchColumn();
$success[] = "✅ Verificación: {$userCount} usuarios, {$configCount} configuraciones";
// Marcar como instalado
file_put_contents('.installation_completed', date('Y-m-d H:i:s'));
// Credenciales
$credentials = "USUARIO: admin\nCONTRASEÑA: {$adminPassword}\nFECHA: " . date('Y-m-d H:i:s');
file_put_contents('CREDENCIALES.txt', $credentials);
$success[] = "✅ Instalación completada exitosamente";
$step = 'completed';
} catch (Exception $e) {
$errors[] = "❌ Error en verificación: " . $e->getMessage();
}
}
} catch (PDOException $e) {
$errors[] = "❌ Error de conexión: " . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🔧 Instalador Integrado</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
body { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; }
.install-card { background: rgba(255,255,255,0.95); border-radius: 15px; }
.btn-primary { background: #667eea; border: none; }
</style>
</head>
<body class="d-flex align-items-center">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="install-card shadow-lg p-4">
<?php if ($step === 'form'): ?>
<div class="text-center mb-4">
<h2><i class="fas fa-cogs me-2"></i>Instalador con Schema Integrado</h2>
<p class="text-muted">Evita problemas de parsing del archivo schema.sql</p>
</div>
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
<strong>Ventajas de este instalador:</strong>
<ul class="mb-0 mt-2">
<li>✅ Schema SQL integrado en el código</li>
<li>✅ No depende de archivos externos</li>
<li>✅ Ejecución paso a paso controlada</li>
<li>✅ Manejo de errores mejorado</li>
</ul>
</div>
<form method="POST" action="?step=install">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label"><i class="fas fa-server me-2"></i>Host</label>
<input type="text" class="form-control" name="db_host" value="localhost" required>
</div>
<div class="col-md-6">
<label class="form-label"><i class="fas fa-plug me-2"></i>Puerto</label>
<input type="text" class="form-control" name="db_port" value="3306" required>
</div>
<div class="col-md-6">
<label class="form-label"><i class="fas fa-database me-2"></i>Base de datos</label>
<input type="text" class="form-control" name="db_name" placeholder="usite_whatsapp_bot" required>
<small class="text-muted">Debe existir previamente</small>
</div>
<div class="col-md-6">
<label class="form-label"><i class="fas fa-user me-2"></i>Usuario</label>
<input type="text" class="form-control" name="db_user" placeholder="usite_usuario" required>
</div>
<div class="col-12">
<label class="form-label"><i class="fas fa-key me-2"></i>Contraseña</label>
<input type="password" class="form-control" name="db_pass" required>
</div>
</div>
<div class="text-center mt-4">
<div class="alert alert-success">
<strong><i class="fas fa-user-shield me-2"></i>Credenciales de Admin:</strong><br>
<strong>Usuario:</strong> admin<br>
<strong>Contraseña:</strong> <code><?= $adminPassword ?></code>
</div>
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-rocket me-2"></i>Instalar con Schema Integrado
</button>
</div>
</form>
<?php elseif ($step === 'install'): ?>
<div class="text-center mb-4">
<h2><i class="fas fa-cog fa-spin me-2"></i>Instalando...</h2>
</div>
<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<h5><i class="fas fa-exclamation-triangle me-2"></i>Errores:</h5>
<?php foreach ($errors as $error): ?>
<div><?= htmlspecialchars($error) ?></div>
<?php endforeach; ?>
<hr>
<a href="?step=form" class="btn btn-outline-danger">Reintentar</a>
</div>
<?php endif; ?>
<?php if (!empty($success)): ?>
<div class="alert alert-success">
<h5><i class="fas fa-check-circle me-2"></i>Progreso:</h5>
<?php foreach ($success as $msg): ?>
<div><?= htmlspecialchars($msg) ?></div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php elseif ($step === 'completed'): ?>
<div class="text-center mb-4">
<h2 class="text-success"><i class="fas fa-check-circle me-2"></i>¡Instalación Exitosa!</h2>
<p class="text-muted">Sistema WhatsApp Bot instalado correctamente</p>
</div>
<div class="row g-4">
<div class="col-md-6">
<h5><i class="fas fa-key me-2 text-success"></i>Credenciales</h5>
<div class="alert alert-success">
<strong>Usuario:</strong> admin<br>
<strong>Contraseña:</strong> <code><?= $adminPassword ?></code><br>
<small>Guardadas en CREDENCIALES.txt</small>
</div>
</div>
<div class="col-md-6">
<h5><i class="fas fa-rocket me-2 text-primary"></i>Acciones</h5>
<div class="d-grid gap-2">
<a href="index.php" class="btn btn-primary">Panel de Control</a>
<a href="test.php" class="btn btn-outline-success">Probar Sistema</a>
<a href="login.php" class="btn btn-outline-info">Iniciar Sesión</a>
</div>
</div>
</div>
<div class="alert alert-info mt-4">
<i class="fas fa-info-circle me-2"></i>
<strong>Próximos pasos:</strong> Configura tu token de WhatsApp en el panel de administración.
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</body>
</html>
+544
View File
@@ -0,0 +1,544 @@
<?php
/**
* Instalador manual para hostings compartidos (HestiaCP, cPanel, etc.)
* Desarrollado por U-Site.app - https://u-site.app
* Para cuando ya creaste la BD y usuario manualmente
* Fecha: 13 de noviembre de 2025
*/
// Verificar si la instalación ya está completada
$installationLockFile = '.installation_completed';
if (file_exists($installationLockFile)) {
header('Location: login.php');
exit('La instalación ya está completada. <a href="login.php">Iniciar sesión</a>');
}
// Función para generar contraseña aleatoria segura
function generateRandomPassword($length = 12) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*-_=+';
$password = '';
for ($i = 0; $i < $length; $i++) {
$password .= $chars[random_int(0, strlen($chars) - 1)];
}
return $password;
}
$installStep = $_GET['step'] ?? 'config';
$errors = [];
$success = [];
// Generar contraseña para admin
$adminPassword = generateRandomPassword(12);
// Procesar instalación manual
if ($installStep === 'install' && $_POST) {
$dbHost = trim($_POST['db_host'] ?? 'localhost');
$dbName = trim($_POST['db_name'] ?? '');
$dbUser = trim($_POST['db_user'] ?? '');
$dbPass = trim($_POST['db_pass'] ?? '');
$dbPort = trim($_POST['db_port'] ?? '3306');
// Validar campos requeridos
if (empty($dbName)) {
$errors[] = "❌ El nombre de la base de datos es obligatorio";
}
if (empty($dbUser)) {
$errors[] = "❌ El usuario de la base de datos es obligatorio";
}
if (empty($dbPass)) {
$errors[] = "❌ La contraseña de la base de datos es obligatoria";
}
if (empty($errors)) {
try {
// 1. Probar conexión con las credenciales proporcionadas
$dsn = "mysql:host={$dbHost};port={$dbPort};charset=utf8mb4";
$testConnection = new PDO($dsn, $dbUser, $dbPass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 5,
]);
$success[] = "✅ Conexión al servidor MySQL exitosa";
// Verificar que la base de datos existe
$dsn = "mysql:host={$dbHost};port={$dbPort};dbname={$dbName};charset=utf8mb4";
$pdo = new PDO($dsn, $dbUser, $dbPass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
$success[] = "✅ Conexión a base de datos '$dbName' exitosa";
// 2. Verificar si las tablas ya existen
$existingTables = [];
$stmt = $pdo->query("SHOW TABLES");
while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
$existingTables[] = $row[0];
}
if (!empty($existingTables)) {
$success[] = "✅ Base de datos contiene " . count($existingTables) . " tablas existentes";
}
// 3. Ejecutar schema.sql para crear/actualizar tablas
$schemaFile = 'database/schema.sql';
if (!file_exists($schemaFile)) {
throw new Exception("No se encontró el archivo schema.sql");
}
$schema = file_get_contents($schemaFile);
if (!$schema) {
throw new Exception("No se pudo leer el archivo schema.sql");
}
// Ejecutar comandos SQL uno por uno
$statements = preg_split('/;\s*$/m', $schema);
$tablesCreated = 0;
$tablesUpdated = 0;
$errors_sql = [];
// Primero ejecutar solo CREATE TABLE
foreach ($statements as $statement) {
$statement = trim($statement);
if (!empty($statement) &&
!preg_match('/^--/', $statement) &&
preg_match('/CREATE TABLE/i', $statement)) {
try {
$pdo->exec($statement);
$tablesCreated++;
// Extraer nombre de tabla para log
if (preg_match('/CREATE TABLE\s+(?:IF NOT EXISTS\s+)?`?(\w+)`?/i', $statement, $matches)) {
$success[] = "✅ Tabla '{$matches[1]}' creada";
}
} catch (PDOException $e) {
$errorMsg = $e->getMessage();
if (strpos($errorMsg, 'already exists') === false) {
$errors_sql[] = "Error creando tabla: " . $errorMsg;
}
}
}
}
// Luego ejecutar INSERT y otras operaciones
foreach ($statements as $statement) {
$statement = trim($statement);
if (!empty($statement) &&
!preg_match('/^--/', $statement) &&
!preg_match('/CREATE TABLE/i', $statement) &&
(preg_match('/INSERT/i', $statement) || preg_match('/UPDATE/i', $statement) || preg_match('/SET/i', $statement))) {
try {
$pdo->exec($statement);
if (preg_match('/INSERT INTO/i', $statement)) {
$tablesUpdated++;
}
} catch (PDOException $e) {
$errorMsg = $e->getMessage();
if (strpos($errorMsg, 'Duplicate entry') === false) {
$errors_sql[] = "Error insertando datos: " . $errorMsg;
}
}
}
}
if (empty($errors_sql)) {
$success[] = "✅ Esquema aplicado: {$tablesCreated} tablas creadas, {$tablesUpdated} registros insertados";
} else {
$success[] = "⚠️ Esquema procesado: {$tablesCreated} tablas, {$tablesUpdated} registros";
foreach ($errors_sql as $sqlError) {
$errors[] = "⚠️ " . $sqlError;
}
}
// 4. Actualizar archivo de configuración
$configFile = 'config/config.php';
$configContent = file_get_contents($configFile);
$configContent = preg_replace("/define\('DB_HOST',\s*'[^']*'\);/", "define('DB_HOST', '$dbHost');", $configContent);
$configContent = preg_replace("/define\('DB_PORT',\s*'[^']*'\);/", "define('DB_PORT', '$dbPort');", $configContent);
$configContent = preg_replace("/define\('DB_NAME',\s*'[^']*'\);/", "define('DB_NAME', '$dbName');", $configContent);
$configContent = preg_replace("/define\('DB_USER',\s*'[^']*'\);/", "define('DB_USER', '$dbUser');", $configContent);
$configContent = preg_replace("/define\('DB_PASS',\s*'[^']*'\);/", "define('DB_PASS', '$dbPass');", $configContent);
$configContent = preg_replace("/define\('ADMIN_PASSWORD',\s*'[^']*'\);/", "define('ADMIN_PASSWORD', '" . password_hash($adminPassword, PASSWORD_DEFAULT) . "');", $configContent);
// Auto-detectar URL del servidor
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://';
$host = $_SERVER['HTTP_HOST'];
$path = dirname($_SERVER['SCRIPT_NAME']);
$autoUrl = $protocol . $host . $path;
$configContent = preg_replace("/define\('APP_URL',\s*'[^']*'\);/", "define('APP_URL', '$autoUrl');", $configContent);
if (file_put_contents($configFile, $configContent)) {
$success[] = "✅ Archivo config.php actualizado";
} else {
$errors[] = "❌ No se pudo actualizar config.php";
}
// 5. Verificar que la configuración funcione
try {
// Probar directamente con PDO usando las nuevas credenciales
$testPdo = new PDO("mysql:host={$dbHost};port={$dbPort};dbname={$dbName};charset=utf8mb4", $dbUser, $dbPass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$testQuery = $testPdo->query("SELECT COUNT(*) as count FROM users");
$userCount = $testQuery->fetch()['count'];
$success[] = "✅ Prueba de conexión exitosa: {$userCount} usuarios registrados";
// Verificar que las tablas principales existan
$mainTables = ['users', 'conversations', 'menus', 'system_config'];
foreach ($mainTables as $table) {
$checkTable = $testPdo->query("SHOW TABLES LIKE '{$table}'");
if ($checkTable->rowCount() > 0) {
$success[] = "✅ Tabla '{$table}' verificada";
} else {
$errors[] = "❌ Tabla '{$table}' no encontrada";
}
}
} catch (Exception $e) {
$errors[] = "❌ Error probando la nueva configuración: " . $e->getMessage();
}
if (empty($errors)) {
// 6. Crear archivo de bloqueo de instalación
$lockContent = "Instalación manual completada: " . date('Y-m-d H:i:s') . "\n";
$lockContent .= "Host: $dbHost\n";
$lockContent .= "Base de datos: $dbName\n";
$lockContent .= "Usuario BD: $dbUser\n";
$lockContent .= "Desarrollado por: U-Site.app\n";
$lockContent .= "URL: https://u-site.app\n";
file_put_contents($installationLockFile, $lockContent);
$success[] = "✅ Sistema bloqueado contra reinstalaciones";
// 7. Crear archivo de credenciales
$credentialsFile = "CREDENCIALES_SISTEMA.txt";
$credentialsContent = "=== CREDENCIALES DEL SISTEMA WHATSAPP BOT ===\n";
$credentialsContent .= "Instalación manual completada\n";
$credentialsContent .= "Desarrollado por: U-Site.app (https://u-site.app)\n";
$credentialsContent .= "Generado automáticamente: " . date('Y-m-d H:i:s') . "\n\n";
$credentialsContent .= "=== BASE DE DATOS ===\n";
$credentialsContent .= "Host: $dbHost\n";
$credentialsContent .= "Puerto: $dbPort\n";
$credentialsContent .= "Base de datos: $dbName\n";
$credentialsContent .= "Usuario: $dbUser\n";
$credentialsContent .= "Contraseña: [LA QUE CONFIGURASTE MANUALMENTE]\n\n";
$credentialsContent .= "=== PANEL ADMINISTRATIVO ===\n";
$credentialsContent .= "Usuario: admin\n";
$credentialsContent .= "Contraseña: $adminPassword\n";
$credentialsContent .= "URL Login: $autoUrl/login.php\n\n";
$credentialsContent .= "⚠️ IMPORTANTE: Guarda estas credenciales en un lugar seguro.\n";
$credentialsContent .= "⚠️ La contraseña de BD es la que TÚ configuraste manualmente.\n";
$credentialsContent .= "⚠️ El sistema está protegido contra reinstalaciones.\n\n";
$credentialsContent .= "📞 Soporte técnico: support@u-site.app\n";
file_put_contents($credentialsFile, $credentialsContent);
$success[] = "✅ Archivo de credenciales creado: $credentialsFile";
$installStep = 'completed';
}
} catch (PDOException $e) {
$errors[] = "❌ Error de base de datos: " . $e->getMessage();
} catch (Exception $e) {
$errors[] = "❌ Error general: " . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🛠️ Instalación Manual - WhatsApp Bot</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
:root {
--primary-color: #25d366;
--secondary-color: #075e54;
--bg-gradient: linear-gradient(135deg, #25d366 0%, #075e54 100%);
}
body {
background: var(--bg-gradient);
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.install-card {
background: rgba(255,255,255,0.95);
border-radius: 20px;
backdrop-filter: blur(10px);
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
}
.btn-install {
background: var(--bg-gradient);
border: none;
color: white;
padding: 15px 30px;
border-radius: 10px;
font-weight: 600;
}
.btn-install:hover {
transform: translateY(-2px);
color: white;
}
.password-display {
background: #f8f9fa;
border: 2px dashed #28a745;
border-radius: 10px;
font-family: 'Courier New', monospace;
font-size: 1.1rem;
font-weight: bold;
color: #155724;
word-break: break-all;
padding: 15px;
}
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.2rem rgba(37, 211, 102, 0.25);
}
.hosting-info {
background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 15px;
padding: 20px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="install-card p-4">
<?php if ($installStep === 'config'): ?>
<!-- Pantalla de Configuración Manual -->
<div class="text-center mb-4">
<i class="fas fa-tools text-warning" style="font-size: 3rem;"></i>
<h1 class="mt-3 fw-bold">Instalación Manual</h1>
<p class="text-muted">Para hostings compartidos (HestiaCP, cPanel, etc.)</p>
</div>
<div class="hosting-info">
<h5><i class="fas fa-server me-2"></i>Configuración para Hosting Compartido</h5>
<p class="mb-2">Este instalador es para cuando ya creaste manualmente:</p>
<div class="row">
<div class="col-md-6">
<ul class="mb-0">
<li>✅ Base de datos MySQL</li>
<li>✅ Usuario de base de datos</li>
</ul>
</div>
<div class="col-md-6">
<ul class="mb-0">
<li>✅ Permisos asignados</li>
<li>✅ Acceso desde tu dominio</li>
</ul>
</div>
</div>
</div>
<div class="alert alert-info">
<h6><i class="fas fa-info-circle me-2"></i>Pasos previos en HestiaCP/cPanel:</h6>
<ol class="mb-0">
<li><strong>Crear base de datos:</strong> Ve a MySQL Databases</li>
<li><strong>Crear usuario:</strong> Asigna un usuario a la BD</li>
<li><strong>Asignar permisos:</strong> Da ALL PRIVILEGES al usuario</li>
<li><strong>Anotar credenciales:</strong> Host, nombre BD, usuario y contraseña</li>
</ol>
</div>
<form action="?step=install" method="POST">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label"><i class="fas fa-server me-2"></i>Host de BD</label>
<input type="text" class="form-control" name="db_host" value="localhost" required>
<small class="text-muted">Generalmente 'localhost' en hosting compartido</small>
</div>
<div class="col-md-6">
<label class="form-label"><i class="fas fa-plug me-2"></i>Puerto</label>
<input type="text" class="form-control" name="db_port" value="3306">
<small class="text-muted">Puerto estándar de MySQL</small>
</div>
<div class="col-md-6">
<label class="form-label"><i class="fas fa-database me-2"></i>Nombre de BD <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="db_name" placeholder="mi_basedatos" required>
<small class="text-muted">Nombre exacto de la base de datos creada</small>
</div>
<div class="col-md-6">
<label class="form-label"><i class="fas fa-user me-2"></i>Usuario de BD <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="db_user" placeholder="mi_usuario" required>
<small class="text-muted">Usuario asignado a la base de datos</small>
</div>
<div class="col-12">
<label class="form-label"><i class="fas fa-key me-2"></i>Contraseña de BD <span class="text-danger">*</span></label>
<input type="password" class="form-control" name="db_pass" required>
<small class="text-muted">Contraseña del usuario de base de datos</small>
</div>
</div>
<div class="mt-4">
<div class="row">
<div class="col-md-6">
<h6><i class="fas fa-user-shield me-2 text-success"></i>Credenciales Admin</h6>
<div class="alert alert-success">
<strong>Usuario:</strong> admin<br>
<strong>Contraseña:</strong> <span class="password-display d-inline p-2"><?= htmlspecialchars($adminPassword) ?></span>
</div>
</div>
<div class="col-md-6">
<h6><i class="fas fa-shield-alt me-2 text-primary"></i>Que incluye</h6>
<ul class="list-unstyled">
<li>✅ Configuración automática</li>
<li>✅ Creación de tablas</li>
<li>✅ Datos iniciales</li>
<li>✅ Protección contra reinstalación</li>
</ul>
</div>
</div>
</div>
<div class="text-center mt-4">
<button type="submit" class="btn btn-install btn-lg">
<i class="fas fa-cog me-2"></i>Configurar Sistema
</button>
</div>
</form>
<?php elseif ($installStep === 'install'): ?>
<!-- Pantalla de Procesamiento -->
<div class="text-center mb-4">
<i class="fas fa-cog fa-spin text-warning" style="font-size: 3rem;"></i>
<h2 class="mt-3">Configurando Sistema...</h2>
<p class="text-muted">Aplicando configuración con tus credenciales</p>
</div>
<!-- Mostrar errores -->
<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<h5><i class="fas fa-exclamation-triangle me-2"></i>Errores encontrados:</h5>
<?php foreach ($errors as $error): ?>
<div><?= htmlspecialchars($error) ?></div>
<?php endforeach; ?>
<hr>
<a href="?step=config" class="btn btn-outline-danger">
<i class="fas fa-arrow-left me-2"></i>Volver a intentar
</a>
</div>
<?php endif; ?>
<!-- Mostrar éxitos -->
<?php if (!empty($success)): ?>
<div class="alert alert-success">
<h5><i class="fas fa-check-circle me-2"></i>Progreso de instalación:</h5>
<?php foreach ($success as $msg): ?>
<div><?= htmlspecialchars($msg) ?></div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if (!empty($errors) && empty($success)): ?>
<!-- Solo mostrar si hay errores sin éxitos -->
<?php elseif ($installStep !== 'completed'): ?>
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Procesando...</span>
</div>
<p class="mt-2">Procesando configuración...</p>
</div>
<?php endif; ?>
<?php elseif ($installStep === 'completed'): ?>
<!-- Pantalla de Completado -->
<div class="text-center mb-4">
<i class="fas fa-check-circle text-success" style="font-size: 4rem;"></i>
<h1 class="mt-3 text-success fw-bold">¡Instalación Completada!</h1>
<p class="text-muted">Tu sistema WhatsApp Bot está configurado y listo</p>
</div>
<!-- Mostrar errores si los hay -->
<?php if (!empty($errors)): ?>
<div class="alert alert-warning">
<h5><i class="fas fa-exclamation-triangle me-2"></i>Advertencias:</h5>
<?php foreach ($errors as $error): ?>
<div><?= htmlspecialchars($error) ?></div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<!-- Mostrar éxitos -->
<?php if (!empty($success)): ?>
<div class="alert alert-success">
<h5><i class="fas fa-check-circle me-2"></i>Instalación exitosa:</h5>
<?php foreach ($success as $msg): ?>
<div><?= htmlspecialchars($msg) ?></div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="row g-4 mb-4">
<div class="col-md-6">
<h5><i class="fas fa-user-shield me-2 text-success"></i>Acceso al Sistema</h5>
<div class="bg-light p-3 rounded">
<div><strong>Usuario:</strong> admin</div>
<div class="mt-2">
<strong>Contraseña:</strong><br>
<div class="password-display mt-1">
<?= htmlspecialchars($adminPassword) ?>
</div>
</div>
<div class="mt-3">
<a href="login.php" class="btn btn-install btn-sm">
<i class="fas fa-sign-in-alt me-2"></i>Iniciar Sesión
</a>
</div>
</div>
</div>
<div class="col-md-6">
<h5><i class="fas fa-link me-2 text-primary"></i>Próximos Pasos</h5>
<div class="d-grid gap-2">
<a href="test.php" class="btn btn-outline-success">
<i class="fas fa-check-circle me-2"></i>Probar Sistema
</a>
<a href="index.php" class="btn btn-outline-primary">
<i class="fas fa-home me-2"></i>Ir al Panel
</a>
<a href="server_setup.php" class="btn btn-outline-warning">
<i class="fab fa-whatsapp me-2"></i>Configurar WhatsApp
</a>
</div>
</div>
</div>
<div class="alert alert-info">
<h6><i class="fas fa-info-circle me-2"></i>Información Importante:</h6>
<ul class="mb-0">
<li><strong>Credenciales guardadas:</strong> Revisa el archivo CREDENCIALES_SISTEMA.txt</li>
<li><strong>Base de datos:</strong> Se conecta usando tus credenciales existentes</li>
<li><strong>Sistema protegido:</strong> No se puede reinstalar accidentalmente</li>
<li><strong>Soporte:</strong> <a href="mailto:support@u-site.app">support@u-site.app</a></li>
</ul>
</div>
<?php endif; ?>
<!-- Footer -->
<div class="mt-4 pt-3 border-top text-center">
<small class="text-muted">
Instalación Manual WhatsApp Bot |
Desarrollado por <strong><a href="https://u-site.app" target="_blank">U-Site.app</a></strong> |
<a href="https://u-site.app/support" target="_blank">Soporte</a>
</small>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
+239
View File
@@ -0,0 +1,239 @@
<?php
/**
* Instalador Simplificado para Hosting Compartido
* Version mínima y robusta para HestiaCP/cPanel
* Desarrollado por U-Site.app
* Fecha: 13 de noviembre de 2025
*/
// Configuración básica
error_reporting(E_ALL);
ini_set('display_errors', 1);
$step = $_GET['step'] ?? 'form';
$errors = [];
$success = [];
// Generar contraseña admin
function generatePassword($length = 12) {
return bin2hex(random_bytes($length / 2));
}
$adminPassword = generatePassword(12);
if ($step === 'install' && $_POST) {
$dbHost = trim($_POST['db_host'] ?? 'localhost');
$dbPort = trim($_POST['db_port'] ?? '3306');
$dbName = trim($_POST['db_name'] ?? '');
$dbUser = trim($_POST['db_user'] ?? '');
$dbPass = $_POST['db_pass'] ?? '';
// Validar campos
if (empty($dbName)) $errors[] = "Nombre de BD requerido";
if (empty($dbUser)) $errors[] = "Usuario de BD requerido";
if (empty($dbPass)) $errors[] = "Contraseña de BD requerida";
if (empty($errors)) {
try {
// 1. Conectar a BD
$dsn = "mysql:host={$dbHost};port={$dbPort};dbname={$dbName};charset=utf8mb4";
$pdo = new PDO($dsn, $dbUser, $dbPass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$success[] = "✅ Conexión exitosa";
// 2. Aplicar schema básico
$basicSchema = "
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 conversations (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
direction ENUM('incoming', 'outgoing') NOT NULL,
content TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
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()),
('admin_user', 'admin');
";
$statements = array_filter(explode(';', $basicSchema));
foreach ($statements as $stmt) {
if (trim($stmt)) {
$pdo->exec(trim($stmt));
}
}
$success[] = "✅ Tablas creadas";
// 3. Actualizar config.php
$configFile = 'config/config.php';
if (file_exists($configFile)) {
$config = file_get_contents($configFile);
$config = preg_replace("/define\('DB_HOST',\s*'[^']*'\);/", "define('DB_HOST', '$dbHost');", $config);
$config = preg_replace("/define\('DB_PORT',\s*'[^']*'\);/", "define('DB_PORT', '$dbPort');", $config);
$config = preg_replace("/define\('DB_NAME',\s*'[^']*'\);/", "define('DB_NAME', '$dbName');", $config);
$config = preg_replace("/define\('DB_USER',\s*'[^']*'\);/", "define('DB_USER', '$dbUser');", $config);
$config = preg_replace("/define\('DB_PASS',\s*'[^']*'\);/", "define('DB_PASS', '$dbPass');", $config);
$config = preg_replace("/define\('ADMIN_PASSWORD',\s*'[^']*'\);/", "define('ADMIN_PASSWORD', '" . password_hash($adminPassword, PASSWORD_DEFAULT) . "');", $config);
file_put_contents($configFile, $config);
$success[] = "✅ Configuración actualizada";
}
// 4. Crear archivo de instalación completada
file_put_contents('.installation_completed', date('Y-m-d H:i:s'));
$success[] = "✅ Instalación completada";
// 5. Crear credenciales
$creds = "USUARIO ADMIN: admin\nCONTRASEÑA: $adminPassword\nFECHA: " . date('Y-m-d H:i:s');
file_put_contents('CREDENCIALES.txt', $creds);
$success[] = "✅ Credenciales guardadas en CREDENCIALES.txt";
$step = 'completed';
} catch (Exception $e) {
$errors[] = "❌ Error: " . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>📦 Instalador Simplificado</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; }
.card { background: rgba(255,255,255,0.95); border-radius: 15px; }
.btn-primary { background: #667eea; border: none; }
</style>
</head>
<body class="d-flex align-items-center">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card shadow-lg p-4">
<?php if ($step === 'form'): ?>
<div class="text-center mb-4">
<h2>📦 Instalador Simplificado</h2>
<p class="text-muted">Para hosting compartido (HestiaCP/cPanel)</p>
</div>
<div class="alert alert-info">
<strong>Antes de continuar:</strong> Asegúrate de haber creado la base de datos y usuario en tu panel de control.
</div>
<form method="POST" action="?step=install">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Host</label>
<input type="text" class="form-control" name="db_host" value="localhost" required>
</div>
<div class="col-md-6">
<label class="form-label">Puerto</label>
<input type="text" class="form-control" name="db_port" value="3306" required>
</div>
<div class="col-md-6">
<label class="form-label">Base de datos</label>
<input type="text" class="form-control" name="db_name" placeholder="mi_basedatos" required>
</div>
<div class="col-md-6">
<label class="form-label">Usuario</label>
<input type="text" class="form-control" name="db_user" placeholder="mi_usuario" required>
</div>
<div class="col-12">
<label class="form-label">Contraseña</label>
<input type="password" class="form-control" name="db_pass" required>
</div>
</div>
<div class="text-center mt-4">
<div class="alert alert-success">
<strong>Contraseña de administrador:</strong><br>
<code><?= $adminPassword ?></code>
</div>
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-play"></i> Instalar Sistema
</button>
</div>
</form>
<?php elseif ($step === 'install'): ?>
<div class="text-center mb-4">
<h2>⚙️ Instalando...</h2>
</div>
<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<h5>❌ Errores:</h5>
<?php foreach ($errors as $error): ?>
<div><?= htmlspecialchars($error) ?></div>
<?php endforeach; ?>
<hr>
<a href="?step=form" class="btn btn-outline-danger">Reintentar</a>
</div>
<?php endif; ?>
<?php if (!empty($success)): ?>
<div class="alert alert-success">
<h5>✅ Progreso:</h5>
<?php foreach ($success as $msg): ?>
<div><?= htmlspecialchars($msg) ?></div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php elseif ($step === 'completed'): ?>
<div class="text-center mb-4">
<h2 class="text-success">🎉 ¡Instalación Exitosa!</h2>
</div>
<div class="row g-4">
<div class="col-md-6">
<h5>🔐 Credenciales de Acceso</h5>
<div class="alert alert-success">
<strong>Usuario:</strong> admin<br>
<strong>Contraseña:</strong> <code><?= $adminPassword ?></code>
</div>
</div>
<div class="col-md-6">
<h5>🚀 Próximos Pasos</h5>
<div class="d-grid gap-2">
<a href="index.php" class="btn btn-primary">Panel de Control</a>
<a href="test.php" class="btn btn-outline-success">Probar Sistema</a>
</div>
</div>
</div>
<div class="alert alert-warning mt-4">
<strong>Importante:</strong> Guarda las credenciales. El archivo CREDENCIALES.txt contiene toda la información.
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</body>
</html>
+282
View File
@@ -0,0 +1,282 @@
<?php
/**
* Instalador Ultra Básico - Paso a Paso
* Para resolver problemas de creación de tablas
* Desarrollado por U-Site.app
* Fecha: 14 de noviembre de 2025
*/
error_reporting(E_ALL);
ini_set('display_errors', 1);
if (file_exists('.installation_completed')) {
header('Location: login.php');
exit('Sistema ya instalado');
}
$step = $_GET['step'] ?? 'form';
$errors = [];
$success = [];
$adminPassword = bin2hex(random_bytes(6)); // 12 caracteres
if ($step === 'install' && $_POST) {
$dbHost = trim($_POST['db_host'] ?? 'localhost');
$dbPort = trim($_POST['db_port'] ?? '3306');
$dbName = trim($_POST['db_name'] ?? '');
$dbUser = trim($_POST['db_user'] ?? '');
$dbPass = $_POST['db_pass'] ?? '';
if (empty($dbName)) $errors[] = "BD requerida";
if (empty($dbUser)) $errors[] = "Usuario requerido";
if (empty($dbPass)) $errors[] = "Contraseña requerida";
if (empty($errors)) {
try {
$dsn = "mysql:host={$dbHost};port={$dbPort};dbname={$dbName};charset=utf8mb4";
$pdo = new PDO($dsn, $dbUser, $dbPass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$success[] = "✅ Conectado a '{$dbName}'";
// Crear tabla users primero
$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
)";
$pdo->exec($sql);
// Verificar que se creó
try {
$result = $pdo->query("SELECT COUNT(*) FROM users");
$success[] = "✅ Tabla 'users' creada y funcional";
} catch (Exception $e) {
$errors[] = "❌ Tabla users no funciona: " . $e->getMessage();
}
// Crear tabla system_config
$sql = "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
)";
$pdo->exec($sql);
// Verificar
try {
$result = $pdo->query("SELECT COUNT(*) FROM system_config");
$success[] = "✅ Tabla 'system_config' creada y funcional";
} catch (Exception $e) {
$errors[] = "❌ Tabla system_config no funciona: " . $e->getMessage();
}
// Solo insertar datos si no hay errores
if (empty($errors)) {
// Insertar configuración básica
$configSql = "INSERT IGNORE INTO system_config (config_key, config_value) VALUES
('app_installed', '1'),
('install_date', NOW()),
('business_name', 'Mi Empresa'),
('admin_user', 'admin')";
try {
$pdo->exec($configSql);
$success[] = "✅ Configuración inicial insertada";
} catch (Exception $e) {
$errors[] = "⚠️ Error insertando config: " . $e->getMessage();
}
// Actualizar config.php
if (file_exists('config/config.php')) {
$config = file_get_contents('config/config.php');
$config = preg_replace("/define\('DB_HOST',\s*'[^']*'\);/", "define('DB_HOST', '$dbHost');", $config);
$config = preg_replace("/define\('DB_PORT',\s*'[^']*'\);/", "define('DB_PORT', '$dbPort');", $config);
$config = preg_replace("/define\('DB_NAME',\s*'[^']*'\);/", "define('DB_NAME', '$dbName');", $config);
$config = preg_replace("/define\('DB_USER',\s*'[^']*'\);/", "define('DB_USER', '$dbUser');", $config);
$config = preg_replace("/define\('DB_PASS',\s*'[^']*'\);/", "define('DB_PASS', '$dbPass');", $config);
$config = preg_replace("/define\('ADMIN_PASSWORD',\s*'[^']*'\);/", "define('ADMIN_PASSWORD', '" . password_hash($adminPassword, PASSWORD_DEFAULT) . "');", $config);
file_put_contents('config/config.php', $config);
$success[] = "✅ config.php actualizado";
}
// Verificar que todo funciona
try {
$userCount = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
$configCount = $pdo->query("SELECT COUNT(*) FROM system_config")->fetchColumn();
$success[] = "✅ Verificación final: {$userCount} usuarios, {$configCount} configs";
// Marcar como instalado
file_put_contents('.installation_completed', date('Y-m-d H:i:s'));
file_put_contents('CREDENCIALES_BASICO.txt', "USUARIO: admin\nCONTRASEÑA: {$adminPassword}\nFECHA: " . date('Y-m-d H:i:s'));
$success[] = "🎉 Instalación básica completada";
$step = 'completed';
} catch (Exception $e) {
$errors[] = "❌ Error en verificación final: " . $e->getMessage();
}
}
} catch (PDOException $e) {
$errors[] = "❌ Error de conexión: " . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>⚡ Instalador Ultra Básico</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background: linear-gradient(45deg, #667eea, #764ba2); min-height: 100vh; }
.card { background: rgba(255,255,255,0.95); border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); }
</style>
</head>
<body class="d-flex align-items-center">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card p-4">
<?php if ($step === 'form'): ?>
<div class="text-center mb-4">
<h2>⚡ Instalador Ultra Básico</h2>
<p class="text-muted">Solo crea las tablas esenciales paso a paso</p>
</div>
<div class="alert alert-info">
<h6><i class="fas fa-info-circle me-2"></i>Este instalador:</h6>
<ul class="mb-0">
<li>✅ Crea solo 2 tablas esenciales: <code>users</code> y <code>system_config</code></li>
<li>✅ Verifica cada paso antes de continuar</li>
<li>✅ Proceso ultra simplificado</li>
<li>✅ Ideal para resolver problemas de creación de tablas</li>
</ul>
</div>
<form method="POST" action="?step=install">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Host</label>
<input type="text" class="form-control" name="db_host" value="localhost" required>
</div>
<div class="col-md-6">
<label class="form-label">Puerto</label>
<input type="text" class="form-control" name="db_port" value="3306" required>
</div>
<div class="col-md-6">
<label class="form-label">Base de datos</label>
<input type="text" class="form-control" name="db_name" placeholder="usite_whatsapp_bot" required>
</div>
<div class="col-md-6">
<label class="form-label">Usuario</label>
<input type="text" class="form-control" name="db_user" required>
</div>
<div class="col-12">
<label class="form-label">Contraseña</label>
<input type="password" class="form-control" name="db_pass" required>
</div>
</div>
<div class="text-center mt-4">
<div class="alert alert-success">
<strong>Contraseña de admin que se generará:</strong><br>
<code><?= $adminPassword ?></code>
</div>
<button type="submit" class="btn btn-primary btn-lg">
⚡ Instalación Ultra Básica
</button>
</div>
</form>
<?php elseif ($step === 'install'): ?>
<div class="text-center mb-4">
<h2>⚙️ Instalando paso a paso...</h2>
</div>
<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<h5>❌ Errores:</h5>
<?php foreach ($errors as $error): ?>
<div><?= htmlspecialchars($error) ?></div>
<?php endforeach; ?>
<hr>
<a href="?step=form" class="btn btn-outline-danger">Reintentar</a>
</div>
<?php endif; ?>
<?php if (!empty($success)): ?>
<div class="alert alert-success">
<h5>✅ Progreso:</h5>
<?php foreach ($success as $msg): ?>
<div><?= htmlspecialchars($msg) ?></div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if ($step !== 'completed' && !empty($errors)): ?>
<div class="alert alert-warning">
<h6>💡 Sugerencias para resolver errores:</h6>
<ul class="mb-0">
<li>Verifica que la base de datos existe</li>
<li>Confirma que el usuario tiene permisos ALL en la BD</li>
<li>Prueba conectarte manualmente con phpMyAdmin</li>
<li>Si persiste, contacta soporte con estos mensajes</li>
</ul>
</div>
<?php endif; ?>
<?php elseif ($step === 'completed'): ?>
<div class="text-center mb-4">
<h2 class="text-success">🎉 ¡Instalación Básica Exitosa!</h2>
<p class="text-muted">Sistema mínimo funcional instalado</p>
</div>
<div class="row g-4">
<div class="col-md-6">
<h5>🔑 Credenciales</h5>
<div class="alert alert-success">
<strong>Usuario:</strong> admin<br>
<strong>Contraseña:</strong> <code><?= $adminPassword ?></code><br>
<small>Guardadas en: CREDENCIALES_BASICO.txt</small>
</div>
</div>
<div class="col-md-6">
<h5>🚀 Siguiente</h5>
<div class="d-grid gap-2">
<a href="test.php" class="btn btn-success">Probar Sistema</a>
<a href="index.php" class="btn btn-primary">Panel Admin</a>
<a href="login.php" class="btn btn-outline-info">Login</a>
</div>
</div>
</div>
<div class="alert alert-info mt-4">
<h6>📋 Lo que se instaló:</h6>
<ul class="mb-0">
<li>✅ Tabla <code>users</code> - Para gestión de usuarios</li>
<li>✅ Tabla <code>system_config</code> - Para configuración</li>
<li>✅ Configuración básica del sistema</li>
<li>✅ Credenciales de administrador</li>
</ul>
<hr>
<small><strong>Nota:</strong> Puedes agregar más funcionalidades desde el panel de administración.</small>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</body>
</html>
+331
View File
@@ -0,0 +1,331 @@
<?php
/**
* Sistema de Login del WhatsApp Bot Manager
* Desarrollado por U-Site.app
* Con protección contra ataques de fuerza bruta
*/
require_once 'config/config.php';
// Verificar si la instalación está completada
if (!isInstallationCompleted()) {
header('Location: install.php');
exit('Sistema no instalado. <a href="install.php">Instalar ahora</a>');
}
// Si ya está logueado, redirigir al panel
if (isUserLoggedIn()) {
header('Location: index.php');
exit;
}
$error = '';
$loginBlocked = false;
$clientIp = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
// Verificar si la IP está bloqueada
if (!checkLoginAttempts($clientIp)) {
$loginBlocked = true;
$error = 'Demasiados intentos de login. Inténtalo en ' . (LOGIN_LOCKOUT_TIME / 60) . ' minutos.';
}
// Procesar login
if ($_POST && !$loginBlocked) {
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
if ($username === ADMIN_USERNAME) {
// Verificar contraseña
$adminHash = constant('ADMIN_PASSWORD');
if (password_verify($password, $adminHash)) {
// Login exitoso
clearLoginAttempts($clientIp);
$_SESSION['admin_logged_in'] = true;
$_SESSION['last_activity'] = time();
$_SESSION['login_ip'] = $clientIp;
$_SESSION['login_time'] = time();
header('Location: index.php');
exit;
}
}
// Login fallido
recordFailedLogin($clientIp);
$error = 'Usuario o contraseña incorrectos.';
// Verificar si se bloqueó después de este intento
if (!checkLoginAttempts($clientIp)) {
$loginBlocked = true;
$error = 'Demasiados intentos de login. Cuenta bloqueada por ' . (LOGIN_LOCKOUT_TIME / 60) . ' minutos.';
}
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🔐 Login - WhatsApp Bot Manager</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
:root {
--primary-color: #25d366;
--secondary-color: #075e54;
--bg-gradient: linear-gradient(135deg, #25d366 0%, #075e54 100%);
}
body {
background: var(--bg-gradient);
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
align-items: center;
justify-content: center;
}
.login-card {
background: rgba(255,255,255,0.95);
border-radius: 20px;
backdrop-filter: blur(10px);
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
width: 100%;
max-width: 400px;
}
.btn-login {
background: var(--bg-gradient);
border: none;
color: white;
padding: 12px;
border-radius: 10px;
font-weight: 600;
width: 100%;
}
.btn-login:hover {
transform: translateY(-2px);
color: white;
box-shadow: 0 8px 25px rgba(37, 211, 102, 0.3);
}
.btn-login:disabled {
background: #6c757d;
transform: none;
box-shadow: none;
}
.form-control {
padding: 12px;
border-radius: 10px;
border: 2px solid #e9ecef;
}
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.2rem rgba(37, 211, 102, 0.25);
}
.logo-icon {
font-size: 4rem;
background: var(--bg-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.developer-info {
background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 15px;
padding: 1rem;
margin-top: 2rem;
text-align: center;
}
.security-badge {
background: #28a745;
color: white;
padding: 5px 10px;
border-radius: 15px;
font-size: 0.8rem;
margin: 2px;
display: inline-block;
}
.attempts-warning {
background: linear-gradient(45deg, #ff6b6b, #ee5a24);
color: white;
border-radius: 10px;
padding: 15px;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
</style>
</head>
<body>
<div class="login-card p-5">
<!-- Header -->
<div class="text-center mb-4">
<i class="fab fa-whatsapp logo-icon"></i>
<h2 class="mt-3 fw-bold">WhatsApp Bot Manager</h2>
<p class="text-muted mb-0">Panel de Administración</p>
<small class="text-muted">Desarrollado por <strong>U-Site.app</strong></small>
</div>
<!-- Características de Seguridad -->
<div class="mb-4 text-center">
<span class="security-badge"><i class="fas fa-shield-alt me-1"></i>Protección Brute Force</span>
<span class="security-badge"><i class="fas fa-lock me-1"></i>Sesiones Seguras</span>
<span class="security-badge"><i class="fas fa-user-shield me-1"></i>Control de Acceso</span>
</div>
<!-- Error de bloqueo -->
<?php if ($loginBlocked): ?>
<div class="attempts-warning mb-4">
<div class="text-center">
<i class="fas fa-ban fa-2x mb-2"></i>
<h5>Acceso Bloqueado</h5>
<p class="mb-0"><?= htmlspecialchars($error) ?></p>
</div>
</div>
<?php endif; ?>
<!-- Formulario de Login -->
<form method="POST" id="loginForm">
<!-- Campo Usuario -->
<div class="mb-3">
<label for="username" class="form-label">
<i class="fas fa-user me-2"></i>Usuario
</label>
<input
type="text"
class="form-control"
id="username"
name="username"
required
<?= $loginBlocked ? 'disabled' : '' ?>
value="admin"
readonly
>
</div>
<!-- Campo Contraseña -->
<div class="mb-3">
<label for="password" class="form-label">
<i class="fas fa-lock me-2"></i>Contraseña
</label>
<div class="input-group">
<input
type="password"
class="form-control"
id="password"
name="password"
required
<?= $loginBlocked ? 'disabled' : '' ?>
placeholder="Ingresa tu contraseña"
>
<button
class="btn btn-outline-secondary"
type="button"
id="togglePassword"
<?= $loginBlocked ? 'disabled' : '' ?>
>
<i class="fas fa-eye"></i>
</button>
</div>
</div>
<!-- Error Messages -->
<?php if ($error && !$loginBlocked): ?>
<div class="alert alert-danger">
<i class="fas fa-exclamation-triangle me-2"></i>
<?= htmlspecialchars($error) ?>
</div>
<?php endif; ?>
<!-- Submit Button -->
<button
type="submit"
class="btn-login"
<?= $loginBlocked ? 'disabled' : '' ?>
>
<?php if ($loginBlocked): ?>
<i class="fas fa-ban me-2"></i>Acceso Bloqueado
<?php else: ?>
<i class="fas fa-sign-in-alt me-2"></i>Iniciar Sesión
<?php endif; ?>
</button>
</form>
<!-- Information -->
<div class="mt-4">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
<strong>Información del Sistema:</strong>
<ul class="mb-0 mt-2">
<li>Usuario por defecto: <code>admin</code></li>
<li>Contraseña generada en la instalación</li>
<li>Máximo <?= MAX_LOGIN_ATTEMPTS ?> intentos por IP</li>
<li>Bloqueo de <?= LOGIN_LOCKOUT_TIME / 60 ?> minutos tras fallos</li>
</ul>
</div>
</div>
<!-- Links útiles -->
<div class="mt-4 text-center">
<div class="d-grid gap-2">
<a href="test.php" class="btn btn-outline-primary">
<i class="fas fa-check-circle me-2"></i>Probar Sistema
</a>
<?php if (file_exists('CREDENCIALES_SISTEMA.txt')): ?>
<a href="CREDENCIALES_SISTEMA.txt" class="btn btn-outline-warning btn-sm">
<i class="fas fa-file-text me-2"></i>Ver Credenciales
</a>
<?php endif; ?>
</div>
</div>
<!-- Developer Info -->
<div class="developer-info">
<h6><i class="fas fa-code me-2"></i>Desarrollado por U-Site.app</h6>
<p class="mb-2">Sistema profesional de WhatsApp Bot con máxima seguridad</p>
<div class="d-flex justify-content-center gap-3">
<a href="https://u-site.app" target="_blank" class="text-white">
<i class="fas fa-globe me-1"></i>Website
</a>
<a href="mailto:support@u-site.app" class="text-white">
<i class="fas fa-envelope me-1"></i>Soporte
</a>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Toggle password visibility
document.getElementById('togglePassword').addEventListener('click', function() {
const password = document.getElementById('password');
const icon = this.querySelector('i');
if (password.type === 'password') {
password.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
password.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
});
// Auto-focus en password field
<?php if (!$loginBlocked): ?>
document.getElementById('password').focus();
<?php endif; ?>
// Disable form on blocked
<?php if ($loginBlocked): ?>
document.getElementById('loginForm').addEventListener('submit', function(e) {
e.preventDefault();
return false;
});
<?php endif; ?>
</script>
</body>
</html>
+44
View File
@@ -0,0 +1,44 @@
@echo off
echo 🔄 Renombrando proyecto de "migrador" a "bot"...
REM Detener el servidor PHP si está corriendo
echo 📛 Deteniendo servidor PHP...
taskkill /f /im php.exe >nul 2>&1
REM Cambiar al directorio padre
cd ..
REM Copiar archivos a nueva carpeta "bot"
echo 📁 Creando nueva carpeta "bot"...
if not exist "bot" mkdir bot
echo 📋 Copiando archivos...
xcopy "migrador\*" "bot\" /E /H /C /I /Y >nul
REM Eliminar carpeta vieja (opcional)
echo ❓ ¿Deseas eliminar la carpeta "migrador" antigua? (y/n):
set /p choice=
if /i "%choice%"=="y" (
echo 🗑️ Eliminando carpeta "migrador"...
rmdir /s /q "migrador"
echo ✅ Carpeta "migrador" eliminada
) else (
echo ⚠️ Carpeta "migrador" conservada
)
REM Cambiar al nuevo directorio
cd bot
echo 🚀 ¡Renombrado completado!
echo.
echo 📋 Nuevas URLs:
echo 🏠 Panel: http://localhost/bot/index.php
echo 📋 Inicio: http://localhost/bot/inicio.html
echo ⚙️ Instalador: http://localhost/bot/install.php
echo 🧪 Pruebas: http://localhost/bot/test.php
echo.
echo 💡 Para usar con servidor PHP:
echo cd C:\laragon\www\bot
echo php -S localhost:8000 -t .
echo.
pause
+342
View File
@@ -0,0 +1,342 @@
<?php
/**
* Configurador automático para hosting compartido
* Ejecuta este script una sola vez después de subir archivos
*/
$step = $_GET['step'] ?? 'welcome';
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🚀 Configurador de Servidor - WhatsApp Bot</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
body { background: linear-gradient(135deg, #25d366 0%, #075e54 100%); min-height: 100vh; }
.config-card { background: rgba(255,255,255,0.95); border-radius: 20px; backdrop-filter: blur(10px); }
.btn-whatsapp { background: linear-gradient(135deg, #25d366, #075e54); border: none; }
.step-active { background: #25d366; color: white; }
.step-pending { background: #e9ecef; color: #6c757d; }
</style>
</head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="config-card p-5">
<?php if ($step === 'welcome'): ?>
<!-- Pantalla de Bienvenida -->
<div class="text-center mb-4">
<i class="fab fa-whatsapp text-success" style="font-size: 4rem;"></i>
<h1 class="mt-3">Configurador de Servidor</h1>
<p class="text-muted">Configura tu WhatsApp Bot Manager en el servidor</p>
</div>
<div class="row g-3 mb-4">
<div class="col-md-4">
<div class="text-center p-3 rounded step-pending">
<i class="fas fa-server fa-2x mb-2"></i>
<h6>1. Verificar Servidor</h6>
</div>
</div>
<div class="col-md-4">
<div class="text-center p-3 rounded step-pending">
<i class="fas fa-database fa-2x mb-2"></i>
<h6>2. Configurar BD</h6>
</div>
</div>
<div class="col-md-4">
<div class="text-center p-3 rounded step-pending">
<i class="fas fa-cog fa-2x mb-2"></i>
<h6>3. WhatsApp API</h6>
</div>
</div>
</div>
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
<strong>Antes de comenzar, asegúrate de tener:</strong>
<ul class="mb-0 mt-2">
<li>Credenciales de base de datos MySQL</li>
<li>Token de WhatsApp Business API</li>
<li>Phone Number ID de WhatsApp</li>
<li>Dominio con SSL/HTTPS activo</li>
</ul>
</div>
<div class="text-center">
<a href="?step=check" class="btn btn-whatsapp btn-lg text-white">
<i class="fas fa-play me-2"></i>Comenzar Configuración
</a>
</div>
<?php elseif ($step === 'check'): ?>
<!-- Verificación del Servidor -->
<h2 class="mb-4">
<i class="fas fa-server me-2 text-success"></i>Verificación del Servidor
</h2>
<?php
$checks = [
'PHP Version' => version_compare(PHP_VERSION, '8.0.0', '>='),
'PDO Extension' => extension_loaded('pdo'),
'PDO MySQL' => extension_loaded('pdo_mysql'),
'cURL Extension' => extension_loaded('curl'),
'JSON Extension' => extension_loaded('json'),
'mbstring Extension' => extension_loaded('mbstring'),
'OpenSSL Extension' => extension_loaded('openssl'),
'Write Permissions' => is_writable('.'),
'HTTPS' => (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
];
$allPassed = true;
foreach ($checks as $check => $status) {
if (!$status) $allPassed = false;
}
?>
<div class="row g-3">
<?php foreach ($checks as $check => $status): ?>
<div class="col-md-6">
<div class="d-flex align-items-center p-3 border rounded">
<i class="fas fa-<?= $status ? 'check-circle text-success' : 'times-circle text-danger' ?> me-3"></i>
<div>
<strong><?= $check ?></strong><br>
<small class="text-muted">
<?= $status ? 'OK' : 'Falta' ?>
<?php if ($check === 'PHP Version'): ?>
(<?= PHP_VERSION ?>)
<?php endif; ?>
</small>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="mt-4">
<?php if ($allPassed): ?>
<div class="alert alert-success">
<i class="fas fa-check-circle me-2"></i>
¡Excelente! Tu servidor cumple todos los requisitos.
</div>
<div class="text-center">
<a href="?step=database" class="btn btn-whatsapp btn-lg text-white">
<i class="fas fa-arrow-right me-2"></i>Configurar Base de Datos
</a>
</div>
<?php else: ?>
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
Algunos requisitos no se cumplen. Contacta a tu proveedor de hosting.
</div>
<div class="text-center">
<a href="?step=check" class="btn btn-outline-primary">
<i class="fas fa-refresh me-2"></i>Verificar Nuevamente
</a>
</div>
<?php endif; ?>
</div>
<?php elseif ($step === 'database'): ?>
<!-- Configuración de Base de Datos -->
<h2 class="mb-4">
<i class="fas fa-database me-2 text-primary"></i>Configuración de Base de Datos
</h2>
<?php if ($_POST): ?>
<?php
$host = $_POST['db_host'] ?? '';
$name = $_POST['db_name'] ?? '';
$user = $_POST['db_user'] ?? '';
$pass = $_POST['db_pass'] ?? '';
try {
$pdo = new PDO("mysql:host=$host;dbname=$name;charset=utf8mb4", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Guardar configuración
$config = file_get_contents('config/config.php');
$config = preg_replace("/define\('DB_HOST',\s*'[^']*'\);/", "define('DB_HOST', '$host');", $config);
$config = preg_replace("/define\('DB_NAME',\s*'[^']*'\);/", "define('DB_NAME', '$name');", $config);
$config = preg_replace("/define\('DB_USER',\s*'[^']*'\);/", "define('DB_USER', '$user');", $config);
$config = preg_replace("/define\('DB_PASS',\s*'[^']*'\);/", "define('DB_PASS', '$pass');", $config);
file_put_contents('config/config.php', $config);
echo '<div class="alert alert-success">
<i class="fas fa-check-circle me-2"></i>
¡Conexión exitosa! Configuración guardada.
</div>';
echo '<div class="text-center">
<a href="?step=whatsapp" class="btn btn-whatsapp btn-lg text-white">
<i class="fas fa-arrow-right me-2"></i>Configurar WhatsApp
</a>
</div>';
} catch (PDOException $e) {
echo '<div class="alert alert-danger">
<i class="fas fa-exclamation-circle me-2"></i>
Error de conexión: ' . htmlspecialchars($e->getMessage()) . '
</div>';
}
?>
<?php endif; ?>
<form method="POST">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Host de Base de Datos</label>
<input type="text" class="form-control" name="db_host"
value="localhost" required
placeholder="localhost">
<small class="text-muted">Generalmente: localhost</small>
</div>
<div class="col-md-6">
<label class="form-label">Nombre de Base de Datos</label>
<input type="text" class="form-control" name="db_name"
required placeholder="whatsapp_bot">
</div>
<div class="col-md-6">
<label class="form-label">Usuario de BD</label>
<input type="text" class="form-control" name="db_user"
required placeholder="usuario_db">
</div>
<div class="col-md-6">
<label class="form-label">Contraseña de BD</label>
<input type="password" class="form-control" name="db_pass"
required placeholder="••••••••">
</div>
</div>
<div class="alert alert-info mt-3">
<i class="fas fa-info-circle me-2"></i>
Estos datos los obtienes desde tu panel de hosting (cPanel, Plesk, etc.)
</div>
<div class="text-center mt-4">
<button type="submit" class="btn btn-whatsapp btn-lg text-white">
<i class="fas fa-plug me-2"></i>Probar Conexión
</button>
</div>
</form>
<?php elseif ($step === 'whatsapp'): ?>
<!-- Configuración de WhatsApp -->
<h2 class="mb-4">
<i class="fab fa-whatsapp me-2 text-success"></i>Configuración de WhatsApp
</h2>
<?php if ($_POST): ?>
<?php
$token = $_POST['whatsapp_token'] ?? '';
$phone_id = $_POST['phone_id'] ?? '';
$webhook_token = $_POST['webhook_token'] ?? 'mi_token_secreto_123';
$domain = $_POST['domain'] ?? '';
// Actualizar configuración
$config = file_get_contents('config/config.php');
$config = preg_replace("/define\('WHATSAPP_TOKEN',\s*'[^']*'\);/",
"define('WHATSAPP_TOKEN', '$token');", $config);
$config = preg_replace("/define\('WHATSAPP_PHONE_NUMBER_ID',\s*'[^']*'\);/",
"define('WHATSAPP_PHONE_NUMBER_ID', '$phone_id');", $config);
$config = preg_replace("/define\('WEBHOOK_VERIFY_TOKEN',\s*'[^']*'\);/",
"define('WEBHOOK_VERIFY_TOKEN', '$webhook_token');", $config);
$config = preg_replace("/define\('APP_URL',\s*'[^']*'\);/",
"define('APP_URL', 'https://$domain');", $config);
file_put_contents('config/config.php', $config);
echo '<div class="alert alert-success">
<i class="fas fa-check-circle me-2"></i>
¡Configuración de WhatsApp guardada correctamente!
</div>';
?>
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Importante:</strong> Configura estos datos en Facebook Developers:
<ul class="mt-2 mb-0">
<li><strong>Webhook URL:</strong> https://<?= htmlspecialchars($domain) ?>/bot/api/webhook.php</li>
<li><strong>Verify Token:</strong> <?= htmlspecialchars($webhook_token) ?></li>
<li><strong>Subscribe to:</strong> messages</li>
</ul>
</div>
<div class="text-center">
<a href="install.php" class="btn btn-success btn-lg me-3">
<i class="fas fa-database me-2"></i>Instalar Base de Datos
</a>
<a href="index.php" class="btn btn-whatsapp btn-lg text-white">
<i class="fas fa-rocket me-2"></i>Ir al Panel
</a>
</div>
<?php endif; ?>
<form method="POST">
<div class="row g-3">
<div class="col-12">
<label class="form-label">Token de WhatsApp Business</label>
<input type="text" class="form-control" name="whatsapp_token"
required placeholder="EAAxxxxxxxxxxxx...">
<small class="text-muted">Obténlo desde Facebook for Developers</small>
</div>
<div class="col-md-6">
<label class="form-label">Phone Number ID</label>
<input type="text" class="form-control" name="phone_id"
required placeholder="123456789012345">
</div>
<div class="col-md-6">
<label class="form-label">Tu Dominio</label>
<input type="text" class="form-control" name="domain"
required placeholder="midominio.com"
value="<?= htmlspecialchars($_SERVER['HTTP_HOST'] ?? '') ?>">
</div>
<div class="col-12">
<label class="form-label">Token de Verificación Webhook</label>
<input type="text" class="form-control" name="webhook_token"
value="mi_token_secreto_123" required>
<small class="text-muted">Usado para verificar el webhook</small>
</div>
</div>
<div class="text-center mt-4">
<button type="submit" class="btn btn-whatsapp btn-lg text-white">
<i class="fas fa-save me-2"></i>Guardar Configuración
</button>
</div>
</form>
<?php endif; ?>
<!-- Navigation -->
<div class="mt-5 pt-4 border-top text-center">
<div class="btn-group" role="group">
<?php if ($step !== 'welcome'): ?>
<a href="?step=welcome" class="btn btn-outline-secondary">
<i class="fas fa-home me-2"></i>Inicio
</a>
<?php endif; ?>
<?php if ($step === 'whatsapp'): ?>
<a href="?step=database" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-2"></i>Anterior
</a>
<?php endif; ?>
</div>
<div class="mt-3">
<small class="text-muted">
WhatsApp Bot Manager v1.0 |
<a href="DEPLOYMENT_GUIDE.md" target="_blank">Guía Completa</a>
</small>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
+380
View File
@@ -0,0 +1,380 @@
<?php
/**
* Servicio Bot - Manejo de conversaciones y menús
* Fecha: 13 de noviembre de 2025
*/
class BotService {
private $db;
private $whatsappService;
public function __construct() {
$this->db = Database::getInstance();
$this->whatsappService = new WhatsAppService();
}
/**
* Procesar mensaje entrante
*/
public function processMessage($user, $messageText, $messageType = 'text') {
// Solo procesar mensajes de texto
if ($messageType !== 'text') {
return;
}
$messageText = trim($messageText);
$phoneNumber = $user['phone_number'];
// Verificar si es un nuevo usuario (enviar mensaje de bienvenida)
if ($this->isNewUser($user['id'])) {
$this->sendWelcomeMessage($phoneNumber);
return;
}
// Procesar comandos especiales
if ($this->processSpecialCommands($phoneNumber, $messageText)) {
return;
}
// Verificar si el usuario está en un menú
if ($user['current_menu_id']) {
$this->processMenuSelection($user, $messageText);
} else {
// Procesar respuestas automáticas o comando menu
$this->processAutoResponses($phoneNumber, $messageText);
}
}
/**
* Verificar si es un nuevo usuario
*/
private function isNewUser($userId) {
$messageCount = $this->db->fetch(
"SELECT COUNT(*) as count FROM conversations WHERE user_id = :user_id",
['user_id' => $userId]
);
return $messageCount['count'] <= 1; // Solo el mensaje actual
}
/**
* Enviar mensaje de bienvenida
*/
private function sendWelcomeMessage($phoneNumber) {
$welcomeMessage = getConfigFromDB('welcome_message', '¡Hola! 👋 Bienvenido a nuestro servicio automatizado. Escribe *menu* para ver las opciones disponibles.');
$this->whatsappService->sendTextMessage($phoneNumber, $welcomeMessage);
}
/**
* Procesar comandos especiales
*/
private function processSpecialCommands($phoneNumber, $messageText) {
$command = strtolower($messageText);
switch ($command) {
case 'menu':
case 'menú':
case 'inicio':
$this->showMainMenu($phoneNumber);
return true;
case 'salir':
case 'exit':
case 'cancelar':
$this->exitMenu($phoneNumber);
return true;
case 'help':
case 'ayuda':
$this->showHelp($phoneNumber);
return true;
}
return false;
}
/**
* Mostrar menú principal
*/
private function showMainMenu($phoneNumber) {
$mainMenu = $this->db->fetch(
"SELECT * FROM menus WHERE is_root = 1 AND is_active = 1 ORDER BY order_position LIMIT 1"
);
if ($mainMenu) {
$this->showMenu($phoneNumber, $mainMenu['id']);
} else {
$this->whatsappService->sendTextMessage(
$phoneNumber,
"❌ No hay menús configurados. Contacta con soporte."
);
}
}
/**
* Mostrar menú específico
*/
private function showMenu($phoneNumber, $menuId) {
// Obtener información del menú
$menu = $this->db->fetch(
"SELECT * FROM menus WHERE id = :id AND is_active = 1",
['id' => $menuId]
);
if (!$menu) {
$this->whatsappService->sendTextMessage(
$phoneNumber,
"❌ Menú no encontrado."
);
return;
}
// Obtener opciones del menú
$options = $this->db->fetchAll(
"SELECT * FROM menu_options WHERE menu_id = :menu_id AND is_active = 1 ORDER BY option_number",
['menu_id' => $menuId]
);
if (empty($options)) {
$this->whatsappService->sendTextMessage(
$phoneNumber,
"❌ Este menú no tiene opciones configuradas."
);
return;
}
// Construir mensaje del menú
$menuText = "📋 *" . $menu['title'] . "*\n\n";
if ($menu['description']) {
$menuText .= $menu['description'] . "\n\n";
}
foreach ($options as $option) {
$menuText .= $option['option_number'] . ". " . $option['text'] . "\n";
}
$menuText .= "\n💬 *Responde con el número de la opción que deseas*";
// Actualizar estado del usuario
$this->updateUserMenuState($phoneNumber, $menuId);
// Enviar mensaje
$this->whatsappService->sendTextMessage($phoneNumber, $menuText);
}
/**
* Procesar selección de menú
*/
private function processMenuSelection($user, $messageText) {
$phoneNumber = $user['phone_number'];
$currentMenuId = $user['current_menu_id'];
// Verificar si es un número
if (!is_numeric($messageText)) {
$this->whatsappService->sendTextMessage(
$phoneNumber,
"❌ Por favor, responde solo con el número de la opción deseada."
);
return;
}
$optionNumber = (int)$messageText;
// Buscar la opción seleccionada
$option = $this->db->fetch(
"SELECT * FROM menu_options WHERE menu_id = :menu_id AND option_number = :option_number AND is_active = 1",
['menu_id' => $currentMenuId, 'option_number' => $optionNumber]
);
if (!$option) {
$this->whatsappService->sendTextMessage(
$phoneNumber,
"❌ Opción inválida. Por favor, selecciona una opción válida del menú."
);
return;
}
// Procesar acción de la opción
$this->processMenuAction($phoneNumber, $option);
}
/**
* Procesar acción del menú
*/
private function processMenuAction($phoneNumber, $option) {
switch ($option['action_type']) {
case 'menu':
// Navegar a otro menú
$targetMenu = $this->db->fetch(
"SELECT * FROM menus WHERE name = :name AND is_active = 1",
['name' => $option['action_value']]
);
if ($targetMenu) {
$this->showMenu($phoneNumber, $targetMenu['id']);
} else {
$this->whatsappService->sendTextMessage(
$phoneNumber,
"❌ Menú no encontrado: " . $option['action_value']
);
}
break;
case 'message':
// Enviar mensaje de respuesta
$this->whatsappService->sendTextMessage($phoneNumber, $option['action_value']);
$this->exitMenu($phoneNumber);
break;
case 'api_call':
// Llamar API externa (implementar según necesidad)
$this->processApiCall($phoneNumber, $option['action_value']);
break;
case 'end':
// Finalizar conversación
$message = $option['action_value'] ?: "Gracias por usar nuestro servicio. ¡Hasta pronto!";
$this->whatsappService->sendTextMessage($phoneNumber, $message);
$this->exitMenu($phoneNumber);
break;
}
}
/**
* Procesar llamada API externa
*/
private function processApiCall($phoneNumber, $apiEndpoint) {
// Aquí puedes implementar llamadas a APIs externas
// Por ejemplo, consultar saldos, procesar pagos, etc.
$this->whatsappService->sendTextMessage(
$phoneNumber,
"🔄 Procesando tu solicitud... Un momento por favor."
);
// Simular procesamiento
sleep(2);
$this->whatsappService->sendTextMessage(
$phoneNumber,
"✅ Tu solicitud ha sido procesada exitosamente."
);
$this->exitMenu($phoneNumber);
}
/**
* Procesar respuestas automáticas
*/
private function processAutoResponses($phoneNumber, $messageText) {
$keyword = strtolower($messageText);
// Buscar respuesta automática por keyword
$autoResponse = $this->db->fetch(
"SELECT * FROM auto_responses WHERE trigger_type = 'keyword' AND LOWER(trigger_value) = :keyword AND is_active = 1",
['keyword' => $keyword]
);
if ($autoResponse) {
$this->whatsappService->sendTextMessage($phoneNumber, $autoResponse['response_text']);
// Si la keyword es 'menu', mostrar el menú
if ($keyword === 'menu' || $keyword === 'menú') {
$this->showMainMenu($phoneNumber);
}
} else {
// Respuesta por defecto para mensajes no reconocidos
$defaultMessage = "🤖 No entendí tu mensaje. Escribe *menu* para ver las opciones disponibles o *ayuda* para obtener ayuda.";
$this->whatsappService->sendTextMessage($phoneNumber, $defaultMessage);
}
}
/**
* Salir del menú actual
*/
private function exitMenu($phoneNumber) {
$this->db->update(
'users',
['current_menu_id' => null, 'current_step' => 0, 'session_data' => null],
'phone_number = :phone',
['phone' => $phoneNumber]
);
}
/**
* Actualizar estado del menú del usuario
*/
private function updateUserMenuState($phoneNumber, $menuId) {
$this->db->update(
'users',
['current_menu_id' => $menuId, 'current_step' => 1],
'phone_number = :phone',
['phone' => $phoneNumber]
);
}
/**
* Mostrar ayuda
*/
private function showHelp($phoneNumber) {
$helpText = "🆘 *Ayuda del Sistema*\n\n";
$helpText .= "📋 *menu* - Mostrar menú principal\n";
$helpText .= "❌ *salir* - Salir del menú actual\n";
$helpText .= "🆘 *ayuda* - Mostrar esta ayuda\n\n";
$helpText .= "💡 *Consejos:*\n";
$helpText .= "• Responde solo con números en los menús\n";
$helpText .= "• Escribe palabras clave para obtener respuestas rápidas\n";
$helpText .= "• Si tienes problemas, escribe *soporte*";
$this->whatsappService->sendTextMessage($phoneNumber, $helpText);
}
/**
* Procesar mensaje de soporte
*/
public function processSupportMessage($phoneNumber, $message) {
// Notificar a administradores sobre consulta de soporte
$this->whatsappService->sendTextMessage(
$phoneNumber,
"🆘 Tu mensaje ha sido enviado a nuestro equipo de soporte. Te contactaremos pronto.\n\n" .
"📞 También puedes llamarnos al: +57 1 234 5678\n" .
"📧 O escribirnos a: soporte@miempresa.com"
);
// Aquí podrías implementar notificaciones a administradores
// Por ejemplo, enviar email o notificación a Slack
}
/**
* Obtener estadísticas del bot
*/
public function getBotStats() {
$stats = [];
// Total de usuarios
$stats['total_users'] = $this->db->fetch(
"SELECT COUNT(*) as count FROM users"
)['count'];
// Usuarios activos (último mes)
$stats['active_users'] = $this->db->fetch(
"SELECT COUNT(DISTINCT user_id) as count FROM conversations
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)"
)['count'];
// Total de mensajes
$stats['total_messages'] = $this->db->fetch(
"SELECT COUNT(*) as count FROM conversations"
)['count'];
// Mensajes hoy
$stats['messages_today'] = $this->db->fetch(
"SELECT COUNT(*) as count FROM conversations
WHERE DATE(created_at) = CURDATE()"
)['count'];
return $stats;
}
}
?>
+336
View File
@@ -0,0 +1,336 @@
<?php
/**
* Servicio de WhatsApp - Envío de mensajes
* Fecha: 13 de noviembre de 2025
*/
class WhatsAppService {
private $token;
private $phoneNumberId;
private $apiUrl;
private $db;
public function __construct() {
$this->token = WHATSAPP_TOKEN;
$this->phoneNumberId = WHATSAPP_PHONE_NUMBER_ID;
$this->apiUrl = WHATSAPP_API_URL;
$this->db = Database::getInstance();
}
/**
* Enviar mensaje de texto
*/
public function sendTextMessage($to, $message) {
$data = [
'messaging_product' => 'whatsapp',
'to' => $this->formatPhoneNumber($to),
'type' => 'text',
'text' => [
'body' => $message
]
];
return $this->sendMessage($data);
}
/**
* Enviar mensaje con template
*/
public function sendTemplateMessage($to, $templateName, $language = 'es', $parameters = []) {
$template = [
'name' => $templateName,
'language' => [
'code' => $language
]
];
if (!empty($parameters)) {
$template['components'] = [
[
'type' => 'body',
'parameters' => $parameters
]
];
}
$data = [
'messaging_product' => 'whatsapp',
'to' => $this->formatPhoneNumber($to),
'type' => 'template',
'template' => $template
];
return $this->sendMessage($data);
}
/**
* Enviar mensaje con botones interactivos
*/
public function sendInteractiveMessage($to, $bodyText, $buttons, $header = null, $footer = null) {
$interactive = [
'type' => 'button',
'body' => [
'text' => $bodyText
],
'action' => [
'buttons' => $buttons
]
];
if ($header) {
$interactive['header'] = [
'type' => 'text',
'text' => $header
];
}
if ($footer) {
$interactive['footer'] = [
'text' => $footer
];
}
$data = [
'messaging_product' => 'whatsapp',
'recipient_type' => 'individual',
'to' => $this->formatPhoneNumber($to),
'type' => 'interactive',
'interactive' => $interactive
];
return $this->sendMessage($data);
}
/**
* Enviar mensaje con lista
*/
public function sendListMessage($to, $bodyText, $buttonText, $sections, $header = null, $footer = null) {
$interactive = [
'type' => 'list',
'body' => [
'text' => $bodyText
],
'action' => [
'button' => $buttonText,
'sections' => $sections
]
];
if ($header) {
$interactive['header'] = [
'type' => 'text',
'text' => $header
];
}
if ($footer) {
$interactive['footer'] = [
'text' => $footer
];
}
$data = [
'messaging_product' => 'whatsapp',
'recipient_type' => 'individual',
'to' => $this->formatPhoneNumber($to),
'type' => 'interactive',
'interactive' => $interactive
];
return $this->sendMessage($data);
}
/**
* Marcar mensaje como leído
*/
public function markAsRead($messageId) {
$data = [
'messaging_product' => 'whatsapp',
'status' => 'read',
'message_id' => $messageId
];
$url = $this->apiUrl . $this->phoneNumberId . '/messages';
return $this->makeRequest('POST', $url, $data);
}
/**
* Enviar mensaje principal
*/
private function sendMessage($data) {
$url = $this->apiUrl . $this->phoneNumberId . '/messages';
$response = $this->makeRequest('POST', $url, $data);
// Guardar mensaje enviado en la base de datos
if ($response && isset($response['messages'][0]['id'])) {
$this->saveOutgoingMessage($data, $response);
}
return $response;
}
/**
* Realizar petición HTTP
*/
private function makeRequest($method, $url, $data = null) {
$ch = curl_init();
$headers = [
'Authorization: Bearer ' . $this->token,
'Content-Type: application/json',
'User-Agent: WhatsApp-Bot/1.0'
];
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3
]);
if ($method === 'POST' && $data) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
error_log("cURL Error: " . $error);
throw new Exception("Error de comunicación con WhatsApp: " . $error);
}
$decoded = json_decode($response, true);
if ($httpCode >= 400) {
$errorMsg = isset($decoded['error']['message']) ? $decoded['error']['message'] : 'Error desconocido';
error_log("WhatsApp API Error: " . $response);
throw new Exception("Error de WhatsApp API: " . $errorMsg);
}
return $decoded;
}
/**
* Formatear número de teléfono
*/
private function formatPhoneNumber($phone) {
// Remover caracteres especiales
$phone = preg_replace('/[^0-9]/', '', $phone);
// Si empieza con +57 (Colombia), mantenerlo
if (substr($phone, 0, 2) === '57' && strlen($phone) >= 12) {
return $phone;
}
// Si es número colombiano sin código de país
if (strlen($phone) === 10 && substr($phone, 0, 1) === '3') {
return '57' . $phone;
}
return $phone;
}
/**
* Guardar mensaje enviado en BD
*/
private function saveOutgoingMessage($data, $response) {
try {
$user = $this->getUserByPhone($data['to']);
if ($user) {
$messageData = [
'user_id' => $user['id'],
'message_id' => $response['messages'][0]['id'],
'direction' => 'outgoing',
'message_type' => $data['type'],
'content' => $this->extractMessageContent($data),
'status' => 'sent',
'created_at' => date('Y-m-d H:i:s')
];
$this->db->insert('conversations', $messageData);
}
} catch (Exception $e) {
error_log("Error saving outgoing message: " . $e->getMessage());
}
}
/**
* Extraer contenido del mensaje para guardar
*/
private function extractMessageContent($data) {
switch ($data['type']) {
case 'text':
return $data['text']['body'];
case 'template':
return 'Template: ' . $data['template']['name'];
case 'interactive':
if (isset($data['interactive']['body']['text'])) {
return $data['interactive']['body']['text'];
}
break;
}
return json_encode($data);
}
/**
* Obtener usuario por teléfono
*/
private function getUserByPhone($phone) {
return $this->db->fetch(
"SELECT * FROM users WHERE phone_number = :phone",
['phone' => $phone]
);
}
/**
* Descargar archivo multimedia
*/
public function downloadMedia($mediaId) {
$url = $this->apiUrl . $mediaId;
// Primero obtener información del archivo
$mediaInfo = $this->makeRequest('GET', $url);
if (!$mediaInfo || !isset($mediaInfo['url'])) {
throw new Exception("No se pudo obtener información del archivo");
}
// Descargar el archivo
$fileUrl = $mediaInfo['url'];
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $fileUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $this->token
],
CURLOPT_TIMEOUT => 60,
CURLOPT_FOLLOWLOCATION => true
]);
$fileContent = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("Error descargando archivo multimedia");
}
return [
'content' => $fileContent,
'mime_type' => $mediaInfo['mime_type'] ?? 'application/octet-stream',
'file_size' => $mediaInfo['file_size'] ?? strlen($fileContent)
];
}
}
?>
+189
View File
@@ -0,0 +1,189 @@
<?php
/**
* Script de prueba del sistema WhatsApp Bot
* Fecha: 13 de noviembre de 2025
*/
error_reporting(E_ALL);
ini_set('display_errors', 1);
echo "<h1>🤖 Test del Sistema WhatsApp Bot</h1>";
// Verificar archivos principales
$files = [
'config/config.php' => 'Configuración principal',
'classes/Database.php' => 'Clase Database',
'services/WhatsAppService.php' => 'Servicio WhatsApp',
'services/BotService.php' => 'Servicio Bot',
'api/webhook.php' => 'Webhook principal',
'database/schema.sql' => 'Esquema de BD',
'index.php' => 'Página principal'
];
echo "<h2>📂 Verificación de Archivos:</h2>";
echo "<ul>";
foreach ($files as $file => $description) {
$exists = file_exists($file);
$status = $exists ? "✅ Existe" : "❌ Faltante";
echo "<li><strong>{$description}</strong> ({$file}): {$status}</li>";
}
echo "</ul>";
// Probar configuración
echo "<h2>⚙️ Verificación de Configuración:</h2>";
try {
require_once 'config/config.php';
echo "<p>✅ Configuración cargada correctamente</p>";
echo "<ul>";
echo "<li>DB Host: " . DB_HOST . "</li>";
echo "<li>DB Name: " . DB_NAME . "</li>";
echo "<li>WhatsApp Phone ID: " . WHATSAPP_PHONE_NUMBER_ID . "</li>";
echo "<li>App URL: " . APP_URL . "</li>";
echo "</ul>";
} catch (Exception $e) {
echo "<p>❌ Error en configuración: " . $e->getMessage() . "</p>";
}
// Probar conexión a base de datos
echo "<h2>💾 Verificación de Base de Datos:</h2>";
try {
$db = Database::getInstance();
echo "<p>✅ Conexión a base de datos exitosa</p>";
// Verificar tablas principales
$tables = ['users', 'conversations', 'menus', 'menu_options', 'system_config'];
echo "<ul>";
foreach ($tables as $table) {
try {
$count = $db->fetch("SELECT COUNT(*) as count FROM {$table}")['count'];
echo "<li>Tabla <strong>{$table}</strong>: {$count} registros</li>";
} catch (Exception $e) {
echo "<li>Tabla <strong>{$table}</strong>: ❌ Error: " . $e->getMessage() . "</li>";
}
}
echo "</ul>";
} catch (Exception $e) {
echo "<p>❌ Error de conexión: " . $e->getMessage() . "</p>";
}
// Probar servicios
echo "<h2>🔧 Verificación de Servicios:</h2>";
try {
$whatsappService = new WhatsAppService();
echo "<p>✅ WhatsAppService inicializado</p>";
$botService = new BotService();
echo "<p>✅ BotService inicializado</p>";
} catch (Exception $e) {
echo "<p>❌ Error en servicios: " . $e->getMessage() . "</p>";
}
// Probar APIs
echo "<h2>🌐 Verificación de APIs:</h2>";
$apis = [
'api/get_stats.php',
'api/get_users.php',
'api/get_menus.php',
'api/get_conversations.php',
'api/webhook.php'
];
echo "<ul>";
foreach ($apis as $api) {
$exists = file_exists($api);
$readable = $exists ? is_readable($api) : false;
$status = $readable ? "✅ Disponible" : "❌ No disponible";
echo "<li><strong>{$api}</strong>: {$status}</li>";
}
echo "</ul>";
// Probar funcionalidad básica del bot
echo "<h2>🤖 Test Básico del Bot:</h2>";
try {
// Crear usuario de prueba
$testPhone = '573000000000';
// Verificar si el usuario existe
$existingUser = $db->fetch(
"SELECT * FROM users WHERE phone_number = :phone",
['phone' => $testPhone]
);
if (!$existingUser) {
$userId = $db->insert('users', [
'phone_number' => $testPhone,
'name' => 'Usuario de Prueba',
'status' => 'active',
'created_at' => date('Y-m-d H:i:s')
]);
echo "<p>✅ Usuario de prueba creado (ID: {$userId})</p>";
} else {
echo "<p>✅ Usuario de prueba ya existe</p>";
}
// Probar menú principal
$mainMenu = $db->fetch(
"SELECT * FROM menus WHERE is_root = 1 AND is_active = 1 LIMIT 1"
);
if ($mainMenu) {
echo "<p>✅ Menú principal configurado: " . $mainMenu['title'] . "</p>";
// Probar opciones del menú
$options = $db->fetchAll(
"SELECT * FROM menu_options WHERE menu_id = :id ORDER BY option_number",
['id' => $mainMenu['id']]
);
echo "<p>✅ Opciones del menú principal: " . count($options) . " opciones</p>";
} else {
echo "<p>❌ No hay menú principal configurado</p>";
}
} catch (Exception $e) {
echo "<p>❌ Error en test del bot: " . $e->getMessage() . "</p>";
}
// URLs de prueba
echo "<h2>🔗 URLs del Sistema:</h2>";
echo "<ul>";
echo "<li><strong>Panel Principal:</strong> <a href='index.php'>index.php</a></li>";
echo "<li><strong>Webhook:</strong> <a href='api/webhook.php?hub_mode=subscribe&hub_verify_token=mi_token_secreto_123&hub_challenge=test'>webhook.php (test)</a></li>";
echo "<li><strong>API Stats:</strong> <a href='api/get_stats.php'>get_stats.php</a></li>";
echo "<li><strong>API Users:</strong> <a href='api/get_users.php'>get_users.php</a></li>";
echo "</ul>";
echo "<h2>📋 Resumen:</h2>";
echo "<p><strong>El sistema WhatsApp Bot está:</strong></p>";
// Calcular estado general
$allGood = file_exists('config/config.php') &&
file_exists('classes/Database.php') &&
file_exists('api/webhook.php') &&
class_exists('Database');
if ($allGood) {
echo "<div style='background: #d4edda; color: #155724; padding: 15px; border-radius: 5px; border-left: 5px solid #28a745;'>";
echo "<h3>✅ SISTEMA FUNCIONANDO</h3>";
echo "<p>Todos los componentes principales están funcionando correctamente.</p>";
echo "<p><strong>Próximos pasos:</strong></p>";
echo "<ul>";
echo "<li>1. Configura el webhook en Facebook Developers</li>";
echo "<li>2. Ajusta la configuración en el panel</li>";
echo "<li>3. Personaliza los menús según tus necesidades</li>";
echo "<li>4. ¡Comienza a recibir mensajes!</li>";
echo "</ul>";
echo "</div>";
} else {
echo "<div style='background: #f8d7da; color: #721c24; padding: 15px; border-radius: 5px; border-left: 5px solid #dc3545;'>";
echo "<h3>❌ HAY PROBLEMAS</h3>";
echo "<p>Algunos componentes no están funcionando correctamente. Revisa los errores arriba.</p>";
echo "</div>";
}
echo "<hr>";
echo "<p><small>Test ejecutado el: " . date('Y-m-d H:i:s') . "</small></p>";
?>