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:
- Hook del comerciante:
window.OpticalFormHooks.getVariantId(triggerEl) - Selector configurable en el bloque (setting Variant input selector)
- Input estándar del formulario de producto
[name="id"](Dawn/Horizon y la mayoría) - Parámetro de URL
?variant= - Fallback al valor renderizado por el servidor
Hooks (sin forkear la app)
Sección titulada «Hooks (sin forkear la app)»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,};Agrupación en el carrito
Sección titulada «Agrupación en el carrito»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.
Eventos del configurador
Sección titulada «Eventos del configurador»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.
Lo que estos eventos nunca llevan
Sección titulada «Lo que estos eventos nunca llevan»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:
stepTypees 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.totalPricesolo después de añadir. Una vez añadido, esa cifra está en/cart.jspara 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.
Notifican, no dejan intervenir
Sección titulada «Notifican, no dejan intervenir»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.
Versionado
Sección titulada «Versionado»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.
Repintados
Sección titulada «Repintados»: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.
Referencia completa
Sección titulada «Referencia completa»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.