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

444 lines
13 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 💾 PERSISTENCIA EN ARCHIVOS JSON
## Estructura del Archivo config.json
Cada componente DEBE tener su archivo `config.json` en su carpeta:
```json
{
"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
```javascript
/**
* 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
```javascript
/**
* 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
```javascript
/**
* 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
```javascript
/**
* 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:
```javascript
/**
* 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
```javascript
/**
* 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
```javascript
/**
* 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
```javascript
/**
* 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](README.md)