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

# SDK de JavaScript / TypeScript

> Cliente fino sin dependencias para Node ≥ 20 y navegadores, con token, reintentos, respaldo, lotes y webhooks.

El SDK envuelve la API v1 con el mismo comportamiento que el de Python: renueva el
token solo, reintenta en `429` respetando `Retry-After` y en `5xx` con espera
exponencial, y aplica **tu regla de respaldo** si el motor no responde a tiempo o está
en pausa (registrándola para que el aprendizaje no pierda esas decisiones).

```ts theme={null}
import { Motor, verificarWebhook } from 'motor-decision-sdk';

const motor = new Motor({
  url: 'https://api.coupons.piixan.ai',
  clientId: 'cred_…',
  clientSecret: process.env.MOTOR_SECRET!,
  respaldo: (clienteRef, contexto) => 0, // tu regla actual: 0 = nada
  timeoutMs: 300,
});
```

## Eventos

```ts theme={null}
const r = await motor.publicarEventos(eventos); // lotes de 1.000, idempotente por event_id
console.log(r.aceptados, r.duplicados, r.rechazados); // rechazados con índice, campo y motivo
```

## Decisiones en línea

```ts theme={null}
const d = await motor.decidir('c_…', { canal: 'push', campana_id: 'otono' });
if (d.en_sombra) {
  // Modo sombra: aplica tu regla, etiqueta el cupón con d.decision_id y confirma con accion_real.
  await motor.confirmarEjecucion(d.decision_id!, { ejecutada: true, accionReal: tuAccion });
} else if (d.en_respaldo) {
  // El motor no respondió: ya se aplicó y registró tu regla. Nada más que hacer.
} else {
  ejecutar(d.accion); // 0 = nada
  await motor.confirmarEjecucion(d.decision_id!, { ejecutada: true });
}
```

La confirmación es obligatoria (`ejecutada: false` con `motivoRechazo` si no se
ejecutó; `accionReal` si se envió otra cosa). Sin ella, el motor sigue decidiendo pero
deja de aprender bien.

## Lotes

```ts theme={null}
const job = await motor.pedirLote('otono', { clientes: refs }); // o { todos: true }
const estado = await motor.esperarLote(job);                     // en_cola → en_curso → completado
for (const d of await motor.resultadoLote(job)) { /* mismo cuerpo que decidir */ }
```

## Webhooks

```ts theme={null}
app.post('/webhooks/motor', async (req, res) => {
  const ok = await verificarWebhook(SECRETO, req.headers, cuerpoCrudo); // firma y marca de tiempo
  res.sendStatus(ok ? 200 : 401);
});
```

## Errores

Toda respuesta 4xx/5xx llega como `ErrorMotor` con `estado`, `codigo` (el
[catálogo](/guias/errores)), `mensaje`, `campo` y `traceId`.
