Wiki: Consolidação Contábil

Documentação do pipeline de consolidação contábil — atualizada em 29/05/2026.

1. Objetivo

Reproduzir, a partir dos balancetes brutos do Protheus, a coluna Cons YTD da aba Consolidated do .xlsb que a contadora publica como demonstração consolidada do grupo Sinqia. O alvo é match contábil ao centavo com a planilha auditada.

Em produção, isto substitui o trabalho manual de fim de mês de juntar 8 balancetes, aplicar eliminações intercompany, calcular participação minoritária, e formatar o DRE/BP consolidado.

2. Visão geral do pipeline

Três camadas com responsabilidades isoladas, batizadas como bronze / silver / gold.

Upload de .xlsx/.xls Protheus (1 por empresa, 9 no total: 8 operacionais + Proforma) │ ▼ ingestion.py — parser + hash + idempotência bronze.balancetes (raw, 1 linha por arquivo) + bronze.balancete_linhas (~1.444 por balancete) │ ▼ silver_builder.py — agrega filiais, resolve de-para SQIA→EVTC silver.saldos_por_competencia (1 linha por empresa × conta SQIA × competência) │ ▼ gold_builder.py — aplica os 5 tipos de eliminação + linha de minoritários silver.eliminacoes_calculadas (tipos 1/2/3/4/5) + gold.consolidacao_resultado + gold.minority_interest_lines │ ▼ apps/api/app/routers/consolidacao.py — leitura SQIA-first com YTD/Variação/PTD GET /consolidacao → tela "Consolidação" + export XLSX/PDF

Cada camada é idempotente (DELETE + INSERT pelo filtro de competência). Mudou regra de eliminação? POST /pipeline/recalc-all recompõe silver + gold de TODAS as competências em uma transação só.

3. Bronze — ingestão de balancetes

Formatos aceitos

Detecção automática

O nome do arquivo precisa carregar a competência no padrão MM.YYYY (ex.: Sinqia Tec 03.2026.xlsx) e o nome da empresa em qualquer posição. Um mapa FILENAME_TO_COMPANY em parser.py identifica os 8 alvos: sinqia s.a, sinqia tec, torq, fepweb, quiteja, lote45, compliasset, proforma.

Validação Protheus + tolerância de aba renomeada

O parser checa 2 coisas:

  1. Nome da aba bate o padrão NN-<código-empresa-11dig> - Balancete de — se NÃO bater (ex.: alguém renomeou pra 'balancete limpo'), gera um warning não-fatal mas continua. Aceitamos a divergência porque a empresa já é detectada pelo nome do arquivo.
  2. Cabeçalho da row 1: Conta · Descricao · Saldo anterior · Debito · Credito · Mov periodo · Saldo atual. Essa é a validação forte: se não bater, rejeita com erro.

Idempotência por SHA-256

O mesmo arquivo subido 2x retorna status='already_ingested'. Hash diferente pra mesma (empresa, competência) → retorna 409 com modal de confirmação; ao aceitar, o antigo vira status='replaced' (preservado pra audit) e o novo entra como status='validated'.

Status do batch

StatusSignificado
createdLinha em bronze.upload_batches criada, ainda processando
ingestedTodos os files do batch OK
partialAlguns OK, outros falharam. UI lista cada um com seu motivo
failedNenhum file OK
archivedManual via SQL — soft-archive antigo

Erros longos (stack trace Python) viram <details> expansível com botão "📋 Copiar tudo" na tela de upload.

Auto-rebuild

Por default POST /pipeline/upload?auto_rebuild=true dispara build_silver + build_gold pras competências afetadas logo após o ingest, dentro da mesma request.

4. Silver — agregação por (empresa × conta × competência)

silver_builder.py consome bronze e produz silver.saldos_por_competencia. Uma linha por (company_id, chart_sqia_id, competência), com saldo_inicial, saldo_final, débito e crédito agregados.

Duas fontes possíveis

O snapshot em core.valores_iniciais serve a 2 propósitos:

Pra competências > data_base (fev/2026 em diante), silver vem de bronze.balancete_linhas — com saldo_inicial real (= saldo_final do mês anterior, rolagem do Protheus).

5. Gold — eliminações e consolidação

