Files
descarga_factura_transporte/docs/pipeline.md
T
GeorgeandClaude Sonnet 5.5 4deb9d4e4d se responde al remitente con las plantillas de aceptado/rechazado
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>
2026-10-22 08:55:54 -05:00

10 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 --> 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{¿Error de SAP/HANA?}
    T -- Sí --> 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]
    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

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…) Equipo Rechazado con observaciones, al remitente
error (SAP/HANA, fallo inesperado) 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.