66 lines
4.4 KiB
Markdown
66 lines
4.4 KiB
Markdown
# 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.
|