gold_builder.py é o coração contábil. Em ordem:

  1. 3a: Investimento × PL na controladora (tipo 2A, via _apply_investment_eliminations_pure).
  2. 3b: Equity de subsidiária (capital, reservas, resultado) nas controladas (tipo 2B, via _apply_subsidiary_equity_eliminations_pure).
  3. 3c: Calcula minoritária por controlada parcial (gold.minority_interest_lines).
  4. 3d: Wire da soma de minoritária na elim tipo 4 em 44102001 RESULTADO COM PARTICIPAÇÃO MINORITÁRIA, descontando a amortização mais-valia da Proforma (PPA vehicle).
  5. 3e: Cancela equity fictício da Proforma (4 contas) via elim tipo 2, replicando a aba Eliminations do canon.
  6. 4: Equivalência × Apuração (tipo 4) nos pares cadastrados em core.equity_account_pairs.
  7. 5a: Intercompany — lado A e lado B (tipo 1, via SQL com vigência).
  8. 5b: Dividendos a receber e a pagar (tipo 3, proporcional ao %).
  9. 5c: Ajustes manuais (tipo 5, de core.ajustes_manuais).

O resultado é gravado em silver.eliminacoes_calculadas (1 linha por eliminação aplicada, com FK pra regra original via source_*) e em gold.consolidacao_resultado.

6. Plano de contas

Códigos SQIA × EVTC

Versionamento por competência

core.contas_contabeis tem 1 linha por (codigo SQIA × competência) e armazena metadados editáveis: descrição, EVTC code, FS classification, subgrupo, DVA. Após cada ingestão, contas_contabeis_sync.py faz upsert: contas que aparecem no balancete entram automaticamente, marcadas como incompletas se ainda não têm EVTC. A tela Configurações → Plano de Contas permite preencher inline e usa autofill do chart_evtc quando você seleciona um EVTC code.

account_types

core.account_types mapeia o prefixo (2 dígitos do SQIA) pra classificação:

PrefixoTipoConvenção PTD
11, 12, 13AtivoPTD = YTD (estoque)
21, 22, 23Passivo / PLPTD = YTD (estoque)
31, 32ReceitaPTD = saldo_final − saldo_inicial
41, 42, 43Despesa / CustoPTD = saldo_final − saldo_inicial
5Apuração transitóriaPTD = saldo_final − saldo_inicial

Flags especiais

Em core.chart_sqia:

7. Estrutura societária

Empresas

EmpresaTipoControladora%
Sinqia S.A.consolidadora
Sinqia TecnologiaoperacionalSinqia S.A.100,00%
TorqoperacionalSinqia S.A.100,00%
FepWeboperacionalSinqia S.A.100,00%
QuitejáoperacionalSinqia S.A.51,00%
Lote 45operacionalSinqia Tecnologia56,32%*
CompliassetoperacionalSinqia Tecnologia60,00%
Proformaveículo PPA

* Lote 45 mudou de 52,0001% (jan/2026) pra 56,3201% (fev/2026 em diante) — ver /organograma/timeline.

Holdings

core.equity_holdings registra a relação controladora → controlada com o % de participação e vigência (valid_from / valid_to). A tela Organograma permite criar versão nova quando o % muda (POST /organograma/holdings/{id}/version).

Pares investimento × equivalência

core.equity_account_pairs liga a controladora à conta SQIA do investimento que ela carrega no balancete e à conta da equivalência patrimonial. Cada par dispara eliminação tipo 2 (investimento) e tipo 4 (equivalência).

Vigência das regras (valid_from / valid_to)

Todas as 3 famílias de regras (intercompany, dividends, equity_pairs) e o organograma têm valid_from + valid_to. O gold_builder filtra:

WHERE r.is_active
  AND :competencia >= r.valid_from
  AND (r.valid_to IS NULL OR :competencia <= r.valid_to)

Permite, por exemplo, criar uma regra IC que só vale de jan/2026 a mar/2026 e que desliga automaticamente em abril sem precisar inativar manualmente.

8. Os 5 tipos de eliminação

Cadastrados em core.tipos_eliminacao:

Tipo 1: Intercompany (contas a pagar × contas a receber)

