Files
roi-theme/_planificacion/01-design-system/12-PERSISTENCIA-JSON.md
FrankZamora 0f6387ab46 refactor: reorganizar openspec y planificacion con spec recaptcha
- renombrar openspec/ a _openspec/ (carpeta auxiliar)
- mover specs de features a changes/
- crear specs base: arquitectura-limpia, estandares-codigo, nomenclatura
- migrar _planificacion/ con design-system y roi-theme-template
- agregar especificacion recaptcha anti-spam (proposal, tasks, spec)
- corregir rutas y referencias en todas las specs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 15:30:45 -06:00

13 KiB
Raw Blame History

💾 PERSISTENCIA EN ARCHIVOS JSON

Estructura del Archivo config.json

Cada componente DEBE tener su archivo config.json en su carpeta:

{
  "component": "[component-name]",
  "version": "1.0",
  "lastModified": "2025-01-15T10:30:00Z",
  "config": {
    "enabled": true,
    "showOnMobile": true,
    "showOnDesktop": true,
    "bgColor": "#0E2337",
    "textColor": "#ffffff",
    "highlightColor": "#FF8600",
    "linkHoverColor": "#FF6B35",
    "fontSize": "normal",
    "showIcon": true,
    "iconClass": "bi bi-icon-name",
    "highlightText": "Texto destacado",
    "messageText": "Mensaje principal del componente",
    "showLink": true,
    "linkText": "Texto del enlace",
    "linkUrl": "/ruta",
    "linkTarget": "_self"
  }
}

Campos del Metadata

Campo Tipo Descripción
component string Nombre del componente (kebab-case)
version string Versión del config (semver)
lastModified string Timestamp ISO 8601 de última modificación
config object Objeto con la configuración del componente

Funciones de Persistencia

Guardar Configuración

/**
 * Guarda la configuración en archivo JSON
 */
async function saveConfig() {
    const config = {
        component: '[component-name]',  // Nombre del componente en kebab-case
        version: '1.0',
        lastModified: new Date().toISOString(),
        config: {
            enabled: document.getElementById('enabled').checked,
            showOnMobile: document.getElementById('showOnMobile').checked,
            showOnDesktop: document.getElementById('showOnDesktop').checked,
            bgColor: document.getElementById('bgColor').value,
            textColor: document.getElementById('textColor').value,
            highlightColor: document.getElementById('highlightColor').value,
            linkHoverColor: document.getElementById('linkHoverColor').value,
            fontSize: document.getElementById('fontSize').value,
            showIcon: document.getElementById('showIcon').checked,
            iconClass: document.getElementById('iconClass').value,
            highlightText: document.getElementById('highlightText').value,
            messageText: document.getElementById('messageText').value,
            showLink: document.getElementById('showLink').checked,
            linkText: document.getElementById('linkText').value,
            linkUrl: document.getElementById('linkUrl').value,
            linkTarget: document.getElementById('linkTarget').value
        }
    };

    try {
        // Guardar en archivo JSON
        const response = await fetch('./config.json', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(config, null, 2)
        });

        if (response.ok) {
            console.log('💾 Configuración guardada exitosamente');
            showNotification('Cambios guardados', 'success');
        } else {
            throw new Error('Error al guardar');
        }
    } catch (error) {
        console.error('❌ Error al guardar:', error);
        showNotification('Error al guardar cambios', 'error');
    }
}

Cargar Configuración

/**
 * Carga la configuración desde archivo JSON
 */
async function loadConfig() {
    try {
        const response = await fetch('./config.json');

        if (!response.ok) {
            throw new Error('Config file not found');
        }

        const data = await response.json();
        const config = data.config;

        // Validar estructura
        if (!validateConfigStructure(data)) {
            throw new Error('Invalid config structure');
        }

        // Aplicar valores cargados
        if (config.enabled !== undefined) {
            document.getElementById('enabled').checked = config.enabled;
        }
        if (config.showOnMobile !== undefined) {
            document.getElementById('showOnMobile').checked = config.showOnMobile;
        }
        if (config.showOnDesktop !== undefined) {
            document.getElementById('showOnDesktop').checked = config.showOnDesktop;
        }
        if (config.bgColor) {
            document.getElementById('bgColor').value = config.bgColor;
            document.getElementById('bgColorValue').textContent = config.bgColor.toUpperCase();
        }
        if (config.textColor) {
            document.getElementById('textColor').value = config.textColor;
            document.getElementById('textColorValue').textContent = config.textColor.toUpperCase();
        }
        if (config.highlightColor) {
            document.getElementById('highlightColor').value = config.highlightColor;
            document.getElementById('highlightColorValue').textContent = config.highlightColor.toUpperCase();
        }
        if (config.fontSize) {
            document.getElementById('fontSize').value = config.fontSize;
        }
        if (config.showIcon !== undefined) {
            document.getElementById('showIcon').checked = config.showIcon;
        }
        if (config.iconClass) {
            document.getElementById('iconClass').value = config.iconClass;
        }
        if (config.highlightText) {
            document.getElementById('highlightText').value = config.highlightText;
        }
        if (config.messageText) {
            document.getElementById('messageText').value = config.messageText;
            // Actualizar contador si existe
            const counter = document.getElementById('messageTextCount');
            if (counter) {
                counter.textContent = config.messageText.length;
            }
        }
        if (config.showLink !== undefined) {
            document.getElementById('showLink').checked = config.showLink;
        }
        if (config.linkText) {
            document.getElementById('linkText').value = config.linkText;
        }
        if (config.linkUrl) {
            document.getElementById('linkUrl').value = config.linkUrl;
        }
        if (config.linkTarget) {
            document.getElementById('linkTarget').value = config.linkTarget;
        }

        console.log('📂 Configuración cargada:', config);
        updatePreview();

    } catch (error) {
        console.log(' No se encontró config.json, usando valores por defecto');
        resetToDefaults();
    }
}

