El sistema contesta a quien envió el correo con la plantilla que corresponda. Por defecto (NOTIFICACION_MODO=prueba) la respuesta va solo al equipo con el asunto "[PRUEBA → remitente]"; en modo real va al remitente con copia oculta al equipo. El correo técnico (tablas de SAP, JSON) pasa a ir solo al equipo. - respuesta_proveedor.py: contextos de las plantillas, envío y protecciones (remitente desconocido o sin dominio, propio buzón, no-reply, mensajes automáticos, respuestas a notificaciones, límite de 3 por remitente/24 h). - observaciones.py: motivos de rechazo con título, filas y cómo corregir (borrador de textos); también para correos sin comprobante válido. - Un error de SAP/HANA o un fallo inesperado no se le comunica al proveedor y ya no tumba el ciclo; "guía no encontrada" exige que HANA responda. - outlook_utilities: es_mensaje_automatico y copia oculta en send_email. - seguimiento: notificacion_destinatario (con migración) y contar_respuestas_correo. - config_validator anuncia el modo activo; documentación y pruebas. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
4.6 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 |
notificacion_destinatario |
Direcciones a las que se envió de verdad (en modo prueba no son las del remitente) |
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, destinatario=None) |
Guarda el estado de la notificación y a quién se envió. Mismas excepciones. |
contar_respuestas_correo(remitente, horas=24) |
Respuestas enviada a ese remitente en correos sin comprobante: el freno contra bucles de respuesta_proveedor. |
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": "..."}] |
init_db agrega la columna notificacion_destinatario a una tabla creada antes sin ella, sin tocar los datos.
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.
Observaciones guardadas
- Rechazo de una factura: la lista de observaciones completa (
codigo,titulo,filas,como_corregir; verobservaciones.py). - Error:
[{"codigo": "error_interno", "detalle": "..."}]. - Correo sin comprobante: la observación del motivo (
sin_pdf_xml,falta_xml,sin_factura).
Las notificaciones las registra respuesta_proveedor (enviada, fallida u omitida con su motivo).
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.