From 465b879135088a31eecbe69763b7db3af17c6081 Mon Sep 17 00:00:00 2001 From: FrankZamora Date: Sun, 9 Nov 2025 15:13:55 -0600 Subject: [PATCH] Feat: Implementar Top Bar configurable - Issue #143 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implementación completa del componente Top Bar con 15 campos configurables desde el admin panel, siguiendo el algoritmo universal v2.0. ## Cambios Realizados ### Backend (PHP) - Agregados defaults del Top Bar a class-settings-manager.php - Implementada validación completa en class-validator.php (15 campos) - Implementada sanitización con sanitize_text_field, esc_url_raw y sanitize_hex_color - Modificado header.php con código 100% configurable usando wp_parse_args() ### Frontend (Admin Panel) - Creado tab HTML completo con 3 secciones: Activación, Contenido y Estilos - Implementado JavaScript para renderizado y recolección de datos - 15 campos configurables: enabled, visibility, icon, content, link, custom styles ### Infraestructura - Creado admin-panel/init.php para carga del módulo - Creada class-admin-menu.php con enqueue de Bootstrap 5 y assets - Creada estructura base CSS y JavaScript del admin - Ya cargado en functions.php línea 276 ## Características - Responsive: Control independiente mobile/desktop - Estilos personalizables: 4 colores + tamaño de fuente - Validación robusta: Límites de caracteres, URLs, colores hex - Defaults inteligentes: Valores seguros si no hay configuración 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- admin-panel/admin/assets/css/admin-panel.css | 169 +++++++++++ admin-panel/admin/assets/js/admin-app.js | 276 ++++++++++++++++++ .../admin/includes/class-admin-menu.php | 119 ++++++++ admin-panel/admin/pages/main.php | 177 +++++++++++ .../includes/class-settings-manager.php | 183 ++++++++++++ admin-panel/includes/class-validator.php | 100 +++++++ admin-panel/init.php | 25 ++ header.php | 92 +++++- 8 files changed, 1136 insertions(+), 5 deletions(-) create mode 100644 admin-panel/admin/assets/css/admin-panel.css create mode 100644 admin-panel/admin/assets/js/admin-app.js create mode 100644 admin-panel/admin/includes/class-admin-menu.php create mode 100644 admin-panel/admin/pages/main.php create mode 100644 admin-panel/includes/class-settings-manager.php create mode 100644 admin-panel/includes/class-validator.php create mode 100644 admin-panel/init.php diff --git a/admin-panel/admin/assets/css/admin-panel.css b/admin-panel/admin/assets/css/admin-panel.css new file mode 100644 index 00000000..6b335ff2 --- /dev/null +++ b/admin-panel/admin/assets/css/admin-panel.css @@ -0,0 +1,169 @@ +/** + * Admin Panel Styles + * + * Estilos base para el panel de administración + * + * @package Apus_Theme + * @since 2.0.0 + */ + +/* ======================================== + Container + ======================================== */ + +.apus-admin-panel { + max-width: 1400px; + margin: 20px auto; +} + +/* ======================================== + Header + ======================================== */ + +.apus-admin-panel h1 { + margin-bottom: 10px; +} + +.apus-admin-panel .description { + color: #666; + margin-bottom: 20px; +} + +/* ======================================== + Tabs + ======================================== */ + +.nav-tabs { + border-bottom: 2px solid #dee2e6; +} + +.nav-tabs .nav-link { + color: #666; + border: none; + border-bottom: 2px solid transparent; + margin-bottom: -2px; +} + +.nav-tabs .nav-link:hover { + color: #0073aa; + border-bottom-color: #0073aa; +} + +.nav-tabs .nav-link.active { + color: #0073aa; + font-weight: 600; + border-bottom-color: #0073aa; + background-color: transparent; +} + +/* ======================================== + Tab Content + ======================================== */ + +.tab-content { + background: #fff; + padding: 20px; + border: 1px solid #ddd; + border-radius: 4px; +} + +.tab-pane h3 { + margin-top: 0; + margin-bottom: 15px; + font-size: 18px; +} + +.tab-pane h4 { + margin-top: 25px; + margin-bottom: 10px; + font-size: 16px; + color: #333; +} + +/* ======================================== + Form Sections + ======================================== */ + +.form-section { + padding-bottom: 20px; + border-bottom: 1px solid #eee; +} + +.form-section:last-child { + border-bottom: none; +} + +.form-group { + margin-bottom: 15px; +} + +.form-group label { + display: block; + font-weight: 600; + margin-bottom: 5px; + color: #333; +} + +.form-group input[type="text"], +.form-group input[type="url"], +.form-group input[type="email"], +.form-group input[type="number"], +.form-group select, +.form-group textarea { + max-width: 600px; +} + +.form-group .form-text { + margin-top: 5px; + font-size: 13px; +} + +.form-group .form-text code { + background: #f5f5f5; + padding: 2px 5px; + border-radius: 3px; + font-size: 12px; +} + +/* ======================================== + Action Buttons + ======================================== */ + +.admin-actions { + padding: 20px; + background: #f9f9f9; + border-top: 1px solid #ddd; + border-radius: 4px; +} + +.admin-actions .button-primary { + font-size: 14px; + padding: 8px 20px; + height: auto; +} + +.admin-actions .button-primary i { + vertical-align: middle; +} + +/* ======================================== + Responsive + ======================================== */ + +@media (max-width: 782px) { + .apus-admin-panel { + margin: 10px; + } + + .tab-content { + padding: 15px; + } + + .form-group input[type="text"], + .form-group input[type="url"], + .form-group input[type="email"], + .form-group select, + .form-group textarea { + max-width: 100%; + } +} diff --git a/admin-panel/admin/assets/js/admin-app.js b/admin-panel/admin/assets/js/admin-app.js new file mode 100644 index 00000000..3a0535d1 --- /dev/null +++ b/admin-panel/admin/assets/js/admin-app.js @@ -0,0 +1,276 @@ +/** + * Admin Panel Application + * + * Gestión de configuraciones de componentes del tema + * + * @package Apus_Theme + * @since 2.0.0 + */ + +const AdminPanel = { + /** + * Estado de la aplicación + */ + STATE: { + settings: {}, + hasChanges: false, + isLoading: false + }, + + /** + * Inicializar aplicación + */ + init() { + this.bindEvents(); + this.loadSettings(); + }, + + /** + * Vincular eventos + */ + bindEvents() { + // Botón guardar + document.getElementById('saveSettings').addEventListener('click', () => { + this.saveSettings(); + }); + + // Detectar cambios en formularios + document.querySelectorAll('input, select, textarea').forEach(input => { + input.addEventListener('change', () => { + this.STATE.hasChanges = true; + document.getElementById('saveSettings').disabled = false; + }); + }); + + // Tabs + const tabs = document.querySelectorAll('.nav-tabs .nav-link'); + tabs.forEach(tab => { + tab.addEventListener('click', (e) => { + e.preventDefault(); + this.switchTab(tab); + }); + }); + }, + + /** + * Cambiar tab + */ + switchTab(tab) { + // Remover active de todos + document.querySelectorAll('.nav-tabs .nav-link').forEach(t => { + t.classList.remove('active'); + }); + document.querySelectorAll('.tab-pane').forEach(pane => { + pane.classList.remove('show', 'active'); + }); + + // Activar seleccionado + tab.classList.add('active'); + const targetId = tab.getAttribute('data-bs-target').substring(1); + const targetPane = document.getElementById(targetId); + if (targetPane) { + targetPane.classList.add('show', 'active'); + } + }, + + /** + * Cargar configuraciones desde servidor + */ + async loadSettings() { + this.STATE.isLoading = true; + this.showSpinner(true); + + try { + const response = await axios({ + method: 'POST', + url: apusAdminData.ajaxUrl, + data: new URLSearchParams({ + action: 'apus_get_settings', + nonce: apusAdminData.nonce + }) + }); + + if (response.data.success) { + this.STATE.settings = response.data.data; + this.renderAllComponents(); + } else { + this.showNotice('Error al cargar configuraciones', 'error'); + } + } catch (error) { + console.error('Error loading settings:', error); + this.showNotice('Error de conexión', 'error'); + } finally { + this.STATE.isLoading = false; + this.showSpinner(false); + } + }, + + /** + * Guardar configuraciones al servidor + */ + async saveSettings() { + if (!this.STATE.hasChanges) { + this.showNotice('No hay cambios para guardar', 'info'); + return; + } + + this.showSpinner(true); + + try { + const formData = this.collectFormData(); + + const response = await axios({ + method: 'POST', + url: apusAdminData.ajaxUrl, + headers: { + 'Content-Type': 'application/json' + }, + data: JSON.stringify({ + action: 'apus_save_settings', + nonce: apusAdminData.nonce, + ...formData + }) + }); + + if (response.data.success) { + this.STATE.hasChanges = false; + document.getElementById('saveSettings').disabled = true; + this.showNotice('Configuración guardada correctamente', 'success'); + } else { + this.showNotice(response.data.data.message || 'Error al guardar', 'error'); + } + } catch (error) { + console.error('Error saving settings:', error); + this.showNotice('Error de conexión', 'error'); + } finally { + this.showSpinner(false); + } + }, + + /** + * Recolectar datos del formulario + * Cada componente agregará su sección aquí + */ + collectFormData() { + return { + components: { + top_bar: { + enabled: document.getElementById('topBarEnabled').checked, + show_on_mobile: document.getElementById('topBarShowOnMobile').checked, + show_on_desktop: document.getElementById('topBarShowOnDesktop').checked, + icon_class: document.getElementById('topBarIconClass').value.trim(), + show_icon: document.getElementById('topBarShowIcon').checked, + highlight_text: document.getElementById('topBarHighlightText').value.trim(), + message_text: document.getElementById('topBarMessageText').value.trim(), + link_text: document.getElementById('topBarLinkText').value.trim(), + link_url: document.getElementById('topBarLinkUrl').value.trim(), + link_target: document.getElementById('topBarLinkTarget').value, + show_link: document.getElementById('topBarShowLink').checked, + custom_styles: { + background_color: this.getColorValue('topBarBgColor', ''), + text_color: this.getColorValue('topBarTextColor', ''), + highlight_color: this.getColorValue('topBarHighlightColor', ''), + link_hover_color: this.getColorValue('topBarLinkHoverColor', ''), + font_size: document.getElementById('topBarFontSize').value + } + } + // Navbar - Pendiente + // Hero - Pendiente + // Footer - Pendiente + } + }; + }, + + /** + * Renderizar todos los componentes + */ + renderAllComponents() { + const components = this.STATE.settings.components || {}; + + // Top Bar + if (components.top_bar) { + this.renderTopBar(components.top_bar); + } + // Navbar - Pendiente + // Hero - Pendiente + // Footer - Pendiente + }, + + /** + * Renderizar Top Bar + */ + renderTopBar(topBar) { + document.getElementById('topBarEnabled').checked = topBar.enabled !== undefined ? topBar.enabled : true; + document.getElementById('topBarShowOnMobile').checked = topBar.show_on_mobile !== undefined ? topBar.show_on_mobile : true; + document.getElementById('topBarShowOnDesktop').checked = topBar.show_on_desktop !== undefined ? topBar.show_on_desktop : true; + document.getElementById('topBarIconClass').value = topBar.icon_class || 'bi bi-megaphone-fill'; + document.getElementById('topBarShowIcon').checked = topBar.show_icon !== undefined ? topBar.show_icon : true; + document.getElementById('topBarHighlightText').value = topBar.highlight_text || 'Nuevo:'; + document.getElementById('topBarMessageText').value = topBar.message_text || 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.'; + document.getElementById('topBarLinkText').value = topBar.link_text || 'Ver Catálogo'; + document.getElementById('topBarLinkUrl').value = topBar.link_url || '/catalogo'; + document.getElementById('topBarLinkTarget').value = topBar.link_target || '_self'; + document.getElementById('topBarShowLink').checked = topBar.show_link !== undefined ? topBar.show_link : true; + + // Estilos personalizados + if (topBar.custom_styles) { + if (topBar.custom_styles.background_color) { + document.getElementById('topBarBgColor').value = topBar.custom_styles.background_color; + } + if (topBar.custom_styles.text_color) { + document.getElementById('topBarTextColor').value = topBar.custom_styles.text_color; + } + if (topBar.custom_styles.highlight_color) { + document.getElementById('topBarHighlightColor').value = topBar.custom_styles.highlight_color; + } + if (topBar.custom_styles.link_hover_color) { + document.getElementById('topBarLinkHoverColor').value = topBar.custom_styles.link_hover_color; + } + document.getElementById('topBarFontSize').value = topBar.custom_styles.font_size || 'normal'; + } + }, + + /** + * Utilidad: Obtener valor de color con fallback + */ + getColorValue(inputId, defaultValue) { + const input = document.getElementById(inputId); + const value = input ? input.value.trim() : ''; + return value || defaultValue; + }, + + /** + * Utilidad: Mostrar spinner + */ + showSpinner(show) { + const spinner = document.querySelector('.spinner'); + if (spinner) { + spinner.style.display = show ? 'inline-block' : 'none'; + } + }, + + /** + * Utilidad: Mostrar notificación + */ + showNotice(message, type = 'info') { + // WordPress admin notices + const noticeDiv = document.createElement('div'); + noticeDiv.className = `notice notice-${type} is-dismissible`; + noticeDiv.innerHTML = `

