> ## 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.

# Hooks

> Cambia cómo se comporta el Aftersell Cart: transforma líneas, enriquécelas con datos de la Storefront, da forma a las opciones de suscripción y controla el agregar al carrito.

Mientras que los [eventos](/es/aftersell/cart/sdk-events) te permiten *reaccionar* al carrito y las [acciones](/es/aftersell/cart/sdk-actions) te permiten *cambiarlo*, los **hooks** cambian cómo se comporta el carrito en sí: cómo se renderizan las líneas, qué datos llevan y qué sucede al agregar al carrito.

Los hooks viven bajo `window.aftersell.cart.hooks`.

<Note>
  Un hook cambia lo que el comprador **ve**; una acción cambia lo que hay **en su carrito**. Ocultar una línea de regalo gratis con una transformación la deja en el carrito y en el total. Eliminarla con [`removeItem`](/es/aftersell/cart/sdk-actions#removeitemkey) la saca de verdad.
</Note>

<Note>
  Los hooks son llamadas de configuración, así que es seguro registrarlos al principio de tu script, sin necesidad de esperar a `ready()`. Regístralos en el script de **Initialization** de tu carrito (consulta [Scripts personalizados](/es/aftersell/cart/custom-scripts)).
</Note>

<div id="how-registration-works">
  ## Cómo funciona el registro
</div>

Cada hook es un método `register*`. Lo llamas con tu función; devuelve una **función de anulación de registro** que puedes llamar para eliminar la tuya.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
const off = window.aftersell.cart.hooks.registerSkipAddToCartRule((form) =>
  form.hasAttribute('data-skip-aftersell')
);

// later: off();
```

El registro es **aditivo**, así que tu función se ejecuta junto a todas las demás. Eso importa porque tu script rara vez es el único en la página: una app de suscripciones, una app de bundles y el propio tema pueden registrarse todos en el mismo hook. Ninguno puede reemplazar el tuyo, y nada de lo que registres puede ser descartado silenciosamente por lo que se cargue después de ti.

| Hook                                                                                      | Qué hace                                                                                                                                           | Con varios registros                                             |
| ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| [`registerLineTransform`](#registerlinetransform)                                         | Oculta o reetiqueta líneas individuales.                                                                                                           | Todos se ejecutan, en orden de registro.                         |
| [`registerLineComparator`](#registerlinecomparator)                                       | Reordena las líneas renderizadas.                                                                                                                  | Se componen como criterios de desempate.                         |
| [`registerCartEnricher`](#registercartenricher)                                           | Adjunta datos extra de la Storefront a cada línea.                                                                                                 | Todos se ejecutan; cada `id` es su propio namespace.             |
| [`registerSubscriptionOptionsTransform`](#registersubscriptionoptionstransform)           | Oculta o renombra los planes de venta de una línea.                                                                                                | Todos se ejecutan; los parches se fusionan por plan y por campo. |
| [`registerDefaultSubscriptionOptionSelector`](#registerdefaultsubscriptionoptionselector) | Elige qué plan está preseleccionado.                                                                                                               | La primera respuesta no `null` gana.                             |
| [`registerSkipAddToCartRule`](#registerskipaddtocartrule)                                 | Permite que formularios específicos omitan el carrito. Consulta [Intercepción de agregar al carrito](/es/aftersell/cart/add-to-cart-interception). | Cualquier regla que devuelva `true` omite.                       |

Un hook que lanza una excepción, o que no es una función, se omite; el resto sigue ejecutándose y el carrito continúa. Una integración rota no puede tumbar el agregar al carrito, el selector de suscripciones ni el ordenamiento.

La contraparte es que un hook roto tuyo falla **silenciosamente**: nada llega a la consola del navegador. Consulta [Depuración](/es/aftersell/cart/sdk-overview#debugging) para ver dónde sí afloran esos fallos.

***

<div id="registerlinetransform">
  ## registerLineTransform
</div>

`registerLineTransform(fn)` se ejecuta para cada línea del carrito antes de que se renderice. Úsalo para ocultar una línea o cambiar cómo se lee, sin tocar lo que realmente está en el carrito del comprador.

La función recibe una línea de solo lectura más setters. Devuelve una función de anulación de registro.

| Setter                            | Efecto                                                                                                                                                   |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `setHidden(bool)`                 | Oculta la línea del drawer. Permanece en el carrito y en el total.                                                                                       |
| `setTitle(string)`                | Cambia el título mostrado.                                                                                                                               |
| `setVariantTitle(string \| null)` | Cambia la etiqueta de variante mostrada.                                                                                                                 |
| `setInternalProperties(obj)`      | Fusiona propiedades de solo renderizado. Nunca se persisten en Shopify. Se usa para [agrupar líneas de bundle](/es/aftersell/cart/sdk-use-case-bundles). |

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// Hide free gift lines from the drawer. The cart total is unaffected.
const off = window.aftersell.cart.hooks.registerLineTransform((line) => {
  if (line.finalLinePrice === 0) {
    line.setHidden(true);
  }
  if (line.sellingPlan) {
    line.setVariantTitle(`Delivered ${line.sellingPlan.name.toLowerCase()}`);
  }
});

// later: off();
```

<Warning>
  Una transformación solo cambia lo que se renderiza. No puede cambiar el precio, la cantidad ni la identidad de la línea. Usa las [acciones](/es/aftersell/cart/sdk-actions) para eso.
</Warning>

**Úsalo para:** ocultar líneas de regalo con compra o inyectadas por apps, reetiquetar líneas de suscripción, marcar artículos con descuento, ocultar componentes de bundle que el comprador no debería gestionar individualmente.

`setInternalProperties` es el setter detrás de la agrupación de bundles: estampar las propiedades canónicas del bundle en cada línea es cómo haces que las líneas de carrito separadas de una app de terceros se rendericen como un solo artículo. Consulta [Agrupar líneas de bundle de otra app](/es/aftersell/cart/sdk-use-case-bundles).

<div id="registerlinecomparator">
  ## registerLineComparator
</div>

Un comparador con la misma forma que espera `Array.prototype.sort`. Se ejecuta después de ocultar y renombrar, así que ve las líneas transformadas.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// Subscriptions first, then everything else.
window.aftersell.cart.hooks.registerLineComparator((lineA, lineB) => {
  return (lineB.sellingPlan ? 1 : 0) - (lineA.sellingPlan ? 1 : 0);
});
```

Los comparadores **se componen como criterios de desempate**: el primero en devolver un valor distinto de cero decide ese par, y los demás se consultan solo en empates. Devuelve `0` para los pares sobre los que no tienes opinión. Eso es lo que le pasa la decisión al siguiente comparador en lugar de imponerle un orden.

**Úsalo para:** hacer flotar suscripciones o artículos de alto valor hacia arriba, hundir regalos gratis y complementos hacia abajo, mantener un producto patrocinado en primer lugar.

<div id="registercartenricher">
  ## registerCartEnricher
</div>

`registerCartEnricher(registration)` obtiene datos extra de producto o variante de la Storefront API de Shopify y los adjunta a cada línea del carrito coincidente en `line.metadata[id]`. Úsalo para mostrar metafields, etiquetas o cualquier otra cosa que la Storefront API exponga, sin requerir cambios de código por parte de Aftersell.

| Campo      | Tipo                             | Descripción                                                                                                                            |
| ---------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `id`       | `string`                         | Namespace para el resultado; aterriza en `line.metadata[id]`. Debe ser único; un segundo registro con el mismo `id` se ignora.         |
| `onType`   | `'Product'` o `'ProductVariant'` | A qué nodo apunta el fragment. También la clave de unión (ID de producto vs. ID de variante).                                          |
| `fragment` | `string`                         | Una selección de campos GraphQL (sin llaves exteriores) insertada en la consulta de la Storefront. Las llaves deben estar balanceadas. |

Devuelve una **función de anulación de registro**.

Cada vez que el carrito se carga o cambia, Aftersell obtiene tu fragment para cada producto o variante del carrito y adjunta el resultado. La obtención es no bloqueante: el carrito se renderiza inmediatamente y vuelve a emitir `cart_updated` una vez que los datos llegan. Un fragment lento o que falla nunca retrasa ni rompe el carrito.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerCartEnricher({
  id: 'pricing',
  onType: 'ProductVariant',
  fragment: `
    anchorPrice: metafield(namespace: "custom", key: "anchor_price") { value }
    subscriberPrice: metafield(namespace: "custom", key: "subscriber_price") { value }
  `,
});

// Read it once the data arrives.
window.aftersell.cart.events.on('cart_updated', (state) => {
  state.items.forEach((line) => {
    const anchor = line.metadata.pricing?.anchorPrice;
    if (anchor) console.log(line.title, 'anchor price', anchor.value);
  });
});
```

Como el enriquecimiento es asíncrono, protege siempre la lectura, ya que `line.metadata.pricing` es `undefined` hasta que la primera obtención se resuelve, y `metadata` en sí tiene como valor predeterminado `{}`.

**Úsalo para:** traer un metafield a cada línea (una estimación de entrega, una lista de ingredientes, un flag de "se envía por separado", un multiplicador de lealtad) y renderizarlo mediante un [bloque Custom code](/es/aftersell/cart/custom-code-blocks). Consulta [mostrar datos de metafields en las líneas del carrito](/es/aftersell/cart/sdk-use-case-metafields).

<Note>
  Múltiples enriquecedores coexisten sin problemas, ya que cada `id` es su propio namespace, así que sus datos nunca colisionan.
</Note>

<Warning>
  Los valores enriquecidos se devuelven tal cual desde la Storefront API y **no** están sanitizados. Renderízalos como texto, no como HTML sin procesar.
</Warning>

<div id="registersubscriptionoptionstransform">
  ## registerSubscriptionOptionsTransform
</div>

Oculta o renombra los planes de venta ofrecidos en una línea. Tu función recibe opciones de solo lectura más setters, y no devuelve nada.

| Setter            | Efecto                              |
| ----------------- | ----------------------------------- |
| `setHidden(bool)` | Oculta el plan del selector.        |
| `setName(string)` | Cambia el nombre del plan mostrado. |

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerSubscriptionOptionsTransform((options, context) => {
  // context: { productId, variantId }
  options.forEach((option) => {
    if (option.discountPercent === 0) option.setHidden(true);
    option.setName(option.name.replace('Every ', ''));
  });
});
```

**Setters, no una lista devuelta, para que varios scripts puedan coexistir.** Si este hook devolviera un array, una transformación que solo se preocupara por un plan escribiría naturalmente `options.filter(...)` y borraría silenciosamente los planes de todas las demás apps al salir. Con setters solo puedes describir tus propias ediciones: los parches se fusionan por plan y por campo, y el último escritor gana un conflicto genuino en el mismo campo del mismo plan. Una transformación que lanza una excepción no aporta nada, y las demás aún se aplican.

Cada transformación ve las opciones *originales*, no una vista parcheada a medias, así que el orden de registro no cambia lo que estás leyendo.

<Note>
  El orden de los planes se mantiene como Shopify lo devolvió, así que una transformación no puede reordenar. Para controlar qué plan se ofrece primero (y a cuál se suscribe el botón de upgrade de compra única), usa [`registerDefaultSubscriptionOptionSelector`](#registerdefaultsubscriptionoptionselector), que promueve su elección al frente.
</Note>

Tampoco puedes *agregar* un plan ni cambiar un precio: `discountPercent` no tiene setter, porque un plan que Shopify no honrará en el checkout solo sería una promesa rota en el selector.

<div id="registerdefaultsubscriptionoptionselector">
  ## registerDefaultSubscriptionOptionSelector
</div>

Elige qué plan está preseleccionado en una línea. Devuelve un `id` de plan, o `null` para pasar.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerDefaultSubscriptionOptionSelector((options) => {
  const best = options
    .slice()
    .sort((optionA, optionB) => optionB.discountPercent - optionA.discountPercent)[0];
  return best ? best.id : null;
});
```

El **primer selector que devuelve el id de un plan disponible gana**, así que devuelve `null` para las líneas que no te importan en lugar de adivinar. Eso le pasa la decisión al siguiente selector en lugar de anularlo. Un id que no coincide con ningún plan de la línea se trata igual que `null` y también cede, así que un id obsoleto no puede dejar el selector en blanco.

Tu función recibe `(options, context)`, el mismo `context` que recibe la transformación de opciones.

<div id="registerskipaddtocartrule">
  ## registerSkipAddToCartRule
</div>

Devuelve `true` para permitir que un formulario de producto específico agregue al carrito normalmente, omitiendo Aftersell por completo. Esto es útil para un formulario que necesita su propia redirección o manejo.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerSkipAddToCartRule((form) =>
  form.hasAttribute('data-skip-aftersell')
);
```

**Cualquier `true` omite**, así que mantén tu regla estrecha, coincidiendo con los formularios específicos que posees, y devuelve `false` para todo lo demás. Las reglas se evalúan en orden de registro y se detienen en el primer `true`, así que no pongas efectos secundarios en una: si la tuya se ejecuta o no depende de lo que se registró antes.

<Tip>
  Si controlas el marcado del formulario, no necesitas un hook en absoluto: agrega la clase **`aftersell-cart-skip-atc`** al `<form>` y Aftersell lo dejará en paz. Usa este hook cuando no puedas editar el marcado, o cuando la decisión dependa de algo que solo tu código sabe.
</Tip>

**Úsalo para:** un formulario de preventa o de cotización que necesita su propia redirección, el flujo personalizado de una app de suscripciones, un botón de "comprar ahora" que debe ir directo al checkout. Para desactivar la intercepción en toda la página en su lugar, usa [`skip_add_to_cart_interceptor`](/es/aftersell/cart/sdk-configure#skip_add_to_cart_interceptor), pero prefiere este hook, que está limitado a los formularios que nombras.

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

* **[Objeto cart](/es/aftersell/cart/sdk-cart-object)**: la forma de la línea que recibe una transformación.
* **[Eventos](/es/aftersell/cart/sdk-events)**: todo aquello a lo que puedes suscribirte.
* **[Acciones](/es/aftersell/cart/sdk-actions)**: leer y cambiar el carrito.
* **[Casos de uso](/es/aftersell/cart/sdk-use-cases)**: soluciones completas a solicitudes comunes.
