# Changelog — Dashboard Contábil > Histórico específico desta aplicação, extraído de `plano.md` (mesma numeração de rodada usada lá, para referência cruzada). ### 92. Dashboard Contábil (Relatórios > Contabilidade) — v1: execução, auditoria e análise Pedido: otimizar a conferência de balancetes hoje feita manualmente pelo Fisco/Contábil (`ITD-FISCO-7513`). Nova aplicação em Relatórios > Contabilidade: o contador anexa o PDF de Balancete + DRE (modelo Questor, mesmo relatório hoje enviado ao cliente), a ferramenta extrai as contas e roda um motor de 10 regras de auditoria (balanceamento Ativo x Passivo, débito ≠ crédito, saldo negativo de caixa, contas transitórias/genéricas com saldo, contas que deveriam ficar zeradas, sinal de saldo invertido, variação atípica de saldo/DRE mês a mês, percentual custo/receita fora do padrão da própria empresa), apresentando os achados numa tela de revisão com observações por conta e conclusão da análise. Decisões confirmadas com o usuário: entrada só em PDF (levantada a alternativa de XLSX estruturado do Questor, mais confiável de extrair, mas mantido PDF como pedido originalmente); histórico de variação mês a mês fica no próprio banco do Portal, não depende de nenhum relatório complementar anexado; nasce restrita ao perfil "Inovação" (dado financeiro de cliente sensível, mesmo padrão de "Não Conformidades"); o botão "Gerar Dashboard HTML" (que substituiria o BI Contábil por um relatório final ao administrador da empresa, com exportação em XLSX) fica **desabilitado** nesta rodada, escopo de uma rodada futura. Desafio técnico principal: o relatório Questor de Balancete/DRE desenha cada caractere em posição própria e inclui, por baixo do texto real, uma grade densa de caracteres de espaço cobrindo toda a linha — isso quebra a extração padrão do pdfplumber (`extract_words`/`extract_text`), que trata esses espaços como separadores reais e fragmenta números em dígitos isolados. Resolvido reconstruindo cada linha direto de `page.chars`, ignorando espaços literais e reinserindo um só quando o vão horizontal indica uma quebra de campo real — validado rodando de fato contra o PDF real de referência do usuário antes de escrever o parser definitivo. 4 models novos (`ContabilApuracao`/`ContabilConta`/`ContabilLinhaDre`/`ContabilAchado`, migração `0055`), pacote `portal_api/dashboard_contabil/` sem ORM (`parser.py`/`regras.py`/`pipeline.py`/`modelos.py`), subgrupo "Contabilidade" novo em `catalogo.py` dentro de `relatorios`. Detalhe técnico completo no `CLAUDE.md` desta pasta. ### 93. Dashboard Contábil — DRE agrupada por árvore + botão "Gerar Dashboard" implementado A DRE (aba "DRE" da revisão) ganhou a mesma árvore recolhível que o Balancete já tinha (`renderDre()` reaproveita o algoritmo de `renderContas()`, agora sobre `ContabilLinhaDre.nivel`). O botão "Gerar Dashboard" (renomeado de "Gerar Dashboard HTML") saiu do estado desabilitado da rodada 92: gera um documento HTML autocontido, com a marca do escritório, com indicadores financeiros (ROA, ROE, Kanitz, EBIT, EBITDA, Liquidez Corrente/Seca/Geral, Composição/Grau de Endividamento, IPL), gráfico de evolução do Resultado Líquido (Chart.js via CDN), DRE/Balancete agrupados e as observações que o contador já registrou na aplicação. Escopo confirmado com o usuário: sempre uma apuração por vez (sem "Filial"/consolidação multi-empresa do BI antigo que este relatório substitui, fora de escopo). Novo módulo `indicadores.py` (funções puras): grupos do Balancete por código fixo calibrado contra o balancete de referência (mesmo risco já aceito em `regra_saldo_negativo_caixa`), com o Passivo Não Circulante calculado por eliminação (sempre exato, não precisa de mais um código). Liquidez Corrente/Seca, Composição/Grau de Endividamento e IPL validados byte a byte contra a captura de tela do usuário (bateram exatamente). EBITDA fica indisponível na primeira apuração de uma empresa (precisa da variação de Depreciação/Amortização entre dois meses). Kanitz usa a fórmula padrão, não validada contra o BI antigo (valor de referência do usuário fora da faixa clássica do índice) — marcado como estimativa no relatório. O relatório é dividido em 3 abas (Balancete → D.R.E. → Indicadores, nessa ordem pedida pelo usuário), com "Observações da Análise" fora das abas, sempre visível; na impressão a barra de abas some e as 3 aparecem seguidas, cada uma numa página. Nova action `ContabilApuracaoViewSet.dashboard()` renderiza `templates/dashboard-contabil-relatorio.html` via `render_to_string` e devolve `text/html` puro — primeiro documento HTML autocontido do Portal (até então só PDF/CSV/ZIP). Novo `portal_api/templatetags/contabil_extras.py` (primeiro uso de template tags customizadas no projeto) com filtros `moeda`/`percentual`/`indice`/`competencia`. Detalhe técnico completo (todas as fórmulas e limitações conhecidas) no `CLAUDE.md` desta pasta. **Dois ajustes, ainda na mesma rodada, antes do usuário testar de fato**: Balancete e D.R.E. do relatório ganharam a mesma árvore recolhível da tela de revisão (calculada no servidor — `_contabil_arvore_contexto()` em `views.py` — já que o relatório é HTML estático, não uma SPA); e a logo do escritório, que não estava carregando, foi corrigida trocando a action de POST pra **GET** — a causa raiz era o frontend abrir o resultado via `fetch`+blob (`URL.createObjectURL`), e um documento carregado de uma URL `blob:` tem origem sintética, quebrando a URL relativa `{% static %}` da logo; com GET, o frontend só faz `window.open()` direto na URL da API (navegação de verdade, sem blob). **Terceiro ajuste, mesmo dia**: usuário achou o visual simples demais e pediu mais bonito/dinâmico/animado. Fontes "Manrope"/"Inter", gradiente+glow no cabeçalho, ícones SVG, abas com indicador deslizante, contagem animada nos cards (sempre terminando no valor exato já formatado pelos filtros Django, nunca recalculado em JS) e cor por sinal/limiar só onde é seguro sem inventar nada (ROA/ROE/EBIT/EBITDA por sinal, Liquidez por ≥1 — convenções de mercado; Kanitz/Endividamento/IPL ficam sem cor, sem limiar validado). Toda animação de entrada segue a regra de nunca fixar `opacity:0` fora de `@keyframes`, pra `@media print` bastar sozinho pra devolver tudo ao normal na impressão. Corrigido também um bug de condição de corrida entre dois listeners de `beforeprint` que competiam entre si quando a aba "Indicadores" nunca tinha sido aberta. Detalhe completo no `CLAUDE.md` desta pasta ("Visual"). ### 94. Dashboard Contábil — observações do relatório redistribuídas por aba Pedido: a seção única "Observações da Análise" (fora de todas as abas) misturava observações de Balancete, D.R.E. e achados de auditoria juntas, sem separar por contexto. Passou a ficar assim: a aba **Balancete** termina com "Observações do Balancete" (só as observações de conta); a aba **D.R.E.** termina com "Observações da D.R.E." (só as observações de linha); a aba **Indicadores** termina com "Todas as Observações da Análise" — as três listas juntas (contas + linhas de DRE + achados), cada item prefixado com a origem ("Balancete — ...", "D.R.E. — ...", "Auditoria — ..."), já que ali não há mais uma aba pra dar esse contexto sozinha. Puramente reorganização de template (`dashboard-contabil-relatorio.html`) — as três listas já vinham prontas do backend (`views.py`), nenhuma mudança de backend foi necessária. **Correção ainda na mesma rodada**: o prefixo do terceiro grupo tinha nascido "Achado de Auditoria —", contrariando um pedido já feito antes pelo usuário de nunca expor a palavra "achado" em texto visível (mesmo motivo pelo qual a aba de revisão já se chama "Observações", não "Achados" — `dashboard-contabil.html`, `data-dc-tab="achados"` com o texto "Observações"). Trocado pra "Auditoria —", no mesmo padrão dos outros dois grupos (nomeado pela origem/demonstração, não pelo tipo de registro interno). Vale como regra geral pra qualquer texto novo desta aplicação: `achado`/`Achado` só em nome de variável/model/classe CSS, nunca em texto visível ao usuário. Detalhe completo no `CLAUDE.md` desta pasta. ### 95. Card de achado (tela de revisão) expande a conta usada no apontamento Pedido: nos cards de "Observações" (aba Achados da revisão), permitir expandir e ver a conta do Balancete que embasou aquele apontamento — até então só o título/mensagem da regra apareciam, sem o dado de origem. `renderAchados()` (`dashboard-contabil.js`) resolve `achado.conta` (id) pra objeto completo procurando em `apuracaoAtual.contas`, já carregado junto na mesma resposta de `/api/contabil-apuracoes/{id}/` — nenhuma chamada de API nova, nenhuma mudança de backend. Um botão "Ver conta usada no apontamento" aparece só nos achados vinculados a uma conta específica (achados das regras 3-7, que têm `conta` preenchida); as duas regras gerais (balanceamento Ativo x Passivo, débito ≠ crédito) não mostram o botão, já que não têm uma conta única por trás. Expandido, mostra código/descrição/saldo anterior/débito/crédito/saldo atual da conta, num `
` novo (`.dc-achado-card__conta`, `dashboard-contabil.css`). Detalhe completo no `CLAUDE.md` desta pasta. ### 96. Lista de Observações ordenada por severidade Pedido: na aba "Observações" da revisão, as observações apareciam na ordem em que as regras rodaram (mistura de Alta/Média/Baixa), sem prioridade visual — o usuário pediu Alta primeiro, depois Média, depois Baixa. `achadosFiltrados()` (`dashboard-contabil.js`) ganhou um `.sort()` por `PID_DC_SEVERIDADE_ORDEM` (`{alta: 0, media: 1, baixa: 2}`) depois do filtro já existente (severidade/status); dentro de uma mesma severidade a ordem original é preservada (`sort` é estável). Só ordenação de exibição, nenhuma mudança de backend/modelo. ### 97. Resumo por categoria (donut + cards) no topo da aba Observações Pedido: o usuário trouxe capturas de tela da auditoria de outro sistema (checklist de checagens + cards por categoria com a tabela de apontamentos + gráfico de distribuição) perguntando se dava pra estruturar algo parecido. Alinhado por `AskUserQuestion` que: (a) várias checagens daquele sistema dependem de dado que não vem do Balancete/DRE anexado (folha, vencimentos de fornecedor/cliente/imposto, saldo bancário) — fora do escopo já documentado desta ferramenta, não replicadas; (b) o formato escolhido foi "cards por categoria com tabela de apontamentos" + "gráfico de distribuição por categoria", só na tela de revisão do Portal (não no checklist pass/fail, não no relatório "Gerar Dashboard"). Nova seção `.dc-achados-resumo` no topo da aba Observações (`dashboard-contabil.html`), acima dos chips de filtro já existentes: um donut (CSS puro, `conic-gradient` — sem Chart.js/dependência nova nesta tela interativa, diferente do relatório estático que já usa Chart.js via CDN) com o total de observações no centro e legenda por categoria, mais uma grade de cards — um por regra de auditoria (as mesmas 10 de `regras.py`, `PID_DC_REGRAS` em `dashboard-contabil.js` — enumeradas sempre as 10, mesmo as que não geraram achado nesta apuração, mesmo espírito do "Nenhum registro encontrado" do sistema de referência) com a contagem e uma mini-tabela das contas/linhas apontadas (código+descrição da conta quando `achado.conta` está preenchido, "Geral" quando não — mesmas duas regras gerais que já não mostram conta no card expandido, ver rodada 95) e o status de cada uma. `renderAchadosResumo()` roda sempre sobre `apuracaoAtual.achados` **completo**, não sobre `achadosFiltrados()` — visão geral estável, independente dos chips Alta/Média/Baixa/Pendentes/Todos da lista detalhada logo abaixo. Cores das 10 categorias reaproveitam os tokens `--accent-rgb`/`--danger-rgb`/`--gold-rgb`/`--teal-rgb`/`--slate-rgb`/`--coral-rgb` já existentes (theme-aware) completados até 10 com `color-mix()`, sem hex novo hardcoded. Puramente frontend — nenhuma mudança de backend/model (o campo `regra` já existia em `ContabilAchado`). Detalhe completo no `CLAUDE.md` desta pasta. ### 98. Donut do resumo trocado pra severidade + donut/cards clicáveis (filtram a lista) Dois pedidos, mesma rodada: (a) o donut da rodada 97 mostrava distribuição por categoria/regra — o usuário pediu pra mostrar por nível de complexidade (Alta/Média/Baixa) em vez disso; (b) permitir clicar no gráfico ou nos cards pra filtrar os apontamentos relacionados na lista detalhada abaixo. Donut reconstruído em SVG puro (técnica clássica de ``, circunferência ≈ 100, então `stroke-dasharray`/`stroke-dashoffset` já valem como percentual sem precisar de `pathLength`) — 3 segmentos (Alta/Média/Baixa, mesmas cores dos badges: `--danger`/`--gold`/`rgb(var(--slate-rgb))`), cada um um `` clicável (`pointer-events` de SVG só considera o pixel realmente pintado pelo `stroke`, então cada fatia responde só na própria área, sem hit-test manual por ângulo). Clicar numa fatia (ou item da legenda) chama `pidDcSelecionarSeveridade()` — extraída da lógica que os chips "Alta"/"Média"/"Baixa" já tinham (mesmo efeito de clicar o chip: atualiza `filtroSeveridade`, `.is-active` e re-renderiza). Cards de categoria (regra) continuam mostrando a contagem por regra, mas agora clicáveis também: alternam um novo estado `filtroRegra` (`achado.regra` exata ou `null`, clicar de novo no mesmo card limpa) — `achadosFiltrados()` ganhou essa terceira condição, combinando por E lógico com severidade/status. Como não há chip próprio pra esse filtro, uma faixa nova (`#dc-regra-filtro-ativo`) aparece entre os chips e a lista quando `filtroRegra` está ativo, com o nome da categoria e um botão "Limpar". Clicar em qualquer um dos dois (donut/card) dá um scroll suave até a lista (`pidDcScrollParaLista()`). Puramente frontend, nenhuma mudança de backend. Detalhe completo no `CLAUDE.md` desta pasta. ### 99. Exportar Balancete/DRE em XLSX a partir do relatório "Gerar Dashboard" Pedido: permitir exportar Balancete ou DRE em XLSX a partir do relatório HTML. Novo módulo `portal_api/dashboard_contabil/exportacao.py` (funções puras, openpyxl, mesmo espírito de `indicadores.py`/`regras.py` — sem tocar no ORM) com `gera_xlsx_balancete()`/`gera_xlsx_dre()`, recebendo dataclasses (`LinhaBalanceteXlsx`/`LinhaDreXlsx`) já resolvidas pela view. Nova action `ContabilApuracaoViewSet.exportar_xlsx()` (`GET /api/contabil-apuracoes/{id}/exportar-xlsx/?parte=balancete` ou `?parte=dre`, mesma permissão de toggle único) monta essas linhas reaproveitando a mesma fórmula de nível/indentação já usada por `dashboard()`. Cada planilha nasce com cabeçalho (empresa/CNPJ/competência), cabeçalho de colunas com a mesma paleta roxo/dourado dos outros documentos gerados pelo escritório, conta sintética/linha totalizadora em negrito+fundo dourado claro, indentação de hierarquia via `Alignment(indent=nivel)` e colunas monetárias com `number_format` brasileiro (valor gravado como número, não texto — continua editável/somável no Excel). Botão "Exportar XLSX" novo em cada seção do relatório (`.dcr-export-btn`, `dashboard-contabil-relatorio.html`) é um link direto pra API, sem JS — o browser já baixa o arquivo pelo `Content-Disposition` da resposta; escondido na impressão junto do botão "Imprimir". `openpyxl` já era dependência do projeto (usado pra leitura em outras ferramentas), essa é a primeira vez que o projeto **escreve** um XLSX com ele. Detalhe completo no `CLAUDE.md` desta pasta. ### 100. Resumo da rodada 97 reorganizado em 3 cards por grupo temático Pedido: os 10 cards por regra do resumo (rodada 97), cada um com uma mini-tabela de conta+status por achado, ficaram "muito poluídos visualmente" na prática (captura de tela real anexada pelo usuário). Alinhado por `AskUserQuestion` (com preview) o agrupamento das 10 regras em 3 temas fixos: "Divergências de Saldo" (balanceamento Ativo x Passivo, débito x crédito, caixa negativo, sinal de saldo invertido), "Contas Atípicas" (contas transitórias, contas que deveriam zerar, descrição genérica) e "Variações e Indicadores" (variação atípica de saldo, variação atípica na DRE, percentual custo/receita). `PID_DC_GRUPOS` (nova constante, `dashboard-contabil.js`) substitui a iteração antes feita direto sobre `PID_DC_REGRAS` na grade de cards — agora 1 card por grupo (cabeçalho com o total do grupo) contendo uma linha por regra (label + contagem, sem mini-tabela de conta/status). A mini-tabela de conta+status por achado saiu do resumo (era a maior fonte de poluição visual), mas continua disponível na lista completa logo abaixo, ao clicar numa linha de regra — o clique por regra individual (não por grupo) foi preservado, mesmo comportamento de filtro da rodada 98. Puramente frontend (JS + CSS), nenhuma mudança de backend/model. Detalhe completo no `CLAUDE.md` desta pasta. ### 101. Aba "Dashboard" na tela de revisão — ocultar cards/observações antes de gerar o relatório Pedido: depois da aba DRE, uma aba "Dashboard" que mostra os indicadores e observações que vão pro relatório "Gerar Dashboard" **antes** de gerá-lo, onde o contador pode ocultar um card de indicador ou uma observação — o que estiver oculto não entra no relatório gerado. Modelos novos: `ContabilConta.oculta_no_relatorio`/`ContabilLinhaDre.oculta_no_relatorio`/`ContabilAchado.oculto_no_relatorio` (booleanos, só afetam a seção "Observações" do relatório, a linha em si continua normal no Balancete/DRE/Observações da revisão) e `ContabilApuracao.indicadores_ocultos` (lista de chaves de `IndicadoresFinanceiros`, ex. `["kanitz"]`) — migração `0059`. Endpoints novos: `GET /api/contabil-apuracoes/{id}/indicadores/` (mesmo cálculo do relatório, extraído pro helper `_contabil_calcula_indicadores`), `POST .../indicadores-ocultos/` (substitui a lista inteira) e `POST /api/contabil-achados/{id}/alternar-oculto/` (separado do fluxo de tratar/ignorar, que sempre exige status+justificativa); `oculta_no_relatorio` de conta/linha da DRE já entra pelo `PATCH` genérico que essas duas telas já tinham. `dashboard()` filtra as 3 listas de observações por esses campos e envolve cada um dos 11 cards do relatório num `{% if "chave" not in indicadores_ocultos %}` (mais 2 booleanos de grupo, pra não sobrar um `

