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

# Gerar relatório de Banco de Horas

> Disparar geração assíncrona de relatório consolidado de banco de horas, fazer polling até concluir e baixar arquivo. Padrão aplicável a qualquer relatório.

Banco de Horas é dos relatórios mais usados em integrações de RH e
contabilidade — mostra saldos, movimentações, datas de prescrição por
colaborador no período. Útil para:

* Conferir banco antes de quitação em folha
* Auditar banco vencido (Lei 13.467/2017)
* Anexar em acordo coletivo

## Padrão geral aplicável a qualquer relatório

Este fluxo serve para **qualquer endpoint** `POST /relatorio/<tipo>`,
não só banco-horas:

```
1. POST /relatorio/<tipo>          → retorna { relatorioId }
2. GET /relatorio/{id}             → polling até "CONCLUIDO" ou "ERRO"
3. POST /relatorio/download        → retorna URL ou base64 do arquivo
4. (Cliente persiste arquivo localmente para auditoria histórica)
```

## Implementação completa

```javascript theme={null}
async function gerarRelatorioBancoHoras(token, opts = {}) {
  const TOKEN = token || process.env.PONTUA_API_TOKEN
  const headers = {
    Authorization: `Bearer ${TOKEN}`,
    'Content-Type': 'application/json',
  }

  const params = {
    dataInicio: opts.dataInicio,        // ex: '2026-04-01'
    dataFim: opts.dataFim,              // ex: '2026-04-30'
    formato: opts.formato || 'PDF',     // PDF ou CSV
    // demais filtros opcionais conforme schema
  }

  console.log('1. Disparando geração...')
  const dispatchResp = await fetch(
    'https://api.pontua.com.br/relatorio/banco-horas',
    { method: 'POST', headers, body: JSON.stringify(params) },
  )
  if (!dispatchResp.ok) {
    const erro = await dispatchResp.json()
    throw new Error(`Dispatch falhou: ${erro.message}`)
  }
  const { relatorioId } = await dispatchResp.json()
  console.log(`   relatorioId: ${relatorioId}`)

  console.log('2. Polling até concluir (timeout 30 min)...')
  let status
  for (let attempt = 0; attempt < 60; attempt++) {
    const statusResp = await fetch(
      `https://api.pontua.com.br/relatorio/${relatorioId}`,
      { headers },
    )
    status = await statusResp.json()

    if (status.status === 'CONCLUIDO') {
      console.log(`   pronto após ${attempt + 1} tentativa(s)`)
      break
    }
    if (status.status === 'ERRO') {
      throw new Error(`Geração falhou: ${status.mensagemErro || JSON.stringify(status)}`)
    }

    // Backoff: 5s, 7.5s, 11s, 17s, ..., capped 60s
    const delay = Math.min(5000 * Math.pow(1.5, attempt), 60000)
    await new Promise((r) => setTimeout(r, delay))
  }

  if (status?.status !== 'CONCLUIDO') {
    throw new Error('Timeout aguardando geração de relatório')
  }

  console.log('3. Baixando...')
  const downloadResp = await fetch(
    'https://api.pontua.com.br/relatorio/download',
    { method: 'POST', headers, body: JSON.stringify({ relatorioId }) },
  )
  const download = await downloadResp.json()
  console.log(`   download disponível em: ${download.url || '[base64]'}`)

  return download
}

// Uso
const arquivo = await gerarRelatorioBancoHoras(TOKEN, {
  dataInicio: '2026-04-01',
  dataFim: '2026-04-30',
  formato: 'PDF',
})
```

## Persistir o arquivo localmente

Você deve **guardar uma cópia local** do arquivo gerado por questões de
auditoria (Portaria 671 exige retenção de 5 anos para registros, 10
anos para folha):

```javascript theme={null}
import fs from 'node:fs/promises'

async function persistirRelatorio(download, caminhoLocal) {
  // Se download.url é URL S3
  if (download.url) {
    const fileResp = await fetch(download.url)
    const buffer = Buffer.from(await fileResp.arrayBuffer())
    await fs.writeFile(caminhoLocal, buffer)
  }
  // Se download.conteudoBase64
  else if (download.conteudoBase64) {
    const buffer = Buffer.from(download.conteudoBase64, 'base64')
    await fs.writeFile(caminhoLocal, buffer)
  }

  console.log(`Arquivo salvo em ${caminhoLocal}`)
}
```

## Filtros úteis

`POST /relatorio/banco-horas` aceita filtros que reduzem o tamanho
e o tempo de geração:

| Filtro                   | Quando usar                                   |
| ------------------------ | --------------------------------------------- |
| `colaboradorIds`         | Relatório de demitido ou subset específico    |
| `departamentoIds`        | Relatório por departamento (folha de gerente) |
| `formato: 'CSV'`         | Análise em planilha em vez de PDF             |
| `dataInicio` / `dataFim` | Sempre — define o período de cálculo          |

Schema completo do payload em [Referência da API](/api-reference#tag/Relatório).

## Quando usar PDF vs CSV

| Caso                                      | Formato                      |
| ----------------------------------------- | ---------------------------- |
| Anexar em recibo / arquivo do colaborador | PDF (assinado PAdES)         |
| Análise em Excel / BI                     | CSV                          |
| Auditoria oficial / fiscalização          | PDF (tem assinatura digital) |
| Importar para outro sistema               | CSV                          |

## Boas práticas

<CardGroup cols={2}>
  <Card icon="moon" title="Rode em horário noturno">
    Geração de relatório do mês todo pode levar minutos. Faça em horário
    de baixa carga (22h-6h BRT).
  </Card>

  <Card icon="repeat" title="Cache do relatorioId">
    Se o download falhar, **não regenere**. Reuse o mesmo `relatorioId`
    para tentar baixar de novo. Geração custa caro.
  </Card>

  <Card icon="filter" title="Filtre máximo possível">
    Não puxe banco-horas do ano todo se só precisa do mês corrente.
    Filtros antes = relatório menor + mais rápido.
  </Card>

  <Card icon="vault" title="Guarde 5+ anos">
    Por questões fiscais e Portaria 671, mantenha cópia local de cada
    relatório por **no mínimo 5 anos**. Não dependa só do Pontua.
  </Card>
</CardGroup>

## Troubleshooting

### Status fica em PROCESSANDO indefinidamente

Tipicamente significa fila acumulada ou erro na geração. Após 30 min
sem mudar, abra chamado em `tecnologia@pontua.com.br` mencionando o
`relatorioId`.

### Relatório veio vazio

Filtros muito restritivos (ex.: período sem movimentação, departamento
sem colaboradores). Repita sem filtros para confirmar que dados existem.

### Erro "fechamento aberto"

Banco de Horas se baseia em saldos consolidados — se o período tem
fechamento aberto, alguns valores podem não estar definitivos. Para
relatório oficial, gere após `POST /fechamento/concluir/{id}`. Ver
[Fechamento](/referencia/fechamento).

## Veja também

* [Relatórios](/referencia/relatorios) — todos os tipos disponíveis
* [Banco de Horas](/referencia/banco-horas) — endpoints de saldo em tempo real
* [Fechamento](/referencia/fechamento) — quando relatórios ficam imutáveis
* [Idempotência](/conceitos/idempotencia) — não disparar 2x o mesmo
