se reorganiza tests/ y se agrega suite pytest

Los scripts manuales pasan a tools/ y los correos de ejemplo a
tests/fixtures/. Suite de 95 pruebas: caracterización de la extracción sobre
los 46 correos (resultados esperados en tests/golden/, fuera de la carpeta
que descargar_correos_prueba borra), unitarias de draft_sap, notificaciones,
documentos, adjuntos y config_validator, y flujo completo de
_procesar_un_correo con SAP/HANA/Outlook simulados. pytest como dependencia
dev y configuración en pyproject.toml.

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 d4c1e0a264
commit 9d295d6485
261 files changed
+7876 -7

No files matched your search

-76
View File
@@ -1,76 +0,0 @@
import json
import os
from glob import glob
CARPETA_BASE = os.path.join(os.path.dirname(__file__), "correos_prueba")
def _doc_key(d):
return (d.get("tipo"), d.get("serie"), d.get("numero"))
def completar_guias_desde_pdf(xml_path: str, pdf_path: str) -> int:
with open(xml_path, encoding="utf-8") as f:
xml_data = json.load(f)
try:
with open(pdf_path, encoding="utf-8") as f:
pdf_data = json.load(f)
except FileNotFoundError:
return 0
pdf_by_key = {_doc_key(d): d for d in pdf_data}
total_copiadas = 0
for xd in xml_data:
k = _doc_key(xd)
has_all_guias = all(
len(linea.get("guias_remision") or []) > 0
for linea in xd.get("lineas", [])
)
if has_all_guias:
continue
pd = pdf_by_key.get(k)
if not pd:
continue
for li, xl in enumerate(xd.get("lineas", [])):
if len(xl.get("guias_remision") or []) > 0:
continue
pl = pd["lineas"][li] if li < len(pd.get("lineas", [])) else {}
p_gr = pl.get("guias_remision") or []
if p_gr:
xl["guias_remision"] = list(p_gr)
total_copiadas += len(p_gr)
# También copiar guia_transportista si XML no tiene
if not xl.get("guia_transportista") and pl.get("guia_transportista"):
xl["guia_transportista"] = dict(pl["guia_transportista"])
if total_copiadas > 0:
with open(xml_path, "w", encoding="utf-8") as f:
json.dump(xml_data, f, ensure_ascii=False, indent=2)
return total_copiadas
def main():
carpetas = sorted(glob(os.path.join(CARPETA_BASE, "[0-9][0-9][0-9]_correo")))
total_global = 0
for fp in carpetas:
folder = os.path.basename(fp)
idx = folder[:3]
xml_path = os.path.join(fp, f"xml_resultado_{idx}.json")
pdf_path = os.path.join(fp, f"pdf_resultado_{idx}.json")
if not os.path.exists(xml_path):
continue
n = completar_guias_desde_pdf(xml_path, pdf_path)
if n > 0:
print(f" {folder}: {n} guías copiadas")
total_global += n
print(f"\nTotal guías copiadas: {total_global}")
if __name__ == "__main__":
main()
+13
View File
@@ -0,0 +1,13 @@
import os
import pytest
RAIZ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
FIXTURES = os.path.join(RAIZ, "tests", "fixtures", "correos_prueba")
GOLDEN = os.path.join(RAIZ, "tests", "golden")
@pytest.fixture
def actualizar_golden() -> bool:
"""True si se ejecutó con ``UPDATE_GOLDEN=1`` (regenera los resultados esperados)."""
return os.environ.get("UPDATE_GOLDEN") == "1"
-99
View File
@@ -1,99 +0,0 @@
import os
import sys
import json
import shutil
from datetime import datetime
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from dotenv import load_dotenv
import outlook_utilities
import email_utilities
CARPETA_BASE = os.path.join(os.path.dirname(__file__), "correos_prueba")
def limpiar_carpetas():
if os.path.exists(CARPETA_BASE):
shutil.rmtree(CARPETA_BASE)
print(f"🗑️ Carpeta {CARPETA_BASE} eliminada")
os.makedirs(CARPETA_BASE, exist_ok=True)
print(f"📁 Carpeta {CARPETA_BASE} creada")
def descargar_correos():
load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), "..", ".env"))
EMAIL = os.getenv("EMAIL", "")
if not EMAIL:
print("❌ Variable EMAIL no encontrada en .env")
return
print("🔑 Autenticando en Microsoft Graph...")
token = outlook_utilities.get_access_token()
if not token:
print("❌ No se pudo obtener token")
return
fecha_inicio = "2026-06-01"
fecha_fin = "2026-07-31"
print(f"📬 Descargando correos de {fecha_inicio} a {fecha_fin}...")
emails = outlook_utilities.get_emails(EMAIL, token, fecha_inicio, fecha_fin)
if not emails:
print("❌ No se encontraron correos")
return
print(f"📨 Total correos encontrados: {len(emails)}")
indice = {}
for idx, email in enumerate(emails, start=1):
correlativo = f"{idx:03d}"
carpeta_correo = os.path.join(CARPETA_BASE, f"{correlativo}_correo")
os.makedirs(carpeta_correo, exist_ok=True)
print(f"\n[{correlativo}] 📬 {email.get('fecha','')} - {email.get('subject','')[:60]}...")
adjuntos = outlook_utilities.download_attachments(
EMAIL, email["id"], token, carpeta_correo
)
if not adjuntos:
print(f" ├── ⏭️ Sin adjuntos")
os.rmdir(carpeta_correo)
continue
email["adjuntos"] = adjuntos
email_utilities.descomprimir_adjuntos([email], carpeta_correo)
archivos_validos = email_utilities.filtrar_archivos_validos(email.get("adjuntos", []))
carpeta_abs = os.path.abspath(carpeta_correo)
for archivo in email.get("adjuntos", []):
ruta = os.path.join(carpeta_abs, archivo)
if archivo not in archivos_validos and os.path.exists(ruta):
os.remove(ruta)
if archivos_validos:
email["adjuntos"] = archivos_validos
indice[correlativo] = {
"id": email["id"],
"fecha": email.get("fecha", ""),
"subject": email.get("subject", ""),
}
print(f" └── ✅ {len(archivos_validos)} archivo(s) válido(s)")
ruta_indice = os.path.join(CARPETA_BASE, "indice_correos.json")
with open(ruta_indice, "w", encoding="utf-8") as f:
json.dump(indice, f, ensure_ascii=False, indent=2)
print(f"\n{'='*60}")
print(f"📊 Total correos descargados: {len(indice)}")
print(f"📄 Índice guardado en: {ruta_indice}")
if __name__ == "__main__":
limpiar_carpetas()
descargar_correos()
-48
View File
@@ -1,48 +0,0 @@
import os
import sys
import xml.etree.ElementTree as ET
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from sunat_schema import NAMESPACES, _find_text
ns = NAMESPACES
ns["cbc"] = "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"
CARPETA_BASE = os.path.join(os.path.dirname(__file__), "correos_prueba")
def explorar_notes(ruta_carpeta):
archivos = sorted(
os.path.join(ruta_carpeta, f) for f in os.listdir(ruta_carpeta)
if f.lower().endswith(".xml") and os.path.isfile(os.path.join(ruta_carpeta, f))
)
for xml_path in archivos:
tree = ET.parse(xml_path)
root = tree.getroot()
if "Invoice" not in root.tag:
continue
tipo = _find_text(root, "inv:InvoiceTypeCode", ns) or _find_text(root, "cbc:InvoiceTypeCode", ns) or ""
if tipo != "01":
continue
print(f"\n=== {os.path.basename(xml_path)} ===")
lineas = root.findall(".//cac:InvoiceLine", ns)
for i, linea in enumerate(lineas):
notes = linea.findall("cbc:Note", ns)
desc = _find_text(linea, "cac:Item/cbc:Description", ns) or ""
print(f" Línea {i}: Description={desc[:80]}...")
for note in notes:
print(f" Note[{note.attrib.get('languageLocaleID', '?')}]: {note.text}")
if __name__ == "__main__":
carpeta = sys.argv[1] if len(sys.argv) > 1 else "001_correo"
ruta = os.path.join(CARPETA_BASE, carpeta)
if os.path.isdir(ruta):
explorar_notes(ruta)
else:
print(f"❌ Carpeta no encontrada: {ruta}")
Loaded 100 of 261 files, more files were not shown because too many files have changed in this diff. Show more