Skip to content

Configuracion y Deployment - Portal de Clientes

DOCUMENTACION RETROSPECTIVA - Generada a partir de codigo implementado el 2026-04-27.

Modulo: Portal de Clientes Tipo: Process Estado: Implementado Fecha: 2026-04-27 Repo frontend: portal-usuarios/Repo backend: bautista-backend/


Resumen del modelo de deployment

  • Frontend: una unica imagen Docker (portal-usuarios) que se build-ea N veces (una por tenant) inyectando variables VITE_* como build args. Cada tenant obtiene su contenedor con su URL de backend, su tenant_id, su sucursal_id y su branding.
  • Backend: instancia compartida de bautista-backend, configurada con PORTAL_ALLOWED_ORIGIN para autorizar CORS desde el portal.
  • Resolucion multi-tenant: NO hay resolucion DNS por dominio. El frontend declara su tenant_id y sucursal_id en build time; el backend resuelve la conexion via ini.sistema.

Variables de entorno - Frontend (portal-usuarios)

Definidas en portal-usuarios/.env.example. Todas son leidas en build time por Vite (import.meta.env.VITE_*) y quedan inlineadas en el bundle estatico.

VariableRequeridaDescripcionEjemplo
VITE_BACKEND_URLsiURL base de la API backend, sin trailing slash. Usada como baseURL de axios y para CORS.https://api.tenant.com
VITE_TENANT_IDsiID numerico del tenant (empresa). Se manda en cada request via header X-Tenant-Id.1
VITE_SUCURSAL_IDsiID numerico de la sucursal. Se manda via header X-Sucursal-Id y se usa como sucursalId por defecto en login.1
VITE_APP_NAMEnoNombre de la app para branding (titulo del documento, header).Portal de Clientes
VITE_LOGO_URLnoURL absoluta al logo del tenant.https://cdn.tenant.com/logo.png
VITE_PRIMARY_COLORnoColor primario CSS (hex o hsl). Inyectado como --primary en :root.#1e40af
VITE_SECONDARY_COLORnoColor secundario CSS. Inyectado como --secondary en :root.#3b82f6
VITE_THEME_COLORnoColor de la meta tag theme-color para PWA / barra de status mobile.#1e40af

