feat: ConfigManager - gestor autónomo de configuración

Añade ConfigManager que maneja automáticamente:
- Carga/creación de archivo config (loadOrCreate)
- Auto-guardado en deinit si hay cambios pendientes
- Sistema de observers con contexto para sincronización externa

Cambios:
- ConfigManager(variables, ConfigType, app_name) type
- Observer con contexto: fn(change, config, ctx) void
- addObserver(callback, context) para registrar listeners
- Métodos: get, set, getConfig, getConfigMut, markDirty, isDirty

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
reugenio 2025-12-17 13:29:29 +01:00
parent 15c7f7357e
commit 91e5133e13
3 changed files with 1094 additions and 176 deletions

View file

@ -169,18 +169,18 @@ PATH=/mnt/cello2/arno/re/recode/zig/zig-0.15.2/zig-x86_64-linux-0.15.2:$PATH zig
## PLAN DE TRABAJO ## PLAN DE TRABAJO
### Fase 1: Estructura base ### Fase 1: Estructura base - COMPLETADA
- [x] Crear proyecto (build.zig, CLAUDE.md) - [x] Crear proyecto (build.zig, CLAUDE.md)
- [ ] Extraer types.zig de zsimifactu - [x] Extraer types.zig de zsimifactu
- [ ] Adaptar engine.zig (parametrizar variables y Config) - [x] Adaptar engine.zig (parametrizar variables y Config)
- [ ] Adaptar persistence.zig - [x] Adaptar persistence.zig
### Fase 2: Generalizacion ### Fase 2: Generalizacion - COMPLETADA
- [ ] Engine generico con comptime - [x] Engine generico con comptime
- [ ] Tests unitarios - [x] Tests unitarios (9 tests)
- [ ] Documentacion API - [x] Documentacion API
### Fase 3: Integracion ### Fase 3: Integracion - EN PROGRESO
- [ ] Integrar en zsimifactu como dependencia - [ ] Integrar en zsimifactu como dependencia
- [ ] Verificar que zsimifactu funciona igual - [ ] Verificar que zsimifactu funciona igual

View file

