438 lines
20 KiB
PHP
438 lines
20 KiB
PHP
<?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>
|