Pares (lado A × lado B) cadastrados em core.regras_intercompany: ex.: 11305001 CONTAS A RECEBER SINQIA TEC (lado A na Sinqia S.A.) ↔ 21401004 DIVIDENDOS A PAGAR (lado B na Sinqia Tec).

O motor elimina o saldo de cada lado na empresa onde tem saldo, independente. A regra também aceita filtro de empresas (company_short_names): se vazio, vale pra todas; se setado, só pras listadas. Um group_id permite agrupar regras visualmente (não afeta cálculo) — ver tooltip ⓘ na tela.

Tipo 2: Investimento × Patrimônio Líquido

Pra cada par (controladora, controlada, conta_investimento, conta_equivalência) em core.equity_account_pairs:

  1. 2A: elimina o saldo da conta_investimento na controladora.
  2. 2B: elimina o equity da controlada (24 contas marcadas com is_subsidiary_equity=TRUE: todas as 23xxx + 51111111 APURAÇÃO DE RESULTADOS).

A partir de maio/2026 também usado pra cancelar equity fictício da Proforma (4 contas), replicando o canon (ver §10 Empresas virtuais).

Tipo 3: Dividendos

core.regras_dividends com 2 subtipos:

Tipo 4: Equivalência × Apuração + linha de minoritários

A equivalência patrimonial registrada na controladora é eliminada contra a apuração de resultado da controlada — evita dupla contagem do lucro. O par é cadastrado em core.equity_account_pairs (mesma tabela do tipo 2).

Ainda no tipo 4, o step 3d do gold_builder lança a linha de minoritários em 44102001 RESULTADO COM PARTICIPAÇÃO MINORITÁRIA — ver §9.

Tipo 5: Ajustes manuais

core.ajustes_manuais permite lançar reclassificações ou ajustes pontuais que não cabem nos tipos 1–4. Cadastrável na tela Configurações → Ajustes (ou via POST /ajustes). Aplicado como elim tipo 5 no gold_builder, com source_ajuste_id preservando rastreabilidade.

Exemplo em prod: ajuste de −R$ 14,39 em jan/2026 na conta 44102001 pra fechar arredondamento de minoritária, conforme planilha canon.

9. Participação minoritária

Pra cada controlada com participação < 100% (Quitejá 51%, Lote 45 56,32% atual, Compliasset 60%), o pipeline calcula uma linha de minoritária:

resultado da controlada = saldo final de 23301004 (RESULTADO DO EXERCÍCIO)
parcela individual      = (1 − participação) × resultado_controlada
ajuste mais-valia PPA   = soma dos saldos das contas "MAIS VALIA" da Proforma
                          (43101008 AMORTIZAÇÃO MAIS VALIA + 42207005 REVERSÃO)
valor da minoritária    = Σ parcelas − ajuste_mais_valia_PPA + ajustes_manuais

Por que subtrair a mais-valia da Proforma

O PPA (Purchase Price Allocation) foi pago pela Sinqia ao adquirir Compliasset / Lote 45 / Quitejá. A despesa anual de amortização é exclusiva do controlador e não é compartilhada com os minoritários. O canon Sinqia desconta esse valor da linha 44102001; o pipeline replica a partir de maio/2026 (commit da40643).

Validação contra canon (jan/fev/mar 2026)

MêsΣ (1−%) × resultado− mais-valiaelim 44102001canon
Jan/20261.932.465,37-357.267,721.575.197,651.575.183,26*
Fev/20263.224.757,52-702.189,122.522.568,402.612.670,40
Mar/20264.434.402,58-372.023,324.062.379,264.152.481,26

* Delta jan (-R$ 14,39): ajuste manual já lançado. Delta fev/mar (+R$ 90.102): ajuste manual mensal pendente que a contadora faz no .xlsb e ainda não lançou aqui.

10. Convenções e sinal

Códigos e formato

Sinal e natureza

Saldos no Protheus vêm com sufixo D (devedor) ou C (credor), em strings pt-BR (141.138,91 D). O parser converte pra Decimal com sinal:

Eliminações sempre carregam o sinal contrário ao saldo que estão neutralizando.

YTD, Variação e PTD

O Consolidado expõe 3 colunas pra cada conta, alinhadas com o canon:

