Migra el servicio a arquitectura Docker persistente y mueve credenciales HANA a .env
- Reemplaza config_loader/config_model/hana_client/logger_utilities/outlook_utilities por core.py (pipeline funcional), hana_manager.py, logger_manager.py, scheduler.py y descargador_sovos.py (Playwright/Firefox) según la plantilla de servicios. - main.py pasa a loop continuo dirigido por scheduler.json, con manejo de SIGINT/SIGTERM. - Agrega Dockerfile, docker-compose.yml (restart unless-stopped) y certs/ para despliegue. - Agrega smb_transfer.py para la transferencia a carpeta compartida. - Saca las credenciales de HANA de config.json y las mueve a .env (HANA_HOST/PORT/USER/PASSWORD).
This commit is contained in:
1 parent
1503d6a2ec
commit
277d07dfeb
22 files changed
+1081
-750
No files matched your search
@@ -0,0 +1,145 @@
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeout
|
||||
|
||||
from hana_manager import marcar_pdf_descargado, marcar_xml_descargado
|
||||
from logger_manager import get_logger
|
||||
|
||||
logger = get_logger("descargador")
|
||||
|
||||
# ============================================================================
|
||||
URL_SOVOS = "https://public-einvoicing.sovos.com/pe"
|
||||
|
||||
# ============================================================================
|
||||
def descargar_comprobante(page, ruc, tipo, serie, numero, correlativo, monto, fecha, download_dir):
|
||||
page.goto(URL_SOVOS, wait_until="domcontentloaded", timeout=30000)
|
||||
|
||||
try:
|
||||
page.locator('input[name="senderTaxId"]').wait_for(state="visible", timeout=30000)
|
||||
except Exception as e:
|
||||
try:
|
||||
snippet = (page.inner_text("body") or "")[:600].replace("\n", " ")
|
||||
except Exception:
|
||||
snippet = "<no pude leer el body>"
|
||||
logger.error(f" ❌ No se encontró el campo RUC. URL={page.url}")
|
||||
logger.error(f" 🔎 Contenido visible: {snippet}")
|
||||
raise
|
||||
|
||||
page.fill('input[name="senderTaxId"]', ruc)
|
||||
|
||||
page.click('#mui-component-select-invoiceTypeCode')
|
||||
page.wait_for_selector(f'li[role="option"][data-value="{tipo}"]', timeout=5000)
|
||||
page.click(f'li[role="option"][data-value="{tipo}"]')
|
||||
time.sleep(0.5)
|
||||
|
||||
page.fill('input[name="correlative"]', correlativo)
|
||||
|
||||
page.fill('input[name="payableAmount"]', str(monto))
|
||||
|
||||
date_input = page.locator('//input[contains(@placeholder, "Ej: 202")]')
|
||||
date_input.click()
|
||||
date_input.fill(fecha)
|
||||
time.sleep(0.5)
|
||||
|
||||
submit_btn = page.locator('//button[contains(text(), "Ver documento")]')
|
||||
if submit_btn.is_disabled():
|
||||
raise ValueError("Botón 'Ver documento' deshabilitado. Verifique los campos.")
|
||||
|
||||
submit_btn.click()
|
||||
|
||||
download_btn = page.locator("[data-testid='btn-download-document']")
|
||||
errorLocator = page.locator(".sovosSnackbar--error, :text('Document not found')")
|
||||
|
||||
try:
|
||||
download_btn.wait_for(state="visible", timeout=15000)
|
||||
except PlaywrightTimeout:
|
||||
if errorLocator.count() > 0 and errorLocator.first.is_visible():
|
||||
raise FileNotFoundError("El comprobante no existe en el portal de Sovos (Document not found).")
|
||||
raise TimeoutError("Tiempo de espera agotado sin encontrar el comprobante ni error.")
|
||||
|
||||
nombre_base = f"{ruc}-{tipo}-{serie}-{numero}"
|
||||
|
||||
with page.expect_download(timeout=30000) as download_info:
|
||||
download_btn.click()
|
||||
download_pdf = download_info.value
|
||||
pdf_path = Path(download_dir) / f"{nombre_base}.pdf"
|
||||
download_pdf.save_as(str(pdf_path))
|
||||
|
||||
page.click('button:has-text(".pdf"), button:has-text(".xml")')
|
||||
time.sleep(1)
|
||||
page.click('li[role="menuitem"]:has-text(".xml")')
|
||||
page.wait_for_selector('button:has-text(".xml")', timeout=5000)
|
||||
|
||||
with page.expect_download(timeout=30000) as download_info:
|
||||
download_btn.click()
|
||||
download_xml = download_info.value
|
||||
xml_path = Path(download_dir) / f"{nombre_base}.xml"
|
||||
download_xml.save_as(str(xml_path))
|
||||
|
||||
return [pdf_path.name, xml_path.name]
|
||||
|
||||
# ============================================================================
|
||||
def descargar_documentos_pendientes(docs, download_dir):
|
||||
Path(download_dir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
docs_descargar = [
|
||||
d for d in docs
|
||||
if (d.get("tienePDF") or "N").strip() == "N"
|
||||
or (d.get("tieneXML") or "N").strip() == "N"
|
||||
]
|
||||
|
||||
if not docs_descargar:
|
||||
logger.info(" ✅ No hay documentos para descargar")
|
||||
return {"exitosos": 0, "fallidos": 0, "archivos": []}
|
||||
|
||||
logger.info(f"\n📥 Descargando {len(docs_descargar)} comprobantes desde Sovos...")
|
||||
exitosos = 0
|
||||
fallidos = 0
|
||||
archivos_descargados = []
|
||||
|
||||
with sync_playwright() as p:
|
||||
browser = p.firefox.launch(headless=True)
|
||||
context = browser.new_context(accept_downloads=True, ignore_https_errors=True)
|
||||
page = context.new_page()
|
||||
|
||||
for idx, doc in enumerate(docs_descargar):
|
||||
ruc = (doc.get("LicTradNum") or "").strip()
|
||||
tipo = (doc.get("Indicator") or "").strip()
|
||||
serie = (doc.get("FolioPref") or "").strip()
|
||||
numero_raw = doc.get("FolioNum") or 0
|
||||
numero = str(int(numero_raw))
|
||||
monto = doc.get("DocTotal") or 0
|
||||
fecha = (doc.get("TaxDate") or "").strip()
|
||||
correlativo = f"{serie}-{numero}"
|
||||
schema = (doc.get("schema") or "").strip()
|
||||
docentry = doc.get("DocEntry")
|
||||
|
||||
logger.info(f" [{idx+1}/{len(docs_descargar)}] DocEntry={docentry} DocNum={doc.get('DocNum')} "
|
||||
f"{ruc}-{tipo}-{correlativo}")
|
||||
|
||||
try:
|
||||
archivos = descargar_comprobante(
|
||||
page, ruc, tipo, serie, numero, correlativo, monto, fecha, download_dir
|
||||
)
|
||||
exitosos += 1
|
||||
archivos_descargados.extend(archivos)
|
||||
logger.info(f" ✅ Descargado: PDF={archivos[0] if len(archivos) > 0 else ''} "
|
||||
f"| XML={archivos[1] if len(archivos) > 1 else ''}")
|
||||
|
||||
if schema and docentry is not None:
|
||||
if (doc.get("tienePDF") or "N").strip() == "N":
|
||||
marcar_pdf_descargado(schema, docentry, doc.get("urlPDF"), archivos[0] if len(archivos) > 0 else "")
|
||||
if (doc.get("tieneXML") or "N").strip() == "N":
|
||||
marcar_xml_descargado(schema, docentry, doc.get("urlXML"), archivos[1] if len(archivos) > 1 else "")
|
||||
|
||||
time.sleep(1.5)
|
||||
except Exception as e:
|
||||
fallidos += 1
|
||||
logger.error(f" ❌ Error: {e}")
|
||||
|
||||
browser.close()
|
||||
|
||||
logger.info(f"\n📊 Resultado: ✅ {exitosos} éxitos | ❌ {fallidos} fallidos")
|
||||
return {"exitosos": exitosos, "fallidos": fallidos, "archivos": archivos_descargados}
|
||||
# ============================================================================
|
||||
Reference in new issue
Block a user