""" Validación de la configuración (.env) al arrancar el proceso. Falla rápido con un mensaje claro si falta alguna variable obligatoria, en lugar de descubrirlo a mitad de un ciclo (por ejemplo, adjuntos descargados en la raíz del proyecto por un ``DOWNLOAD_FOLDER`` vacío, o consultas a HANA que devuelven 0 en silencio por credenciales ausentes). """ import os import sys import logging logger = logging.getLogger(__name__) # Variables obligatorias, agrupadas por servicio para el mensaje de error. OBLIGATORIAS = { "Pipeline": ["EMAIL", "DOWNLOAD_FOLDER", "OUTPUT_FOLDER"], "Microsoft Graph": ["CLIENT_ID", "CLIENT_SECRET", "TENANT_ID"], "SAP B1 Service Layer": ["SAP_B1_URL", "SAP_B1_COMPANY", "SAP_B1_USER", "SAP_B1_PASSWORD"], "SAP HANA": ["HANA_SERVER", "HANA_USER", "HANA_PASSWORD", "HANA_SCHEMA"], } # Modos de ``NOTIFICACION_MODO``: ver respuesta_proveedor.py MODOS_NOTIFICACION = ("prueba", "real") # Variable de verificación SSL → servicio al que aplica. Cada módulo solo # la activa con el valor "true" (cualquier otro valor, o ausente, la desactiva). VERIFICACION_SSL = { "VERIFY_SSL": "Microsoft Graph", "SAP_B1_VERIFY_SSL": "SAP B1 Service Layer", } def validar_configuracion() -> tuple[list[str], list[str]]: """ Revisa las variables de entorno ya cargadas (tras ``load_dotenv``). Returns: tuple[list[str], list[str]]: ``(errores, advertencias)``. Los errores impiden arrancar; las advertencias solo se registran. """ errores: list[str] = [] advertencias: list[str] = [] for servicio, variables in OBLIGATORIAS.items(): faltan = [v for v in variables if not os.getenv(v, "").strip()] if faltan: errores.append(f"{servicio}: faltan {', '.join(faltan)}") ruta_red = os.getenv("RUTA_RED_FACTURACION", "").strip() ruc_empresa = os.getenv("RUC_EMPRESA", "").strip() if ruta_red and not ruc_empresa: errores.append("Ruta de red: RUTA_RED_FACTURACION está definida pero falta RUC_EMPRESA") elif not ruta_red: advertencias.append( "RUTA_RED_FACTURACION no definida: no se copiarán PDF/XML a la red " "y U_VS_ANEXO_PDF/XML irán vacíos en el draft" ) modo = os.getenv("NOTIFICACION_MODO", "").strip().lower() if modo and modo not in MODOS_NOTIFICACION: advertencias.append(f"NOTIFICACION_MODO='{modo}' no es válido (usa {' o '.join(MODOS_NOTIFICACION)}); se usará prueba") elif modo == "real": advertencias.append("Modo de notificación REAL: las respuestas se envían a quien envía el correo") else: advertencias.append("Modo de notificación PRUEBA: las respuestas van al equipo, no a quien envía el correo") if not os.getenv("NOTIFICACION_EQUIPO", "").strip(): advertencias.append("NOTIFICACION_EQUIPO no definida: se usa la lista por defecto del equipo") if not os.getenv("LOGO_URL", "").strip(): advertencias.append("LOGO_URL no definida: los correos al proveedor saldrán sin logo") nivel = os.getenv("LOG_LEVEL", "").strip() if nivel and not isinstance(getattr(logging, nivel.upper(), None), int): advertencias.append(f"LOG_LEVEL='{nivel}' no es un nivel válido; se usará INFO") for variable, servicio in VERIFICACION_SSL.items(): if os.getenv(variable, "").strip().lower() != "true": advertencias.append(f"Verificación SSL desactivada para {servicio} ({variable} distinto de 'true')") return errores, advertencias def verificar_configuracion() -> None: """ Registra las advertencias y, si hay errores, los registra y termina el proceso con código 1. """ errores, advertencias = validar_configuracion() for a in advertencias: logger.warning(f"⚠️ Configuración: {a}") if errores: logger.error("❌ Configuración incompleta en .env:") for e in errores: logger.error(f" - {e}") sys.exit(1)