- - +
- - +
- - +
-
@@ -95,7 +95,7 @@ get_header();
if ( ! empty( $categories ) ) :
?>
- +'); - } -} - -/** - * EXAMPLE 19: Get all options (for debugging) - */ -function example_debug_all_options() { - if (current_user_can('manage_options') && isset($_GET['debug_options'])) { - $all_options = apus_get_all_options(); - echo '
-
diff --git a/admin/ANALISIS-ESTRUCTURA-ADMIN.md b/admin/ANALISIS-ESTRUCTURA-ADMIN.md
deleted file mode 100644
index 4b2605ab..00000000
--- a/admin/ANALISIS-ESTRUCTURA-ADMIN.md
+++ /dev/null
@@ -1,1088 +0,0 @@
-# Análisis Profundo: Carpeta `admin/`
-
-**Fecha:** 2025-01-14
-**Versión del Sistema:** 2.1.4
-**Autor:** Análisis con Serena MCP + Claude Code
-
----
-
-## 📋 Índice
-
-1. [Resumen Ejecutivo](#resumen-ejecutivo)
-2. [Estructura de Carpetas](#estructura-de-carpetas)
-3. [Arquitectura del Sistema](#arquitectura-del-sistema)
-4. [Componentes Principales](#componentes-principales)
-5. [Flujo de Datos](#flujo-de-datos)
-6. [Sistema de Base de Datos](#sistema-de-base-de-datos)
-7. [Sistema de Migración](#sistema-de-migración)
-8. [Sistema de Sanitización](#sistema-de-sanitización)
-9. [Frontend del Admin Panel](#frontend-del-admin-panel)
-10. [Problemas Identificados](#problemas-identificados)
-11. [Recomendaciones](#recomendaciones)
-
----
-
-## 🎯 Resumen Ejecutivo
-
-La carpeta `admin/` contiene **DOS sistemas administrativos consolidados**:
-
-1. **Admin Panel** (Sistema Modular) - Panel de configuración por componentes del tema
-2. **Theme Options** (Sistema Legacy) - Opciones generales del tema
-
-Ambos sistemas conviven en la misma carpeta pero funcionan de manera independiente, compartiendo:
-- Assets (CSS/JS)
-- Infraestructura de base de datos
-- Sistema de sanitización
-
-**Total de archivos:** 29 archivos PHP + 6 assets (CSS/JS)
-
----
-
-## 📁 Estructura de Carpetas
-
-```
-admin/
-├── assets/ # Assets consolidados (Admin Panel + Theme Options)
-│ ├── css/
-│ │ ├── admin-panel.css # Estilos core del panel modular
-│ │ ├── component-navbar.css # Estilos del componente Navbar
-│ │ └── theme-options.css # Estilos de Theme Options (legacy)
-│ └── js/
-│ ├── admin-app.js # JavaScript core del panel modular
-│ ├── component-navbar.js # JavaScript del componente Navbar
-│ └── theme-options.js # JavaScript de Theme Options (legacy)
-│
-├── components/ # Componentes PHP del Admin Panel
-│ ├── component-top-bar.php # [ACTIVO] Formulario Top Bar (16KB, 475 líneas)
-│ ├── component-navbar.php # [INACTIVO] Formulario Navbar (31KB, 615 líneas)
-│ ├── component-lets-talk-button.php # [INACTIVO] Formulario Let's Talk (23KB)
-│ └── component-hero-section.php # [INACTIVO] Formulario Hero (36KB)
-│
-├── includes/ # Clases PHP del sistema
-│ ├── class-admin-menu.php # Registro de menú y carga de assets
-│ ├── class-db-manager.php # Gestor de base de datos (wp_apus_theme_components)
-│ ├── class-data-migrator.php # Migrador de datos (legacy → nueva estructura)
-│ ├── class-settings-manager.php # Gestor de configuraciones (AJAX handlers)
-│ ├── class-theme-options-migrator.php # Migrador de Theme Options
-│ ├── class-validator.php # Validador de datos
-│ └── sanitizers/ # Sanitizadores (Strategy Pattern)
-│ ├── class-sanitizer-helper.php # Helper DRY con métodos estáticos
-│ ├── class-topbar-sanitizer.php # Sanitizador Top Bar
-│ ├── class-navbar-sanitizer.php # Sanitizador Navbar
-│ ├── class-letstalkbutton-sanitizer.php # Sanitizador Let's Talk
-│ └── class-herosection-sanitizer.php # Sanitizador Hero Section
-│
-├── pages/ # Páginas del Admin Panel
-│ ├── main.php # Página principal (solo Tab Top Bar activo)
-│ └── migration.php # Página de migración de Theme Options
-│
-├── theme-options/ # Sistema Theme Options (Legacy)
-│ ├── options-api.php # API de opciones
-│ ├── options-page-template.php # Template HTML de la página
-│ ├── related-posts-options.php # Opciones de posts relacionados
-│ ├── theme-options.php # Registro de menú y handlers AJAX
-│ └── USAGE-EXAMPLES.php # Ejemplos de uso
-│
-└── init.php # Punto de entrada del módulo
-```
-
----
-
-## 🏗️ Arquitectura del Sistema
-
-### Patrón de Diseño
-
-El sistema utiliza varios patrones:
-
-1. **Singleton implícito**: `APUS_DB_Manager` se instancia una sola vez
-2. **Strategy Pattern**: Sanitizadores específicos por componente
-3. **Repository Pattern**: `APUS_DB_Manager` abstrae el acceso a datos
-4. **Front Controller**: `init.php` carga todas las dependencias
-5. **MVC parcial**: Separación vista (components) / lógica (includes)
-
-### Diagrama de Dependencias
-
-```
-init.php
-├── Constantes
-│ ├── APUS_ADMIN_PANEL_VERSION = '2.1.4'
-│ ├── APUS_ADMIN_PANEL_PATH = '/admin/'
-│ └── APUS_ADMIN_PANEL_URL = '/admin/'
-│
-├── Clases Core (orden de carga)
-│ ├── 1. APUS_Admin_Menu
-│ ├── 2. APUS_DB_Manager
-│ ├── 3. APUS_Data_Migrator
-│ ├── 4. APUS_Validator
-│ ├── 5. APUS_Theme_Options_Migrator
-│ ├── 6. APUS_Sanitizer_Helper
-│ ├── 7. Sanitizadores (TopBar, Navbar, LetsTalk, Hero)
-│ └── 8. APUS_Settings_Manager
-│
-└── Hooks WordPress
- └── admin_init
- ├── APUS_Data_Migrator::maybe_migrate()
- └── APUS_Theme_Options_Migrator::migrate()
-```
-
----
-
-## 🧩 Componentes Principales
-
-### 1. `APUS_Admin_Menu` (class-admin-menu.php)
-
-**Responsabilidad:** Registro del menú admin y carga de assets
-
-**Métodos:**
-- `__construct()` - Registra hooks
-- `add_menu_page()` - Registra "Tema APUs" en menú Apariencia
-- `render_admin_page()` - Renderiza `pages/main.php`
-- `enqueue_assets($hook)` - Carga CSS/JS solo en página del panel
-
-**Assets cargados:**
-```php
-// CSS
-- Bootstrap 5.3.2
-- Bootstrap Icons 1.11.1
-- admin-panel.css
-- component-navbar.css
-
-// JS
-- Bootstrap 5.3.2
-- Axios 1.6.0
-- component-navbar.js
-- admin-app.js (depende de jQuery, Axios, component-navbar.js)
-```
-
-**Ubicación en WordPress:** `Apariencia > Tema APUs`
-
----
-
-### 2. `APUS_DB_Manager` (class-db-manager.php)
-
-**Responsabilidad:** Gestión de la tabla personalizada `wp_apus_theme_components`
-
-**Estructura de tabla:**
-```sql
-CREATE TABLE wp_apus_theme_components (
- id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
- component_name VARCHAR(50) NOT NULL, -- Namespace (topbar, navbar, theme, etc.)
- config_key VARCHAR(100) NOT NULL, -- Clave de configuración
- config_value TEXT NOT NULL, -- Valor (serializado si es complejo)
- data_type ENUM('string','integer','boolean','array','json') DEFAULT 'string',
- version VARCHAR(20), -- Versión que guardó el dato
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- UNIQUE KEY unique_config (component_name, config_key),
- INDEX idx_component (component_name),
- INDEX idx_updated (updated_at)
-)
-```
-
-**Métodos principales:**
-
-| Método | Descripción | Uso |
-|--------|-------------|-----|
-| `save_config($component, $key, $value, $type, $version)` | Guarda/actualiza configuración | INSERT...ON DUPLICATE KEY |
-| `get_config($component, $key = null)` | Obtiene configuración completa o específica | SELECT WHERE component_name |
-| `parse_value($value, $type)` | Deserializa valores según tipo | Convierte string → tipo nativo |
-| `delete_config($component, $key = null)` | Elimina configuración | DELETE WHERE |
-| `list_components()` | Lista todos los componentes | SELECT DISTINCT component_name |
-
-**Ejemplo de uso:**
-```php
-$db = new APUS_DB_Manager();
-
-// Guardar
-$db->save_config('topbar', 'enabled', true, 'boolean', '2.1.4');
-$db->save_config('topbar', 'bg_color', '#FF8600', 'string', '2.1.4');
-
-// Obtener
-$config = $db->get_config('topbar'); // Array con todas las configs de topbar
-$enabled = $db->get_config('topbar', 'enabled'); // Solo el valor de 'enabled'
-```
-
----
-
-### 3. `APUS_Settings_Manager` (class-settings-manager.php)
-
-**Responsabilidad:** Gestión de configuraciones con AJAX + sanitización
-
-**Constante:**
-```php
-const OPTION_NAME = 'apus_admin_panel_settings'; // NOTA: No usado actualmente
-```
-
-**Métodos:**
-
-| Método | Hook AJAX | Acción |
-|--------|-----------|--------|
-| `get_settings()` | - | Obtiene configuraciones desde DB |
-| `save_settings($data)` | - | Sanitiza y guarda en DB |
-| `get_defaults()` | - | Retorna valores por defecto |
-| `sanitize_settings($data)` | - | Aplica sanitizadores por componente |
-| `ajax_get_settings()` | `wp_ajax_apus_get_settings` | Endpoint GET |
-| `ajax_save_settings()` | `wp_ajax_apus_save_settings` | Endpoint POST |
-
-**Flujo de sanitización:**
-```php
-sanitize_settings($data) {
- // 1. Detectar componente
- $component = $data['component'] ?? '';
-
- // 2. Aplicar sanitizador específico (Strategy Pattern)
- switch ($component) {
- case 'topbar':
- return APUS_TopBar_Sanitizer::sanitize($data);
- case 'navbar':
- return APUS_Navbar_Sanitizer::sanitize($data);
- case 'letstalkbutton':
- return APUS_LetsTalkButton_Sanitizer::sanitize($data);
- case 'herosection':
- return APUS_HeroSection_Sanitizer::sanitize($data);
- default:
- return array();
- }
-}
-```
-
----
-
-### 4. `APUS_Theme_Options_Migrator` (class-theme-options-migrator.php)
-
-**Responsabilidad:** Migrar Theme Options de `wp_options` → `wp_apus_theme_components`
-
-**Constantes:**
-```php
-const OLD_OPTION_NAME = 'apus_theme_options'; // Ubicación antigua
-const COMPONENT_NAME = 'theme'; // Namespace en nueva tabla
-```
-
-**Métodos principales:**
-
-| Método | Descripción |
-|--------|-------------|
-| `is_migrated()` | Verifica si ya se migró |
-| `migrate()` | Ejecuta migración completa |
-| `create_backup($options)` | Guarda backup JSON en wp_options |
-| `rollback($backup_id)` | Restaura desde backup |
-| `list_backups()` | Lista backups disponibles |
-| `get_migration_stats()` | Estadísticas de migración |
-
-**Flujo de migración:**
-```
-1. Verificar si ya migró → is_migrated()
-2. Leer wp_options['apus_theme_options']
-3. Crear backup → create_backup()
-4. Iterar cada opción:
- - Determinar tipo de dato → determine_data_type()
- - Normalizar valor → normalize_value()
- - Guardar en wp_apus_theme_components → save_config()
-5. Eliminar de wp_options
-6. Marcar como migrado
-```
-
-**Tipos de datos mapeados:**
-
-```php
-get_data_types_map() {
- return [
- // 8 integers
- 'site_logo' => 'integer',
- 'site_favicon' => 'integer',
- 'excerpt_length' => 'integer',
- 'related_posts_count' => 'integer',
- 'cta_icon_attachment_id' => 'integer',
- 'cta_box_icon_attachment_id' => 'integer',
- 'footer_logo_id' => 'integer',
- 'featured_image_height' => 'integer',
-
- // 17 booleans
- 'enable_breadcrumbs' => 'boolean',
- 'show_featured_image_single' => 'boolean',
- 'enable_toc' => 'boolean',
- // ... (14 más)
-
- // 11 strings (default, no listados)
- // site_name, site_tagline, copyright_text, etc.
- ];
-}
-```
-
----
-
-### 5. Sistema de Sanitización (sanitizers/)
-
-**Patrón Strategy:** Cada componente tiene su propio sanitizador
-
-#### `APUS_Sanitizer_Helper` (Helper estático DRY)
-
-**Métodos reutilizables:**
-
-```php
-// Individuales
-sanitize_boolean($data, $key)
-sanitize_text($data, $key, $default = '')
-sanitize_color($data, $key, $default = '')
-sanitize_enum($data, $key, $allowed, $default)
-sanitize_int($data, $key, $default = 0)
-sanitize_float($data, $key, $default = 0.0)
-sanitize_url($data, $key, $default = '')
-
-// Múltiples (batch)
-sanitize_booleans($data, $keys)
-sanitize_texts($data, $keys, $default = '')
-sanitize_colors($data, $keys, $default = '')
-sanitize_enums($data, $config)
-sanitize_ints($data, $config)
-sanitize_floats($data, $config)
-
-// Grupos anidados
-sanitize_nested_group($data, $group_key, $rules)
-```
-
-**Ejemplo de uso:**
-```php
-// En lugar de:
-$enabled = !empty($data['enabled']);
-$bg_color = sanitize_hex_color($data['bg_color'] ?? '#FF8600');
-$text = sanitize_text_field($data['text'] ?? '');
-
-// Usar:
-$booleans = APUS_Sanitizer_Helper::sanitize_booleans($data, ['enabled', 'show_mobile']);
-$colors = APUS_Sanitizer_Helper::sanitize_colors($data, ['bg_color', 'text_color'], '#FFFFFF');
-$texts = APUS_Sanitizer_Helper::sanitize_texts($data, ['title', 'subtitle'], '');
-```
-
-#### Sanitizadores específicos (Strategy)
-
-Cada uno implementa un método estático `sanitize($data)`:
-
-- `APUS_TopBar_Sanitizer` - 16 campos (5 bool, 5 color, 5 text, 1 enum)
-- `APUS_Navbar_Sanitizer` - 38 campos (grupos: activación, colores, tipografía, efectos, etc.)
-- `APUS_LetsTalkButton_Sanitizer` - ~15 campos
-- `APUS_HeroSection_Sanitizer` - ~20 campos
-
----
-
-## 🔄 Flujo de Datos
-
-### Flujo completo: Usuario → Guardar configuración
-
-```
-1. Usuario modifica campo en admin panel
- ↓
-2. JavaScript detecta cambio (event listener)
- ↓
-3. admin-app.js recopila datos del formulario
- ↓
-4. Axios POST a /wp-admin/admin-ajax.php
- action: 'apus_save_settings'
- nonce: apusAdminData.nonce
- data: { component: 'topbar', ... }
- ↓
-5. WordPress enruta a APUS_Settings_Manager::ajax_save_settings()
- ↓
-6. Verifica nonce + permisos
- ↓
-7. Llama save_settings($data)
- ↓
-8. sanitize_settings($data) aplica sanitizador correcto
- ↓
-9. APUS_TopBar_Sanitizer::sanitize($data) limpia datos
- ↓
-10. APUS_DB_Manager::save_config() guarda en DB
- INSERT INTO wp_apus_theme_components
- (component_name, config_key, config_value, data_type, version)
- VALUES ('topbar', 'enabled', '1', 'boolean', '2.1.4')
- ON DUPLICATE KEY UPDATE
- config_value = '1', updated_at = NOW()
- ↓
-11. Retorna JSON success
- ↓
-12. JavaScript muestra notificación
-```
-
-### Flujo: Cargar configuración al abrir panel
-
-```
-1. Usuario navega a Apariencia > Tema APUs
- ↓
-2. APUS_Admin_Menu::render_admin_page() carga main.php
- ↓
-3. main.php renderiza HTML del formulario
- ↓
-4. admin-app.js se ejecuta (document.ready)
- ↓
-5. Axios GET a /wp-admin/admin-ajax.php
- action: 'apus_get_settings'
- component: 'topbar'
- ↓
-6. WordPress enruta a APUS_Settings_Manager::ajax_get_settings()
- ↓
-7. Llama get_settings('topbar')
- ↓
-8. APUS_DB_Manager::get_config('topbar')
- SELECT config_key, config_value, data_type
- FROM wp_apus_theme_components
- WHERE component_name = 'topbar'
- ↓
-9. parse_value() deserializa cada valor según data_type
- ↓
-10. Retorna JSON con configuración
- ↓
-11. JavaScript puebla formulario con valores
-```
-
----
-
-## 💾 Sistema de Base de Datos
-
-### Tabla: `wp_apus_theme_components`
-
-**Propósito:** Almacenar configuraciones de componentes del tema de forma estructurada
-
-**Ventajas vs wp_options:**
-- ✅ Versionado de configuraciones
-- ✅ Tipado fuerte (data_type)
-- ✅ Timestamps automáticos
-- ✅ Índices optimizados
-- ✅ Namespace por componente
-- ✅ Queries eficientes (1 query para todo el componente)
-
-**Ejemplo de datos:**
-
-```sql
--- Top Bar
-INSERT INTO wp_apus_theme_components VALUES
-(1, 'topbar', 'enabled', '1', 'boolean', '2.1.4', '2025-01-14 10:00:00', '2025-01-14 10:00:00'),
-(2, 'topbar', 'bg_color', '#FF8600', 'string', '2.1.4', '2025-01-14 10:00:00', '2025-01-14 10:00:00'),
-(3, 'topbar', 'text', '¡Oferta especial!', 'string', '2.1.4', '2025-01-14 10:00:00', '2025-01-14 10:00:00'),
-
--- Navbar
-(4, 'navbar', 'enabled', '1', 'boolean', '2.1.4', '2025-01-14 10:00:00', '2025-01-14 10:00:00'),
-(5, 'navbar', 'sticky', '1', 'boolean', '2.1.4', '2025-01-14 10:00:00', '2025-01-14 10:00:00'),
-
--- Theme Options (migradas)
-(6, 'theme', 'site_logo', '123', 'integer', '2.1.4', '2025-01-14 10:00:00', '2025-01-14 10:00:00'),
-(7, 'theme', 'enable_breadcrumbs', '1', 'boolean', '2.1.4', '2025-01-14 10:00:00', '2025-01-14 10:00:00');
-```
-
-### Estrategia de Queries
-
-```php
-// ❌ Malo: 16 queries para 16 configs
-for ($i = 1; $i <= 16; $i++) {
- $value = get_option('topbar_field_' . $i);
-}
-
-// ✅ Bueno: 1 query para todo el componente
-$config = $db->get_config('topbar'); // Array con todas las 16 configs
-```
-
----
-
-## 🔧 Sistema de Migración
-
-### Migración automática en `admin_init`
-
-```php
-// En init.php (líneas 54-68)
-add_action('admin_init', function() {
- $migrator = new APUS_Theme_Options_Migrator();
-
- if (!$migrator->is_migrated()) {
- $result = $migrator->migrate();
-
- if ($result['success']) {
- error_log('Migradas ' . $result['migrated'] . ' configuraciones');
- } else {
- error_log('Error: ' . $result['message']);
- }
- }
-});
-```
-
-### Sistema de Backups
-
-**Formato del backup:**
-```php
-// Guardado en wp_options
-$backup_key = 'apus_theme_options_backup_' . time();
-$backup_data = [
- 'timestamp' => time(),
- 'version' => APUS_ADMIN_PANEL_VERSION,
- 'options' => $old_options, // Array original
- 'count' => count($old_options)
-];
-update_option($backup_key, json_encode($backup_data));
-```
-
-**Operaciones disponibles:**
-- `list_backups()` - Lista todos los backups
-- `rollback($backup_id)` - Restaura desde backup
-- `delete_backup($backup_id)` - Elimina backup
-
----
-
-## 🎨 Frontend del Admin Panel
-
-### Página Principal: `pages/main.php`
-
-**Estructura HTML:**
-
-```html
---``` - -### Componente Top Bar: `components/component-top-bar.php` - -**Estructura (16KB, 475 líneas):** - -```html - -
APUs Theme Settings
- - - - - -- --- -- - --- - -Configuración Top Bar
-- -- - - -``` - -### JavaScript: `assets/js/admin-app.js` - -**Responsabilidades:** -- Event listeners para formularios -- Validación client-side -- Recopilación de datos -- Requests AJAX (Axios) -- Notificaciones (success/error) -- Sincronización color picker ↔ hex input - -**Estructura:** -```javascript -(function($) { - 'use strict'; - - // 1. Cargar configuración al iniciar - function loadSettings() { - axios.post(apusAdminData.ajaxUrl, { - action: 'apus_get_settings', - component: 'topbar', - nonce: apusAdminData.nonce - }) - .then(response => { - populateForm(response.data.data); - }); - } - - // 2. Guardar al hacer clic - $('#saveTopBarSettings').on('click', function() { - const data = { - action: 'apus_save_settings', - nonce: apusAdminData.nonce, - component: 'topbar', - enabled: $('#topBarEnabled').is(':checked'), - bg_color: $('#topBarBgColor').val(), - // ... resto de campos - }; - - axios.post(apusAdminData.ajaxUrl, data) - .then(response => { - showNotification('success', 'Guardado exitoso'); - }); - }); - - // 3. Sincronizar color pickers - $('.color-picker-container input[type="color"]').on('change', function() { - const hex = $(this).val(); - $(this).siblings('.hex-value').val(hex); - }); - - $(document).ready(function() { - loadSettings(); - }); - -})(jQuery); -``` - ---- - -## ⚠️ Problemas Identificados - -### 1. **Componentes INACTIVOS** (CRÍTICO) - -**Síntoma:** Solo el tab "Top Bar" está visible en `main.php` - -**Archivos PHP existentes pero NO utilizados:** -- ❌ `components/component-navbar.php` (31KB, 615 líneas) -- ❌ `components/component-lets-talk-button.php` (23KB) -- ❌ `components/component-hero-section.php` (36KB) - -**Causa:** Nunca se agregaron los tabs correspondientes en `main.php` - -**Impacto:** -- 90KB de código PHP inaccesible -- Sanitizadores cargados pero no usados -- Assets CSS/JS de navbar cargados innecesariamente -- Usuarios no pueden configurar 3 de 4 componentes - -**Solución:** Agregar tabs en `main.php`: -```html --- - ---Activación y Visibilidad
- - -- - -- - -- - ---- - ---Colores Personalizados
- - -- - - --
-
-
-```
-
----
-
-### 2. **Assets CSS/JS FALTANTES** (CRÍTICO)
-
-**Assets esperados vs existentes:**
-
-| Componente | CSS | JS | Estado |
-|------------|-----|----|----|
-| Top Bar | ❌ Falta | ❌ Falta | No hay assets específicos |
-| Navbar | ✅ Existe | ✅ Existe | Único completo |
-| Let's Talk | ❌ Falta | ❌ Falta | No hay assets específicos |
-| Hero | ❌ Falta | ❌ Falta | No hay assets específicos |
-
-**Problema:** Los componentes usan CSS/JS genéricos pero necesitan handlers específicos
-
-**Evidencia en `class-admin-menu.php`:**
-```php
-// Líneas 83-97 (ELIMINADO durante migración)
-// wp_enqueue_style('apus-component-top-bar-css', ...); ❌ No existe
-// wp_enqueue_script('apus-component-top-bar-js', ...); ❌ No existe
-```
-
-**Impacto:**
-- Funcionalidad reducida en componentes
-- JavaScript genérico en lugar de específico por componente
-
----
-
-### 3. **Rutas duplicadas en constants** (MENOR)
-
-**Problema:** Constants en `init.php` apuntan correctamente:
-```php
-define('APUS_ADMIN_PANEL_PATH', get_template_directory() . '/admin/');
-define('APUS_ADMIN_PANEL_URL', get_template_directory_uri() . '/admin/');
-```
-
-Pero se concatenaban con `/admin/` nuevamente:
-```php
-// ❌ ANTES (generaba /admin/admin/pages/main.php)
-require_once APUS_ADMIN_PANEL_PATH . 'admin/pages/main.php';
-
-// ✅ DESPUÉS (corregido)
-require_once APUS_ADMIN_PANEL_PATH . 'pages/main.php';
-```
-
-**Estado:** ✅ CORREGIDO
-
----
-
-### 4. **Theme Options sin migrar** (ADVERTENCIA)
-
-**Síntoma:** Error en debug.log:
-```
-APUS Theme: Error en migración de Theme Options - No hay opciones para migrar
-```
-
-**Causa:** No existe `apus_theme_options` en `wp_options`
-
-**Implicación:**
-- Primera instalación O
-- Ya se ejecutó la migración previamente O
-- Theme Options nunca se configuraron
-
-**Verificación necesaria:**
-```sql
--- Verificar si existen en nueva tabla
-SELECT * FROM wp_apus_theme_components WHERE component_name = 'theme';
-
--- Verificar si existen en wp_options
-SELECT * FROM wp_options WHERE option_name = 'apus_theme_options';
-```
-
----
-
-### 5. **Sanitizers cargados sin uso** (OPTIMIZACIÓN)
-
-**Problema:** Se cargan 4 sanitizadores pero solo 1 está activo:
-
-```php
-// En init.php (líneas 32-36)
-require_once APUS_ADMIN_PANEL_PATH . 'includes/sanitizers/class-topbar-sanitizer.php'; // ✅ USADO
-require_once APUS_ADMIN_PANEL_PATH . 'includes/sanitizers/class-navbar-sanitizer.php'; // ❌ NO USADO
-require_once APUS_ADMIN_PANEL_PATH . 'includes/sanitizers/class-letstalkbutton-sanitizer.php'; // ❌ NO USADO
-require_once APUS_ADMIN_PANEL_PATH . 'includes/sanitizers/class-herosection-sanitizer.php'; // ❌ NO USADO
-```
-
-**Impacto:** Overhead mínimo pero innecesario
-
-**Solución:** Lazy loading o cargar solo cuando se usen
-
----
-
-### 6. **Falta validación en AJAX endpoints** (SEGURIDAD)
-
-**Problema:** Validación básica pero falta sanitización profunda
-
-```php
-// En class-settings-manager.php
-public function ajax_save_settings() {
- check_ajax_referer('apus_admin_nonce', 'nonce'); // ✅ OK
-
- if (!current_user_can('manage_options')) { // ✅ OK
- wp_send_json_error(...);
- }
-
- // ⚠️ FALTA: Validar estructura de $data antes de pasar a sanitize
- $sanitized = $this->sanitize_settings($_POST); // Potencial issue si $_POST está malformado
-}
-```
-
-**Recomendación:** Agregar validación de estructura:
-```php
-if (!isset($_POST['component']) || !is_array($_POST)) {
- wp_send_json_error(['message' => 'Invalid data structure']);
-}
-```
-
----
-
-## 💡 Recomendaciones
-
-### 1. **URGENTE: Activar componentes inactivos**
-
-**Pasos:**
-1. Editar `admin/pages/main.php`
-2. Agregar 3 tabs nuevos (Navbar, Let's Talk, Hero)
-3. Incluir archivos PHP correspondientes
-4. Crear assets CSS/JS específicos si son necesarios
-
-**Impacto:** Desbloquear 75% de funcionalidad implementada pero inaccesible
-
----
-
-### 2. **Crear assets faltantes**
-
-**Requerido:**
-- `admin/assets/css/component-top-bar.css`
-- `admin/assets/js/component-top-bar.js`
-- `admin/assets/css/component-lets-talk-button.css`
-- `admin/assets/js/component-lets-talk-button.js`
-- `admin/assets/css/component-hero-section.css`
-- `admin/assets/js/component-hero-section.js`
-
-**Contenido mínimo:**
-- CSS: Estilos específicos del tab (si necesarios)
-- JS: Event handlers + lógica específica del componente
-
----
-
-### 3. **Optimizar carga de sanitizers**
-
-**Opción A: Lazy Loading**
-```php
-// En init.php
-function apus_get_sanitizer($component) {
- static $sanitizers = [];
-
- if (!isset($sanitizers[$component])) {
- $file = "includes/sanitizers/class-{$component}-sanitizer.php";
- require_once APUS_ADMIN_PANEL_PATH . $file;
- $sanitizers[$component] = true;
- }
-}
-```
-
-**Opción B: Autoloader**
-```php
-spl_autoload_register(function($class) {
- if (strpos($class, 'APUS_') === 0 && strpos($class, '_Sanitizer') !== false) {
- $file = str_replace('_', '-', strtolower($class));
- require_once APUS_ADMIN_PANEL_PATH . "includes/sanitizers/class-{$file}.php";
- }
-});
-```
-
----
-
-### 4. **Mejorar validación AJAX**
-
-```php
-public function ajax_save_settings() {
- // 1. Verificar nonce
- check_ajax_referer('apus_admin_nonce', 'nonce');
-
- // 2. Verificar permisos
- if (!current_user_can('manage_options')) {
- wp_send_json_error(['message' => 'Insufficient permissions']);
- }
-
- // 3. Validar estructura de datos
- if (!isset($_POST['component']) || !is_string($_POST['component'])) {
- wp_send_json_error(['message' => 'Missing or invalid component']);
- }
-
- $allowed_components = ['topbar', 'navbar', 'letstalkbutton', 'herosection'];
- if (!in_array($_POST['component'], $allowed_components, true)) {
- wp_send_json_error(['message' => 'Invalid component name']);
- }
-
- // 4. Sanitizar y guardar
- $sanitized = $this->sanitize_settings($_POST);
- // ...
-}
-```
-
----
-
-### 5. **Documentar API pública**
-
-**Crear:** `admin/API-DOCUMENTATION.md`
-
-**Contenido:**
-```markdown
-# Admin Panel API
-
-## Database Access
-
-### Get component config
-$db = new APUS_DB_Manager();
-$config = $db->get_config('topbar');
-
-### Save component config
-$db->save_config('topbar', 'enabled', true, 'boolean', '2.1.4');
-
-## AJAX Endpoints
-
-### Get Settings
-POST /wp-admin/admin-ajax.php
-action: apus_get_settings
-component: topbar|navbar|letstalkbutton|herosection
-nonce: [required]
-
-### Save Settings
-POST /wp-admin/admin-ajax.php
-action: apus_save_settings
-component: topbar|navbar|letstalkbutton|herosection
-nonce: [required]
-...
-```
-
----
-
-### 6. **Agregar tests automatizados**
-
-**Framework sugerido:** PHPUnit + WP_Mock
-
-**Tests críticos:**
-```php
-// tests/test-db-manager.php
-class Test_APUS_DB_Manager extends WP_UnitTestCase {
- public function test_save_and_get_config() {
- $db = new APUS_DB_Manager();
- $db->save_config('test', 'key', 'value', 'string', '1.0');
- $value = $db->get_config('test', 'key');
- $this->assertEquals('value', $value);
- }
-
- public function test_parse_value_boolean() {
- $db = new APUS_DB_Manager();
- $result = $db->parse_value('1', 'boolean');
- $this->assertTrue($result);
- }
-}
-
-// tests/test-sanitizers.php
-class Test_APUS_Sanitizers extends WP_UnitTestCase {
- public function test_topbar_sanitizer() {
- $input = ['enabled' => 'on', 'bg_color' => 'invalid', 'text' => ''];
- $output = APUS_TopBar_Sanitizer::sanitize($input);
- $this->assertTrue($output['enabled']);
- $this->assertEquals('', $output['bg_color']); // Invalid color = empty
- $this->assertStringNotContainsString('\n";
- }
-}
-add_action('wp_head', 'example_add_custom_js_header', 100);
-
-/**
- * EXAMPLE 12: Custom JS in footer
- */
-function example_add_custom_js_footer() {
- $custom_js = apus_get_custom_js_footer();
-
- if ($custom_js) {
- echo '\n";
- }
-}
-add_action('wp_footer', 'example_add_custom_js_footer', 100);
-
-/**
- * EXAMPLE 13: Posts per page for archives
- */
-function example_set_archive_posts_per_page($query) {
- if ($query->is_archive() && !is_admin() && $query->is_main_query()) {
- $posts_per_page = apus_get_archive_posts_per_page();
- $query->set('posts_per_page', $posts_per_page);
- }
-}
-add_action('pre_get_posts', 'example_set_archive_posts_per_page');
-
-/**
- * EXAMPLE 14: Performance optimizations
- */
-function example_apply_performance_settings() {
- // Remove emoji scripts
- if (apus_is_performance_enabled('remove_emoji')) {
- remove_action('wp_head', 'print_emoji_detection_script', 7);
- remove_action('wp_print_styles', 'print_emoji_styles');
- }
-
- // Remove embeds
- if (apus_is_performance_enabled('remove_embeds')) {
- wp_deregister_script('wp-embed');
- }
-
- // Remove Dashicons for non-logged users
- if (apus_is_performance_enabled('remove_dashicons') && !is_user_logged_in()) {
- wp_deregister_style('dashicons');
- }
-}
-add_action('wp_enqueue_scripts', 'example_apply_performance_settings', 100);
-
-/**
- * EXAMPLE 15: Lazy loading images
- */
-function example_add_lazy_loading($attr, $attachment, $size) {
- if (apus_is_lazy_loading_enabled()) {
- $attr['loading'] = 'lazy';
- }
- return $attr;
-}
-add_filter('wp_get_attachment_image_attributes', 'example_add_lazy_loading', 10, 3);
-
-/**
- * EXAMPLE 16: Layout classes based on settings
- */
-function example_get_layout_class() {
- $layout = 'right-sidebar'; // default
-
- if (is_single()) {
- $layout = apus_get_default_post_layout();
- } elseif (is_page()) {
- $layout = apus_get_default_page_layout();
- }
-
- return 'layout-' . $layout;
-}
-
-/**
- * EXAMPLE 17: Display post meta conditionally
- */
-function example_display_post_meta() {
- if (!apus_get_option('show_post_meta', true)) {
- return;
- }
-
- ?>
-
- ', ', ', '
'; - print_r($all_options); - echo '
'; - } -} -add_action('wp_footer', 'example_debug_all_options'); - -/** - * EXAMPLE 20: Check if specific feature is enabled - */ -function example_check_feature() { - // Multiple ways to check boolean options - - // Method 1: Using helper function - if (apus_is_option_enabled('enable_breadcrumbs')) { - // Breadcrumbs are enabled - } - - // Method 2: Using get_option with default - if (apus_get_option('enable_related_posts', true)) { - // Related posts are enabled - } - - // Method 3: Direct check - $options = apus_get_all_options(); - if (isset($options['enable_lazy_loading']) && $options['enable_lazy_loading']) { - // Lazy loading is enabled - } -} diff --git a/inc/admin/options-api.php b/inc/admin/options-api.php deleted file mode 100644 index b46f95aa..00000000 --- a/inc/admin/options-api.php +++ /dev/null @@ -1,237 +0,0 @@ - 'apus_sanitize_options', - 'default' => apus_get_default_options(), - ) - ); - - // General Settings Section - add_settings_section( - 'apus_general_section', - __('General Settings', 'apus-theme'), - 'apus_general_section_callback', - 'apus-theme-options' - ); - - // Content Settings Section - add_settings_section( - 'apus_content_section', - __('Content Settings', 'apus-theme'), - 'apus_content_section_callback', - 'apus-theme-options' - ); - - // Performance Settings Section - add_settings_section( - 'apus_performance_section', - __('Performance Settings', 'apus-theme'), - 'apus_performance_section_callback', - 'apus-theme-options' - ); - - // Related Posts Settings Section - add_settings_section( - 'apus_related_posts_section', - __('Related Posts Settings', 'apus-theme'), - 'apus_related_posts_section_callback', - 'apus-theme-options' - ); - - // Social Share Settings Section - add_settings_section( - 'apus_social_share_section', - __('Social Share Buttons', 'apus-theme'), - 'apus_social_share_section_callback', - 'apus-theme-options' - ); -} -add_action('admin_init', 'apus_register_settings'); - -/** - * Get default options - * - * @return array - */ -function apus_get_default_options() { - return array( - // General - 'site_logo' => 0, - 'site_favicon' => 0, - 'enable_breadcrumbs' => true, - 'breadcrumb_separator' => '>', - 'date_format' => 'd/m/Y', - 'time_format' => 'H:i', - 'copyright_text' => sprintf(__('© %s %s. All rights reserved.', 'apus-theme'), date('Y'), get_bloginfo('name')), - 'social_facebook' => '', - 'social_twitter' => '', - 'social_instagram' => '', - 'social_linkedin' => '', - 'social_youtube' => '', - - // Content - 'excerpt_length' => 55, - 'excerpt_more' => '...', - 'default_post_layout' => 'right-sidebar', - 'default_page_layout' => 'right-sidebar', - 'archive_posts_per_page' => 10, - 'show_featured_image_single' => true, - 'show_author_box' => true, - 'enable_comments_posts' => true, - 'enable_comments_pages' => false, - 'show_post_meta' => true, - 'show_post_tags' => true, - 'show_post_categories' => true, - - // Performance - 'enable_lazy_loading' => true, - 'performance_remove_emoji' => true, - 'performance_remove_embeds' => false, - 'performance_remove_dashicons' => true, - 'performance_defer_js' => false, - 'performance_minify_html' => false, - 'performance_disable_gutenberg' => false, - - // Related Posts - 'enable_related_posts' => true, - 'related_posts_count' => 3, - 'related_posts_taxonomy' => 'category', - 'related_posts_title' => __('Related Posts', 'apus-theme'), - 'related_posts_columns' => 3, - - // Social Share Buttons - 'apus_enable_share_buttons' => '1', - 'apus_share_text' => __('Compartir:', 'apus-theme'), - - // Advanced - 'custom_css' => '', - 'custom_js_header' => '', - 'custom_js_footer' => '', - ); -} - -/** - * Section Callbacks - */ -function apus_general_section_callback() { - echo '' . __('Configure general theme settings including logo, branding, and social media.', 'apus-theme') . '
'; -} - -function apus_content_section_callback() { - echo '' . __('Configure content display settings for posts, pages, and archives.', 'apus-theme') . '
'; -} - -function apus_performance_section_callback() { - echo '' . __('Optimize your site performance with these settings.', 'apus-theme') . '
'; -} - -function apus_related_posts_section_callback() { - echo '' . __('Configure related posts display on single post pages.', 'apus-theme') . '
'; -} - -function apus_social_share_section_callback() { - echo '' . __('Configure social share buttons display on single post pages.', 'apus-theme') . '
'; -} - -/** - * Sanitize all options - * - * @param array $input The input array - * @return array The sanitized array - */ -function apus_sanitize_options($input) { - $sanitized = array(); - - if (!is_array($input)) { - return $sanitized; - } - - // General Settings - $sanitized['site_logo'] = isset($input['site_logo']) ? absint($input['site_logo']) : 0; - $sanitized['site_favicon'] = isset($input['site_favicon']) ? absint($input['site_favicon']) : 0; - $sanitized['enable_breadcrumbs'] = isset($input['enable_breadcrumbs']) ? (bool) $input['enable_breadcrumbs'] : false; - $sanitized['breadcrumb_separator'] = isset($input['breadcrumb_separator']) ? sanitize_text_field($input['breadcrumb_separator']) : '>'; - $sanitized['date_format'] = isset($input['date_format']) ? sanitize_text_field($input['date_format']) : 'd/m/Y'; - $sanitized['time_format'] = isset($input['time_format']) ? sanitize_text_field($input['time_format']) : 'H:i'; - $sanitized['copyright_text'] = isset($input['copyright_text']) ? wp_kses_post($input['copyright_text']) : ''; - - // Social Media - $social_fields = array('facebook', 'twitter', 'instagram', 'linkedin', 'youtube'); - foreach ($social_fields as $social) { - $key = 'social_' . $social; - $sanitized[$key] = isset($input[$key]) ? esc_url_raw($input[$key]) : ''; - } - - // Content Settings - $sanitized['excerpt_length'] = isset($input['excerpt_length']) ? absint($input['excerpt_length']) : 55; - $sanitized['excerpt_more'] = isset($input['excerpt_more']) ? sanitize_text_field($input['excerpt_more']) : '...'; - $sanitized['default_post_layout'] = isset($input['default_post_layout']) ? sanitize_text_field($input['default_post_layout']) : 'right-sidebar'; - $sanitized['default_page_layout'] = isset($input['default_page_layout']) ? sanitize_text_field($input['default_page_layout']) : 'right-sidebar'; - $sanitized['archive_posts_per_page'] = isset($input['archive_posts_per_page']) ? absint($input['archive_posts_per_page']) : 10; - $sanitized['show_featured_image_single'] = isset($input['show_featured_image_single']) ? (bool) $input['show_featured_image_single'] : false; - $sanitized['show_author_box'] = isset($input['show_author_box']) ? (bool) $input['show_author_box'] : false; - $sanitized['enable_comments_posts'] = isset($input['enable_comments_posts']) ? (bool) $input['enable_comments_posts'] : false; - $sanitized['enable_comments_pages'] = isset($input['enable_comments_pages']) ? (bool) $input['enable_comments_pages'] : false; - $sanitized['show_post_meta'] = isset($input['show_post_meta']) ? (bool) $input['show_post_meta'] : false; - $sanitized['show_post_tags'] = isset($input['show_post_tags']) ? (bool) $input['show_post_tags'] : false; - $sanitized['show_post_categories'] = isset($input['show_post_categories']) ? (bool) $input['show_post_categories'] : false; - - // Performance Settings - $sanitized['enable_lazy_loading'] = isset($input['enable_lazy_loading']) ? (bool) $input['enable_lazy_loading'] : false; - $sanitized['performance_remove_emoji'] = isset($input['performance_remove_emoji']) ? (bool) $input['performance_remove_emoji'] : false; - $sanitized['performance_remove_embeds'] = isset($input['performance_remove_embeds']) ? (bool) $input['performance_remove_embeds'] : false; - $sanitized['performance_remove_dashicons'] = isset($input['performance_remove_dashicons']) ? (bool) $input['performance_remove_dashicons'] : false; - $sanitized['performance_defer_js'] = isset($input['performance_defer_js']) ? (bool) $input['performance_defer_js'] : false; - $sanitized['performance_minify_html'] = isset($input['performance_minify_html']) ? (bool) $input['performance_minify_html'] : false; - $sanitized['performance_disable_gutenberg'] = isset($input['performance_disable_gutenberg']) ? (bool) $input['performance_disable_gutenberg'] : false; - - // Related Posts - $sanitized['enable_related_posts'] = isset($input['enable_related_posts']) ? (bool) $input['enable_related_posts'] : false; - $sanitized['related_posts_count'] = isset($input['related_posts_count']) ? absint($input['related_posts_count']) : 3; - $sanitized['related_posts_taxonomy'] = isset($input['related_posts_taxonomy']) ? sanitize_text_field($input['related_posts_taxonomy']) : 'category'; - $sanitized['related_posts_title'] = isset($input['related_posts_title']) ? sanitize_text_field($input['related_posts_title']) : __('Related Posts', 'apus-theme'); - $sanitized['related_posts_columns'] = isset($input['related_posts_columns']) ? absint($input['related_posts_columns']) : 3; - - // Social Share Buttons - $sanitized['apus_enable_share_buttons'] = isset($input['apus_enable_share_buttons']) ? sanitize_text_field($input['apus_enable_share_buttons']) : '1'; - $sanitized['apus_share_text'] = isset($input['apus_share_text']) ? sanitize_text_field($input['apus_share_text']) : __('Compartir:', 'apus-theme'); - - // Advanced Settings - $sanitized['custom_css'] = isset($input['custom_css']) ? apus_sanitize_css($input['custom_css']) : ''; - $sanitized['custom_js_header'] = isset($input['custom_js_header']) ? apus_sanitize_js($input['custom_js_header']) : ''; - $sanitized['custom_js_footer'] = isset($input['custom_js_footer']) ? apus_sanitize_js($input['custom_js_footer']) : ''; - - return $sanitized; -} - -/** - * NOTE: All sanitization functions have been moved to inc/sanitize-functions.php - * to avoid function redeclaration errors. This includes: - * - apus_sanitize_css() - * - apus_sanitize_js() - * - apus_sanitize_integer() - * - apus_sanitize_text() - * - apus_sanitize_url() - * - apus_sanitize_html() - * - apus_sanitize_checkbox() - * - apus_sanitize_select() - */ diff --git a/inc/admin/options-page-template.php b/inc/admin/options-page-template.php deleted file mode 100644 index 325748a9..00000000 --- a/inc/admin/options-page-template.php +++ /dev/null @@ -1,661 +0,0 @@ - - - - - - diff --git a/inc/admin/related-posts-options.php b/inc/admin/related-posts-options.php deleted file mode 100644 index 40de6637..00000000 --- a/inc/admin/related-posts-options.php +++ /dev/null @@ -1,272 +0,0 @@ - array( - 'key' => 'apus_related_posts_enabled', - 'value' => get_option('apus_related_posts_enabled', true), - 'type' => 'boolean', - 'default' => true, - 'label' => __('Enable Related Posts', 'apus-theme'), - 'description' => __('Show related posts section at the end of single posts', 'apus-theme'), - ), - 'title' => array( - 'key' => 'apus_related_posts_title', - 'value' => get_option('apus_related_posts_title', __('Related Posts', 'apus-theme')), - 'type' => 'text', - 'default' => __('Related Posts', 'apus-theme'), - 'label' => __('Section Title', 'apus-theme'), - 'description' => __('Title displayed above related posts', 'apus-theme'), - ), - 'count' => array( - 'key' => 'apus_related_posts_count', - 'value' => get_option('apus_related_posts_count', 3), - 'type' => 'number', - 'default' => 3, - 'min' => 1, - 'max' => 12, - 'label' => __('Number of Posts', 'apus-theme'), - 'description' => __('Maximum number of related posts to display', 'apus-theme'), - ), - 'columns' => array( - 'key' => 'apus_related_posts_columns', - 'value' => get_option('apus_related_posts_columns', 3), - 'type' => 'select', - 'default' => 3, - 'options' => array( - 1 => __('1 Column', 'apus-theme'), - 2 => __('2 Columns', 'apus-theme'), - 3 => __('3 Columns', 'apus-theme'), - 4 => __('4 Columns', 'apus-theme'), - ), - 'label' => __('Grid Columns', 'apus-theme'), - 'description' => __('Number of columns in the grid layout (responsive)', 'apus-theme'), - ), - 'show_excerpt' => array( - 'key' => 'apus_related_posts_show_excerpt', - 'value' => get_option('apus_related_posts_show_excerpt', true), - 'type' => 'boolean', - 'default' => true, - 'label' => __('Show Excerpt', 'apus-theme'), - 'description' => __('Display post excerpt in related posts cards', 'apus-theme'), - ), - 'excerpt_length' => array( - 'key' => 'apus_related_posts_excerpt_length', - 'value' => get_option('apus_related_posts_excerpt_length', 20), - 'type' => 'number', - 'default' => 20, - 'min' => 5, - 'max' => 100, - 'label' => __('Excerpt Length', 'apus-theme'), - 'description' => __('Number of words in the excerpt', 'apus-theme'), - ), - 'show_date' => array( - 'key' => 'apus_related_posts_show_date', - 'value' => get_option('apus_related_posts_show_date', true), - 'type' => 'boolean', - 'default' => true, - 'label' => __('Show Date', 'apus-theme'), - 'description' => __('Display publication date in related posts', 'apus-theme'), - ), - 'show_category' => array( - 'key' => 'apus_related_posts_show_category', - 'value' => get_option('apus_related_posts_show_category', true), - 'type' => 'boolean', - 'default' => true, - 'label' => __('Show Category', 'apus-theme'), - 'description' => __('Display category badge on related posts', 'apus-theme'), - ), - 'bg_colors' => array( - 'key' => 'apus_related_posts_bg_colors', - 'value' => get_option('apus_related_posts_bg_colors', array( - '#1a73e8', '#e91e63', '#4caf50', '#ff9800', '#9c27b0', '#00bcd4', - )), - 'type' => 'color_array', - 'default' => array( - '#1a73e8', // Blue - '#e91e63', // Pink - '#4caf50', // Green - '#ff9800', // Orange - '#9c27b0', // Purple - '#00bcd4', // Cyan - ), - 'label' => __('Background Colors', 'apus-theme'), - 'description' => __('Colors used for posts without featured images', 'apus-theme'), - ), - ); -} - -/** - * Update a related posts option - * - * @param string $option_key The option key (without 'apus_related_posts_' prefix) - * @param mixed $value The new value - * @return bool True if updated successfully - */ -function apus_update_related_posts_option($option_key, $value) { - $full_key = 'apus_related_posts_' . $option_key; - return update_option($full_key, $value); -} - -/** - * Reset related posts options to defaults - * - * @return bool True if reset successfully - */ -function apus_reset_related_posts_options() { - $options = apus_get_related_posts_options(); - $success = true; - - foreach ($options as $option) { - if (!update_option($option['key'], $option['default'])) { - $success = false; - } - } - - return $success; -} - -/** - * Example: Programmatically configure related posts - * - * This function shows how to configure related posts options programmatically. - * You can call this from your functions.php or a plugin. - * - * @return void - */ -function apus_example_configure_related_posts() { - // Example usage - uncomment to use: - - // Enable related posts - // update_option('apus_related_posts_enabled', true); - - // Set custom title - // update_option('apus_related_posts_title', __('You Might Also Like', 'apus-theme')); - - // Show 4 related posts - // update_option('apus_related_posts_count', 4); - - // Use 2 columns layout - // update_option('apus_related_posts_columns', 2); - - // Show excerpt with 30 words - // update_option('apus_related_posts_show_excerpt', true); - // update_option('apus_related_posts_excerpt_length', 30); - - // Show date and category - // update_option('apus_related_posts_show_date', true); - // update_option('apus_related_posts_show_category', true); - - // Custom background colors for posts without images - // update_option('apus_related_posts_bg_colors', array( - // '#FF6B6B', // Red - // '#4ECDC4', // Teal - // '#45B7D1', // Blue - // '#FFA07A', // Coral - // '#98D8C8', // Mint - // '#F7DC6F', // Yellow - // )); -} - -/** - * Filter hook example: Modify related posts query - * - * This example shows how to customize the related posts query. - * Add this to your functions.php or child theme. - */ -function apus_example_modify_related_posts_query($args, $post_id) { - // Example: Order by date instead of random - // $args['orderby'] = 'date'; - // $args['order'] = 'DESC'; - - // Example: Only show posts from the last 6 months - // $args['date_query'] = array( - // array( - // 'after' => '6 months ago', - // ), - // ); - - // Example: Exclude specific category - // $args['category__not_in'] = array(5); // Replace 5 with category ID - - return $args; -} -// add_filter('apus_related_posts_args', 'apus_example_modify_related_posts_query', 10, 2); - -/** - * Get documentation for related posts configuration - * - * @return array Documentation array - */ -function apus_get_related_posts_documentation() { - return array( - 'overview' => array( - 'title' => __('Related Posts Overview', 'apus-theme'), - 'content' => __( - 'The related posts feature automatically displays relevant posts at the end of each blog post. ' . - 'Posts are related based on shared categories and displayed in a responsive Bootstrap grid.', - 'apus-theme' - ), - ), - 'features' => array( - 'title' => __('Key Features', 'apus-theme'), - 'items' => array( - __('Automatic category-based matching', 'apus-theme'), - __('Responsive Bootstrap 5 grid layout', 'apus-theme'), - __('Configurable number of posts and columns', 'apus-theme'), - __('Support for posts with and without featured images', 'apus-theme'), - __('Beautiful color backgrounds for posts without images', 'apus-theme'), - __('Customizable excerpt length', 'apus-theme'), - __('Optional display of dates and categories', 'apus-theme'), - __('Smooth hover animations', 'apus-theme'), - __('Print-friendly styles', 'apus-theme'), - __('Dark mode support', 'apus-theme'), - ), - ), - 'configuration' => array( - 'title' => __('How to Configure', 'apus-theme'), - 'methods' => array( - 'database' => array( - 'title' => __('Via WordPress Options API', 'apus-theme'), - 'code' => "update_option('apus_related_posts_enabled', true);\nupdate_option('apus_related_posts_count', 4);", - ), - 'filter' => array( - 'title' => __('Via Filter Hook', 'apus-theme'), - 'code' => "add_filter('apus_related_posts_args', function(\$args, \$post_id) {\n \$args['posts_per_page'] = 6;\n return \$args;\n}, 10, 2);", - ), - ), - ), - 'customization' => array( - 'title' => __('Customization Examples', 'apus-theme'), - 'examples' => array( - array( - 'title' => __('Change title and layout', 'apus-theme'), - 'code' => "update_option('apus_related_posts_title', 'También te puede interesar');\nupdate_option('apus_related_posts_columns', 4);", - ), - array( - 'title' => __('Customize colors', 'apus-theme'), - 'code' => "update_option('apus_related_posts_bg_colors', array(\n '#FF6B6B',\n '#4ECDC4',\n '#45B7D1'\n));", - ), - ), - ), - ); -} diff --git a/inc/admin/theme-options.php b/inc/admin/theme-options.php deleted file mode 100644 index de75cd82..00000000 --- a/inc/admin/theme-options.php +++ /dev/null @@ -1,217 +0,0 @@ - admin_url('admin-ajax.php'), - 'nonce' => wp_create_nonce('apus_admin_nonce'), - 'strings' => array( - 'selectImage' => __('Select Image', 'apus-theme'), - 'useImage' => __('Use Image', 'apus-theme'), - 'removeImage' => __('Remove Image', 'apus-theme'), - 'confirmReset' => __('Are you sure you want to reset all options to default values? This cannot be undone.', 'apus-theme'), - 'saved' => __('Settings saved successfully!', 'apus-theme'), - 'error' => __('An error occurred while saving settings.', 'apus-theme'), - ), - )); -} -add_action('admin_enqueue_scripts', 'apus_enqueue_admin_scripts'); - -/** - * Add settings link to theme actions - */ -function apus_add_settings_link($links) { - $settings_link = '' . __('Settings', 'apus-theme') . ''; - array_unshift($links, $settings_link); - return $links; -} -add_filter('theme_action_links_' . get_template(), 'apus_add_settings_link'); - -/** - * AJAX handler for resetting options - */ -function apus_reset_options_ajax() { - check_ajax_referer('apus_admin_nonce', 'nonce'); - - if (!current_user_can('manage_options')) { - wp_send_json_error(array('message' => __('Insufficient permissions.', 'apus-theme'))); - } - - // Delete options to reset to defaults - delete_option('apus_theme_options'); - - wp_send_json_success(array('message' => __('Options reset to defaults successfully.', 'apus-theme'))); -} -add_action('wp_ajax_apus_reset_options', 'apus_reset_options_ajax'); - -/** - * AJAX handler for exporting options - */ -function apus_export_options_ajax() { - check_ajax_referer('apus_admin_nonce', 'nonce'); - - if (!current_user_can('manage_options')) { - wp_send_json_error(array('message' => __('Insufficient permissions.', 'apus-theme'))); - } - - $options = get_option('apus_theme_options', array()); - - wp_send_json_success(array( - 'data' => json_encode($options, JSON_PRETTY_PRINT), - 'filename' => 'apus-theme-options-' . date('Y-m-d') . '.json' - )); -} -add_action('wp_ajax_apus_export_options', 'apus_export_options_ajax'); - -/** - * AJAX handler for importing options - */ -function apus_import_options_ajax() { - check_ajax_referer('apus_admin_nonce', 'nonce'); - - if (!current_user_can('manage_options')) { - wp_send_json_error(array('message' => __('Insufficient permissions.', 'apus-theme'))); - } - - if (!isset($_POST['import_data'])) { - wp_send_json_error(array('message' => __('No import data provided.', 'apus-theme'))); - } - - $import_data = json_decode(stripslashes($_POST['import_data']), true); - - if (json_last_error() !== JSON_ERROR_NONE) { - wp_send_json_error(array('message' => __('Invalid JSON data.', 'apus-theme'))); - } - - // Sanitize imported data - $sanitized_data = apus_sanitize_options($import_data); - - // Update options - update_option('apus_theme_options', $sanitized_data); - - wp_send_json_success(array('message' => __('Options imported successfully.', 'apus-theme'))); -} -add_action('wp_ajax_apus_import_options', 'apus_import_options_ajax'); - -/** - * Add admin notices - */ -function apus_admin_notices() { - $screen = get_current_screen(); - if ($screen->id !== 'appearance_page_apus-theme-options') { - return; - } - - // Check if settings were updated - if (isset($_GET['settings-updated']) && $_GET['settings-updated'] === 'true') { - ?> -- -- add_panel('apus_theme_options', array( - 'title' => __('Apus Theme Options', 'apus-theme'), - 'description' => __('Configure theme options (Also available in Theme Options page)', 'apus-theme'), - 'priority' => 10, - )); - - // General Section - $wp_customize->add_section('apus_general', array( - 'title' => __('General Settings', 'apus-theme'), - 'panel' => 'apus_theme_options', - 'priority' => 10, - )); - - // Enable breadcrumbs - $wp_customize->add_setting('apus_theme_options[enable_breadcrumbs]', array( - 'default' => true, - 'type' => 'option', - 'sanitize_callback' => 'apus_sanitize_checkbox', - )); - - $wp_customize->add_control('apus_theme_options[enable_breadcrumbs]', array( - 'label' => __('Enable Breadcrumbs', 'apus-theme'), - 'section' => 'apus_general', - 'type' => 'checkbox', - )); -} -add_action('customize_register', 'apus_customize_register'); diff --git a/inc/adsense-delay.php b/inc/adsense-delay.php index f7a3974b..7a125fb9 100644 --- a/inc/adsense-delay.php +++ b/inc/adsense-delay.php @@ -5,7 +5,7 @@ * Delays the loading of AdSense scripts until user interaction or timeout * to improve initial page load performance. * - * @package Apus_Theme + * @package ROI_Theme * @since 1.0.0 */ @@ -20,23 +20,23 @@ if (!defined('ABSPATH')) { * Esta función inicia el output buffering y reemplaza los scripts de AdSense * con versiones retrasadas cuando se renderiza la página. */ -function apus_delay_adsense_scripts() { +function roi_delay_adsense_scripts() { // Solo ejecutar en frontend if (is_admin()) { return; } // Verificar si el retardo de AdSense está habilitado en las opciones del tema - $delay_enabled = apus_get_option('apus_adsense_delay_enabled', '1'); + $delay_enabled = roi_get_option('roi_adsense_delay_enabled', '1'); if ($delay_enabled !== '1') { return; } // Iniciar output buffering - ob_start('apus_replace_adsense_scripts'); + ob_start('roi_replace_adsense_scripts'); } -add_action('template_redirect', 'apus_delay_adsense_scripts', 1); +add_action('template_redirect', 'roi_delay_adsense_scripts', 1); /** * Reemplaza scripts de AdSense con versiones retrasadas @@ -47,7 +47,7 @@ add_action('template_redirect', 'apus_delay_adsense_scripts', 1); * @param string $html El contenido HTML a procesar * @return string HTML modificado con scripts de AdSense retrasados */ -function apus_replace_adsense_scripts($html) { +function roi_replace_adsense_scripts($html) { // Solo procesar si hay contenido real de AdSense if (strpos($html, 'pagead2.googlesyndication.com') === false && strpos($html, 'adsbygoogle.js') === false) { @@ -93,7 +93,7 @@ function apus_replace_adsense_scripts($html) { // Agregar comentario para indicar que se procesó (solo en modo debug) if (defined('WP_DEBUG') && WP_DEBUG) { - $html = str_replace(' -
diff --git a/admin/ANALISIS-ESTRUCTURA-ADMIN.md b/admin/ANALISIS-ESTRUCTURA-ADMIN.md
deleted file mode 100644
index 4b2605ab..00000000
--- a/admin/ANALISIS-ESTRUCTURA-ADMIN.md
+++ /dev/null
@@ -1,1088 +0,0 @@
-# Análisis Profundo: Carpeta `admin/`
-
-**Fecha:** 2025-01-14
-**Versión del Sistema:** 2.1.4
-**Autor:** Análisis con Serena MCP + Claude Code
-
----
-
-## 📋 Índice
-
-1. [Resumen Ejecutivo](#resumen-ejecutivo)
-2. [Estructura de Carpetas](#estructura-de-carpetas)
-3. [Arquitectura del Sistema](#arquitectura-del-sistema)
-4. [Componentes Principales](#componentes-principales)
-5. [Flujo de Datos](#flujo-de-datos)
-6. [Sistema de Base de Datos](#sistema-de-base-de-datos)
-7. [Sistema de Migración](#sistema-de-migración)
-8. [Sistema de Sanitización](#sistema-de-sanitización)
-9. [Frontend del Admin Panel](#frontend-del-admin-panel)
-10. [Problemas Identificados](#problemas-identificados)
-11. [Recomendaciones](#recomendaciones)
-
----
-
-## 🎯 Resumen Ejecutivo
-
-La carpeta `admin/` contiene **DOS sistemas administrativos consolidados**:
-
-1. **Admin Panel** (Sistema Modular) - Panel de configuración por componentes del tema
-2. **Theme Options** (Sistema Legacy) - Opciones generales del tema
-
-Ambos sistemas conviven en la misma carpeta pero funcionan de manera independiente, compartiendo:
-- Assets (CSS/JS)
-- Infraestructura de base de datos
-- Sistema de sanitización
-
-**Total de archivos:** 29 archivos PHP + 6 assets (CSS/JS)
-
----
-
-## 📁 Estructura de Carpetas
-
-```
-admin/
-├── assets/ # Assets consolidados (Admin Panel + Theme Options)
-│ ├── css/
-│ │ ├── admin-panel.css # Estilos core del panel modular
-│ │ ├── component-navbar.css # Estilos del componente Navbar
-│ │ └── theme-options.css # Estilos de Theme Options (legacy)
-│ └── js/
-│ ├── admin-app.js # JavaScript core del panel modular
-│ ├── component-navbar.js # JavaScript del componente Navbar
-│ └── theme-options.js # JavaScript de Theme Options (legacy)
-│
-├── components/ # Componentes PHP del Admin Panel
-│ ├── component-top-bar.php # [ACTIVO] Formulario Top Bar (16KB, 475 líneas)
-│ ├── component-navbar.php # [INACTIVO] Formulario Navbar (31KB, 615 líneas)
-│ ├── component-lets-talk-button.php # [INACTIVO] Formulario Let's Talk (23KB)
-│ └── component-hero-section.php # [INACTIVO] Formulario Hero (36KB)
-│
-├── includes/ # Clases PHP del sistema
-│ ├── class-admin-menu.php # Registro de menú y carga de assets
-│ ├── class-db-manager.php # Gestor de base de datos (wp_apus_theme_components)
-│ ├── class-data-migrator.php # Migrador de datos (legacy → nueva estructura)
-│ ├── class-settings-manager.php # Gestor de configuraciones (AJAX handlers)
-│ ├── class-theme-options-migrator.php # Migrador de Theme Options
-│ ├── class-validator.php # Validador de datos
-│ └── sanitizers/ # Sanitizadores (Strategy Pattern)
-│ ├── class-sanitizer-helper.php # Helper DRY con métodos estáticos
-│ ├── class-topbar-sanitizer.php # Sanitizador Top Bar
-│ ├── class-navbar-sanitizer.php # Sanitizador Navbar
-│ ├── class-letstalkbutton-sanitizer.php # Sanitizador Let's Talk
-│ └── class-herosection-sanitizer.php # Sanitizador Hero Section
-│
-├── pages/ # Páginas del Admin Panel
-│ ├── main.php # Página principal (solo Tab Top Bar activo)
-│ └── migration.php # Página de migración de Theme Options
-│
-├── theme-options/ # Sistema Theme Options (Legacy)
-│ ├── options-api.php # API de opciones
-│ ├── options-page-template.php # Template HTML de la página
-│ ├── related-posts-options.php # Opciones de posts relacionados
-│ ├── theme-options.php # Registro de menú y handlers AJAX
-│ └── USAGE-EXAMPLES.php # Ejemplos de uso
-│
-└── init.php # Punto de entrada del módulo
-```
-
----
-
-## 🏗️ Arquitectura del Sistema
-
-### Patrón de Diseño
-
-El sistema utiliza varios patrones:
-
-1. **Singleton implícito**: `APUS_DB_Manager` se instancia una sola vez
-2. **Strategy Pattern**: Sanitizadores específicos por componente
-3. **Repository Pattern**: `APUS_DB_Manager` abstrae el acceso a datos
-4. **Front Controller**: `init.php` carga todas las dependencias
-5. **MVC parcial**: Separación vista (components) / lógica (includes)
-
-### Diagrama de Dependencias
-
-```
-init.php
-├── Constantes
-│ ├── APUS_ADMIN_PANEL_VERSION = '2.1.4'
-│ ├── APUS_ADMIN_PANEL_PATH = '/admin/'
-│ └── APUS_ADMIN_PANEL_URL = '/admin/'
-│
-├── Clases Core (orden de carga)
-│ ├── 1. APUS_Admin_Menu
-│ ├── 2. APUS_DB_Manager
-│ ├── 3. APUS_Data_Migrator
-│ ├── 4. APUS_Validator
-│ ├── 5. APUS_Theme_Options_Migrator
-│ ├── 6. APUS_Sanitizer_Helper
-│ ├── 7. Sanitizadores (TopBar, Navbar, LetsTalk, Hero)
-│ └── 8. APUS_Settings_Manager
-│
-└── Hooks WordPress
- └── admin_init
- ├── APUS_Data_Migrator::maybe_migrate()
- └── APUS_Theme_Options_Migrator::migrate()
-```
-
----
-
-## 🧩 Componentes Principales
-
-### 1. `APUS_Admin_Menu` (class-admin-menu.php)
-
-**Responsabilidad:** Registro del menú admin y carga de assets
-
-**Métodos:**
-- `__construct()` - Registra hooks
-- `add_menu_page()` - Registra "Tema APUs" en menú Apariencia
-- `render_admin_page()` - Renderiza `pages/main.php`
-- `enqueue_assets($hook)` - Carga CSS/JS solo en página del panel
-
-**Assets cargados:**
-```php
-// CSS
-- Bootstrap 5.3.2
-- Bootstrap Icons 1.11.1
-- admin-panel.css
-- component-navbar.css
-
-// JS
-- Bootstrap 5.3.2
-- Axios 1.6.0
-- component-navbar.js
-- admin-app.js (depende de jQuery, Axios, component-navbar.js)
-```
-
-**Ubicación en WordPress:** `Apariencia > Tema APUs`
-
----
-
-### 2. `APUS_DB_Manager` (class-db-manager.php)
-
-**Responsabilidad:** Gestión de la tabla personalizada `wp_apus_theme_components`
-
-**Estructura de tabla:**
-```sql
-CREATE TABLE wp_apus_theme_components (
- id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
- component_name VARCHAR(50) NOT NULL, -- Namespace (topbar, navbar, theme, etc.)
- config_key VARCHAR(100) NOT NULL, -- Clave de configuración
- config_value TEXT NOT NULL, -- Valor (serializado si es complejo)
- data_type ENUM('string','integer','boolean','array','json') DEFAULT 'string',
- version VARCHAR(20), -- Versión que guardó el dato
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- UNIQUE KEY unique_config (component_name, config_key),
- INDEX idx_component (component_name),
- INDEX idx_updated (updated_at)
-)
-```
-
-**Métodos principales:**
-
-| Método | Descripción | Uso |
-|--------|-------------|-----|
-| `save_config($component, $key, $value, $type, $version)` | Guarda/actualiza configuración | INSERT...ON DUPLICATE KEY |
-| `get_config($component, $key = null)` | Obtiene configuración completa o específica | SELECT WHERE component_name |
-| `parse_value($value, $type)` | Deserializa valores según tipo | Convierte string → tipo nativo |
-| `delete_config($component, $key = null)` | Elimina configuración | DELETE WHERE |
-| `list_components()` | Lista todos los componentes | SELECT DISTINCT component_name |
-
-**Ejemplo de uso:**
-```php
-$db = new APUS_DB_Manager();
-
-// Guardar
-$db->save_config('topbar', 'enabled', true, 'boolean', '2.1.4');
-$db->save_config('topbar', 'bg_color', '#FF8600', 'string', '2.1.4');
-
-// Obtener
-$config = $db->get_config('topbar'); // Array con todas las configs de topbar
-$enabled = $db->get_config('topbar', 'enabled'); // Solo el valor de 'enabled'
-```
-
----
-
-### 3. `APUS_Settings_Manager` (class-settings-manager.php)
-
-**Responsabilidad:** Gestión de configuraciones con AJAX + sanitización
-
-**Constante:**
-```php
-const OPTION_NAME = 'apus_admin_panel_settings'; // NOTA: No usado actualmente
-```
-
-**Métodos:**
-
-| Método | Hook AJAX | Acción |
-|--------|-----------|--------|
-| `get_settings()` | - | Obtiene configuraciones desde DB |
-| `save_settings($data)` | - | Sanitiza y guarda en DB |
-| `get_defaults()` | - | Retorna valores por defecto |
-| `sanitize_settings($data)` | - | Aplica sanitizadores por componente |
-| `ajax_get_settings()` | `wp_ajax_apus_get_settings` | Endpoint GET |
-| `ajax_save_settings()` | `wp_ajax_apus_save_settings` | Endpoint POST |
-
-**Flujo de sanitización:**
-```php
-sanitize_settings($data) {
- // 1. Detectar componente
- $component = $data['component'] ?? '';
-
- // 2. Aplicar sanitizador específico (Strategy Pattern)
- switch ($component) {
- case 'topbar':
- return APUS_TopBar_Sanitizer::sanitize($data);
- case 'navbar':
- return APUS_Navbar_Sanitizer::sanitize($data);
- case 'letstalkbutton':
- return APUS_LetsTalkButton_Sanitizer::sanitize($data);
- case 'herosection':
- return APUS_HeroSection_Sanitizer::sanitize($data);
- default:
- return array();
- }
-}
-```
-
----
-
-### 4. `APUS_Theme_Options_Migrator` (class-theme-options-migrator.php)
-
-**Responsabilidad:** Migrar Theme Options de `wp_options` → `wp_apus_theme_components`
-
-**Constantes:**
-```php
-const OLD_OPTION_NAME = 'apus_theme_options'; // Ubicación antigua
-const COMPONENT_NAME = 'theme'; // Namespace en nueva tabla
-```
-
-**Métodos principales:**
-
-| Método | Descripción |
-|--------|-------------|
-| `is_migrated()` | Verifica si ya se migró |
-| `migrate()` | Ejecuta migración completa |
-| `create_backup($options)` | Guarda backup JSON en wp_options |
-| `rollback($backup_id)` | Restaura desde backup |
-| `list_backups()` | Lista backups disponibles |
-| `get_migration_stats()` | Estadísticas de migración |
-
-**Flujo de migración:**
-```
-1. Verificar si ya migró → is_migrated()
-2. Leer wp_options['apus_theme_options']
-3. Crear backup → create_backup()
-4. Iterar cada opción:
- - Determinar tipo de dato → determine_data_type()
- - Normalizar valor → normalize_value()
- - Guardar en wp_apus_theme_components → save_config()
-5. Eliminar de wp_options
-6. Marcar como migrado
-```
-
-**Tipos de datos mapeados:**
-
-```php
-get_data_types_map() {
- return [
- // 8 integers
- 'site_logo' => 'integer',
- 'site_favicon' => 'integer',
- 'excerpt_length' => 'integer',
- 'related_posts_count' => 'integer',
- 'cta_icon_attachment_id' => 'integer',
- 'cta_box_icon_attachment_id' => 'integer',
- 'footer_logo_id' => 'integer',
- 'featured_image_height' => 'integer',
-
- // 17 booleans
- 'enable_breadcrumbs' => 'boolean',
- 'show_featured_image_single' => 'boolean',
- 'enable_toc' => 'boolean',
- // ... (14 más)
-
- // 11 strings (default, no listados)
- // site_name, site_tagline, copyright_text, etc.
- ];
-}
-```
-
----
-
-### 5. Sistema de Sanitización (sanitizers/)
-
-**Patrón Strategy:** Cada componente tiene su propio sanitizador
-
-#### `APUS_Sanitizer_Helper` (Helper estático DRY)
-
-**Métodos reutilizables:**
-
-```php
-// Individuales
-sanitize_boolean($data, $key)
-sanitize_text($data, $key, $default = '')
-sanitize_color($data, $key, $default = '')
-sanitize_enum($data, $key, $allowed, $default)
-sanitize_int($data, $key, $default = 0)
-sanitize_float($data, $key, $default = 0.0)
-sanitize_url($data, $key, $default = '')
-
-// Múltiples (batch)
-sanitize_booleans($data, $keys)
-sanitize_texts($data, $keys, $default = '')
-sanitize_colors($data, $keys, $default = '')
-sanitize_enums($data, $config)
-sanitize_ints($data, $config)
-sanitize_floats($data, $config)
-
-// Grupos anidados
-sanitize_nested_group($data, $group_key, $rules)
-```
-
-**Ejemplo de uso:**
-```php
-// En lugar de:
-$enabled = !empty($data['enabled']);
-$bg_color = sanitize_hex_color($data['bg_color'] ?? '#FF8600');
-$text = sanitize_text_field($data['text'] ?? '');
-
-// Usar:
-$booleans = APUS_Sanitizer_Helper::sanitize_booleans($data, ['enabled', 'show_mobile']);
-$colors = APUS_Sanitizer_Helper::sanitize_colors($data, ['bg_color', 'text_color'], '#FFFFFF');
-$texts = APUS_Sanitizer_Helper::sanitize_texts($data, ['title', 'subtitle'], '');
-```
-
-#### Sanitizadores específicos (Strategy)
-
-Cada uno implementa un método estático `sanitize($data)`:
-
-- `APUS_TopBar_Sanitizer` - 16 campos (5 bool, 5 color, 5 text, 1 enum)
-- `APUS_Navbar_Sanitizer` - 38 campos (grupos: activación, colores, tipografía, efectos, etc.)
-- `APUS_LetsTalkButton_Sanitizer` - ~15 campos
-- `APUS_HeroSection_Sanitizer` - ~20 campos
-
----
-
-## 🔄 Flujo de Datos
-
-### Flujo completo: Usuario → Guardar configuración
-
-```
-1. Usuario modifica campo en admin panel
- ↓
-2. JavaScript detecta cambio (event listener)
- ↓
-3. admin-app.js recopila datos del formulario
- ↓
-4. Axios POST a /wp-admin/admin-ajax.php
- action: 'apus_save_settings'
- nonce: apusAdminData.nonce
- data: { component: 'topbar', ... }
- ↓
-5. WordPress enruta a APUS_Settings_Manager::ajax_save_settings()
- ↓
-6. Verifica nonce + permisos
- ↓
-7. Llama save_settings($data)
- ↓
-8. sanitize_settings($data) aplica sanitizador correcto
- ↓
-9. APUS_TopBar_Sanitizer::sanitize($data) limpia datos
- ↓
-10. APUS_DB_Manager::save_config() guarda en DB
- INSERT INTO wp_apus_theme_components
- (component_name, config_key, config_value, data_type, version)
- VALUES ('topbar', 'enabled', '1', 'boolean', '2.1.4')
- ON DUPLICATE KEY UPDATE
- config_value = '1', updated_at = NOW()
- ↓
-11. Retorna JSON success
- ↓
-12. JavaScript muestra notificación
-```
-
-### Flujo: Cargar configuración al abrir panel
-
-```
-1. Usuario navega a Apariencia > Tema APUs
- ↓
-2. APUS_Admin_Menu::render_admin_page() carga main.php
- ↓
-3. main.php renderiza HTML del formulario
- ↓
-4. admin-app.js se ejecuta (document.ready)
- ↓
-5. Axios GET a /wp-admin/admin-ajax.php
- action: 'apus_get_settings'
- component: 'topbar'
- ↓
-6. WordPress enruta a APUS_Settings_Manager::ajax_get_settings()
- ↓
-7. Llama get_settings('topbar')
- ↓
-8. APUS_DB_Manager::get_config('topbar')
- SELECT config_key, config_value, data_type
- FROM wp_apus_theme_components
- WHERE component_name = 'topbar'
- ↓
-9. parse_value() deserializa cada valor según data_type
- ↓
-10. Retorna JSON con configuración
- ↓
-11. JavaScript puebla formulario con valores
-```
-
----
-
-## 💾 Sistema de Base de Datos
-
-### Tabla: `wp_apus_theme_components`
-
-**Propósito:** Almacenar configuraciones de componentes del tema de forma estructurada
-
-**Ventajas vs wp_options:**
-- ✅ Versionado de configuraciones
-- ✅ Tipado fuerte (data_type)
-- ✅ Timestamps automáticos
-- ✅ Índices optimizados
-- ✅ Namespace por componente
-- ✅ Queries eficientes (1 query para todo el componente)
-
-**Ejemplo de datos:**
-
-```sql
--- Top Bar
-INSERT INTO wp_apus_theme_components VALUES
-(1, 'topbar', 'enabled', '1', 'boolean', '2.1.4', '2025-01-14 10:00:00', '2025-01-14 10:00:00'),
-(2, 'topbar', 'bg_color', '#FF8600', 'string', '2.1.4', '2025-01-14 10:00:00', '2025-01-14 10:00:00'),
-(3, 'topbar', 'text', '¡Oferta especial!', 'string', '2.1.4', '2025-01-14 10:00:00', '2025-01-14 10:00:00'),
-
--- Navbar
-(4, 'navbar', 'enabled', '1', 'boolean', '2.1.4', '2025-01-14 10:00:00', '2025-01-14 10:00:00'),
-(5, 'navbar', 'sticky', '1', 'boolean', '2.1.4', '2025-01-14 10:00:00', '2025-01-14 10:00:00'),
-
--- Theme Options (migradas)
-(6, 'theme', 'site_logo', '123', 'integer', '2.1.4', '2025-01-14 10:00:00', '2025-01-14 10:00:00'),
-(7, 'theme', 'enable_breadcrumbs', '1', 'boolean', '2.1.4', '2025-01-14 10:00:00', '2025-01-14 10:00:00');
-```
-
-### Estrategia de Queries
-
-```php
-// ❌ Malo: 16 queries para 16 configs
-for ($i = 1; $i <= 16; $i++) {
- $value = get_option('topbar_field_' . $i);
-}
-
-// ✅ Bueno: 1 query para todo el componente
-$config = $db->get_config('topbar'); // Array con todas las 16 configs
-```
-
----
-
-## 🔧 Sistema de Migración
-
-### Migración automática en `admin_init`
-
-```php
-// En init.php (líneas 54-68)
-add_action('admin_init', function() {
- $migrator = new APUS_Theme_Options_Migrator();
-
- if (!$migrator->is_migrated()) {
- $result = $migrator->migrate();
-
- if ($result['success']) {
- error_log('Migradas ' . $result['migrated'] . ' configuraciones');
- } else {
- error_log('Error: ' . $result['message']);
- }
- }
-});
-```
-
-### Sistema de Backups
-
-**Formato del backup:**
-```php
-// Guardado en wp_options
-$backup_key = 'apus_theme_options_backup_' . time();
-$backup_data = [
- 'timestamp' => time(),
- 'version' => APUS_ADMIN_PANEL_VERSION,
- 'options' => $old_options, // Array original
- 'count' => count($old_options)
-];
-update_option($backup_key, json_encode($backup_data));
-```
-
-**Operaciones disponibles:**
-- `list_backups()` - Lista todos los backups
-- `rollback($backup_id)` - Restaura desde backup
-- `delete_backup($backup_id)` - Elimina backup
-
----
-
-## 🎨 Frontend del Admin Panel
-
-### Página Principal: `pages/main.php`
-
-**Estructura HTML:**
-
-```html
-