Ir al contenido

Qué hacer con cada evento

El configurador cuenta lo que hace el comprador mediante eventos del navegador. No hay que activar nada: se emiten siempre, y si nadie escucha no pasa nada. Esta página va evento por evento con ideas concretas; el contrato técnico (campos comunes, versionado, lo que nunca llevan) está en Integración con el tema.

Se emiten sobre <optical-form-drawer> y burbujean, así que basta con escuchar en document. Todos llevan detail con el mismo contexto: sessionId (cose una visita entera), productId, variantId, stepIndex, stepId, stepType y totalSteps.

document.addEventListener('opticalform:open', (e) => {
console.log('configurador abierto en el producto', e.detail.productId);
});

Dos límites que conviene tener claros antes de construir: los eventos no llevan las respuestas del comprador (ni graduación, ni fichero, ni textos) y no se pueden cancelar. Dicen dónde está, no qué ha contestado, y no dejan intervenir.

opticalform:open — se abre el configurador

Sección titulada «opticalform:open — se abre el configurador»

Es el primer momento en que sabes que esa persona quiere montar unas gafas, no solo mirar la montura.

  • Cargar el chat solo ahora. Si tu chat se carga en diferido por privacidad, este evento es el buen momento: quien mira sin tocar no deja cookies de terceros.
  • Marcar la sesión en el chat o en la analítica: «está configurando gafas» vale más que «vio un producto».
  • Arrancar un reloj: el tiempo desde aquí hasta :addtocart:success es tu tiempo de configuración real.
document.addEventListener('opticalform:open', () => {
if (!window.miChatCargado) cargarMiChat(); // tu función, tu proveedor
});

opticalform:step:rendered — el comprador llega a un paso

Sección titulada «opticalform:step:rendered — el comprador llega a un paso»

Trae además isFirst e isLast. Se emite cuando el paso cambia, no cada vez que se actualiza la pantalla.

  • Embudo por paso en tu analítica: sabrás en qué paso se queda la gente, que es la pregunta que decide qué arreglar.
  • Mensaje contextual del chat al llegar a la graduación (stepType graduation, reading_rx o contact_lens): «¿tienes tu receta a mano? Si no, sube una foto». Una vez por sesión, no en cada paso.
  • Preparar el final cuando isLast es true: por ejemplo, avisar de que el envío es gratis.
document.addEventListener('opticalform:step:rendered', (e) => {
dataLayer.push({ event: 'lens_step', step: e.detail.stepType, index: e.detail.stepIndex });
});

opticalform:validation:error — un paso no le deja avanzar

Sección titulada «opticalform:validation:error — un paso no le deja avanzar»

No lleva el valor que falló, solo el paso.

  • Contar en qué paso tropieza la gente. Si un paso concentra los errores, el problema es el paso: su texto de ayuda, su ejemplo, su obligatoriedad.
  • Ofrecer ayuda tras varios intentos: al segundo o tercer error en el mismo paso, el chat puede abrirse con una pregunta directa.

opticalform:addtocart:start — empieza el añadido

Sección titulada «opticalform:addtocart:start — empieza el añadido»

Entre este evento y el siguiente el configurador está hablando con el carrito de Shopify.

  • Esconder lo que estorbe durante el añadido, por ejemplo un lanzador de chat que flota sobre el botón.
  • Medir la latencia hasta :addtocart:success: si sube, algo ha cambiado en el tema o en una app del carrito.

opticalform:addtocart:success — el carrito lo aceptó

Sección titulada «opticalform:addtocart:success — el carrito lo aceptó»

Trae lineCount, totalPrice (en céntimos) y currency. Es tu evento de conversión.

  • Enviar la conversión a GA4, Meta o TikTok con su valor. Antes de este momento el precio no viaja a propósito; después ya está en /cart.js.
  • Recomendar el complemento que no eligió: un estuche, un segundo par, un tratamiento.
  • Explicar el carrito a quien lo ve por primera vez: la montura y cada lente o tratamiento llegan como líneas separadas, con su precio. Es lo que recibe tu laboratorio.
document.addEventListener('opticalform:addtocart:success', (e) => {
gtag('event', 'add_to_cart', {
value: e.detail.totalPrice / 100,
currency: e.detail.currency,
items: [{ item_id: String(e.detail.productId), quantity: e.detail.lineCount }],
});
});

Trae kind — la causa — y line — qué línea falló. kind es uno de sold_out (la montura está agotada), addon_unavailable (un tratamiento, color o accesorio no se puede vender), limit (el carrito ya tiene todo el stock), unavailable (algo ya no existe), throttled (Shopify pidió esperar) o unknown; line es frame, addon o unknown. Sale también cuando el panel se niega a enviar sin llamar a Shopify, porque ya sabía que la montura o un complemento estaban agotados.

  • Avisar a soporte con sessionId, productId, kind y line: llegas antes de que el comprador escriba.
  • Reintentar solo si es throttled; los demás no se arreglan repitiendo.
  • Vigilar addon_unavailable: cada uno es una variante de lente, color o accesorio que no se puede vender mientras la gente la elegía — el editor te lo enseña con Comprobar precios. sold_out es la montura: el botón de la página de producto ya debería decir «Agotado».

