2026-09-02 07:40:26 -05:00
# Inventario del código
## Archivos
| Archivo | Líneas | Rol |
|---------|--------|-----|
2026-10-22 12:08:38 -05:00
| `main.py` | 142 | Demonio orquestador (loop de horarios) |
| `pipeline.py` | 437 | Orquestador del pipeline (autenticar, descargar correos, procesar un correo) |
| `pipeline_utils.py` | 38 | `log()` (logging con indentación de árbol y `severity` ) y `ruta_carpeta()` |
2026-10-14 22:49:11 -05:00
| `adjuntos.py` | 82 | Descarga de adjuntos (Graph) y filtrado PDF/XML |
2026-10-22 12:08:38 -05:00
| `documentos.py` | 378 | Extracción PDF/XML, asignación de guías, copia a red |
| `persistencia.py` | 165 | Guardado de JSON de resultados y de drafts |
| `draft_sap.py` | 444 | Validación de precios contra SAP, valor referencial y construcción del draft |
| `notificaciones.py` | 121 | Cuerpos HTML de los correos de notificación |
2026-10-14 22:49:11 -05:00
| `sunat_schema.py` | 722 | Parseo de XML SUNAT (Invoice, DespatchAdvice) |
| `pdf_utilities.py` | 471 | Parseo de PDFs (factura, guías) |
2026-10-22 12:08:38 -05:00
| `sapb1_manager.py` | 469 | Comunicación con SAP B1 Service Layer |
2026-10-22 08:55:54 -05:00
| `outlook_utilities.py` | 274 | Comunicación con Microsoft Graph |
2026-10-14 22:49:11 -05:00
| `hana_manager.py` | 141 | Comunicación con SAP HANA directa (fallback de `sapb1_manager` y valor referencial) |
2026-10-22 12:08:38 -05:00
| `database_utilities.py` | 164 | SQLite local (cache de correos) |
2026-10-15 00:14:41 -05:00
| `tools/*_prueba.py` , `tools/completar_guias_pipeline.py` , `tools/explorar_notes_xml.py` | — | Scripts manuales sobre `tests/fixtures/correos_prueba/` (anteriores a la suite pytest) |
2026-10-22 12:08:38 -05:00
| `tools/verificar_conexiones.py` | 85 | Comprueba `.env` , Graph, SAP, HANA y la ruta de red sin modificar nada |
| `deploy/*.ps1` | — | Instalar, actualizar (versión etiquetada / rollback) y desinstalar el servicio de Windows con NSSM |
2026-10-14 22:49:11 -05:00
| `tools/consulta_rapida.py` | 120 | Script auxiliar para reprocesar correos por ID (sets en `tools/ids_prueba.json` ) |
2026-10-22 12:08:38 -05:00
| `seguimiento.py` | 226 | Códigos de seguimiento y resultado por comprobante (tabla `comprobantes` ) |
| `respuesta_proveedor.py` | 300 | Respuesta de aceptado/rechazado al remitente (modo prueba/real, protecciones, contextos) |
| `observaciones.py` | 165 | Catálogo de motivos de rechazo y sus textos |
2026-10-22 08:55:54 -05:00
| `plantillas.py` | 123 | Renderizador de las plantillas HTML de correo |
| `config_validator.py` | 98 | Valida el `.env` al arrancar (obligatorias, ruta de red, avisos de SSL) |
2026-10-14 22:49:11 -05:00
| `email_utilities.py` | 55 | Utilitarios de adjuntos (ZIP, filtrado) |
| `logger_manager.py` | 150 | Logging a archivo rotado (30 días) + consola + captura de `print()` |
2026-10-22 14:56:34 -05:00
Documentación por módulo: `sapb1_manager.py` → [sapb1_manager.md ](sapb1_manager.md ), despliegue → [despliegue_windows.md ](despliegue_windows.md ), `respuesta_proveedor.py` /`observaciones.py` /`plantillas.py` → [respuesta_proveedor.md ](respuesta_proveedor.md ), `seguimiento.py` → [seguimiento.md ](seguimiento.md ), `outlook_utilities.py` → [outlook_utilities.md ](outlook_utilities.md ), `hana_manager.py` → [hana_manager.md ](hana_manager.md ), validación y draft → [validar_precios.md ](validar_precios.md ) y [construir_draft.md ](construir_draft.md ).
2026-09-02 07:40:26 -05:00
---
## Dependencias entre archivos
```
main.py
2026-09-10 13:21:04 -05:00
├── logger_manager.py
2026-09-02 07:40:26 -05:00
└── pipeline.py
├── sunat_schema.py
├── pdf_utilities.py
├── sapb1_manager.py
│ └── hana_manager.py
2026-09-10 13:21:04 -05:00
├── hana_manager.py (valor referencial, Sp_ObtenerValorReferencial)
2026-09-02 07:40:26 -05:00
├── outlook_utilities.py
├── email_utilities.py
└── database_utilities.py
2026-10-14 22:28:48 -05:00
```
Módulos que `pipeline.py` orquesta (todos usan `pipeline_utils.py` ):
```
pipeline.py
2026-10-22 14:56:34 -05:00
├── adjuntos.py → outlook_utilities
├── documentos.py → sunat_schema, pdf_utilities
├── persistencia.py → sunat_schema, pdf_utilities
├── draft_sap.py → sapb1_manager, hana_manager
├── notificaciones.py (correo técnico para el equipo)
├── observaciones.py (motivos de rechazo y sus textos)
├── respuesta_proveedor.py → plantillas.py, observaciones.py, outlook_utilities, seguimiento.py
└── seguimiento.py → database_utilities
main.py → config_validator.py (valida el .env al arrancar)
```
2026-09-10 13:21:04 -05:00
2026-10-14 22:38:36 -05:00
tools/consulta_rapida.py
2026-09-10 13:21:04 -05:00
├── logger_manager.py
├── database_utilities.py
└── pipeline.py (_procesar_un_correo, step_authenticate)
2026-09-02 07:40:26 -05:00
```
---
## Flujo de datos
```
main()
└── run_pipeline()
├── step_authenticate() → token MS Graph
├── step_fetch_emails() → emails[]
└── _procesar_un_correo() (por cada email)
├── BD: verificar duplicado
2026-10-14 22:28:48 -05:00
├── adjuntos.descargar_adjuntos() → archivos
├── email_utilities.descomprimir_adjuntos() ZIPs
├── adjuntos.filtrar_y_limpiar() → solo PDF/XML
├── documentos.procesar_pdfs() → pdf_cache
2026-09-02 07:40:26 -05:00
│ └── pdf_utilities.extraer_datos_pdf()
2026-10-14 22:28:48 -05:00
├── documentos.procesar_xmls(pdf_cache) → documentos[]
2026-09-10 13:21:04 -05:00
│ ├── sunat_schema.extraer_datos_xml()
2026-10-14 22:28:48 -05:00
│ └── documentos.copiar_a_red() → copia PDF/XML a ruta de red (RUTA_RED_FACTURACION)
├── documentos.completar_guias(documentos, pdf_cache) → completa guías faltantes desde PDF
├── documentos.validar_guias(documentos) → advierte guías con datos incompletos
├── persistencia.guardar_resultado(documentos)
├── draft_sap.validate_prices(factura) → (ok, resultados)
2026-09-02 07:40:26 -05:00
│ └── sapb1_manager.get_delivery_notes()
│ ├── SL (Service Layer) GET /DeliveryNotes
2026-09-10 13:21:04 -05:00
│ ├── HANA fallback (get_delivery: ODLN, luego OWTR)
│ └── si vino de OWTR: GET StockTransfers(DocEntry)
│ → U_IDC_NUMOV → GET Orders(U_IDC_NUMOV)
2026-10-14 22:28:48 -05:00
├── draft_sap.obtener_valor_referencial_sap(factura) → guias_valor_ref
2026-09-10 13:21:04 -05:00
│ └── hana_manager.obtener_valor_referencial() (Sp_ObtenerValorReferencial)
2026-10-14 22:28:48 -05:00
├── draft_sap.build_sap_draft(factura, resultados) → draft (incluye U_VS_ANEXO_PDF/XML)
2026-09-10 13:21:04 -05:00
│ └── sapb1_manager.obtener_condicion_pago(CardCode) → DocDueDate
2026-10-14 22:28:48 -05:00
├── persistencia.guardar_draft_json(draft)
2026-09-02 07:40:26 -05:00
├── sapb1_manager.create_draft(draft)
└── outlook_utilities.send_email(notificación)
```
---
## Problemas conocidos hasta ahora
| # | Problema | Archivo | Estado |
|---|----------|---------|--------|
| 1 | ` _extraer_serie_numero_desde_texto` no limpiaba leading zeros | ` sunat_schema.py` | ✅ Corregido |
| 2 | Al asignar guías por línea, si hay +refs que líneas, las sobrantes se perdían | ` pipeline.py` | ✅ Corregido |
| 3 | SL sin fallback a HANA | ` sapb1_manager.py` | ✅ Corregido |
| 4 | Ctrl+C mostraba traceback | ` main.py` | ✅ Corregido |
| 5 | ` .env.dev` vs ` .env` inconsistente | ` sapb1_manager.py` | ✅ Corregido |
| 6 | ` HANA_HOST` vs ` HANA_SERVER` en ` .env` | ` hana_manager.py` | ✅ Corregido |
| 7 | Columna ` Cancelled` vs ` CANCELED` en HANA | ` hana_manager.py` | ✅ Corregido |
2026-09-10 13:21:04 -05:00
| 8 | Bloque ` if __name__ == "__main__"` borraba un correo real de la BD al ejecutar el módulo directo | ` database_utilities.py` | ✅ Corregido |
| 9 | ` extraer_datos_xml` solo procesaba guías tipo ` 31`; tipo ` 09` (remitente) se descartaba pese a estar documentado | ` sunat_schema.py` | ✅ Corregido |
| 10 | ` DatosComprobante.__str__` usaba ` ref.id` (campo que ya no existe en ` DocumentoReferencia`) | ` sunat_schema.py` | ✅ Corregido |
| 11 | Log de error HTTP con ` print("%s", ...)` no interpolaba el mensaje | ` sapb1_manager.py` | ✅ Corregido |
| 12 | Fuga de conexión HANA si ` cursor()` fallaba (quedaba fuera del ` try`) | ` hana_manager.py` | ✅ Corregido |
| 13 | ` razon_social_emisor` se llenaba con el nombre del receptor en el JSON de depuración del PDF | ` pipeline.py` | ✅ Corregido |
| 14 | Typo ` VERIFI_SSL` en vez de ` VERIFY_SSL` | ` outlook_utilities.py` / ` .env` | ✅ Corregido |
| 15 | Conexiones SQLite sin ` try/finally` (fuga si fallaba una consulta a mitad de camino) | ` database_utilities.py` | ✅ Corregido |
| 16 | Código muerto sin llamadores (` quitar_correos_ya_registrados_en_base_de_datos`, ` procesar_adjuntos_email`) | ` email_utilities.py` | ✅ Corregido (eliminado) |
| 17 | ` logger_utilities.py` (redirección global de stdout, sin niveles ni rotación real) reemplazado por ` logger_manager.py` (logging estándar, rotación diaria 30 días, captura de ` print()`) | ` logger_manager.py` | ✅ Corregido |
| 18 | ` task()`/` last_task()` de ` logger_manager.py` no devolvían el sub-logger (` with ... as sub` daba siempre ` None`) | ` logger_manager.py` | ✅ Corregido |
| 19 | ` DOWNLOAD_FOLDER` duplicado en ` .env` | ` .env` | ✅ Corregido |
| 20 | Guías tipo ` 09` no se encontraban si el documento estaba en ` OWTR` (Traslado de Inventario) en vez de ` ODLN` | ` hana_manager.py` | ✅ Corregido (fallback ODLN → OWTR) |
| 21 | Parámetros ` tipo`/` serie` cruzados al llamar ` get_delivery`/` get_delivery_notes` (mapeaban al revés a ` U_BPP_MDSD`/` U_BPP_MDTD`) | ` hana_manager.py`, ` sapb1_manager.py`, ` pipeline.py` | ✅ Corregido (orden natural ` tipo, serie, numero`) |
| 22 | ` StockTransfers` (OWTR) no tiene líneas ` FL*`, por lo que la validación de precios fallaba siempre para esas guías | ` sapb1_manager.py` | ✅ Corregido (resuelve la Orden de Venta vía ` U_IDC_NUMOV` y valida contra ` Orders(DocEntry)`) |
| 23 | ` DocDueDate` del draft usaba ` fecha_emision` del XML, sin considerar la condición de pago del proveedor | ` pipeline.py`, ` sapb1_manager.py` | ✅ Corregido (` DocDueDate` = fecha de contabilización + días de ` PaymentTermsTypes`) |
| 24 | Patrón regex de valor referencial no reconocía la variante truncada "VALOR REFER" (ej. "VALOR REFER:S/ 1431.16") | ` sunat_schema.py` | ✅ Corregido |
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
> Antes eran funciones anidadas en ` _procesar_un_correo`; ahora son funciones de módulo que reciben explícitamente ` email`, ` download_folder`, ` output_folder`, etc. ` pipeline.py` solo las orquesta.
2026-09-10 13:21:04 -05:00
2026-10-14 22:28:48 -05:00
| Módulo | Función | Propósito |
|--------|---------|-----------|
| ` pipeline_utils` | ` ruta_carpeta` | Resuelve una ruta relativa al directorio del proyecto |
| ` pipeline_utils` | ` log` | Imprime con indentación de árbol (niveles 1-4) |
| ` adjuntos` | ` descargar_adjuntos` | Descarga adjuntos vía Graph API |
| ` adjuntos` | ` filtrar_y_limpiar` | Filtra PDF/XML, elimina el resto |
| ` documentos` | ` copiar_a_red` | Copia PDF/XML a la ruta de red de Facturación Electrónica (` RUTA_RED_FACTURACION`) |
| ` documentos` | ` procesar_pdfs` | Extrae datos de PDFs, renombra, genera cache |
| ` documentos` | ` procesar_xmls` | Procesa XMLs, cruza con PDF, asigna guías, copia a red |
| ` documentos` | ` completar_guias` | Completa guías faltantes en líneas XML desde el PDF cache |
| ` documentos` | ` validar_guias` | Advierte (log) guías con datos incompletos |
| ` persistencia` | ` guardar_resultado` | Guarda JSON con datos extraídos (XML y PDF) |
| ` persistencia` | ` guardar_draft_json` | Guarda draft JSON para depuración |
| ` draft_sap` | ` calcular_fecha_vencimiento` | Calcula ` DocDueDate` = fecha de contabilización + días de la condición de pago (` PaymentTermsTypes`) |
| ` draft_sap` | ` build_sap_draft` | Construye payload del draft SAP B1 |
| ` draft_sap` | ` validate_prices` | Valida precios contra DeliveryNotes / Orders (SAP) |
| ` draft_sap` | ` obtener_valor_referencial_sap` | Consulta ` Sp_ObtenerValorReferencial` en HANA por guía |
| ` notificaciones` | ` build_validation_html` | Genera tabla HTML de validación de precios |
| ` notificaciones` | ` build_valor_referencial_html` | Genera tabla HTML comparando valor referencial XML vs SAP |
| ` notificaciones` | ` build_notification_body` | Arma cuerpo del email de notificación |