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

# Reporte de WhatsApp

> Ejemplos por semana, mes y rango de fechas; tarjetas, gráfico y confirmaciones con una sola llamada

Usa **`GET /api/v1/whatsapp/events`** con la llave de la empresa y el permiso `whatsapp_events:read`. Esta única respuesta contiene el reporte completo y su detalle paginado. Cobrix obtiene la empresa de la llave; no necesitas un token de Meta ni enviar `companyId`.

## Primera consulta

```bash theme={null}
curl --get 'https://api.cobrix.co/api/v1/whatsapp/events' \
  --header "Authorization: Bearer $COBRIX_API_KEY" \
  --data-urlencode 'dateFrom=2026-09-01T00:00:00-04:00' \
  --data-urlencode 'dateTo=2026-09-10T23:59:59.999-04:00' \
  --data-urlencode 'page=1' \
  --data-urlencode 'limit=20'
```

## Construye el tablero

* **Tarjetas:** `report.summary.sent`, `delivered`, `read` y `failed`.
* **Gráfico y tabla diaria:** `report.daily`, ordenado por fecha ascendente.
* **Detalle de confirmaciones:** `events`, con `page`, `limit` y `totalPages`.

El reporte incluye las estadísticas importadas de Meta y las confirmaciones disponibles en una sola serie. No necesitas combinar fuentes ni consultar otro endpoint. `report.history` indica la disponibilidad y las cifras del histórico incluido, como respaldo del reporte.

### Ejemplo: tablero de una empresa con histórico

Para el período **14 de agosto a 10 de septiembre de 2026**, el siguiente ejemplo muestra 4.256 enviados y 4.198 entregados. Usa la llave de la empresa correspondiente; son cifras de ejemplo para ese período, no totales actuales de todas las empresas.

| Tarjeta    | Valor | Campo                      |
| ---------- | ----- | -------------------------- |
| Enviados   | 4.256 | `report.summary.sent`      |
| Entregados | 4.198 | `report.summary.delivered` |
| Leídos     | —     | `report.summary.read`      |
| Fallidos   | —     | `report.summary.failed`    |

Respuesta completa del mismo endpoint:

```json theme={null}
{
  "coverage": {
    "status": "partial",
    "unit": "events",
    "attribution": "confirmed"
  },
  "events": [],
  "summary": {
    "read": 0,
    "sent": 0,
    "total": 0,
    "failed": 0,
    "delivered": 0
  },
  "report": {
    "daily": [
      {
        "date": "2026-09-04",
        "read": null,
        "sent": 4,
        "failed": null,
        "source": "meta_analytics",
        "delivered": 4
      },
      {
        "date": "2026-09-06",
        "read": null,
        "sent": 1,
        "failed": null,
        "source": "meta_analytics",
        "delivered": 1
      },
      {
        "date": "2026-09-07",
        "read": null,
        "sent": 2863,
        "failed": null,
        "source": "meta_analytics",
        "delivered": 2814
      },
      {
        "date": "2026-09-08",
        "read": null,
        "sent": 13,
        "failed": null,
        "source": "meta_analytics",
        "delivered": 26
      },
      {
        "date": "2026-09-09",
        "read": null,
        "sent": 0,
        "failed": null,
        "source": "meta_analytics",
        "delivered": 2
      },
      {
        "date": "2026-09-10",
        "read": null,
        "sent": 1375,
        "failed": null,
        "source": "meta_analytics",
        "delivered": 1351
      }
    ],
    "history": {
      "sent": 4256,
      "delivered": 4198,
      "availability": "available"
    },
    "summary": {
      "read": null,
      "sent": 4256,
      "failed": null,
      "delivered": 4198
    },
    "timezone": "America/Caracas"
  },
  "page": 1,
  "limit": 20,
  "totalPages": 0
}
```

**`events: []` no contradice las tarjetas.** En este ejemplo hay estadísticas históricas agregadas, pero no confirmaciones individuales recuperadas para ese período. El `summary` original cuenta ese detalle y permanece en cero; las tarjetas usan **`report.summary`**. No sumes `summary` ni `report.history` a `report.summary`: el reporte ya está combinado.

## Contrato y significado de las cifras

El `summary` original sigue contando **eventos almacenados**, incluidos callbacks repetidos. Paginar no cambia sus totales. `report.summary` cuenta una observación por `messageId` y estado dentro del filtro, más el histórico que corresponda. Un mensaje puede tener varios estados: no sumes enviados y entregados como mensajes distintos.

