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

# Paginação

> Iteração eficiente sobre listas grandes — parâmetros, envelope, exemplos em 5 linguagens, gotchas de ordenação.

Endpoints de listagem (`GET /colaborador`, `GET /registro-ponto`, etc.)
paginam resultados via query string. O padrão é **idêntico** em todos os
endpoints públicos da Pontua, então você pode encapsular uma única função
de paginação no seu código.

## Parâmetros

| Parâmetro | Tipo    | Obrigatório | Default                            | Limites            |
| --------- | ------- | ----------- | ---------------------------------- | ------------------ |
| `pagina`  | integer | sim         | —                                  | ≥ 0 (zero-indexed) |
| `limite`  | integer | não         | varia por endpoint (geralmente 25) | 1 – 100            |

```
GET /colaborador?pagina=0&limite=100
GET /colaborador?pagina=1&limite=100  ← próxima página
GET /colaborador?pagina=2&limite=100
```

<Note>
  **`pagina` é zero-indexed:** `pagina=0` é a primeira página. Não é
  `pagina=1` como em alguns sistemas. Tenha cuidado se sua aplicação faz
  `page = userInput - 1` por hábito.
</Note>

## Envelope de resposta

Todas as respostas paginadas usam o mesmo envelope:

```json theme={null}
{
  "pagina": 0,
  "limite": 25,
  "totalRegistros": 847,
  "resultados": [
    { /* item 1 */ },
    { /* item 2 */ },
    { /* ... */ }
  ]
}
```

| Campo            | Descrição                                                      |
| ---------------- | -------------------------------------------------------------- |
| `pagina`         | Página atual (eco do parâmetro enviado)                        |
| `limite`         | Limite usado (eco do parâmetro ou default aplicado se omitido) |
| `totalRegistros` | Total de itens em **todas** as páginas combinadas              |
| `resultados`     | Array com itens **desta** página                               |

## Calcular total de páginas

```javascript theme={null}
const totalPaginas = Math.ceil(totalRegistros / limite)
const ultimaPagina = totalPaginas - 1  // zero-indexed
```

## Iteração completa em 5 linguagens

<CodeGroup>
  ```javascript Node.js theme={null}
  async function* paginarTodos(client, path, params = {}) {
    let pagina = 0
    const limite = params.limite || 100

    while (true) {
      const data = await client.fetch(
        `${path}?${new URLSearchParams({ ...params, pagina, limite })}`,
      )

      yield* data.resultados

      const totalProcessado = (pagina + 1) * limite
      if (totalProcessado >= data.totalRegistros) return
      pagina++
    }
  }

  // Uso — async iterator (memory-efficient)
  const pontua = new PontuaClient({ token: process.env.PONTUA_API_TOKEN })
  for await (const colaborador of paginarTodos(pontua, '/colaborador', { status: 'ATIVO' })) {
    await processar(colaborador)
  }
  ```

  ```python Python theme={null}
  from typing import Iterator, Dict, Any

  def paginar_todos(client, path: str, **params) -> Iterator[Dict[str, Any]]:
      pagina = 0
      limite = params.pop('limite', 100)

      while True:
          data = client.request('GET', path, params={**params, 'pagina': pagina, 'limite': limite})

          yield from data['resultados']

          total_processado = (pagina + 1) * limite
          if total_processado >= data['totalRegistros']:
              return
          pagina += 1

  # Uso — generator (memory-efficient)
  pontua = PontuaClient()
  for colaborador in paginar_todos(pontua, '/colaborador', status='ATIVO'):
      processar(colaborador)
  ```

  ```go Go theme={null}
  type PaginatedResponse[T any] struct {
      Pagina         int `json:"pagina"`
      Limite         int `json:"limite"`
      TotalRegistros int `json:"totalRegistros"`
      Resultados     []T `json:"resultados"`
  }

  func PaginarTodos[T any](client *Client, path string, params url.Values) (<-chan T, <-chan error) {
      out := make(chan T)
      errc := make(chan error, 1)

      go func() {
          defer close(out)
          defer close(errc)

          pagina := 0
          limite := 100

          for {
              params.Set("pagina", strconv.Itoa(pagina))
              params.Set("limite", strconv.Itoa(limite))

              var data PaginatedResponse[T]
              req, _ := http.NewRequest("GET", path+"?"+params.Encode(), nil)
              if err := client.Do(req, &data); err != nil {
                  errc <- err
                  return
              }

              for _, item := range data.Resultados {
                  out <- item
              }

              if (pagina+1)*limite >= data.TotalRegistros {
                  return
              }
              pagina++
          }
      }()

      return out, errc
  }
  ```

  ```php PHP theme={null}
  <?php
  function paginarTodos(PontuaClient $client, string $path, array $params = []): Generator {
      $pagina = 0;
      $limite = $params['limite'] ?? 100;

      while (true) {
          $data = $client->request('GET', $path, [
              'query' => array_merge($params, ['pagina' => $pagina, 'limite' => $limite]),
          ]);

          foreach ($data['resultados'] as $item) {
              yield $item;
          }

          $totalProcessado = ($pagina + 1) * $limite;
          if ($totalProcessado >= $data['totalRegistros']) return;
          $pagina++;
      }
  }

  // Uso — generator (memory-efficient)
  $pontua = new PontuaClient();
  foreach (paginarTodos($pontua, '/colaborador', ['status' => 'ATIVO']) as $colaborador) {
      processar($colaborador);
  }
  ```

  ```ruby Ruby theme={null}
  def paginar_todos(client, path, **params)
    return enum_for(:paginar_todos, client, path, **params) unless block_given?

    pagina = 0
    limite = params.delete(:limite) || 100

    loop do
      data = client.request(:get, path, params: { **params, pagina: pagina, limite: limite })

      data['resultados'].each { |item| yield item }

      total_processado = (pagina + 1) * limite
      break if total_processado >= data['totalRegistros']
      pagina += 1
    end
  end

  # Uso — Enumerator (lazy)
  pontua = PontuaClient.new
  paginar_todos(pontua, '/colaborador', status: 'ATIVO').each do |colaborador|
    processar(colaborador)
  end
  ```
