La Tienda Pública convierte el inventario que ya está en Axentra en un catálogo en línea con la marca de su negocio. Sus clientes acceden por un enlace tipo su-empresa.axentra.com.do/tienda, ven los productos que usted decida exponer (con foto, precio, existencia) y le escriben directamente por WhatsApp para comprar. Sin carrito, sin pagos en línea, sin pasarelas — pensado para el flujo dominicano real: el cliente llega, pregunta, usted le confirma stock y precio, y cierran por chat.
Es gratis para todos los planes y se administra desde Configuración → Tienda Pública.
storefront.publish.)retail, restaurant o services. Cambia algunos comportamientos por defecto (un restaurante muestra el catálogo agrupado por categorías como menú, retail muestra grilla de tarjetas).+18095551234. Es el número al que se enlazan todos los botones "Comprar por WhatsApp".Tras guardar, la URL pública aparece arriba del switch. Cópiela y compártala por IG, Facebook, WhatsApp, o ponga el QR impreso en la caja.
Por defecto todo el inventario es privado. Usted decide producto por producto qué se ve en la tienda.
En el formulario, sección "Tienda Pública", marque la casilla "Visible en tienda pública". El estado se guarda con el resto de los datos del producto.
Importante: solo se publican productos que están a la vez activos (
is_active=true) y públicos (is_public=true). Desactivar un producto lo retira de la tienda inmediatamente, aunque siga marcado como público.
Configuración → Tienda Pública → Tema controla cómo se ve la tienda sin tocar código.
| Variable | Qué afecta |
|---|---|
| Color primario | Hero, encabezados de sección, hover de tarjetas, links activos |
| Color de acento | Botón "Ver catálogo", precios, badges de "en existencia" |
| Tipo de letra | Familia tipográfica de toda la tienda (Inter, Poppins, Nunito, Roboto) |
| Radio de bordes | Esquinas redondeadas en tarjetas, botones, inputs (slider 0–24 px) |
Los cambios se aplican al guardar; el navegador del cliente toma la nueva versión en la próxima visita (la hoja de estilos tiene caché de 5 minutos con revalidación por ETag).
Si elige un color primario muy claro (ej. un pastel azul), el sistema calcula automáticamente un color de texto legible para el hero (slate-oscuro sobre fondo claro, blanco sobre fondo oscuro), exponiendo la variable --storefront-on-primary. Esto evita el clásico problema de "texto blanco invisible sobre fondo claro" sin que usted tenga que pensarlo.
Para los negocios que tienen un diseñador o quieren un look único, Configuración → Tienda Pública → CSS personalizado abre un editor donde puede pegar CSS arbitrario que se inyecta al final de la hoja de estilos de su tienda.
:root { --storefront-primary: #38bdf8 } repinta todo lo que use esa variable.header, footer, body, main, a[href*="/producto/"], a[href^="https://wa.me/"], etc.@keyframes y animation funcionan.color-mix(), clamp(), :has(), gradientes, backdrop-filter, etc.@import url(...) se eliminan al guardar (vector clásico de exfiltración cross-origin).expression(...) (legado IE) se eliminan.style-src 'self' 'unsafe-inline'; img-src 'self' data:) bloquea cualquier url(http://...) que apunte a un dominio externo aunque pase los filtros.INVALID_CSS y el guardado falla con un mensaje claro — no se guarda CSS roto.La hoja de estilos servida en /storefront/styles.css se construye así, en este orden:
:root { ... }.Como el CSS personalizado va al final, gana el cascade: cualquier :root { --x: ... } que escriba sobreescribe los valores del paso 2.
Nota técnica: la hoja de estilos solo se carga dentro de la SPA pública (
/tienda). No hay riesgo de que sus reglas afecten al panel de administración u otra superficie.
/* Brand variables */
:root {
--storefront-primary: #38bdf8;
--storefront-on-primary: #0c1c2e;
--storefront-accent: #0f172a;
--storefront-radius: 16px;
--storefront-shadow-hover:
0 8px 28px rgba(14,165,233,0.18),
0 2px 6px rgba(15,23,42,0.06);
}
/* Body con sutil gradiente radial en las esquinas */
body {
background:
radial-gradient(ellipse at 0% 0%, rgba(56,189,248,0.08), transparent 40%),
radial-gradient(ellipse at 100% 100%, rgba(15,23,42,0.04), transparent 40%);
}
/* Header tipo "frosted glass" */
header {
backdrop-filter: saturate(140%) blur(8px);
background-color: rgba(255,255,255,0.78) !important;
}
/* Hero con gradiente diagonal */
main > section:first-of-type {
background-image:
linear-gradient(135deg,
var(--storefront-primary) 0%,
color-mix(in srgb, var(--storefront-primary) 75%, #6366f1) 100%) !important;
}
/* Subrayado de acento debajo de los títulos de sección */
main h2 { position: relative; padding-bottom: 8px; }
main h2::after {
content: '';
position: absolute; bottom: 0; left: 0;
width: 48px; height: 3px;
background: var(--storefront-primary);
border-radius: 2px;
}
/* Pulso suave en el botón de WhatsApp */
a[href^="https://wa.me/"] {
animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse {
0%,100% { box-shadow: 0 6px 20px rgba(37,211,102,0.35); }
50% { box-shadow: 0 8px 28px rgba(37,211,102,0.55); }
}
La tienda pública es una SPA mobile-first con tres vistas:
/tienda)/tienda/catalogo)/tienda/producto/<id>)SUM(stock_levels.quantity) > 0 en todos los almacenes en tiempo real)."Hola, quisiera comprar [Producto] (RD$1,200.00)".El embudo entero se basa en enlaces deep-link a WhatsApp:
https://wa.me/<numero-sin-mas>?text=<mensaje-codificado>
Esto es deliberado: el cliente dominicano promedio prefiere chatear antes que llenar un carrito. Y para usted, esa conversación de WhatsApp se convierte en una venta real con un par de mensajes.
Hay dos permisos distintos relacionados con la tienda:
| Permiso | Quién lo necesita | Qué permite |
|---|---|---|
config.update |
Quien edita la tienda | Editar slug, vertical, WhatsApp, tema, CSS personalizado, imágenes |
storefront.publish |
Quien activa/desactiva | Encender o apagar el switch principal de "tienda habilitada" |
Esta separación deja que un administrador pueda dar el permiso de publicar a un rol de marketing sin darle acceso al resto de la configuración de la empresa. Por defecto, ADMIN tiene ambos permisos.
Cada vez que se activa o desactiva la tienda se escribe un evento en el registro de auditoría (audit_logs) con quién hizo el cambio, cuándo, y los valores antes/después.
La tienda incluye:
/storefront/sitemap.xml con todas las páginas públicas (inicio, catálogo, cada producto). Se cachea 1 hora y los crawlers como Googlebot lo descubren automáticamente./storefront/robots.txt permitiendo el rastreo del catálogo público.Product con precio, disponibilidad, imagen).<title> y <meta description> específicos por página.Para v1 no hay SSR (renderizado del lado del servidor); los crawlers modernos (Google, Bing) indexan la SPA correctamente sin SSR.
En la versión actual la tienda vive bajo un path dentro del dominio de la plataforma:
https://su-empresa.axentra.com.do/tienda
(o localhost:3000/tienda en dev).
Cada empresa ya tiene su propio subdominio en la plataforma (un contenedor backend por inquilino), por lo que la tienda activa se infiere del request — no hay slug en la URL. El campo "Identificador" en el panel de tema sigue existiendo como marca informativa pero no forma parte de la ruta pública. En una próxima versión soportaremos dominios personalizados (ej. mitienda.com apuntando vía CNAME al subdominio del tenant).
/storefront/graphql está completamente separado del /graphql autenticado:
mutation { ... } se rechaza al nivel HTTP con MUTATIONS_FORBIDDEN, antes de que el parser de GraphQL la vea.company_config del contenedor. No hay forma de leer el inventario de otro tenant — los datos viven en bases distintas.null en GraphQL, página "Tienda no disponible" en el cliente. Esto evita que alguien pruebe slugs en lote para descubrir tiendas.Hoy la tienda no incluye:
Estos podrían venir en una v2 según demanda. La filosofía de v1 es "el camino más corto entre un producto en su inventario y una conversación de WhatsApp con el cliente".
Verifique en orden:
/storefront apuntando al backend (ver vite.config.ts).La hoja de estilos /storefront/styles.css se cachea 5 minutos en el navegador del cliente. La SPA agrega un parámetro ?t=<timestamp> por cada carga de página para evitar caché obsoleta — pero si tiene la pestaña abierta desde antes del cambio, recargue con Cmd+Shift+R (hard reload) para forzar.
Asegúrese de que:
is_active = true (no está descontinuado).is_public = true (icono de ojo abierto en el listado).Errores comunes:
CSS_TOO_LARGE: el CSS supera 50 KB. Minimice o elimine reglas no usadas.INVALID_CSS: hay un error de sintaxis. Use un linter (ej. la consola del navegador) para encontrarlo.@import se eliminan silenciosamente: si dependía de ellas, integre el contenido directamente en su CSS.+: +18095551234, no (809) 555-1234./storefront/graphql — esquema en backend/internal/api/graphql/schema/storefront.graphqls./storefront/styles.css — hoja de estilos compuesta (defaults + tenant vars + custom CSS), 5 min cache, ETag./storefront/sitemap.xml — sitemap dinámico, cache 1h./storefront/robots.txt — robots permisivo.frontend/src/modules/storefront/ (StorefrontApp, Layout, HomePage, CatalogPage, ProductPage).backend/internal/rbac/permissions.go — PermStorefrontPublish y PermConfigUpdate.Para más detalles sobre el modelo de datos y migraciones, ver el código fuente del módulo.