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.
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
- .xlsx: formato Protheus moderno, parseado direto via openpyxl.
- .xls: formato Protheus legacy (BIFF8 / 97-2003).
xls_compat.pydetecta o magic byte D0 CF 11 E0 A1 B1 1A E1 e converte pra .xlsx em memória antes de parsear.
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:
- 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. - 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
| Status | Significado |
|---|---|
created | Linha em bronze.upload_batches criada, ainda processando |
ingested | Todos os files do batch OK |
partial | Alguns OK, outros falharam. UI lista cada um com seu motivo |
failed | Nenhum file OK |
archived | Manual 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:
- Base comparativa pra cálculo YTD desde a data_base
(
core.app_config['data_base'], hoje2026-01-31). - Fonte do silver para competências ≤ data_base — onde o Protheus ainda não tem extração consistente (ex.: jan/2026 foi populado por snapshot manual da contadora antes do balancete final ficar disponível).
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:
- 3a: Investimento × PL na controladora (tipo 2A, via
_apply_investment_eliminations_pure). - 3b: Equity de subsidiária (capital, reservas, resultado) nas
controladas (tipo 2B, via
_apply_subsidiary_equity_eliminations_pure). - 3c: Calcula minoritária por controlada parcial
(
gold.minority_interest_lines). - 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). - 3e: Cancela equity fictício da Proforma (4 contas) via elim tipo 2,
replicando a aba
Eliminationsdo canon. - 4: Equivalência × Apuração (tipo 4) nos pares cadastrados em
core.equity_account_pairs. - 5a: Intercompany — lado A e lado B (tipo 1, via SQL com vigência).
- 5b: Dividendos a receber e a pagar (tipo 3, proporcional ao %).
- 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
- SQIA (8 dígitos, formato concat ex.:
11102001): plano analítico do Protheus. ~14.000 contas no chart. - EVTC (6 dígitos, ex.:
109110): plano consolidado da contadora. ~700 contas emcore.chart_evtc. - de_para (
core.de_para): mapeamento N:1 SQIA → EVTC. Conta sem mapping aparece no consolidado pelo próprio código SQIA.
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:
| Prefixo | Tipo | Convenção PTD |
|---|---|---|
11, 12, 13 | Ativo | PTD = YTD (estoque) |
21, 22, 23 | Passivo / PL | PTD = YTD (estoque) |
31, 32 | Receita | PTD = saldo_final − saldo_inicial |
41, 42, 43 | Despesa / Custo | PTD = saldo_final − saldo_inicial |
5 | Apuração transitória | PTD = saldo_final − saldo_inicial |
Flags especiais
Em core.chart_sqia:
is_subsidiary_equity(24 contas hoje): contas de PL elegíveis pra eliminação tipo 2B (capital social, reservas, resultado acumulado, etc).is_subsidiary_period_result(1 conta:23301004): a conta usada como resultado do exercício no cálculo de minoritária. Separada do flag anterior porque todas as contas de PL fazem parte do tipo 2B, mas só uma é base pra (1 − %) × resultado.
7. Estrutura societária
Empresas
| Empresa | Tipo | Controladora | % |
|---|---|---|---|
| Sinqia S.A. | consolidadora | — | — |
| Sinqia Tecnologia | operacional | Sinqia S.A. | 100,00% |
| Torq | operacional | Sinqia S.A. | 100,00% |
| FepWeb | operacional | Sinqia S.A. | 100,00% |
| Quitejá | operacional | Sinqia S.A. | 51,00% |
| Lote 45 | operacional | Sinqia Tecnologia | 56,32%* |
| Compliasset | operacional | Sinqia Tecnologia | 60,00% |
| Proforma | veí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:
- 2A: elimina o saldo da
conta_investimentona controladora. - 2B: elimina o equity da controlada (24 contas marcadas com
is_subsidiary_equity=TRUE: todas as23xxx+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:
- a_receber (10 contas, ex.:
11503001 DIVIDENDOS A RECEBER - FEPWEB): saldo na controladora já reflete só a fatia dela; elimina integral. - a_pagar (2 contas:
21401004,21401018): a controlada registra o total devido (incluindo a parcela dos minoritários); eliminasaldo × %da controladora, deixando(1 − %)como obrigação real aos minoritários.
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-valia | elim 44102001 | canon |
|---|---|---|---|---|
| Jan/2026 | 1.932.465,37 | -357.267,72 | 1.575.197,65 | 1.575.183,26* |
| Fev/2026 | 3.224.757,52 | -702.189,12 | 2.522.568,40 | 2.612.670,40† |
| Mar/2026 | 4.434.402,58 | -372.023,32 | 4.062.379,26 | 4.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
- SQIA dotted (parser):
1.1.1.02.001(5 níveis, ponto-separado). - SQIA concat (DB):
11102001(8 dígitos, sem ponto). - EVTC:
109110(6 dígitos).
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:
- Débito → valor positivo. Ativo, custo, despesa têm natureza devedora → saldo positivo.
- Crédito → valor negativo. Passivo, PL, receita têm natureza credora → saldo negativo.
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:
| Coluna | Significado | Como calcula |
|---|---|---|
| YTD | Saldo acumulado até a competência | SUM(saldo_final) |
| Variação | Mudança vs. mês anterior | YTD_atual − YTD_mês_anterior (NULL se o mês anterior não está em silver) |
| PTD | Posição do período corrente | BS (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'.
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
- Subtotal acima do header: cheque visual de balanço por empresa (preto se zero, vermelho se ≠ 0). Resume também Combinado / Eliminação / Consolidado YTD / Variação / PTD.
- Filtro de coluna estilo Excel: clique em ▾ no header → popover com checkboxes dos valores únicos.
- Sort por click no header (asc/desc por coluna).
- Drag & reorder de colunas de empresas (HTML5 drag-and-drop),
persistido em
localStorage. - Resize de coluna estilo Excel (drag na borda direita, double-click pra reset).
- Truncar com ellipsis + tooltip no hover pro valor completo.
Controles globais
- Filtro full-text (código, EVTC, descrição, FS).
- Só linhas com eliminação: esconde linhas onde
elim = 0. - Só sem EVTC: destaca contas que ainda não foram classificadas.
- Mostrar contas zeradas: relaxa o filtro do API pra incluir contas com agregado e per-empresa todos zero — útil pra auditar zeragem.
- ⚙ Colunas: popover de visibilidade de cada coluna + cada empresa.
Export
GET /consolidacao/export?format=xlsx ou format=pdf. Inclui:
- YTD + PTD por empresa
- Combined YTD/PTD, Eliminação YTD/PTD, Consolidado YTD/Variação/PTD
- Linhas de minoritária no rodapé
- Timestamp de extração no título (SOX-friendly)
- Formato monetário pt-BR
1.234,56com vermelho pra negativo
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
- admin: pode acessar
/usersCRUD e/admin/usuarios. - member: uso normal (uploads, regras, ajustes, consolidação).
- visualizador: somente leitura (enforcement parcial — ainda não bloqueia escritas em todos os routers).
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ço | Imagem | Porta interna | Função |
|---|---|---|---|
evtc_db | postgres:16-alpine | 5432 | Postgres com schemas core/bronze/silver/gold |
evtc_api | python:3.11-slim | 8000 | FastAPI + SQLAlchemy + openpyxl/pyxlsb/xlrd |
evtc_web | node:20-alpine | 3000 | Node.js puro (template literal). UI da app |
evtc_caddy | caddy:2-alpine | 80/443 | Reverse proxy + Let's Encrypt (só em prod) |
evtc_design_system | node:20-alpine | 6006 | Storybook (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):
- Push em
main→ build das imagens API + Web em paralelo, push pro Artifact Registry. - SCP do
docker-compose.prod.ymle migrations pra VM. - Update do
IMAGE_TAG,docker compose pull+up -d. - 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étodo | Rota | Descrição |
|---|---|---|
| POST | /pipeline/upload | Upload 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-all | Recalcula silver+gold de TODAS as competências em transação única |
| POST | /pipeline/sync-all | Sincroniza assets da pasta Continuação |
Leitura consolidada
| Método | Rota | Descrição |
|---|---|---|
| GET | /consolidacao?competencia=… | Resultado consolidado SQIA-first + YTD/Variação/PTD + linhas minoritária |
| GET | /consolidacao/export?format=xlsx|pdf | Export com timestamp SOX |
| GET | /competencias | Lista competências disponíveis + flags has_gold / has_xlsb_target |
| GET | /balancetes?competencia=… | Lista balancetes bronze |
| GET | /balancetes/{id}/linhas | Linhas do balancete com paginação |
Regras e cadastros
| Método | Rota | Descrição |
|---|---|---|
| GET/POST/PUT/DELETE | /regras/intercompany | Tipo 1 — pares lado A × lado B |
| GET | /regras/dividends | Tipo 3 (leitura) — editar via editor de dividendos da organograma |
| GET/POST/PUT/DELETE | /regras/equity-pairs | Tipo 2 + 4 — investimento × equivalência |
| GET/POST/PUT/DELETE | /ajustes | Tipo 5 — ajustes manuais por (conta, empresa, competência) |
| GET/POST/PUT/DELETE | /empresas | CRUD de companies |
| GET/PUT/POST/DELETE | /organograma | Holdings + pares + dividendos + versionamento |
| GET/POST/PUT | /contas-contabeis | Plano de contas versionado + EVTC chart |
| GET/PUT/POST/DELETE | /valores-iniciais | Snapshot data_base + cálculo YTD |
Auth, admin, audit
| Método | Rota | Descrição |
|---|---|---|
| POST | /auth/login | Username + senha → JWT (7 dias) |
| GET | /auth/me | Decode do token corrente |
| POST | /auth/logout | No-op (cliente apaga o token) |
| GET/POST/PUT/DELETE | /users | Gestão de contas (admin only) |
| POST | /users/me/password | Trocar a própria senha |
| GET | /audit | Log 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):
| Conta | Descrição | Jan | Fev | Mar |
|---|---|---|---|---|
23201004 | Transação sócios minoritários | ✅ centavo | ✅ centavo | ✅ centavo |
23301004 | Resultado do exercício | ✅ centavo | ✅ centavo | ✅ centavo |
23301007 | Resultado exercícios anteriores | ✅ centavo | ✅ centavo | ✅ centavo |
44102001 | Resultado com participação minoritária | ✅ centavo | ⚠ Δ R$ 90k* | ⚠ Δ R$ 90k* |
51111111 | Apuraçã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
- 2026-05-03: BS match 96,9% inicial.
- 2026-05-04: Cluster A fechado (R$ 38,8M de bugs do motor) → BS 97,8%.
- 2026-05-06: Tipo 3 dividendos a_pagar com proporcionalidade.
- 2026-05-07: Fev/mar BS 100%; resíduo P&L em FepWeb (data quality na fonte).
- 2026-05-28: Proforma equity inclusion + canon-aligned cancellation; mais-valia subtraction no cálculo de minoritária; coluna PTD/Variação.
- 2026-05-29: Filtro per-empresa nonzero; parser tolerante a aba renomeada; status partial; admin panel.