""" Configuración centralizada de la aplicación usando Pydantic Settings """ from typing import List, Literal from pydantic import field_validator from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): """Configuración de la aplicación""" # Application APP_NAME: str = "Mi Aplicación" # Sobreescribible con APP_VERSION (Dockerfile/Jenkins: build-arg + ENV) o entorno en runtime APP_VERSION: str = "dev-local" DEBUG: bool = True ENVIRONMENT: str = "development" # Auth local para desarrollo (sin Keycloak/Hub) # Nunca activar en producción. DEV_LOCAL_AUTH: bool = False DEV_LOCAL_AUTH_EMAIL: str = "dev@local.test" DEV_LOCAL_AUTH_NAME: str = "Dev User" DEV_LOCAL_AUTH_TENANT_ID: int = 1 DEV_LOCAL_AUTH_COMPANY_ID: int = 1 # Database - Core (Shared) CORE_DB_HOST: str = "postgres" CORE_DB_PORT: int = 5432 CORE_DB_NAME: str = "app_core" CORE_DB_USER: str = "postgres" CORE_DB_PASSWORD: str = "postgres" # Security SECRET_KEY: str = "change-this-secret-key-in-production" ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 # Valkey / Redis VALKEY_URL: str = "redis://valkey:6379/0" PERMISSION_CACHE_ENABLED: bool = True PERMISSION_CACHE_TTL_SECONDS: int = 300 # Synchronization SYNC_SECRET_TOKEN: str = "change-this-sync-token-in-production" CENTRAL_SERVER_URL: str = "http://localhost:8000/api/v1/core/help-center/sync/" SPOKE_URLS: str = "" # Comma separated list of Spoke URLs for Broadcast (Hub only) # CORS CORS_ORIGINS: str = "http://localhost:5173,http://localhost:3000" # Hub de Aduanasoft — requerido siempre (SaaS y self-hosted) HUB_URL: str = "http://localhost:8001" # Base API del Hub/Workspace para endpoint /v1/auth/me (fuente de verdad de perfil) HUB_API_BASE_URL: str = "" HUB_PROFILE_SYNC_TIMEOUT_MS: int = 3000 # Cuenta de servicio Hub — usada para operaciones admin (ej. sync de nombre a Keycloak) HUB_ADMIN_EMAIL: str = "" HUB_ADMIN_PASSWORD: str = "" # URL pública del frontend — usada en links de email (invitaciones, etc.) APP_PUBLIC_URL: str = "http://localhost:3000" @field_validator("CENTRAL_SERVER_URL", "SPOKE_URLS", "HUB_URL", "HUB_API_BASE_URL", mode="before") @classmethod def strip_quotes(cls, v: str) -> str: if v and isinstance(v, str): v = v.strip().strip('"').strip("'") # Evitar que solo espacios en .env se conviertan en "/" (rompe httpx: falta protocolo). if not v: return "" if not v.endswith("/"): v += "/" return v return v # External APIs SITAR_API_URL: str = "api.sitar.aduanasoft.com:880" COVE_API_URL: str = "https://api.vu.aduanasoft.com" COVE_API_VERIFY_SSL: bool = False COVE_FIEL_HASH_KEY: str = "" COVE_FIEL_HASH_IV: str = "" SITAR_API_USER: str = "" SITAR_API_PASSWORD: str = "" # SMTP Email Configuration SMTP_HOST: str = "smtp.gmail.com" SMTP_PORT: int = 587 SMTP_USER: str = "" SMTP_PASSWORD: str = "" SMTP_FROM_NAME: str = "Mi Aplicación" SMTP_USE_TLS: bool = True # CSV imports (layouts_csv): redis = base64 en Valkey; minio = S3 + referencia en Redis CSV_IMPORT_STORAGE: Literal["redis", "minio"] = "minio" S3_ENDPOINT_URL: str = "http://minio:9000" S3_ACCESS_KEY: str = "" S3_SECRET_KEY: str = "" S3_BUCKET: str = "app" S3_REGION: str = "us-east-1" S3_USE_SSL: bool = False # Logos, certificados, help (si no quieres MinIO aquí, pon false Y CSV_IMPORT_STORAGE=redis) S3_FILE_STORAGE: bool = True S3_PRESIGNED_EXPIRES_SECONDS: int = 3600 model_config = SettingsConfigDict( env_file=[".env", "../.env"], case_sensitive=True, extra="ignore", env_file_encoding="utf-8", ) @property def core_database_url(self) -> str: """URL de conexión a la base de datos core""" return f"postgresql://{self.CORE_DB_USER}:{self.CORE_DB_PASSWORD}@{self.CORE_DB_HOST}:{self.CORE_DB_PORT}/{self.CORE_DB_NAME}" @property def async_core_database_url(self) -> str: """URL de conexión asíncrona a la base de datos core""" return f"postgresql+asyncpg://{self.CORE_DB_USER}:{self.CORE_DB_PASSWORD}@{self.CORE_DB_HOST}:{self.CORE_DB_PORT}/{self.CORE_DB_NAME}" @property def cors_origins_list(self) -> List[str]: """Lista de orígenes CORS permitidos""" return [origin.strip() for origin in self.CORS_ORIGINS.split(",")] @property def use_s3_object_storage(self) -> bool: """ Usar MinIO para logos, certificados y Help (mismo bucket que CSV). True si los imports CSV ya usan MinIO o si S3_FILE_STORAGE está activo. """ return self.CSV_IMPORT_STORAGE == "minio" or self.S3_FILE_STORAGE @property def hub_api_base_url(self) -> str: """ Base URL para endpoints /v1 del Workspace/Hub. Si HUB_API_BASE_URL no está definido, deriva de HUB_URL + /api. """ custom = (self.HUB_API_BASE_URL or "").strip().rstrip("/") if custom: return custom return f"{self.HUB_URL.rstrip('/')}/api" # Instancia global de configuración settings = Settings()