ColunaSignificadoComo calcula
YTDSaldo acumulado até a competênciaSUM(saldo_final)
VariaçãoMudança vs. mês anteriorYTD_atual − YTD_mês_anterior (NULL se o mês anterior não está em silver)
PTDPosição do período correnteBS (prefixo 1/2): igual ao YTD (estoque). Resultado (3/4/5): igual à Variação (movimento)

Linhas com agregado zero mas distribuído por empresa

Contas de rateio (ex.: 41208020 COMPARTILHAMENTO CUSTOS - FACILITIES) frequentemente têm combined = 0 mas saldos espelhados não-zero entre as empresas (um lado central, vários rateando). Essas linhas são exibidas no Consolidado — sem isso, o cheque "soma da coluna = 0" não fecharia.

Empresas virtuais (veículos PPA)

A Proforma não é operacional — é veículo contábil pra Purchase Price Allocation. Carrega os intangíveis das aquisições (ágio, marcas, software, carteira de clientes) + contingências, em ~24 contas ativas. Marcada com entity_type = 'ppa_vehicle'.

Estratégia de consolidação (canon-aligned, maio/2026): Proforma entra integralmente em silver, incluindo as 4 contas equity fictícias (23201004, 23301004, 23301007, 51111111). O gold_builder step 3e cria elim tipo 2 que cancela esses saldos no Cons YTD final. Mesma estratégia da aba Eliminations do canon. Antes (até maio/2026) a exclusão era feita em silver — funcionava pro Cons YTD mas quebrava o cheque "soma da coluna Proforma = 0" porque os ativos PPA continuavam visíveis sem contra-partida.

11. UI da consolidação

Tabela principal

Controles globais

Export

GET /consolidacao/export?format=xlsx ou format=pdf. Inclui:

12. Auth e gestão de contas

JWT customizado

Sem dependência de lib JWT. app/auth.py implementa payload_b64.signature_b64 onde payload é JSON com {sub, username, name, role, exp} e signature é HMAC-SHA256 com EVTC_AUTH_SECRET. TTL = 7 dias.

Papéis

Clique no nome do usuário no canto superior direito → /admin/usuarios. Não-admin vê só troca de senha.

Audit log

Toda request com método > GET é registrada em core.audit_log: user_id, user_email, método, path, query_string, status_code, duration_ms, IP, user_agent + request_body (JSON parsed e com campos sensíveis redacted — password, senha, token, secret, api_key).

Requests sem token válido aparecem com user_email=NULL, exibidas como (anônimo) na tela Logs. Casos típicos: login com senha errada (401), curl sem header Authorization, token expirado. Não é bug — é audit de tentativa sem auth.

13. Stack e deploy

Containers (docker-compose)

ServiçoImagemPorta internaFunção
evtc_dbpostgres:16-alpine5432Postgres com schemas core/bronze/silver/gold
evtc_apipython:3.11-slim8000FastAPI + SQLAlchemy + openpyxl/pyxlsb/xlrd
evtc_webnode:20-alpine3000Node.js puro (template literal). UI da app
evtc_caddycaddy:2-alpine80/443Reverse proxy + Let's Encrypt (só em prod)
evtc_design_systemnode:20-alpine6006Storybook (só dev local)

Schemas Postgres

core    → metadados (users, companies, chart_sqia, chart_evtc, de_para,
           equity_holdings, equity_account_pairs, regras_*, ajustes_manuais,
           account_types, tipos_eliminacao, valores_iniciais, contas_contabeis,
           audit_log, app_config)
bronze  → upload_batches, balancetes, balancete_linhas
silver  → saldos_por_competencia, eliminacoes_calculadas
gold    → consolidacao_resultado, consolidacao_runs, minority_interest_lines

Deploy

GCP shared project (positive-oven-471913-k8), VM e2-small em us-central1-a. CI/CD via GitHub Actions (Workload Identity Federation, sem chave de SA):

  1. Push em main → build das imagens API + Web em paralelo, push pro Artifact Registry.
  2. SCP do docker-compose.prod.yml e migrations pra VM.
  3. Update do IMAGE_TAG, docker compose pull + up -d.
  4. Smoke test /health.