@ -0,0 +1,348 @@
# ConfigManager - Diseño e Implementación
**Fecha:** 2025-12-17
**Versión:** v0.2.0
**Estado:** En implementación
---
## Objetivo
Crear un gestor de configuración completo y autónomo que:
1. Gestione automáticamente el archivo de configuración
2. Mantenga las variables en memoria
3. Notifique cambios via observers (para sincronizar con BD u otros sistemas)
4. Minimice el trabajo requerido en proyectos futuros
---
## Filosofía
> "Invertir trabajo ahora para ahorrar problemas y trabajo en el futuro"
La librería debe hacer todo el trabajo pesado. El programa solo debe:
1. Definir las variables (array de ConfigVariable)
2. Definir el struct Config
3. Crear el ConfigManager
4. Opcionalmente registrar observers
---
## Arquitectura
```
┌─────────────────────────────────────────────────────────────────────┐
│ ZCATCONFIG │
│ (Librería independiente, reutilizable) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ConfigManager(comptime variables, comptime ConfigType) │
│ ├── allocator: Allocator │
│ ├── config: ConfigType ← Datos en memoria │
│ ├── file_path: []const u8 ← Ruta archivo │
│ ├── app_name: []const u8 ← Nombre app (para headers) │
│ ├── dirty: bool ← Cambios pendientes │
│ ├── observers: []Observer ← Lista de callbacks │
│ │ │
│ ├── init(allocator, path, app_name) │
│ │ └── Llama loadOrCreate() automáticamente │
│ │ │
│ ├── deinit() │
│ │ └── Auto-save si dirty, libera recursos │
│ │ │
│ ├── get(key) → ConfigResult │
│ ├── set(key, value) → !void │
│ │ └── Marca dirty=true, notifica observers │
│ │ │
│ ├── getConfig() → *ConfigType │
│ │ └── Acceso directo al struct (para campos tipados) │
│ │ │
│ ├── save() → !void │
│ ├── load() → !void │
│ ├── loadOrCreate() → !void │
│ │ └── Si archivo no existe, guarda defaults │
│ │ │
│ ├── addObserver(callback) → void │
│ ├── removeObserver(callback) → void │
│ └── notifyObservers(change) → void (interno) │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## Tipos
### ConfigChange
Información sobre un cambio de configuración:
```zig
pub const ConfigChange = struct {
key: []const u8, // Clave que cambió ("@auto_guardar")
variable: *const ConfigVariable, // Definición de la variable
old_value: ConfigResult, // Valor anterior
new_value: ConfigResult, // Valor nuevo
};
```
### Observer
Callback que recibe notificaciones de cambios:
```zig
pub const Observer = *const fn (change: ConfigChange, config: *const ConfigType) void;
```
---
## Flujo de Uso
### Uso Básico (sin BD)
```zig
const zcatconfig = @import("zcatconfig");
// Definir variables (en variables.zig)
const config_variables = [_]zcatconfig.ConfigVariable{
.{ .name = "timeout", .config_key = "@timeout", ... },
// ...
};
// Definir struct (en structures.zig)
const Config = struct {
timeout: u32 = 30,
// ...
};
// En main o donde sea necesario:
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
// Crear manager - carga o crea archivo automáticamente
var config_manager = try zcatconfig.ConfigManager(
&config_variables,
Config
).init(allocator, "mi_app_config.txt", "MiApp");
defer config_manager.deinit(); // Auto-save si hay cambios
// Usar configuración
const timeout = config_manager.getConfig().timeout;
// Cambiar valor (notifica observers, marca dirty)
try config_manager.set("@timeout", "60");
}
```
### Uso con Sincronización a BD
```zig
// En DataManager:
pub const DataManager = struct {
config_manager: *zcatconfig.ConfigManager(&config_variables, Config),
db: *Database,
pub fn init(allocator: Allocator, db: *Database) !*Self {
var self = try allocator.create(Self);
// Crear config manager
self.config_manager = try zcatconfig.ConfigManager(
&config_variables,
Config
).init(allocator, "config.txt", "zsimifactu");
// Registrar observer para sincronizar con BD
self.config_manager.addObserver(syncToDatabase);
return self;
}
fn syncToDatabase(change: ConfigChange, config: *const Config) void {
// INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)
// Usar change.key y change.new_value
}
};
```
---
## Comportamiento Automático
### En init()
1. Inicializa Config con valores por defecto
2. Llama `loadOrCreate()`:
- Si archivo existe → carga valores
- Si no existe → guarda archivo con defaults
3. `dirty = false`
### En set()
1. Obtiene valor actual (para ConfigChange)
2. Aplica nuevo valor al struct
3. `dirty = true`
4. Notifica a todos los observers
### En deinit()
1. Si `dirty == true` → llama `save()`
2. Libera recursos del Config (strings alocados)
3. Libera lista de observers
---
## Integración con zsimifactu
### Antes (actual)
```
DataManager
├── config: Config ← Struct directo
├── config_file_path: []const u8
├── config_dirty: bool
├── loadConfig() / saveConfig() ← Métodos manuales
└── getConfigValue() / setConfigValue()
```
### Después (con ConfigManager)
```
DataManager
├── config_manager: *ConfigManager ← Gestor completo
└── (todo lo demás delegado al manager)
// Acceso a config:
dm.config_manager.getConfig().mi_campo
// Cambiar valor:
try dm.config_manager.set("@mi_campo", "valor");
```
---
## Sincronización con BD
### Estrategia
1. ConfigManager notifica cambios via observer
2. DataManager registra un observer que escribe a BD
3. Al iniciar, se puede cargar desde BD y aplicar al ConfigManager
### Tabla BD (ya existe en zsimifactu)
```sql
CREATE TABLE IF NOT EXISTS config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
```
### Flujo de Sincronización
```
Inicio:
1. ConfigManager.init() → carga archivo (o crea con defaults)
2. DataManager carga valores de BD
3. Aplica valores BD al ConfigManager (si BD tiene prioridad)
Durante ejecución:
1. Usuario cambia valor
2. ConfigManager.set() → actualiza memoria + notifica
3. Observer → escribe a BD
4. ConfigManager marca dirty
Al cerrar:
1. ConfigManager.deinit() → guarda archivo (si dirty)
```
---
## Prioridad de Valores
Orden de prioridad (mayor a menor):
1. **Cambios en runtime** - Siempre prevalecen
2. **Base de datos** - Persistencia principal
3. **Archivo config** - Backup/portabilidad
4. **Defaults en código** - Fallback
---
## Archivos Afectados
### zcatconfig (crear/modificar)
- `src/zcatconfig.zig` - Añadir ConfigManager
- `docs/CONFIGMANAGER_DESIGN.md` - Este documento
### zsimifactu (modificar)
- `src/data_manager/data_manager.zig` - Usar ConfigManager
- `src/config/config.zig` - Simplificar (delegar a zcatconfig)
---
## Plan de Implementación
1. **Implementar ConfigManager en zcatconfig**
- Struct con todos los campos
- init/deinit con loadOrCreate automático
- get/set con notificación
- Sistema de observers
2. **Actualizar zsimifactu**
- DataManager usa ConfigManager
- Registrar observer para BD
- Eliminar código duplicado
3. **Probar**
- Borrar archivo config → debe regenerarse
- Cambiar valor → debe notificar y guardar
- Cerrar app → debe auto-guardar
---
## Notas de Implementación Zig
### Observers como ArrayList
```zig
observers: std.ArrayList(Observer),
pub fn addObserver(self: *Self, observer: Observer) void {
self.observers.append(self.allocator, observer) catch {};
}
fn notifyObservers(self: *Self, change: ConfigChange) void {
for (self.observers.items) |observer| {
observer(change, &self.config);
}
}
```
### ConfigManager como tipo genérico
```zig
pub fn ConfigManager(
comptime variables: []const ConfigVariable,
comptime ConfigType: type,
) type {
return struct {
const Self = @This();
allocator: std.mem.Allocator,
config: ConfigType,
file_path: []const u8,
app_name: []const u8,
dirty: bool,
observers: std.ArrayList(Observer),
pub const Observer = *const fn (ConfigChange, *const ConfigType) void;
pub fn init(...) !Self { ... }
pub fn deinit(self: *Self) void { ... }
// ...
};
}
```

File diff suppressed because it is too large Load diff