Saltar al contenido principal

Recipe: Cart bridge custom

El widget escucha el evento neuroon:cart-update en window. Cuando lo recibe, refresca sus respuestas: cross-sell, validaciones de "ya en el carrito", prompts contextuales.

Si tu storefront no usa WooCommerce ni un plugin oficial, emite tú el evento desde el código que muta el carrito.

Schema del payload

type CartItem = {
productId: string; // tu externalId / SKU (debe coincidir con el sincronizado en Neuroon)
name: string;
price: number;
quantity: number;
imageUrl?: string;
variantId?: string;
attributes?: Record<string, string>;
};

type CartPayload = {
items: CartItem[];
total: number;
currency: string; // ISO 4217
itemsCount?: number; // opcional, deducible de items
};

Si tu carrito tiene cálculos sensibles (ofertas, tax) que prefieres no exponer al frontend, dispatcha el evento sin detail. El widget pedirá al servidor el estado actualizado.

Modo "ping" (sin payload)

window.dispatchEvent(new CustomEvent('neuroon:cart-update'));

Es el patrón más simple y privado: el widget consultará al backend.

Modo "completo" (con payload)

window.dispatchEvent(new CustomEvent('neuroon:cart-update', {
detail: {
items: [
{ productId: 'SKU-001', name: 'Smart Speaker', price: 79.99, quantity: 2 }
],
total: 159.98,
currency: 'EUR'
}
}));

Ventaja: el widget no necesita una segunda llamada al servidor.

Vanilla JS

<script>
(function () {
const debounce = (fn, ms) => {
let t;
return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); };
};

const sendCartUpdate = debounce(() => {
const cart = window.__cart; // tu store global
window.dispatchEvent(new CustomEvent('neuroon:cart-update', {
detail: {
items: cart.lines.map(l => ({
productId: l.sku,
name: l.name,
price: l.price,
quantity: l.quantity,
imageUrl: l.imageUrl,
})),
total: cart.total,
currency: cart.currency,
}
}));
}, 200);

document.addEventListener('cart:added', sendCartUpdate);
document.addEventListener('cart:removed', sendCartUpdate);
document.addEventListener('cart:cleared', sendCartUpdate);
})();
</script>

Debounce 200 ms replica lo que hace el plugin oficial (ver ).

jQuery (legacy stores)

(function ($) {
if (!$) return;

var debounceTimer = null;

$(document.body).on('myStore:cart-changed', function () {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(function () {
window.dispatchEvent(new CustomEvent('neuroon:cart-update'));
}, 200);
});
})(window.jQuery);

React / Next.js

Con un store cliente (Zustand, Redux, Context):

// app/components/CartBridge.tsx
'use client';
import { useEffect } from 'react';
import { useCartStore } from '@/store/cart';

export function CartBridge() {
const cart = useCartStore((s) => s.snapshot()); // selector estable

useEffect(() => {
const t = setTimeout(() => {
window.dispatchEvent(new CustomEvent('neuroon:cart-update', {
detail: {
items: cart.lines.map((l) => ({
productId: l.sku, name: l.name, price: l.price, quantity: l.quantity,
})),
total: cart.total,
currency: cart.currency,
},
}));
}, 200);
return () => clearTimeout(t);
}, [cart]);

return null;
}

Renderiza <CartBridge /> en app/layout.tsx o _app.tsx.

Vue 3 / Nuxt 3

Con Pinia:

// composables/useNeuroonCartBridge.ts
import { storeToRefs } from 'pinia';
import { useCartStore } from '~/stores/cart';

export function useNeuroonCartBridge() {
if (process.server) return;
const cart = storeToRefs(useCartStore());
let timer: ReturnType<typeof setTimeout> | null = null;

watch(
() => cart.snapshot.value,
(s) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
window.dispatchEvent(new CustomEvent('neuroon:cart-update', { detail: s }));
}, 200);
},
{ deep: true },
);
}

Y desde app.vue:

<script setup lang="ts">
useNeuroonCartBridge();
</script>

Validar que el bridge funciona

// En la consola del navegador
window.addEventListener('neuroon:cart-update', (e) => console.log('cart-update', e.detail));
// Añade un producto al carrito

Buenas prácticas

  • Debounce 200 ms. Carritos con descuentos disparan varios eventos en cadena; sin debounce el widget se sobrecarga.
  • productId coincidente con el externalId que enviaste en /products/sync. Si difieren, el widget no podrá deduplicar contra el carrito.
  • No emitas datos personales en detail (email, nombre, dirección). El payload solo debe contener identificadores de producto y agregados.
  • Una sola fuente de verdad: si ya usas el plugin oficial WordPress, no añadas un dispatch manual; el plugin ya cubre el 95 % de los casos.

Errores frecuentes

SíntomaCausaSolución
El widget ignora el eventoListener registrado antes de cargar el widgetAsegúrate de que el widget está cargado (ver evento neuroon:ready).
Llamadas duplicadasSin debounceAplica setTimeout 200 ms y clearTimeout al recibir nuevos eventos.
productId no encontradoSKU diferente al sincronizadoReconcilia: ambos endpoints deben usar el mismo identificador.

Próximos pasos