Para evitar duplicar cifras, un día importado sustituye las observaciones de envío y entrega del **mismo número emisor y empresa**. Se utiliza la fecha del estado informada por Meta para detectar la superposición, o la recepción si falta esa fecha. Las observaciones de otros emisores o empresas se conservan. Una estadística histórica no crea destinatarios, `messageId` ni eventos ficticios.

Los días observados se agrupan por recepción en Cobrix. Los importados corresponden al día estadístico de Meta. Ambos se muestran en `America/Caracas`; `source` identifica `events`, `meta_analytics` o `mixed`. La serie contiene días con datos; un día ausente no prueba que no hubo envíos.

`null` significa información no disponible. Si el histórico no incluye lecturas o fallos, esos totales permanecen `null`; no se presentan como cero. Las entregas diarias pueden superar los envíos por entregas de mensajes anteriores. La diferencia entre ambas cifras no demuestra fallos.

## Esta semana, este mes o un rango de fechas

La API recibe **`dateFrom` y `dateTo`**. Los botones «Esta semana» y «Este mes» son opciones del tablero: tu aplicación calcula las fechas y las envía. No existen parámetros `week`, `month` ni `period`.

Por ejemplo, si hoy fuera **12 de septiembre de 2026**, con semanas de lunes a domingo y calendario de `America/Caracas`:

| Opción del tablero           | `dateFrom`                  | `dateTo`                        |
| ---------------------------- | --------------------------- | ------------------------------- |
| Hoy                          | `2026-09-12T00:00:00-04:00` | `2026-09-12T23:59:59.999-04:00` |
| Esta semana                  | `2026-09-07T00:00:00-04:00` | `2026-09-13T23:59:59.999-04:00` |
| Este mes                     | `2026-09-01T00:00:00-04:00` | `2026-09-30T23:59:59.999-04:00` |
| Rango del ejemplo histórico  | `2026-08-14T00:00:00-04:00` | `2026-09-10T23:59:59.999-04:00` |
| Todo el histórico disponible | Omitir                      | Omitir                          |

El final de semana o mes puede ser posterior a hoy: la respuesta sólo contiene datos disponibles al consultar. Para un período **hasta hoy**, usa el final del día actual; para cortar en este instante, usa su fecha y hora exactas, teniendo en cuenta la exclusión de días históricos parciales.

```bash theme={null}
# Esta semana en el ejemplo: lunes 7 a domingo 13 de septiembre.
curl --get 'https://api.cobrix.co/api/v1/whatsapp/events' \
  --header "Authorization: Bearer $COBRIX_API_KEY" \
  --data-urlencode 'dateFrom=2026-09-07T00:00:00-04:00' \
  --data-urlencode 'dateTo=2026-09-13T23:59:59.999-04:00' \
  --data-urlencode 'limit=20'
```

Para consultar el mes, reemplaza ambas fechas por las de «Este mes». Para un rango personalizado, usa las fechas del selector. Se admite enviar sólo uno de los límites; el extremo omitido queda sin restricción. Usa `--data-urlencode` o `URLSearchParams` para codificar correctamente las fechas y teléfonos.

## Filtros

| Parámetro            | Uso                                                                                                 |
| -------------------- | --------------------------------------------------------------------------------------------------- |
| `dateFrom`, `dateTo` | Fechas ISO 8601 con segundos y zona horaria, límites inclusivos. Un rango invertido devuelve `422`. |
| `status`             | `sent`, `delivered`, `read` o `failed`. Omitir para todos.                                          |
| `phone`              | Coincidencia parcial del teléfono, hasta 64 caracteres.                                             |
| `page`               | Página del detalle, desde 1.                                                                        |
| `limit`              | Eventos por página: 20 por defecto, máximo 100.                                                     |

Conserva los [filtros y errores originales](/api-reference/categories/whatsapp): `dateFrom`, `dateTo`, `phone`, `status`, `page` y `limit`. Las fechas requieren zona horaria y los límites son inclusivos sobre la recepción de los eventos. Sin fechas, consulta el histórico disponible. La página sólo afecta al detalle; el reporte cubre todo el resultado.

El histórico sólo permite días completos y estados enviados o entregados. Con `phone`, `read` o `failed`, el reporte utiliza las confirmaciones individuales disponibles y marca `report.history.availability: "unsupported_filter"`. Un estado seleccionado deja los demás contadores del reporte en cero.

Para incluir un día histórico completo, consulta desde `00:00:00-04:00` hasta `23:59:59.999-04:00`. Los días históricos parciales se omiten y se indica `partial_days_excluded`; las confirmaciones individuales dentro del intervalo permanecen disponibles. `no_data` indica que no hay histórico importado para ese filtro.