Trae completed: true si terminó, false si se fue. Es toda la diferencia para recuperar un abandono.

  • Abandono con contexto: con completed a false sabes en qué paso estaba (stepType, stepIndex) y en qué producto. Un mensaje del chat, un aviso de «te guardamos la configuración» o un formulario de correo valen más aquí que en cualquier popup de salida.
  • No molestar a quien terminó: con completed a true lo único que toca es dejarle ir al carrito.
document.addEventListener('opticalform:close', (e) => {
if (!e.detail.completed) miChat.mensaje('¿Te has quedado con dudas? Escríbenos aquí.');
});

opticalform:resume — el comprador retoma una configuración a medias

Sección titulada «opticalform:resume — el comprador retoma una configuración a medias»

Si cerró el configurador sin terminar y vuelve al mismo producto en 7 días, la app le ofrece «Continuar donde lo dejaste». Este evento sale cuando lo pulsa. Trae restoredSteps: cuántos pasos han recuperado su respuesta. Como todos, no dice qué respondió. Y solo sale si el comerciante ha activado «Recordar la configuración a medias» en Diseño › Navegación (viene desactivado por defecto).

  • Medir si sirve: cuántos resume frente a cuántos close sin completed dice si vale la pena mantenerlo activado. La pestaña Analítica ya te lo cuenta («N retomaron una configuración a medias»).
  • Cambiar el tono del chat: quien retoma ya decidió la mitad; un «¿te ayudo con la graduación?» encaja mejor que un «¿empezamos?».
document.addEventListener('opticalform:resume', (e) => {
miAnalitica.evento('configurador_retomado', { pasos: e.detail.restoredSteps });
});

optical-form:cart:orphan — en el carrito falta una línea del conjunto

Sección titulada «optical-form:cart:orphan — en el carrito falta una línea del conjunto»

Este no lo emite el configurador sino la integración con el carrito, en document, cuando detecta una montura sin las lentes, tratamientos o colores con los que salió: el cliente los borró desde el carrito del tema (un accesorio borrado no cuenta). Trae groupId, key, expected, actual y action: 'removed' cuando la app ha quitado el conjunto entero —lo que hace por defecto desde octubre de 2026; lleva además lines, las claves de las líneas que puso a 0— o 'notice' cuando elegiste «Conservar la montura y avisar» en la incrustación: entonces la app muestra un aviso traducido bajo la fila y no borra nada ni bloquea el checkout.

  • Decidir tú (con «Conservar la montura y avisar»): quitar la montura, avisar por chat, o dejarlo pasar si en tu tienda una montura se puede comprar sola.
  • Medirlo: cuántos borran las lentes dice si el precio del complemento sorprende en el carrito.

optical-form:cart:resync — la integración con el carrito ha cuadrado un conjunto

Sección titulada «optical-form:cart:resync — la integración con el carrito ha cuadrado un conjunto»

También de la integración con el carrito, en document, cada vez que corrige las cantidades de un conjunto tras un cambio del cliente en el carrito del tema: la montura subió y los complementos se han multiplicado con ella («Escalar»), o la montura ha vuelto a su base («Bloquear», o una lentilla). Trae groupId, mainKey, quantity (la cantidad de la montura tras el ajuste), base, sets, mode ('scale' o 'lock') y changed (una lista de { key, from, to } con cada línea tocada). Puede llevar reason: 'form-quantity' cuando lo que ha vuelto es una lentilla a sus cajas, 'unknown-ratio' cuando la integración con el carrito no sabe la proporción del conjunto y no toca nada, y 'capped' cuando Shopify no aceptó la cantidad pedida (sin stock) y el conjunto queda marcado con ofd-bundle-mismatch.

  • Enseñar el ajuste a tu manera: la app muestra un aviso traducido bajo la línea; con este evento puedes sustituirlo por el tuyo.
  • Medirlo: cuántos suben la montura desde el carrito, y cuántas veces Shopify recorta por stock.

Embudo en Google Analytics 4. Escucha :open, :step:rendered, :validation:error, :addtocart:success y :close, y manda cada uno como evento con stepType e stepIndex. En GA4, un informe de embudo por stepIndex te enseña la caída paso a paso; sessionId como dimensión personalizada une la visita entera.

Chat que acompaña. Carga tu proveedor en :open, guarda en sessionStorage qué mensajes ya has enseñado para no repetirlos, y lanza como mucho uno por hito: llegar a la graduación, añadir al carrito, cerrar sin terminar. Si tu chat vive abajo a la derecha, dale sitio con el ajuste Espacio libre a la derecha del pie (Diseño › Navegación) para que no tape el botón de continuar: ver Diseño y carrito.

Recuperación de abandono. En :close con completed a false, guarda productId y stepIndex; si tienes el correo del cliente (porque ha iniciado sesión o lo pide tu popup), tu herramienta de correo puede enviarle un «sigue donde lo dejaste» con el enlace al producto. La app no guarda la configuración a medias: lo que recuperas es la intención, no las respuestas.

  • Cancelar un paso, un añadido o un cierre: preventDefault() no hace nada, a propósito.
  • Leer la graduación, el fichero subido o los textos del comerciante: el detail es una lista cerrada. stepType sí viaja, y es un dato de salud inferido; trátalo como tal.
  • Cambiar lo que valida un paso: eso se configura en el formulario, no en un script.

Los detalles de versionado y de actualizaciones de pantalla están en Integración con el tema.