Skip to content
ES

Dominio propio por tenant (white-label)

This content is not available in your language yet.

Un tenant Enterprise puede operar bajo su propio dominio: el cliente ve sucliente.com en la barra, no *.zentto.net. Es parte de lo vendido. Caso de referencia: Repuestos San José (sanjoserepuestos.com, Pulse 625, 2026-09-12).

  • Resolución de tenant por dominio: cfg.Company.CustomDomain + usp_cfg_tenant_resolvedomain. El middleware de la API resuelve un host fuera de *.zentto.net probando el host exacto y sin su primer label (caja.sucliente.comsucliente.com), con cache. CORS acepta los orígenes de dominios registrados con el mismo cache.
  • API mismo-origen: bajo un dominio custom, @zentto/shared-api usa base relativa ('') — el vhost nginx del dominio proxea /v1, /api y /media-files al API. Cero CORS real, cookies first-party.
  • TenantGuard resuelve por GET /api/tenants/resolve-domain/:host y persiste {companyId, slug, domain}.
  • Caja/Mostrador: el host-lock del POS matchea caja.* / mostrador.* de cualquier dominio — no requiere cambios por cliente.

Con el tenant ya provisionado y la zona del dominio activa en la cuenta CF:

Ventana de terminal
ssh root@178.104.56.185
bash /opt/zentto/alta-dominio-cliente.sh sucliente.com <companyId>

El script es idempotente (re-ejecutarlo verifica/repara) y hace todo el runbook de abajo: DNS → SSL strict → cert → vhost → registro en BD → orígenes federados (persisten: el seed de zentto-auth hace MERGE) → restart API → verificación E2E. Al final, versionar el .conf generado con un PR a zentto-infra (el propio script lo recuerda). Además, desde Pulse 626 el provision-full siembra también el contenido del tenant (company, branch y ADMIN dual-store) — ya no hay seed manual de usuarios del pipeline.

Runbook manual (referencia — lo que hace el script)

Sección titulada «Runbook manual (referencia — lo que hace el script)»

Prerequisito: el tenant existe (Enterprise, BD dedicada) y el dominio está en la cuenta Cloudflare de Zentto como zona activa (comprado en CF Registrar o delegado por NS).

  1. DNS (zona del cliente en CF): registros A proxied → 178.104.56.185 para: raíz, www, caja, mostrador. SSL/TLS de la zona en Full (strict).
  2. Cert en el server (antes del vhost — sin cert el sync lo omite):
    Ventana de terminal
    certbot certonly --dns-cloudflare \
    --dns-cloudflare-credentials /opt/zentto/cloudflare.ini \
    -d sucliente.com -d '*.sucliente.com'
  3. Vhost: copiar zentto-infra/nginx/clientes/sanjoserepuestos.com.confsucliente.com.conf, reemplazar el dominio, PR a developer y promoción. El sync prod lo planta solo si el cert existe.
  4. Orígenes federados en zentto-auth (OBLIGATORIO): añadir https://sucliente.com, https://caja.sucliente.com y https://mostrador.sucliente.com a AllowedOrigins de la app zentto-erp en el SEED 00000_register_apps.sql de zentto-auth (el seed pisa AllowedOrigins en cada deploy — una migración sola se deshace). Sin esto el login Microsoft/Google bajo el dominio falla. ⚠️ El seed es zona caliente de conflictos entre carriles: coordinar antes de tocarlo. Hecho para San José en zentto-auth#137/#139.
  5. Registrar el dominio en el tenant (master y BD dedicada):
    SELECT * FROM usp_cfg_tenant_setcustomdomain(<companyId>, 'sucliente.com');
    Reiniciar el API (o esperar el TTL del cache de dominios).
  6. Verificar: raíz y www → 200 sirviendo el shell; caja./mostrador. → 200 en su pantalla con el dominio visible; GET https://sucliente.com/api/tenants/resolve-domain/sucliente.com → 200; login del ADMIN del tenant bajo el dominio → token; y el botón Microsoft del login → 302 a login.microsoftonline.com (federado del dominio activo).

Trampas conocidas (pagadas en el caso San José)

Sección titulada «Trampas conocidas (pagadas en el caso San José)»
  • El vhost sin cert tumba el arranque de nginx — por eso el sync lo condiciona a /etc/letsencrypt/live/<dominio>/. Cert SIEMPRE primero.
  • /api/auth/ va al SHELL con X-Forwarded-Host (no al API): sin ese header Auth.js emite cookies/redirects para el host equivocado (patrón del item 618).
  • BrandCode y otros varchar cortos: si además migras inventario legacy, ver el runbook del bulk-import (marcas >20 chars se truncan).
  • Los usuarios de un tenant viven en TRES stores: ERP master + BD dedicada (dual-store del ERP) y el broker central zentto_auth — el login del shell (Auth.js) autentica contra el broker y NO tiene fallback legacy: sin la identidad central el usuario ve “credenciales incorrectas” aunque el ERP esté perfecto (caso San José, Pulse 626). El seed central requiere: auth."User" (username GLOBAL único + hash bcrypt), auth."UserApp" (app zentto-erp), auth."UserCompanyAccess" (CompanyId/BranchId espejo + ErpUserCode/ErpUserId) y la empresa/sucursal en auth."Company"/"Branch" (patrón: sanjose-seed-auth.sql). ⚠️ El username central es global: dos tenants no pueden tener ambos “ADMIN” — pendiente de diseño el login central scoped por tenant.
  • Bajo un tenant, el selector de empresas del login muestra solo la empresa del tenant (+DEMO si tiene acceso); ver otras empresas reales es exclusivo de los superadmin de plataforma (fix en login-options).
  • El origen federado vive en el SEED de zentto-auth, no en una migración (el seed pisa AllowedOrigins en cada deploy) — por eso es el paso 4 del runbook. Verificado en San José: con el seed, el start OIDC bajo el dominio responde 302 a Microsoft.