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

# Exportaciones del resumen gerencial

> Pide una exportación CSV asíncrona del resumen gerencial, consulta su estado hasta que esté lista, descárgala con un enlace de corta duración y verifica su checksum.

Una exportación del resumen gerencial es un archivo CSV con el mismo resumen que devuelve `GET /v1/analytics/briefing`: alertas, excepciones de entrega, aprobaciones pendientes, consumo y tiempos de ciclo. muveya genera el archivo en segundo plano, así que el flujo tiene tres pasos: pedirlo, consultar su estado y descargarlo.

`POST /v1/analytics/exports` es la única operación de `/v1` que escribe algo, y lo que escribe es el propio trabajo de exportación. No cambia nada de tu operación.

## Quién puede hacerlo

Una clave con el scope `analytics:read` (consulta [Scopes](/docs/es/api-reference/scopes)). El mismo scope cubre pedir, consultar y descargar.

## El flujo

```mermaid theme={null}
sequenceDiagram
  participant App as Tu servidor
  participant API as api.muveya.com
  App->>API: POST /v1/analytics/exports
  API-->>App: 202, estado pending, exportId
  loop cada pocos segundos
    App->>API: GET /v1/analytics/exports/exportId
    API-->>App: 200, estado pending o building
  end
  API-->>App: 200, estado ready, downloadUrl, checksum
  App->>App: descarga el archivo y verifica su SHA-256
```

## Paso 1: pide la exportación

<ParamField body="period" type="string" default="daily">
  La periodicidad del resumen: `daily` o `weekly`. Cualquier otro valor se rechaza con `400` y el código `common.invalid_request`. El cuerpo es opcional; sin él obtienes un resumen diario. No envíes ningún otro campo.
</ParamField>

<ParamField header="Accept-Language" type="string" default="en">
  El idioma de la columna `label` del CSV: `en`, `es` o `pt`. Queda fijado al pedir la exportación, porque el archivo se genera después. Las claves de métricas, dimensiones, unidades, valores e ids de evidencia son iguales en todos los idiomas.
