> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Eventos

> Todos los eventos del Cart SDK de Aftersell: cuándo se dispara cada uno, qué te entrega, para qué usarlo y los errores que causan bucles infinitos.

Los eventos te permiten ejecutar código **cuando algo sucede** en el carrito. Viven bajo `window.aftersell.cart.events`.

Suscribirse es una llamada de configuración, así que es seguro al principio de tu script, sin necesidad de esperar a `ready()`.

<div id="available-events">
  ## Eventos disponibles
</div>

| Evento                                        | Payload                                               | Se dispara cuando                                             |
| --------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------- |
| [`cart_loaded`](#cart_loaded)                 | [`AftersellCart`](/es/aftersell/cart/sdk-cart-object) | El carrito se carga, una vez por página.                      |
| [`cart_updated`](#cart_updated)               | [`AftersellCart`](/es/aftersell/cart/sdk-cart-object) | El contenido del carrito cambia, después de la primera carga. |
| [`item_added`](#item_added)                   | `{ item }`                                            | Una nueva línea aparece en el carrito.                        |
| [`item_removed`](#item_removed)               | `{ item }`                                            | Una línea desaparece del carrito.                             |
| [`cart_opened`](#cart_opened-and-cart_closed) | Ninguno                                               | El drawer se abre.                                            |
| [`cart_closed`](#cart_opened-and-cart_closed) | Ninguno                                               | El drawer se cierra.                                          |
| [`checkout`](#checkout)                       | Ninguno                                               | Se hace clic en el botón de checkout.                         |

<div id="subscribing">
  ## Suscripción
</div>

`events.on(event, handler)` registra un handler y **devuelve una función que cancela su suscripción**:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
const off = window.aftersell.cart.events.on('cart_updated', (state) => {
  console.log('Cart total is now', state.totalPrice);
});

// later, to stop listening:
off();
```

* `events.once(event, handler)`: se dispara una vez y luego cancela su propia suscripción.
* `events.off(event, handler)`: elimina un handler específico.

Un handler que lanza una excepción queda aislado y se registra en la consola; los demás handlers siguen ejecutándose.

***

<div id="the-two-rules">
  ## Las dos reglas
</div>

Casi todos los bugs de eventos se remontan a una de estas.

<div id="dont-change-the-cart-from-cart_updated-without-a-guard">
  ### No cambies el carrito desde `cart_updated` sin una protección
</div>

Cambiar el carrito dentro de un handler de `cart_updated` dispara `cart_updated` de nuevo. Si ese handler cambia el carrito otra vez, tienes un bucle infinito. El comprador ve su carrito agitarse mientras la página martillea a Shopify.

<Warning>
  **Nunca llames a una acción incondicionalmente desde `cart_updated` o `cart_loaded`.** Protégela con una verificación del estado que estás a punto de crear, para que la segunda pasada no haga nada.
</Warning>

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// ❌ Loops forever: every add triggers an update, which triggers another add.
window.aftersell.cart.events.on('cart_updated', (state) => {
  window.aftersell.cart.actions.addItem(GIFT_VARIANT_ID, 1);
});

// ✅ Guarded: once the gift is present, the condition is false and it stops.
window.aftersell.cart.events.on('cart_updated', (state) => {
  const hasGift = state.items.some((line) => line.variantId === GIFT_VARIANT_ID);
  if (state.totalPrice >= 5000 && !hasGift) {
    window.aftersell.cart.actions.addItem(GIFT_VARIANT_ID, 1);
  }
});
```

El carrito sí te da una red de seguridad: una actualización que produce un carrito **idéntico** no emite nada, así que un refetch que no cambia nada no reiniciará el ciclo. Eso te protege de bucles accidentales sin efecto. **No** te protege de un handler que genuinamente cambia el carrito cada vez.

<div id="treat-the-payload-as-read-only">
  ### Trata el payload como de solo lectura
</div>

Todos los handlers de un evento reciben el *mismo* objeto. Mutarlo cambia lo que ven los handlers posteriores al tuyo, incluidos los handlers que pertenecen a otras apps de la tienda.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// ❌ Corrupts the payload for every later handler.
window.aftersell.cart.events.on('cart_updated', (state) => {
  state.items = state.items.filter((line) => line.finalLinePrice > 0);
});

// ✅ Copy first.
window.aftersell.cart.events.on('cart_updated', (state) => {
  const paidItems = state.items.filter((line) => line.finalLinePrice > 0);
});
```

Para cambiar realmente el carrito, usa una [acción](/es/aftersell/cart/sdk-actions). Para cambiar cómo se renderizan las líneas, usa [`registerLineTransform`](/es/aftersell/cart/sdk-hooks#registerlinetransform).

***

<div id="cart_loaded">
  ## cart\_loaded
</div>

Se dispara **una vez**, cuando el carrito se carga por primera vez en la página. El payload es el [objeto cart](/es/aftersell/cart/sdk-cart-object) completo.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('cart_loaded', (state) => {
  console.log('Page loaded with', state.itemCount, 'items');
});
```

**Úsalo para:** cualquier cosa que necesite ejecutarse contra el estado inicial del carrito, como reconciliar un regalo gratis, inicializar un widget o reportar el contenido del carrito a analíticas al cargar la página.

**`cart_loaded` se reproduce para los suscriptores tardíos.** Si te suscribes después de que el carrito ya se cargó, tu handler es llamado inmediatamente con el carrito actual. El orden de suscripción nunca importa, así que no tienes que preocuparte de si tu script le ganó al carrito.

<Tip>
  La lógica que tiene que ser correcta tanto al cargar la página como en cada cambio posterior debe suscribirse a **ambos** `cart_loaded` y `cart_updated` con la misma función. Ese es el patrón estándar para "mantener X sincronizado con el carrito".
</Tip>

<div id="cart_updated">
  ## cart\_updated
</div>

Se dispara cada vez que el contenido del carrito cambia **después** de la primera carga, ya sea desde el drawer, desde tus propias acciones, desde el tema o desde otra app. El payload es el [objeto cart](/es/aftersell/cart/sdk-cart-object) completo.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('cart_updated', (state) => {
  document.querySelector('#my-total').textContent =
    window.aftersell.cart.actions.formatMoney(state.totalPrice);
});
```

**Úsalo para:** mantener sincronizado algo fuera del carrito, como un total personalizado, una barra de progreso, una insignia en el encabezado o un evento de analíticas en cada cambio.

Una actualización que produce un carrito idéntico no emite nada. Volver a abrir el drawer, regresar de otra pestaña o un refetch que devuelve el mismo contenido no lo disparará.

<Warning>
  Vuelve a leer [las dos reglas](#the-two-rules) antes de llamar a una acción aquí dentro.
</Warning>

<div id="item_added">
  ## item\_added
</div>

Se dispara cuando una **nueva línea** aparece en el carrito. El payload es `{ item }`, donde `item` es la [línea del carrito](/es/aftersell/cart/sdk-cart-object#cart-lines).

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('item_added', (payload) => {
  myAnalytics.track('Added to cart', {
    id: payload.item.variantId,
    title: payload.item.title,
    quantity: payload.item.quantity,
  });
});
```

**Úsalo para:** el seguimiento de agregar al carrito en una herramienta de analíticas de terceros. Este es el uso más común del SDK. Consulta [seguimiento de agregar al carrito](/es/aftersell/cart/sdk-use-case-analytics).

Dos cosas que debes saber sobre cómo se deriva:

<Warning>
  **Un cambio de cantidad no es un agregado.** El carrito determina los agregados y las eliminaciones comparando *líneas*, no cantidades. Un comprador que sube una línea de 1 a 3 dispara `cart_updated`, no `item_added`. Si necesitas capturar también los aumentos de cantidad, compara contra el estado anterior en un handler de `cart_updated`.
</Warning>

Tampoco se dispara para artículos que ya estaban en el carrito cuando la página se cargó; esos llegan vía `cart_loaded`. Agregar varios productos distintos a la vez dispara el evento una vez por línea.

<div id="item_removed">
  ## item\_removed
</div>

Se dispara cuando una línea desaparece del carrito. El payload es `{ item }`, la línea tal como estaba justo antes de desaparecer, así que aún puedes leer su `key`, `variantId` y `title`.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('item_removed', (payload) => {
  console.log('Removed', payload.item.title);
});
```

**Úsalo para:** revertir algo que hiciste al agregar, como limpiar un flag, volver a mostrar una oferta que el comprador rechazó o reportar eliminaciones a analíticas.

La misma salvedad que `item_added`: bajar una cantidad sin llegar a cero no es una eliminación.

<div id="cart_opened-and-cart_closed">
  ## cart\_opened y cart\_closed
</div>

Se disparan cuando el drawer se abre y se cierra. Sin payload.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('cart_opened', () => {
  myAnalytics.track('Cart viewed');
});

window.aftersell.cart.events.on('cart_closed', () => {
  document.body.classList.remove('cart-is-open');
});
```

**Úsalos para:** seguimiento de vistas, pausar un video o carrusel detrás del drawer, alternar una clase en la página.

Ninguno se dispara en la carga inicial de la página, solo en una apertura o cierre real.

<div id="checkout">
  ## checkout
</div>

Se dispara cuando el comprador hace clic en el botón de checkout, inmediatamente antes de que el navegador navegue. Sin payload.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('checkout', () => {
  myAnalytics.track('Checkout started');
});
```

**Úsalo para:** seguimiento de intención de checkout.

<Warning>
  **No puedes cancelar el checkout desde este handler.** El evento es una notificación, no una compuerta; la navegación ocurre sin importar lo que haga tu código. Mantén el handler rápido y síncrono: un `await` o una llamada de red lenta pueden no terminar antes de que la página se descargue. Usa [`navigator.sendBeacon`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon) para cualquier cosa que necesites enviar de forma confiable.
</Warning>

***

<div id="listening-from-outside-the-sdk">
  ## Escuchar desde fuera del SDK
</div>

Cada evento también se despacha como un `CustomEvent` del DOM en `window`, así que puedes escuchar sin tocar `window.aftersell.cart`. Eso es útil desde un archivo del tema, una app de terceros o un script que se carga independientemente del carrito.

| Evento del bus | Evento del DOM                |
| -------------- | ----------------------------- |
| `cart_loaded`  | `aftersell:cart:cart-loaded`  |
| `cart_updated` | `aftersell:cart:cart-updated` |
| `item_added`   | `aftersell:cart:item-added`   |
| `item_removed` | `aftersell:cart:item-removed` |
| `cart_opened`  | `aftersell:cart:cart-opened`  |
| `cart_closed`  | `aftersell:cart:cart-closed`  |
| `checkout`     | `aftersell:cart:checkout`     |

Ten cuidado con la nomenclatura: el bus usa `snake_case`, los eventos del DOM usan `kebab-case` detrás de un prefijo `aftersell:cart:`.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.addEventListener('aftersell:cart:cart-updated', (event) => {
  console.log('Cart total is now', event.detail.totalPrice);
});
```

El payload llega en `event.detail` y coincide con el [objeto cart](/es/aftersell/cart/sdk-cart-object). Los eventos se despachan en `window`, así que un listener en cualquier parte de la página los recibe. El carrito se renderiza en un shadow root, pero el límite del shadow nunca está en la ruta del evento. Cada despacho clona el payload, así que un listener que muta `event.detail` no puede afectar a nadie más, y un listener que lanza una excepción no puede interrumpir el SDK.

<Warning>
  **`cart-loaded` no se reproduce en el DOM.** El bus reproduce `cart_loaded` para los suscriptores tardíos, pero esa ruta omite el despacho al DOM, así que un `window.addEventListener('aftersell:cart:cart-loaded')` registrado después de que el carrito ya se cargó nunca se disparará. Si el orden de carga de tu script no está garantizado, usa `window.aftersell.cart.events.on('cart_loaded', …)`, que sí se reproduce, o escucha también `aftersell:cart:cart-updated`.
</Warning>

<div id="shopify-standard-cart-events">
  ### Eventos estándar de carrito de Shopify
</div>

Por separado, el carrito publica los [eventos estándar de carrito](https://shopify.dev/docs/storefronts/themes/best-practices/standard-events) de Shopify en `document` cada vez que cambia el carrito, para que el código del tema y otras apps puedan reaccionar a las mutaciones de Aftersell de la misma forma que reaccionan a las del tema:

| Evento                         | Payload en la instancia del evento                                               |
| ------------------------------ | -------------------------------------------------------------------------------- |
| `shopify:cart:lines-update`    | `action: 'add' \| 'update' \| 'remove'`, `context: 'cart' \| 'product'`, `lines` |
| `shopify:cart:note-update`     | `context: 'cart'`, `note`                                                        |
| `shopify:cart:discount-update` | `discountCodes: [{ code }]`                                                      |

<Warning>
  **El payload no está en `event.detail`.** `detail` solo lleva `{ source: 'aftersell' }`, la etiqueta que el carrito usa para ignorar sus propios eventos en lugar de entrar en bucle. Todo lo de la tabla de arriba se asigna directamente al objeto del evento, así que lee `event.action`, no `event.detail.action`.
</Warning>

Cada evento también lleva una `promise` que Aftersell resuelve cuando la escritura subyacente se completa, siguiendo el estándar de Shopify: espérala con await, no la resuelvas tú. Estos se despachan en `document` y burbujean, así que un listener en `window` también los recibe.

<div id="where-to-go-next">
  ## Adónde ir después
</div>

* **[Objeto cart](/es/aftersell/cart/sdk-cart-object)**: la forma completa de los payloads de arriba.
* **[Acciones](/es/aftersell/cart/sdk-actions)**: cómo cambiar el carrito desde un handler.
* **[Hooks](/es/aftersell/cart/sdk-hooks)**: para cambiar cómo se renderiza el carrito, en lugar de reaccionar a él.
* **[Casos de uso](/es/aftersell/cart/sdk-use-cases)**: seguimiento de analíticas, regalos gratis y otros ejemplos completos.