${message}

`; + + const container = document.querySelector('.apus-admin-panel'); + if (container) { + container.insertBefore(noticeDiv, container.firstChild); + + // Auto-dismiss después de 5 segundos + setTimeout(() => { + noticeDiv.remove(); + }, 5000); + } + } +}; + +// Inicializar cuando el DOM esté listo +document.addEventListener('DOMContentLoaded', () => { + AdminPanel.init(); +}); diff --git a/admin-panel/admin/includes/class-admin-menu.php b/admin-panel/admin/includes/class-admin-menu.php new file mode 100644 index 00000000..3378bb9f --- /dev/null +++ b/admin-panel/admin/includes/class-admin-menu.php @@ -0,0 +1,119 @@ + admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('apus_admin_nonce') + )); + } +} + +// Instanciar clase +new APUS_Admin_Menu(); diff --git a/admin-panel/admin/pages/main.php b/admin-panel/admin/pages/main.php new file mode 100644 index 00000000..73ce46f1 --- /dev/null +++ b/admin-panel/admin/pages/main.php @@ -0,0 +1,177 @@ + + +
+

+

Configure los componentes del tema Apus

+ + + + + +
+ +
+

Configuración Top Bar

+

Personaliza la barra de notificación superior del sitio.

+ + +
+

Activación y Visibilidad

+
+ +

Si está desactivado, el Top Bar no se mostrará en el sitio.

+
+
+ +
+
+ +
+
+ + +
+

Contenido

+ +
+
+
+ + +

Usa clases de Bootstrap Icons

+
+
+
+
+ +
+
+
+ +
+ + +

Máximo 30 caracteres. Aparece en negritas.

+
+ +
+ + +

Máximo 250 caracteres. Campo obligatorio.

+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+
+
+
+ + +
+

Estilos Personalizados

+

Deja en blanco para usar los colores del tema por defecto.

+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+ +
+ + +
+
+
+
+ + +
+ + +
+
diff --git a/admin-panel/includes/class-settings-manager.php b/admin-panel/includes/class-settings-manager.php new file mode 100644 index 00000000..750839c3 --- /dev/null +++ b/admin-panel/includes/class-settings-manager.php @@ -0,0 +1,183 @@ +get_defaults(); + + return wp_parse_args($settings, $defaults); + } + + /** + * Guardar configuraciones + */ + public function save_settings($data) { + // Validar + $validator = new APUS_Validator(); + $validation = $validator->validate($data); + + if (!$validation['valid']) { + return array( + 'success' => false, + 'message' => 'Error de validación', + 'errors' => $validation['errors'] + ); + } + + // Sanitizar + $sanitized = $this->sanitize_settings($data); + + // Agregar metadata + $sanitized['version'] = APUS_ADMIN_PANEL_VERSION; + $sanitized['updated_at'] = current_time('mysql'); + + // Guardar + update_option(self::OPTION_NAME, $sanitized, false); + + return array( + 'success' => true, + 'message' => 'Configuración guardada correctamente' + ); + } + + /** + * Valores por defecto + * NOTA: Aquí se agregan los defaults de cada componente + */ + public function get_defaults() { + return array( + 'version' => APUS_ADMIN_PANEL_VERSION, + 'components' => array( + 'top_bar' => array( + 'enabled' => true, + 'show_on_mobile' => true, + 'show_on_desktop' => true, + 'icon_class' => 'bi bi-megaphone-fill', + 'show_icon' => true, + 'highlight_text' => 'Nuevo:', + 'message_text' => 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.', + 'link_text' => 'Ver Catálogo', + 'link_url' => '/catalogo', + 'link_target' => '_self', + 'show_link' => true, + 'custom_styles' => array( + 'background_color' => '', + 'text_color' => '', + 'highlight_color' => '', + 'link_hover_color' => '', + 'font_size' => 'normal' + ) + ) + // Navbar - Pendiente + // Hero - Pendiente + // Footer - Pendiente + ) + ); + } + + /** + * Sanitizar configuraciones + * NOTA: Aquí se agrega sanitización de cada componente + */ + public function sanitize_settings($data) { + $sanitized = array( + 'components' => array() + ); + + // Sanitizar Top Bar + if (isset($data['components']['top_bar'])) { + $top_bar = $data['components']['top_bar']; + + $sanitized['components']['top_bar'] = array( + 'enabled' => !empty($top_bar['enabled']), + 'show_on_mobile' => !empty($top_bar['show_on_mobile']), + 'show_on_desktop' => !empty($top_bar['show_on_desktop']), + 'icon_class' => sanitize_text_field($top_bar['icon_class'] ?? ''), + 'show_icon' => !empty($top_bar['show_icon']), + 'highlight_text' => sanitize_text_field($top_bar['highlight_text'] ?? ''), + 'message_text' => sanitize_text_field($top_bar['message_text'] ?? ''), + 'link_text' => sanitize_text_field($top_bar['link_text'] ?? ''), + 'link_url' => esc_url_raw($top_bar['link_url'] ?? ''), + 'link_target' => in_array($top_bar['link_target'] ?? '', array('_self', '_blank')) ? $top_bar['link_target'] : '_self', + 'show_link' => !empty($top_bar['show_link']), + 'custom_styles' => array( + 'background_color' => sanitize_hex_color($top_bar['custom_styles']['background_color'] ?? ''), + 'text_color' => sanitize_hex_color($top_bar['custom_styles']['text_color'] ?? ''), + 'highlight_color' => sanitize_hex_color($top_bar['custom_styles']['highlight_color'] ?? ''), + 'link_hover_color' => sanitize_hex_color($top_bar['custom_styles']['link_hover_color'] ?? ''), + 'font_size' => in_array($top_bar['custom_styles']['font_size'] ?? '', array('small', 'normal', 'large')) ? $top_bar['custom_styles']['font_size'] : 'normal' + ) + ); + } + + return $sanitized; + } + + /** + * AJAX: Obtener configuraciones + */ + public function ajax_get_settings() { + check_ajax_referer('apus_admin_nonce', 'nonce'); + + if (!current_user_can('manage_options')) { + wp_send_json_error('Permisos insuficientes'); + } + + $settings = $this->get_settings(); + wp_send_json_success($settings); + } + + /** + * AJAX: Guardar configuraciones + */ + public function ajax_save_settings() { + check_ajax_referer('apus_admin_nonce', 'nonce'); + + if (!current_user_can('manage_options')) { + wp_send_json_error('Permisos insuficientes'); + } + + $data = json_decode(file_get_contents('php://input'), true); + + if (!$data) { + wp_send_json_error('Datos inválidos'); + } + + $result = $this->save_settings($data); + + if ($result['success']) { + wp_send_json_success($result); + } else { + wp_send_json_error($result); + } + } +} + +// Instanciar clase +new APUS_Settings_Manager(); diff --git a/admin-panel/includes/class-validator.php b/admin-panel/includes/class-validator.php new file mode 100644 index 00000000..f3dc311e --- /dev/null +++ b/admin-panel/includes/class-validator.php @@ -0,0 +1,100 @@ + false, 'errors' => $errors); + } + + // Validar Top Bar + if (isset($data['components']['top_bar'])) { + $top_bar_errors = $this->validate_top_bar($data['components']['top_bar']); + $errors = array_merge($errors, $top_bar_errors); + } + + return array( + 'valid' => empty($errors), + 'errors' => $errors + ); + } + + /** + * Validar Top Bar + */ + public function validate_top_bar($top_bar) { + $errors = array(); + + // Validar icon_class + if (!empty($top_bar['icon_class']) && strlen($top_bar['icon_class']) > 50) { + $errors[] = 'La clase del icono no puede exceder 50 caracteres'; + } + + // Validar highlight_text + if (!empty($top_bar['highlight_text']) && strlen($top_bar['highlight_text']) > 30) { + $errors[] = 'El texto destacado no puede exceder 30 caracteres'; + } + + // Validar message_text + if (empty($top_bar['message_text'])) { + $errors[] = 'El mensaje principal es obligatorio'; + } elseif (strlen($top_bar['message_text']) > 250) { + $errors[] = 'El mensaje principal no puede exceder 250 caracteres'; + } + + // Validar link_text + if (!empty($top_bar['link_text']) && strlen($top_bar['link_text']) > 50) { + $errors[] = 'El texto del enlace no puede exceder 50 caracteres'; + } + + // Validar link_url + if (!empty($top_bar['link_url']) && !filter_var($top_bar['link_url'], FILTER_VALIDATE_URL)) { + $errors[] = 'La URL del enlace no es válida'; + } + + // Validar link_target + if (!in_array($top_bar['link_target'] ?? '', array('_self', '_blank'))) { + $errors[] = 'El target del enlace debe ser _self o _blank'; + } + + // Validar colores + if (!empty($top_bar['custom_styles']['background_color']) && !preg_match('/^#[a-f0-9]{6}$/i', $top_bar['custom_styles']['background_color'])) { + $errors[] = 'El color de fondo debe ser un color hexadecimal válido'; + } + if (!empty($top_bar['custom_styles']['text_color']) && !preg_match('/^#[a-f0-9]{6}$/i', $top_bar['custom_styles']['text_color'])) { + $errors[] = 'El color de texto debe ser un color hexadecimal válido'; + } + if (!empty($top_bar['custom_styles']['highlight_color']) && !preg_match('/^#[a-f0-9]{6}$/i', $top_bar['custom_styles']['highlight_color'])) { + $errors[] = 'El color del highlight debe ser un color hexadecimal válido'; + } + if (!empty($top_bar['custom_styles']['link_hover_color']) && !preg_match('/^#[a-f0-9]{6}$/i', $top_bar['custom_styles']['link_hover_color'])) { + $errors[] = 'El color hover del enlace debe ser un color hexadecimal válido'; + } + + // Validar font_size + if (!in_array($top_bar['custom_styles']['font_size'] ?? '', array('small', 'normal', 'large'))) { + $errors[] = 'El tamaño de fuente debe ser small, normal o large'; + } + + return $errors; + } +} diff --git a/admin-panel/init.php b/admin-panel/init.php new file mode 100644 index 00000000..8356af50 --- /dev/null +++ b/admin-panel/init.php @@ -0,0 +1,25 @@ + data-bs-spy="scroll" data-bs-target=".toc-container" data-bs-offset="100"> - -
+ + true, + 'show_on_mobile' => true, + 'show_on_desktop' => true, + 'icon_class' => 'bi bi-megaphone-fill', + 'show_icon' => true, + 'highlight_text' => 'Nuevo:', + 'message_text' => 'Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025.', + 'link_text' => 'Ver Catálogo', + 'link_url' => '/catalogo', + 'link_target' => '_self', + 'show_link' => true, + 'custom_styles' => array( + 'background_color' => '', + 'text_color' => '', + 'highlight_color' => '', + 'link_hover_color' => '', + 'font_size' => 'normal' + ) +); + +$top_bar_config = isset($settings['components']['top_bar']) ? $settings['components']['top_bar'] : array(); +$top_bar = wp_parse_args($top_bar_config, $top_bar_defaults); + +// Verificar si está habilitado +if ($top_bar['enabled']): + // Construir clases CSS + $classes = array('top-notification-bar'); + if (!$top_bar['show_on_mobile']) { + $classes[] = 'd-none'; + $classes[] = 'd-md-block'; + } + if (!$top_bar['show_on_desktop']) { + $classes[] = 'd-md-none'; + } + $class_attr = implode(' ', $classes); + + // Construir estilos inline + $inline_styles = array(); + if (!empty($top_bar['custom_styles']['background_color'])) { + $inline_styles[] = 'background-color: ' . esc_attr($top_bar['custom_styles']['background_color']); + } + if (!empty($top_bar['custom_styles']['text_color'])) { + $inline_styles[] = 'color: ' . esc_attr($top_bar['custom_styles']['text_color']); + } + + // Font size + $font_size_map = array( + 'small' => '0.8rem', + 'normal' => '0.9rem', + 'large' => '1rem' + ); + if (isset($font_size_map[$top_bar['custom_styles']['font_size']])) { + $inline_styles[] = 'font-size: ' . $font_size_map[$top_bar['custom_styles']['font_size']]; + } + + $style_attr = !empty($inline_styles) ? ' style="' . implode('; ', $inline_styles) . '"' : ''; +?> +
>
- - Nuevo: Accede a más de 200,000 Análisis de Precios Unitarios actualizados para 2025. - Ver Catálogo + + + + + + + style="color: "> + + + + + + + + + data-hover-color="" + > + + +
+