Files
George 277d07dfeb Migra el servicio a arquitectura Docker persistente y mueve credenciales HANA a .env
- Reemplaza config_loader/config_model/hana_client/logger_utilities/outlook_utilities
  por core.py (pipeline funcional), hana_manager.py, logger_manager.py, scheduler.py y
  descargador_sovos.py (Playwright/Firefox) según la plantilla de servicios.
- main.py pasa a loop continuo dirigido por scheduler.json, con manejo de SIGINT/SIGTERM.
- Agrega Dockerfile, docker-compose.yml (restart unless-stopped) y certs/ para despliegue.
- Agrega smb_transfer.py para la transferencia a carpeta compartida.
- Saca las credenciales de HANA de config.json y las mueve a .env (HANA_HOST/PORT/USER/PASSWORD).
2026-08-25 09:29:35 -05:00

4.4 KiB

CLAUDE.md — Convenciones del proyecto

Servicio Python de descarga de comprobantes electrónicos (CPE) desde Sovos/SUNAT. Sigue la estructura de la plantilla ~/Desarrollo/Python/plantilla-servicio/.

Estructura

main.py            # entrada delgada: while loop dirigido por scheduler.json, llama ejecutar()
core.py            # pipeline funcional: describir_servicio/obtener_datos/procesar_y_descargar/ejecutar
scheduler.py       # reutilizable (franja activa / próximo inicio a partir de scheduler.json)
logger_manager.py  # reutilizable (logging niveles + árbol; TimedRotatingFileHandler 30 días)
hana_manager.py    # reutilizable (HANA multi-schema, obtener_pendientes, marcar)
descargador_sovos.py  # específico: descarga PDF/XML en Sovos con Playwright (Firefox)
smb_transfer.py    # reutilizable (detectar/montar SMB + rsync por empresa)
config.json        # hana, rucs, consulta.dias, empresas(locked), files
scheduler.json     # HORARIOS ([hora_ini,min_ini,hora_fin,min_fin,modo]) + DIAS_ACTIVOS
.env               # SMB_* + SMB_AUTO_MOUNT + LOG_LEVEL

Dockerfile                    # imagen (Playwright+Firefox ya incluidos, CA Fortinet, rsync)
docker-compose.yml            # servicio `descarga-cpe` persistente (restart: unless-stopped)
certs/fortinet-root.crt       # CA de inspección SSL de la red (FG100FTK22018356)

Reglas

  1. Empresas en config.json con { nombre, schema, locked, smb_subfolder }. locked: true = no ejecutar. Descargas en downloads/<nombre>/.
  2. Credenciales en .env, nunca en git.
  3. Logging con logger_manager.get_logger(...) y niveles info/warning/error (sin print).
  4. core.py es funcional (sin clases): cada servicio reescribe describir_servicio(), obtener_datos(empresa) y procesar_y_descargar(datos, dir_empresa); main.py siempre delgado (solo llama a ejecutar(); no reescribe lógica de negocio — su única lógica propia es el loop/scheduler de la regla 8).
  5. Después de descargar PDF/XML se marca en HANA (U_PGX_PDF/U_PGX_XML) por schema.
  6. Transferencia con smb_transfer.transferir_a_smb(...); si el SMB no está montado conserva los archivos (no los borra).
  7. Verificar: python -m py_compile main.py core.py scheduler.py hana_manager.py descargador_sovos.py smb_transfer.py logger_manager.py y correr docker compose up -d descarga-cpe (revisar con docker compose logs -f).
  8. Solo Docker, servicio persistente (no hay venv nativa ni LaunchAgent/cron del HOST en el proyecto): docker-compose.yml usa restart: unless-stopped y main.py mantiene un while continuo — en cada ciclo llama a ejecutar() (pipeline de core.py) y luego duerme según el modo de la franja horaria activa. scheduler.py lee scheduler.json (releído en cada ciclo, así se pueden editar horarios sin reiniciar el contenedor) y responde:
    • si el día/hora actual cae dentro de una franja de HORARIOS, su modo ("rapido" → dormir 10 min, "full" → dormir 1 hora; mapeo fijo en INTERVALOS_POR_MODO dentro de scheduler.py, no en el JSON);
    • si no, el inicio de la próxima franja activa (respetando DIAS_ACTIVOS), y main.py duerme exactamente hasta ese instante. El sueño se hace en pasos de 1s (_dormir_interrumpible) y main.py registra handlers de SIGINT/SIGTERM para poder frenar el loop y salir limpio ante docker compose stop/down, en vez de que Docker tenga que matarlo por timeout. Una excepción no controlada en un ciclo se loguea (exc_info=True, va a logs/descarga-cpe.log con su rotación de 30 días) y el loop reintenta tras 60s — ya no hay cron externo que reintente por nosotros. Como el contenedor ya no es efímero, docker-compose.yml limita el driver json-file de Docker (max-size: 10m, max-file: 5) para que el log propio del contenedor (stdout) no crezca sin límite; esto es independiente de la rotación de logs/descarga-cpe.log, que maneja logger_manager.py.
  9. scheduler.json define HORARIOS (lista de [hora_ini, min_ini, hora_fin, min_fin, modo], modo es "rapido" o "full") y DIAS_ACTIVOS (0=lunes … 6=domingo, según datetime.weekday()). Fuera de esas franjas y días no se ejecuta nada.
  10. Bloqueos de red (FortiGuard Web Filter, p.ej. categoría "Finance and Banking" en public-einvoicing.sovos.com) no son bug de la app: se resuelven en el FortiGate, no reintentando ni parcheando el código.