Skip to main content
Caso de uso clássico: sua empresa usa folha de pagamento externa (Domínio Sistemas, Sage, ContabilQuasar, ERP próprio) e precisa que a frequência do Pontua reflita lá diariamente para fechamento mensal. Este guia mostra o padrão cron noturno + cursor incremental que roda à 1h da manhã, puxa frequências de D-1 e popula seu ERP.

Estratégia

1. Cron 01:00 BRT
2. Calcular data ontem (D-1)
3. Verificar se D-1 está em fechamento concluído (opcional mas recomendado)
4. Paginar por GET /frequency/daily?dataInicio=D-1&dataFim=D-1
5. Para cada linha, upsertar no ERP
6. Salvar cursor (timestamp da última sync) para auditoria

Implementação Node.js

async function syncDiarioFrequencia(token) {
  const TOKEN = token || process.env.PONTUA_API_TOKEN
  const ontem = new Date(Date.now() - 86400000).toISOString().slice(0, 10)
  const headers = { Authorization: `Bearer ${TOKEN}` }

  console.log(`Sync frequência de ${ontem}`)

  // 1. Validação opcional: só sync se fechamento aberto/concluído
  // (algumas empresas preferem aguardar fechamento; outras puxam preview)

  // 2. Paginar todas as frequências do dia
  let pagina = 0
  let totalLido = 0
  let totalUpsertado = 0
  let totalErros = 0

  while (true) {
    const params = new URLSearchParams({
      dataInicio: ontem,
      dataFim: ontem,
      pagina: String(pagina),
      limite: '100',
    })

    const resp = await fetch(
      `https://api.pontua.com.br/frequency/daily?${params}`,
      { headers },
    )

    if (!resp.ok) {
      throw new Error(`Falha ao listar frequências: ${resp.status}`)
    }

    const { resultados, totalRegistros } = await resp.json()
    totalLido += resultados.length

    // 3. Upsert cada linha no ERP
    for (const freq of resultados) {
      try {
        await meuErp.upsertFrequencia({
          colaboradorCpf: freq.colaboradorCpf,  // ou ID conforme ERP
          data: freq.data,
          horasTrabalhadasMinutos: parseHHMMToMin(freq.totais.horasTrabalhadas),
          horasExtrasMinutos: parseHHMMToMin(freq.totais.horasExtras),
          status: freq.status,
        })
        totalUpsertado++
      } catch (e) {
        console.error(`Erro upsert ${freq.colaboradorId} ${freq.data}:`, e.message)
        totalErros++
      }
    }

    // 4. Próxima página?
    if ((pagina + 1) * 100 >= totalRegistros) break
    pagina++
  }

  // 5. Log/cursor
  console.log(`Sync ${ontem} concluído: ${totalLido} lidos, ${totalUpsertado} upsertados, ${totalErros} erros`)
  await storage.set('pontua.ultimo_sync_frequencia', new Date().toISOString())

  return { lido: totalLido, upsertado: totalUpsertado, erros: totalErros }
}

function parseHHMMToMin(hhmm) {
  const [h, m] = hhmm.split(':').map(Number)
  return h * 60 + m
}

// Setup cron (ex: node-cron)
import cron from 'node-cron'
cron.schedule('0 1 * * *', () => syncDiarioFrequencia().catch(console.error), {
  timezone: 'America/Sao_Paulo',
})

Tratamento de retry

Se o cron falhar (rede, timeout), use backoff exponencial + idempotência:
async function syncComRetry(maxAttempts = 5) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await syncDiarioFrequencia()
    } catch (e) {
      if (attempt === maxAttempts - 1) throw e
      const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000
      console.warn(`Tentativa ${attempt + 1} falhou, aguardando ${delay}ms`)
      await new Promise((r) => setTimeout(r, delay))
    }
  }
}
A idempotência vem do upsert no ERP: rodar o cron 2x no mesmo dia não cria duplicatas (colaboradorCpf + data é a chave natural).

Catch-up de períodos esquecidos

Cron pode ter ficado quebrado por dias. Para alcançar atrasados:
async function catchUpFrequencia(token, diasAtras = 7) {
  const hoje = new Date()

  for (let i = 1; i <= diasAtras; i++) {
    const data = new Date(hoje.getTime() - i * 86400000)
      .toISOString().slice(0, 10)

    console.log(`Catch-up ${data}`)
    await syncDiaEspecifico(token, data)
  }
}

Recomendação de horário

HoraVantagem
01:00 BRTMadrugada, pouca pressão na API. Recomendado.
06:00 BRTAntes do expediente — frequência já consolidada
Hora comercialNão recomendado — competindo com tráfego app/portal
Se você atende múltiplas UNs (contador), distribua os jobs em janelas de 5-10 min para não saturar.

Gotchas

Se D-1 ainda está em período de fechamento aberto, a frequência pode mudar com aprovações de ajuste posteriores. Para snapshot imutável, use GET /relatorio/<tipo> após o fechamento concluir — ver Relatórios.
Colaborador faltou ontem, sync trouxe status: FALTA, mas hoje o RH aprovou atestado retroativo. Próximo sync vai mudar o status — seu ERP precisa aceitar update do mesmo registro, não criar duplicata.
data no response é date pura (yyyy-MM-dd) sem timezone. Já representa BRT. Não converta para UTC ao salvar no seu DB se ele trata como timestamp — pode resultar em “dia anterior” depending no fuso.

Veja também