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

# Rastrear un trabajo en lote

> Consulta el estado y los contadores de un trabajo en lote, lee los resultados por objetivo y descarga el archivo de resultados.

Después de [enviar una operación en lote](/submit-bulk-operation), síguela con dos consultas: `getBulkOperationJob` para el estado del trabajo y contadores agregados, y `getBulkOperationItems` para el detalle por objetivo. La finalización es solo por sondeo — no hay webhooks de lote.

## Requisitos

* `Authorization: Basic <API_KEY>`
* La capacidad de API de lote en tu aplicación.
* El trabajo debe pertenecer a tu aplicación — un id de trabajo desconocido o de otra aplicación devuelve no encontrado.

## Consulta — estado del trabajo

Sondéalo hasta que `status` sea terminal (`COMPLETED`, `COMPLETED_WITH_ERRORS`, `FAILED` o `CANCELLED`).

```graphql theme={null}
query BulkJob($bulkJobId: UUID!) {
  getBulkOperationJob(bulkJobId: $bulkJobId) {
    jobId
    status
    operation
    requestedTargetCount
    acceptedItemCount
    succeededItemCount
    failedItemCount
    skippedItemCount
    resultUrl
    resultUrlExpiresAt
  }
}
```

```json theme={null}
{
  "data": {
    "getBulkOperationJob": {
      "jobId": "9c1e6f2a-1d4b-4a2e-8f0c-2b7e5a9d1234",
      "status": "COMPLETED_WITH_ERRORS",
      "operation": "CREATE_TRANSFER",
      "requestedTargetCount": 3,
      "acceptedItemCount": 3,
      "succeededItemCount": 2,
      "failedItemCount": 1,
      "skippedItemCount": 0,
      "resultUrl": "https://storage.googleapis.com/…",
      "resultUrlExpiresAt": "2024-06-01T15:20:00Z"
    }
  }
}
```

## Consulta — resultados por objetivo

Paginada por cursor (100 por página) y filtrable por estado del ítem — filtra a `FAILED` para conciliar solo los fallos, o lee `resultResourceId` para mapear una escritura exitosa al recurso de Fluz que produjo.

```graphql theme={null}
query BulkItems($bulkJobId: UUID!, $status: BulkItemStatus, $after: String) {
  getBulkOperationItems(bulkJobId: $bulkJobId, status: $status, limit: 100, after: $after) {
    totalCount
    hasNextPage
    nextCursor
    items {
      externalReferenceId
      accountId
      itemIndex
      status
      resultResourceId
      errorCode
      errorMessage
      metadataResults { recordId success errorCode errorMessage }
    }
  }
}
```

```json theme={null}
{
  "data": {
    "getBulkOperationItems": {
      "totalCount": 3,
      "hasNextPage": false,
      "nextCursor": null,
      "items": [
        {
          "externalReferenceId": "user-123",
          "accountId": "a1b2…",
          "itemIndex": 0,
          "status": "SUCCEEDED",
          "resultResourceId": "7453414c-e290-4bb5-9516-79b212bd84cc",
          "errorCode": null,
          "errorMessage": null,
          "metadataResults": null
        },
        {
          "externalReferenceId": "user-456",
          "accountId": "c3d4…",
          "itemIndex": 1,
          "status": "FAILED",
          "resultResourceId": null,
          "errorCode": "PT-0007",
          "errorMessage": "The sender does not have sufficient balance to complete this transfer.",
          "metadataResults": null
        }
      ]
    }
  }
}
```

## Descargar el archivo de resultados

Seleccionar `resultUrl` genera una URL de descarga firmada de corta duración (\~15 minutos), producida bajo demanda — consultar el estado sin ella se mantiene económico. Ábrela (o `curl -L`) para descargar un archivo **NDJSON**, una fila por objetivo.

* **Exportaciones** (`GET_*_EXPORT`) — los saldos/transacciones exportados.
* **Escrituras** (`CREATE_TRANSFER`, `DEPOSIT_CASH_BALANCE`) — un libro de resultados: `{ externalReferenceId, accountId, status, resultResourceId, resourceType, errorCode, errorMessage }` por objetivo.

`resultUrl` es `null` hasta que el trabajo sea terminal, y para trabajos que no produjeron archivo. Solicita el campo nuevamente para una URL fresca una vez que expire (`resultUrlExpiresAt`).

## Argumentos

| Parameter   | Type             | Description                                                                     |
| ----------- | ---------------- | ------------------------------------------------------------------------------- |
| `bulkJobId` | `UUID!`          | El id del trabajo devuelto por `submitBulkOperation`.                           |
| `status`    | `BulkItemStatus` | *(solo ítems)* Filtra a `QUEUED`, `RUNNING`, `SUCCEEDED`, `FAILED` o `SKIPPED`. |
| `limit`     | `Int`            | *(solo ítems)* Tamaño de página, máximo y predeterminado 100.                   |
| `after`     | `String`         | *(solo ítems)* Cursor opaco de la `nextCursor` de una página anterior.          |

## Campos de la respuesta

