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:
1 parent
ee64e5f773
commit
7d2f3212c0
9 files changed
+1309
-1228
No files matched your search
+163
@@ -0,0 +1,163 @@
|
||||
"""Persistencia a disco (JSON) de resultados y drafts."""
|
||||
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
import sunat_schema
|
||||
import pdf_utilities
|
||||
from pipeline_utils import log
|
||||
|
||||
|
||||
# ============================================================================
|
||||
def guardar_resultado(documentos: list[dict], datos_pdf: list[dict], output_folder: str) -> list[str]:
|
||||
"""
|
||||
Guarda los resultados XML y PDF en archivos JSON separados.
|
||||
|
||||
El nombre del archivo sigue el patrón
|
||||
``{tipo}_resultado_{serie}-{numero}_{timestamp}.json`` y se escribe en
|
||||
``output_folder``.
|
||||
|
||||
Args:
|
||||
documentos: Lista de diccionarios con datos extraídos de XML
|
||||
(producida por ``procesar_xmls``).
|
||||
datos_pdf: Lista de diccionarios con datos extraídos de PDF
|
||||
(producida por ``procesar_pdfs``).
|
||||
|
||||
Example:
|
||||
Si ``documentos[0]`` tiene ``serie="F002", numero="486"``::
|
||||
|
||||
guardar_resultado(documentos, pdf_datos, output_folder)
|
||||
# → Crea xml_resultado_F002-486_2026-07-08_12-00-00.json
|
||||
# → Crea pdf_resultado_F002-486_2026-07-08_12-00-00.json
|
||||
"""
|
||||
if not documentos:
|
||||
return []
|
||||
if not os.path.exists(output_folder):
|
||||
os.makedirs(output_folder, exist_ok=True)
|
||||
ts = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
serie = documentos[0].get("serie", "unknown")
|
||||
numero = documentos[0].get("numero", "unknown")
|
||||
base = f"{serie}-{numero}_{ts}"
|
||||
|
||||
saved: list[str] = []
|
||||
|
||||
# XML result
|
||||
xml_name = f"xml_resultado_{base}.json"
|
||||
xml_path = os.path.join(output_folder, xml_name)
|
||||
with open(xml_path, "w", encoding="utf-8") as f:
|
||||
json.dump(documentos, f, ensure_ascii=False, indent=4)
|
||||
log(f"💾 XML guardado: {xml_name}")
|
||||
saved.append(xml_path)
|
||||
|
||||
# PDF result (normalizado al mismo schema que XML)
|
||||
pdf_docs = []
|
||||
for raw in datos_pdf:
|
||||
if raw.get("error"):
|
||||
continue
|
||||
td = (raw.get("tipo_documento") or "").upper()
|
||||
tipo = pdf_utilities.TIPO_MAP.get(td)
|
||||
if not tipo:
|
||||
continue
|
||||
sn = raw.get("serie_numero") or ""
|
||||
serie_p, num_p = sunat_schema.split_serie_numero(sn)
|
||||
if not serie_p:
|
||||
continue
|
||||
doc = {
|
||||
"ruc_emisor": raw.get("ruc_emisor"),
|
||||
"razon_social_emisor": None, # el PDF no extrae la razón social del emisor
|
||||
"fecha_emision": raw.get("fecha_emision"),
|
||||
"tipo": tipo,
|
||||
"serie": serie_p,
|
||||
"numero": num_p,
|
||||
"ruc_receptor": raw.get("ruc_receptor"),
|
||||
"razon_social_receptor": raw.get("nombre_receptor"),
|
||||
"total_factura": raw.get("monto_total"),
|
||||
"documentos_referencia": [],
|
||||
"tiene_detraccion": False,
|
||||
"codigo_detraccion": None,
|
||||
"porcentaje_detraccion": None,
|
||||
"monto_detraccion": None,
|
||||
"lineas": [],
|
||||
"ruta_pdf": raw.get("archivo"),
|
||||
}
|
||||
det = raw.get("detraccion") or {}
|
||||
if det.get("tiene"):
|
||||
doc["tiene_detraccion"] = True
|
||||
doc["porcentaje_detraccion"] = det.get("porcentaje")
|
||||
doc["monto_detraccion"] = det.get("monto")
|
||||
for item in raw.get("items") or []:
|
||||
linea = {
|
||||
"descripcion": item.get("descripcion", ""),
|
||||
"cantidad": item.get("cantidad"),
|
||||
"precio_unitario": item.get("precio_unitario"),
|
||||
"total_linea": item.get("total"),
|
||||
"guias_remision": [],
|
||||
"guia_transportista": None,
|
||||
}
|
||||
for g in item.get("guias_remision") or []:
|
||||
if isinstance(g, str):
|
||||
m = re.search(r"\b([TVE][A-Z0-9]{3,4})\s*-?\s*(\d+)", g)
|
||||
if m:
|
||||
linea["guias_remision"].append({
|
||||
"tipo": "09",
|
||||
"serie": m.group(1).upper(),
|
||||
"numero": m.group(2).lstrip("0") or "0",
|
||||
})
|
||||
elif isinstance(g, dict):
|
||||
linea["guias_remision"].append(dict(g))
|
||||
gt = item.get("guia_transportista")
|
||||
if gt:
|
||||
if isinstance(gt, str):
|
||||
m = re.search(r"\b([TVE][A-Z0-9]{3,4})\s*-?\s*(\d+)", gt)
|
||||
if m:
|
||||
linea["guia_transportista"] = {
|
||||
"tipo": "31",
|
||||
"serie": m.group(1).upper(),
|
||||
"numero": m.group(2).lstrip("0") or "0",
|
||||
}
|
||||
elif isinstance(gt, dict):
|
||||
linea["guia_transportista"] = dict(gt)
|
||||
doc["lineas"].append(linea)
|
||||
pdf_docs.append(doc)
|
||||
|
||||
if pdf_docs:
|
||||
pdf_name = f"pdf_resultado_{base}.json"
|
||||
pdf_path = os.path.join(output_folder, pdf_name)
|
||||
with open(pdf_path, "w", encoding="utf-8") as f:
|
||||
json.dump(pdf_docs, f, ensure_ascii=False, indent=4)
|
||||
log(f"💾 PDF guardado: {pdf_name}")
|
||||
saved.append(pdf_path)
|
||||
|
||||
return saved
|
||||
|
||||
|
||||
# ============================================================================
|
||||
def guardar_draft_json(draft: dict, serie: str, numero: str, output_folder: str) -> None:
|
||||
"""
|
||||
Guarda el payload del draft SAP B1 en un archivo JSON para
|
||||
depuración.
|
||||
|
||||
El nombre sigue el patrón
|
||||
``draft_sapb1_{serie}-{numero}_{timestamp}.json``.
|
||||
|
||||
Args:
|
||||
draft: Payload del draft SAP B1 (producido por
|
||||
``build_sap_draft``).
|
||||
serie: Serie de la factura (ej. ``"F002"``).
|
||||
numero: Número de la factura (ej. ``"486"``).
|
||||
|
||||
Example:
|
||||
Draft de factura F002-486::
|
||||
|
||||
guardar_draft_json(draft, "F002", "486", output_folder)
|
||||
# → Crea draft_sapb1_F002-486_2026-07-08_12-00-00.json
|
||||
"""
|
||||
if not os.path.exists(output_folder):
|
||||
os.makedirs(output_folder, exist_ok=True)
|
||||
ts = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
||||
filename = f"draft_sapb1_{serie}-{numero}_{ts}.json"
|
||||
with open(os.path.join(output_folder, filename), "w", encoding="utf-8") as f:
|
||||
json.dump(draft, f, ensure_ascii=False, indent=4)
|
||||
log(f"💾 Draft guardado: {filename}")
|
||||
Reference in new issue
Block a user