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>
348 lines
11 KiB
Markdown
348 lines
11 KiB
Markdown
# 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 { ... }
|
|
// ...
|
|
};
|
|
}
|
|
```
|