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>
65 lines
4.6 KiB
Markdown
65 lines
4.6 KiB
Markdown
# 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 |
|
|
| `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`; ver `observaciones.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_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`.
|