w
This commit is contained in:
@@ -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! 🎉
|
||||
@@ -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)** 🚀
|
||||
@@ -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
@@ -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! 🚀
|
||||
@@ -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
|
||||
@@ -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!** 🚀
|
||||
@@ -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.
|
||||
@@ -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! 🚀
|
||||
@@ -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']);
|
||||
}
|
||||
?>
|
||||
@@ -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']);
|
||||
}
|
||||
?>
|
||||
@@ -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']);
|
||||
}
|
||||
?>
|
||||
@@ -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']);
|
||||
}
|
||||
?>
|
||||
@@ -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']);
|
||||
}
|
||||
?>
|
||||
@@ -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']);
|
||||
}
|
||||
?>
|
||||
@@ -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']);
|
||||
}
|
||||
?>
|
||||
@@ -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']);
|
||||
}
|
||||
?>
|
||||
@@ -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']);
|
||||
}
|
||||
?>
|
||||
@@ -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']);
|
||||
}
|
||||
?>
|
||||
@@ -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()]);
|
||||
}
|
||||
?>
|
||||
@@ -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()]);
|
||||
}
|
||||
?>
|
||||
@@ -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()]);
|
||||
}
|
||||
?>
|
||||
@@ -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
@@ -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']);
|
||||
}
|
||||
?>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -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();
|
||||
}
|
||||
?>
|
||||
@@ -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');
|
||||
@@ -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>
|
||||
@@ -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
@@ -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>
|
||||
@@ -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
@@ -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>
|
||||
@@ -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
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -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)
|
||||
];
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -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>";
|
||||
?>
|
||||
Reference in New Issue
Block a user