- setSilent(): carga masiva sin disparar observers - Formato extendido auto_validate: opcion=descripcion - Genera comentarios multilínea en archivo config - 100% compatible hacia atrás 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
14 KiB
ZCATCONFIG - Sistema de Configuracion Declarativo
IMPORTANTE PARA CLAUDE: Lee la seccion "PROTOCOLO DE INICIO" antes de hacer cualquier cosa.
PROTOCOLO DE INICIO (LEER PRIMERO)
Paso 1: Leer normas del equipo
/mnt/cello2/arno/re/recode/teamdocs/LAST_UPDATE.md
Paso 2: Verificar estado del proyecto
cd /mnt/cello2/arno/re/recode/zig/zcatconfig
git status
git log --oneline -5
PATH=/mnt/cello2/arno/re/recode/zig/zig-0.15.2/zig-x86_64-linux-0.15.2:$PATH zig build test
INFORMACION DEL PROYECTO
| Campo | Valor |
|---|---|
| Nombre | zcatconfig |
| Version | v0.2.4 |
| Fecha inicio | 2025-12-17 |
| Estado | FUNCIONAL - Integrado en zsimifactu (arquitectura 3 fuentes) |
| Lenguaje | Zig 0.15.2 |
| Dependencias | Ninguna (Zig puro) |
Descripcion
zcatconfig es una libreria para gestion de configuracion declarativa:
- Definicion de variables con metadatos (tipo, default, descripcion, categoria)
- Persistencia a archivo de texto legible con comentarios
- Validacion de valores (rangos, enums, tipos)
- Sistema Get/Set generico con inline for + @field
- ConfigManager: Gestor autonomo con observers y auto-save
- FileWatcher: Detecta cambios en archivo via mtime (hot-reload)
- loadFromString: Carga config desde string embebido
ORIGEN: zsimifactu config/
Esta libreria extrae y generaliza el sistema de configuracion implementado en zsimifactu.
Archivos originales en zsimifactu:
src/config/
├── config.zig # Re-exports publicos
├── types.zig # ConfigVariable, ConfigVarType, Color
├── variables.zig # Definiciones declarativas (proyecto-especifico)
├── structures.zig # Config struct (proyecto-especifico)
├── engine.zig # Meta-engine Get/Set con validacion
└── persistence.zig # Load/Save archivo texto
Que se extrae a zcatconfig:
- types.zig (completo)
- engine.zig (generalizado)
- persistence.zig (generalizado)
Que queda en el proyecto consumidor:
- variables.zig (definiciones especificas del proyecto)
- structures.zig (struct Config especifico)
ARQUITECTURA
┌─────────────────────────────────────────────────────────────────┐
│ PROYECTO CONSUMIDOR │
│ │
│ variables.zig: │
│ pub const config_variables = [_]ConfigVariable{ │
│ .{ .name = "auto_save", .var_type = .boolean, ... }, │
│ .{ .name = "font_size", .var_type = .integer, ... }, │
│ }; │
│ │
│ structures.zig: │
│ pub const Config = struct { │
│ auto_save: bool = true, │
│ font_size: i32 = 14, │
│ }; │
├─────────────────────────────────────────────────────────────────┤
│ ZCATCONFIG │
│ │
│ types.zig: │
│ ConfigVariable, ConfigVarType, ConfigResult, Color │
│ │
│ engine.zig: │
│ Engine(comptime variables, comptime ConfigStruct) │
│ - get(), set(), getByName(), setByName() │
│ - Validacion automatica │
│ │
│ persistence.zig: │
│ - load(config, path, variables) │
│ - save(config, path, variables) │
│ - Formato: @variable_name: valor # comentario │
└─────────────────────────────────────────────────────────────────┘
API PROPUESTA
Uso basico
const zcatconfig = @import("zcatconfig");
// En el proyecto consumidor:
const variables = @import("config/variables.zig");
const Config = @import("config/structures.zig").Config;
// Crear engine tipado
const Engine = zcatconfig.Engine(variables.config_variables, Config);
// Uso
var config = Config{};
Engine.load(&config, "app_config.txt") catch {};
config.font_size = 16;
try Engine.save(&config, "app_config.txt");
Tipos de variables soportados
| Tipo | Zig Type | Formato archivo |
|---|---|---|
| boolean | bool | Si/No |
| integer | i32 | 123 |
| float | f32 | 1.5 |
| string | []const u8 | texto libre |
| color | Color | RGB(r,g,b) |
| enum_tipo | enum | NombreVariante |
COMANDOS
# Compilar
PATH=/mnt/cello2/arno/re/recode/zig/zig-0.15.2/zig-x86_64-linux-0.15.2:$PATH zig build
# Tests
PATH=/mnt/cello2/arno/re/recode/zig/zig-0.15.2/zig-x86_64-linux-0.15.2:$PATH zig build test
RUTAS
# Este proyecto
/mnt/cello2/arno/re/recode/zig/zcatconfig/
# Proyecto origen (referencia)
/mnt/cello2/arno/re/recode/zig/zsimifactu/src/config/
# Documentacion equipo
/mnt/cello2/arno/re/recode/teamdocs/
PLAN DE TRABAJO
Fase 1: Estructura base ✅
- Crear proyecto (build.zig, CLAUDE.md)
- Extraer types.zig de zsimifactu
- Adaptar engine.zig (parametrizar variables y Config)
- Adaptar persistence.zig
Fase 2: Generalizacion ✅
- Engine generico con comptime
- Tests unitarios
- Documentacion API
Fase 3: Integracion ✅
- Integrar en zsimifactu como dependencia
- Verificar que zsimifactu funciona igual
Fase 4: ConfigManager ✅
- ConfigManager con loadOrCreate, auto-save, observers
- Sistema de observers con contexto
- isDirty tracking
Fase 5: FileWatcher + Utils ✅
- FileWatcher para hot-reload (mtime polling)
- loadFromString para defaults embebidos
- updateMtime para evitar auto-detectar cambios propios
API PRINCIPAL
ConfigManager (gestor autonomo)
const zcatconfig = @import("zcatconfig");
// Crear ConfigManager tipado
const MyConfigManager = zcatconfig.ConfigManager(&variables, MyConfig, "mi_app");
// Opcion 1: init() - Carga o crea archivo automaticamente
var manager = try MyConfigManager.init(allocator, "config.txt");
defer manager.deinit(); // Auto-save si dirty
// Opcion 2: initDeferred() - NO carga/crea archivo (control manual)
var manager = MyConfigManager.initDeferred(allocator, "config.txt");
// Luego cargar manualmente cuando sea necesario:
try manager.load(); // o manager.save() para crear
// Acceso
const cfg = manager.getConfig();
try manager.set("@mi_variable", "nuevo_valor");
// Observers
manager.addObserver(myCallback, my_context);
initDeferred() - Control manual de archivo (v0.2.3)
Cuando necesitas controlar cuándo se crea/carga el archivo (ej: arquitectura 3 fuentes):
// Caso de uso: File > BD > Defaults
// 1. Crear manager SIN cargar archivo
var manager = MyConfigManager.initDeferred(allocator, "config.txt");
// 2. Verificar si existe archivo
const archivo_existe = std.fs.cwd().access("config.txt", .{}) catch false;
// 3. Verificar si BD tiene config
const bd_tiene_config = checkDatabase();
// 4. Aplicar prioridad
if (archivo_existe) {
try manager.load(); // Cargar de archivo
syncToBd(&manager); // Sync a BD
} else if (bd_tiene_config) {
loadFromBd(&manager); // Cargar de BD
try manager.save(); // Crear archivo
} else {
try manager.save(); // Crear con defaults
syncToBd(&manager); // Sync defaults a BD
}
Por qué initDeferred()?
init()llamaloadOrCreate()que crea el archivo con defaults- Si BD tiene valores, el archivo ya existe antes de poder verificar
initDeferred()permite verificar BD primero, luego decidir
FileWatcher (hot-reload)
var watcher = zcatconfig.FileWatcher.init("config.txt", 1000); // check cada 1s
// En main loop:
if (watcher.checkForChanges()) {
// Recargar configuracion
}
// Despues de guardar:
watcher.updateMtime(); // Evitar detectar nuestro propio cambio
loadFromString (defaults embebidos)
const defaults = @embedFile("defaults.txt");
zcatconfig.loadFromString(&variables, Config, &config, defaults);
VALIDACION DE RANGOS (v0.2.3)
Rangos negativos soportados
La validación de rangos ahora soporta valores mínimos negativos:
// Definición de variable con rango negativo
.{ .name = "offset_x", .var_type = .integer, .range = "-100-100", ... }
.{ .name = "offset_y", .var_type = .integer, .range = "-50-50", ... }
.{ .name = "temperature", .var_type = .float, .range = "-40.0-85.0", ... }
Bug corregido (commit 957767d)
Problema: splitScalar('-') dividía "-100-100" como ["", "100", "100"]
Solución: Buscar el separador '-' excluyendo el primer carácter si es negativo:
const separator_pos = blk: {
const start: usize = if (range.len > 0 and range[0] == '-') 1 else 0;
for (range[start..], start..) |c, i| {
if (c == '-') break :blk i;
}
return false;
};
Formatos de rango válidos
| Rango | Mínimo | Máximo | Notas |
|---|---|---|---|
"0-100" |
0 | 100 | Positivo simple |
"-100-100" |
-100 | 100 | Negativo a positivo |
"-50-50" |
-50 | 50 | Centrado en cero |
"-100-0" |
-100 | 0 | Solo negativos |
setSilent() - Carga masiva sin observers (v0.2.4)
Problema
Al cargar config desde BD, cada set() dispara observers que escriben de vuelta a BD → loop infinito de logs.
Solución
setSilent() establece valores SIN notificar observers:
// Cargar desde BD sin disparar observers
var stmt = try db.prepare("SELECT config_key, config_value FROM config_backup");
while (try stmt.step()) {
const key = stmt.columnText(0) orelse continue;
const value = stmt.columnText(1) orelse continue;
// Usar setSilent en vez de set
manager.setSilent(key, value) catch continue;
}
API
/// Establece valor SIN notificar observers
/// Útil para carga masiva desde BD
pub fn setSilent(self: *Self, key: []const u8, value: []const u8) ConfigError!void
DESCRIPCIONES EXTENDIDAS EN ARCHIVO CONFIG (v0.2.4)
Problema
El archivo de configuración mostraba opciones pero no explicaba qué hacía cada una:
@validacion_nif_modo: Estricto # Modo validación NIF [Estricto,Permisivo,Desactivado]
El usuario ve las opciones pero no sabe qué significa cada una.
Solución: Formato extendido en auto_validate
En config_variables, usar formato opcion=descripcion:
// Formato simple (sin descripciones):
.{ "mi_var", .string, .cat, "Op1,Op2,Op3", "Descripción", ... }
// Formato extendido (con descripciones):
.{ "mi_var", .string, .cat, "Op1=Hace X,Op2=Hace Y,Op3=Hace Z", "Descripción", ... }
Resultado en archivo config
Formato simple (sin =):
@mi_var: Op1 # Descripción [Op1,Op2,Op3]
Formato extendido (con =):
# Descripción
# Op1: Hace X
# Op2: Hace Y
# Op3: Hace Z
@mi_var: Op1
Ejemplo real
// En config_unified.zig:
.{ "validacion_nif_modo", .string, Cat.validaciones,
"Estricto=Bloquea guardar si NIF inválido,Permisivo=Avisa pero permite guardar,Desactivado=Sin validación",
"Modo validación NIF", false, "# VALIDACIONES" },
Genera:
# Modo validación NIF
# Estricto: Bloquea guardar si NIF inválido
# Permisivo: Avisa pero permite guardar
# Desactivado: Sin validación
@validacion_nif_modo: Estricto
Compatibilidad
- Si
auto_validateNO contiene=→ formato simple (actual) - Si
auto_validatecontiene=→ formato extendido (nuevo) - 100% compatible con archivos existentes
EQUIPO
- Usuario (R.Eugenio): Desarrollador principal
- Claude: Asistente de programacion (Claude Code / Opus 4.5)
HISTORIAL
| Fecha | Version | Commit | Cambios |
|---|---|---|---|
| 2025-12-25 | v0.2.4 | 3dd835e |
setSilent() + descripciones extendidas en archivo config |
| 2025-12-18 | v0.2.3 | 957767d |
validateIntRange/Float fix: rangos negativos "-100-100" |
| 2025-12-18 | v0.2.2 | 0ef5efd |
initDeferred(): control manual sin auto-crear archivo |
| 2025-12-18 | v0.2.1 | - | FileWatcher, loadFromString, updateMtime |
| 2025-12-17 | v0.2.0 | - | ConfigManager con observers, auto-save, isDirty |
| 2025-12-17 | v0.1.0 | - | Proyecto creado, Engine + persistence |