D4 · Gate híbrido (Liquid + Locksmith)¶
Estado del documento
Versión: 1.0 · 17-may-2026 Estado: ✅ aceptada Audiencia: Equipo de desarrollo
Estado¶
Aceptada · Fase C (mayo 2025) · vigente. Iteración del destino para anónimos: 4-may-2026 (intercalación de la landing /pages/acceso-profesional).
Contexto¶
El portal necesita un gate de acceso con tres reglas por estado del Customer:
- Anónimo — debe ver la landing comercial y los CTAs de registro/login. No puede acceder a productos, colecciones, checkout, ni páginas internas.
- Pendiente / rechazado — autenticado pero sin tag
aprobado. Debe ver una página de estado (cuenta en revisión o cuenta rechazada) y no acceder a rutas comerciales. - Aprobado — acceso completo al catálogo y a
/pages/solicitar-pedido(sustitutivo del checkout nativo, deshabilitado en Fase D).
El plan original (kickoff Fase C) era implementar las tres reglas con Locksmith:
- Rule 1:
customer.tags contains 'rechazado'→ bloquea y redirige. - Rule 2:
customer.tags NOT contains 'aprobado'→ bloquea coleccióncoleccion-2026. - Rule 3: Entire Store, anónimo → bloquea y redirige a login.
Al instalar Locksmith con scope Entire Store (Rule 3), la app falla con High-level job failure y no permite completar la regla. Causa raíz no resuelta — el soporte de Locksmith atribuye el fallo al tamaño del catálogo y a las particularidades del template B2B. Rules 1 y 2 sí se pueden definir, pero Rule 1 redirige por path completo (no contempla rutas dinámicas como /account/*) y queda inutilizable.
Decisión¶
Gate principal en Liquid, dentro de layout/theme.liquid (<head>, justo antes de </head>). Locksmith queda como segunda capa de defensa únicamente para Rule 2 (collection lock).
Lógica del gate en theme:
{%- unless request.design_mode or request.host contains '.shopifypreview.com' or request.page_type == 'password' -%}
{%- assign gate_exempt_paths = '/,/account/login,/account/register,...,/pages/acceso-profesional' | split: ',' -%}
{%- if gate_path is exempt -%}
{# no-op #}
{%- elsif customer == nil -%}
<script>window.location.replace('{{ locale_prefix }}/pages/acceso-profesional');</script>
{%- elsif customer.tags contains 'rechazado' -%}
<script>window.location.replace('{{ locale_prefix }}/pages/cuenta-rechazada');</script>
{%- elsif customer.tags contains 'aprobado' -%}
{%- if gate_path contains '/checkout' -%}
<script>window.location.replace('{{ routes.cart_url }}');</script>
{%- endif -%}
{%- else -%}
{# pendiente: bloquea solo rutas comerciales #}
<script>window.location.replace('{{ locale_prefix }}/pages/cuenta-en-revision');</script>
{%- endif -%}
{%- endunless -%}
Tres excepciones técnicas que no aplican gate:
request.design_mode— theme editor de Shopify (rompería previsualización).request.host contains '.shopifypreview.com'— preview de theme. El OAuth de new customer accounts rechazaredirect_uride dominios preview, así que aplicar gate aquí encadenaría errores.request.page_type == 'password'— tienda en password protection.
Paths exentos (acceso público): home, /account/login, /account/register, /account/recover, /account/activate/*, /account/reset_password/*, /account/logout, /account/sign_out, /pages/cuenta-en-revision, /pages/cuenta-rechazada, /pages/acceso-profesional, /pages/registro-recibido, /pages/aviso-legal, /pages/politica-de-privacidad, /pages/condiciones-de-uso, /pages/canal-de-denuncias, /policies/*.
Locksmith Rule 2 sigue activa como segunda capa: bloquea la collection coleccion-2026 a customers sin tag aprobado. Es defense-in-depth — si el script del head falla a cargar (CSP, error JS, navegador antiguo), Locksmith atrapa el acceso directo a producto/colección.
Destino del anónimo (iteración 4-may-2026)¶
Hasta el 4-may-2026, anónimo redirigía directamente a /customer_authentication/login?return_to=.... Desde esa fecha, se intercala la landing /pages/acceso-profesional, que presenta el portal y ofrece dos CTAs:
- Primario —
/account/register(form B2B real). - Secundario — login OAuth con
return_tocorrecto.
Motivo del cambio: reducir fricción para visitantes que no conocen aún la marca y dar contexto antes de pedir credenciales.
Alternativas consideradas¶
Locksmith puro (3 reglas). Bloqueada por el fallo High-level job failure en Rule 3 (Entire Store). Sin Rule 3 no hay gate para anónimo, así que la opción quedó técnicamente inviable.
Liquid puro (sin Locksmith). Descartada — sin segunda capa, un fallo del script en <head> deja toda la colección accesible vía URL directa. Locksmith Rule 2 mitiga este escenario en el path crítico (compra).
Customer Account UI Extensions. Shopify expone extensiones para customizar el portal hospedado en /account (post-login). No cubren el gate de storefront, solo personalizan vistas internas. Pendiente para Fase E (mejora del dashboard del aprobado, no del gate).
Consecuencias¶
- Mantenimiento del gate vive en el theme. Cualquier cambio en estados de customer (nuevas tags, nuevos paths exentos) requiere editar
layout/theme.liquid. Documentado en 04-storefront-gate. - No hay protección server-side. El gate redirige client-side con
window.location.replace. Un cliente con JS deshabilitado, o una request directa a la API del storefront, ve la página. Mitigaciones: Locksmith Rule 2 en colecciones críticas, y el catálogo B2B nativo (D2) que limita qué productos son visibles a quién (los precios B2B no aparecen sin Company asignada). - Locale-aware. El gate usa
routes.root_urlpara construirlocale_prefix(''en raíz,/fren/fr). Mantiene el locale tras redirect. - Excepciones no son configurables. Lista de exempt paths está hardcoded en el theme. Añadir uno requiere PR. Documentado en 04-storefront-gate.
- Customer Account UI Extensions (D5) viven en otro plano y no comparten lógica con este gate.
Cambios¶
- v1.0 (17-may-2026): cabecera de estado actualizada; el documento estaba completo pero figuraba como v0.1.
- v0.1 (15-may-2026): primera publicación.