'WhatsApp Bot', // ── Laboratorio (Oleada 0) ─────────────────────────────────────────────── 'lab_dashboard' => 'Dashboard Lab', 'lab_ordenes' => 'Órdenes Médicas', 'lab_pacientes' => 'Pacientes', 'lab_domicilios' => 'Domicilios', 'lab_enfermeras' => 'Enfermeras', 'lab_formularios' => 'Formularios', 'lab_reportes' => 'Reportes', 'lab_configuracion' => 'Configuración Lab', // ── Sistema ────────────────────────────────────────────────────────────── 'usuarios' => 'Gestión de Usuarios', 'enfermero_portal' => 'Portal Enfermero', // ── Oleada 1 — Turnero ─────────────────────────────────────────────────── 'turnero' => 'Turnero', // ── Oleada 2 — pendiente ───────────────────────────────────────────────── 'registro_exams' => 'Registro de Exámenes', 'facturacion' => 'Facturación', 'inventario' => 'Inventario', 'citas' => 'Citas', 'resultados' => 'Resultados', ]); // loadEnvFile ya fue definida y ejecutada al inicio del archivo. // 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', '2.0.0'); // URL auto-detectada según protocolo y host actuales if (!defined('APP_URL')) { // Detectar HTTPS correctamente detrás de proxy reverso (Coolify, nginx, etc.) $__isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https' || ($_SERVER['HTTP_X_FORWARDED_SSL'] ?? '') === 'on' || ($_SERVER['HTTP_FRONT_END_HTTPS'] ?? '') === 'on'; $__proto = $__isHttps ? 'https' : 'http'; $__host = $_SERVER['HTTP_HOST'] ?? 'localhost'; define('APP_URL', $__proto . '://' . $__host); } // BASE_URL: alias de APP_URL con barra final (usado en vistas de módulos ERP) if (!defined('BASE_URL')) { define('BASE_URL', rtrim(APP_URL, '/') . '/'); } // Token para runners de migración accesibles por web if (!defined('MIGRATION_TOKEN')) { define('MIGRATION_TOKEN', 'lab2026migrate'); } 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', (int)(getenv('SESSION_TIMEOUT') ?: 1800)); // 30 minutos define('MAX_LOGIN_ATTEMPTS', (int)(getenv('MAX_LOGIN_ATTEMPTS') ?: 3)); define('LOGIN_LOCKOUT_TIME', (int)(getenv('LOGIN_LOCKOUT_TIME') ?: 900)); // 15 minutos // Configuración de logs (LOG_LEVEL sobreescribible desde env) define('ENABLE_LOGGING', true); define('LOG_LEVEL', getenv('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']]); // Cargar módulos del rol (slug y permiso) $modules = []; $modulePermissions = []; $roleSlug = $admin['role'] ?? 'admin'; $homePage = null; if (!empty($admin['role_id'])) { $modStmt = $pdo->prepare( "SELECT module_slug, COALESCE(permission,'write') AS permission FROM role_modules WHERE role_id = ?" ); $modStmt->execute([$admin['role_id']]); foreach ($modStmt->fetchAll(PDO::FETCH_ASSOC) as $row) { $modules[] = $row['module_slug']; $modulePermissions[$row['module_slug']] = $row['permission']; } // Leer home_page del rol $rpStmt = $pdo->prepare("SELECT home_page FROM roles WHERE id = ?"); $rpStmt->execute([$admin['role_id']]); $rp = $rpStmt->fetch(PDO::FETCH_ASSOC); $homePage = $rp['home_page'] ?? null; } elseif ($roleSlug === 'admin') { // Fallback: admin sin role_id tiene todos los módulos $modules = array_keys(SYSTEM_MODULES); } elseif ($roleSlug === 'enfermero') { $modules = ['enfermero_portal', 'lab_formularios']; } return [ 'id' => $admin['id'], 'username' => $admin['username'], 'full_name' => $admin['full_name'], 'email' => $admin['email'], 'role' => $roleSlug, 'role_id' => $admin['role_id'] ?? null, 'home_page' => $homePage, 'enfermera_id' => $admin['enfermera_id'] ?? null, 'modules' => $modules, 'module_permissions' => $modulePermissions, ]; } } 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')) { /** * Verifica si el usuario autenticado tiene el rol indicado. */ function userHasRole(string $role): bool { return isset($_SESSION['admin_user']['role']) && $_SESSION['admin_user']['role'] === $role; } /** * Verdadero si el usuario es enfermero. */ function isEnfermero(): bool { return userHasRole('enfermero'); } /** * Devuelve el ID de enfermera del usuario en sesión, o null si no aplica. */ function enfermeraId(): ?int { $id = $_SESSION['admin_user']['enfermera_id'] ?? null; return $id ? (int)$id : null; } /** * Comprueba si el usuario en sesión tiene acceso al módulo indicado. * Los administradores sin modules cargados tienen acceso total (retrocompat.). */ function hasModule(string $slug): bool { $modules = $_SESSION['admin_user']['modules'] ?? null; // Si no hay módulos en sesión (usuario legacy) y es admin → acceso total if ($modules === null) { return userHasRole('admin'); } return in_array($slug, $modules, true); } /** * Comprueba si el usuario tiene permiso de escritura (write) en el módulo. * Los administradores tienen acceso de escritura total. * Un usuario con permission='read' solo puede ver, no modificar. */ function hasModuleWrite(string $slug): bool { // Admins siempre pueden escribir if (userHasRole('admin')) return true; $permissions = $_SESSION['admin_user']['module_permissions'] ?? null; if ($permissions === null) { // Sesión legacy sin 'module_permissions': si tiene el módulo, se asume write return hasModule($slug); } return ($permissions[$slug] ?? '') === 'write'; } /** * Detiene la ejecución si el usuario no tiene el rol requerido. */ function requireRole(string $role): void { if (!isUserLoggedIn()) { header('Location: login.php'); exit; } if (!userHasRole($role)) { http_response_code(403); // Redirigir al portal correcto $destino = userHasRole('enfermero') ? 'enfermero_portal.php' : 'index.php'; header("Location: $destino"); exit; } } 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']); } if (isset($whatsappConfig['terms_message'])) { $success &= saveConfigToDB('terms_message', $whatsappConfig['terms_message']); } if (isset($whatsappConfig['terms_rejected_message'])) { $success &= saveConfigToDB('terms_rejected_message', $whatsappConfig['terms_rejected_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', ''), 'terms_message' => getConfigFromDB('terms_message', ''), 'terms_rejected_message' => getConfigFromDB('terms_rejected_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. Corregir ahora'; } 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; } } ?>