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

# Contrato de eventos v1

> Los seis tipos de evento que el CRM publica en POST /v1/eventos, campo a campo, con ejemplos.

Cada evento es un objeto JSON con los campos comunes y un `extra` propio de su
`tipo`. Los campos desconocidos se rechazan. El JSON Schema completo está en
`esquemas/v1/evento.schema.json`.

## Campos comunes

| campo         | tipo                    | obligatorio | descripción                                                                                                                                                            |
| ------------- | ----------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event_id`    | texto                   | sí          | Único por inquilino; hace idempotente la entrega (longitud ≥ 1; longitud ≤ 128; patrón `^[A-Za-z0-9][A-Za-z0-9_.:\-]{0,127}$`)                                         |
| `cliente_ref` | texto                   | sí          | Identificador seudonimizado del cliente (hash con sal del inquilino) (longitud ≥ 8; longitud ≤ 128; patrón `^[A-Za-z0-9][A-Za-z0-9_.:\-]{0,127}$`)                     |
| `ts`          | fecha ISO-8601 con zona | sí          | Cuándo ocurrió el evento (no cuándo se envía)                                                                                                                          |
| `decision_id` | texto \| null           | no          | Obligatorio en CUPON\_RECIBIDO cuando el cupón lo decidió el motor; opcional en el resto (longitud ≥ 1; longitud ≤ 128; patrón `^[A-Za-z0-9][A-Za-z0-9_.:\-]{0,127}$`) |

<Note>
  `ts` es cuándo ocurrió el evento, no cuándo se envía. Lleva zona horaria; el motor
  lo guarda y lo devuelve en UTC (`Z`). El dinero va en MXN sin IVA, con dos decimales.
</Note>

## Tipos de evento

### `COMPRA_CAT`

Compras de un ticket agregadas por categoría: un ticket con 3 lácteos y 1 pan
produce dos eventos (§2.2).

| campo   | tipo         | obligatorio | descripción                                                                        |
| ------- | ------------ | ----------- | ---------------------------------------------------------------------------------- |
| `tipo`  | `COMPRA_CAT` | sí          |                                                                                    |
| `clave` | texto        | sí          | Categoría del catálogo del inquilino (longitud ≥ 1; longitud ≤ 256)                |
| `valor` | número       | sí          | Importe de las líneas de la categoría en el ticket, sin IVA (≥ 0; ≤ 1,000,000,000) |

**`extra`** (obligatorio):

| campo          | tipo           | obligatorio | descripción                                                                                                        |
| -------------- | -------------- | ----------- | ------------------------------------------------------------------------------------------------------------------ |
| `ticket_id`    | texto          | sí          | longitud ≥ 1; longitud ≤ 128; patrón `^[A-Za-z0-9][A-Za-z0-9_.:\-]{0,127}$`                                        |
| `margen`       | número         | sí          | Margen de las líneas de esta categoría en el ticket (importe − coste), sin IVA (≥ -1,000,000,000; ≤ 1,000,000,000) |
| `cantidad`     | número         | no          | Unidades de la categoría en el ticket (≥ 0)                                                                        |
| `tienda`       | texto \| null  | no          | Tienda o punto de venta (longitud ≥ 1; longitud ≤ 256)                                                             |
| `canal`        | texto \| null  | no          | tienda, web, app… (longitud ≥ 1; longitud ≤ 256)                                                                   |
| `ticket_total` | número \| null | no          | Total del ticket completo. Permite al validador detectar si los importes traen IVA (≥ 0; ≤ 1,000,000,000)          |

```json theme={null}
{
  "event_id": "tk-88213-lacteos",
  "cliente_ref": "c_9f1e2d3c4b5a6978",
  "ts": "2026-03-04T18:22:10Z",
  "tipo": "COMPRA_CAT",
  "clave": "lacteos",
  "valor": 186.21,
  "extra": {
    "ticket_id": "tk-88213",
    "margen": 41.9,
    "cantidad": 4,
    "tienda": "suc-014",
    "canal": "tienda",
    "ticket_total": 612.4
  }
}
```

### `CUPON_RECIBIDO`

| campo   | tipo             | obligatorio | descripción                                                           |
| ------- | ---------------- | ----------- | --------------------------------------------------------------------- |
| `tipo`  | `CUPON_RECIBIDO` | sí          |                                                                       |
| `clave` | texto            | sí          | Tipo de cupón del catálogo de acciones (longitud ≥ 1; longitud ≤ 256) |
| `valor` | número           | no          | Descuento máximo en MXN; 0 si es un porcentaje (≥ 0; ≤ 1,000,000,000) |

**`extra`** (obligatorio):

| campo              | tipo                    | obligatorio | descripción                                                                                                                     |
| ------------------ | ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `cupon_emitido_id` | texto                   | sí          | Enlaza la redención o la caducidad con este envío (longitud ≥ 1; longitud ≤ 128; patrón `^[A-Za-z0-9][A-Za-z0-9_.:\-]{0,127}$`) |
| `campana_id`       | texto \| null           | no          | longitud ≥ 1; longitud ≤ 128; patrón `^[A-Za-z0-9][A-Za-z0-9_.:\-]{0,127}$`                                                     |
| `caducidad`        | fecha ISO-8601 con zona | sí          | ISO-8601 con zona horaria; el motor la guarda y la devuelve en UTC (Z)                                                          |
| `canal`            | texto \| null           | no          | sms, email, push, app, ticket… (longitud ≥ 1; longitud ≤ 256)                                                                   |
| `descuento_pct`    | número \| null          | no          | ≥ 0; ≤ 100                                                                                                                      |

```json theme={null}
{
  "event_id": "cup-55019",
  "cliente_ref": "c_9f1e2d3c4b5a6978",
  "ts": "2026-03-06T15:00:00Z",
  "tipo": "CUPON_RECIBIDO",
  "clave": "lacteos_15",
  "valor": 0,
  "decision_id": "dec_01J8Z3K2M9Q4N7R6T5V8W1X0Y2",
  "extra": {
    "cupon_emitido_id": "cup-55019",
    "campana_id": "camp-2026-10",
    "caducidad": "2026-03-20T05:59:59Z",
    "canal": "push",
    "descuento_pct": 15
  }
}
```

### `CUPON_REDIMIDO`

| campo   | tipo             | obligatorio | descripción                                                     |
| ------- | ---------------- | ----------- | --------------------------------------------------------------- |
| `tipo`  | `CUPON_REDIMIDO` | sí          |                                                                 |
| `clave` | texto            | sí          | Tipo de cupón (longitud ≥ 1; longitud ≤ 256)                    |
| `valor` | número           | sí          | Descuento aplicado en el ticket, sin IVA (≥ 0; ≤ 1,000,000,000) |

**`extra`** (obligatorio):

| campo              | tipo  | obligatorio | descripción                                                                 |
| ------------------ | ----- | ----------- | --------------------------------------------------------------------------- |
| `cupon_emitido_id` | texto | sí          | longitud ≥ 1; longitud ≤ 128; patrón `^[A-Za-z0-9][A-Za-z0-9_.:\-]{0,127}$` |
| `ticket_id`        | texto | sí          | longitud ≥ 1; longitud ≤ 128; patrón `^[A-Za-z0-9][A-Za-z0-9_.:\-]{0,127}$` |

```json theme={null}
{
  "event_id": "tk-88420-cup-55019",
  "cliente_ref": "c_9f1e2d3c4b5a6978",
  "ts": "2026-03-09T19:40:00Z",
  "tipo": "CUPON_REDIMIDO",
  "clave": "lacteos_15",
  "valor": 27.93,
  "extra": {
    "cupon_emitido_id": "cup-55019",
    "ticket_id": "tk-88420"
  }
}
```

### `CUPON_CADUCADO`

| campo   | tipo             | obligatorio | descripción                                  |
| ------- | ---------------- | ----------- | -------------------------------------------- |
| `tipo`  | `CUPON_CADUCADO` | sí          |                                              |
| `clave` | texto            | sí          | Tipo de cupón (longitud ≥ 1; longitud ≤ 256) |
| `valor` | número           | no          | ≥ 0; ≤ 0                                     |

**`extra`** (obligatorio):

| campo              | tipo  | obligatorio | descripción                                                                 |
| ------------------ | ----- | ----------- | --------------------------------------------------------------------------- |
| `cupon_emitido_id` | texto | sí          | longitud ≥ 1; longitud ≤ 128; patrón `^[A-Za-z0-9][A-Za-z0-9_.:\-]{0,127}$` |

```json theme={null}
{
  "event_id": "cad-cup-55020",
  "cliente_ref": "c_1a2b3c4d5e6f7081",
  "ts": "2026-03-20T06:00:00Z",
  "tipo": "CUPON_CADUCADO",
  "clave": "bebidas_10",
  "valor": 0,
  "extra": {
    "cupon_emitido_id": "cup-55020"
  }
}
```

### `BAJA`

`comunicaciones` es la baja que penaliza la recompensa (R-17); `cuenta` es
el cierre de la cuenta y solo cierra el historial.

| campo   | tipo                         | obligatorio | descripción |
| ------- | ---------------------------- | ----------- | ----------- |
| `tipo`  | `BAJA`                       | sí          |             |
| `clave` | `comunicaciones` \| `cuenta` | sí          |             |
| `valor` | número                       | no          | ≥ 0; ≤ 0    |

**`extra`** (opcional):

| campo   | tipo          | obligatorio | descripción                                                            |
| ------- | ------------- | ----------- | ---------------------------------------------------------------------- |
| `canal` | texto \| null | no          | Canal del que se dio de baja, si aplica (longitud ≥ 1; longitud ≤ 256) |

```json theme={null}
{
  "event_id": "baja-c_1a2b3c4d5e6f7081-2026-03-21",
  "cliente_ref": "c_1a2b3c4d5e6f7081",
  "ts": "2026-03-21T14:05:00Z",
  "tipo": "BAJA",
  "clave": "comunicaciones",
  "valor": 0,
  "extra": {
    "canal": "push"
  }
}
```

### `ALTA`

| campo   | tipo   | obligatorio | descripción                                                     |
| ------- | ------ | ----------- | --------------------------------------------------------------- |
| `tipo`  | `ALTA` | sí          |                                                                 |
| `clave` | texto  | sí          | Canal de alta: tienda, web, app… (longitud ≥ 1; longitud ≤ 256) |
| `valor` | número | no          | ≥ 0; ≤ 0                                                        |

**`extra`** (opcional):

| campo      | tipo          | obligatorio | descripción                                                                   |
| ---------- | ------------- | ----------- | ----------------------------------------------------------------------------- |
| `segmento` | texto \| null | no          | Segmento declarado por el inquilino, si lo hay (longitud ≥ 1; longitud ≤ 256) |
| `perfil`   | texto \| null | no          | longitud ≥ 1; longitud ≤ 256                                                  |

```json theme={null}
{
  "event_id": "alta-c_7788990011223344",
  "cliente_ref": "c_7788990011223344",
  "ts": "2026-01-15T16:30:00Z",
  "tipo": "ALTA",
  "clave": "app",
  "valor": 0,
  "extra": {
    "segmento": "familia",
    "perfil": "app"
  }
}
```

## Lote

`POST /v1/eventos` recibe `{"eventos": [...]}` con entre 1 y 1.000 eventos y hasta 1 MB.
La respuesta dice cuántos se aceptaron, cuántos eran duplicados y cuáles se
rechazaron, con la posición, el campo y el motivo de cada uno:

```json theme={null}
{
  "aceptados": 998,
  "duplicados": 1,
  "rechazados": [
    {
      "indice": 17,
      "event_id": "tk-1-lacteos",
      "codigo": "esquema_invalido",
      "campo": "extra.margen",
      "mensaje": "Field required"
    }
  ]
}
```