</ParamField>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.muveya.com/v1/analytics/exports \
    -H "Authorization: Bearer $MUVEYA_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Accept-Language: es" \
    -d '{"period": "weekly"}'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://api.muveya.com/v1/analytics/exports", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.MUVEYA_API_KEY}`,
      "Content-Type": "application/json",
      "Accept-Language": "es",
    },
    body: JSON.stringify({ period: "weekly" }),
  });
  const job = await response.json();
  console.log(response.status, job.exportId, job.status);
  ```

  ```python Python theme={null}
  import os
  import requests

  response = requests.post(
      "https://api.muveya.com/v1/analytics/exports",
      headers={
          "Authorization": f"Bearer {os.environ['MUVEYA_API_KEY']}",
          "Accept-Language": "es",
      },
      json={"period": "weekly"},
      timeout=30,
  )
  job = response.json()
  print(response.status_code, job["exportId"], job["status"])
  ```
</CodeGroup>

```json 202 Accepted theme={null}
{
  "exportId": "3f6c2a9e-0b1d-4c7a-9e55-1a2b3c4d5e6f",
  "status": "pending",
  "reportType": "briefing",
  "period": "weekly"
}
```

Guarda el `exportId`: es la única referencia al trabajo.

<Note>
  Esta operación no tiene clave de idempotencia. Cada `POST` crea un trabajo de exportación nuevo e independiente. Si una solicitud vence antes de que recibas el `exportId`, envíala de nuevo; el trabajo adicional no causa problemas y se elimina junto con los demás a los 7 días.
</Note>

## Paso 2: consulta hasta que esté lista

Llama a `GET /v1/analytics/exports/{exportId}` cada pocos segundos (por ejemplo, cada 3 segundos). Cada consulta cuenta para tu presupuesto de solicitudes (consulta [Límites de uso](/docs/es/api-reference/rate-limits)).

| `status` | Significado | Qué hacer |
| - | - | - |
| `pending` | El trabajo fue aceptado y espera ser generado. | Sigue consultando. |
| `building` | El archivo se está generando. | Sigue consultando. |
| `ready` | El archivo está guardado. La respuesta ahora incluye `byteSize`, `checksum`, `downloadUrl` y `downloadExpiresAt`. | Descárgalo (paso 3). |
| `failed` | No se pudo generar el archivo. La respuesta incluye `error`. | Consulta [Si el archivo no se genera](#si-el-archivo-no-se-genera). |

```json 200 OK (building) theme={null}
{
  "exportId": "3f6c2a9e-0b1d-4c7a-9e55-1a2b3c4d5e6f",
  "status": "building",
  "reportType": "briefing",
  "period": "weekly"
}
```

```json 200 OK (ready) theme={null}
{
  "exportId": "3f6c2a9e-0b1d-4c7a-9e55-1a2b3c4d5e6f",
  "status": "ready",
  "reportType": "briefing",
  "period": "weekly",
  "byteSize": 1874,
  "checksum": "5d41c0e8b7a24f3e9c6b1d0a8f7e6d5c4b3a29180f1e2d3c4b5a69788796a5b4",
  "downloadUrl": "https://files.example.invalid/management-briefing-weekly.csv?signature=EXAMPLE",
  "downloadExpiresAt": "2026-09-17T14:05:00.000Z"
}
```

<ResponseField name="exportId" type="string" required>
  El id del trabajo que recibiste al pedir la exportación.
</ResponseField>

<ResponseField name="status" type="string" required>
  `pending`, `building`, `ready` o `failed`.
</ResponseField>

<ResponseField name="reportType" type="string" required>
  El reporte que contiene el archivo. Hoy siempre es `briefing`.
</ResponseField>

<ResponseField name="period" type="string" required>
  `daily` o `weekly`, según lo pedido.
</ResponseField>

<ResponseField name="byteSize" type="integer">
  El tamaño del archivo en bytes. Aparece cuando el estado es `ready`.
</ResponseField>

<ResponseField name="checksum" type="string">
  El SHA-256 del archivo, en 64 caracteres hexadecimales en minúscula. Aparece cuando el estado es `ready`.
</ResponseField>

<ResponseField name="downloadUrl" type="string">
  Un enlace de descarga firmado. Aparece solo cuando el estado es `ready`. Cada consulta emite un enlace nuevo.
</ResponseField>

<ResponseField name="downloadExpiresAt" type="string">
  Cuándo deja de funcionar el `downloadUrl` actual (UTC).
</ResponseField>

<ResponseField name="error" type="string">
  Un motivo legible por máquina. Aparece cuando el estado es `failed`. Un trabajo que falló una vez y se reintentó automáticamente conserva este campo mientras está en `building` y después de pasar a `ready`, así que revisa siempre `status` primero.
</ResponseField>

## Paso 3: descarga y verifica

* El `downloadUrl` funciona durante **5 minutos** después de la consulta que lo devolvió. Lleva su propia firma: no agregues tu clave de API ni ningún otro encabezado a la solicitud de descarga.
* Descarga de inmediato. No guardes ni compartas el enlace: mientras sea válido, cualquiera que lo tenga puede descargar el archivo. Si venció, vuelve a consultar el trabajo para obtener un enlace nuevo.
* El archivo se llama `management-briefing-daily.csv` o `management-briefing-weekly.csv`.
* Compara el SHA-256 de los bytes recibidos con `checksum`, y su tamaño con `byteSize`. Si no coinciden, vuelve a descargar.

<CodeGroup>
  ```bash cURL theme={null}
  curl -s -o management-briefing-weekly.csv "$DOWNLOAD_URL"
  sha256sum management-briefing-weekly.csv
  # The first value must equal the checksum field
  ```

  ```javascript JavaScript theme={null}
  import { createHash } from "node:crypto";
  import { writeFile } from "node:fs/promises";

  const file = Buffer.from(await (await fetch(job.downloadUrl)).arrayBuffer());
  const sha256 = createHash("sha256").update(file).digest("hex");
  if (sha256 !== job.checksum || file.length !== job.byteSize) {
    throw new Error("Export download is incomplete; download it again");
  }
  await writeFile("management-briefing-weekly.csv", file);
  ```

  ```python Python theme={null}
  import hashlib
  import requests

  file = requests.get(job["downloadUrl"], timeout=60).content
  if hashlib.sha256(file).hexdigest() != job["checksum"] or len(file) != job["byteSize"]:
      raise RuntimeError("Export download is incomplete; download it again")
  with open("management-briefing-weekly.csv", "wb") as handle:
      handle.write(file)
  ```
</CodeGroup>

## Ejemplo completo

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { createHash } from "node:crypto";
  import { writeFile } from "node:fs/promises";

  const BASE_URL = "https://api.muveya.com";
  const auth = { Authorization: `Bearer ${process.env.MUVEYA_API_KEY}` };
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

  async function exportBriefing(period = "weekly", language = "en") {
    const created = await fetch(`${BASE_URL}/v1/analytics/exports`, {
      method: "POST",
      headers: { ...auth, "Content-Type": "application/json", "Accept-Language": language },
      body: JSON.stringify({ period }),
    });
    if (created.status !== 202) throw new Error(`Export request failed: ${created.status}`);
    let job = await created.json();

    for (let attempt = 0; attempt < 100 && job.status !== "ready"; attempt += 1) {
      if (job.status === "failed") throw new Error(`Export failed: ${job.error}`);
      await sleep(3000);
      const polled = await fetch(`${BASE_URL}/v1/analytics/exports/${job.exportId}`, { headers: auth });
      if (polled.status === 429) {
        await sleep(Number(polled.headers.get("retry-after") ?? "1") * 1000);
        continue;
      }
      if (!polled.ok) throw new Error(`Export poll failed: ${polled.status}`);
      job = await polled.json();
    }
    if (job.status !== "ready") throw new Error("Export is taking too long; poll it again later");

    const file = Buffer.from(await (await fetch(job.downloadUrl)).arrayBuffer());
    if (createHash("sha256").update(file).digest("hex") !== job.checksum) {
      throw new Error("Checksum mismatch; poll again for a fresh link and retry");
    }
    await writeFile(`management-briefing-${period}.csv`, file);
    return job;
  }

  console.log(await exportBriefing("weekly", "es"));
  ```

  ```python Python theme={null}
  import hashlib
  import os
  import time
  import requests

  BASE_URL = "https://api.muveya.com"
  session = requests.Session()
  session.headers["Authorization"] = f"Bearer {os.environ['MUVEYA_API_KEY']}"

  def export_briefing(period="weekly", language="en"):
      created = session.post(
          f"{BASE_URL}/v1/analytics/exports",
          json={"period": period},
          headers={"Accept-Language": language},
          timeout=30,
      )
      if created.status_code != 202:
          raise RuntimeError(f"Export request failed: {created.status_code}")
      job = created.json()

      for _ in range(100):
          if job["status"] == "ready":
              break
          if job["status"] == "failed":
              raise RuntimeError(f"Export failed: {job['error']}")
          time.sleep(3)
          polled = session.get(f"{BASE_URL}/v1/analytics/exports/{job['exportId']}", timeout=30)
          if polled.status_code == 429:
              time.sleep(int(polled.headers.get("Retry-After", "1")))
              continue
          polled.raise_for_status()
          job = polled.json()
      else:
          raise RuntimeError("Export is taking too long; poll it again later")

      file = requests.get(job["downloadUrl"], timeout=60).content
      if hashlib.sha256(file).hexdigest() != job["checksum"]:
          raise RuntimeError("Checksum mismatch; poll again for a fresh link and retry")
      with open(f"management-briefing-{period}.csv", "wb") as handle:
          handle.write(file)
      return job

  print(export_briefing("weekly", "es"))
  ```
