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 de la incrustación, 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 panel 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
Plan de suscripción (apps de suscripciones)
Sección titulada «Plan de suscripción (apps de suscripciones)»El configurador no muestra ningún selector de planes: el cliente elige el plan en la página de producto, con la app de suscripciones que tenga la tienda (Shopify Subscriptions, Appstle o cualquier otra), y Optical Form lleva esa elección al carrito. Se resuelve al añadir, en este orden:
- Hook del comerciante:
window.OpticalFormHooks.getSellingPlanId(triggerEl) - Un
[name="selling_plan"]dentro del formulario de producto de este producto (form[action*="/cart/add"]cuyo[name="id"]es una de sus variantes): radio marcado,<select>o input oculto; lo que haya es la respuesta, también «compra única» (vacío) - Cualquier
[name="selling_plan"]de la página (Appstle muestra sus radios en un bloque de app fuera del formulario)
El plan va solo en la línea principal (complementos y accesorios son compra única) y solo si la variante tiene ese plan asignado: el panel enseña el precio del plan en el pie, los botones y el resumen, y el carrito cobra ese mismo importe. Si la app de suscripciones parchea fetch (Appstle lo hace), escribe el mismo valor que ya enviamos.
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: el id del plan de suscripción elegido (máxima prioridad); undefined = leerlo de la ficha. getSellingPlanId(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 complementos 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 muestran como líneas sueltas, CartBridge las reordena para que cada línea de complemento quede justo debajo de su línea principal y marca cada fila con su papel: ofd-bundle-parent (y ofd-set-fixed si es una lentilla), ofd-bundle-child más ofd-bundle-addon o ofd-bundle-accessory; y dentro de la fila, ofd-ctl-qty en el selector de cantidad y ofd-ctl-remove en la papelera.
Las cantidades del conjunto las cuadra la integración con el carrito, no el CSS: las líneas agrupadas de Shopify no siguen a su línea principal, así que cuando el cliente sube la montura desde el carrito, CartBridge multiplica los complementos con ella en un solo POST /cart/update.js (con las secciones del tema en la misma respuesta), devuelve a su sitio un complemento cambiado por su cuenta, deja libres los accesorios y devuelve una lentilla a sus cajas. Emite optical-form:cart:resync con lo que ha cambiado (ver Eventos). Con «Bloquear» la montura vuelve a 1. Una sola pasada por cambio, nunca un bucle, nunca añade nada.
Por defecto la app no envía ni una regla de CSS al carrito: qué controles enseña cada fila lo decide el comerciante en la incrustación de la app (Complementos en el carrito, Ocultar el botón de eliminar de los accesorios, Ocultar el selector de cantidad de las lentillas), y esas reglas solo alcanzan a las filas de un conjunto de Optical Form: la papelera de otras apps (garantías, seguros de envío) no se toca. Para afinar el aspecto: la casilla Compactar las líneas de complementos (cart_tidy_addons, desactivada por defecto) y el campo CSS personalizado. Ver Diseño y carrito y Temas probados.
Los selectores del carrito y el comportamiento tras añadir se ajustan en la incrustación de la app — 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, sellingPlanId (o null) |
opticalform:addtocart:error |
falló | kind |
opticalform:close |
se cierra | completed |
optical-form:cart:orphan |
en el carrito falta alguna línea del conjunto 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 la integración con el 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 la integración con el carrito 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 desactivar en la configuración de la integración con el carrito: 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.
Actualizaciones de pantalla
Sección titulada «Actualizaciones de pantalla»:step:rendered se emite cuando el paso cambia, no cada vez que se actualiza la pantalla: 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.