Ir al contenido

Integración con el tema

Optical Form funciona en cualquier tema con Online Store 2.0 —los que permiten añadir bloques desde el editor de temas— y no necesita desarrollador para instalarse. Está verificado en Dawn y Horizon; para temas a medida hay adaptadores y los hooks de abajo. Esta página resume la capa de integración para quien quiera personalizarla. Para los ajustes visuales del bloque y del embed, ver Botón y carrito (tema).

Resolución de variante (agnóstica al tema)

Sección titulada «Resolución de variante (agnóstica al tema)»

El drawer y el botón son web components independientes que no se re-renderizan al cambiar de variante. Por eso Optical Form resuelve la variante actual de forma perezosa (al abrir y al añadir al carrito), con esta cadena de prioridad:

  1. Hook del comerciante: window.OpticalFormHooks.getVariantId(triggerEl)
  2. Selector configurable en el bloque (setting Variant input selector)
  3. Input estándar del formulario de producto [name="id"] (Dawn/Horizon y la mayoría)
  4. Parámetro de URL ?variant=
  5. Fallback al valor renderizado por el servidor
window.OpticalFormHooks = {
// Devuelve el id de la variante seleccionada (máxima prioridad).
getVariantId(triggerEl) { return /* id de variante */; },
// Opcional: sobrescribe el precio (en céntimos) de una variante.
getVariantPrice(variantId, triggerEl) { return undefined; },
// Opcional: desactiva la agrupación de bundle en el carrito.
disableCartGrouping: false,
};

Los addons y accesorios se añaden como líneas separadas con parent_id (bundle nativo de Shopify — necesario para el borrado en cascada, el checkout y los emails de pedido). Como los temas las pintan como líneas sueltas, CartBridge las reordena para que cada hija quede justo debajo de su línea principal y las marca con ofd-bundle-parent / ofd-bundle-child.

Ahí acaba: no oculta la cantidad ni el botón de eliminar de las hijas, y la app no envía ni una regla de CSS al carrito. Esconder un control no impide la acción —la línea se puede cambiar y borrar igual por la API— y esa misma fila la comparten otras apps (garantías, seguros de envío), así que taparles la papelera dejaría al comprador con un cargo de un tercero que no puede quitar. Mantener el pedido coherente es trabajo del servidor, no del CSS.

Para afinar cómo se ve hay dos palancas en el app embed: la casilla Compactar las líneas de complementos (cart_tidy_addons, apagada por defecto) y el campo CSS personalizado. Ver Diseño y carrito.

Los selectores del carrito y el comportamiento tras añadir se ajustan en el app embed — ver Botón y carrito. CartBridge autodetecta los patrones de la mayoría de temas; solo hace falta sobrescribir selectores en temas no estándar.

Los hooks de arriba van hacia dentro: el tema responde a lo que la app pregunta. Estos eventos van al revés — la app cuenta lo que está haciendo el comprador, para que tu analítica, tu chat o tu recuperación de carritos vivan donde ya viven, sin necesitar una función nuestra.

No hay que hacer nada para tenerlos. Si nadie escucha, no pasa nada. Qué montar encima de cada uno, con ejemplos para chat, analítica y abandono: Qué hacer con cada evento.

document.addEventListener('opticalform:step:rendered', (e) => {
gtag('event', 'lens_configurator_step', {
step_type: e.detail.stepType,
step_index: e.detail.stepIndex,
session_id: e.detail.sessionId,
});
});

Se emiten sobre <optical-form-drawer> y burbujean, así que puedes escuchar en el elemento o en document.

Evento Cuándo Campos propios
opticalform:open se abre el configurador
opticalform:step:rendered el comprador llega a un paso isFirst, isLast
opticalform:validation:error un paso le impide avanzar
opticalform:addtocart:start empieza el añadido
opticalform:addtocart:success el carrito lo aceptó lineCount, totalPrice, currency
opticalform:addtocart:error falló kind
opticalform:close se cierra completed
optical-form:cart:orphan en el carrito falta alguna línea hija de una montura: el cliente borró las lentes desde el carrito del tema groupId, key, expected, actual

El último no lo emite el configurador sino el puente del carrito, en document, en cuanto detecta una montura sin las lentes o tratamientos con los que salió: la fila recibe además la clase ofd-bundle-orphan y un aviso traducido debajo. La app no borra nada ni bloquea el checkout — con el evento decides tú (avisar a tu chat, quitar la montura, lo que quieras). Mientras el puente recoloca el carrito tras un añadido, los botones de checkout se desactivan un instante y <html> lleva la clase ofd-cart-busy. Las dos cosas se pueden apagar en la configuración del puente: orphanNotice: false y guardCheckoutWhileBusy: false.

Todos llevan el mismo contexto:

{
version: 1,
sessionId, // aleatorio, por sesión del panel: cose una visita entera
productId, variantId,
stepIndex, stepId, stepType,
totalSteps, // pasos CONFIGURADOS, no los que recorrerá esta persona
}

kind vale 'throttled' | 'unavailable' | 'unknown': la causa, no el mensaje —el mensaje es una etiqueta que el comerciante puede reescribir—. Solo throttled merece reintento.

completed distingue «terminó» de «se fue», que es toda la diferencia para recuperar un abandono.

totalPrice va en céntimos y solo en :addtocart:success.

Dicen DÓNDE está el comprador, nunca QUÉ ha contestado. Un CustomEvent lo lee cualquier script de la página, así que el detail es una lista cerrada: nunca contiene los valores de la graduación, el fichero subido ni su nombre, la foto de la receta, los textos del comerciante ni el nodo del paso.

Dos cosas que conviene saber antes de construir encima:

  • stepType es un dato de salud inferido. Llegar al paso de graduación significa que esa persona tiene un defecto refractivo. Va incluido porque sin él los eventos no sirven para nada, y porque esa misma inferencia ya está al alcance de cualquier script de la página. Trátalo en consecuencia.
  • totalPrice solo después de añadir. Una vez añadido, esa cifra está en /cart.js para quien quiera leerla. Antes de añadir sería información nueva, y en un formulario de lentillas revelaría un pedido de un solo ojo.

Ninguno es cancelable: preventDefault() no hace nada. Es deliberado. Los tests A/B y las reglas de validación propias del comerciante necesitarán otro mecanismo, y se añadirá como tal en vez de cambiar lo que significan estos.

version sube solo si un campo existente cambia de forma. Añadir campos nuevos no la sube: quien lea lo que ya conocía sigue funcionando. Los eventos no se renombran ni se retiran sin un cambio de versión mayor.

:step:rendered se emite cuando el paso cambia, no en cada repintado: adjuntar un fichero, el catálogo de lentillas estrechando una combinación o las opciones excluyentes redibujan el panel sin mover al comprador de sitio. Si necesitas enterarte de cuándo entra HTML nuevo —una capa de traducción, por ejemplo— usa un MutationObserver sobre el panel.

El detalle técnico (resolver, CartBridge, selectores, eventos) vive en INTEGRATION.md, dentro de la extensión de la app: extensions/optical-form-ext/INTEGRATION.md.