</CodeGroup>

## Padrão paralelo (mais rápido para datasets grandes)

Se a ordem dos resultados não importa, você pode paralelizar:

```javascript theme={null}
async function paginarParalelo(client, path, params, concurrency = 5) {
  // 1. Pega primeira página para descobrir total
  const primeira = await client.fetch(`${path}?pagina=0&limite=100`)
  const totalPaginas = Math.ceil(primeira.totalRegistros / 100)

  // 2. Dispara páginas restantes em paralelo, controlando concorrência
  const resultados = [...primeira.resultados]
  const paginas = Array.from({ length: totalPaginas - 1 }, (_, i) => i + 1)

  for (let i = 0; i < paginas.length; i += concurrency) {
    const batch = paginas.slice(i, i + concurrency)
    const responses = await Promise.all(
      batch.map((p) => client.fetch(`${path}?pagina=${p}&limite=100`)),
    )
    for (const r of responses) resultados.push(...r.resultados)
  }

  return resultados
}
```

⚠️ Não exagere na concorrência. **Máximo 5 simultâneas por token** é
seguro hoje. Veja [Rate Limits](/conceitos/rate-limits) para o roadmap
oficial.

## Gotchas conhecidos

<Warning title="Ordenação não-estável sob mutação concorrente">
  Se dados estão sendo modificados enquanto você itera, podem aparecer
  duplicatas (item lido em página 1 reaparece na página 3) ou faltar
  itens. Para datasets que mudam ativamente:

  * Use **filtro de período fechado** (`atualizadoAte=<data passada>`) onde aplicável
  * Ou aceite que pode haver pequena inconsistência (ok para dashboards, problemático para auditoria)
</Warning>

<Warning title="`totalRegistros` pode mudar entre páginas">
  Se um colaborador é deletado enquanto você itera, `totalRegistros` na
  página 5 pode ser **menor** que na página 0. Considere isso uma
  estimativa, não verdade absoluta.
</Warning>

<Warning title="Limite > 100 é silenciosamente capeado">
  `?limite=500` retorna no máximo 100 resultados (cap server-side).
  Sempre cheque `data.limite` no response — se não bate com o que enviou,
  use o real para calcular `totalPaginas`.
</Warning>

## Boas práticas

<CardGroup cols={2}>
  <Card icon="rocket" title="Use limite alto (50–100)">
    `limite=100` é 10x mais eficiente que `limite=10`. Reduz chamadas e
    pressão na API.
  </Card>

  <Card icon="filter" title="Filtre antes de paginar">
    Sempre que possível, use filtros (`status=ATIVO`, `dataInicio=...`)
    antes de paginar. Reduz `totalRegistros` e tempo total.
  </Card>

  <Card icon="database" title="Cache `totalRegistros`">
    Se está apenas paginando um resultado estático (ex.: dashboard), pegue
    `totalRegistros` da primeira página e não recalcule.
  </Card>

  <Card icon="hourglass" title="Streaming > arrays grandes">
    Em Node/Python/Go, use generators/async iterators para não carregar
    100 mil colaboradores em memória.
  </Card>
</CardGroup>

## Veja também

* [Rate Limits](/conceitos/rate-limits) — controle de concorrência paralela
* [Erros](/conceitos/erros) — `400 Bad Request` se passar parâmetros inválidos
* [Sincronizar frequência com ERP](/guias-praticas/sincronizar-frequencia-erp) — caso real de paginação incremental
