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

# Carrito AJAX y secciones

> Los endpoints AJAX del carrito, el Section Rendering API y los eventos del tema.

El JavaScript del tema habla con la tienda por endpoints al estilo Shopify. Todos los montos son **centavos enteros** y todas las respuestas de carrito comparten una misma forma (el carrito completo), también en `add.js`.

| Endpoint                | Método | Para qué                                                                  |
| ----------------------- | ------ | ------------------------------------------------------------------------- |
| `/cart.js`              | GET    | Leer el carrito actual.                                                   |
| `/cart/add.js`          | POST   | Agregar una variante.                                                     |
| `/cart/change.js`       | POST   | Cambiar la cantidad de una línea (0 la elimina).                          |
| `/cart/update.js`       | POST   | Cambiar varias líneas en un solo request.                                 |
| `/cart/clear.js`        | POST   | Vaciar el carrito.                                                        |
| `/cart/add`             | POST   | Fallback sin JavaScript de `{% form 'product' %}`; redirige a `/carrito`. |
| `/products/{handle}.js` | GET    | JSON del producto (variantes, precios, disponibilidad).                   |
| `/api/sections`         | GET    | Re-renderizar secciones de una ruta (Section Rendering API).              |

## Leer el carrito

```js theme={null}
const cart = await fetch("/cart.js").then((res) => res.json());
// { token, item_count, items: [...], total_price, original_total_price, total_discount, currency }
```

Cada línea trae `id`, `variant_id`, `quantity`, `title`, `variant_title`, `sku`, `price`, `line_price`, `final_line_price`, `image`, `url` y `available`.

## Escribir en el carrito

Los cuatro endpoints de escritura aceptan JSON (o form-encoded) y responden con el carrito completo:

```js theme={null}
// Agregar: id = id de variante
await fetch("/cart/add.js", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ id: variantId, quantity: 2 }),
});

// Cambiar una línea; quantity: 0 la elimina
await fetch("/cart/change.js", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ id: variantId, quantity: 0 }),
});

// Varias líneas de una vez
await fetch("/cart/update.js", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ updates: { [variantIdA]: 3, [variantIdB]: 0 } }),
});
```

Un error de negocio (sin stock, cantidad inválida) responde `422` con `{ status, message, description }`.

Después de cada escritura exitosa, anuncia el cambio al resto del tema:

```js theme={null}
window.dispatchEvent(new Event("riqra:cart-changed"));
```

## Secciones re-renderizadas en la misma respuesta

Igual que en Shopify, las escrituras aceptan un parámetro `sections` para que la respuesta incluya el HTML actualizado de las secciones que nombres — un solo round-trip actualiza estado y DOM:

```js theme={null}
const res = await fetch("/cart/add.js", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    id: variantId,
    quantity: 1,
    sections: "cart-1,header-1",
    sections_url: "/carrito",
  }),
}).then((r) => r.json());

// res.sections → { "cart-1": "<div id=\"riqra-section-cart-1\">…</div>", "header-1": "…" }
```

`sections_url` es la ruta contra la que se renderizan (por defecto `/carrito`). Cada fragmento llega con su wrapper `riqra-section-{id}` incluido: reemplaza el nodo completo.

## Section Rendering API

Fuera de las escrituras de carrito, `GET /api/sections` re-renderiza secciones de cualquier ruta con superficie de tema — filtros de colección, mini-carrito, etc.:

```js theme={null}
const sections = await fetch(
  `/api/sections?path=${encodeURIComponent("/collections/ofertas?brand=acme")}&sections=main-collection`,
).then((res) => res.json());
// { "main-collection": "<div id=\"riqra-section-main-collection\">…</div>" }
```

<Note>
  A diferencia de Shopify, `?sections=` no se intercepta sobre las URLs de página: usa siempre este endpoint.
</Note>

Al reemplazar el HTML de una sección, despacha `riqra:section:load` sobre el nodo nuevo para que los scripts que escuchan ese evento se re-inicialicen (los custom elements se re-montan solos):

```js theme={null}
function swapSection(id, html) {
  const target = document.getElementById(`riqra-section-${id}`);
  if (!target || typeof html !== "string") return;
  target.outerHTML = html;
  document
    .getElementById(`riqra-section-${id}`)
    ?.dispatchEvent(new Event("riqra:section:load", { bubbles: true }));
}
```

El personalizador del panel usa este mismo evento cuando refresca secciones en la vista previa.

## Eventos del tema

| Evento               | Se despacha                                        | Escúchalo para                                          |
| -------------------- | -------------------------------------------------- | ------------------------------------------------------- |
| `riqra:cart-changed` | En `window`, tras cualquier escritura de carrito.  | Refrescar el contador del header, el mini-carrito, etc. |
| `riqra:section:load` | Sobre una sección cuyo HTML acaba de reemplazarse. | Re-inicializar listeners no basados en custom elements. |

Origin resume el patrón completo — un snapshot compartido del carrito, invalidado por el evento:

```js theme={null}
let cartSnapshot = null;
function readCart() {
  cartSnapshot ??= fetch("/cart.js")
    .then((res) => (res.ok ? res.json() : null))
    .catch(() => null);
  return cartSnapshot;
}
window.addEventListener("riqra:cart-changed", () => {
  cartSnapshot = null;
});
```

## JSON de producto

Para pickers de variantes o vistas rápidas:

```js theme={null}
const product = await fetch(`/products/${handle}.js`).then((res) => res.json());
// { id, title, handle, url, vendor, available, price, price_min, price_max,
//   compare_at_price, images, featured_image, variants: [...] }
```

`compare_at_price` mayor que `price` es la señal de oferta — los badges de descuento se derivan de esa comparación, no de un objeto de promociones.
