Files
descarga_factura_transporte/docs/pipeline.md
T
GeorgeandClaude Sonnet 5.5 6030320822 se actualiza la documentación: sapb1_manager, nombres de funciones y cifras
- Nueva página docs/sapb1_manager.md: sesión, funciones, clasificación de
  errores (SapNoDisponible frente a rechazo de SAP), búsqueda de facturas
  existentes y cómo se ubica una guía.
- procesar_pdfs.md y procesar_xmls.md usaban los nombres anteriores a la
  división de pipeline.py (_procesar_pdfs, _ruta_carpeta...) y números de
  línea que envejecen; ahora apuntan a documentos.py.
- inventario.md: árbol de dependencias con los módulos nuevos.
- pipeline.md: _procesar_factura, importes_coinciden, hana_disponible y
  buscar_factura_existente; validar_precios.md, hana_manager.md, main.md y
  procesar_xmls.md con las notas que faltaban; README sin la cifra de pruebas
  vieja.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 14:56:34 -05:00

12 KiB

pipeline.py — run_pipeline

Estructura general

pipeline.py
├── PASO 1: step_authenticate() → token | None
├── PASO 2: step_fetch_emails() → list[dict]
├── _procesar_un_correo() → None  (orquesta los módulos de abajo)
└── run_pipeline() → None

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
├── 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
└── pipeline_utils.py  log, ruta_carpeta

Proceso de run_pipeline()

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()

flowchart TD
    A[_procesar_un_correo] --> B[1-2. BD: ¿ya procesado?]
    B -- Sí --> C[⏭️ Saltar]
    B -- No --> D[Guardar en BD]
    D --> E[3. descargar_adjuntos]
    E --> F{Hay adjuntos?}
    F -- No --> C
    F -- Sí --> G[4. _descomprimir ZIPs]
    G --> H[5. filtrar_y_limpiar]
    H --> I{Hay PDF/XML?}
    I -- No --> RC[Rechazo del correo<br>responder al remitente]
    I -- Sí --> J[6. procesar_pdfs]:::highlight
    J --> K[7. procesar_xmls]:::highlight
    K --> K2[8. completar_guias]
    K2 --> K3[9. validar_guias]
    K3 --> L{Hay documentos?}
    L -- No --> RC
    L -- Sí --> M[10. guardar_resultado]
    M --> N[11. Filtrar facturas tipo 01]
    N --> O{Hay facturas?}
    O -- No --> RC
    O -- Sí --> P[Bucle: por cada factura]

    P --> P2{11.5 ¿Ya existe en SAP?}
    P2 -- Sí --> V
    P2 -- No --> Q[12. validate_prices]:::highlight
    Q --> Q2[12.5 obtener_valor_referencial_sap]
    Q2 --> R[13. build_sap_draft]:::highlight
    R --> S[14. guardar_draft_json]
    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]
    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]
    V --> W
    X --> W2[16. Correo técnico al equipo<br>NO se responde al proveedor]
    FL --> P
    W --> P
    W2 --> P

    classDef highlight fill:#fff3e0,stroke:#e65100,stroke-width:3px;

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.


Funciones por módulo

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.

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
_procesar_factura(email, token, email_addr, factura, output_folder) pipeline Valida una factura contra SAP, crea el draft y notifica; devuelve si hay que reintentar el correo
importes_coinciden(a, b) pipeline_utils Compara importes a nivel de céntimos: diferencia redondeada a 2 decimales, hasta 1 céntimo
version_del_proyecto() pipeline_utils Versión de pyproject.toml, para el log de arranque
hay_error_de_consulta(resultados_val) draft_sap Detalle del primer error de consulta a SAP que dejó validate_prices, o None
hana_disponible() draft_sap Abre y cierra una conexión a HANA: descarta que una guía "no encontrada" sea una caída
buscar_factura_existente(ruc, tipo, serie, numero) sapb1_manager ¿La factura ya está en SAP (factura de compra no anulada o draft)? Ver sapb1_manager.md

copiar_a_red(data) — Copia a ruta de red

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.
  • 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"].

Flujo de llamadas

run_pipeline
  ├── database_utilities.init_db
  ├── step_authenticate
  │     └── outlook_utilities.get_access_token
  ├── step_fetch_emails
  │     └── outlook_utilities.get_emails
  └── _procesar_un_correo (por cada email)
        ├── database_utilities.get_existing_ids / save_emails
        ├── descargar_adjuntos
        │     └── outlook_utilities.download_attachments
        ├── _descomprimir
        │     └── email_utilities.descomprimir_adjuntos
        ├── filtrar_y_limpiar
        ├── procesar_pdfs
        │     └── pdf_utilities.extraer_datos_pdf / renombrar_pdf
        ├── procesar_xmls
        │     ├── sunat_schema.extraer_datos_xml
        │     ├── pdf_utilities.extraer_referencias
        │     └── copiar_a_red
        ├── completar_guias
        ├── validar_guias
        ├── guardar_resultado
        │     └── sunat_schema.split_serie_numero, pdf_utilities.TIPO_MAP
        ├── (por cada factura tipo 01)
        │     ├── validate_prices
        │     │     ├── 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*
        │     ├── build_validation_html
        │     ├── obtener_valor_referencial_sap
        │     │     └── hana_manager.obtener_valor_referencial
        │     ├── build_valor_referencial_html
        │     ├── build_sap_draft
        │     │     └── sapb1_manager.obtener_condicion_pago (BusinessPartners +
        │     │           PaymentTermsTypes, para calcular DocDueDate)
        │     ├── guardar_draft_json
        │     ├── sapb1_manager.create_draft  (si todas las líneas coinciden)
        │     ├── build_notification_body
        │     ├── respuesta_proveedor.enviar_a_equipo      (correo técnico, solo equipo)
        │     └── respuesta_proveedor.responder            (plantilla aceptado/rechazado, ver respuesta_proveedor.md)

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
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
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.

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.