</CodeGroup>

## El archivo CSV

* Codificación UTF-8 con marca de orden de bytes, para que las hojas de cálculo muestren bien las tildes. Las líneas terminan en CRLF.
* Una fila de encabezado y luego una fila por cada cifra del resumen.
* Las celdas que contienen una coma, una comilla o un salto de línea van entre comillas. Una celda que empieza con `=`, `+`, `-`, `@`, una tabulación o un retorno de carro lleva delante una comilla simple (`'`), para que una hoja de cálculo nunca la ejecute como fórmula.

| Columna | Contenido |
| - | - |
| `metric` | La métrica de origen, por ejemplo `LOW_STOCK`, `APPROVAL_BACKLOG`, `CONSUMPTION_TREND` u `ORDER_CYCLE_TIME`. |
| `dimension` | La parte de una métrica de varias filas: un tramo de antigüedad, una etapa del ciclo o una serie de consumo. Vacía en las métricas de un solo valor. |
| `label` | La etiqueta para personas, en el idioma que pediste. |
| `value` | El número. **Vacío** cuando la métrica no tuvo muestra, nunca `0`. |
| `unit` | La unidad de `value`: `count`, `orders`, `movements`, `hours` o la unidad del catálogo de un insumo (`box`, `unit`, etc.). |
| `evidenceId` | La referencia reproducible de la métrica de origen, la misma que devuelven las operaciones de analítica. |

