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.
Cómo escuchar
Sección titulada «Cómo escuchar»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.
Los nueve eventos
Sección titulada «Los nueve eventos»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:successes 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 en cada repintado.
- 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 (
stepTypegraduation,reading_rxocontact_lens): «¿tienes tu receta a mano? Si no, sube una foto». Una vez por sesión, no en cada paso. - Preparar el final cuando
isLastestrue: 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 }], });});opticalform:addtocart:error — falló
Sección titulada «opticalform:addtocart:error — falló»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,kindyline: 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_outes la montura: el botón de la ficha ya debería decir «Agotado».
opticalform:close — se cierra
Sección titulada «opticalform:close — se cierra»Trae completed: true si terminó, false si se fue. Es toda la diferencia para recuperar un abandono.
- Abandono con contexto: con
completedafalsesabes 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
completedatruelo ú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 apagado de fábrica).
- Medir si sirve: cuántos
resumefrente a cuántosclosesincompleteddice si vale la pena mantenerlo encendido. 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 hija
Sección titulada «optical-form:cart:orphan — en el carrito falta una línea hija»Este no lo emite el configurador sino el puente del carrito, en document, cuando detecta una montura sin las lentes o tratamientos con los que salió: el cliente las borró desde el carrito del tema. Trae groupId, key, expected y actual. La app pinta un aviso traducido bajo la fila y no borra nada ni bloquea el checkout.
- Decidir tú: quitar la montura, avisar por chat, o dejarlo pasar si en tu tienda una montura se puede comprar sola. Es decisión del comerciante, no un fallo.
- Medirlo: cuántos borran las lentes dice si el precio del complemento sorprende en el carrito.
Tres recetas completas
Sección titulada «Tres recetas completas»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.
Lo que no puedes hacer con ellos
Sección titulada «Lo que no puedes hacer con ellos»- 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
detailes una lista cerrada.stepTypesí 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 repintados están en Integración con el tema.