898 lines
29 KiB
PHP
898 lines
29 KiB
PHP
<?php
|
|
/**
|
|
* Configuración del sistema WhatsApp Bot Manager
|
|
* Desarrollado por U-Site.app
|
|
* Fecha: 4 de enero de 2026 - Sistema de autenticación unificado
|
|
*/
|
|
|
|
// Configuración de la base de datos - usa variables de entorno si están disponibles
|
|
define('DB_HOST', getenv('DB_HOST') ?: '77.37.126.126');
|
|
define('DB_PORT', getenv('DB_PORT') ?: '3306');
|
|
define('DB_NAME', getenv('DB_NAME') ?: 'usite_whatsapp_bot');
|
|
define('DB_USER', getenv('DB_USER') ?: 'usite_whatsapp_user');
|
|
define('DB_PASS', getenv('DB_PASS') ?: '6q!Vio0fn@PFmsLyHjH5');
|
|
define('DB_CHARSET', 'utf8mb4');
|
|
|
|
// Configuración de autenticación (LEGACY - migrado a BD)
|
|
// NOTA: Esta constante se mantiene para retrocompatibilidad durante la migración
|
|
// El sistema ahora usa autenticación basada en base de datos (admin_users table)
|
|
define('ADMIN_PASSWORD', '$2y$10$IXCY8Sm1xFkfhC6Y67Ahn.QLHxE.sjWfmTEOKFZdCN2a9s9YLVuGW'); // password123
|
|
|
|
// Función para cargar archivo .env
|
|
if (!function_exists('loadEnvFile')) {
|
|
function loadEnvFile($path) {
|
|
if (!file_exists($path)) {
|
|
return false;
|
|
}
|
|
|
|
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
|
foreach ($lines as $line) {
|
|
if (strpos($line, '#') === 0 || strpos($line, '=') === false) {
|
|
continue;
|
|
}
|
|
|
|
[$name, $value] = explode('=', $line, 2);
|
|
$name = trim($name);
|
|
$value = trim($value, " \t\n\r\0\x0B\"'");
|
|
|
|
if (!array_key_exists($name, $_ENV)) {
|
|
$_ENV[$name] = $value;
|
|
putenv("$name=$value");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Cargar .env si existe
|
|
loadEnvFile(__DIR__ . '/../.env');
|
|
|
|
// Cargar autoloader de Composer si existe (solo una vez)
|
|
$composerAutoload = __DIR__ . '/../vendor/autoload.php';
|
|
if (file_exists($composerAutoload)) {
|
|
// Verificar que no se haya cargado ya usando una clase del autoloader
|
|
if (!class_exists('Predis\Client', false)) {
|
|
require_once $composerAutoload;
|
|
}
|
|
}
|
|
|
|
// Función helper para obtener variables de entorno
|
|
if (!function_exists('env')) {
|
|
function env($key, $default = null) {
|
|
return $_ENV[$key] ?? getenv($key) ?: $default;
|
|
}
|
|
}
|
|
|
|
// Incluir clase Database
|
|
require_once __DIR__ . '/../classes/Database.php';
|
|
|
|
// Función para verificar si la instalación está completada
|
|
if (!function_exists('isInstallationCompleted')) {
|
|
function isInstallationCompleted() {
|
|
return file_exists(dirname(__DIR__) . '/.installation_completed');
|
|
}
|
|
}
|
|
|
|
// Función para obtener configuración desde base de datos
|
|
if (!function_exists('getConfigFromDB')) {
|
|
function getConfigFromDB($key, $default = null) {
|
|
static $configs = null;
|
|
global $config_cache_cleared;
|
|
|
|
// Asegurar que la bandera exista y tenga valor booleano
|
|
if (!isset($config_cache_cleared)) {
|
|
$config_cache_cleared = false;
|
|
}
|
|
|
|
if ($configs === null || $config_cache_cleared) {
|
|
$config_cache_cleared = false;
|
|
$configs = [];
|
|
|
|
if (isInstallationCompleted()) {
|
|
try {
|
|
// Usar la función centralizada para crear la conexión
|
|
$pdo = createDbConnection();
|
|
|
|
// Verificar si existe la tabla system_config
|
|
$tableExistsStmt = $pdo->query("SHOW TABLES LIKE 'system_config'");
|
|
$tableExists = $tableExistsStmt && $tableExistsStmt->rowCount() > 0;
|
|
|
|
if ($tableExists) {
|
|
// Cargar todas las configuraciones en caché de una sola vez
|
|
$stmt = $pdo->query("SELECT config_key, config_value FROM system_config");
|
|
while ($row = $stmt->fetch()) {
|
|
$configs[$row['config_key']] = $row['config_value'];
|
|
}
|
|
} else {
|
|
// Si no existe la tabla, usar valores por defecto
|
|
$configs = [];
|
|
}
|
|
} catch (PDOException $e) {
|
|
error_log("Error loading config: " . $e->getMessage());
|
|
$configs = [];
|
|
}
|
|
} else {
|
|
$configs = [];
|
|
}
|
|
}
|
|
|
|
return $configs[$key] ?? $default;
|
|
}
|
|
} // end if !function_exists('getConfigFromDB')
|
|
|
|
/**
|
|
* Guarda una configuración en la base de datos
|
|
* @param string $key Clave de configuración
|
|
* @param mixed $value Valor de configuración
|
|
* @param string $description Descripción opcional
|
|
* @return bool True si se guardó correctamente
|
|
*/
|
|
function saveConfigToDB($key, $value, $description = null) {
|
|
try {
|
|
if (!isInstallationCompleted()) {
|
|
return false;
|
|
}
|
|
|
|
$pdo = createDbConnection();
|
|
|
|
// Crear tabla si no existe
|
|
createSystemConfigTable($pdo);
|
|
|
|
// Verificar si ya existe la configuración
|
|
$stmt = $pdo->prepare("SELECT id FROM system_config WHERE config_key = ?");
|
|
$stmt->execute([$key]);
|
|
$exists = $stmt->fetch();
|
|
|
|
if ($exists) {
|
|
// Actualizar configuración existente
|
|
$sql = "UPDATE system_config SET config_value = ?, updated_at = NOW()";
|
|
$params = [$value];
|
|
|
|
if ($description !== null) {
|
|
$sql .= ", description = ?";
|
|
$params[] = $description;
|
|
}
|
|
|
|
$sql .= " WHERE config_key = ?";
|
|
$params[] = $key;
|
|
|
|
$stmt = $pdo->prepare($sql);
|
|
$result = $stmt->execute($params);
|
|
} else {
|
|
// Insertar nueva configuración
|
|
$stmt = $pdo->prepare("
|
|
INSERT INTO system_config (config_key, config_value, description, created_at, updated_at)
|
|
VALUES (?, ?, ?, NOW(), NOW())
|
|
");
|
|
$result = $stmt->execute([$key, $value, $description]);
|
|
}
|
|
|
|
// Limpiar cache estático
|
|
if ($result) {
|
|
clearConfigCache();
|
|
}
|
|
|
|
return $result;
|
|
|
|
} catch (PDOException $e) {
|
|
error_log("Error guardando configuración '$key': " . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Guarda múltiples configuraciones en una sola transacción
|
|
* @param array $configs Array asociativo [key => value]
|
|
* @return bool True si todas se guardaron correctamente
|
|
*/
|
|
function saveMultipleConfigsToDB($configs) {
|
|
try {
|
|
if (!isInstallationCompleted() || empty($configs)) {
|
|
return false;
|
|
}
|
|
|
|
$pdo = createDbConnection();
|
|
$pdo->beginTransaction();
|
|
|
|
// Crear tabla si no existe
|
|
createSystemConfigTable($pdo);
|
|
|
|
$success = true;
|
|
foreach ($configs as $key => $value) {
|
|
// Verificar si ya existe
|
|
$stmt = $pdo->prepare("SELECT id FROM system_config WHERE config_key = ?");
|
|
$stmt->execute([$key]);
|
|
$exists = $stmt->fetch();
|
|
|
|
if ($exists) {
|
|
// Actualizar
|
|
$stmt = $pdo->prepare("UPDATE system_config SET config_value = ?, updated_at = NOW() WHERE config_key = ?");
|
|
$result = $stmt->execute([$value, $key]);
|
|
} else {
|
|
// Insertar
|
|
$stmt = $pdo->prepare("
|
|
INSERT INTO system_config (config_key, config_value, created_at, updated_at)
|
|
VALUES (?, ?, NOW(), NOW())
|
|
");
|
|
$result = $stmt->execute([$key, $value]);
|
|
}
|
|
|
|
if (!$result) {
|
|
$success = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($success) {
|
|
$pdo->commit();
|
|
clearConfigCache();
|
|
return true;
|
|
} else {
|
|
$pdo->rollback();
|
|
return false;
|
|
}
|
|
|
|
} catch (PDOException $e) {
|
|
if (isset($pdo) && $pdo->inTransaction()) {
|
|
$pdo->rollback();
|
|
}
|
|
error_log("Error guardando configuraciones múltiples: " . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtiene todas las configuraciones de la base de datos
|
|
* @return array Array asociativo con todas las configuraciones
|
|
*/
|
|
function getAllConfigsFromDB() {
|
|
try {
|
|
if (!isInstallationCompleted()) {
|
|
return [];
|
|
}
|
|
|
|
$pdo = createDbConnection();
|
|
|
|
// Verificar si existe la tabla
|
|
$tableExists = $pdo->query("SHOW TABLES LIKE 'system_config'")->rowCount() > 0;
|
|
|
|
if (!$tableExists) {
|
|
return [];
|
|
}
|
|
|
|
$stmt = $pdo->query("
|
|
SELECT config_key, config_value, description, created_at, updated_at
|
|
FROM system_config
|
|
ORDER BY config_key
|
|
");
|
|
|
|
$configs = [];
|
|
while ($row = $stmt->fetch()) {
|
|
$configs[$row['config_key']] = [
|
|
'value' => $row['config_value'],
|
|
'description' => $row['description'],
|
|
'created_at' => $row['created_at'],
|
|
'updated_at' => $row['updated_at']
|
|
];
|
|
}
|
|
|
|
return $configs;
|
|
|
|
} catch (PDOException $e) {
|
|
error_log("Error obteniendo todas las configuraciones: " . $e->getMessage());
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Elimina una configuración de la base de datos
|
|
* @param string $key Clave de configuración a eliminar
|
|
* @return bool True si se eliminó correctamente
|
|
*/
|
|
function deleteConfigFromDB($key) {
|
|
try {
|
|
if (!isInstallationCompleted()) {
|
|
return false;
|
|
}
|
|
|
|
$pdo = createDbConnection();
|
|
$stmt = $pdo->prepare("DELETE FROM system_config WHERE config_key = ?");
|
|
$result = $stmt->execute([$key]);
|
|
|
|
if ($result) {
|
|
clearConfigCache();
|
|
}
|
|
|
|
return $result;
|
|
|
|
} catch (PDOException $e) {
|
|
error_log("Error eliminando configuración '$key': " . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Limpia el cache estático de configuraciones
|
|
*/
|
|
function clearConfigCache() {
|
|
// Necesitamos acceder a la variable estática de getConfigFromDB
|
|
// Como no podemos modificar directamente variables estáticas de otra función,
|
|
// establecemos una flag para que getConfigFromDB reinicie el cache
|
|
global $config_cache_cleared;
|
|
$config_cache_cleared = true;
|
|
}
|
|
|
|
/**
|
|
* Crea la tabla system_config si no existe
|
|
* @param PDO $pdo Conexión a la base de datos
|
|
* @return bool True si se creó o ya existía
|
|
*/
|
|
function createSystemConfigTable($pdo) {
|
|
try {
|
|
$sql = "CREATE TABLE IF NOT EXISTS system_config (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
config_key VARCHAR(255) NOT NULL UNIQUE,
|
|
config_value TEXT,
|
|
description TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
INDEX idx_config_key (config_key)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
|
|
|
|
return $pdo->exec($sql) !== false;
|
|
|
|
} catch (PDOException $e) {
|
|
error_log("Error creando tabla system_config: " . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// WhatsApp Business API (prioridad: BD > .env > default)
|
|
define('WHATSAPP_TOKEN', getConfigFromDB('whatsapp_token', env('WHATSAPP_TOKEN', 'TU_TOKEN_DE_WHATSAPP_AQUI')));
|
|
define('WHATSAPP_PHONE_NUMBER_ID', getConfigFromDB('whatsapp_phone_number_id', env('WHATSAPP_PHONE_NUMBER_ID', 'TU_PHONE_ID_AQUI')));
|
|
define('WHATSAPP_API_URL', getConfigFromDB('whatsapp_api_url', env('WHATSAPP_API_URL', 'https://graph.facebook.com/v22.0/')));
|
|
define('WEBHOOK_VERIFY_TOKEN', getConfigFromDB('webhook_verify_token', env('WEBHOOK_VERIFY_TOKEN', 'mi_token_secreto_123')));
|
|
|
|
// Configuración general
|
|
define('APP_NAME', 'WhatsApp Bot System');
|
|
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('SECRET_KEY', 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
|
|
define('ADMIN_USERNAME', 'admin'); // Usuario administrador por defecto
|
|
// ADMIN_PASSWORD movido arriba como parte del sistema unificado (línea 18)
|
|
define('SESSION_TIMEOUT', 1800); // 30 minutos
|
|
define('MAX_LOGIN_ATTEMPTS', 3); // Intentos máximos de login
|
|
define('LOGIN_LOCKOUT_TIME', 900); // 15 minutos de bloqueo tras fallos
|
|
|
|
// 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);
|
|
}
|
|
|
|
|
|
// ========================================
|
|
// SISTEMA DE AUTENTICACIÓN UNIFICADO
|
|
// ========================================
|
|
|
|
/**
|
|
* Crea conexión a la base de datos
|
|
* @return PDO
|
|
*/
|
|
function createDbConnection() {
|
|
try {
|
|
$pdo = new PDO(
|
|
"mysql:host=" . DB_HOST . ";port=" . DB_PORT . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET,
|
|
DB_USER,
|
|
DB_PASS,
|
|
[
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
PDO::ATTR_EMULATE_PREPARES => false,
|
|
]
|
|
);
|
|
return $pdo;
|
|
} catch (PDOException $e) {
|
|
error_log("Error de conexión BD: " . $e->getMessage());
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Autentica un usuario usando la base de datos
|
|
* @param string $username
|
|
* @param string $password
|
|
* @return array|false Datos del usuario o false si falla
|
|
*/
|
|
function authenticateUser($username, $password) {
|
|
try {
|
|
$pdo = createDbConnection();
|
|
|
|
// Primero verificar si existe la tabla admin_users
|
|
$checkTable = $pdo->query("SHOW TABLES LIKE 'admin_users'");
|
|
$tableExists = $checkTable->rowCount() > 0;
|
|
|
|
if ($tableExists) {
|
|
// Sistema moderno: autenticación por BD
|
|
$stmt = $pdo->prepare("SELECT * FROM admin_users WHERE username = ? AND is_active = 1");
|
|
$stmt->execute([$username]);
|
|
$admin = $stmt->fetch();
|
|
|
|
if ($admin && password_verify($password, $admin['password_hash'])) {
|
|
// Actualizar último login
|
|
$updateLogin = $pdo->prepare("UPDATE admin_users SET last_login = NOW() WHERE id = ?");
|
|
$updateLogin->execute([$admin['id']]);
|
|
|
|
return [
|
|
'id' => $admin['id'],
|
|
'username' => $admin['username'],
|
|
'full_name' => $admin['full_name'],
|
|
'email' => $admin['email']
|
|
];
|
|
}
|
|
} else {
|
|
// Sistema legacy: usar constante ADMIN_PASSWORD
|
|
if ($username === 'admin' && password_verify($password, ADMIN_PASSWORD)) {
|
|
return [
|
|
'id' => 1,
|
|
'username' => 'admin',
|
|
'full_name' => 'Administrador',
|
|
'email' => 'admin@sistema.local'
|
|
];
|
|
}
|
|
}
|
|
} catch (Exception $e) {
|
|
error_log("Error en autenticación: " . $e->getMessage());
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Verifica si el sistema está migrado a BD
|
|
* @return bool
|
|
*/
|
|
function isAuthSystemMigrated() {
|
|
try {
|
|
$pdo = createDbConnection();
|
|
$checkTable = $pdo->query("SHOW TABLES LIKE 'admin_users'");
|
|
return $checkTable->rowCount() > 0;
|
|
} catch (Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtiene todos los administradores del sistema
|
|
* @return array
|
|
*/
|
|
function getAdminUsers() {
|
|
try {
|
|
$pdo = createDbConnection();
|
|
|
|
if (isAuthSystemMigrated()) {
|
|
$stmt = $pdo->query("SELECT * FROM admin_users ORDER BY created_at DESC");
|
|
return $stmt->fetchAll();
|
|
} else {
|
|
// Sistema legacy: retornar admin por defecto
|
|
return [[
|
|
'id' => 1,
|
|
'username' => 'admin',
|
|
'full_name' => 'Administrador',
|
|
'email' => 'admin@sistema.local',
|
|
'is_active' => 1,
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
'last_login' => null
|
|
]];
|
|
}
|
|
} catch (Exception $e) {
|
|
error_log("Error obteniendo usuarios admin: " . $e->getMessage());
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Función para verificar login
|
|
if (!function_exists('isUserLoggedIn')) {
|
|
function isUserLoggedIn() {
|
|
return isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in'] === true;
|
|
}
|
|
}
|
|
|
|
// Función para verificar intentos de login
|
|
if (!function_exists('checkLoginAttempts')) {
|
|
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
|
|
if (!function_exists('recordFailedLogin')) {
|
|
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
|
|
if (!function_exists('clearLoginAttempts')) {
|
|
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;
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
* Guarda la configuración completa de WhatsApp
|
|
* @param array $whatsappConfig Array con las configuraciones de WhatsApp
|
|
* @return bool True si se guardó correctamente
|
|
*/
|
|
function saveWhatsAppConfigToDB($whatsappConfig) {
|
|
$success = true;
|
|
|
|
// Mapear configuraciones de WhatsApp y guardar individualmente
|
|
if (isset($whatsappConfig['token'])) {
|
|
$success &= saveConfigToDB('whatsapp_token', $whatsappConfig['token']);
|
|
}
|
|
|
|
if (isset($whatsappConfig['phone_number_id'])) {
|
|
$success &= saveConfigToDB('whatsapp_phone_number_id', $whatsappConfig['phone_number_id']);
|
|
}
|
|
|
|
if (isset($whatsappConfig['business_account_id'])) {
|
|
$success &= saveConfigToDB('whatsapp_business_account_id', $whatsappConfig['business_account_id']);
|
|
}
|
|
|
|
if (isset($whatsappConfig['api_url'])) {
|
|
$success &= saveConfigToDB('whatsapp_api_url', $whatsappConfig['api_url']);
|
|
}
|
|
|
|
if (isset($whatsappConfig['webhook_verify_token'])) {
|
|
$success &= saveConfigToDB('webhook_verify_token', $whatsappConfig['webhook_verify_token']);
|
|
}
|
|
|
|
if (isset($whatsappConfig['business_name'])) {
|
|
$success &= saveConfigToDB('business_name', $whatsappConfig['business_name']);
|
|
}
|
|
|
|
if (isset($whatsappConfig['welcome_message'])) {
|
|
$success &= saveConfigToDB('welcome_message', $whatsappConfig['welcome_message']);
|
|
}
|
|
|
|
return $success;
|
|
}
|
|
|
|
/**
|
|
* Obtiene toda la configuración de WhatsApp desde BD
|
|
* @return array Configuración completa de WhatsApp
|
|
*/
|
|
function getWhatsAppConfigFromDB() {
|
|
// Usar constantes como fallback (ya tienen cascada BD > env > default)
|
|
$token = getConfigFromDB('whatsapp_token', '');
|
|
if (empty($token) && defined('WHATSAPP_TOKEN')) $token = WHATSAPP_TOKEN;
|
|
if (empty($token)) $token = getenv('WHATSAPP_TOKEN') ?: '';
|
|
|
|
$phoneId = getConfigFromDB('whatsapp_phone_number_id', '');
|
|
if (empty($phoneId) && defined('WHATSAPP_PHONE_NUMBER_ID')) $phoneId = WHATSAPP_PHONE_NUMBER_ID;
|
|
if (empty($phoneId)) $phoneId = getenv('WHATSAPP_PHONE_NUMBER_ID') ?: '';
|
|
|
|
$apiUrl = getConfigFromDB('whatsapp_api_url', '');
|
|
if (empty($apiUrl) && defined('WHATSAPP_API_URL')) $apiUrl = WHATSAPP_API_URL;
|
|
if (empty($apiUrl)) $apiUrl = 'https://graph.facebook.com/v22.0/';
|
|
|
|
$webhookToken = getConfigFromDB('webhook_verify_token', '');
|
|
if (empty($webhookToken) && defined('WEBHOOK_VERIFY_TOKEN')) $webhookToken = WEBHOOK_VERIFY_TOKEN;
|
|
|
|
return [
|
|
// Mapear a nombres esperados por el código
|
|
'token' => $token,
|
|
'whatsapp_token' => $token,
|
|
'phone_number_id' => $phoneId,
|
|
'business_account_id' => getConfigFromDB('whatsapp_business_account_id', ''),
|
|
'api_url' => $apiUrl,
|
|
'whatsapp_api_url' => $apiUrl,
|
|
'webhook_verify_token' => $webhookToken,
|
|
'business_name' => getConfigFromDB('business_name', ''),
|
|
'welcome_message' => getConfigFromDB('welcome_message', ''),
|
|
'status' => validateWhatsAppConfigFromDB()
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Valida la configuración de WhatsApp desde BD
|
|
* @return array Estado de validación
|
|
*/
|
|
function validateWhatsAppConfigFromDB() {
|
|
$token = getConfigFromDB('whatsapp_token', '');
|
|
$phoneId = getConfigFromDB('whatsapp_phone_number_id', '');
|
|
$webhookToken = getConfigFromDB('webhook_verify_token', '');
|
|
|
|
$errors = [];
|
|
$warnings = [];
|
|
|
|
if (empty($token) || $token === 'TU_TOKEN_DE_WHATSAPP_AQUI') {
|
|
$errors[] = 'Token de WhatsApp no configurado';
|
|
}
|
|
|
|
if (empty($phoneId) || $phoneId === 'TU_PHONE_ID_AQUI') {
|
|
$errors[] = 'Phone Number ID no configurado';
|
|
} elseif ($phoneId === '858157464051987') {
|
|
$warnings[] = 'Phone Number ID problemático detectado (858157464051987)';
|
|
}
|
|
|
|
if (empty($webhookToken)) {
|
|
$errors[] = 'Token de verificación del webhook no configurado';
|
|
}
|
|
|
|
return [
|
|
'valid' => empty($errors),
|
|
'errors' => $errors,
|
|
'warnings' => $warnings,
|
|
'configured_fields' => [
|
|
'token' => !empty($token) && $token !== 'TU_TOKEN_DE_WHATSAPP_AQUI',
|
|
'phone_id' => !empty($phoneId) && $phoneId !== 'TU_PHONE_ID_AQUI',
|
|
'webhook_token' => !empty($webhookToken)
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Inicializa configuraciones por defecto en la base de datos
|
|
* @return bool True si se inicializaron correctamente
|
|
*/
|
|
function initializeDefaultConfigs() {
|
|
$defaultConfigs = [
|
|
// Configuración de WhatsApp
|
|
'whatsapp_token' => env('WHATSAPP_TOKEN', 'TU_TOKEN_DE_WHATSAPP_AQUI'),
|
|
'whatsapp_phone_number_id' => env('WHATSAPP_PHONE_NUMBER_ID', 'TU_PHONE_ID_AQUI'),
|
|
'whatsapp_api_url' => env('WHATSAPP_API_URL', 'https://graph.facebook.com/v22.0/'),
|
|
'webhook_verify_token' => env('WEBHOOK_VERIFY_TOKEN', 'mi_token_secreto_123'),
|
|
|
|
// Configuración de la aplicación
|
|
'app_name' => 'WhatsApp Bot System',
|
|
'app_version' => '1.0.0',
|
|
'timezone' => 'America/Bogota',
|
|
'developer_name' => 'U-Site.app',
|
|
'developer_url' => 'https://u-site.app',
|
|
'developer_email' => 'support@u-site.app',
|
|
|
|
// Configuración de logs y archivos
|
|
'enable_logging' => '1',
|
|
'log_level' => 'INFO',
|
|
'upload_max_size' => '10485760',
|
|
'allowed_file_types' => 'jpg,jpeg,png,gif,pdf,doc,docx',
|
|
|
|
// Configuración de seguridad
|
|
'session_timeout' => '1800',
|
|
'max_login_attempts' => '3',
|
|
'login_lockout_time' => '900'
|
|
];
|
|
|
|
return saveMultipleConfigsToDB($defaultConfigs);
|
|
}
|
|
|
|
/**
|
|
* Migra configuraciones desde constantes/archivos a la base de datos
|
|
* @return bool True si la migración fue exitosa
|
|
*/
|
|
function migrateConfigsToDatabase() {
|
|
try {
|
|
writeLog('INFO', 'Iniciando migración de configuraciones a BD');
|
|
|
|
// Crear tabla si no existe
|
|
$pdo = createDbConnection();
|
|
if (!createSystemConfigTable($pdo)) {
|
|
writeLog('ERROR', 'No se pudo crear la tabla system_config');
|
|
return false;
|
|
}
|
|
|
|
// Migrar configuraciones actuales
|
|
$success = initializeDefaultConfigs();
|
|
|
|
if ($success) {
|
|
writeLog('INFO', 'Migración de configuraciones completada exitosamente');
|
|
} else {
|
|
writeLog('ERROR', 'Error durante la migración de configuraciones');
|
|
}
|
|
|
|
return $success;
|
|
|
|
} catch (Exception $e) {
|
|
writeLog('ERROR', 'Error crítico en migración de configuraciones: ' . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Función para validar configuración de WhatsApp
|
|
function validateWhatsAppConfig() {
|
|
$errors = [];
|
|
|
|
if (WHATSAPP_TOKEN === 'TU_TOKEN_DE_WHATSAPP_AQUI' || empty(WHATSAPP_TOKEN)) {
|
|
$errors[] = 'Token de WhatsApp no configurado';
|
|
}
|
|
|
|
if (WHATSAPP_PHONE_NUMBER_ID === 'TU_PHONE_ID_AQUI' || empty(WHATSAPP_PHONE_NUMBER_ID)) {
|
|
$errors[] = 'Phone Number ID de WhatsApp no configurado';
|
|
}
|
|
|
|
// Detectar Phone Number ID problemático conocido
|
|
if (WHATSAPP_PHONE_NUMBER_ID === '858157464051987') {
|
|
$errors[] = '⚠️ PHONE NUMBER ID PROBLEMÁTICO: El ID 858157464051987 genera errores. <a href="fix_phone_number_id.php" class="alert-link"><strong>Corregir ahora</strong></a>';
|
|
}
|
|
|
|
if (empty(WEBHOOK_VERIFY_TOKEN)) {
|
|
$errors[] = 'Token de verificación del webhook no configurado';
|
|
}
|
|
|
|
return $errors;
|
|
}
|
|
|
|
// Función para logging centralizado
|
|
if (!function_exists('writeLog')) {
|
|
function writeLog($level, $message, $context = []) {
|
|
if (!ENABLE_LOGGING) return;
|
|
|
|
$logLevels = ['DEBUG' => 0, 'INFO' => 1, 'WARNING' => 2, 'ERROR' => 3];
|
|
$currentLevel = $logLevels[LOG_LEVEL] ?? 1;
|
|
|
|
if (($logLevels[$level] ?? 1) < $currentLevel) {
|
|
return;
|
|
}
|
|
|
|
$timestamp = date('Y-m-d H:i:s');
|
|
$contextStr = $context ? ' ' . json_encode($context) : '';
|
|
$logMessage = "[$timestamp] [$level] $message$contextStr" . PHP_EOL;
|
|
|
|
$logDir = dirname(__DIR__) . '/logs';
|
|
if (!is_dir($logDir)) {
|
|
mkdir($logDir, 0755, true);
|
|
}
|
|
|
|
file_put_contents($logDir . '/system.log', $logMessage, FILE_APPEND | LOCK_EX);
|
|
}
|
|
}
|
|
|
|
// Crear directorios necesarios
|
|
$requiredDirs = [
|
|
dirname(__DIR__) . '/logs',
|
|
dirname(__DIR__) . '/uploads'
|
|
];
|
|
|
|
foreach ($requiredDirs as $dir) {
|
|
if (!is_dir($dir)) {
|
|
mkdir($dir, 0755, true);
|
|
}
|
|
}
|
|
|
|
// Inicializar sesión si no está activa
|
|
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();
|
|
}
|
|
|
|
// Función para verificar autenticación en APIs
|
|
function requireAuthentication() {
|
|
if (!isUserLoggedIn()) {
|
|
http_response_code(401);
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
echo json_encode([
|
|
'success' => false,
|
|
'error' => 'No autorizado. Inicie sesión.',
|
|
'redirect' => 'login.php'
|
|
]);
|
|
exit;
|
|
}
|
|
}
|
|
?>
|