```csv management-briefing-weekly.csv (extracto, Accept-Language: es) theme={null}
metric,dimension,label,value,unit,evidenceId
LOW_STOCK,,Alertas bajo el mínimo,3,count,LOW_STOCK
EXPIRING_STOCK,,Alertas de cajas por vencer,1,count,EXPIRING_STOCK
PENDING_ORDERS,,Aprobaciones pendientes,2,count,PENDING_ORDERS
APPROVAL_BACKLOG,within_24h,Aprobaciones pendientes: menos de 24 horas,2,orders,APPROVAL_BACKLOG
CONSUMPTION_TREND,movements,Movimientos de consumo,48,movements,CONSUMPTION_TREND:granularity=week
CONSUMPTION_TREND,66d0a1f0c0ffee0000000401:box,"Nitrile gloves, size M",14,box,CONSUMPTION_TREND:granularity=week
ORDER_CYCLE_TIME,submit_to_dispatch,Tiempo de ciclo: de solicitud a despacho,26.5,hours,ORDER_CYCLE_TIME
ORDER_CYCLE_TIME,receive_to_close,Tiempo de ciclo: de recepción a cierre,,hours,ORDER_CYCLE_TIME
```

Las cifras son las mismas que devuelve `GET /v1/analytics/briefing` para el mismo período en el momento en que se generó el archivo. El nombre de cada insumo aparece tal como está en tu catálogo. Para saber qué significa cada métrica, consulta [Analítica de gestión](/docs/es/reports/management-analytics).

## Lo que el archivo nunca contiene

La exportación omite datos sensibles por diseño: contiene conteos, cantidades, duraciones, etiquetas e ids de evidencia. Nunca contiene costos, valores de pedidos, referencias de pacientes ni nombres de miembros del equipo. Cubre todas las sedes y bodegas de la clínica dental, como cualquier otra lectura con clave de API.

## Qué registra muveya

* Cada solicitud queda en la auditoría de tu clínica dental, con la clave de API como autora, el tipo de reporte y el período.
* Cuando se genera el archivo, la auditoría también registra su tamaño y su checksum SHA-256, para que luego puedas comparar el archivo que tienes con el registro.
* Los trabajos de exportación se conservan **7 días** desde la solicitud. Después, consultar el trabajo devuelve `404` con el código `common.not_found`: pide una exportación nueva.

## Si el archivo no se genera

| `error` | Significado | Qué hacer |
| - | - | - |
| `schedule_failed` | No se pudo poner el trabajo en cola para generarlo. Nunca consultas un trabajo así: el propio `POST` falla con `500` (`common.internal_error`) y no devuelve `exportId`. | Pide una exportación nueva. |
| `processing_error` | Falló la generación del archivo. muveya la reintenta automáticamente, así que el trabajo puede volver a `building` y terminar en `ready`. | Sigue consultando unos minutos. Si continúa en `failed`, pide una exportación nueva. |

Si las exportaciones nuevas siguen fallando, escribe a [team@muveya.com](mailto:team@muveya.com) con el `exportId`.

## Errores

| Estado | `code` | Causa |
| - | - | - |
| `400` | `common.invalid_request` | `period` no es `daily` ni `weekly`, o el cuerpo no es JSON válido |
| `401` | `api_keys.invalid` | Consulta [Autenticación](/docs/es/api-reference/authentication#respuestas-401-y-403) |
| `403` | `api_keys.scope_missing` | A la clave le falta `analytics:read` |
| `404` | `common.not_found` | Consulta de un `exportId` desconocido, de una exportación de otra clínica dental o de una con más de 7 días |
| `429` | `common.too_many_requests` | Consulta [Límites de uso](/docs/es/api-reference/rate-limits) |
| `500` | `common.internal_error` | No se pudo poner la exportación en cola. Pide una exportación nueva |

## Páginas relacionadas

* [Analítica de gestión](/docs/es/reports/management-analytics)
* [Scopes](/docs/es/api-reference/scopes)
* [Errores](/docs/es/api-reference/errors)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.