Landing y login
Layouts, hero, testimonios, textos por idioma y registro.
En esta página
La pantalla de login y registro de cada empresa es configurable sin código. La configuración vive en Domain.theme.landing (JSON) y es dato, nunca código: solo claves de una lista blanca, longitudes máximas, enums, colores hex, números acotados y URLs https (o rutas relativas en los enlaces). Sin HTML, CSS libre, JavaScript, data: ni http:. Un saneador idéntico en frontend y backend (con test de paridad) la valida al guardar y al servir.
Sin landing se usa el diseño histórico (hero a la izquierda y formulario a la derecha). Cualquier valor inválido se descarta o se recorta sin romper la pantalla; el tamaño total máximo es 32 KB.
Layouts#
| `layout` | Descripción |
|---|---|
split-right (por defecto) | Hero a un lado y panel de login a la derecha |
split-left | Panel de login a la izquierda |
center | Formulario centrado sobre el fondo |
fullscreen-bg | Fondo a pantalla completa (color, degradado o imagen) con el panel encima |
minimal | Solo el formulario, sin hero |
Las páginas de administración (/admin/login, /admin/register) fuerzan center y ocultan hero, funciones, estadísticas y testimonios. En móvil, hero.mobile es banner (hero compacto arriba) u hidden (solo la cabecera de marca).
Referencia de campos#
| Sección | Campos y límites |
|---|---|
| raíz | layout, panelWidth (320–560 px), locale (es | en: fuerza el idioma de la landing) |
hero | title (80), subtitle (240), badge (40), imageUrl (https), imageAlt (120), imagePosition (center|top|bottom|left|right), overlay (0–1; el render sube el mínimo necesario para garantizar AA del texto), gradient {from, to, angle 0–360}, pattern (none|dots|grid|diagonal), mobile |
logo | light y dark (https), height (16–96), position (panel|hero|header), showName |
background | type (color|gradient|image), color, gradient, imageUrl, overlay |
form | title (80), subtitle (200), submitLabel (40), registerTitle, registerSubtitle, registerSubmitLabel, showRegisterLink, showForgotLink, showGoogle, showRememberMe, alignment (left|center) |
testimonials | enabled, style (cards|carousel|quote), items (hasta 8): quote (280, obligatorio), author (60), role (80), avatarUrl (https) |
features | Hasta 6: title (60, obligatorio), text (160), icon (shield lock zap globe mail users sparkles check star heart clock cloud key inbox send calendar bell file headphones rocket chart building) |
stats | Hasta 4: value (16, obligatorio), label (40) |
footer | text (200), links (hasta 6: label 40 y url https o ruta que empiece por una sola /), showPoweredBy |
legal | termsUrl, privacyUrl (https o ruta relativa) |
docs | visible, showInFooter, showInSidebar, landingLink: ver Ocultar o mostrar docs |
registration | enabled, requireKey, message (200): ver abajo |
i18n | {es: {...}, en: {...}} con los textos traducibles |
Textos por idioma#
Los textos de la empresa se pueden dar por idioma en i18n.es / i18n.en. Claves admitidas: heroTitle, heroSubtitle, heroBadge, imageAlt, formTitle, formSubtitle, submitLabel, registerTitle, registerSubtitle, registerSubmitLabel, footerText, registrationMessage. Prioridad de un texto: i18n[idioma] > valor base (hero.title, …) > diccionario global de BloomX. El idioma efectivo es landing.locale si la empresa lo fuerza; si no, el del visitante (cookie bloomx-lang → Accept-Language → español).
Registro#
registration.enabled: falseoculta el enlace "crear cuenta" del login y muestra en/registerun aviso (registration.message). Es solo interfaz: el servidor sigue aceptandoPOST /api/registercon la clave correcta.registration.enabled: falsehace que el servidor (/api/register) responda 403 (el formulario también se oculta).registration.requireKey: falsehace que el servidor no pida la clave de registro, ytrue(por defecto) exigeREGISTRATION_KEY; en producción, si falta o valedev-secret, el registro con clave responde 403. La política la aplica el servidor aunque el formulario se manipule.form.showRegisterLink: falseoculta el enlace de registro del login.showForgotLink,showGoogleyshowRememberMecontrolan esos elementos del formulario.- El acceso con Google solo funciona si configuraste
GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRETyNEXTAUTH_URL(Variables).
Ejemplos JSON copiables#
Cada ejemplo va dentro de theme (el mismo objeto de Temas empresariales). Las URL de imágenes deben ser https y servidas por ti.
{
"landing": {
"layout": "split-right",
"hero": {
"badge": "Correo corporativo",
"title": "Tu correo, bajo tu marca",
"subtitle": "Seguro, rápido y con tus reglas.",
"gradient": { "from": "#0b5fff", "to": "#1e1b4b", "angle": 135 },
"pattern": "dots",
"mobile": "banner"
},
"logo": { "light": "https://cdn.mi-empresa.com/logo.svg", "dark": "https://cdn.mi-empresa.com/logo-dark.svg", "height": 32, "position": "panel", "showName": true },
"features": [
{ "icon": "shield", "title": "Seguridad de nivel empresarial", "text": "MFA y cifrado en reposo." },
{ "icon": "zap", "title": "Rápido", "text": "Búsqueda instantánea y atajos de teclado." },
{ "icon": "calendar", "title": "Calendario integrado" }
],
"footer": { "text": "© Mi Empresa", "links": [ { "label": "Soporte", "url": "https://mi-empresa.com/soporte" } ], "showPoweredBy": false }
}
}{
"landing": {
"layout": "minimal",
"form": { "showRegisterLink": false, "showGoogle": false, "showRememberMe": true },
"registration": { "enabled": false, "message": "El alta es solo por invitación." },
"legal": { "termsUrl": "https://mi-empresa.com/terminos", "privacyUrl": "https://mi-empresa.com/privacidad" }
}
}{
"landing": {
"layout": "fullscreen-bg",
"locale": "es",
"panelWidth": 440,
"background": { "type": "image", "imageUrl": "https://cdn.mi-empresa.com/fondo.jpg", "overlay": 0.55 },
"hero": { "title": "Bienvenido", "subtitle": "Accede a tu correo corporativo", "imageAlt": "Oficina" },
"form": { "title": "Inicia sesión", "submitLabel": "Entrar", "alignment": "left", "showForgotLink": true },
"testimonials": {
"enabled": true,
"style": "carousel",
"items": [
{ "quote": "Migramos en un fin de semana.", "author": "Ana Ruiz", "role": "CTO", "avatarUrl": "https://cdn.mi-empresa.com/ana.jpg" },
{ "quote": "Por fin un correo que respeta nuestra marca.", "author": "Luis Peña", "role": "Operaciones" }
]
},
"stats": [ { "value": "99.9%", "label": "Disponibilidad" }, { "value": "24/7", "label": "Soporte" } ],
"docs": { "visible": true, "landingLink": true, "showInFooter": true },
"i18n": {
"es": { "heroTitle": "Bienvenido", "submitLabel": "Entrar" },
"en": { "heroTitle": "Welcome", "heroSubtitle": "Sign in to your company mail", "submitLabel": "Sign in", "formTitle": "Sign in" }
}
}
}# Guardar (sesión de manager). El cuerpo reemplaza TODO el tema: incluye también palette/colores si los tienes.
curl -sS -X PUT "$BACKEND/api/admin/domain" \
-H 'content-type: application/json' -b 'auth_session=<cookie>' \
-d @tema-completo.json # {"theme": { ...colores..., "landing": { ... } }}PUT reemplaza el tema completo
El backend guarda exactamente el theme que recibe: si envías solo landing, pierdes la paleta. Envía el objeto completo (colores + landing). El editor de landing del panel (LandingEditor) puede no estar montado en el panel de administración de tu versión: en ese caso usa PUT /api/admin/domain, y comprueba que el guardado desde el panel no descarte landing.
Seguridad y límites#
- Todo texto pasa por una limpieza (sin HTML ni caracteres de control o de dirección bidi); el texto que exceda el máximo se recorta.
- Las imágenes solo pueden ser https y se pintan con
<img referrerPolicy="no-referrer">; nunca conurl(...)en CSS, para evitar inyección por URL. La CSP ya permitehttps:enimg-src. - Un elemento sin campo obligatorio (testimonio sin
quote, función sintitle, enlace sinlabelourl) se descarta. - Límites: 8 testimonios, 6 funciones, 4 estadísticas, 6 enlaces de pie, 32 KB en total. Más elementos se recortan.
- El contraste del texto sobre imagen o degradado se calcula en el render para cumplir AA (sube el
overlaymínimo si hace falta).