0019 — Cota Petrobrás: full-replace, allow-list de produto e alias de endpoint¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-08-18 · Decidido em: 2026-08-18
Contexto¶
Novo tipo de arquivo (spec 011, migração
V19): a planilha "Cota Petrobrás mês.xlsx" — cota mensal de volume (m³) concedida pela
Petrobras por polo de distribuição e produto. Shape verificado no arquivo real (2022–2026):
uma aba por ano (^\d{4}$) + aba Planilha1 (resumo derivado, ignorar); cabeçalho
POLO | PRODUTO | JANEIRO..DEZEMBRO; célula POLO mesclada (só na 1ª linha do bloco);
coluna de ruído JANEIRO.2024 (bleed do mês seguinte, colide com o Janeiro da aba 2024) e
TOTAL PRODUTO/ANO; linha TOTAL MÊS; célula "_" (sem cota) e vazia (mês futuro); e uma
side-table de anotação solta (na aba 2023: Gas/S10 com número na coluna de mês, produto
em branco). A planilha entra pela porta genérica (POST /api/xls/processar) e pela página manual
do botão "Importar XLS" do bi-comercial.
Decisão¶
-
bi_cota_petrobras: PK surrogateuuidv7(), sem chave natural de linha. A planilha é a matriz inteira;(competencia, polo, produto)poderia repetir sob correções. -
Idempotência/correção = full-replace (não replace-por-período como Compras/0017): numa transação,
deletarTudo()(DELETE FROM bi_cota_petrobras) +inserirBatch()da matriz inteira. Guard de vazio: arquivo detectado como cota mas sem nenhum produto canônico com número não roda o DELETE — nunca há caso legítimo de esvaziar a matriz via upload. -
Guard de emissão = allow-list de produto canônico (
Diesel S10/Diesel S500/Gasolina/Diesel R5, comparado pornorm= NFD + strip acento + lowercase + colapso de espaços; grava a forma canônica). É o único guard — mata de uma vez a linhaTOTAL MÊS(produto vazio), as separadoras e a side-table de anotação. Só os 12 meses canônicos são mapeados por header normalizado;JANEIRO.2024/TOTAL PRODUTO/ANO/ruído são descartados. -
Detecção por conteúdo, prioridade logo após COMPRAS: existe aba
^\d{4}$e cujo cabeçalho começa com POLO + PRODUTO + JANEIRO. Não usa o header da 1ª aba física (éPlanilha1). -
Endpoint manual: alias
/xls/importar(canônico) +/compras/importar(herdado, vivo). A página aberta aceita a allow-list{COMPRAS, COTA_PETROBRAS}e no máximo uma planilha de cota por lote. A rota herdada continua servida porque o botão do bi-comercial já está deployado nela (segredoBI_COMERCIAL_XLS_IMPORT_TOKEN).
Consequências¶
- Diverge do diff-aware e também do replace-por-período (0017): é full-replace, a 2ª exceção documentada na cobertura de DELETE do cap. 4 do livro.
- Premissa: ≤1 planilha de cota por lote. O
DELETE FROMfull-table não é seguro sob concorrência (o executor roda até 4 arquivos em paralelo; oORDEM_INSERCAOanti-deadlock só cobre row-locks deON CONFLICT, não um DELETE de tabela inteira). Mitigado pela guarda ≤1 cota/batch na página de import. UmLOCK TABLEfoi considerado e descartado (over-engineering para um risco operacional ~zero). - Produto novo/renomeado some em silêncio? Não: linha que parecia dado (polo + valor de mês)
com produto não-vazio e não-canônico vira
LOG.warn(sinal pro operador), não descarte mudo. - Correção reprocessa a matriz inteira → um checkpoint PowerSync no reenvio corrigido (o checksum
do
xls_processamentobarra o reenvio idêntico). - Contrato com o cliente Flutter (painel de cotas) e sync rules (
../powersync/) assumem esse schema — passo externo, fora deste repo.
Alternativas consideradas¶
- Replace-por-período (como 0017): descartada para a v1 — a planilha é a matriz completa (todos os anos), full-replace é mais simples. Fica como caminho futuro se a cota passar a ser enviada partida por ano (Open Question OQ2 da spec 011).
- Guard "pular polo que começa com TOTAL" (proposto no prompt): frágil — não pega a
side-table de anotação da aba 2023 (polo
Gas/S10, produto vazio, com número). A allow-list de produto canônico é mais robusta e cobre os três descartes de uma vez. LOCK TABLE bi_cota_petrobras IN EXCLUSIVE MODEno full-replace: descartada — fecharia o buraco de dois lotes simultâneos, mas a probabilidade operacional é ~zero e a premissa ≤1 cota/lote já basta. Simplicidade primeiro.- Renomear a rota
/compras/importar→/xls/importar(hard): descartada — quebraria o botão do bi-comercial já deployado. Alias mantém as duas vivas; o bi-comercial migra depois e aí a herdada some. Rename da classeComprasImportControllerfica deferido (cosmético).
Correção 2026-09-14: o ponteiro para o
CLAUDE.md/HOWTO-DEPLOY.mdpassou para a página dona do fato; os dois arquivos deixaram de carregar esse conteúdo.Correção 2026-09-14: o rename da classe saiu junto com o package-by-feature (0030):
ComprasImportControllerpassou aimportacao.ImportacaoXlsController.