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:
1 parent
d4c1e0a264
commit
9d295d6485
261 files changed
+7876
-7
No files matched your search
@@ -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()
|
||||
@@ -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"
|
||||
@@ -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()
|
||||
@@ -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}")
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 261 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user