2026-09-02 07:40:26 -05:00
# pipeline.py — run_pipeline
## Estructura general
```
pipeline.py
├── PASO 1: step_authenticate() → token | None
├── PASO 2: step_fetch_emails() → list[dict]
2026-10-14 22:28:48 -05:00
├── _procesar_un_correo() → None (orquesta los módulos de abajo)
2026-09-02 07:40:26 -05:00
└── run_pipeline() → None
2026-10-14 22:28:48 -05:00
Módulos que orquesta _procesar_un_correo():
├── adjuntos.py descargar_adjuntos, filtrar_y_limpiar
├── documentos.py procesar_pdfs, procesar_xmls, completar_guias, validar_guias, copiar_a_red
├── persistencia.py guardar_resultado, guardar_draft_json
├── draft_sap.py validate_prices, obtener_valor_referencial_sap, build_sap_draft
2026-10-22 08:55:54 -05:00
├── notificaciones.py build_validation_html, build_valor_referencial_html, build_notification_body (correo técnico)
├── observaciones.py motivos de rechazo con sus textos
├── respuesta_proveedor.py respuesta con plantilla al remitente (modo prueba/real, protecciones)
├── plantillas.py renderizador de las plantillas HTML
2026-10-14 22:28:48 -05:00
└── pipeline_utils.py log, ruta_carpeta
2026-09-02 07:40:26 -05:00
```
---
## Proceso de `run_pipeline()`
```mermaid
flowchart TD
A[run_pipeline] --> B[init_db]
B --> C[Calcular rango fechas: ayer-dias → hoy]
C --> D[step_authenticate]
D --> E{Token obtenido?}
E -- No --> F[❌ Abortar]
E -- Sí --> G[step_fetch_emails]
G --> H{Hay correos?}
H -- No --> I[Fin]
H -- Sí --> J[Bucle: por cada email]
J --> K[_procesar_un_correo]:::highlight
K --> J
classDef highlight fill:#fff3e0,stroke:#e65100,stroke-width:3px;
```
---
## Proceso de `_procesar_un_correo()`
```mermaid
flowchart TD
A[_procesar_un_correo] --> B[1-2. BD: ¿ya procesado?]
B -- Sí --> C[⏭️ Saltar]
B -- No --> D[Guardar en BD]
2026-10-14 22:28:48 -05:00
D --> E[3. descargar_adjuntos]
2026-09-02 07:40:26 -05:00
E --> F{Hay adjuntos?}
F -- No --> C
F -- Sí --> G[4. _descomprimir ZIPs]
2026-10-14 22:28:48 -05:00
G --> H[5. filtrar_y_limpiar]
2026-09-02 07:40:26 -05:00
H --> I{Hay PDF/XML?}
2026-10-22 08:55:54 -05:00
I -- No --> RC[Rechazo del correo<br>responder al remitente]
2026-10-14 22:28:48 -05:00
I -- Sí --> J[6. procesar_pdfs]:::highlight
J --> K[7. procesar_xmls]:::highlight
K --> K2[8. completar_guias]
K2 --> K3[9. validar_guias]
2026-09-10 13:21:04 -05:00
K3 --> L{Hay documentos?}
2026-10-22 08:55:54 -05:00
L -- No --> RC
2026-10-14 22:28:48 -05:00
L -- Sí --> M[10. guardar_resultado]
2026-09-10 13:21:04 -05:00
M --> N[11. Filtrar facturas tipo 01]
2026-09-02 07:40:26 -05:00
N --> O{Hay facturas?}
2026-10-22 08:55:54 -05:00
O -- No --> RC
2026-09-02 07:40:26 -05:00
O -- Sí --> P[Bucle: por cada factura]
2026-10-22 12:08:38 -05:00
P --> P2{11.5 ¿Ya existe en SAP?}
P2 -- Sí --> V
P2 -- No --> Q[12. validate_prices]:::highlight
2026-10-14 22:28:48 -05:00
Q --> Q2[12.5 obtener_valor_referencial_sap]
Q2 --> R[13. build_sap_draft]:::highlight
R --> S[14. guardar_draft_json]
2026-10-22 12:08:38 -05:00
S --> T{¿SAP/HANA caídos<br>u otro error?}
T -- Caída --> FL[estado fuera de línea:<br>aviso 'En cola' una vez<br>y el correo se reintenta]
T -- Otro error --> X[estado error]
2026-10-22 08:55:54 -05:00
T -- No --> T2{Todas coinciden?}
T2 -- Sí --> U[15. sapb1_manager.create_draft]
T2 -- No --> V[estado rechazado + observaciones]
U --> U2[estado aceptado]
U2 --> W[16. Correo técnico al equipo<br>+ respuesta con plantilla al remitente]
2026-09-02 07:40:26 -05:00
V --> W
2026-10-22 08:55:54 -05:00
X --> W2[16. Correo técnico al equipo<br>NO se responde al proveedor]
2026-10-22 12:08:38 -05:00
FL --> P
2026-09-02 07:40:26 -05:00
W --> P
2026-10-22 08:55:54 -05:00
W2 --> P
2026-09-02 07:40:26 -05:00
classDef highlight fill:#fff3e0,stroke:#e65100,stroke-width:3px;
```
2026-10-14 22:28:48 -05:00
`procesar_xmls` (paso 7) incluye internamente la copia de PDF/XML a la ruta de red (`copiar_a_red` , ver más abajo) por cada comprobante procesado.
2026-09-10 13:21:04 -05:00
2026-09-02 07:40:26 -05:00
---
2026-10-14 22:28:48 -05:00
## Funciones por módulo
2026-09-02 07:40:26 -05:00
2026-10-14 22:28:48 -05:00
> Son funciones de módulo (antes anidadas en `_procesar_un_correo`) y reciben explícitamente `email`, `download_folder`, `output_folder`, etc. `_procesar_un_correo` solo las orquesta.
2026-09-10 13:21:04 -05:00
2026-10-14 22:28:48 -05:00
| Función | Módulo | Propósito |
|---------|--------|-----------|
| `ruta_carpeta()` | `pipeline_utils` | Resuelve una ruta relativa al directorio del proyecto |
| `log(msg, level, last)` | `pipeline_utils` | Imprime con indentación de árbol |
| `copiar_a_red(data)` | `documentos` | Copia PDF/XML de un comprobante a la ruta de red configurable |
| `descargar_adjuntos(email, email_addr, token, download_folder)` | `adjuntos` | Descarga adjuntos vía Microsoft Graph |
| `filtrar_y_limpiar(email, download_folder)` | `adjuntos` | Filtra solo PDF/XML, elimina el resto |
| ** `procesar_pdfs(email, download_folder)` ** | `documentos` | **Extrae datos de PDFs, renombra, genera cache** |
| ** `procesar_xmls(email, download_folder, pdf_cache)` ** | `documentos` | **Procesa XMLs, cruza con PDF, asigna guías, copia a red** |
| `completar_guias(documentos, pdf_cache)` | `documentos` | Completa guías faltantes en líneas XML desde el PDF cache |
| `validar_guias(documentos)` | `documentos` | Advierte (log) guías con datos incompletos (falta tipo/serie/numero) |
| `guardar_resultado(documentos, datos_pdf, output_folder)` | `persistencia` | Guarda JSON con datos extraídos (XML y PDF) |
| `guardar_draft_json(draft, serie, numero, output_folder)` | `persistencia` | Guarda draft JSON para depuración |
| `calcular_fecha_vencimiento(fecha_base, condicion_pago)` | `draft_sap` | Calcula `DocDueDate` = fecha de contabilización + días de `PaymentTermsTypes` |
| ** `build_sap_draft(factura, resultados_val)` ** | `draft_sap` | **Construye payload del draft SAP B1 (incluye `U_VS_ANEXO_PDF`/`U_VS_ANEXO_XML`, `DocDueDate` por condición de pago)** |
| ** `validate_prices(factura)` ** | `draft_sap` | **Valida precios contra DeliveryNotes/Orders SAP** |
| `obtener_valor_referencial_sap(factura)` | `draft_sap` | Consulta `Sp_ObtenerValorReferencial` en HANA por cada guía única |
| `build_validation_html(resultados)` | `notificaciones` | Genera tabla HTML de validación de precios |
| `build_valor_referencial_html(factura, guias_valor_ref)` | `notificaciones` | Genera tabla HTML comparando valor referencial XML vs SAP |
| `build_notification_body(status, factura, validation_table, valor_referencial_table, **kwargs)` | `notificaciones` | Arma cuerpo del email de notificación |
2026-09-10 13:21:04 -05:00
---
2026-10-14 22:28:48 -05:00
## `copiar_a_red(data)` — Copia a ruta de red
2026-09-10 13:21:04 -05:00
Copia el PDF y el XML de cada comprobante procesado a una ruta de red UNC configurable, en paralelo al guardado local existente (no lo reemplaza). No falla el pipeline si la ruta no está configurada o no está disponible: solo registra una advertencia.
- **Configuración** (`.env` ): `RUTA_RED_FACTURACION` (ruta base UNC) y `RUC_EMPRESA` (RUC fijo de Aberio).
- **Estructura de destino**:
```
{RUTA_RED_FACTURACION}\{año}\{mes}\{día}\{tipo}\{RUC_EMPRESA}\{ruc_emisor}\
{RUC_EMPRESA}-{tipo}-{serie}-{numero}_FormatoImpreso.pdf
{RUC_EMPRESA}-{tipo}-{serie}-{numero}.xml
` ``
` RUC_EMPRESA` es el RUC de Aberio (fijo); ` ruc_emisor` es el RUC del proveedor que emitió la factura (varía por comprobante). Año/mes/día salen de ` fecha_emision`, sin ceros a la izquierda en mes/día.
2026-10-14 22:28:48 -05:00
- Guarda las rutas resultantes en ` data["ruta_red_pdf"]` / ` data["ruta_red_xml"]`, que luego usa ` build_sap_draft` para llenar ` U_VS_ANEXO_PDF` / ` U_VS_ANEXO_XML` en el draft SAP B1.
- Se invoca desde ` procesar_xmls`, justo después de resolver ` data["ruta_pdf"]`.
2026-09-02 07:40:26 -05:00
---
## Flujo de llamadas
` ``
run_pipeline
2026-09-10 13:21:04 -05:00
├── database_utilities.init_db
2026-09-02 07:40:26 -05:00
├── step_authenticate
2026-09-10 13:21:04 -05:00
│ └── outlook_utilities.get_access_token
2026-09-02 07:40:26 -05:00
├── step_fetch_emails
2026-09-10 13:21:04 -05:00
│ └── outlook_utilities.get_emails
2026-09-02 07:40:26 -05:00
└── _procesar_un_correo (por cada email)
2026-09-10 13:21:04 -05:00
├── database_utilities.get_existing_ids / save_emails
2026-10-14 22:28:48 -05:00
├── descargar_adjuntos
2026-09-10 13:21:04 -05:00
│ └── outlook_utilities.download_attachments
2026-09-02 07:40:26 -05:00
├── _descomprimir
2026-09-10 13:21:04 -05:00
│ └── email_utilities.descomprimir_adjuntos
2026-10-14 22:28:48 -05:00
├── filtrar_y_limpiar
├── procesar_pdfs
2026-09-10 13:21:04 -05:00
│ └── pdf_utilities.extraer_datos_pdf / renombrar_pdf
2026-10-14 22:28:48 -05:00
├── procesar_xmls
2026-09-10 13:21:04 -05:00
│ ├── sunat_schema.extraer_datos_xml
│ ├── pdf_utilities.extraer_referencias
2026-10-14 22:28:48 -05:00
│ └── copiar_a_red
├── completar_guias
├── validar_guias
├── guardar_resultado
2026-09-10 13:21:04 -05:00
│ └── sunat_schema.split_serie_numero, pdf_utilities.TIPO_MAP
├── (por cada factura tipo 01)
2026-10-14 22:28:48 -05:00
│ ├── validate_prices
2026-09-10 13:21:04 -05:00
│ │ ├── sapb1_manager.get_zonas
│ │ └── sapb1_manager.get_delivery_notes
│ │ ├── hana_manager.get_delivery (fallback si SL no responde;
│ │ │ busca en ODLN y, si no hay, en OWTR)
│ │ └── si el DocEntry vino de OWTR (StockTransfers), resuelve
│ │ además la Orden de Venta asociada (Orders vía
│ │ U_IDC_NUMOV) para poder validar las líneas FL*
2026-10-14 22:28:48 -05:00
│ ├── build_validation_html
│ ├── obtener_valor_referencial_sap
2026-09-10 13:21:04 -05:00
│ │ └── hana_manager.obtener_valor_referencial
2026-10-14 22:28:48 -05:00
│ ├── build_valor_referencial_html
│ ├── build_sap_draft
2026-09-10 13:21:04 -05:00
│ │ └── sapb1_manager.obtener_condicion_pago (BusinessPartners +
│ │ PaymentTermsTypes, para calcular DocDueDate)
2026-10-14 22:28:48 -05:00
│ ├── guardar_draft_json
2026-09-10 13:21:04 -05:00
│ ├── sapb1_manager.create_draft (si todas las líneas coinciden)
2026-10-14 22:28:48 -05:00
│ ├── build_notification_body
2026-10-22 08:55:54 -05:00
│ ├── respuesta_proveedor.enviar_a_equipo (correo técnico, solo equipo)
│ └── respuesta_proveedor.responder (plantilla aceptado/rechazado, ver respuesta_proveedor.md)
2026-09-02 07:40:26 -05:00
` ``
2026-10-22 08:55:54 -05:00
---
## Qué se envía y a quién (paso 16)
| Resultado de la factura | Correo técnico (tablas de SAP, JSON adjuntos) | Respuesta con plantilla |
|-------------------------|-----------------------------------------------|-------------------------|
| ` aceptado` (draft creado) | Equipo | Aceptado, al remitente |
2026-10-22 12:08:38 -05:00
| ` rechazado` (precio, guía, o la factura **ya existe en SAP**) | Equipo | Rechazado con observaciones, al remitente |
| ` fuera_de_linea` (SAP o HANA no responden) | Equipo (primer intento y al agotarse) | "En cola", **una sola vez**; el correo se reintenta |
| ` error` (fallo inesperado, rechazo normal de SAP) | Equipo | **Ninguna**: es una falla nuestra, no del proveedor |
2026-10-22 08:55:54 -05:00
| Correo sin comprobante válido (sin PDF/XML, sin XML, sin factura) | — | Rechazado sin comprobante, al remitente |
| Correo sin adjuntos | — | Ninguna |
"Al remitente" depende de ` NOTIFICACION_MODO` (en modo ` prueba` va al equipo) y de las protecciones. Todo en [respuesta_proveedor.md](respuesta_proveedor.md).
2026-10-22 12:08:38 -05:00
Un fallo inesperado al procesar una factura ya no interrumpe el ciclo: se registra como ` error`, se avisa al equipo y se sigue con la siguiente. La lógica por factura vive en ` _procesar_factura`; ` _procesar_un_correo` la llama una vez por factura y, si alguna pide reintento, deja el correo en cola.
2026-10-22 08:55:54 -05:00