Migrations DB não são automáticas: aplicadas manualmente via psql -f packages/db/migrations/NNNN_*.sql. Comportamento intencional pra evitar mudanças destrutivas no deploy.

HTTPS: https://consolidacao-evertec.beorange.app (Caddy + LE auto-renovação, A record na GoDaddy).

Design system

CSS único em /styles.css (concatenação de tokens + componentes de apps/design-system/src/). Catálogo em Storybook 8 na porta 6006 (dev). Namespace evtc-*, modificadores BEM (evtc-btn--ghost, evtc-table__num).

14. Endpoints da API

Pipeline e ingestão

MétodoRotaDescrição
POST/pipeline/uploadUpload multipart (.xlsx/.xls) com auto-rebuild + detecção de conflito (409)
POST/pipeline/ingest?directory=…Ingere todos os arquivos de um diretório
POST/pipeline/silver?competencia=…Rebuild silver pra uma competência
POST/pipeline/gold?competencia=…Rebuild gold pra uma competência
POST/pipeline/run-all?competencia=…Pipeline completo (ingestão + silver + gold)
POST/pipeline/recalc-allRecalcula silver+gold de TODAS as competências em transação única
POST/pipeline/sync-allSincroniza assets da pasta Continuação

Leitura consolidada

MétodoRotaDescrição
GET/consolidacao?competencia=…Resultado consolidado SQIA-first + YTD/Variação/PTD + linhas minoritária
GET/consolidacao/export?format=xlsx|pdfExport com timestamp SOX
GET/competenciasLista competências disponíveis + flags has_gold / has_xlsb_target
GET/balancetes?competencia=…Lista balancetes bronze
GET/balancetes/{id}/linhasLinhas do balancete com paginação

Regras e cadastros

MétodoRotaDescrição
GET/POST/PUT/DELETE/regras/intercompanyTipo 1 — pares lado A × lado B
GET/regras/dividendsTipo 3 (leitura) — editar via editor de dividendos da organograma
GET/POST/PUT/DELETE/regras/equity-pairsTipo 2 + 4 — investimento × equivalência
GET/POST/PUT/DELETE/ajustesTipo 5 — ajustes manuais por (conta, empresa, competência)
GET/POST/PUT/DELETE/empresasCRUD de companies
GET/PUT/POST/DELETE/organogramaHoldings + pares + dividendos + versionamento
GET/POST/PUT/contas-contabeisPlano de contas versionado + EVTC chart
GET/PUT/POST/DELETE/valores-iniciaisSnapshot data_base + cálculo YTD

Auth, admin, audit

MétodoRotaDescrição
POST/auth/loginUsername + senha → JWT (7 dias)
GET/auth/meDecode do token corrente
POST/auth/logoutNo-op (cliente apaga o token)
GET/POST/PUT/DELETE/usersGestão de contas (admin only)
POST/users/me/passwordTrocar a própria senha
GET/auditLog de auditoria (filtros: user, since, until, only_failures)

15. Validação contra o canon

A validação contra o canon comparava gold.consolidacao_resultado contra a coluna Cons YTD da aba Consolidated do .xlsb de fechamento, conta por conta, com tolerância de R$ 0,50.

Estado em 2026-05-29

As contas-chave de equity batem ao centavo após os fixes de maio/2026 (Proforma inclusion + cancelamento, mais-valia subtraction, filtro per-empresa):

ContaDescriçãoJanFevMar
23201004Transação sócios minoritários✅ centavo✅ centavo✅ centavo
23301004Resultado do exercício✅ centavo✅ centavo✅ centavo
23301007Resultado exercícios anteriores✅ centavo✅ centavo✅ centavo
44102001Resultado com participação minoritária✅ centavo⚠ Δ R$ 90k*⚠ Δ R$ 90k*
51111111Apuração de resultados✅ centavo✅ centavo✅ centavo

* Diferença residual em fev/mar é o ajuste manual mensal de R$ ~90k que a contadora faz no .xlsb (citado na reunião 25/05/26) e ainda não foi lançado aqui via /ajustes.

Subtotal por empresa: todas as 8 empresas fecham em R$ 0,00 em mar/2026 (com pequenos resíduos de arredondamento ≤ R$ 0,01 em FepWeb).

Histórico