- 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).
4.4 KiB
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
- Empresas en
config.jsoncon{ nombre, schema, locked, smb_subfolder }.locked: true= no ejecutar. Descargas endownloads/<nombre>/. - Credenciales en
.env, nunca en git. - Logging con
logger_manager.get_logger(...)y niveles info/warning/error (sinprint). core.pyes funcional (sin clases): cada servicio reescribedescribir_servicio(),obtener_datos(empresa)yprocesar_y_descargar(datos, dir_empresa);main.pysiempre delgado (solo llama aejecutar(); no reescribe lógica de negocio — su única lógica propia es el loop/scheduler de la regla 8).- Después de descargar PDF/XML se marca en HANA (
U_PGX_PDF/U_PGX_XML) por schema. - Transferencia con
smb_transfer.transferir_a_smb(...); si el SMB no está montado conserva los archivos (no los borra). - Verificar:
python -m py_compile main.py core.py scheduler.py hana_manager.py descargador_sovos.py smb_transfer.py logger_manager.pyy correrdocker compose up -d descarga-cpe(revisar condocker compose logs -f). - Solo Docker, servicio persistente (no hay venv nativa ni LaunchAgent/cron del
HOST en el proyecto):
docker-compose.ymlusarestart: unless-stoppedymain.pymantiene unwhilecontinuo — en cada ciclo llama aejecutar()(pipeline decore.py) y luego duerme según el modo de la franja horaria activa.scheduler.pyleescheduler.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, sumodo("rapido"→ dormir 10 min,"full"→ dormir 1 hora; mapeo fijo enINTERVALOS_POR_MODOdentro descheduler.py, no en el JSON); - si no, el inicio de la próxima franja activa (respetando
DIAS_ACTIVOS), ymain.pyduerme exactamente hasta ese instante. El sueño se hace en pasos de 1s (_dormir_interrumpible) ymain.pyregistra handlers deSIGINT/SIGTERMpara poder frenar el loop y salir limpio antedocker 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 alogs/descarga-cpe.logcon 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.ymllimita el driverjson-filede 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 delogs/descarga-cpe.log, que manejalogger_manager.py.
- si el día/hora actual cae dentro de una franja de
scheduler.jsondefineHORARIOS(lista de[hora_ini, min_ini, hora_fin, min_fin, modo],modoes"rapido"o"full") yDIAS_ACTIVOS(0=lunes … 6=domingo, segúndatetime.weekday()). Fuera de esas franjas y días no se ejecuta nada.- 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.