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

# Agrupar líneas de bundle de otra app

> Usa setInternalProperties para decirle al Aftersell Cart qué líneas pertenecen al mismo bundle, para que se rendericen como un solo artículo en lugar de varias líneas sin relación.

La mayoría de las apps de bundles construyen un bundle agregando **cada componente como su propia línea de carrito**, y luego las vinculan con propiedades de línea de artículo de su propio diseño. La Ajax API de Shopify entrega esas líneas al carrito sin ninguna indicación de que van juntas, así que de forma predeterminada el drawer muestra un bundle de tres partes como tres artículos sin relación, cada uno con su propio precio y control de cantidad.

`setInternalProperties` es cómo le dices al carrito que son una sola cosa.

<div id="how-grouping-works">
  ## Cómo funciona la agrupación
</div>

El carrito agrupa líneas según dos **propiedades canónicas**. No conoce los nombres de propiedades de tu app de bundles, así que tú traduces: lee lo que sea que la app escribió, y estampa el par canónico en cada línea con una [transformación de línea](/es/aftersell/cart/sdk-hooks#registerlinetransform).

| Propiedad                     | Obligatoria | Valor                                                                     |
| ----------------------------- | ----------- | ------------------------------------------------------------------------- |
| `_aftersell_cart_bundle_id`   | Sí          | Un ID compartido. Cada línea que lleva el mismo ID es un bundle.          |
| `_aftersell_cart_bundle_role` | No          | Establécela en `parent` en la línea como la que el bundle debe mostrarse. |

Estas van a través de `setInternalProperties`, no a través de Shopify. Son una **superposición de solo renderizado**: nunca llegan a `properties`, nunca se persisten en Shopify y nunca aparecen en el pedido.

<div id="step-1-find-out-what-your-app-writes">
  ## Paso 1: descubre qué escribe tu app
</div>

Cada app de bundles nombra sus propiedades de forma diferente, así que empieza mirando un carrito real. Agrega un bundle en tu tienda, luego ejecuta esto en la consola del navegador:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.actions.getCart().items.forEach((line) => {
  console.log(line.title, line.properties);
});
```

Estás buscando una propiedad compartida entre las líneas del bundle. Normalmente es una propiedad oculta (el nombre empieza con `_`) que contiene un ID, una referencia o el nombre del bundle. Algo como `_bundle_id`, `_bundle_ref` o `_parent_id` es típico. Anota la clave exacta, y si una línea está marcada como el producto principal.

<div id="step-2-map-it-onto-the-canonical-properties">
  ## Paso 2: mapéala a las propiedades canónicas
</div>

Pega en **Cart settings → Custom script → Initialization**, reemplazando los nombres de propiedades con los que encontraste:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerLineTransform((line) => {
  const props = line.properties;
  if (!props) return;

  const bundleId = props._bundle_id;
  if (!bundleId) return;

  line.setInternalProperties({
    _aftersell_cart_bundle_id: bundleId,
    // Mark the main product so the bundle renders under it.
    _aftersell_cart_bundle_role: props._bundle_role === 'main' ? 'parent' : 'child',
  });
});
```

Esa es toda la integración. Una vez que dos o más líneas comparten un ID, el carrito las pliega en un solo bundle.

<Note>
  Si tu app no marca un producto principal, omite `_aftersell_cart_bundle_role` por completo. El carrito elige un ancla por ti.
</Note>

<div id="what-you-get">
  ## Qué obtienes
</div>

Una vez que las líneas están agrupadas, la línea ancla lleva un [objeto `bundle`](/es/aftersell/cart/sdk-cart-object#bundles) y el drawer renderiza el bundle como un solo artículo:

* **Los hijos se anidan bajo el ancla** en lugar de aparecer como filas separadas.
* **La cantidad es atómica.** Cambiar la cantidad del bundle escala todos los miembros juntos, usando la proporción `perAnchorQty` de cada hijo, así que un bundle con dos unidades de un componente mantiene esa relación de dos a uno.
* **La eliminación es atómica.** Eliminar el bundle elimina cada línea miembro en una sola solicitud, en lugar de dejar componentes huérfanos.
* **Una sola fila de precio.** Lo que muestra sigue la configuración de **bundle price** en el bloque [Cart items](/es/aftersell/cart/cart-items-block): el total de todos los miembros, o solo el precio del producto principal.

<div id="how-the-anchor-is-chosen">
  ## Cómo se elige el ancla
</div>

El ancla es la línea como la que se muestra el bundle. El carrito la elige en este orden:

1. La línea con `_aftersell_cart_bundle_role` establecida en `parent`.
2. De lo contrario, el miembro de **precio más alto**.
3. De lo contrario, el primer miembro en el carrito.

El respaldo por precio suele ser correcto, ya que las apps de bundles tienden a poner el descuento en el producto principal. Establece el rol explícitamente cuando no lo sea, por ejemplo cuando el producto principal es el artículo más barato o es gratis.

<div id="rules-worth-knowing">
  ## Reglas que vale la pena conocer
</div>

* **Un bundle necesita al menos dos líneas.** Una sola línea que lleva un ID de bundle se deja en paz y se renderiza normalmente.
* **Los bundles nativos de Shopify ya están manejados.** Las líneas que el propio Shopify marca como componentizadas son omitidas por esta agrupación y se adaptan automáticamente. Solo necesitas esto para apps que agregan líneas separadas.
* **La transformación se ejecuta en cada renderizado.** Mantenla barata y libre de efectos secundarios. No llames a acciones ni hagas fetch desde dentro.
* **La fusión es aditiva.** Tus propiedades se fusionan con las establecidas por otra transformación. En un conflicto genuino sobre la misma clave, gana la transformación registrada al último.
* **La agrupación se ejecuta después de ocultar y renombrar**, y antes de ordenar. Así que una línea que ocultas con `setHidden` nunca se convierte en parte de un bundle, y un [comparador](/es/aftersell/cart/sdk-hooks#registerlinecomparator) ve el ancla, no los hijos.

<Warning>
  **Los hijos agrupados salen de `state.items`.** Una vez que las líneas se pliegan en un bundle, solo el ancla aparece en `getCart().items` y en los payloads de eventos; los hijos se mueven a `anchor.bundle.children`. También dejan de contar para `itemCount`.

  El **total del carrito no se ve afectado**, porque los totales vienen directamente de Shopify. La agrupación cambia la presentación, nunca lo que el comprador paga.
</Warning>

<div id="reading-a-bundle-back">
  ## Leer un bundle de vuelta
</div>

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('cart_updated', (state) => {
  state.items.forEach((line) => {
    if (!line.bundle) return;
    console.log(line.title, 'is a bundle of', line.bundle.children.length, 'items:');
    line.bundle.children.forEach((child) => {
      console.log('  ', child.quantity, 'x', child.title);
    });
  });
});
```

Para actuar sobre las líneas de un bundle, usa `bundle.memberKeys`, que contiene la `key` de cada miembro incluida el ancla.

<div id="using-it-for-other-things">
  ## Usarlo para otras cosas
</div>

La agrupación de bundles es para lo que se construyó `setInternalProperties`, pero la superposición es un canal general para **datos de solo renderizado que derivas de una línea**. Cualquier cosa que pongas ahí es legible en `line.internalProperties` y en un [bloque Custom code](/es/aftersell/cart/custom-code-blocks), sin tocar el carrito real:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerLineTransform((line) => {
  if (line.properties?._preorder_ship_date) {
    line.setInternalProperties({ _badge: `Ships ${line.properties._preorder_ship_date}` });
  }
});
```

Úsalo cuando el valor sea **derivado** y solo de visualización. Si los datos necesitan sobrevivir hasta el pedido, pertenecen a una propiedad de línea de artículo real, establecida con un input oculto `properties[...]` en el formulario del producto para que llegue a Shopify sin importar quién realice el agregado.

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

* **[`registerLineTransform`](/es/aftersell/cart/sdk-hooks#registerlinetransform)**: el hook a través del cual esto se ejecuta.
* **[Objeto cart](/es/aftersell/cart/sdk-cart-object#bundles)**: la forma de `bundle` y sus hijos.
* **[Bloque Cart items](/es/aftersell/cart/cart-items-block)**: la configuración de bundle price.
