0031 — Títulos por posição, com atual e recusa total¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-10-02 · Decidido em: 2026-10-02
Contexto¶
Novo tipo de arquivo FLUXO_PAGAR_RECEBER (spec 026 do bi-comercial, dono da feature; migração
V22): o relatório FluxoPagarReceber que o ERP gera todo dia às 04:01 com os títulos em
aberto (a pagar e a receber) numa data — a posição. Shape verificado na amostra de 28/09/2026:
- cinco abas; Grupo, Filial, Cliente e Detalhado são visões só do RECEBER; o PAGAR existe apenas na SemQuebras (uma linha por título), que é a única aba ingerida;
- NBSP à esquerda de
DOCUMENTOeVCTO;VALORcom ruído binário (1159.3499999999999);FL/PCnuméricos;CCcomo"cod-nome"; CNPJ ou CPF com máscara; - a posição está no título "VALORES A RECEBER dd/mm/aaaa" da Grupo (e numa célula de data do cabeçalho da Detalhado); o "Total" da Grupo é o único total de controle (só do receber).
Um título não tem chave natural estável entre posições: pago, ele some; renegociado, muda de valor ou vencimento. O app (PowerSync) precisa só da posição mais recente; a diretoria quer o histórico guardado para análises futuras.
Decisão¶
-
bi_tituloguarda todas as posições, eatual = truesó na maiordata_posicao. O PowerSync sincronizaWHERE atual = 1; ao virar a posição, as linhas antigas saem do bucket e as novas entram, sem reset do banco local. O histórico fica no Postgres (~3 mil linhas por dia). -
Idempotência = replace da posição, numa transação: garante as filiais (só o código) → calcula o destino da posição (nova / histórico / substitui) → upsert diff-aware de
bi_centro_custo→DELETE FROM bi_titulo WHERE data_posicao = ?→ INSERT comatual = false→UPDATE … SET atual = (data_posicao = max)tocando só as linhas que mudam. Arquivo de posição anterior à maior grava o histórico sem mudar a atual. -
pg_advisory_xact_lockserializa as importações deste tipo. O executorprocessamentoroda 4 arquivos em paralelo; duas posições recalculandoatualao mesmo tempo poderiam terminar ambas atuais. O lock é por transação (solta no commit/rollback). -
Recusa total, sem gravar nada: aba SemQuebras ausente, posição não encontrada, cabeçalho incompleto ou qualquer linha inválida →
ResultadoProcessamento.Ignorado(statusIGNORADO, motivo com até 20 linhas emerro_mensagem). Arquivo ruim é recusa esperada, não falha técnica: não vai ao GlitchTip (log INFO do pipeline). O processador lê e valida tudo antes de escrever. -
Total de controle do RECEBER vira aviso, não recusa: Σ RECEBER × "Total" da Grupo; divergindo, grava e o resumo avisa.
-
Reenvio de
ERRO/IGNORADOreprocessa o mesmo registro (todos os tipos, inclusive pela porta única): antes, o checksum repetido batia no UNIQUE e devolvia 409 "Checksum duplicado (concorrência)", e umIGNORADOnão tinha caminho de reprocessamento — um arquivo recusado por bug do parser ficava preso mesmo depois da correção. -
A página
/xls/importarespera o processamento deste tipo (até 30 s) e mostra o resumo, que o processador devolve noResultadoProcessamento.Sucessoe o pipeline grava emxls_processamento.resumo(o processador não toca oxls_processamento: trava do ArchUnit). -
Detecção por conteúdo, logo após COTA_PETROBRAS: qualquer aba que comece com "Fluxo Pagar-Receber" — não só a SemQuebras, para que a falta dela seja recusada com mensagem clara em vez de "tipo não reconhecido".
Consequências¶
- 3ª estratégia sem upsert, ao lado do replace-por-período (0017) e do full-replace (0019):
replace-por-posição. Reimportar um arquivo diferente da mesma posição regenera os
ide manda ~3 mil remoções e inserções ao aparelho — de propósito (o checksum já barra o reenvio idêntico, e uma chave natural de título por posição não compensa a complexidade). Não "otimizar" para upsert sem rever esta decisão. - Credores do PAGAR não vão a
bi_fornecedor(que é do Relatório de Compras) nem clientes do RECEBER abi_cliente: CPF/CNPJ e nome ficam no próprio título, com máscara, para o join. - Centro de custo é dimensão nova (
bi_centro_custo), aChaveUniddo X-Adm — numeração distinta da filial. - A regra do PowerSync e as raw tables do app são passos externos (repos
../powersync/ebi-comercial); ordem de deploy: xls → app → regra. - Sem retenção de posições antigas por ora (~1 milhão de linhas/ano); reavaliar se pesar.
Alternativas consideradas¶
- Substituir tudo a cada arquivo (como a 0019): descartada — perde o histórico de posições.
- Sincronizar todas as posições: descartada — o app só usa a atual e o volume cresceria sem fim no aparelho.
- Chave natural por posição + upsert diff-aware: descartada — evitaria o churn no reenvio de arquivo corrigido da mesma posição, raro, ao custo de uma chave (tipo, documento, parcela, filial, CPF/CNPJ) que o ERP não garante.
- Pular a linha ruim e gravar o resto: descartada — o total importado deixaria de bater com o ERP sem ninguém ver.
- Manter a recusa como
ERRO: descartada — todo ERROR vai ao GlitchTip (norma de log da casa), e cada envio ruim seria um alarme falso.