Validación de JSON

/**
 * Valida la estructura del archivo config.json
 */
function validateConfigStructure(data) {
    const required = ['component', 'version', 'config'];

    // Verificar campos requeridos
    for (const field of required) {
        if (!data.hasOwnProperty(field)) {
            console.error(`❌ Campo requerido faltante: ${field}`);
            return false;
        }
    }

    // Verificar que config sea un objeto
    if (typeof data.config !== 'object' || data.config === null) {
        console.error('❌ El campo "config" debe ser un objeto');
        return false;
    }

    return true;
}

Sistema de Notificaciones

/**
 * Muestra notificación temporal
 */
function showNotification(message, type = 'success') {
    const notification = document.createElement('div');
    notification.className = `alert alert-${type === 'success' ? 'success' : 'danger'} position-fixed top-0 start-50 translate-middle-x mt-3`;
    notification.style.zIndex = '9999';
    notification.innerHTML = `
        <i class="bi bi-${type === 'success' ? 'check-circle' : 'exclamation-circle'} me-2"></i>
        ${message}
    `;

    document.body.appendChild(notification);

    setTimeout(() => {
        notification.remove();
    }, 3000);
}

// Uso
showNotification('Cambios guardados', 'success');
showNotification('Error al guardar cambios', 'error');
showNotification('Configuración restaurada', 'success');

Migración de localStorage a JSON

Si tienes datos en localStorage que quieres migrar a JSON:

/**
 * Migra la configuración de localStorage a archivo JSON
 */
async function migrateFromLocalStorage() {
    const saved = localStorage.getItem('topBarConfig');

    if (!saved) {
        console.log(' No hay datos en localStorage para migrar');
        return;
    }

    const config = JSON.parse(saved);

    // Crear estructura de config.json
    const jsonConfig = {
        component: '[component-name]',  // Nombre del componente en kebab-case
        version: '1.0',
        lastModified: new Date().toISOString(),
        config: config
    };

    try {
        const response = await fetch('./config.json', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(jsonConfig, null, 2)
        });

        if (response.ok) {
            console.log('✅ Migración exitosa de localStorage a JSON');
            // Opcional: limpiar localStorage
            localStorage.removeItem('topBarConfig');
        }
    } catch (error) {
        console.error('❌ Error al migrar:', error);
    }
}

Manejo de Errores

/**
 * Guarda con manejo completo de errores
 */
async function saveConfigWithErrorHandling() {
    // 1. Validar antes de guardar
    if (!validateForm()) {
        return;
    }

    // 2. Preparar datos
    const config = buildConfigObject();

    // 3. Intentar guardar
    try {
        const response = await fetch('./config.json', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(config, null, 2)
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        console.log('💾 Configuración guardada exitosamente');
        showNotification('Cambios guardados correctamente', 'success');

    } catch (error) {
        console.error('❌ Error al guardar:', error);

        // Notificar al usuario
        showNotification('Error al guardar. Verifica la conexión.', 'error');

        // Fallback: guardar en localStorage temporalmente
        localStorage.setItem('topBarConfig_backup', JSON.stringify(config));
        console.log('💾 Backup guardado en localStorage');
    }
}

/**
 * Construye el objeto de configuración
 */
function buildConfigObject() {
    return {
        component: '[component-name]',  // Nombre del componente en kebab-case
        version: '1.0',
        lastModified: new Date().toISOString(),
        config: {
            enabled: document.getElementById('enabled').checked,
            bgColor: document.getElementById('bgColor').value,
            // ... resto de campos
        }
    };
}

Backup y Restore

Crear Backup

/**
 * Crea un backup de la configuración actual
 */
async function createBackup() {
    try {
        const response = await fetch('./config.json');
        const config = await response.json();

        // Agregar timestamp al nombre del backup
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        const backupName = `config_backup_${timestamp}.json`;

        // Descargar como archivo
        const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = backupName;
        a.click();
        URL.revokeObjectURL(url);

        showNotification('Backup creado exitosamente', 'success');
    } catch (error) {
        console.error('❌ Error al crear backup:', error);
        showNotification('Error al crear backup', 'error');
    }
}

Restaurar Backup

/**
 * Restaura un backup
 */
function restoreBackup(file) {
    const reader = new FileReader();

    reader.onload = async function(e) {
        try {
            const config = JSON.parse(e.target.result);

            // Validar estructura
            if (!validateConfigStructure(config)) {
                throw new Error('Invalid backup structure');
            }

            // Aplicar configuración
            const data = config.config;
            Object.keys(data).forEach(key => {
                const element = document.getElementById(key);
                if (element) {
                    if (element.type === 'checkbox') {
                        element.checked = data[key];
                    } else {
                        element.value = data[key];
                    }
                }
            });

            updatePreview();
            showNotification('Backup restaurado exitosamente', 'success');

        } catch (error) {
            console.error('❌ Error al restaurar backup:', error);
            showNotification('Error al restaurar backup', 'error');
        }
    };

    reader.readAsText(file);
}

Volver al Índice

← Volver al README