Appearance
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 variablesVITE_*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 conPORTAL_ALLOWED_ORIGINSpara autorizar CORS desde el portal. Soporta multiples origenes separados por coma. - Resolucion multi-tenant: NO hay resolucion DNS por dominio. El frontend declara su
tenant_idysucursal_iden build time; el backend resuelve la conexion viaini.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.
| Variable | Requerida | Descripcion | Ejemplo |
|---|---|---|---|
VITE_BACKEND_URL | si | URL base de la API backend, sin trailing slash. Usada como baseURL de axios y para CORS. | https://api.tenant.com |
VITE_TENANT_ID | si | ID numerico del tenant (empresa). Se manda en cada request via header X-Tenant-Id. | 1 |
VITE_SUCURSAL_ID | si | ID numerico de la sucursal. Se manda via header X-Sucursal-Id y se usa como sucursalId por defecto en login. | 1 |
VITE_APP_NAME | no | Nombre de la app para branding (titulo del documento, header). | Portal de Clientes |
VITE_LOGO_URL | no | URL absoluta al logo del tenant. | https://cdn.tenant.com/logo.png |
VITE_PRIMARY_COLOR | no | Color primario CSS (hex o hsl). Inyectado como --primary en :root. | #1e40af |
VITE_SECONDARY_COLOR | no | Color secundario CSS. Inyectado como --secondary en :root. | #3b82f6 |
VITE_THEME_COLOR | no | Color de la meta tag theme-color para PWA / barra de status mobile. | #1e40af |
Notas:
- Validacion de variables obligatorias: el
Dockerfilefalla el build siVITE_BACKEND_URL,VITE_TENANT_IDoVITE_SUCURSAL_IDno 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_URLen runtime.
Variables de entorno - Backend (bautista-backend)
Solo las variables del backend que afectan al portal. Definidas en bautista-backend/.env.dist.
| Variable | Requerida | Descripcion | Ejemplo |
|---|---|---|---|
PORTAL_ALLOWED_ORIGINS | si (cuando portal esta activo) | Origenes autorizados para CORS desde el portal de clientes. Separar multiples origenes con coma, sin espacios alrededor de la coma. Cada origen debe coincidir EXACTO con el del frontend (sin barra al final). Usado por PortalCorsMiddleware. | https://portal.tenant.com,https://portal2.tenant.com |
BACKEND_URL | si (cuando pagos online estan activos) | URL publica del backend, sin barra final. Se usa para construir notification_url del gateway: {BACKEND_URL}/backend/portal/pagos/webhook?.... | https://api.tenant.com |
Constantes relacionadas en constants.dist.php:
| Constante | Uso |
|---|---|
ALLOWED_ORIGINS | Origenes permitidos para el ERP principal. NO se usa para el portal -- el portal tiene su propio middleware con PORTAL_ALLOWED_ORIGINS. |
HOST, PORT, USER, PASSWORD, DB_INI | Conexion a la DB principal (ini.sistema) que resuelve la conexion por tenant. |
Notas:
PORTAL_ALLOWED_ORIGINSadmite multiples origenes separados por coma (ej:https://portal-a.com,https://portal-b.com,http://localhost:5174). Esto permite un backend compartido entre multiples portales con dominios distintos sin modificar el middleware.BACKEND_URLdebe apuntar a una URL accesible por PayPerTIC/los gateways. Si falta, el inicio de pago falla rapido conMISCONFIGURED(HTTP 500) antes de crear pagos o llamar al gateway.- El backend usa JWT con claves RSA. Las claves estan fuera del scope del portal (son globales del backend). Ver
bautista-backend/CLAUDE.mdparaprivate-key.pem/public-key.pem.
data_config - Configuracion en base de datos
Nota: la documentacion existente en
index.mddel modulo menciona que el backend resuelvetenant_id -> DBviaini.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.*endata_config(tipos, valores admitidos, defaults). - Mecanismo exacto por el cual el backend resuelve
tenant_iddesdeini.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:
| Stage | Base | Proposito |
|---|---|---|
builder | node:22-alpine | npm ci, copiar fuentes, validar build args, ejecutar npm run build (genera /app/dist). |
runner | nginx:alpine | Copia /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_URLVITE_TENANT_IDVITE_SUCURSAL_ID
SPA routing en nginx
Definido en portal-usuarios/nginx.conf:
| Regla | Comportamiento |
|---|---|
location / con try_files $uri $uri/ /index.html | Fallback 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.html | Cache-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-routerfeature-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.
| Tenant | Build args distintos | Resultado |
|---|---|---|
| Tenant A | VITE_BACKEND_URL=https://api.bautista.com, VITE_TENANT_ID=1, VITE_SUCURSAL_ID=1, branding A | Imagen portal-tenant-a:latest |
| Tenant B | VITE_BACKEND_URL=https://api.bautista.com, VITE_TENANT_ID=2, VITE_SUCURSAL_ID=3, branding B | Imagen 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_IDyVITE_SUCURSAL_IDquedan fijas en build. - El backend (
bautista-backend) se comparte. Cada portal apunta al mismo backend con sus headersX-Tenant-Id/X-Sucursal-Id.
Resolucion multi-tenant en el flujo de request
Documentada en index.md del modulo. Resumen:
- El frontend manda
X-Tenant-Id,X-Sucursal-Idy (cuando esta autenticado)Authorization: Bearer {access_token}. - El backend resuelve
tenant_id -> base de datosviaini.sistema. - El backend resuelve
sucursal_id -> schema PostgreSQL(suc0001, suc0002, etc.). - El JWT lleva
tenant_idysucursal_id-- el backend valida que coincidan con los headers (proteccion contra tampering).
Infraestructura de orquestación por tenant
La infraestructura de deploy está en portal-usuarios/tenants/ y portal-usuarios/scripts/:
portal-usuarios/
├── tenants/
│ ├── .gitignore # ignora */ (configs reales), versiona _template/ y README
│ ├── README.md # guía de operación
│ └── _template/
│ ├── .env.example # 11 variables: DOMAIN, PORT, TENANT_SLUG, 3 VITE obligatorias, 5 VITE branding
│ ├── docker-compose.yml # build context ../.. , 8 build args VITE_*, ${PORT}:80
│ └── apache-vhost.conf.tpl # VirtualHost con tokens {{DOMAIN}}, {{PORT}}, {{SLUG}}
└── scripts/
├── portal-new-tenant.sh # crea tenants/<slug>/ desde el template con puerto auto-detectado
└── portal-deploy.sh # docker compose up + apache graceful reloadFlujo para agregar un tenant nuevo:
bash
# 1. Crear la carpeta del tenant (porta auto-incrementa el puerto)
./scripts/portal-new-tenant.sh <slug>
# 2. Editar tenants/<slug>/.env con las variables reales del tenant
# (VITE_BACKEND_URL, VITE_TENANT_ID, VITE_SUCURSAL_ID, branding)
# 3. Deployar
./scripts/portal-deploy.sh <slug>Las configuraciones reales de cada tenant (tenants/<slug>/) están en .gitignore (contienen credenciales y datos de producción). Solo el template _template/ y el README están versionados.
La detección de puerto libre la hace portal-new-tenant.sh leyendo PORT= de los .env existentes en tenants/*/ — el compose usa ${PORT}:80 como variable.
Setup local de desarrollo
Pasos minimos para levantar el portal localmente, derivados de package.json y los archivos de config:
| Paso | Comando / accion | Resultado |
|---|---|---|
| 1 | Clonar portal-usuarios y entrar al directorio | -- |
| 2 | Copiar .env.example -> .env.local y completar al menos VITE_BACKEND_URL, VITE_TENANT_ID, VITE_SUCURSAL_ID | Variables Vite disponibles |
| 3 | npm ci | Instala dependencias declaradas en package-lock.json |
| 4 | Levantar backend en paralelo (bautista-backend) con PORTAL_ALLOWED_ORIGINS=http://localhost:5173 (puerto default de Vite) | CORS habilitado para el frontend local |
| 5 | npm run dev | Vite dev server en http://localhost:5173 |
Otros scripts utiles definidos en package.json:
| Script | Comando | Uso |
|---|---|---|
build | tsc -b && vite build | Build de produccion |
preview | vite preview | Servir el build localmente |
test | vitest run --coverage | Tests unitarios + cobertura |
test:watch | vitest | Tests en watch mode |
test:e2e | playwright test | Tests E2E |
lint | eslint src --max-warnings 0 | Lint estricto |
format | prettier --write src tests | Format |
type-check | tsc --noEmit | Type check sin emitir |
Notas:
- Para que la cookie
portal_refresh_tokenfuncione en local, el backend tiene que servirse con HTTPS (Secure) o el atributoSecuredebe relajarse para desarrollo. Actualmente la cookie es siempreSecure; SameSite=None(verPortalAuthController::setRefreshCookie). Pendiente de validacion: como se maneja el setup local con HTTP. Posibles caminos:mkcertpara certificados locales,localhostexempt en algunos browsers, o un override de la cookie en entorno dev. - Las variables
VITE_*opcionales de branding pueden quedar vacias en local;BrandingContexttiene defaults sensatos.
Notas y pendientes
- HTTPS en local: la cookie
Secure; SameSite=Noneno funciona sobre HTTP. Falta documentar el flujo recomendado para desarrollo local. - Multi-portal con backend compartido:
PORTAL_ALLOWED_ORIGINSacepta lista de origenes separados por coma. Un backend puede servir multiples portales con dominios distintos sin configuracion adicional. - Claves
portal.recibo.*:portal.recibo.cuenta_bancariayportal.recibo.caja_schemase configuran desde ERP → Config → Gateway de Pagos. Ver Auto-reconciliación técnico.
Ver tambien
- Autenticacion Frontend - Como se usan
VITE_BACKEND_URL,VITE_TENANT_ID,VITE_SUCURSAL_IDen el flujo de auth. portal-usuarios/Dockerfile- Build multi-stage con argsVITE_*.portal-usuarios/nginx.conf- SPA routing y politicas de cache.portal-usuarios/.env.example- Template de variables del frontend.portal-usuarios/vite.config.ts- Manual chunks y aliases.bautista-backend/.env.dist-PORTAL_ALLOWED_ORIGINSy demas config backend.bautista-backend/Modules/Portal/Infrastructure/Http/Middleware/PortalCorsMiddleware.php- Middleware que consumePORTAL_ALLOWED_ORIGINS. Registrado como middleware global con path-awareness (/backend/portal).- Multi-Tenancy del portal - Como se resuelve
tenant_id-> DB ysucursal_id-> schema. - Infraestructura del deployment - (si existe) Diagrama de la infra Docker por tenant.
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.