import logging import os import adjuntos import documentos # ============================================================================ # completar_guias # ============================================================================ def test_completar_guias_desde_pdf_normaliza_strings(): xml_doc = { "ruc_emisor": "20537929520", "tipo": "01", "serie": "F002", "numero": "486", "lineas": [{"guias_remision": []}, {"guias_remision": [{"tipo": "09", "serie": "T001", "numero": "5"}]}], } pdf_cache = {"20537929520-01-F002-486.pdf": {"items": [ {"guias_remision": ["T001-00123"], "guia_transportista": "V002-0011"}, {"guias_remision": ["T001-999"]}, ]}} copiadas = documentos.completar_guias([xml_doc], pdf_cache) assert copiadas == 1 assert xml_doc["lineas"][0]["guias_remision"] == [{"tipo": "09", "serie": "T001", "numero": "123"}] assert xml_doc["lineas"][0]["guia_transportista"] == {"tipo": "31", "serie": "V002", "numero": "11"} assert xml_doc["lineas"][1]["guias_remision"][0]["numero"] == "5" # la que ya tenía no se toca def test_completar_guias_sin_pdf_no_hace_nada(): doc = {"ruc_emisor": "1", "tipo": "01", "serie": "F", "numero": "1", "lineas": [{"guias_remision": []}]} assert documentos.completar_guias([doc], {}) == 0 assert doc["lineas"][0]["guias_remision"] == [] # ============================================================================ # validar_guias # ============================================================================ def test_validar_guias_advierte_solo_las_invalidas(caplog): doc = {"serie": "F002", "numero": "1", "lineas": [{ "guias_remision": [{"tipo": "09", "serie": "T001", "numero": "1"}, {"tipo": "09", "serie": "T001"}], "guia_transportista": {"tipo": "31", "serie": "", "numero": "2"}, }]} with caplog.at_level(logging.WARNING, logger="pipeline"): documentos.validar_guias([doc]) mensajes = [r.getMessage() for r in caplog.records] assert len(mensajes) == 2 assert any("guias_remision[1] inválida" in m for m in mensajes) assert any("guia_transportista inválida" in m for m in mensajes) assert all(r.levelno == logging.WARNING for r in caplog.records) def test_validar_guias_sin_problemas_no_registra(caplog): doc = {"serie": "F", "numero": "1", "lineas": [{"guias_remision": [{"tipo": "09", "serie": "T001", "numero": "1"}]}]} with caplog.at_level(logging.WARNING, logger="pipeline"): documentos.validar_guias([doc]) assert caplog.records == [] # ============================================================================ # copiar_a_red # ============================================================================ def _comprobante(tmp_path, fecha="2026-03-05"): xml = tmp_path / "a.xml" pdf = tmp_path / "a.pdf" xml.write_text("") pdf.write_bytes(b"%PDF") return {"ruc_emisor": "20111", "tipo": "01", "serie": "F002", "numero": "7", "fecha_emision": fecha, "ruta_xml": str(xml), "ruta_pdf": str(pdf)} def test_copiar_a_red_arma_la_estructura(tmp_path, monkeypatch): red = tmp_path / "red" monkeypatch.setenv("RUTA_RED_FACTURACION", str(red)) monkeypatch.setenv("RUC_EMPRESA", "20999") data = _comprobante(tmp_path) documentos.copiar_a_red(data) destino = red / "2026" / "3" / "5" / "01" / "20999" / "20111" # mes y día sin ceros assert (destino / "20999-01-F002-7.xml").exists() assert (destino / "20999-01-F002-7_FormatoImpreso.pdf").exists() assert data["ruta_red_xml"].endswith("\\20999-01-F002-7.xml") # estilo UNC con backslash assert data["ruta_red_pdf"].endswith("\\20999-01-F002-7_FormatoImpreso.pdf") def test_copiar_a_red_deshabilitada_si_no_hay_ruta(tmp_path, monkeypatch): monkeypatch.setenv("RUTA_RED_FACTURACION", "") data = _comprobante(tmp_path) documentos.copiar_a_red(data) assert "ruta_red_xml" not in data and "ruta_red_pdf" not in data def test_copiar_a_red_con_una_ruta_que_no_es_absoluta_no_crea_carpetas_ni_apunta_el_draft_a_la_nada(tmp_path, monkeypatch, caplog): """Fuera de Windows una ruta UNC no es absoluta: antes se creaba como una carpeta local con ese nombre.""" monkeypatch.chdir(tmp_path) monkeypatch.setenv("RUTA_RED_FACTURACION", "red_relativa") monkeypatch.setenv("RUC_EMPRESA", "20999") data = _comprobante(tmp_path) with caplog.at_level(logging.WARNING, logger="pipeline"): documentos.copiar_a_red(data) assert not (tmp_path / "red_relativa").exists() # no se creó nada assert "ruta_red_xml" not in data and "ruta_red_pdf" not in data # el draft no referencia archivos inexistentes assert any("no es una ruta absoluta" in r.getMessage() for r in caplog.records) def test_copiar_a_red_fecha_invalida_no_falla(tmp_path, monkeypatch): red = tmp_path / "red" monkeypatch.setenv("RUTA_RED_FACTURACION", str(red)) monkeypatch.setenv("RUC_EMPRESA", "20999") data = _comprobante(tmp_path, fecha="no-es-fecha") documentos.copiar_a_red(data) assert not red.exists() and "ruta_red_xml" not in data # ============================================================================ # adjuntos.filtrar_y_limpiar # ============================================================================ def test_filtrar_conserva_pdf_xml_y_borra_el_resto(tmp_path): for nombre in ("a.pdf", "B.XML", "c.png"): (tmp_path / nombre).write_text("x") email = {"adjuntos": ["a.pdf", "B.XML", "c.png"]} assert adjuntos.filtrar_y_limpiar(email, str(tmp_path)) is True assert email["adjuntos"] == ["a.pdf", "B.XML"] assert sorted(os.listdir(tmp_path)) == ["B.XML", "a.pdf"] def test_filtrar_sin_pdf_ni_xml_borra_todo(tmp_path): for nombre in ("c.png", "d.docx"): (tmp_path / nombre).write_text("x") email = {"adjuntos": ["c.png", "d.docx"]} assert adjuntos.filtrar_y_limpiar(email, str(tmp_path)) is False assert os.listdir(tmp_path) == [] # ============================================================================ # Rutas: no dependen del directorio de trabajo # ============================================================================ def test_la_base_de_datos_va_junto_al_codigo_aunque_se_lance_desde_otra_carpeta(tmp_path, monkeypatch): import importlib.util import database_utilities monkeypatch.chdir(tmp_path) # un servicio puede arrancar desde cualquier carpeta spec = importlib.util.spec_from_file_location("database_utilities_nueva", database_utilities.__file__) nueva = importlib.util.module_from_spec(spec) spec.loader.exec_module(nueva) # DB_NAME se calcula al importar carpeta_del_codigo = os.path.dirname(os.path.abspath(database_utilities.__file__)) assert nueva.DB_NAME == os.path.join(carpeta_del_codigo, "emails.db") def test_ruta_carpeta_no_depende_del_directorio_actual(tmp_path, monkeypatch): import pipeline_utils monkeypatch.chdir(tmp_path) esperado = os.path.join(os.path.dirname(os.path.abspath(pipeline_utils.__file__)), "resultado") assert pipeline_utils.ruta_carpeta("resultado") == esperado def test_los_resultados_relativos_se_guardan_con_ruta_carpeta_no_en_el_cwd(tmp_path, monkeypatch): import persistencia proyecto, otro = tmp_path / "proyecto", tmp_path / "otro" proyecto.mkdir(), otro.mkdir() monkeypatch.chdir(otro) monkeypatch.setattr(persistencia, "ruta_carpeta", lambda r: os.path.join(proyecto, r)) persistencia.guardar_draft_json({"a": 1}, "F002", "1", "resultado") assert any(n.startswith("draft_sapb1_F002-1") for n in os.listdir(proyecto / "resultado")) assert os.listdir(otro) == [] # nada en el directorio desde el que se lanzó def test_una_carpeta_de_resultados_absoluta_se_respeta(tmp_path): import persistencia persistencia.guardar_draft_json({"a": 1}, "F002", "2", str(tmp_path / "salida")) assert any(n.startswith("draft_sapb1_F002-2") for n in os.listdir(tmp_path / "salida"))