Service Attestations
AlphaO serviço foi prestado? Quem paga confirma — lido direto do sistema de escala
Em vez de pedir ao prestador um PDF dizendo que trabalhou, o bloco lê o sistema de escala e repasse de quem paga e avisa quando um repasse nasce, muda ou é cancelado.
- Fundo ou fintech que antecipa recebível de prestador de serviço (médico, plantonista, terceirizado)
- Operação de crédito que precisa de lastro confirmado pela fonte, não declarado pelo cedente
- Conferência manual de demonstrativo de repasse em PDF ou planilha
- Integração ponto a ponto com cada sistema de escala dentro do produto
- Motor de antecipação ou de crédito — isso é o painel, que consome os eventos
- Sistema de escala — o bloco só LÊ a fonte, nunca escreve nela
- Cadastro de pessoa ou empresa — isso é People e Companies
10 endpoints em 1 recurso.
Resumo executivo
O Service Attestations conecta uma organização ao sistema de escala e repasse de quem paga (hoje, o Pega Plantão) e guarda, para cada serviço prestado e cada repasse gerado, o que a fonte afirmou: quem prestou, para quem, quando, por quanto e quando será pago. Cada atestado tem versão e hash; quando a fonte muda um valor ou retira um item, o bloco percebe e avisa por evento.
É agnóstico: não sabe de antecipação, médico ou guia. Um "atestado" é só "a fonte afirma que este serviço foi prestado por este sujeito, para este pagador, por este valor". O painel decide o que fazer com isso (no primeiro uso, a antecipação de plantão confirmado).
Enquanto a credencial real do Pega não existe, tudo roda com o provedor simulado (MOCK),
determinístico, com o mesmo formato. Trocar para o real é criar uma conexão PEGA_PLANTAO.
| Atributo | Valor |
|---|---|
| Identificador | service-attestations |
| Categoria | Documentos |
| Escopo | Tenant-scoped (por organização; sem subconta) |
| Porta (standalone) | 3042 |
| Path alias | @service-attestations |
| Status | Alpha desde 2026-10 |
| Depende de | IAM (autenticação) |
O problema
negócioO cenário. Um fundo antecipa ao médico o repasse que a organização social (OS) vai pagar no dia 20. O lastro é "o médico trabalhou e a OS vai pagar".
O que trava hoje.
- A prova é um PDF ou print enviado pelo próprio cedente.
- O valor muda depois (glosa, plantão cancelado) e ninguém fica sabendo até o vencimento.
- Cada produto que quisesse ler a escala teria de embutir um cliente HTTP do provedor.
O custo de não resolver. Antecipar sobre lastro declarado é risco de fraude e de glosa sem aviso.
Proposta de valor
negócio| Antes | Depois |
|---|---|
| PDF de demonstrativo enviado pelo cedente | Repasse lido da fonte do pagador, com data e valor |
| Mudança descoberta no vencimento | Evento changed/cancelled na leitura seguinte |
| Integração por produto | Uma porta, vários provedores, um contrato |
Lastro confirmado pela fonte. O dado vem de quem paga, não de quem recebe.
Mudança vira evento. Versão + hash por atestado; o consumidor não precisa reler tudo.
Trocar de fonte não muda o consumidor. Simulado e real devolvem o mesmo canônico.
Casos de uso reais
negócioCaso 1 — Antecipação de plantão confirmado Cenário ilustrativo
Fundo que antecipa repasses de médicos PJ de uma OS que usa o Pega Plantão.
A dor. Comprovante de serviço declarado; glosa descoberta no vencimento.
A solução. Conexão PEGA_PLANTAO com o CNPJ da OS em payerDocument; o painel assina
service-attestations.attestation.* e lê GET /attestations/:id para casar a nota com o repasse.
O resultado. Só antecipa sobre repasse confirmado; cancelamento chega como evento.
Caso 2 — Dossiê com evidência de plantões Cenário ilustrativo
Mesa de crédito montando dossiê do cedente.
A dor. Sem histórico de serviço prestado.
A solução. GET /attestations?kind=shift&subjectDocument=<CPF> devolve os plantões lidos.
O resultado. Evidência datada e com origem.
Caso 3 — Demonstração e homologação sem credencial Cenário ilustrativo
Staging e demos antes de a fonte real liberar acesso.
A solução. Conexão MOCK; settings.mockOverrides provoca mudança e cancelamento sob demanda.
O resultado. O fluxo inteiro (eventos, painel, dossiê) roda igual ao real.
Mercado e diferenciais
negócioPanorama. Quem antecipa recebível de serviço confere demonstrativo à mão ou integra cada fonte dentro do próprio produto.
| Critério | Catalisa | Integração por cliente |
|---|---|---|
| Fonte nova | Um driver novo | Projeto novo |
| Versão e cancelamento | Nativos, com evento | Raramente |
| Multi-tenancy | Por organização, credencial cifrada | Variável |
Nossos diferenciais
- Canônico + versão + evento. Difícil de copiar sem o motor de leitura e a trilha.
- Simulado com o mesmo formato da fonte. Homologa o fluxo inteiro sem credencial.
Quando escolher o concorrente. Se a operação tem uma única fonte, sem multi-tenant e sem necessidade de trilha, uma integração direta pode bastar.
Modelo de cobrança e ROI
negócioUnidade de cobrança. Conexão ativa por mês (proposta; ainda não medida pelo Billing).
O que dispara custo. Conexões ativas e volume de atestados lidos.
ROI. Uma glosa evitada numa antecipação paga meses de conexão. Sem número defensável ainda.
Arquitetura
Decisões não óbvias.
- O motor é separável.
src/service-attestations/sync/não conhece atestado: recebe itens{ kind, externalId, windowDate, payload, raw }e fala com o domínio por interfaces (SyncStore,SyncHooks,SyncRunStore). Há teste que falha se aparecer termo do domínio ou import de fora do diretório. É o corte para o bloco irmão de rastreio da garantia viva (Traccar, resumo por lote): quando ele existir,sync/sobe parasrc/sharedsem mudar o contrato (regra de dois — não se extrai antes do segundo consumidor). - Cancelamento só com leitura completa. O driver diz quais tipos leu por inteiro
(
completeKinds). Um dia que falhou deixa o tipo incompleto e nada dele é cancelado. - Token em memória. O token do Pega dura ~10 min e cabe numa leitura; não se persiste.
- Sem
@Service()resolvido pelo container. Tudo é montado à mão emsrc/shared/container/modules/service-attestations.ts(defeito do typedi semdesign:paramtypes).
Monolito vs. standalone. As rotas existem nos dois. A leitura agendada só liga no processo
standalone (main.ts).
Conceitos e modelo de dados
Glossário
| Termo | Significa |
|---|---|
| Conexão | AttestationProviderConfig: a ligação de uma organização com uma fonte |
| Atestado | Attestation: um item afirmado pela fonte, kind = payout (repasse) ou shift (plantão) |
| Janela | windowDays (padrão 45) dias até hoje, no fuso da conexão |
windowDate | O dia pelo qual a fonte devolve o item (geração do repasse, início do plantão) |
| Leitura | AttestationSyncRun: uma passada, manual ou agendada |
Modelo de dados
| Modelo Prisma | Tabela | Propósito | Campos-chave |
|---|---|---|---|
AttestationProviderConfig | attestation_provider_configs | Conexão por organização | providerType, credentials (cifrada), settings, lastSync* |
Attestation | attestations | Atestado canônico | único (organizationId, configId, kind, externalId), contentHash, version, status |
AttestationRaw | attestation_raws | Bruto da fonte, à parte | attestationId, raw |
AttestationSyncRun | attestation_sync_runs | Cada leitura | status, read/created/changed/cancelled/failed, error |
AttestationSimulatedProviderGrant | attestation_simulated_provider_grants | Concessão do simulado em produção | uma ativa por organização |
Canônico (Attestation): subjectDocument (CPF), subjectName, subjectBusinessDocument
(CNPJ), subjectBusinessName, payerDocument, payerName, location, sector, serviceStart,
serviceEnd (ISO), competence (YYYY-MM), amountCents, payoutDate (YYYY-MM-DD),
payoutGroup, status, contentHash, version, firstSeenAt, lastSeenAt.
Máquina de estados
Referência da API
Prefixo /service-attestations/api/v1. Respostas { data } (listas com meta: { total, page, pageSize }). Erros { error, message, details }.
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /provider-configs | Cria conexão (credencial no corpo) | SERVICE_ATTESTATIONS_ADMIN |
GET | /provider-configs | Lista | SERVICE_ATTESTATIONS_READ |
GET | /provider-configs/:id | Detalhe | SERVICE_ATTESTATIONS_READ |
PATCH | /provider-configs/:id | name, settings (merge raso), isActive, isDefault, credentials | SERVICE_ATTESTATIONS_ADMIN |
DELETE | /provider-configs/:id | Remoção lógica (204) | SERVICE_ATTESTATIONS_ADMIN |
POST | /provider-configs/:id/test | { ok, message } | SERVICE_ATTESTATIONS_ADMIN |
POST | /provider-configs/:id/sync | Lê agora → SyncRun (409 se já houver leitura) | SERVICE_ATTESTATIONS_ADMIN |
GET | /sync-runs?configId=&page&pageSize | Leituras | SERVICE_ATTESTATIONS_READ |
GET | /attestations?kind&status&subjectDocument&subjectBusinessDocument&payerDocument&competence&from&to&configId&page&pageSize | Atestados | SERVICE_ATTESTATIONS_READ |
GET | /attestations/:id[?includeRaw=true] | Detalhe (bruto só sob pedido) | SERVICE_ATTESTATIONS_READ |
GET/PUT/DELETE | /admin/platform/organizations/:organizationId/simulated-provider | Concessão do simulado | operador da plataforma |
POST /provider-configs
{
"name": "Pega Plantão — OS Bem Viver",
"providerType": "PEGA_PLANTAO",
"credentials": { "clientId": "…", "clientSecret": "…", "username": "…", "password": "…" },
"settings": {
"baseUrl": "https://api.pegaplantao.com.br",
"payerDocument": "11222333000181",
"payerName": "OS Bem Viver",
"windowDays": 45,
"syncIntervalMinutes": 15,
"timezone": "America/Sao_Paulo"
},
"isActive": true,
"isDefault": true
}{
"name": "Pega Plantão — OS Bem Viver",
"providerType": "PEGA_PLANTAO",
"credentials": { "clientId": "…", "clientSecret": "…", "username": "…", "password": "…" },
"settings": {
"baseUrl": "https://api.pegaplantao.com.br",
"payerDocument": "11222333000181",
"payerName": "OS Bem Viver",
"windowDays": 45,
"syncIntervalMinutes": 15,
"timezone": "America/Sao_Paulo"
},
"isActive": true,
"isDefault": true
}Campo de settings | Tipo | Padrão | Descrição |
|---|---|---|---|
baseUrl | URL | — | Obrigatório no PEGA_PLANTAO; https em produção |
tokenUrl | URL | <baseUrl>/token | Endpoint OAuth2 |
payerDocument / payerName | CNPJ / texto | — | Quem paga (a fonte não informa) |
windowDays | 1–120 | 45 | Janela de leitura |
syncIntervalMinutes | 5–1440 | 15 | Intervalo da leitura agendada |
locationCodes / sectorCodes | texto[] | — | Filtro por CodigoLocal / CodigoSetor |
timezone | IANA | America/Sao_Paulo | Fuso dos dias |
scenario | texto | default | Só MOCK: default, empty, unavailable, partial |
mockOverrides | [{ externalId, action: 'cancel' | 'amount', amountCents? }] | — | Só MOCK: provoca cancelled/changed |
Resposta 201: a conexão com hasCredentials — nunca credentials.
Erros
| Status | Código | Quando |
|---|---|---|
400 | VALIDATION | Corpo inválido; Pega sem baseUrl ou credencial completa; CNPJ inválido |
403 | FORBIDDEN (SIMULATED_PROVIDER_NOT_GRANTED) | MOCK em produção sem concessão |
404 | NOT_FOUND | Fora da organização |
409 | CONFLICT | Nome repetido; leitura já em andamento |
Início rápido
1. Conectar o simulado (staging)
curl -s -X POST "$BB/service-attestations/api/v1/provider-configs" -H "x-api-key: $KEY" \
-H 'content-type: application/json' \
-d '{"name":"Simulado","providerType":"MOCK","settings":{"payerDocument":"11222333000181","payerName":"OS Teste"}}'curl -s -X POST "$BB/service-attestations/api/v1/provider-configs" -H "x-api-key: $KEY" \
-H 'content-type: application/json' \
-d '{"name":"Simulado","providerType":"MOCK","settings":{"payerDocument":"11222333000181","payerName":"OS Teste"}}'2. Ler agora
curl -s -X POST "$BB/service-attestations/api/v1/provider-configs/$ID/sync" -H "x-api-key: $KEY"curl -s -X POST "$BB/service-attestations/api/v1/provider-configs/$ID/sync" -H "x-api-key: $KEY"3. Consultar os repasses
curl -s "$BB/service-attestations/api/v1/attestations?kind=payout&status=confirmed" -H "x-api-key: $KEY"curl -s "$BB/service-attestations/api/v1/attestations?kind=payout&status=confirmed" -H "x-api-key: $KEY"4. Provocar mudança e cancelamento (smoke)
curl -s -X PATCH "$BB/service-attestations/api/v1/provider-configs/$ID" -H "x-api-key: $KEY" \
-H 'content-type: application/json' \
-d '{"settings":{"mockOverrides":[{"externalId":"MOCK-REP-202609-P01","action":"cancel"},{"externalId":"MOCK-REP-202609-P02","action":"amount","amountCents":500000}]}}'
curl -s -X POST "$BB/service-attestations/api/v1/provider-configs/$ID/sync" -H "x-api-key: $KEY"
# → changed: 1, cancelled: 1; eventos service-attestations.attestation.changed|cancelledcurl -s -X PATCH "$BB/service-attestations/api/v1/provider-configs/$ID" -H "x-api-key: $KEY" \
-H 'content-type: application/json' \
-d '{"settings":{"mockOverrides":[{"externalId":"MOCK-REP-202609-P01","action":"cancel"},{"externalId":"MOCK-REP-202609-P02","action":"amount","amountCents":500000}]}}'
curl -s -X POST "$BB/service-attestations/api/v1/provider-configs/$ID/sync" -H "x-api-key: $KEY"
# → changed: 1, cancelled: 1; eventos service-attestations.attestation.changed|cancelledReceitas
Trocar MOCK → PEGA_PLANTAO. Crie uma conexão PEGA_PLANTAO com a credencial real e
baseUrl; teste (POST …/test); desative a MOCK (PATCH { "isActive": false }). Nada muda no
consumidor: mesmo canônico, mesmos eventos.
Reagir a eventos. Assine service-attestations.attestation.* no webhooks-engine e busque o
detalhe em GET /attestations/:id — o evento não leva dado pessoal.
Integração com outros building blocks
- webhooks-engine: entrega os eventos do
iam-eventsàs assinaturas. - audit-trail: mapeia
service-attestations.attestation|sync|provider_config. - Operador da plataforma:
service-attestations.simulated-provider.*vai ao stream do operador (@shared/lib/operator-events), nunca a webhook.
Eventos (payload sem PII):
| Evento | Payload |
|---|---|
service-attestations.attestation.confirmed | attestationId, configId, provider, kind, externalId, status, amountCents, payoutDate, competence, version |
service-attestations.attestation.changed | o mesmo + previousAmountCents, changedFields |
service-attestations.attestation.cancelled | o mesmo |
service-attestations.sync.failed | configId, provider, syncRunId, reason |
service-attestations.raw-retention.purged | organizationId, deleted, retentionDays, cutoff — por organização afetada a cada passada da retenção; só a contagem |
Configuração e operação
| Variável | Padrão | Descrição |
|---|---|---|
SERVICE_ATTESTATIONS_CREDENTIAL_MASTER_KEY | — | 64 hex; cifra a credencial da fonte. Obrigatória |
SERVICE_ATTESTATIONS_SIMULATED_PROVIDER_ORGANIZATION_IDS | vazia | Reserva da trava do simulado em produção (* só em staging) |
SERVICE_ATTESTATIONS_SYNC_ENABLED | true | Leitura agendada |
SERVICE_ATTESTATIONS_SYNC_TICK_SECONDS | 60 | Tique do agendador |
SERVICE_ATTESTATIONS_SYNC_MAX_PER_TICK | 20 | Conexões lidas por tique |
SERVICE_ATTESTATIONS_RAW_RETENTION_DAYS | 90 | Retenção do bruto da fonte (attestation_raws), pela última regravação. 0 desliga. O atestado canônico nunca é apagado |
BANKING_PLATFORM_ORGANIZATION_IDS | vazia | Quem é operador (concessão do simulado) |
Migrations: prisma/migrations/20261016180000_service_attestations_core e
20261016200000_service_attestations_raw_retention_index (índice em updated_at; ambas
idempotentes). No Launch, pela fase 0 (scripts/launch/), com
MIGRATIONS="20261016180000_service_attestations_core 20261016200000_service_attestations_raw_retention_index".
Retenção do bruto: agendador próprio (jobs/raw-retention.scheduler.ts, LockedPass
service-attestations:raw-retention:sweep:lock) a cada 6 h, primeira passada 60 s após subir.
Apaga em lotes de 500 (FOR UPDATE SKIP LOCKED, seguro com N réplicas), no máximo 200 lotes ou
8 min por passada; o resto fica para a próxima. Uma linha de log por passada
(service-attestations: retenção do bruto concluída, com deleted) e o evento
service-attestations.raw-retention.purged por organização afetada. Depois de apagado, o
detalhe com includeRaw=true devolve raw: null; a próxima leitura que mudar o atestado regrava.
Provedor PEGA_PLANTAO: leituras GET /api/v1/demonstrativos/repasse?dataDeGeracao= (dia a
dia), GET /api/v1/plantoes?filtro.dataInicio&filtro.dataFim (janelas de 7 dias; 204 = vazio),
GET /api/v1/fornecedores (paginado, paginaAtual/itensPorPagina), POST /api/v1/profissionais/situacao (nomes, melhor esforço). Token POST /token (Basic do cliente,
grant_type=password); renova com < 60 s; 401 → renova uma vez; refresh tenta POST, cai para
GET com query e, por fim, refaz o login. Nenhuma escrita: lista de leitura conferida antes do
envio. Intervalo mínimo de 250 ms entre chamadas.
Padrões nossos (declarados). Competência do repasse do Pega = mês anterior à DataRepasse,
com serviço do primeiro ao último dia dela (a fonte não informa). Duas linhas de repasse com a
mesma chave no mesmo dia viram um repasse com a soma. externalId sem código da fonte =
<CodigoGrupoGeracao>-<hash16> (nunca CPF).
Simulado (MOCK). 12 profissionais (CPF e CNPJ válidos), dois hospitais, setores UTI Adulto,
Pronto-socorro, Centro cirúrgico, Enfermaria clínica e UTI Neonatal; 4 a 8 plantões de 12 h por
mês; repasse mensal por profissional = soma dos plantões, gerado no dia 5 e pago no dia 20 (útil)
do mês seguinte.
Regra da prévia (para sempre haver repasse a receber): a competência em curso já sai como
repasse a partir do dia 1 dela — para que sempre exista um repasse a receber com prazo para adiantar (o anterior vence no dia 20) — com o mesmo
externalId, o mesmo valor (a escala do mês inteiro) e a mesma payoutDate (dia 20 útil do mês
seguinte); só o windowDate é o dia 1. No dia 5 do mês seguinte ela fecha e o windowDate passa
a ser o dia 5 (o windowDate não entra no hash, então não há changed). A data de referência é o
until da janela. Resultado: em qualquer dia, todo profissional tem ao menos um repasse com
payoutDate no futuro — dias 5–19, o fechado do mês anterior; dias 20–4, a prévia do mês em curso.
Meses já fechados não mudam.
Segurança e compliance
- Credencial cifrada (aes-256-gcm), decifrada só ao montar o driver; nunca em resposta, log,
evento ou
SyncRun. Mensagens de erro da fonte não levam corpo nem segredo. - Escopo por organização em toda consulta; fora dela é
404. - Eventos sem CPF, nome ou CNPJ. O
externalIdnunca carrega CPF. - Fonte real só por
httpsem produção; driver só leitura. - Simulado em produção só com concessão do operador (ou lista de reserva).
Limitações conhecidas
- Sem subconta (
X-Subaccount-Ide chave de subconta são recusados pelo middleware). - Sem retenção automática de
attestation_raws. - O exemplo da doc do Pega corta os campos do plantão: a identificação do profissional é
procurada por nomes prováveis (
CodigoProfissional,CPF…) e confirmada na primeira leitura real. - Feriados não entram no "dia útil" do simulado.
Perguntas frequentes
Como trocar do simulado para o Pega? Criar uma conexão PEGA_PLANTAO com a credencial real.
Nada mais muda.
Por que um atestado sumiu como cancelled? Uma leitura completa da janela não o devolveu.
Leituras parciais nunca cancelam.
O painel guarda a senha do Pega? Não. A credencial vai direto ao bloco e não sai dele.