se divide pipeline.py en módulos (adjuntos, documentos, persistencia, draft_sap, notificaciones)

Las funciones anidadas de _procesar_un_correo pasan a funciones de módulo
con parámetros explícitos; pipeline.py queda solo como orquestador.
copiar_a_red lee las variables de entorno al ejecutarse. Se actualizan docs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-20 22:03:38 -05:00
1 parent c5210c8a57
commit 6cc5e03209
9 files changed
+1309 -1228

No files matched your search

+117
View File
@@ -0,0 +1,117 @@
"""Cuerpos HTML de los correos de notificación."""
# ============================================================================
def build_validation_html(resultados: list[dict]) -> str:
"""
Genera una tabla HTML con el resultado de la validación de precios
para incluir en el cuerpo del email de notificación.
Args:
resultados: Lista de resultados de ``validate_prices``.
Returns:
str — Código HTML de la tabla con columnas:
Guía, Estado, Draft Price, SAP, Almacén, Resultado.
Example:
Con un resultado ``ok=True`` y ``grflete="09T001-127773"``::
html = build_validation_html(resultados)
# → "<table ...><tr><td>09T001-127773</td><td>✅</td>...</tr></table>"
"""
rows = ""
for r in resultados:
icono = "✅" if r["ok"] else "❌"
dp = f"{r['draft_price']:.6f}" if r["draft_price"] is not None else "-"
sap_price_str = f"{r['sap_price']:.6f}" if r["sap_price"] is not None else "-"
rows += f"<tr><td>{r['grflete']}</td><td>{icono}</td><td>{dp}</td><td>{sap_price_str}</td><td>{r['warehouse_code']}</td><td>{r['label']}</td></tr>"
return (
"<table border='1' cellpadding='6' cellspacing='0' style='border-collapse:collapse;'>"
"<tr><th>Guía</th><th>Estado</th><th>Draft Price</th><th>SAP</th><th>Almacén</th><th>Resultado</th></tr>"
f"{rows}</table>"
)
# ============================================================================
def build_valor_referencial_html(factura: dict, guias_valor_ref: list[dict]) -> str:
"""
Genera una tabla HTML comparando el valor referencial del XML de la
factura contra el valor referencial obtenido en SAP
(``Sp_ObtenerValorReferencial``) por cada guía de remisión, y el
total acumulado.
Args:
factura: Diccionario de la factura (usa ``lineas[]`` para sumar
``valor_referencial`` extraído del XML).
guias_valor_ref: Resultado de ``obtener_valor_referencial_sap``.
Returns:
str — Tabla HTML con una fila por guía, el total SAP, el valor
del XML y un ícono de coincidencia.
"""
valor_xml = sum(l.get("valor_referencial") or 0 for l in factura.get("lineas", []))
total_sap = sum(g["valor_sap"] for g in guias_valor_ref)
rows = "".join(
f"<tr><td>09{g['serie']}-{g['numero']}</td><td>{g['valor_sap']:.2f}</td></tr>"
for g in guias_valor_ref
)
coincide = abs(valor_xml - total_sap) < 0.01
icono = "✅" if coincide else "❌"
return (
"<table border='1' cellpadding='6' cellspacing='0' style='border-collapse:collapse;'>"
"<tr><th>Guía de Remisión</th><th>Valor Referencial SAP</th></tr>"
f"{rows}"
f"<tr><td><b>Total SAP</b></td><td><b>{total_sap:.2f}</b></td></tr>"
f"<tr><td><b>Valor Referencial XML</b></td><td><b>{valor_xml:.2f}</b></td></tr>"
f"<tr><td colspan='2'>{icono} {'Coincide' if coincide else 'No coincide'}</td></tr>"
"</table>"
)
# ============================================================================
def build_notification_body(status: str, factura: dict, validation_table: str, valor_referencial_table: str, **kwargs) -> str:
"""
Construye el cuerpo HTML del email de notificación del resultado
del proceso SAP.
Args:
status: ``"created"`` (draft creado), ``"error"`` (error al
crear), o cualquier otro valor (validación falló, draft no
creado).
factura: Diccionario de la factura (se usa ``serie``,
``numero``, ``razon_social_emisor``).
validation_table: Tabla HTML generada por
``build_validation_html``.
valor_referencial_table: Tabla HTML generada por
``build_valor_referencial_html``.
**kwargs: Argumentos adicionales:
- ``doc_entry`` (int): DocEntry del draft creado.
- ``error`` (str): Mensaje de error si ocurrió.
Returns:
str — Cuerpo HTML completo del email.
Example:
Draft creado exitosamente::
body = build_notification_body(
"created", factura, tabla_html, valref_html, doc_entry=2021
)
# → "<p>✅ Draft SAP B1 creado exitosamente.</p>..."
"""
base = (
f"<p><b>Factura:</b> {factura.get('serie')}-{factura.get('numero')}</p>"
f"<p><b>Emisor:</b> {factura.get('razon_social_emisor', '')}</p>"
)
valref_section = f"<h3>Valor Referencial (XML vs SAP)</h3>{valor_referencial_table}"
if status == "created":
return f"<p>✅ Draft SAP B1 creado exitosamente.</p><p><b>DocEntry:</b> {kwargs.get('doc_entry')}</p>{base}<h3>Validación de precios</h3>{validation_table}{valref_section}"
elif status == "error":
return f"<p>❌ Error al crear Draft SAP B1.</p>{base}<p><b>Error:</b> {kwargs.get('error')}</p><h3>Validación de precios</h3>{validation_table}{valref_section}"
else:
return f"<p>⏭️ Draft NO creado. Los precios de las guías no coinciden con SAP.</p>{base}<h3>Validación de precios</h3>{validation_table}{valref_section}"