# 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 { ... } // ... }; } ```