portal_publico/portal_api/dashboard_contabil/CHANGELOG.md

17 KiB

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 <dl> 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 <circle r="15.9155">, 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 <circle> 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.