Notas:

  • Validacion de variables obligatorias: el Dockerfile falla el build si VITE_BACKEND_URL, VITE_TENANT_ID o VITE_SUCURSAL_ID no estan definidas.
  • Variables opcionales tienen defaults definidos en BrandingContext.tsx (Portal de Clientes, #1e40af, #3b82f6).
  • Como Vite inlinea las variables en build time, cada tenant requiere un build distinto. No es posible cambiar VITE_BACKEND_URL en runtime.

Variables de entorno - Backend (bautista-backend)

Solo las variables del backend que afectan al portal. Definidas en bautista-backend/.env.dist.

VariableRequeridaDescripcionEjemplo
PORTAL_ALLOWED_ORIGINsi (cuando portal esta activo)Origen autorizado para CORS desde el portal de clientes. Debe coincidir EXACTO con el origen del frontend (sin barra al final). Usado por PortalCorsMiddleware.https://portal.tenant.com

Constantes relacionadas en constants.dist.php:

ConstanteUso
ALLOWED_ORIGINSOrigenes permitidos para el ERP principal. NO se usa para el portal -- el portal tiene su propio middleware con PORTAL_ALLOWED_ORIGIN.
HOST, PORT, USER, PASSWORD, DB_INIConexion a la DB principal (ini.sistema) que resuelve la conexion por tenant.

Notas:

  • PORTAL_ALLOWED_ORIGIN admite un solo origen. Para soportar multiples tenants en un mismo backend, el valor debe ser el origen de cada portal por separado o el middleware debe reescribirse para aceptar una lista. Pendiente de validacion con stakeholders si el modelo soporta backend compartido entre multiples portales con dominios distintos.
  • El backend usa JWT con claves RSA. Las claves estan fuera del scope del portal (son globales del backend). Ver bautista-backend/CLAUDE.md para private-key.pem / public-key.pem.

data_config - Configuracion en base de datos

Nota: la documentacion existente en index.md del modulo menciona que el backend resuelve tenant_id -> DB via ini.sistema. El operador del ERP configura los gateways de pago y otros parametros del portal desde las pantallas de configuracion del ERP.

La estructura concreta de las claves portal.* en la tabla de configuracion (gateway, caja_id, etc.) no fue verificada en el codigo durante esta documentacion retrospectiva. Para evitar inventar contratos, se deja documentada la existencia del mecanismo y se marca como pendiente.

Pendiente de validacion:

  • Listado completo de claves portal.* en data_config (tipos, valores admitidos, defaults).
  • Mecanismo exacto por el cual el backend resuelve tenant_id desde ini.sistema.
  • Referencias cruzadas a las pantallas de configuracion del ERP donde se setean estas claves.

Una vez validado, este apartado deberia documentarse en una doc tecnica backend dedicada al subsistema de configuracion del portal.


Docker - Frontend

Construccion de la imagen

Definida en portal-usuarios/Dockerfile. Build multi-stage:

StageBaseProposito
buildernode:22-alpinenpm ci, copiar fuentes, validar build args, ejecutar npm run build (genera /app/dist).
runnernginx:alpineCopia /app/dist a /usr/share/nginx/html, copia nginx.conf, expone puerto 80.

Build args declarados (todos VITE_*): VITE_BACKEND_URL, VITE_TENANT_ID, VITE_SUCURSAL_ID, VITE_APP_NAME, VITE_LOGO_URL, VITE_PRIMARY_COLOR, VITE_SECONDARY_COLOR, VITE_THEME_COLOR.

Validaciones de build (fallan el build si falta el arg):

  • VITE_BACKEND_URL
  • VITE_TENANT_ID
  • VITE_SUCURSAL_ID

SPA routing en nginx

Definido en portal-usuarios/nginx.conf:

ReglaComportamiento
location / con try_files $uri $uri/ /index.htmlFallback a index.html para rutas SPA. Permite que TanStack Router maneje rutas como /dashboard, /deudas, etc. sin que nginx devuelva 404.
location ~* \.(js|css|woff2|png|svg|ico|jpg|webp)$Cache-Control: public, immutable; expires 1y. Los assets de Vite tienen hash en el nombre, por eso se pueden cachear inmutablemente.
location = /index.htmlCache-Control: no-cache, no-store, must-revalidate; Pragma: no-cache; Expires: 0. El entry point NUNCA se cachea, asi cada deploy entra inmediatamente.

Bundles definidos como manualChunks en vite.config.ts (separan vendor y features para cache eficiente):

  • vendor-react, vendor-query, vendor-router
  • feature-auth, feature-deudas, feature-pagos, feature-cupones

Docker - Deployment por tenant

Modelo

Una sola imagen base, N contenedores (uno por tenant). Cada contenedor se construye con sus propios build args.

TenantBuild args distintosResultado
Tenant AVITE_BACKEND_URL=https://api.bautista.com, VITE_TENANT_ID=1, VITE_SUCURSAL_ID=1, branding AImagen portal-tenant-a:latest
Tenant BVITE_BACKEND_URL=https://api.bautista.com, VITE_TENANT_ID=2, VITE_SUCURSAL_ID=3, branding BImagen portal-tenant-b:latest

Implicancias

  • Cada tenant requiere su propio build (los VITE_* son inlineados por Vite).
  • Una sola imagen no puede servir multiples tenants -- la VITE_BACKEND_URL, VITE_TENANT_ID y VITE_SUCURSAL_ID quedan fijas en build.
  • El backend (bautista-backend) se comparte. Cada portal apunta al mismo backend con sus headers X-Tenant-Id / X-Sucursal-Id.

Resolucion multi-tenant en el flujo de request

Documentada en index.md del modulo. Resumen:

  1. El frontend manda X-Tenant-Id, X-Sucursal-Id y (cuando esta autenticado) Authorization: Bearer {access_token}.
  2. El backend resuelve tenant_id -> base de datos via ini.sistema.
  3. El backend resuelve sucursal_id -> schema PostgreSQL (suc0001, suc0002, etc.).
  4. El JWT lleva tenant_id y sucursal_id -- el backend valida que coincidan con los headers (proteccion contra tampering).

Compose por tenant

Pendiente de validacion: el repo portal-usuarios no tiene docker-compose.yml ni docker-compose.dev.yml en el momento de esta documentacion retrospectiva. La orquestacion de N contenedores (uno por tenant) probablemente se hace fuera del repo (en un orquestador de infra superior). Validar con DevOps como se gestiona el deploy de tenants nuevos.


Setup local de desarrollo

Pasos minimos para levantar el portal localmente, derivados de package.json y los archivos de config:

PasoComando / accionResultado
1Clonar portal-usuarios y entrar al directorio--
2Copiar .env.example -> .env.local y completar al menos VITE_BACKEND_URL, VITE_TENANT_ID, VITE_SUCURSAL_IDVariables Vite disponibles
3npm ciInstala dependencias declaradas en package-lock.json
4Levantar backend en paralelo (bautista-backend) con PORTAL_ALLOWED_ORIGIN=http://localhost:5173 (puerto default de Vite)CORS habilitado para el frontend local
5npm run devVite dev server en http://localhost:5173

Otros scripts utiles definidos en package.json:

ScriptComandoUso
buildtsc -b && vite buildBuild de produccion
previewvite previewServir el build localmente
testvitest run --coverageTests unitarios + cobertura
test:watchvitestTests en watch mode
test:e2eplaywright testTests E2E
linteslint src --max-warnings 0Lint estricto
formatprettier --write src testsFormat
type-checktsc --noEmitType check sin emitir

Notas:

  • Para que la cookie portal_refresh_token funcione en local, el backend tiene que servirse con HTTPS (Secure) o el atributo Secure debe relajarse para desarrollo. Actualmente la cookie es siempre Secure; SameSite=None (ver PortalAuthController::setRefreshCookie). Pendiente de validacion: como se maneja el setup local con HTTP. Posibles caminos: mkcert para certificados locales, localhost exempt en algunos browsers, o un override de la cookie en entorno dev.
  • Las variables VITE_* opcionales de branding pueden quedar vacias en local; BrandingContext tiene defaults sensatos.

Notas y pendientes

  • Listado de claves portal.* en data_config: no verificado en codigo. Documentar en doc tecnica backend dedicada cuando se valide.
  • docker-compose.yml: no presente en el repo. Validar con DevOps el orquestador real.
  • HTTPS en local: la cookie Secure; SameSite=None no funciona sobre HTTP. Falta documentar el flujo recomendado para desarrollo local.
  • Multi-portal con backend compartido: PORTAL_ALLOWED_ORIGIN es un unico string. Si hay varios portales con dominios distintos contra el mismo backend, hay que validar como se autorizan todos (o reescribir el middleware para aceptar lista).

Ver tambien


NOTA IMPORTANTE: Documentacion retrospectiva. Validar con stakeholders antes de considerarla final. Las secciones marcadas como "Pendiente de validacion" requieren confirmacion del equipo de DevOps / backend.