---
name: crossup
description: Integrar el motor de recomendaciones de CrossUp en una tienda con @crossup/storefront o la API, y verificar cada paso con evidencia. Usar cuando se pida integrar CrossUp, mostrar recomendaciones, mandar señales o depurar una integración.
---
# CrossUp — integrar y verificar
Documentación: https://docs.crossup.ai/llms.txt · Referencia de errores: https://docs.crossup.ai/es/api/errores.md
## Reglas que no se negocian
1. La tienda sale de la clave. Ningún request lleva `store_id`.
2. `results: []` es una respuesta válida; `reason_codes` dice por qué. No se muestra nada.
3. Un campo ausente es desconocido, no cero. `sellable` es un string; `title` es un objeto por idioma.
4. Nunca cortar la página: usar `tryGetRecommendations`, nunca `await` en `track.*`.
5. Ante `429`, esperar `Retry-After`. Nunca reintentar en loop.
6. No inventar endpoints, campos ni valores. Si la doc no lo dice, preguntar.
## Recorrido
1. **Datos.** Pedir la clave `spk_…` y el `connection_id` (`cn_…`). Nunca commitear la clave en un archivo que no sea de configuración pública; es pública por diseño, pero igual va en variables de entorno.
2. **Verificar la clave.** `GET {base}/v1/catalog/items/{item_id}` con `Authorization: Bearer`. Un `200` con `item.item_id` confirma clave y conexión. `401` → clave; `403 forbidden_connection` → conexión de otra tienda; `404` → ítem de otra tienda o inexistente.
3. **Instalar.** `npm install @crossup/storefront`. Una instancia por página: `createStorefront({ apiKey, connectionId })`.
4. **Pedir.** `tryGetRecommendations({ surface, placementKey, contextItemId?, cartItems? })`. `pdp` y `cart` necesitan `contextItemId`; `carousel`, `in-checkout` y `success-page` necesitan `cartItems`; `checkout` acepta cualquiera de los dos.
5. **Mostrar.** Recorrer `results[]`; omitir los que no traen `product`; usar `product.item.title[lang]` con fallback, `variants[].price.amount` como string, `availability[].sellable` como texto (nunca como booleano). Sanitizar `item.description` si es HTML. El diseño es el de la tienda: no traer componentes de CrossUp.
6. **Señales.** `track.impression(decision, { position })` cuando la recomendación entra en pantalla (IntersectionObserver, una vez). `track.click(decision, result)` al abrir. `track.addToCart` con la cantidad sumada. `track.purchase(decision, { orderExternalId, items })` en la página de compra.
7. **Evidencia.** Mostrar un `decision_id` de una decisión de prueba y un `signal_id` con `status: "accepted"`. Sin los dos, la integración no está terminada.
## Cuando algo falla
- `results: []` con `module_not_configured`: el momento no está activado en el admin. No es un bug del código.
- `results: []` con `no_offer` o `brain_unavailable`: no mostrar nada. La tienda sigue vendiendo.
- `rejected` con `unknown_decision` en una señal: se mandó una decisión que no es de esta conexión.
- `429 rate_limited`: buscar el loop (un pedido por render, un pedido por tarjeta).