zcatconfig/CLAUDE.md
R.Eugenio 05909e2aae chore: Migración jj → git puro
- CLAUDE.md: VCS jj → git, comandos actualizados

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 13:27:05 +01:00

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/NORMAS_ESENCIALES.md

Contiene: rutas, VCS (git), flujo de trabajo, credenciales.

Paso 2: Verificar estado con git

git status && git log --oneline -5
zig build test

Nota: Usamos git con commits frecuentes. Ver teamdocs/NORMAS_ESENCIALES.md sección VCS.


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() llama loadOrCreate() 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_validate NO contiene = → formato simple (actual)
  • Si auto_validate contiene = → 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

CLAUDE CODE + ZLS (LSP)

Claude Code tiene integración LSP con zls (Zig Language Server).

Plugin: $HOME/.claude/plugins/zls-lsp/

Operaciones disponibles:

Operación Uso
goToDefinition Navegar a definición de símbolo
findReferences Encontrar todas las referencias
hover Info de tipo y documentación
documentSymbol Símbolos del archivo actual
incomingCalls Qué funciones llaman a ésta

Documentación: teamdocs/INFRASTRUCTURE/ZLS_LSP_CLAUDE_CODE.md