Files
descarga_factura_transporte/docs/seguimiento.md
T

57 lines
4.1 KiB
Markdown
Raw Normal View History

# seguimiento.py — Códigos de seguimiento y resultado por comprobante
Cada comprobante que llega por correo recibe un **código de seguimiento** (`ABR-AAMMDD-NNNNN`, por ejemplo `ABR-261019-00042`) que se le comunicará al proveedor y que permite reconocer el comprobante si lo reenvía. El registro también guarda el resultado del proceso y el estado de la notificación.
Los datos viven en la tabla `comprobantes` de `emails.db`. El esquema lo crea `database_utilities.init_db()` con `CREATE TABLE IF NOT EXISTS`, por lo que una base existente se actualiza sola sin tocar la tabla `emails`.
## Identidad
- Un comprobante se identifica por **(RUC emisor, tipo, serie, número)**. Si vuelve a llegar, en el mismo correo o en otro, conserva su código.
- Un correo que no trae ningún comprobante identificable (sin adjuntos, sin XML válido…) se registra aparte con `registrar_correo`: una fila por correo, con RUC, serie y número en `NULL`.
- El código sale del `id` de la fila (`ABR-` + fecha + `id` con 5 dígitos), así que es único por construcción, sin reintentos ni contadores aparte.
## Tabla `comprobantes`
| Columna | Contenido |
|---------|-----------|
| `codigo_seguimiento` | Código único del comprobante |
| `email_id` | Último correo en el que llegó |
| `ruc_emisor`, `tipo`, `serie`, `numero` | Identidad del comprobante (`NULL` en las filas de correo sin comprobante) |
| `estado` | `pendiente`, `aceptado`, `rechazado` o `error` |
| `observaciones` | JSON con los motivos de rechazo o el detalle del error |
| `doc_entry` | DocEntry del draft creado en SAP |
| `veces_recibido` | Cuántos correos distintos trajeron este comprobante |
| `notificacion_estado`, `notificacion_error`, `notificacion_fecha` | `pendiente`, `enviada`, `fallida` u `omitida`, y su motivo |
| `creado_en`, `actualizado_en` | Marcas de tiempo |
## Funciones
| Función | Qué hace |
|---------|----------|
| `registrar_comprobante(email_id, ruc_emisor, tipo, serie, numero)` | Crea el registro o recupera el existente. Devuelve la fila más `reenvio` (True si ya había llegado en **otro** correo). Reprocesar el mismo correo no cambia nada. |
| `registrar_correo(email_id)` | Igual, para un correo sin comprobante. Idempotente por correo. |
| `obtener(codigo)` | Devuelve el registro o `None`. |
| `actualizar_resultado(codigo, estado, observaciones=None, doc_entry=None)` | Guarda el resultado. `ValueError` si el estado no es válido, `LookupError` si no existe el código. |
| `registrar_notificacion(codigo, estado, error=None)` | Guarda el estado de la notificación. Mismas excepciones. |
## Uso en el pipeline
En `pipeline._procesar_un_correo`, cada factura se registra al empezar (`_registrar_comprobante`) y su resultado se guarda al terminar (`_registrar_resultado`):
| Resultado del proceso | `estado` | Detalle guardado |
|-----------------------|----------|------------------|
| Draft creado en SAP | `aceptado` | `doc_entry` |
| Precios no coinciden con SAP | `rechazado` | `[{"codigo": "precios_no_coinciden"}]` |
| Error al crear el draft | `error` | `[{"codigo": "error_sap", "detalle": "..."}]` |
El seguimiento **nunca impide procesar la factura**: si falla la base de datos, se registra un `WARNING` y el flujo continúa. Si el comprobante ya había llegado antes, se registra un `WARNING` con las veces recibidas y el estado anterior, pero **no se bloquea** la creación de otro draft.
## Estado actual y pendiente
- Los `observaciones` guardados hoy son mínimos. Se reemplazarán por observaciones estructuradas (título, filas, cómo corregir) cuando se definan las validaciones de rechazo.
- `registrar_correo` y `registrar_notificacion` están implementados y probados, pero el pipeline todavía no los llama: los usarán las notificaciones al proveedor.
## Notas
- `database_utilities.DB_NAME` es `"emails.db"`, una ruta **relativa al directorio de trabajo**. Si el demonio se lanza desde otra carpeta, usará (y creará) otra base y los códigos ya emitidos no se encontrarían. Hoy funciona porque se ejecuta desde la raíz del proyecto.
- Las pruebas redirigen `DB_NAME` a una base temporal (fixture automático en `tests/conftest.py`) para no escribir en `emails.db`.