diff --git a/adjuntos.py b/adjuntos.py index 7f7527a..d1e8d50 100644 --- a/adjuntos.py +++ b/adjuntos.py @@ -1,6 +1,7 @@ """Descarga y filtrado de adjuntos de un correo.""" import os +import logging import outlook_utilities from pipeline_utils import log, ruta_carpeta @@ -33,7 +34,7 @@ def descargar_adjuntos(email: dict, email_addr: str, token: str, download_folder return adjuntos return None except Exception as ex: - log(f"❌ Error descargando adjuntos: {ex}") + log(f"❌ Error descargando adjuntos: {ex}", severity=logging.ERROR) return None diff --git a/docs/inventario.md b/docs/inventario.md index 84be56f..0833848 100644 --- a/docs/inventario.md +++ b/docs/inventario.md @@ -6,7 +6,7 @@ |---------|--------|-----| | `main.py` | 134 | Demonio orquestador (loop de horarios) | | `pipeline.py` | 230 | Orquestador del pipeline (autenticar, descargar correos, procesar un correo) | -| `pipeline_utils.py` | 15 | `log()` con indentación de árbol y `ruta_carpeta()` | +| `pipeline_utils.py` | 27 | `log()` (logging con indentación de árbol y `severity`) y `ruta_carpeta()` | | `adjuntos.py` | 81 | Descarga de adjuntos (Graph) y filtrado PDF/XML | | `documentos.py` | 371 | Extracción PDF/XML, asignación de guías, copia a red | | `persistencia.py` | 163 | Guardado de JSON de resultados y de drafts | diff --git a/documentos.py b/documentos.py index 6bba5fb..4753818 100644 --- a/documentos.py +++ b/documentos.py @@ -1,6 +1,7 @@ """Extracción de datos de PDF/XML y asignación de guías de remisión.""" import os +import logging import re import shutil import ntpath @@ -47,7 +48,7 @@ def copiar_a_red(data: dict) -> None: try: fecha = datetime.strptime(data.get("fecha_emision", ""), "%Y-%m-%d") except (ValueError, TypeError): - log(f"⚠️ No se pudo copiar a red: fecha_emision inválida ({data.get('fecha_emision')})", 3) + log(f"⚠️ No se pudo copiar a red: fecha_emision inválida ({data.get('fecha_emision')})", 3, severity=logging.WARNING) return carpeta_destino = os.path.join( @@ -81,7 +82,7 @@ def copiar_a_red(data: dict) -> None: log(f"📁 Copiado a red: {carpeta_destino}", 3) except OSError as e: - log(f"⚠️ Error copiando a ruta de red: {e}", 3) + log(f"⚠️ Error copiando a ruta de red: {e}", 3, severity=logging.WARNING) # ============================================================================ @@ -133,7 +134,7 @@ def procesar_pdfs(email: dict, download_folder: str) -> dict: pdf_cache[canonical] = data log(f"📄 PDF procesado: {canonical} ({data.get('tipo_documento', '?')})", 3) else: - log(f"⚠️ PDF no procesado: {adjunto} - {data.get('error', 'error desconocido')}", 2) + log(f"⚠️ PDF no procesado: {adjunto} - {data.get('error', 'error desconocido')}", 2, severity=logging.WARNING) if datos_pdf: email["datos_pdf"] = datos_pdf @@ -365,7 +366,7 @@ def validar_guias(documentos: list[dict]) -> None: for i, linea in enumerate(doc.get("lineas", [])): for j, g in enumerate(linea.get("guias_remision") or []): if not isinstance(g, dict) or not all(k in g and g[k] for k in ("tipo", "serie", "numero")): - log(f"⚠️ guias_remision[{j}] inválida en {doc.get('serie','?')}-{doc.get('numero','?')} línea {i}: {g}", 2) + log(f"⚠️ guias_remision[{j}] inválida en {doc.get('serie','?')}-{doc.get('numero','?')} línea {i}: {g}", 2, severity=logging.WARNING) gt = linea.get("guia_transportista") if gt and (not isinstance(gt, dict) or not all(k in gt and gt[k] for k in ("tipo", "serie", "numero"))): - log(f"⚠️ guia_transportista inválida en {doc.get('serie','?')}-{doc.get('numero','?')} línea {i}: {gt}", 2) + log(f"⚠️ guia_transportista inválida en {doc.get('serie','?')}-{doc.get('numero','?')} línea {i}: {gt}", 2, severity=logging.WARNING) diff --git a/draft_sap.py b/draft_sap.py index 44c4e88..0ca6f8b 100644 --- a/draft_sap.py +++ b/draft_sap.py @@ -1,6 +1,7 @@ """Validación de precios contra SAP y construcción del draft SAP B1.""" import re +import logging import math from datetime import datetime, timedelta @@ -340,7 +341,7 @@ def validate_prices(factura: dict) -> tuple[bool, list[dict]]: guias_lt.append({"guia": guia, "line_total": suma_line_total, "warehouse_code": ", ".join(sorted(wh_set)) if wh_set else "A-CENTRA", "quantity": max_qty, "price_after_vat_sum": sum_pav, "zona":zona, "weight1": weight1, "draft_quantity": 0, "draft_price": 0.0}) all_wh.update(wh_set) except Exception as ex: - log(f"⚠️ Guía {codigo_guia}: error al consultar SAP - {ex}", 2) + log(f"⚠️ Guía {codigo_guia}: error al consultar SAP - {ex}", 2, severity=logging.WARNING) guias_lt.append({"guia": guia, "line_total": 0.0, "warehouse_code": "A-CENTRA", "weight1": 0.0, "quantity": 0.0, "price_after_vat_sum": 0.0, "zona": "", "draft_quantity": 0, "draft_price": 0.0}) all_ok = False @@ -376,7 +377,7 @@ def validate_prices(factura: dict) -> tuple[bool, list[dict]]: if ok: log(f"✅ Coincide: {total_linea:.6f} ≈ {sap_total:.6f}", 3) else: - log(f"❌ NO coincide — XML: {total_linea:.6f} vs SAP: {sap_total:.6f}", 3) + log(f"❌ NO coincide — XML: {total_linea:.6f} vs SAP: {sap_total:.6f}", 3, severity=logging.WARNING) todas_coinciden = all(r["ok"] for r in resultados) return todas_coinciden, resultados diff --git a/email_utilities.py b/email_utilities.py index b35ef2d..f8e4eac 100644 --- a/email_utilities.py +++ b/email_utilities.py @@ -1,6 +1,9 @@ +import logging import os import zipfile +logger = logging.getLogger(__name__) + # ============================================================================ def descomprimir_adjuntos(emails: list[dict], ruta: str) -> list[dict]: """ @@ -16,7 +19,7 @@ def descomprimir_adjuntos(emails: list[dict], ruta: str) -> list[dict]: continue ruta_zip = os.path.join(ruta, adjunto) if not os.path.exists(ruta_zip): - print(f"⚠️ No existe: {ruta_zip}") + logger.warning(f"⚠️ No existe: {ruta_zip}") continue try: with zipfile.ZipFile(ruta_zip, "r") as zip_ref: @@ -27,16 +30,16 @@ def descomprimir_adjuntos(emails: list[dict], ruta: str) -> list[dict]: ] zip_ref.extractall(ruta) nuevos_adjuntos.extend(nombres_extraidos) - print( + logger.info( f"📦 {adjunto}: {len(nombres_extraidos)} archivos extraídos" ) except Exception as e: - print(f"⚠️ Error descomprimiendo {adjunto}: {e}") + logger.warning(f"⚠️ Error descomprimiendo {adjunto}: {e}") email["adjuntos"].extend(nuevos_adjuntos) return emails except Exception as ex: - print(f"❌ Error general: {ex}") + logger.error(f"❌ Error general: {ex}") return emails # ============================================================================ diff --git a/hana_manager.py b/hana_manager.py index f3cd8a6..54b5f24 100644 --- a/hana_manager.py +++ b/hana_manager.py @@ -1,9 +1,12 @@ #!/usr/bin/env python3 +import logging import os from datetime import datetime, date from hdbcli import dbapi from dotenv import load_dotenv +logger = logging.getLogger(__name__) + # ============================================================================ # CONFIGURACIÓN # ============================================================================ @@ -31,7 +34,7 @@ def get_connection(): ) return conn except Exception as e: - print(f" ❌ Error conectando a HANA: {e}") + logger.error(f" ❌ Error conectando a HANA: {e}") return None # ============================================================================ @@ -89,7 +92,7 @@ def _query_delivery_table(connection, tabla: str, tipo: str, serie: str, numero: results.append(registro) return results except Exception as e: - print(f" ❌ Error en consulta HANA get_delivery ({tabla}): {e}") + logger.error(f" ❌ Error en consulta HANA get_delivery ({tabla}): {e}") return [] finally: if cursor: @@ -127,7 +130,7 @@ def obtener_valor_referencial(serie: str, numero: str) -> float: valor = dict(zip(columns, row)).get("ValorReferencial") return float(valor) if valor is not None else 0.0 except Exception as e: - print(f" ❌ Error en consulta HANA obtener_valor_referencial: {e}") + logger.error(f" ❌ Error en consulta HANA obtener_valor_referencial: {e}") return 0.0 finally: if cursor: diff --git a/main.py b/main.py index 7a30fb2..a1d3e41 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,4 @@ +import logging import os import time import argparse @@ -7,6 +8,8 @@ from dotenv import load_dotenv import logger_manager from pipeline import run_pipeline +logger = logging.getLogger(__name__) + # ============================================================================ HORARIOS = [ @@ -72,8 +75,8 @@ def main() -> None: DOWNLOAD_FOLDER = os.getenv("DOWNLOAD_FOLDER", "") OUTPUT_FOLDER = os.getenv("OUTPUT_FOLDER", "") - print("🚀 Demonio iniciado") - print("=" * 60) + logger.info("🚀 Demonio iniciado") + logger.info("=" * 60) try: while True: @@ -86,8 +89,8 @@ def main() -> None: intervalo, modo = resultado - print( - f"\n[{inicio_ciclo:%Y-%m-%d %H:%M:%S}] " + logger.info( + f"[{inicio_ciclo:%Y-%m-%d %H:%M:%S}] " f"Iniciando ciclo [{modo}] (cada {intervalo}s)" ) @@ -96,14 +99,14 @@ def main() -> None: duracion = (datetime.now() - inicio_ciclo).total_seconds() if duracion > intervalo: - print( + logger.warning( f"⚠️ El ciclo demoró {duracion:.1f}s " f"(intervalo configurado {intervalo}s)" ) dormir = max(0, intervalo - duracion) - print(f"💤 Próximo ciclo [{modo}] en {dormir:.0f}s...") + logger.info(f"💤 Próximo ciclo [{modo}] en {dormir:.0f}s...") else: @@ -111,25 +114,22 @@ def main() -> None: despertar = inicio_ciclo + timedelta(seconds=dormir) - print( - f"\n[{inicio_ciclo:%Y-%m-%d %H:%M:%S}] " - f"Fuera del horario.\n" - f"Próxima ejecución: {despertar:%Y-%m-%d %H:%M:%S}" - ) + logger.info(f"[{inicio_ciclo:%Y-%m-%d %H:%M:%S}] Fuera del horario.") + logger.info(f"Próxima ejecución: {despertar:%Y-%m-%d %H:%M:%S}") time.sleep(dormir) except KeyboardInterrupt: - print("\n\n🛑 Demonio detenido por el usuario.") + logger.info("🛑 Demonio detenido por el usuario.") # ============================================================================ if __name__ == "__main__": logger_manager.setup_logging() - print("=" * 60) - print(f"🚀 Demonio iniciado: {datetime.now():%Y-%m-%d %H:%M:%S}") - print(f"📄 Log guardado en: {logger_manager.LOG_FILE}") - print("=" * 60) + logger.info("=" * 60) + logger.info(f"🚀 Demonio iniciado: {datetime.now():%Y-%m-%d %H:%M:%S}") + logger.info(f"📄 Log guardado en: {logger_manager.LOG_FILE}") + logger.info("=" * 60) main() diff --git a/outlook_utilities.py b/outlook_utilities.py index a00977f..ba21007 100644 --- a/outlook_utilities.py +++ b/outlook_utilities.py @@ -1,3 +1,4 @@ +import logging import msal import requests import warnings @@ -5,6 +6,8 @@ import base64 import os from datetime import datetime, timedelta from dotenv import load_dotenv + +logger = logging.getLogger(__name__) warnings.filterwarnings("ignore") # ============================================================================ @@ -214,7 +217,7 @@ def send_email(access_token: str, from_email: str, to_recipients: list[str], sub "contentBytes": encoded_content }) else: - print(f"Advertencia: El archivo no existe y no se adjuntará: {path}") + logger.warning(f"Advertencia: El archivo no existe y no se adjuntará: {path}") # Estructura del mensaje para la API email_data = { @@ -234,13 +237,13 @@ def send_email(access_token: str, from_email: str, to_recipients: list[str], sub try: response = requests.post(url, headers=headers, json=email_data, verify=verify_ssl) if response.status_code == 202: - print(f" ├── SUCCESS: Correo enviado exitosamente a: {', '.join(to_recipients)}") + logger.info(f" ├── SUCCESS: Correo enviado exitosamente a: {', '.join(to_recipients)}") return True else: - print(f" ├── ERROR: Al enviar correo: {response.status_code} - {response.text}") + logger.error(f" ├── ERROR: Al enviar correo: {response.status_code} - {response.text}") return False except Exception as e: - print(f" ├── ERROR: Excepcion al enviar correo: {e}") + logger.error(f" ├── ERROR: Excepcion al enviar correo: {e}") return False # ============================================================================ diff --git a/pipeline.py b/pipeline.py index 0a91b3a..a83fda0 100644 --- a/pipeline.py +++ b/pipeline.py @@ -8,6 +8,7 @@ Cada correo se procesa individualmente en un bucle: → crear draft en SAP (si válido) → notificar → guardar draft JSON """ +import logging import os from datetime import datetime, timedelta @@ -22,21 +23,23 @@ import notificaciones import sapb1_manager from pipeline_utils import log +logger = logging.getLogger(__name__) + # ============================================================================ # PASO 1: Autenticación Microsoft Graph # ============================================================================ def step_authenticate() -> str | None: try: - print("Intentando obtener token de acceso...") + logger.info("Intentando obtener token de acceso...") token = outlook_utilities.get_access_token() if token: - print("✅ Token obtenido exitosamente.") - print(f"Token prefix: {token[:25]}...") + logger.info("✅ Token obtenido exitosamente.") + logger.info(f"Token prefix: {token[:25]}...") return token return None except Exception as e: - print(f"❌ Error: {e}") + logger.error(f"❌ Error: {e}") return None @@ -44,14 +47,14 @@ def step_authenticate() -> str | None: # PASO 2: Descarga de correos # ============================================================================ def step_fetch_emails(token: str, email: str, fecha_inicio: str, fecha_fin: str) -> list[dict]: - print("\n" + "=" * 60) - print(f"[MAIL] {email}") + logger.info("=" * 60) + logger.info(f"[MAIL] {email}") try: emails = outlook_utilities.get_emails(email, token, fecha_inicio, fecha_fin) - print(f"Se encontraron {len(emails)} correos.") + logger.info(f"Se encontraron {len(emails)} correos.") return emails except Exception as e: - print(f"❌ Error: {e}") + logger.error(f"❌ Error: {e}") return [] @@ -95,7 +98,7 @@ def _procesar_un_correo(email: dict, token: str, email_addr: str, download_folde # FLUJO DEL CORREO # ======================================================================== - print(f"\n📬 [{email['fecha']}] {email['subject'][:80]}...") + logger.info(f"📬 [{email['fecha']}] {email['subject'][:80]}...") log("📂 Adjuntos:") # 1-2. BD: verificar existencia y guardar @@ -178,7 +181,7 @@ def _procesar_un_correo(email: dict, token: str, email_addr: str, download_folde log(f"✅ Draft SAP B1 creado: {serie}-{numero} → DocEntry {doc_entry}") html_body = notificaciones.build_notification_body("created", factura, validation_table, valor_referencial_table, doc_entry=doc_entry) except Exception as ex: - log(f"❌ Error creando draft {serie}-{numero}: {ex}") + log(f"❌ Error creando draft {serie}-{numero}: {ex}", severity=logging.ERROR) html_body = notificaciones.build_notification_body("error", factura, validation_table, valor_referencial_table, error=ex) else: log(f"⏭️ Draft NO creado: {serie}-{numero} — validación falló") @@ -196,7 +199,7 @@ def _procesar_un_correo(email: dict, token: str, email_addr: str, download_folde cc_recipients=["jlopez@aberio.com.pe", "gcalle@pucp.pe"], ) else: - log("⚠️ No se pudo determinar el remitente, notificación no enviada") + log("⚠️ No se pudo determinar el remitente, notificación no enviada", severity=logging.WARNING) # ============================================================================ @@ -211,18 +214,18 @@ def run_pipeline(dias: int, EMAIL: str, DOWNLOAD_FOLDER: str, OUTPUT_FOLDER: str ayer = hoy - timedelta(days=dias) fecha_inicio = ayer.strftime("%Y-%m-%d") fecha_fin = hoy.strftime("%Y-%m-%d") - print(f"Rango de búsqueda: {fecha_inicio} al {fecha_fin}") + logger.info(f"Rango de búsqueda: {fecha_inicio} al {fecha_fin}") # 1. Autenticar token = step_authenticate() if not token: - print("❌ No se pudo autenticar. Abortando.") + logger.error("❌ No se pudo autenticar. Abortando.") return # 2. Descargar correos emails = step_fetch_emails(token, EMAIL, fecha_inicio, fecha_fin) if not emails: - print("No se encontraron correos.") + logger.info("No se encontraron correos.") return # 3. Bucle por cada correo diff --git a/pipeline_utils.py b/pipeline_utils.py index e53fb5b..50cea01 100644 --- a/pipeline_utils.py +++ b/pipeline_utils.py @@ -1,6 +1,9 @@ """Utilidades compartidas por los módulos del pipeline.""" import os +import logging + +logger = logging.getLogger("pipeline") def ruta_carpeta(subcarpeta: str) -> str: @@ -8,8 +11,17 @@ def ruta_carpeta(subcarpeta: str) -> str: return os.path.join(os.path.dirname(__file__), subcarpeta) -def log(msg: str, level: int = 1, last: bool = False) -> None: - """Imprime ``msg`` con la indentación de árbol usada en el log del pipeline.""" +def log(msg: str, level: int = 1, last: bool = False, severity: int = logging.INFO) -> None: + """ + Registra ``msg`` con la indentación de árbol usada en el log del pipeline. + + Args: + msg: Mensaje a registrar. + level: Nivel de indentación del árbol (1-4). + last: True si es el último hijo de su rama (``└──`` en vez de ``├──``). + severity: Nivel de ``logging`` (``logging.WARNING``, ``logging.ERROR``…). + Por defecto ``logging.INFO``. + """ branch = " └── " if last else " ├── " pfx = {1: branch, 2: f" │ {branch}", 3: f" │ │ {branch}", 4: f" │ │ │ {branch}"}.get(level, branch) - print(f"{pfx}{msg}") + logger.log(severity, f"{pfx}{msg}") diff --git a/sapb1_manager.py b/sapb1_manager.py index 10a4a83..f234d3b 100644 --- a/sapb1_manager.py +++ b/sapb1_manager.py @@ -1,4 +1,5 @@ +import logging import requests import urllib3 import os @@ -7,6 +8,8 @@ from typing import Optional, Dict, Any from dotenv import load_dotenv import hana_manager +logger = logging.getLogger(__name__) + # ============================================================================ # CONFIGURACIÓN @@ -37,7 +40,7 @@ def login() -> dict: "UserName": SAPB1_USER, "Password": SAPB1_PASSWORD, } - print("🔐 Iniciando sesión en SAP B1 Service Layer...") + logger.info("🔐 Iniciando sesión en SAP B1 Service Layer...") resp = requests.post( f"{SAPB1_URL}/Login", json=payload, @@ -55,7 +58,7 @@ def login() -> dict: headers = {"Cookie": f"B1SESSION={b1session}; ROUTEID={routeid}"} _sap_session.update({"headers": headers, "b1session": b1session, "routeid": routeid}) - print(f"✅ Sesión SAP B1 establecida (B1SESSION: {b1session[:10]}...)") + logger.info(f"✅ Sesión SAP B1 establecida (B1SESSION: {b1session[:10]}...)") return headers @@ -64,9 +67,9 @@ def login() -> dict: # ============================================================================ def logout() -> None: if not _sap_session["b1session"]: - print("⚠️ No hay sesión activa para cerrar.") + logger.warning("⚠️ No hay sesión activa para cerrar.") return - print("🔓 Cerrando sesión SAP B1...") + logger.info("🔓 Cerrando sesión SAP B1...") try: resp = requests.post( f"{SAPB1_URL}/Logout", @@ -75,11 +78,11 @@ def logout() -> None: verify=SAPB1_VERIFY_SSL, ) if resp.status_code == 200: - print("✅ Sesión SAP B1 cerrada correctamente.") + logger.info("✅ Sesión SAP B1 cerrada correctamente.") else: - print(f"⚠️ Logout respondió HTTP {resp.status_code}") + logger.warning(f"⚠️ Logout respondió HTTP {resp.status_code}") except Exception as exc: - print(f"❌ Error al cerrar sesión: {exc}") + logger.error(f"❌ Error al cerrar sesión: {exc}") finally: _sap_session.update({"headers": None, "b1session": None, "routeid": None}) @@ -102,9 +105,9 @@ def _log_response(method: str, endpoint: str, response: requests.Response, paylo f" ({len(response.content)} bytes)" ) if response.status_code >= 400: - print(f"⚠️ {log_line} - {response.text[:300]}") + logger.warning(f"⚠️ {log_line} - {response.text[:300]}") else: - print(log_line) + logger.info(log_line) # ============================================================================ @@ -133,7 +136,7 @@ def forward_to_sapb1( if extra_headers: req_headers.update(extra_headers) - print(f" ├── 🔄 [{method}] {endpoint_path}") + logger.info(f" ├── 🔄 [{method}] {endpoint_path}") try: resp = requests.request( @@ -153,7 +156,7 @@ def forward_to_sapb1( raise ConnectionError(f"Error en solicitud HTTP: {exc}") if resp.status_code == 401: - print("⚠️ Sesión expirada (401). Reautenticando e intentando una vez...") + logger.warning("⚠️ Sesión expirada (401). Reautenticando e intentando una vez...") req_headers["Cookie"] = login()["Cookie"] resp = requests.request( method=method, @@ -191,7 +194,7 @@ def forward_to_sapb1( def create_draft(payload: dict) -> dict: resp = forward_to_sapb1("POST", "Drafts", json_data=payload) result = resp.json() - print(f" ├── ✅ Draft creado DocEntry={result.get('DocEntry')}") + logger.info(f" ├── ✅ Draft creado DocEntry={result.get('DocEntry')}") return result @@ -201,7 +204,7 @@ def create_draft(payload: dict) -> dict: def get_purchase_invoice(doc_entry: int) -> dict: resp = forward_to_sapb1("GET", f"PurchaseInvoices({doc_entry})") result = resp.json() - print(f" ├── ✅ PurchaseInvoices({doc_entry}) obtenida") + logger.info(f" ├── ✅ PurchaseInvoices({doc_entry}) obtenida") return result # ============================================================================ @@ -249,7 +252,7 @@ def get_delivery_notes(tipo: str, serie: str, numero: str) -> list: entries = _get_delivery_entries(tipo, serie, numero) if not entries: - print(f" │ ⚠️ Guía {codigo}: sin registros en SAP (SL + HANA)") + logger.warning(f" │ ⚠️ Guía {codigo}: sin registros en SAP (SL + HANA)") return [] # ── Fase 2: Obtener documento completo por cada DocEntry ───────────── @@ -260,20 +263,20 @@ def get_delivery_notes(tipo: str, serie: str, numero: str) -> list: try: resp = forward_to_sapb1("GET", f"{entidad}({doc_entry})") doc = resp.json() - print(f" │ └─ {entidad}({doc_entry}) obtenido") + logger.info(f" │ └─ {entidad}({doc_entry}) obtenido") if entidad == "StockTransfers": num_ov = doc.get("U_IDC_NUMOV") if not num_ov: - print(f" │ ⚠️ StockTransfers({doc_entry}) sin U_IDC_NUMOV: no se puede validar precios") + logger.warning(f" │ ⚠️ StockTransfers({doc_entry}) sin U_IDC_NUMOV: no se puede validar precios") continue resp_ov = forward_to_sapb1("GET", f"Orders({num_ov})") doc = resp_ov.json() - print(f" │ └─ Orders({num_ov}) obtenido (Orden de Venta de StockTransfers({doc_entry}))") + logger.info(f" │ └─ Orders({num_ov}) obtenido (Orden de Venta de StockTransfers({doc_entry}))") docs.append(doc) except Exception as e: - print(f" │ ⚠️ Error obteniendo {entidad}({doc_entry}): {e}") + logger.warning(f" │ ⚠️ Error obteniendo {entidad}({doc_entry}): {e}") return docs @@ -302,13 +305,13 @@ def _get_delivery_entries(tipo: str, serie: str, numero: str) -> list: if sl_entries: for sl_entry in sl_entries: sl_entry["Entidad"] = "DeliveryNotes" - print(f" │ 🔍 Guía {codigo}: SL → {len(sl_entries)} DocEntry encontrados") + logger.info(f" │ 🔍 Guía {codigo}: SL → {len(sl_entries)} DocEntry encontrados") return sl_entries - print(f" │ 🔍 Guía {codigo}: SL → 0 resultados. Consultando HANA...") + logger.info(f" │ 🔍 Guía {codigo}: SL → 0 resultados. Consultando HANA...") except Exception as e: - print(f" │ ⚠️ Guía {codigo}: SL falló ({e}). Consultando HANA...") + logger.warning(f" │ ⚠️ Guía {codigo}: SL falló ({e}). Consultando HANA...") # ── Fallback a HANA ────────────────────────────────────────────────── try: @@ -318,11 +321,11 @@ def _get_delivery_entries(tipo: str, serie: str, numero: str) -> list: hana_entry["Entidad"] = _TABLA_A_ENTIDAD_SL.get( hana_entry.get("Tabla"), "DeliveryNotes" ) - print(f" │ 🔍 Guía {codigo}: HANA → {len(hana_entries)} DocEntry encontrados") + logger.info(f" │ 🔍 Guía {codigo}: HANA → {len(hana_entries)} DocEntry encontrados") return hana_entries - print(f" │ 🔍 Guía {codigo}: HANA → 0 registros") + logger.info(f" │ 🔍 Guía {codigo}: HANA → 0 registros") except Exception as e: - print(f" │ ⚠️ Guía {codigo}: HANA falló ({e})") + logger.warning(f" │ ⚠️ Guía {codigo}: HANA falló ({e})") return [] @@ -361,7 +364,7 @@ def obtener_condicion_pago(card_code: str) -> dict | None: "NumberOfAdditionalDays": terms.get("NumberOfAdditionalDays") or 0, } except Exception as e: - print(f" │ ⚠️ Error obteniendo condición de pago de {card_code}: {e}") + logger.warning(f" │ ⚠️ Error obteniendo condición de pago de {card_code}: {e}") return None # ============================================================================ @@ -394,5 +397,5 @@ def get_zonas() -> list: ) result = resp.json() value = result.get("value", []) - print(f" ├── ✅ U_IDC_ZONAS obtenida ({len(value)} registros)") + logger.info(f" ├── ✅ U_IDC_ZONAS obtenida ({len(value)} registros)") return value