La página, los totales y el reporte se obtienen de la misma instantánea de lectura. El límite sigue siendo **60 solicitudes por minuto por llave**; respeta `Retry-After` ante `429`.

Las confirmaciones se distinguen de la [entrega de webhooks de Cobrix al integrador](/guides/webhooks/delivery). Ninguna cifra de este reporte representa pagos recibidos.

## Cómo mostrar lecturas y fallos

* **`null` → «—» o «No disponible»:** el histórico incluido no proporciona esa métrica. No conviertas `null` a cero.
* **`0` → «0»:** no hay confirmaciones de ese estado en el resultado disponible; no garantiza que se hayan recibido todas las confirmaciones de Meta.
* **Número positivo:** conteo disponible para el filtro seleccionado.

Si un día histórico tiene lecturas o fallos desconocidos, el total de esa métrica también queda en `null`, aunque haya confirmaciones recientes. La tabla diaria permite ver cuáles días sí tienen el dato. Con un filtro de estado, los otros estados se ponen en cero porque están excluidos del resultado.

Desde que los envíos estén asociados a la empresa y sus confirmaciones lleguen a Cobrix, un período nuevo sin histórico incompleto permite contar enviados, entregados, leídos y fallidos. Una confirmación puede llegar después del envío. No leer un mensaje, no recibir una lectura o no tener confirmación de entrega no equivale a un fallo.

## Ejemplo de integración del tablero

Este ejemplo de **JavaScript en el servidor** recibe las fechas del selector, realiza una llamada y prepara los campos del tablero. Guarda `COBRIX_API_KEY` en el servidor y resuelve la llave desde la empresa autorizada en tu aplicación; no la incluyas en JavaScript público ni en la URL.

```javascript theme={null}
async function consultarTablero({ desde, hasta, status, phone, page = 1 }) {
  const apiKey = process.env.COBRIX_API_KEY;
  if (!apiKey) throw new Error('Falta configurar COBRIX_API_KEY');

  const url = new URL('https://api.cobrix.co/api/v1/whatsapp/events');
  // Desde y hasta son fechas YYYY-MM-DD del selector en Caracas.
  if (desde) url.searchParams.set('dateFrom', `${desde}T00:00:00-04:00`);
  if (hasta) url.searchParams.set('dateTo', `${hasta}T23:59:59.999-04:00`);
  if (status) url.searchParams.set('status', status);
  if (phone) url.searchParams.set('phone', phone);
  url.searchParams.set('page', String(page));
  url.searchParams.set('limit', '20');

  const response = await fetch(url, {
    headers: { Authorization: `Bearer ${apiKey}` },
    cache: 'no-store',
    signal: AbortSignal.timeout(15000),
  });
  if (!response.ok) {
    const error = new Error(`Cobrix respondió HTTP ${response.status}`);
    error.status = response.status;
    error.retryAfter = response.headers.get('Retry-After');
    throw error;
  }

  const data = await response.json();
  if (!data.report) throw new Error('Reporte no disponible en la respuesta');
  return {
    tarjetas: data.report.summary,
    dias: data.report.daily,
    eventos: data.events,
    paginacion: { page: data.page, limit: data.limit, totalPages: data.totalPages },
  };
}

const tablero = await consultarTablero({ desde: '2026-09-01', hasta: '2026-09-30' });
const mostrarConteo = (valor) => valor === null ? '—' : valor.toLocaleString('es-VE');
console.log(mostrarConteo(tablero.tarjetas.sent));
console.log(mostrarConteo(tablero.tarjetas.read));
// Grafica tablero.dias por date, con las series sent y delivered.
// La tabla de detalle usa tablero.eventos.
```

Al cambiar fechas, empresa, estado o teléfono, vuelve a `page=1` y consulta de nuevo. Cancela o descarta respuestas anteriores para que una consulta lenta no reemplace el filtro actual. No sustituyas un error HTTP por tarjetas en cero.

Para actualización periódica, una consulta por minuto mientras el tablero está visible es suficiente como punto de partida. Coordina las consultas que compartan la misma llave, evita solicitudes superpuestas y ante `429` espera los segundos de `Retry-After` antes de reintentar. El ejemplo realiza una sola consulta; no crea un temporizador automáticamente.

Los datos cambian cuando llegan nuevas confirmaciones o se incorpora evidencia. Para seguimiento de detalle, deduplica por `id` y vuelve a consultar fechas recientes: la paginación entre llamadas no congela el conjunto de datos. No se garantiza la reconstrucción de confirmaciones históricas no recibidas.
