zcatconfig/docs/CONFIGMANAGER_DESIGN.md
reugenio 91e5133e13 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>
2025-12-17 13:29:29 +01:00

11 KiB

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:

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:

pub const Observer = *const fn (change: ConfigChange, config: *const ConfigType) void;

Flujo de Uso

Uso Básico (sin BD)

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

// 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)

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

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

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