` de seção sem nenhum card embaixo). Nova aba `data-dc-tab="dashboard"` em `dashboard-contabil.html`, carregada sob demanda (`renderDashboardTab()`, só na primeira vez que é aberta) pra não pagar o custo de recalcular os indicadores em toda apuração aberta. Cada card/observação ganha um botão de olho (ícone SVG inline, reaproveitando `.icon-btn`) que alterna o oculto e atualiza só o item local, sem recarregar a apuração inteira; desabilitado quando a apuração já está concluída. Detalhe completo no `CLAUDE.md` desta pasta. ### 102. Banco de indicadores personalizados — fórmulas, componentes e "Ver fórmula" Pedido: cada card de indicador devia ser "um indicador efetivo calculado a partir das contas contábeis" — o contador poder ver a fórmula de qualquer card (inclusive os 11 de sistema) e criar indicador **novo**, escolhendo contas do Balancete/linhas da DRE/variação entre apurações/outros indicadores já existentes como componentes de uma fórmula de verdade. Escopo alinhado por `AskUserQuestion` antes de implementar: os 11 de sistema continuam com o cálculo Python fixo de sempre (zero risco de mudar um valor já calibrado), só ganharam metadados de exibição; a fórmula personalizada é avaliada por um interpretador restrito (`ast`, nunca `eval()`); "selecionar conta ou grupo" é marcar uma ou mais contas específicas via checklist, não digitar um prefixo de código. Modelos novos `IndicadorContabilDefinicao`/`IndicadorContabilComponente` (migração `0060`) — componente guardado por **código de conta**/**descrição de linha da DRE** (nunca FK a uma linha de uma apuração específica), reaplicado em qualquer apuração/empresa que o relatório for gerado. Novo módulo `dashboard_contabil/formula.py` (`avalia_formula()`/`valida_formula()`) — expressão aritmética (+, -, *, /, parênteses) sobre as chaves dos componentes, com `None` propagando como "indisponível" (nunca vira 0) e divisão por zero também virando `None`. Resolução iterativa em `views.py` (`_contabil_calcula_indicadores_personalizados()`) cobre encadeamento entre indicadores personalizados e referência a indicador de sistema, sem quebrar em caso de ciclo (fica `None`) — validado manualmente contra a produção com uma transação revertida (rollback), cobrindo os três casos. CRUD novo `IndicadorContabilDefinicaoViewSet` (`/api/contabil-indicadores-definicoes/`) — mesma permissão de toggle único do resto da ferramenta (qualquer contador com acesso pode criar, não é restrito ao perfil "Inovação"); sempre substitui o indicador **inteiro** (nome/descrição/fórmula/formato/componentes) a cada criação/edição, nunca um diff incremental; bloqueia exclusão se outro indicador o referencia pela fórmula. `GET /contabil-apuracoes/{id}/indicadores/` ganhou `metadados` (nome/grupo/formato/descrição/fórmula em texto, uniforme pra indicador de sistema ou personalizado) — única fonte de verdade que o frontend usa agora pra agrupar/formatar cada card, substituindo as constantes hardcoded da rodada 101. Metadados dos 11 de sistema (`indicadores.METADADOS_CARDS`) vieram do glossário de KPI que o usuário forneceu, com 2 ajustes pra bater com o que o código realmente calcula: Grau de Endividamento/IPL (o texto original tinha um "×100" que já é só a exibição em %, não uma conta a mais) e EBIT (o texto original é a definição-livro-texto "de cima pra baixo", o código calcula "de baixo pra cima" a partir do Lucro Líquido — os dois tendem a convergir mas não foram provados equivalentes). Frontend: card agora é clicável (fora dos botões) e abre um modal "Ver fórmula"; indicador personalizado ganha um botão de editar (lápis) além do de ocultar. Modal "Novo/Editar Indicador" com um construtor de componentes dinâmico, reaproveitando `.checklist-box`/`.checklist-item`/`.checklist-search` (mesmo padrão dos checklists de Perfis/Departamento) pros pickers de conta/linha da DRE — necessário porque uma apuração real tem ~100-150 contas/linhas. Bug real pego antes do usuário testar: editar um indicador configurado a partir de **outra** apuração apagaria silenciosamente qualquer código/descrição que não existisse na apuração atualmente aberta (não aparecia no checklist pra continuar marcado); corrigido mostrando esses como item extra "não encontrado nesta apuração, mantido" no topo da lista. Detalhe completo no `CLAUDE.md` desta pasta. ### 103. Indicador padrão vs. não padrão + "Gerenciar Indicadores" + modal com scroll Pedido: (a) o modal "Novo/Editar Indicador" da rodada 102 não tinha scroll — com vários componentes, só dava pra alcançar "Salvar" dando zoom out no navegador; (b) precisava de um jeito de ver/editar qualquer indicador já criado, não só os que já estão aparecendo na apuração aberta; (c) indicador personalizado devia poder ser "padrão" (aparece automaticamente em toda apuração, comportamento que já existia) ou "não padrão" (fica salvo/editável mas só aparece numa apuração quando selecionado ali). `.modal-card` (`components.css`) ganhou `max-height`/`overflow-y:auto` — correção global, sem efeito em modal que já cabia na tela; a lista de componentes também rola sozinha (`.dc-ind-componentes-lista`, 320px), pra não precisar rolar duas vezes. Modelo `IndicadorContabilDefinicao.padrao` (default `True`, sem regressão pros já criados) e `ContabilApuracao.indicadores_selecionados` (migração `0061`). `_contabil_calcula_indicadores_personalizados()` passou a filtrar por `padrao=True` ou chave selecionada nesta apuração antes de calcular qualquer coisa — um indicador não padrão não selecionado não aparece nem no relatório nem na aba "Dashboard", mas continua existindo/editável. Endpoint novo `POST /api/contabil-apuracoes/{id}/indicadores-selecionados/` (mesmo padrão de `indicadores_ocultos/`). Frontend: o botão "Novo Indicador" virou "Gerenciar Indicadores", abrindo um modal-hub com a lista **completa** (padrão e não padrão) — cada linha com nome clicável ("Ver fórmula"), editar, e (só não padrão) um checkbox "ativo nesta apuração". O form de criar/editar agora abre empilhado por cima do hub (`.modal-overlay--top`, mesmo mecanismo do modal de confirmação genérico) e ganhou um checkbox "Indicador padrão". Detalhe completo no `CLAUDE.md` desta pasta. ### 104. Migração dos 11 indicadores de sistema pro banco de indicadores Pedido: reversão da decisão de escopo da rodada 102 (que tinha deixado os 11 indicadores "de sistema" — ROA/ROE/Kanitz/EBIT/EBITDA/Liquidez Corrente/Liquidez Seca/Liquidez Geral/Composição do Endividamento/Grau de Endividamento/IPL — fora do banco, calculados em Python fixo, pelo risco de mapear uma conta errado). Confirmado por `AskUserQuestion` (risco explicado antes) que o usuário queria a fórmula de verdade editável, não só o texto de exibição. Os 11 viraram `IndicadorContabilDefinicao` de verdade, com as mesmas chaves de antes (preserva `indicadores_ocultos` já salvo) — não são mais um caso especial em lugar nenhum do código. Novo tipo de componente `resultado_liquido` (sem nenhum campo de referência, resolve sempre pra última linha da DRE por **posição**) — necessário porque essa linha muda de rótulo ("LUCRO" vs "PREJUÍZO") conforme o sinal do resultado, confirmado contra balancete real; um componente comum de linha da DRE (que casa por texto exato) quebraria assim que o resultado de uma empresa mudasse de sinal entre apurações. Novo campo `IndicadorContabilDefinicao.grupo` (não exposto no formulário — só a migração de dados setou os 2 grupos originais pros 11 migrados; indicador novo nasce em "Indicadores Personalizados"). **Verificação antes de produção**: dry-run revertido (transação com rollback forçado) criou os 11 e comparou o motor novo contra o cálculo Python antigo pra uma apuração real — bateram exatos até a 20ª casa decimal, inclusive o `None` do EBITDA sem apuração anterior. Só depois disso o script rodou de verdade. Testado em seguida contra a API/relatório reais (incluindo editar um componente `resultado_liquido` via PATCH). `dashboard_contabil/indicadores.py` teve `CHAVES_CARDS`/`METADADOS_CARDS` removidos (zero consumidor); `calcula_indicadores()` e companhia foram **mantidos deliberadamente**, mesmo sem chamador em produção, como referência/auditoria pra recalcular e comparar se algum valor um dia parecer suspeito — única exceção neste projeto à convenção de apagar código sem uso, justificada pelo risco financeiro. O relatório "Gerar Dashboard" perdeu os 11 `.dcr-card` hardcoded, substituídos por um `{% for %}` genérico único (mesmo caminho que já servia indicador personalizado) — perda real e aceita conscientemente: cada um dos 11 tinha um ícone SVG próprio, agora todo indicador usa o mesmo ícone genérico. Detalhe completo no `CLAUDE.md` desta pasta. ### 105. Scroll aninhado no construtor de componentes + "Calcular com esta apuração" Dois pedidos na sequência, testando a migração da rodada 104: (a) captura de tela mostrando que a lista de componentes tinha um scroll (320px) por cima do scroll do checklist de contas (160px) — dois scrolls aninhados deixavam a área minúscula, nem um componente inteiro cabia sem rolar duas vezes; (b) além da fórmula em texto, mostrar os valores exatos que cada componente busca e o resultado calculado, pro contador conferir se a fórmula está certa. `.dc-ind-componentes-lista` perdeu o `max-height`/`overflow-y` (volta a crescer no fluxo normal do modal, que já rola inteiro); `.checklist-box` de cada componente aumentou de 160px pra 260px — sobra só um scroll aninhado (o checklist em si, necessário pelas ~100-150 contas/linhas de uma apuração real). Novo botão "Calcular com esta apuração" no modal de indicador, entre "Fórmula" e as ações — chama `POST /api/contabil-apuracoes/{id}/pre-visualizar-indicador/` (`ContabilApuracaoViewSet.pre_visualizar_indicador()`) com o formulário como está na tela (ainda não salvo), calcula contra a apuração aberta e devolve o valor de cada componente + o resultado final, sem persistir nada. Reaproveita a mesma validação (`IndicadorContabilDefinicaoInputSerializer`) e o mesmo motor de cálculo (`_contabil_resolve_componente_personalizado()`/`avalia_formula()`) de criar/editar de verdade, só que sobre componentes construídos em memória, nunca salvos. Detalhe completo no `CLAUDE.md` desta pasta. ### 106. Bug real — exclusão de indicador não padrão travava o hub inteiro Usuário reportou o modal de aviso "Chave(s) de indicador não padrão inexistente(s): diferença." ao tentar alternar um indicador no hub "Gerenciar Indicadores". Causa: um indicador não padrão de chave `diferença` tinha sido excluído em algum momento, mas `IndicadorContabilDefinicaoViewSet.perform_destroy()` só bloqueava/limpava referência pela **fórmula** (`indicador_referenciado`), nunca pelas referências em `ContabilApuracao.indicadores_selecionados`/`indicadores_ocultos` — a chave ficou órfã na apuração que a tinha selecionada. Como o frontend sempre reenvia a lista **inteira** a cada alternância de checkbox (`pidDcRenderHubIndicadores`/evento `change` em `dashboard-contabil.js`), e `ContabilIndicadoresSelecionadosSerializer`/`ContabilIndicadoresOcultosSerializer` rejeitavam a lista inteira se qualquer chave nela não existisse mais, o usuário ficava travado sem conseguir alternar **nenhum** indicador na apuração afetada, não só o excluído. Dois ajustes, um pro sintoma já existente e outro pra causa raiz: (a) as duas validações passaram a **descartar silenciosamente** chave inexistente/órfã em vez de rejeitar a lista inteira — é só estado de exibição (quais cards aparecem/estão ocultos), não dado auditado, então autocorrigir é seguro e resolve o travamento já em produção sem precisar de um script de correção manual; (b) `perform_destroy()` agora também limpa a chave excluída de toda `ContabilApuracao` que a referenciava em `indicadores_selecionados`/`indicadores_ocultos`, pra não deixar mais nenhuma referência órfã nova daqui pra frente. ### 107. Balancete/D.R.E. nascem recolhidos a partir do "grupo 4" Pedido: a árvore do Balancete (e, por extensão confirmada com o usuário, da D.R.E.) nascia sempre totalmente expandida, poluindo visualmente uma apuração com muitas contas analíticas. Passou a nascer recolhida a partir do nível equivalente ao "grupo 4" do código de classificação (ex. `1.01.01.001`, 4 segmentos) — essa conta aparece aberta, mas seus filhos (`1.01.01.001.001` em diante) ficam ocultos até o contador clicar pra expandir; o mesmo limiar de nível é aplicado à D.R.E., sobre `ContabilLinhaDre.nivel`. Aplicado nos dois lugares que já compartilhavam o mesmo algoritmo de árvore recolhível: a tela de revisão (`dashboard-contabil.js`, `dcColapsoPadrao()` pré-popula `dcContasColapsadas`/`dcDreColapsadas` em `renderRevisao()`, em vez de nascerem como `Set()` vazio) e o relatório "Gerar Dashboard" (`_contabil_arvore_contexto()` em `views.py` ganhou o campo `colapsado_padrao` por item, calculado a partir da nova constante `_CONTABIL_NIVEL_ABERTO_PADRAO = 3`; `dashboard-contabil-relatorio.html` usa esse campo pra não marcar `is-expanded` no botão e `pidDcrArvore()` semeia o estado `colapsadas` a partir do atributo `data-dcr-colapsado-padrao` antes da primeira renderização). A impressão continua forçando toda linha a aparecer (`tr[hidden] { display: table-row !important }` em `@media print`), então o comportamento de "documento impresso nunca esconde conta atrás de um grupo recolhido" não muda. Nenhuma mudança de modelo/migração — é só o estado inicial da mesma árvore que já existia. ### 108. Fundo uniforme na tabela do Balancete (revisão) Pedido: a linha sintética do Balancete (grupo do plano de contas, ex. "1.02.05 IMOBILIZADO") tinha um fundo elevado (`--bg-surface-raised`) além do negrito, criando um efeito de cores alternadas entre linha de grupo e linha analítica — o usuário pediu pra unificar, no mesmo padrão já usado na D.R.E. (que só usa negrito na linha totalizadora, sem fundo diferente). Removida a regra `.dc-conta-row--sintetica td { background: var(--bg-surface-raised); }` em `dashboard-contabil.css`, mantendo só o negrito (`.dc-conta-row--sintetica { font-weight: 600; }`) — a tabela do Balancete passa a ter uma única cor de fundo, igual à D.R.E. ### 109. Limiar de recolhimento ajustado pra nível 3 + destaque acompanha o grupo expandido Pedido: (a) o limiar de recolhimento padrão da rodada 107 ("grupo 4", ex. `1.01.01.001` aberto) ficou permissivo demais — passou a recolher a partir do 3º segmento do código (ex. `1.01.01` aberto, `1.01.01.001` em diante só sob demanda). `PID_DC_NIVEL_ABERTO_PADRAO` (`dashboard-contabil.js`) e `_CONTABIL_NIVEL_ABERTO_PADRAO` (`views.py`, usada pelo relatório "Gerar Dashboard") foram de `3` pra `2` — mesma dupla de constantes já documentada na rodada 107, ajustadas juntas. (b) Novo mecanismo de destaque: ao expandir uma conta/linha, só as linhas que acabaram de ficar visíveis (filhos diretos, não os netos — que continuam recolhidos pelo limiar acima) ganham um realce dourado; expandir uma delas em seguida move o realce pra seus filhos e o grupo destacado antes volta à cor padrão, nunca acumulando mais de uma "leva" ao mesmo tempo — ajuda o contador a acompanhar visualmente onde ele acabou de descer na árvore. `dcContasDestaque`/`dcDreDestaque` (`dashboard-contabil.js`, dois `Set()` resetados em `renderRevisao()`) guardam só a leva mais recente; `dcFilhosDiretos()` (função genérica, reaproveitada pelas duas árvores) calcula os filhos diretos de um item a partir da lista plana. Classe nova `.dc-conta-row--destaque` (`dashboard-contabil.css`, fundo `rgba(var(--gold-rgb), .16)`). Só na tela de revisão — o relatório "Gerar Dashboard" não ganhou esse destaque (é uma navegação estática, sem o mesmo conceito de "acabei de expandir isto agora"). Puramente visual/em memória, sem persistência nem mudança de modelo.