# SDK

El paquete @linkerestate/sdk — sus tres entradas, cómo se instala y las dos trampas que hay que evitar al integrarlo.

`@linkerestate/sdk` es el paquete que consumen los sitios externos: micrositios de
proyecto, landings de campaña, el portal corporativo.

- **npm**: `@linkerestate/sdk` (público)
- Sin dependencias de ejecución, ESM, Node ≥ 18

```bash
pnpm add @linkerestate/sdk
```

## Tres entradas, tres llaves

La separación por subpath **es la frontera de seguridad**: cada entrada exige una llave
distinta, y las de servidor no pueden llegar al navegador porque el sitio nunca las
importa desde código de cliente.

| Subpath                     | Para qué                                             | Scope            | ¿Pública?     |
| --------------------------- | ---------------------------------------------------- | ---------------- | ------------- |
| `@linkerestate/sdk/client`  | Rotación: leer el turno y registrar el clic de WaTel | `campaign_rr`    | **Sí**        |
| `@linkerestate/sdk/server`  | Publicar un prospecto                                | `campaign_leads` | **No, nunca** |
| `@linkerestate/sdk/catalog` | Propiedades y asesores                               | `api_v1`         | **No**        |

## Rotación (navegador o servidor)

```ts
import { createRoundRobinClient } from "@linkerestate/sdk/client"

const rotation = createRoundRobinClient({
  campaignId: RR_ID,
  apiKey: RR_API_KEY, // campaign_rr
  source: RR_WEBHOOK_KEY, // opcional: atribuye el clic a esa fuente
})

// Botones de WaTel (WhatsApp y llamada), con el asesor de turno
const turn = await rotation.peek() // { waUrl, telUrl, phone } | null

// Ficha del asesor que atenderá el formulario
const lead = await rotation.peekLeadForm() // { leadFormAgentId, agent } | null

// Registra el clic sin bloquear la navegación (sendBeacon). Devuelve un limpiador.
const detach = rotation.attachCommitHandlers()
```

`attachCommitHandlers()` engancha `[id^="wa-btn"], [data-rr-link="whatsapp"]` y
`[id^="tel-btn"], [data-rr-link="tel"]`, una sola vez por elemento. Cada clic así
enganchado es un turno de **WaTel**; el carril del formulario no se toca.

:::note
`peek()` y `peekLeadForm()` **devuelven `null` en vez de lanzar**: una caída de la
rotación no puede tumbar una página. El resto del SDK sí lanza `LinkerestateError`, con
`status` y el `body` ya parseado.

`agent` viene `null` cuando el perfil no es público: el prospecto igual le llega, pero no
hay ficha que mostrar.
:::

## Catálogo

```ts
import { createCatalogClient } from "@linkerestate/sdk/catalog"

const catalog = createCatalogClient({ apiKey: process.env.LINKER_API_KEY })

const { data, meta } = await catalog.properties({ city: "Punta Cana", page: 1 })
const one = await catalog.property("luxury-beachfront-villa-punta-cana")
```

## Las dos trampas

:::danger[1. `import.meta.env` para un secreto de servidor]
Una llave `campaign_leads` o `api_v1` leída con prefijo público **queda expuesta en el
bundle del navegador**. Usa la variable de entorno del servidor, sin prefijo; qué hace ese
prefijo está en [la regla de seguridad](/desarrolladores/).
:::

:::danger[2. `baseUrl` apuntado a un prefijo de ruta]
`baseUrl` es la **raíz de la API**, nunca un prefijo de ruta: el SDK arma
`{base}/campaigns/{id}/…` por su cuenta. Si le pasas un prefijo, queda duplicado en la
URL —`/api/rr/campaigns/…`— y el resultado es un **404** que pierde prospectos en
silencio, porque nada en la página se rompe a la vista.
:::

## Caché en un host Next

Las lecturas del catálogo son cacheables. La rotación **no**: `peek()` tiene que
reflejar el turno vivo, así que va sin caché.

---

Índice completo del centro de ayuda: https://help.linkerestate.com/llms.txt