Nueva tabla comprobantes (creada en init_db) y módulo seguimiento.py: cada comprobante recibe un código ABR-AAMMDD-NNNNN que conserva si reaparece en otro correo, más su resultado (aceptado/rechazado/error) y el estado de la notificación. El pipeline lo registra por factura sin bloquear el flujo si el seguimiento falla. Las pruebas usan una BD temporal para no tocar emails.db. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
4.1 KiB
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 enNULL. - El código sale del
idde la fila (ABR-+ fecha +idcon 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
observacionesguardados hoy son mínimos. Se reemplazarán por observaciones estructuradas (título, filas, cómo corregir) cuando se definan las validaciones de rechazo. registrar_correoyregistrar_notificacionestán implementados y probados, pero el pipeline todavía no los llama: los usarán las notificaciones al proveedor.
Notas
database_utilities.DB_NAMEes"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_NAMEa una base temporal (fixture automático entests/conftest.py) para no escribir enemails.db.