"""Carga de config.json sin depender de argparse. El ``config_parser`` original invoca ``ArgParser().parse()`` en tiempo de import, lo que obliga a que haya argumentos de linea de comandos validos. El daemon y las herramientas necesitan leer la configuracion sin eso. """ from __future__ import annotations import json import os ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DEFAULT_CONFIG = os.path.join(ROOT, "config.json") DEFAULTS = { "api_url": "https://timenet-wcp.gpisoftware.com/api/", "timezone": "Europe/Madrid", "jitter": { # margen, en segundos, con el que se ficha respecto de la hora teorica. # Entradas ANTES (Timenet no acredita nada fuera de la ventana, pero si # debita las entradas tardias). Salidas DESPUES. # perfil "equilibrado": saldo 0 con ~3,6 min/dia de presencia extra. # Mas holgado (40/150/30/130) = mas seguro pero ~5,7 min/dia. # Mas ajustado (20/70/15/45) = ~2,7 min/dia pero solo 15s de colchon. "entry_before_min": 30, "entry_before_max": 100, "exit_after_min": 20, "exit_after_max": 70, # deriva lenta de la hora habitual, para no parecer un dado nuevo cada dia "drift_sigma": 25, "drift_clamp": 60, # margen de seguridad: nunca fichar una entrada a menos de X s de la hora "entry_guard": 20, }, "geo_jitter_meters": 12.0, "telegram": {}, } def _merge(base: dict, over: dict) -> dict: out = dict(base) for k, v in (over or {}).items(): out[k] = _merge(base[k], v) if isinstance(v, dict) and isinstance(base.get(k), dict) else v return out def load(path: str | None = None) -> dict: path = path or os.environ.get("AUTOFICHER_CONFIG") or DEFAULT_CONFIG if not os.path.exists(path): raise FileNotFoundError(f"No se encuentra el archivo de configuracion: {path}") with open(path, encoding="utf-8") as f: cfg = json.load(f) if not isinstance(cfg, dict): raise ValueError("config.json no contiene un objeto JSON") cfg = _merge(DEFAULTS, cfg) for req in ("api_url", "user"): if not cfg.get(req): raise ValueError(f"Falta '{req}' en {path}") return cfg def pin(cfg: dict | None = None) -> str: """PIN desde el entorno, la config o por teclado. Nunca se escribe a disco.""" import getpass p = os.environ.get("TIMENET_PIN") or (cfg or {}).get("pin") return str(p) if p else getpass.getpass("PIN de Timenet: ")