Files
descarga_cpe_sovos/CLAUDE.md
T

66 lines
4.4 KiB
Markdown
Raw Normal View History

# 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.