| Field                                                         | Description                                                                                                             |
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `status` (job)                                                | `QUEUED` → `RUNNING` → `COMPLETED` / `COMPLETED_WITH_ERRORS`, o `FAILED` / `CANCELLED`.                                 |
| `succeededItemCount` / `failedItemCount` / `skippedItemCount` | Contadores agregados por ítem.                                                                                          |
| `resultUrl` / `resultUrlExpiresAt`                            | URL de descarga firmada para el archivo de resultados, y cuándo expira.                                                 |
| `items[].status`                                              | `SUCCEEDED`, `FAILED` o `SKIPPED` (terminal), o `QUEUED` / `RUNNING`.                                                   |
| `items[].resultResourceId`                                    | El recurso de Fluz que el ítem produjo en caso de éxito (p. ej., un id de transferencia, un `cash_balance_deposit_id`). |
| `items[].errorCode` / `errorMessage`                          | Motivo legible por máquina y mensaje cuando `FAILED` o `SKIPPED`.                                                       |
| `items[].metadataResults`                                     | Resultados por edición para ítems `UPDATE_TRANSACTION_METADATA`; `null` para otras operaciones.                         |
| `totalCount`                                                  | El conteo total de ítems del trabajo en todos los estados — no el conteo de la página filtrada.                         |

<Note>
  `getBulkOperationItems` y el archivo de resultados son dos vistas de los mismos datos por ítem. Usa la consulta de ítems para conciliar programáticamente (filtra a `FAILED`, mapea `resultResourceId`); usa el archivo de resultados para extraer todo el libro de una vez para trabajos grandes.
</Note>

## Errores

Estas consultas fallan en su totalidad solo por la solicitud en sí — un objetivo inválido nunca es un error de solicitud, es un resultado por ítem `FAILED`/`SKIPPED` (ver abajo).

| Error                 | HTTP | When                                                                                                                                                                                            |
| --------------------- | ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BulkApiAccessDenied` | 403  | Tu aplicación no tiene la capacidad de API de lote.                                                                                                                                             |
| `BulkJobNotFound`     | 404  | El id del trabajo es desconocido **o** pertenece a otra aplicación. Foráneos e inexistentes son deliberadamente indistinguibles — la respuesta no filtra nada sobre los trabajos de otras apps. |
| `ArgumentsInvalid`    | 400  | Se proporcionó a `getBulkOperationItems` un cursor `after` mal formado.                                                                                                                         |

## Códigos de fallo por ítem

Un ítem `FAILED` o `SKIPPED` lleva un `errorCode` legible por máquina y un `errorMessage` humano. Los códigos `SKIPPED` se asignan al enviar (el objetivo nunca ingresó a la cola); los códigos `FAILED` provienen ya sea de la validación al enviar o, para ítems aceptados, del servicio downstream en el momento de la ejecución.

**Cualquier operación:**

| `errorCode`                 | Significado                                                                |
| --------------------------- | -------------------------------------------------------------------------- |
| `TARGET_NOT_CONNECTED`      | El id no resuelve a un usuario conectado a tu aplicación.                  |
| `INVALID_TARGET_IDENTIFIER` | El id no es un identificador de objetivo válido.                           |
| `INSUFFICIENT_SCOPE`        | Al consentimiento del usuario le falta un scope que la operación requiere. |
| `ACCOUNT_NOT_PERMITTED`     | El usuario no ha permitido esta operación en la cuenta solicitada.         |

**Solo `CREATE_TRANSFER`:**

| `errorCode`                          | Significado                                                                                                                         |
| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| `SOURCE_MATCHES_DESTINATION`         | `from` y `to` resolvieron a la misma cuenta.                                                                                        |
| `AMBIGUOUS_DESTINATION_CASH_BALANCE` | El usuario de destino permite múltiples cuentas de gasto sin predeterminada — el destino es ambiguo.                                |
| *downstream*                         | En la ejecución, un fallo de pago expone el propio código/mensaje del servicio de pagos (p. ej., saldo insuficiente del remitente). |

**Solo `DEPOSIT_CASH_BALANCE`:**

| `errorCode`                          | Significado                                                                                                                                                              |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `PAYMENT_METHOD_NOT_PERMITTED`       | El `bankCardId` / `bankAccountId` no pertenece al usuario objetivo (faltante y no perteneciente comparten el mismo código — no se filtra la existencia del instrumento). |
| `ACCOUNT_NOT_PERMITTED`              | No se encontró un asiento de cuenta de gasto para el objetivo, o el saldo de destino no está permitido.                                                                  |
| `AMBIGUOUS_DESTINATION_CASH_BALANCE` | El objetivo permite múltiples cuentas de gasto sin predeterminada — pasa `userCashBalanceId` en el ítem.                                                                 |
| *downstream*                         | En la ejecución, un fallo de depósito expone el propio código/mensaje del servicio de compras (p. ej., monto por debajo del mínimo).                                     |

<Note>
  Los códigos con prefijo de categoría son asignados por Fluz; una fila *downstream* significa que el ítem lleva literalmente el código de error propio del servicio ejecutor, por lo que esos códigos están abiertos. Programa contra los códigos que manejas y trata los desconocidos como un fallo genérico — lee `errorMessage` para el motivo legible por humanos.
</Note>
