Convierte el menú AR en una campaña comercial con leads, WhatsApp, eventos y optimización.
Nivel avanzado
Campaña comercial WebAR para restaurante
En el intermedio creamos un menú dinámico: los productos salían de un array, se filtraban por categoría y se conectaban con MindAR. En el avanzado convertimos esa experiencia en una campaña comercial. Eso significa que ya no solo mostramos modelos 3D: también medimos acciones importantes y capturamos datos para que el restaurante pueda continuar la conversación por WhatsApp.
La meta es que el estudiante entienda cómo una experiencia WebAR puede servir para vender, reservar, pedir información o medir interés. Por eso vamos a agregar un formulario de lead, un contador de eventos, una función trackEvent() y mediciones en momentos clave: filtro seleccionado, producto seleccionado, marcador encontrado, marcador perdido y formulario enviado.
Proyecto final avanzado
Partimos del template intermedio final. No empezamos desde cero porque en proyectos reales se construye por capas. El avanzado toma el menú dinámico y le agrega una capa comercial: datos del cliente, analítica local y mensaje automático para WhatsApp.
Preparación
Crear la carpeta del proyecto avanzado
Crea una carpeta llamada campana-menu-ar. Copia dentro los archivos del proyecto intermedio: index.html, styles.css, app.js, assets, models y targets.
Desde dónde partimos
Las líneas de referencia salen del template intermedio final. En index.html, el panel flotante está entre las líneas 49 y 56. En styles.css, el botón de WhatsApp termina cerca de la línea 276 y el media query del panel empieza cerca de la línea 278. En app.js, los selectores están entre las líneas 1 y 19, los eventos de AR empiezan cerca de la línea 93 y el botón de WhatsApp está entre las líneas 113 y 118.
Si tus líneas no coinciden, busca los nombres exactos: ui-panel, .whatsapp-button, orderButton, targetFound y targetLost.
Reemplazar el botón de pedido por un formulario de lead
Abre index.html. En el intermedio, el panel flotante empieza aproximadamente en la línea 49 con <aside id="ui-panel" class="ui-panel"> y termina en la línea 56 con </aside>. Reemplaza todo ese bloque por esta versión.
Qué cambia y por qué
En el intermedio había un botón directo: btn-order. En el avanzado necesitamos capturar datos antes de abrir WhatsApp. Por eso quitamos el botón directo y agregamos lead-form. El formulario tiene dos campos: lead-name y lead-phone. Ambos tienen name porque JavaScript usará esos nombres con FormData.
event-counter muestra cuántos eventos hemos registrado. No es una base de datos real todavía, pero ayuda al estudiante a ver que cada acción se está midiendo.
Panel comercial con formularioindex.html
<aside id="ui-panel" class="ui-panel">
<div>
<h2 id="ui-title">Selecciona un producto</h2>
<strong id="ui-price">$0.00</strong>
</div>
<p id="ui-desc">Cuando MindAR detecte el marcador, el modelo aparecerá sobre la imagen.</p>
<form id="lead-form" class="lead-form">
<input id="lead-name" name="name" placeholder="Tu nombre" required />
<input id="lead-phone" name="phone" placeholder="WhatsApp" required />
<button class="whatsapp-button" type="submit">Enviar y pedir por WhatsApp</button>
</form>
<p id="event-counter">Eventos registrados: 0</p>
</aside>
CSS
Diseñar el formulario del panel AR
Abre styles.css. Busca .whatsapp-button, que en el intermedio termina aproximadamente en la línea 276. Pega este bloque justo después de .whatsapp-button y antes del media query que empieza con @media (min-width: 760px).
Explicación del CSS línea por línea
.lead-form agrupa los campos del formulario. display: grid los coloca uno debajo del otro, gap agrega separación y margin-top evita que el formulario quede pegado a la descripción del producto.
.lead-form input estiliza los campos. width: 100% los hace ocupar todo el ancho del panel, font: inherit usa la misma tipografía del proyecto y border-radius suaviza las esquinas. #event-counter es el texto pequeño donde JavaScript mostrará el número de eventos registrados.
Abre app.js. En el intermedio, los selectores están al inicio del archivo. Reemplaza const orderButton = document.querySelector("#btn-order");, cerca de la línea 8, por los selectores del formulario y contador.
Qué se agrega en app.js
leadForm apunta al formulario nuevo del HTML. Sin este selector no podríamos escuchar el envío del formulario. eventCounter apunta al texto donde mostraremos la cantidad de eventos. Luego, donde antes estaba let currentProduct = null;, dejamos esa línea y agregamos debajo const events = [];.
events será una lista local. Cada vez que ocurra algo importante, guardaremos un objeto dentro de esa lista. Este patrón prepara al estudiante para enviar esos eventos más adelante a GA4, Firebase o una API.
Agrega esta función después de closeMenu() y antes de renderMenu(). En el intermedio, closeMenu() termina aproximadamente en la línea 34 y renderMenu() comienza cerca de la línea 36.
Explicación línea por línea
Línea 1: la función recibe type, el nombre del evento, y data, un objeto con información extra. Líneas 2 a 6: creamos un objeto llamado event. Guardamos tipo, datos y fecha. new Date().toISOString() crea una fecha estándar fácil de guardar o enviar a un servidor.
Línea 8: agregamos el evento al array events. Línea 9: actualizamos el contador visible. Línea 10: mostramos el evento completo en consola para depurar. En una versión profesional, desde aquí podríamos enviar el evento a Google Analytics 4, Firebase o una base de datos.
Función trackEventapp.js
function trackEvent(type, data = {}) {
const event = {
type,
data,
createdAt: new Date().toISOString()
};
events.push(event);
eventCounter.textContent = "Eventos registrados: " + events.length;
console.log("Evento WebAR:", event);
}
Métricas
Medir filtros seleccionados
Busca el bloque filterButtons.forEach, que en el intermedio está aproximadamente entre las líneas 85 y 89. Reemplázalo por esta versión para medir cada vez que el usuario filtra el menú.
Qué mide este bloque
Primero volvemos a renderizar el menú con renderMenu(button.dataset.filter). Después registramos filter_selected y enviamos como dato la categoría elegida. Esto permite saber si los usuarios exploran más carne, pollo, vegetarianos o todos los productos.
button.dataset.filter lee el atributo data-filter del HTML. Es el puente entre el botón visible y el dato que guardamos en analítica.
Busca selectProduct(). En el intermedio empieza aproximadamente en la línea 63. Dentro de esa función, después de homeScreen.classList.add("hidden");, agrega este evento.
Por qué va en selectProduct()
Ese es el punto exacto donde ya sabemos qué producto eligió el usuario. Si medimos antes, podríamos no tener producto. Si medimos después de abrir WhatsApp, perderíamos usuarios que solo exploraron el modelo. product_selected mide intención temprana.
Guardamos productId y no solo el nombre porque los IDs son más estables. Si mañana cambias el nombre comercial del producto, el ID puede seguir igual y tus métricas siguen ordenadas.
Busca los eventos de MindAR al final de app.js. En el intermedio empiezan aproximadamente en la línea 93 con target.addEventListener("targetFound". Reemplaza esos dos eventos por esta versión.
Explicación de eventos AR
target_found confirma que el marcador fue reconocido. Es una de las métricas más importantes en WebAR porque nos dice si la experiencia realmente llegó a activarse. target_lost indica que el usuario dejó de apuntar al marcador o que el tracking perdió estabilidad.
Usamos currentProduct?.id con signo de pregunta porque puede ocurrir que el evento se dispare cuando todavía no hay producto activo. Esa sintaxis evita que JavaScript se rompa si currentProduct es null.
En el intermedio existía un evento de clic sobre orderButton, aproximadamente entre las líneas 113 y 118. Como ahora quitamos ese botón y agregamos un formulario, reemplaza ese bloque por leadForm.addEventListener("submit", ...).
Explicación línea por línea
Línea 1: escuchamos el envío del formulario. Línea 2: evitamos que el navegador recargue la página. Línea 3: si no hay producto seleccionado, detenemos el flujo.
Líneas 5 a 7:FormData lee los campos usando sus atributos name. Líneas 9 a 13: registramos el evento lead_submit con nombre, teléfono y producto. Líneas 16 y 17: construimos el mensaje y usamos encodeURIComponent() para convertirlo en una URL válida para WhatsApp.
El archivo .mind sigue yendo en targets/menu.mind. En una campaña para restaurante puedes usar un menú impreso, un individual de mesa, un flyer, un cartel de entrada, un póster de habitación de hotel o una señal turística.
Recomendación profesional
El marcador no debe ser una imagen lisa ni con muy pocos detalles. Usa alto contraste, texto, fotos, bordes y elementos visuales variados. Después de compilar, prueba en cámara real y verifica que targetFound se registre de forma estable en consola.
Compilador integrado
Crear el archivo .mind para el menú del restaurante
Sube la imagen del menú, flyer, mesa, poster o marcador de práctica. MindAR generará el archivo menu.mind que usaremos en el proyecto.
No hay imágenes seleccionadas.
Selecciona una imagen para comenzar.
Producción
Optimización, privacidad y publicación
Una campaña WebAR comercial debe funcionar bien en móvil, cargar rápido y ser clara con los datos que solicita. Aunque este proyecto guarda eventos solo en memoria y consola, ya estamos pidiendo nombre y teléfono, así que debemos explicar al usuario para qué se usan.
Checklist avanzado
Rendimiento: comprime modelos GLB, texturas y evita archivos enormes. Cámara: no la inicies hasta que el usuario elija un producto. HTTPS: en servidor real es obligatorio para cámara. Privacidad: si capturas nombre y WhatsApp, agrega aviso de uso de datos. Métricas: revisa en consola que se registren filter_selected, product_selected, target_found, target_lost y lead_submit.
Prueba en móvil real.
Publica con HTTPS.
Verifica que el marcador compile y rastree bien.
Confirma que WhatsApp abre con el mensaje correcto.
Revisa consola para validar eventos.
No pongas fondos opacos encima del canvas de cámara.
Código final
Proyecto completo avanzado
Este es el código completo del proyecto avanzado. Compara los tres archivos con el intermedio y observa que el avance principal está en el formulario, el contador de eventos y las llamadas a trackEvent().