La mayoría de los GWP se rompen por el mismo motivo: alguien escribe JavaScript que vigila el cart y borra el gift a mano cuando algo cambia. Funciona hasta que el cliente edita quantities, usa el drawer de una app o el evento no dispara. La forma sólida es no vigilar nada: atas el gift a su producto como un nested line item y dejas que Shopify lo borre con cascade delete.
El problema del GWP “a mano”
El patrón típico es un script que escucha cambios del cart, revisa si el gift
sigue mereciéndose y lo elimina si no. El detalle es que hay muchos caminos para
mutar un cart en Shopify: el add-to-cart del theme, el quick-add, el drawer de una
app de terceros, la página /cart, un update de quantity. Si tu lógica de borrado
no cubre todos, tarde o temprano el gift se queda pegado en un cart donde ya no
corresponde. Y perseguir cada evento es una pelea que no ganas.
La idea: un child line con parent_line_key
/cart/add.js acepta un campo poco conocido: parent_line_key. Cuando lo pasas,
la línea que agregas queda anidada bajo otra, como child line. Es el mismo
mecanismo que usan los bundles. Y lo importante: cuando el cliente elimina la
parent line, Shopify hace cascade delete de la child, sin que tú escribas nada
para ese borrado.
// Al agregar el gift, lo cuelgas de la parent line (el producto que lo dispara).
await fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
body: JSON.stringify({
items: [{
id: giftVariantId,
quantity: parentLine.quantity,
parent_line_key: parentLine.key, // <- esto lo vuelve un nested line item
properties: { _gift: '1', _gift_for: String(parentLine.variant_id) },
}],
}),
});
parent_line_key apunta a la key de la parent line. Con eso el gift pasa a ser
su child. Si el cliente quita el producto, el gift se va con el cascade delete.
El precio en 0 no lo pone el JavaScript
El gift cuesta 0 por un automatic discount de Shopify que apunta a esa variant, no por algo que hagas en el front. Esto importa: el precio vive en Shopify, no en el navegador. Nunca toques precios en el cliente, se pueden manipular y rompen la integridad del checkout.
Lo que sí tienes que sincronizar
El cascade delete te lo regala Shopify, pero el resto es tuyo. En cada mutación del
cart reevalúas: por cada producto que califica y no tiene su gift pareado, lo
agregas; si cambió la quantity de la parent line, ajustas la del gift. Pareas cada
gift con su parent por una property (_gift_for = variant id de la parent line).
const cart = await fetch('/cart.js').then((r) => r.json());
const parents = cart.items.filter((i) => qualifies(i) && i.variant_id !== giftVariantId);
const gifts = cart.items.filter((i) => i.properties?._gift === '1');
// agregas el gift que falte y ajustas quantity; el borrado lo hace el cascade delete.
Los detalles que evitan que se te caiga
Esto es lo que separa una demo de algo que aguanta producción:
- Reentrancy guard. Una bandera
inFlightpara que una mutación a mitad de un add no dispare otra evaluación encima. - Detectar mutaciones. Puedes parchar
window.fetchpara reaccionar a/cart/(add|change|update|clear).js, pero los drawers de apps de terceros a veces esquivan ese fetch: suscríbete también a los eventos de tu drawer. - Kill switch. Si un add falla varias veces, apaga la feature por el resto del page load. Un add rechazado que se reintenta en loop te lleva directo a rate limiting (429).
- Cachea la lista de productos que califican en
sessionStoragepara no pedirla en cada evaluación.
La regla que sigo
Cuando una relación entre líneas del cart la puede manejar Shopify, deja que la maneje Shopify. Tu JavaScript debería agregar y sincronizar, no vigilar y borrar. El cascade delete de los nested line items es nativo, gratis y no se te cae.
Preguntas frecuentes
¿Qué es parent_line_key en Shopify?
Es un campo de /cart/add.js que ata un line item a otro como child. Shopify trata el nuevo como nested line item bajo su parent line, y le hace cascade delete cuando quitas la parent line.
¿Cómo hago que el gift cueste 0?
Con un automatic discount de Shopify que apunte a la variant del gift. El precio vive en Shopify, nunca lo pongas en el cliente.
¿Necesito una app para un GWP con cascade delete?
No. Con parent_line_key y un automatic discount lo haces en el theme. El JavaScript solo agrega el gift y sincroniza la quantity; el borrado lo hace Shopify.