Files
descarga_factura_transporte/config_validator.py
T
GeorgeandClaude Sonnet 5.5 108d52da33 se responde al remitente con las plantillas de aceptado/rechazado
El sistema contesta a quien envió el correo con la plantilla que corresponda.
Por defecto (NOTIFICACION_MODO=prueba) la respuesta va solo al equipo con el
asunto "[PRUEBA → remitente]"; en modo real va al remitente con copia oculta
al equipo. El correo técnico (tablas de SAP, JSON) pasa a ir solo al equipo.

- respuesta_proveedor.py: contextos de las plantillas, envío y protecciones
  (remitente desconocido o sin dominio, propio buzón, no-reply, mensajes
  automáticos, respuestas a notificaciones, límite de 3 por remitente/24 h).
- observaciones.py: motivos de rechazo con título, filas y cómo corregir
  (borrador de textos); también para correos sin comprobante válido.
- Un error de SAP/HANA o un fallo inesperado no se le comunica al proveedor y
  ya no tumba el ciclo; "guía no encontrada" exige que HANA responda.
- outlook_utilities: es_mensaje_automatico y copia oculta en send_email.
- seguimiento: notificacion_destinatario (con migración) y
  contar_respuestas_correo.
- config_validator anuncia el modo activo; documentación y pruebas.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 08:55:54 -05:00

99 lines
3.9 KiB
Python

"""
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)