Revisão completa — My Money DRE

Versão auditada: 10.32 · versão corrigida: 10.33 (11/09/2026) · site 2026.09.11-a · auditoria em 6 frentes (super dev, matemático, legislação) com recomputação independente de cada cálculo em Python/Node e checagem das normas vigentes em setembro/2026

123achados
27gravidade alta
59média
37baixa
107corrigidos na v10.33
33testes novos (v1043–v1076)
Situação dos achados: Corrigido: 99 · Parcial: 9 · Corrigido (servidor): 8 · Decisão do dono: 5 · Sem correção necessária: 2.
Como ler: gravidade alta = valor errado que custa dinheiro ao cliente ou fere a lei; média = engana ou contradiz outra tela; baixa = melhoria/texto. Cada achado traz a entrada usada, o valor que o app dava, o valor correto recomputado por fora e a base legal com a fonte consultada. "Corrigido" significa: código alterado na v10.33 e coberto por teste automatizado (138 testes de tela passam no build final). "Parcial": a parte que custa dinheiro foi corrigida; o resto é evolução. "Decisão do dono": depende de dado ou escolha que só o titular tem.
Método: seis auditores independentes (fiscal RV, IRPF/PJ, simuladores, carteira/valuation, código/segurança, jurídico) leram o fonte, extraíram cada função e a executaram fora do app comparando com a fórmula/lei; depois seis corretores reproduziram cada achado antes de mexer, corrigiram, escreveram testes com números calculados de forma independente e rodaram a suíte completa. Correções que mudam o resultado de telas antigas (TWR, previdência, TIEI, Fórmula Mágica, Gordon) estão sinalizadas.

Fiscal — renda variável (DARF, IRRF, isenções, FII/ETF/BDR, cripto, aluguel, proventos)

21 achados · 5 alta · 10 média · 6 baixa
Alta FIS-01

IR de 15% do swing calculado sobre o resultado JÁ líquido de IRRF+DARF, e sem abater o IRRF de 0,005%

Corrigido
📍 relContadorHTML L15884-15896 (lucroSw / irSw); apuracaoRelatorio L15725 (swTot)

O problema. No mapa mês a mês, lucroSw = Σ o.resultado. Mas salvarOp (L3672) grava resultado = lucroB − taxas − corretagem − irrf − darf, ou seja, já sem o imposto. O relatório então aplica 15% sobre esse valor líquido (irSw = baseSw*0.15) e nunca deduz o IRRF retido. O day trade, na mesma função, usa a base bruta (r.baseBruto = resultado+irrf+darf) — o swing ficou inconsistente. A correção 'contador swing líquido' da v10.27 não chegou a esta linha.

Evidência numérica. Duas vendas swing em maio/2026 salvas pela calculadora: PETR4 40→50 ×1000 (base 9.953,00, irrf 2,50, darf 1.490,45, resultado 8.460,05) e VALE3 20→25 ×2000 (base 9.973,00, irrf 2,50, darf 1.493,45, resultado 8.477,05). Vendas do mês 83.000 (>20k). App (relMapa reexecutado em Node): 'Lucro swing' = 16.937,10 (líquido) → 'IR 15%' = 2.540,57. Correto: base 19.926,00 × 15% = 2.988,90 − IRRF 5,00 = 2.983,90. Diferença R$ 443,33 a menos no relatório entregue ao contador (em outro cenário testado, 10.469,25→1.570,39 vs correto 1.866,11).

Base legal / matemática. IN RFB 1.585/2015 art. 56 §3º (ganho líquido = resultado positivo, admitida dedução de custos, não do imposto), art. 57 (15%), art. 63 §8º I (IRRF 0,005% deduzido do imposto do mês) — http://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=67494 ; Perguntas e Respostas IRPF 2024 Q676/678 — https://www.gov.br/receitafederal/pt-br/centrais-de-conteudo/publicacoes/perguntas-e-respostas/dirpf/pr-irpf-2024.pdf

O que foi feito. Reproduzido (relMapa maio: lucroSw 16.937,10 líquido → IR 2.540,56). Novo motor canônico rvApuracao/darfDTMes (bloco 'APURAÇÃO MENSAL DE RENDA VARIÁVEL', ~L15710) soma a base BRUTA do swing (resultado+irrf+darf) por classe e deduz o IRRF 0,005% do imposto; relContadorHTML passou a usar relMapa(ano,mes1) em vez de recomputar. Maio → base 19.926,00, IR 2.983,90.

Observação. relPrejuizos e relVendaAprox viraram wrappers do motor; apuracaoRelatorio (swTot) mostra a caixa 'Apuração do swing trade' com base/compensação/imposto.

🧪 /tmp/test_v1043.js · confiança do auditor: alta
Alta FIS-02

Operação salva sem classe do ativo: o relatório aplica 15% e a isenção de R$ 20 mil a FII/ETF/BDR e mistura prejuízo de FII com ações

Corrigido
📍 salvarOp L3672 (não grava classe/aliq); relContadorHTML L15883-15896 (isento=vendasMes<=20000, irSw=baseSw*0.15 para todo swing); relPrejuizos L15784-15807 (um único balde 'sw')

O problema. A calculadora (v10.27) decide 20%/sem isenção pelo sufixo '11', mas salvarOp não persiste essa decisão. No relatório, todo swing vira um balde só: vendas de FII/ETF/BDR entram na soma dos R$ 20 mil, lucro de FII é tributado a 15% e prejuízo de FII abate lucro de ações (a lei só permite FII×FII).

Evidência numérica. Março/2026: HGLG11 100→95 ×200 (venda 19.000, prejuízo −1.011,70) + VALE3 20→25 ×2000 (venda 50.000, base 9.973,00, irrf 2,50). relMapa: vendasSw 68.961,30, lucroSw 7.464,40 (líquido, ver FIS-01), IR 15% = 1.119,66. Correto: ações base 9.973,00 × 15% − 2,50 = 1.493,45; prejuízo de FII (−1.011,70) fica em saldo próprio, só compensável com ganho em cotas de FII. Se o único lucro fosse em HGLG11 com vendas ≤ 20k, o relatório diria 'SIM — isento' (FII não tem isenção e paga 20%).

Base legal / matemática. IN RFB 1.585/2015 art. 37 caput (20% FII) e §2º (perdas em FII só com ganhos em FII), art. 59 I e §2º II (isenção só ações à vista; não vale para fundos de índice) — http://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=67494 ; Lei 11.033/2004 art. 3º I — https://www.planalto.gov.br/ccivil_03/_ato2004-2006/2004/lei/l11033compilado.htm

O que foi feito. salvarOp grava classe (acao/unit/fii/etf/bdr/etf_rf), aliq, venda e custos; rvClasseOp infere a classe das ops antigas pelo ticker. No motor: vendas de ações/units contam para os R$ 20 mil, ETF/BDR 15% sem isenção, FII 20% em balde próprio (saldoFII) — prejuízo de FII só compensa FII (art. 37 §2º). saveEditOp preservava só 8 campos e apagava `modo` (swing editado virava day trade): agora preserva e expõe modalidade/classe/venda no modal de edição.

Observação. Março do cenário do auditor: irSw 1.493,45 (ações) e saldoFII 1.011,70.

🧪 /tmp/test_v1043.js, /tmp/test_v1044.js · confiança do auditor: alta
Alta FIS-03

Imposto do swing trade nunca aparece no DARF a recolher: badge diz 'Sem DARF', Agenda e DRE ignoram os 15%

Corrigido
📍 darfDTMes L15644-15662 (`if(o.modo==='swing') return`); getDARFMes/updateDARFBadge L15663-15700; agEventosDoMes L21340; avisosDarf L21540; DRE L29196; fechamento L24105

O problema. darfDTMes é a 'função canônica' usada por badge, Agenda, avisos e DRE, e ela descarta toda operação swing. O imposto de 15% do swing só é calculado no Relatório Completo (e errado, FIS-01). Um investidor que só faz swing vê 'Sem DARF', não recebe aviso de vencimento e a DRE subestima os impostos.

Evidência numérica. Uma única operação swing salva em setembro/2026: PETR4 40→50 ×1000, vendas do mês 50.000 → calcAcoes mostra DARF 1.490,45 (correto: base 9.953 × 15% − 2,50). Badge do Histórico: darfDTMes(2026,9).darf = 0 → 'Sem DARF'; Agenda de outubro sem evento 'DARF 6015'; avisosDarf vazio. Correto: DARF 6015 de R$ 1.490,45 vencendo 30/10/2026.

Base legal / matemática. IN RFB 1.585/2015 art. 56 §5º (apuração mensal, pagamento até o último dia útil do mês seguinte, um só código 6015 para ganhos líquidos comuns e day trade), art. 57 — http://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=67494 ; P&R IRPF 2024 Q700

O que foi feito. darfDTMes agora É a função canônica de renda variável (day + swing + FII, compensações separadas, IRRF excedente, piso R$ 10); aliases darfRVMes e darfSwingMes. Badge (getDARFMes/updateDARFBadge), agEventosDoMes (evento 'DARF Renda Variável 6015 (comp. MM/AAAA) — day trade + swing'), avisosDarf, DRE (L~29360 + rótulos do extrato), dashboard e fechamentoCalc passam a receber o valor com swing automaticamente.

Observação. Setembro só com swing: darfDTMes(2026,9).darf = 1.490,45; Agenda de outubro com evento no dia 30 (último dia útil).

🧪 /tmp/test_v1043.js · confiança do auditor: alta
Alta FIS-04

Heurística 'termina em 11': ETF de ações tributado a 20% (correto 15%), BDR (…34) ganha isenção (não tem), Units viram 'fundo', ETF de renda fixa vira DARF

Corrigido
📍 calcAcoes L3617-3631 (ehFundo=/11$/, aliqSwing = ehFundo?0.20:0.15, temIsencao=!ehFundo)

O problema. Só há duas regras: 'termina em 11' = 20% sem isenção; resto = ação com isenção. Mas ETF de ações (BOVA11, IVVB11, SMAL11) paga 15% (sem isenção); BDR (AAPL34, MSFT34) paga 15% sem isenção; Units (BPAC11, TAEE11, KLBN11, SANB11) são ações com isenção; ETF de renda fixa (IMAB11, B5P211) é tributado na fonte pelo administrador (25/20/15%), sem DARF. Só o FII/Fiagro é 20%.

Evidência numérica. BOVA11 100→110 ×300 (venda 33.000): app DARF = 2.981,10×20% − 1,65 = 594,57; correto 2.981,10×15% − 1,65 = 445,52 (paga R$ 149,05 a mais). AAPL34 50→60 ×250 (venda 15.000): app 'ISENTO', DARF 0; correto 2.491,75×15% − 0,75 = 373,01. BPAC11 30→35 ×500 (vendas 17.500): app 20% = 497,17; correto isento (Unit é ação, vendas ≤ 20k) = 0.

Base legal / matemática. IN RFB 1.585/2015 art. 56 §1º I 'a' (BDR) e 'e' (FII), art. 57 (15%), art. 37 (20% só FII), art. 59 I e §2º II (isenção só ações; excluídos fundos de índice) — http://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=67494 ; ETF de renda fixa: Lei 13.043/2014 art. 2º-4º — https://www.planalto.gov.br/ccivil_03/_ato2011-2014/2014/lei/l13043.htm

O que foi feito. Reproduzido (BOVA11 20%, AAPL34 isento, BPAC11 20%). Novo select #a-classe 'Classe do ativo' na aba Ações, pré-preenchido por rvClasseInferir (listas RV_ETF_RF, RV_ETF_ACOES, RV_UNITS, sufixo 3x = BDR, catálogo de FIIs, ACOES_CATALOGO/B3_STOCKS = unit; '11' desconhecido = FII); rvRegra(classe, modo) devolve alíquota/isenção/texto; calcAcoes usa isso (ETF RF: sem DARF, IR na fonte).

Observação. BOVA11 → 445,51 (2.981,10×15% − 1,65; o auditor arredondou 445,52), AAPL34 → 373,01 sem isenção, BPAC11 → isento.

🧪 /tmp/test_v1044.js · confiança do auditor: alta
Alta FIS-05

'DARF DEVIDA (20%)' e 'Impostos' somam o DARF gravado operação a operação — sem netting mensal nem compensação de prejuízo

Corrigido
📍 irpfRV L29960-29972 (porMes.darf += o.darf; totDarf); renderHistory L4252 (tDARF += o.darf)

O problema. O DARF gravado em cada operação (salvarOp) é 20% do lucro daquela operação isolada; operações com prejuízo gravam 0. Somar isso ignora a compensação dentro do mês e o saldo de meses anteriores. O comentário da v8.16 diz que darfDTMes é 'usada em TODOS os lugares', mas irpfRV e o card 'Impostos' do Histórico continuam somando por operação — o usuário vê três números diferentes para o mesmo mês.

Evidência numérica. Maio/2026, duas operações day trade: PETR4 30→35 ×1000 (base 4.985,05, irrf 49,85, darf 947,16) e 30→27 ×1000 (base −3.013,11). irpfRV: 'DARF DEVIDA (20%)' = 947,16; Histórico 'Impostos' = 997,01 (irrf+darf). Badge/darfDTMes (correto): base do mês 1.971,94 × 20% − 49,85 = 344,54.

Base legal / matemática. IN RFB 1.585/2015 art. 65 §§10-11 (resultado mensal da compensação de day trade; se positivo, 20%) e art. 64 — http://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=67494 ; P&R IRPF 2024 Q679

O que foi feito. irpfRV: DARF de cada mês = darfDTMes(ano,m).darf (netting, compensação, swing, piso), rótulo 'DARF 6015 (day trade 20% + swing 15%/20%)' e IRRF a compensar; renderHistory card 'Impostos' = Σ IRRF das ops + Σ DARF mensal dos meses do período. O `darf` por operação continua gravado como estimativa isolada (o motor reconstrói a base com ele) mas não é mais somado.

Observação. Julho: irpfRV 344,54 e card Impostos 5.086,18 no cenário do teste.

🧪 /tmp/test_v1043.js · confiança do auditor: alta
Média FIS-06

IRRF de 1% que excede o imposto do mês (após compensar prejuízo) é descartado, em vez de compensado nos meses seguintes do ano

Corrigido
📍 darfDTMes L15659 (`Math.max(0, baseTributavel*0.20 - irrf)`); relContadorHTML L15889; gerarRelatorioIR L19286; calcImpostos L9256

O problema. Quando o prejuízo acumulado zera a base tributável, o IRRF retido naquele mês vira crédito a compensar até dezembro do mesmo ano (e restituível no ajuste). O app aplica max(0, …) e esquece o crédito; no mês seguinte cobra DARF cheio.

Evidência numérica. Jan/2026 DT base −2.000,00; fev/2026 DT PETR4 30→32 ×1000: base 1.985,74, IRRF 19,86 → darfDTMes(fev): compensado 1.985,74, baseTributavel 0, darf 0 — os R$ 19,86 retidos somem. Mar/2026 PETR4 30→31 ×1000: base 985,97, IRRF 9,86, saldo de prejuízo 14,26 → app DARF = 971,71×20% − 9,86 = 184,48. Correto: 194,34 − 9,86 − 19,86 (IRRF de fev a compensar) = 164,62.

Base legal / matemática. IN RFB 1.585/2015 art. 65 §8º II e §9º — http://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=67494 ; P&R IRPF 2024 Q684-685 (compensável só até dezembro do ano da retenção)

O que foi feito. Reproduzido (mar DARF 184,48). O motor carrega saldoIrrf (crédito = IRRF do mês + saldo anterior; darfBruto = max(0, imposto − crédito); saldo zera na virada do ano — art. 65 §8º-9º). Exibido no badge, apuracaoRelatorio, relatório completo (coluna 'IRRF a comp.'), irpfRV e gerarRelatorioIR; calcImpostos (calculadora manual) também carrega o crédito.

Observação. mar = 194,34 − 9,86 − 19,86 = 164,62; darfDTMes(2027,1).saldoIrrfAnt = 0.

🧪 /tmp/test_v1043.js · confiança do auditor: alta
Média FIS-07

'Imposto devido (20%)' é calculado sobre a base bruta sem a compensação, contradizendo o DARF impresso duas linhas abaixo

Corrigido
📍 apuracaoRelatorio L15743-15744

O problema. A caixa 'Apuração do DARF' imprime 'Imposto devido (20%)' = 20% × r.baseBruto e, em seguida, 'DARF a recolher' = r.darf (que já compensa prejuízo). O contador recebe duas contas que não fecham, e o prejuízo compensado nem aparece.

Evidência numérica. Jan −3.013,11; fev base 4.985,05, IRRF 49,85. Relatório de fev: 'Base de cálculo 4.985,05 · Imposto devido (20%) 997,01 · IRRF 49,85 · DARF 344,54'. 997,01 − 49,85 = 947,16 ≠ 344,54. Correto: mostrar prejuízo compensado 3.013,11, base tributável 1.971,94, imposto 394,39, IRRF 49,85, DARF 344,54.

Base legal / matemática. IN RFB 1.585/2015 art. 65 §11 — http://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=67494

O que foi feito. apuracaoRelatorio: caixa 'Apuração do day trade' com resultado bruto, prejuízo compensado (saldo anterior), base tributável, imposto 20% e IRRF; caixa 'Apuração do swing trade' (vendas de ações, isenção, comum/FII com compensação); caixa final 'DARF do mês — 6015' com imposto − IRRF − excedente (+ acumulado) e vencimento.

Observação. Fev: 'Prejuízo compensado 3.013,11 · Base tributável 1.971,94 · Imposto (20%) 394,39 · DARF 344,54'.

🧪 /tmp/test_v1045.js · confiança do auditor: alta
Média FIS-08

relPrejuizos usa o resultado líquido de imposto e ignora a regra do mês isento: a seção 5 contradiz a seção 4 do mesmo relatório

Corrigido
📍 relPrejuizos L15784-15807 vs mapa L15871-15905

O problema. A seção 4 abate o prejuízo pela base bruta (r.baseBruto) e não computa prejuízo de mês isento; a seção 5 (relPrejuizos) abate pelo o.resultado (líquido de IRRF+DARF) e computa todo prejuízo swing. Os dois saldos saem diferentes no mesmo PDF.

Evidência numérica. DT: jan base −6.012,42; fev base 4.985,05 (resultado gravado 3.988,04). Seção 4 'saldo' = 6.012,42 − 4.985,05 = 1.027,37; seção 5 'Saldo ao fim' = 6.012,42 − 3.988,04 = 2.024,38. Swing: mar prejuízo −3.008,10 com vendas 12.000 (mês isento) e abr lucro tributável: seção 4 'Prej. comp.' = 0 e saldo 0; seção 5 'Saldo antes' = 3.008,10 e 'fim' = 0 (consumido).

Base legal / matemática. IN RFB 1.585/2015 arts. 64 e 65 §11 (compensação sobre o resultado, não sobre o líquido de imposto) — http://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=67494

O que foi feito. relPrejuizos (líquido de imposto, sem regra do mês isento) foi substituída por um wrapper do motor; a seção 5 do relatório deriva de mapa[0].saldo*Ant e mapa[último].saldo* (três linhas: day, comum, FII), mesmos números da seção 4.

🧪 /tmp/test_v1043.js · confiança do auditor: alta
Média FIS-09

'Prejuízo em mês isento não entra no saldo (IN RFB 1.585, art. 64)': o art. 64 não diz isso; a própria IN (art. 59 §1º) e o P&R da Receita admitem compensar essas perdas

Corrigido
📍 relContadorHTML L15892 (comentário e regra `if(!isento)`), L15982 (memória de cálculo)

O problema. O relatório afirma, com citação de artigo, que a perda de um mês com vendas ≤ R$ 20 mil não pode ser compensada. O art. 64 trata da compensação em geral e não contém essa restrição. O art. 59 §1º dispensa o preenchimento do demonstrativo para as operações isentas 'exceto no caso de pretender compensar as perdas apuradas' — ou seja, contempla a compensação; o P&R IRPF 2024 (Q699, mesma redação) idem. A interpretação restritiva existe no mercado, mas não pode ser apresentada como texto da IN, e não é o que a RFB publica.

Evidência numérica. Mar/2026 swing PETR4 10→8 ×1500 (venda 12.000, prejuízo −3.008,10); abr/2026 VALE3 lucro base 9.973 com vendas 50.000. App: 'Prej. comp.' 0, IR 15% sobre 9.973 (=1.495,95 − 2,50). Pelo art. 59 §1º informando o prejuízo no demonstrativo: base 6.964,90 × 15% − 2,50 = 1.042,24 (diferença R$ 451,21).

Base legal / matemática. IN RFB 1.585/2015 art. 59 §1º e art. 64 — http://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=67494 ; P&R IRPF 2024 Q679-681 e Q699 — https://www.gov.br/receitafederal/pt-br/centrais-de-conteudo/publicacoes/perguntas-e-respostas/dirpf/pr-irpf-2024.pdf ; Receita, 'Compensações' — https://www.gov.br/receitafederal/pt-br/assuntos/meu-imposto-de-renda/pagamento/renda-variavel/bolsa-de-valores-1/compensacoes

O que foi feito. Confirmado no texto da IN 1.585 art. 59 §1º ('exceto no caso de pretender compensar as perdas apuradas'). Opção rvCompensarIsento() (localStorage dt_rv_comp_isento, padrão ligada) com checkbox no modal relContadorAbrir; motor leva a perda de mês isento ao saldo comum quando ligada (lucro de mês isento nunca consome saldo); memória de cálculo cita art. 59 §1º e explica a leitura restritiva quando desligada.

Observação. test_v1025.js antigo codificava a regra restritiva (R$ 450,00 e 'Dividendos (isentos)'): ajustado para R$ 225,00 + art. 59 §1º e rótulo novo — ele passa só no arquivo corrigido.

🧪 /tmp/test_v1045.js (ligada: compSw 3.008,10 / irSw 1.042,24; desligada: 0 / 1.493,45) · confiança do auditor: media
Média FIS-10

DARF inferior a R$ 10,00 é apresentado como 'a recolher' em vez de acumulado para o mês seguinte

Corrigido
📍 darfDTMes L15659; updateDARFBadge L15687; agEventosDoMes L21341 (`darfDT>0.01`); avisosDarf L21541 (`r.darf>0.009`)

O problema. Nenhuma rotina aplica o piso de R$ 10 do DARF. O app manda pagar (e lança na Agenda) valores de centavos, que a rede bancária nem aceita; o correto é somar ao DARF do mesmo código nos meses seguintes até atingir R$ 10.

Evidência numérica. DT PETR4 30→30,05 ×1000: base 36,19, IRRF 0,36, DARF = 7,24 − 0,36 = 6,88 → badge 'DARF a recolher R$ 6,88', evento na Agenda. Correto: 'R$ 6,88 acumulado para o próximo mês (abaixo de R$ 10,00 — Lei 9.430 art. 68)'; se o mês seguinte devesse 5,00, pagar 11,88 no vencimento deste último.

Base legal / matemática. Lei 9.430/1996 art. 68 e §1º — https://www.planalto.gov.br/ccivil_03/leis/l9430.htm

O que foi feito. Motor: acum = darfBruto + saldoDarf anterior; < R$ 10 → darfPagar 0, acumulaDarf true, saldoDarf carregado (sem zerar no ano — Lei 9.430 art. 68 §1º). `.darf` devolvido = darfPagar; badge mostra 'Acumula' + nota, Agenda/avisos só lançam quando ≥ 10 ('inclui acumulado'), irpfRV mostra 'acumula X', relatórios explicam.

🧪 /tmp/test_v1043.js (ago 6,88 → 0; set 5,09+6,88 = 11,97) · confiança do auditor: alta
Média FIS-11

Agenda lança o DARF no último dia CALENDÁRIO do mês seguinte; o vencimento é o último dia ÚTIL

Corrigido
📍 agEventosDoMes L21281 (`ultimoDia = new Date(_agAno,_agMes+1,0).getDate()`) e L21342 (`dia:ultimoDia`); contraste com avisoUltimoDiaUtil L21526 e apuracaoVencimento L15706

O problema. O evento 'DARF Day Trade 6015' entra na Agenda no dia 30/31, mesmo quando cai em sábado/domingo. As outras rotinas (avisos, relatório) usam o último dia útil. Quem se guia pela Agenda paga em atraso (multa 0,33%/dia + Selic).

Evidência numérica. Competência 09/2026: Agenda de outubro mostra o DARF em 31/10/2026 (sábado). Correto: 30/10/2026 (sexta), como apuracaoVencimento(2026,9) já devolve. Competência 04/2026 → Agenda 31/05/2026 (domingo) vs correto 29/05/2026.

Base legal / matemática. IN RFB 1.585/2015 art. 56 §5º ('até o último dia útil do mês subsequente') — http://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=67494

O que foi feito. Não havia função de feriados (só a lista de eventos de 2026). Criadas rvPascoa (Meeus), rvFeriadoNacional (fixos + 20/11 + Carnaval seg/ter, Sexta-feira Santa, Corpus Christi — dias sem expediente bancário), rvDiaUtil e rvUltimoDiaUtil. agEventosDoMes usa o último dia útil nos dois eventos (darfdt e darfcl); apuracaoVencimento e avisoUltimoDiaUtil passam por rvUltimoDiaUtil.

Observação. O evento darfcl (carnê-leão) também mudou de dia — avisar o corretor do IRPF (FIS-05 dele trata do clRender, não da Agenda).

🧪 /tmp/test_v1045.js (out/26 → 30, mai/26 → 29; Páscoa 2026 = 05/04) · confiança do auditor: alta
Média FIS-12

Calculadora de aluguel usa IR fixo de 15% sobre a remuneração do doador; a alíquota é regressiva (22,5% até 180 dias) e 'Estimativa mensal' é igual ao período

Corrigido
📍 calcAluguel L9159-9180 (`irValor=rendimentoPeriodo*0.15`, `rendimentoPeriodo/30*30`)

O problema. A remuneração do emprestador (doador) no BTC é tributada como renda fixa: 22,5% ≤180 dias, 20% até 360, 17,5% até 720, 15% acima. Contratos de aluguel são curtos (30-90 dias), então a calculadora quase sempre subestima o IR em 50%. A aba BTC Aluguel (btcAliquotaIR L28443) já faz certo; esta calculadora ficou defasada. Além disso, 'Estimativa mensal' = rendimentoPeriodo/30*30 = o próprio valor do período, e 'Período (dias)' é tratado como dias úteis (taxa/252×dias) sem dizer.

Evidência numérica. 1.000 ações × R$ 28,50 × 3,5% a.a. × 30 dias: bruto 118,75; app IR 17,81 (15%); correto 26,72 (22,5%) — líquido 92,03 e não 100,94. Card 'Estimativa mensal' mostra 118,75 para um período de 30 dias e também para 10 dias (mostraria 39,58 rotulado de 'mensal').

Base legal / matemática. IN RFB 1.585/2015 art. 73 (remuneração do emprestador tributada como renda fixa) e art. 46 (22,5/20/17,5/15%) — http://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=67494 ; Lei 11.033/2004 art. 1º

O que foi feito. Reproduzido (IR 15% = 17,81; mensal = período). calcAluguel reescrita com a lógica de btcCalcular sem chamá-la: du = round(dias×252/365), bruto = volume×[(1+taxa)^(du/252)−1], IR por btcAliquotaIR(dias corridos), mensal = 21 d.u., anual = volume×taxa; rótulos 'dias corridos', 'IR 22,5% (regressivo)'; btcCalcular intocada.

Observação. Cobre também SIM-03 do auditor de simuladores.

🧪 /tmp/test_v1046.js (1.000×28,50×3,5%×30d → 81,82 / 18,41 / 63,41) · confiança do auditor: alta
Média FIS-13

'IR estimado (15%)' do mês neteia prejuízos entre alienações de cripto — no ganho de capital cada alienação é apurada isoladamente e perda não compensa ganho

Corrigido
📍 cdtRender L22028-22032 (`lucroMes*0.15`); cdtSalvar L22011 (imposto gravado com vendasMes só da própria venda)

O problema. O texto do app diz corretamente que 'prejuízos em cripto não compensam meses seguintes', mas o resumo do mês soma lucros e prejuízos das operações e aplica 15% sobre o líquido. No regime de ganho de capital (IN 1.888 / GCAP) a perda de uma alienação não abate o ganho de outra, nem no mesmo mês. Além disso, o imposto gravado em cada operação (cdtSalvar) usa a isenção pela venda daquela operação, e não pelo total do mês, então ops salvas cedo no mês ficam com imposto 0 mesmo quando o mês estoura os R$ 35 mil.

Evidência numérica. Mês com vendas 60.000: op1 lucro +5.000, op2 −3.000. App: 'IR estimado (15%)' = 2.000 × 15% = 300,00. Correto (GCAP): 5.000 × 15% = 750,00. Op1 salva quando vendasMes era 20.000: imposto gravado 0; após op2 (vendas 60.000), o histórico continua mostrando 0 para op1.

Base legal / matemática. Lei 8.981/1995 art. 21 (ganho de capital por alienação); IN SRF 84/2001; P&R IRPF 2024 Q627 (isenção pelo conjunto de alienações do mês; acima, 'o ganho de capital relativo a todas as alienações' é tributado) — https://www.gov.br/receitafederal/pt-br/centrais-de-conteudo/publicacoes/perguntas-e-respostas/dirpf/pr-irpf-2024.pdf

O que foi feito. Reproduzido (IR do mês 300 sobre o líquido). cdtRender tributa Σ ganhos (perdas listadas como 'não compensáveis'); cdtRecalcMes refaz o imposto de todas as ops do mês pelo total vendido (chamada em cdtSalvar e cdtRemove); coluna 'IR' no histórico; textos da infobar e do cálculo.

🧪 /tmp/test_v1047.js (op1 +5.000 e op2 −3.000 com vendas 50 mil → 750; op1 recalculada de 0 para 750) · confiança do auditor: alta
Média FIS-14

Relatório afirma isenção de FII com 'fundo com 50+ cotistas'; desde a Lei 14.754/2023 o mínimo é 100 cotistas, e o app dá 'isento' sem checar nada

Corrigido
📍 relContadorHTML L16003; fiisProvAdd L27527 (toast 'isento de IR p/ pessoa física'); ajuda L12072/L12074

O problema. O número de 50 cotistas é a redação antiga (Lei 11.196/2005). A Lei 14.754/2023 elevou para 100 cotistas (prazo de adequação 30/06/2024) e criou o limite de 30% para cotistas ligados. O app também não pede/mostra as condições (cotas negociadas exclusivamente em bolsa/balcão organizado; cotista com < 10% das cotas/rendimentos).

Evidência numérica. Texto impresso para o contador: 'fundo com 50+ cotistas'. Lei 11.033 art. 3º §1º I (redação Lei 14.754): 'no mínimo, 100 (cem) cotistas'. Um FII com 80 cotistas: app 'isento'; correto: rendimento tributado na fonte a 20% (Lei 8.668 art. 17).

Base legal / matemática. Lei 11.033/2004 art. 3º III e §1º-§4º (redação da Lei 14.754/2023) — https://www.planalto.gov.br/ccivil_03/_ato2004-2006/2004/lei/l11033compilado.htm ; Lei 14.754/2023 — https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2023/lei/l14754.htm

O que foi feito. Textos: relatório do contador ('100 (cem) cotistas', cotas exclusivamente em bolsa/balcão organizado, cotista < 10%, ligados < 30%, Lei 11.033 art. 3º III e §§1º-4º, redação da Lei 14.754/2023; fora disso, 20% na fonte), toast de fiisProvAdd e tabela de regras da ajuda.

Observação. Não criei o checkbox 'atende aos requisitos' com IR 20%: o campo do FII é 'valor recebido' (líquido) e a retenção, quando há, é feita pelo fundo — um checkbox sem cálculo seria só informativo. Fica como sugestão de produto.

🧪 /tmp/test_v1048.js · confiança do auditor: alta
Média FIS-15

Dividendos tratados como sempre isentos: sem o IRRF de 10% acima de R$ 50 mil/mês por fonte (2026) nem a tributação mínima; JCP entra com IR 0 embora o relatório diga '15% retidos na fonte'

Corrigido
📍 relContadorHTML L15909-15911 e L16003 ('Dividendos (isentos)'); addProv L13369-13376 (ir manual, default 0); provUsarAnunciado L13350-13363 (não sugere 15% para JCP); ajuda L11924+ (IRPF) sem IRPFM

O problema. Desde 01/01/2026 (Lei 15.270/2025, art. 6º-A da Lei 9.250): lucros/dividendos pagos por uma mesma PJ a uma mesma PF acima de R$ 50.000 no mês sofrem IRRF de 10% (exceto lucros apurados até 2025 com distribuição aprovada até 31/12/2025); e a tributação mínima (art. 16-A) alcança dividendos e JCP de quem recebe > R$ 600 mil/ano (ganhos em bolsa ficam fora). O app rotula dividendo como isento sem ressalva e lança no Contas a Receber o valor bruto. Para JCP, o campo IR fica 0 salvo digitação manual, e o relatório imprime a linha 'Juros sobre capital próprio (tributados, 15% na fonte)' com IR 0,00.

Evidência numérica. Dividendo de R$ 60.000 de uma mesma companhia em 03/2026: app → líquido 60.000, 'Dividendos (isentos)'. Correto: IRRF 10% × 60.000 = 6.000, líquido 54.000 (retenção sobre o total, não só o excedente). JCP 1.000 ações × R$ 1,00 puxado por 'proventos anunciados': app IR 0, líquido 1.000; correto IR 150,00, líquido 850,00.

Base legal / matemática. Lei 15.270/2025 (art. 6º-A e 16-A da Lei 9.250/1995; vigência 01/01/2026) — https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2025/lei/L15270.htm ; JCP: Lei 9.249/1995 art. 9º §2º

O que foi feito. provIrSugerido(tipo, ticker, bruto, pgto): JCP → 15%; dividendo (ano ≥ 2026) → soma os dividendos do mesmo ticker no mês de pagamento e, acima de R$ 50.000, sugere 10% × total − IR já lançado. updateProvPreview mostra 'IR sugerido … [aplicar]'; addProv aplica automaticamente quando o campo IR não foi digitado (flag _pvIrManual). Relatório: rótulo dos dividendos com a ressalva dos 50 mil e nota sobre art. 6º-A/16-A da Lei 9.250 (Lei 15.270/2025) e a exceção dos lucros de até 2025.

Observação. Artigo conferido em fontes secundárias (planalto bloqueado pelo proxy): IRRF de 10% = art. 6º-A da Lei 9.250/1995 incluído pela Lei 15.270/2025; IRPFM = art. 16-A.

🧪 /tmp/test_v1048.js (JCP 1.000×1 → 150; PETR4 30 mil + 30 mil em mar/2026 → 6.000 no 2º; 2025 → 0) · confiança do auditor: alta
Baixa FIS-16

relVendaAprox subestima a venda pelas taxas/corretagem: vendas logo acima de R$ 20.000 podem sair como 'isentas'

Corrigido
📍 relVendaAprox L15777-15783

O problema. venda ≈ total + resultado + irrf + darf = venda real − taxas − corretagem. A isenção compara essa soma com 20.000, então uma venda real de R$ 20.008 vira 19.998,99 e o mês é classificado como isento.

Evidência numérica. PETR4 10→20,008 ×1000: venda real 20.008,00; reconstruída 19.998,9976 → 'SIM — vendas ≤ R$ 20.000'. Correto: não isento (20.008 > 20.000), IR 15% sobre 9.998,99 − 1,00 = 1.498,85.

Base legal / matemática. Lei 11.033/2004 art. 3º I (limite pelo valor das alienações) — https://www.planalto.gov.br/ccivil_03/_ato2004-2006/2004/lei/l11033compilado.htm

O que foi feito. salvarOp grava venda = saída×qtd e custos = taxas+corretagem; rvVendaOp/relVendaAprox usam `venda` quando existe e, nas ops antigas, reconstroem somando também `custos`.

🧪 /tmp/test_v1044.js (venda 20.008 → não isento) · confiança do auditor: alta
Baixa FIS-17

Rótulos e ajuda desatualizados: taxa B3 do swing, 'DARF — 15%' quando aplica 20%, 'Isento' em day trade, '0,023% por perna' para swing

Corrigido
📍 setAModo L3519 (infobar swing); calcAcoes L3641 (rótulo 'DARF — 15% sobre lucro'); calcImpostos L9271/L9288 ('Isento'); ajuda L12281 ('0,023% por perna')

O problema. (a) infobar do swing diz 'emol. 0,0115% + liquidação 0,0115%' mas o código usa 0,005% + 0,025% = 0,030% (v9.28); (b) para FII o app calcula 20% mas a linha do resultado diz 'DARF — 15% sobre lucro'; (c) calcImpostos imprime 'Isento' quando o DARF de day trade é zero por compensação — day trade nunca é isento (IN 1.585 art. 65 §15); (d) a ajuda diz que o app aplica 0,023% por perna, o que só vale para day trade.

Evidência numérica. HGLG11 150→160 ×100: linhas 'Regra aplicada … alíquota 20%' e, logo abaixo, 'DARF — 15% sobre lucro: R$ 197,34' (197,34 = 20%). Infobar swing 0,0115%+0,0115% = 0,023% vs fees efetivas 0,030% (R$ 27,00 sobre 40k+50k, não R$ 20,70).

Base legal / matemática. IN RFB 1.585/2015 art. 65 §15 (isenção não se aplica a day trade); tarifas B3 (boa prática de coerência texto×código)

O que foi feito. (a) infobar do swing: 'emolumentos 0,005% + liquidação 0,025%'; (b) rótulo 'DARF — '+aliq+'% sobre lucro' por classe; (c) calcImpostos e gerarRelatorioIR: 'Isento' → 'Sem DARF (compensado/IRRF)' com nota do art. 65 §15; (d) ajuda: '0,023% por perna no day trade e 0,030% no swing'.

🧪 /tmp/test_v1044.js (rótulo 20% do FII, infobar) · confiança do auditor: alta
Baixa FIS-18

Minicontratos só têm regra de day trade (20%/1%); posição carregada (swing) paga 15% sobre a soma dos ajustes com IRRF 0,005%; aba Opções não tem apuração fiscal

Parcial
📍 calcMini L3677-3698 (sempre calcTaxes = DT); painel Opções L1884-1970 (calcBS/calcIV/breakeven sem IR)

O problema. A aba 'Mini Dólar' não tem seletor day/swing: quem carrega WIN/WDO de um dia para outro vê 20% + IRRF 1%, quando o correto é 15% sobre o resultado dos ajustes diários e IRRF 0,005% sobre a soma positiva dos ajustes no encerramento. A aba Opções calcula prêmio/gregas/break-even mas não mostra ganho líquido tributável (prêmio, exercício, custo médio) nem IRRF 0,005% sobre a soma dos prêmios do dia.

Evidência numérica. WIN 120.000→120.500 ×5 contratos, posição de 3 dias: app IRRF 1% = 4,97 e DARF 20% = 94,43 sobre base 497 (500×0,20×5 − 3,00). Correto swing: IRRF 0,005% × 500 = 0,03 (dispensado, ≤ R$ 1) e imposto 15% × 497 = 74,55.

Base legal / matemática. IN RFB 1.585/2015 art. 61 (futuros: soma dos ajustes diários), art. 63 I (0,005% sobre ajustes), art. 57 (15%), art. 60 (opções), art. 63 II — http://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=67494

O que foi feito. Mini Dólar/Índice: seletor 'Modalidade' (day trade × posição/swing) com setMiniModo; em swing: IRRF 0,005% sobre os ajustes positivos (dispensado se ≤ R$ 1,00, art. 63 §§4º-5º) e DARF 15% sobre a base líquida de custos. window._lastMini exposto.

Observação. Bloco 'Resultado fiscal' na aba Opções (art. 60: prêmios, exercício, IRRF 0,005%) não implementado — é funcionalidade nova na calculadora de gregas; recomendo decidir o escopo com o dono.

🧪 /tmp/test_v1044.js (WIN 500 pts ×5 → IRRF 0, DARF 74,10) · confiança do auditor: media
Baixa FIS-19

Operações importadas entram com resultado 0, sem modo e com o IRRF da nota rateado — viram 'day trade' com base fantasma igual ao IRRF e nunca geram DARF

Corrigido
📍 _notaDoImport L14506-14535

O problema. Cada trade importado é gravado com resultado '0.00', darf '0.00', irrf = IRRF da nota ÷ nº de trades e sem `modo`. darfDTMes trata a op como day trade e reconstrói base = 0 + irrf + 0 → um 'lucro' igual ao IRRF; o IRRF de swing (0,005%) da nota é abatido do DARF de day trade. As vendas da nota não geram apuração alguma (pendência já conhecida: 'importar nota descarta vendas').

Evidência numérica. Nota com 2 trades e IRRF R$ 1,20 (swing): darfDTMes do mês → baseBruto 1,20, irrf 1,20, ops 2. Somando um DT real de base 1.000 (irrf 10): DARF = 1.001,20×20% − 11,20 = 189,04 em vez de 190,00 (e a base do mês fica inflada em 1,20).

Base legal / matemática. IN RFB 1.585/2015 art. 58 (custo médio) e art. 65 §1º I (day trade = mesma corretora, mesmo dia) — http://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=67494

O que foi feito. _notaDoImport('historico'): custos da nota (liquidação+emolumentos+taxa operacional) rateados pelo valor; compra×venda do mesmo papel na nota = day trade pela quantidade casada (art. 65 §1º I); venda restante = swing pelo preço médio da Carteira (art. 58) — sem posição fica com resultado 0 e aviso; compra restante = registro sem apuração (irrf 0). IRRF da nota vai para os day trades com lucro (1%) ou, sem day trade, para as vendas swing (0,005%). Grava modo/classe/aliq/venda/custos. Motor ignora registros `_fromNota` sem modo (só o IRRF conta). Destino 'carteira' inclui os custos rateados no preço médio.

Observação. Sem nota real para testar o parser; o teste injeta _notaData já parseada.

🧪 /tmp/test_v1049.js · confiança do auditor: alta
Baixa FIS-20

Isenção de R$ 35 mil aplicada sem distinguir exchange nacional de exterior/autocustódia (Lei 14.754/2023: 15% sem isenção desde 2024)

Corrigido
📍 cdtApurar L21980-21985; infobar L828

O problema. Confirmado (set/2026) que a isenção de R$ 35 mil e o 15% continuam valendo para cripto em exchange nacional (MP 1.303/2025 caducou em out/2025; nenhuma lei posterior a alterou). Porém, criptoativos classificados como aplicação financeira no exterior (exchange estrangeira/carteira própria) seguem a Lei 14.754/2023 desde 01/01/2024: 15% na declaração anual, sem a isenção de R$ 35 mil (Cosit 130/2026 reafirmou a inaplicabilidade do art. 22 da Lei 9.250 a aplicações no exterior). O app não pergunta onde a cripto está custodiada.

Evidência numérica. Venda de 0,3 BTC por R$ 30.000 com lucro R$ 10.000 em exchange estrangeira: app 'ISENTO (≤ R$ 35 mil)', IR 0. Correto (Lei 14.754/IN 2.180): 15% × 10.000 = 1.500 na DAA.

Base legal / matemática. Lei 14.754/2023 arts. 2º-3º — https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2023/lei/l14754.htm ; IN RFB 2.180/2024; P&R IRPF 2024 Q627 ('Atenção') — https://www.gov.br/receitafederal/pt-br/centrais-de-conteudo/publicacoes/perguntas-e-respostas/dirpf/pr-irpf-2024.pdf ; status da MP 1.303 (caducou) — https://www.mercadobitcoin.com.br/blog/criptos/entenda-o-fim-da-mp-1303/

O que foi feito. Select #cdt-custodia (Brasil / exterior-autocustódia). cdtApurar: exterior → isento=false, imposto = 15% do ganho, texto 'apurado na declaração anual (sem DARF 4600)'; custódia gravada na op e respeitada em cdtRecalcMes/cdtRender (linha separada 'IR exterior/autocustódia'). Infobar e nota mantêm que a isenção considera o conjunto das vendas (Q627) mas só vale para exchange nacional.

🧪 /tmp/test_v1047.js (0,5 BTC vendido por 10.000 no exterior, lucro 4.000 → 600) · confiança do auditor: media
Baixa FIS-21

Preço médio sem custos, 'Transferir para Carteira' soma uma VENDA como compra, e não há tratamento de desdobramento/bonificação

Parcial
📍 transferToCarteira L3922-3956; portAdd L13682-13707 (não consolida ticker repetido); relContadorHTML seção 1 L15946-15953

O problema. O custo de aquisição legal inclui corretagem e emolumentos e é a média ponderada por ticker (art. 58). A carteira usa só o preço; portAdd cria linhas duplicadas do mesmo ticker (o relatório imprime dois 'preços médios' para a Bens e Direitos); transferToCarteira adiciona qtd×entrada mesmo quando o.tipo==='Venda'; bonificação (custo = valor capitalizado, art. 58 §1º) e desdobramento (mesmo custo total, mais cotas) não existem — o usuário só pode editar preço/qtd na mão, perdendo o custo.

Evidência numérica. Compra 100 PETR4 @30 com corretagem 10 + emolumentos 0,90: custo legal 3.010,90 (PM 30,109); app PM 30,00. Op do Histórico tipo 'Venda' 100 PETR4 → 📊 'Transferir' → carteira passa de 200 para 300 ações (deveria ir a 100). Desdobramento 1:2 → PM 15,00 esperado; app exige edição manual.

Base legal / matemática. IN RFB 1.585/2015 art. 58 caput e §1º; art. 56 §§3º-4º — http://normas.receita.fazenda.gov.br/sijut2consulta/link.action?idAto=67494 ; P&R IRPF 2024 Q678

O que foi feito. Reproduzido (transferir venda: 200 → 300). transferToCarteira: tipo 'Venda' baixa a quantidade mantendo o PM (bloqueia se exceder a posição; remove a linha ao zerar). portAdd: campo 'Custos (R$)' somado ao custo e consolidação por ticker pela média ponderada (mesma moeda). exportCSV robusto a irrf/darf em texto e com as colunas novas.

Observação. Desdobramento/grupamento e bonificação NÃO implementados: o corretor da carteira (CV-12, saveEditPortfolio) propõe as mesmas ações — deixei para ele evitar conflito de merge. CV-02 também toca transferToCarteira/salvarOp: conferir no merge (minhas mudanças ali são pequenas e localizadas).

🧪 /tmp/test_v1049.js (100@30 + 10,90 → PM 30,109; consolidação 200 × 35,0545; venda 50 → 150) · confiança do auditor: alta

IRPF anual, carnê-leão, regime tributário, previdência/INSS, inventário, ação judicial

22 achados · 5 alta · 10 média · 7 baixa
Alta FIS-01

Redutor anual da Lei 15.270 usa 12× a fórmula mensal em vez da tabela anual do art. 11-A (8.429,73 − 0,095575×R)

Corrigido
📍 irpfRedutor L29844-29847 (constantes IRPF_RED_A/IRPF_RED_COEF L29788-29789; usado em irpfCalcular L29876-29883)

O problema. O código calcula a redução anual como 11.743,44 − 0,133145 × rendimento (978,62×12 e o mesmo coeficiente mensal). A Lei 15.270/2025 criou para o ajuste anual uma tabela PRÓPRIA (art. 11-A da Lei 9.250): até R$ 60.000 → redução = imposto (máx. 2.694,15); de 60.000,01 a 88.200 → 8.429,73 − 0,095575 × rendimentos tributáveis. As duas retas só coincidem em 88.200; entre 60 mil e 88,2 mil o app concede redução maior que a legal e mostra imposto a menor (ou restituição a maior).

Evidência numérica. Rendimento anual 70.000, sem deduções, simplificada: base 56.000 → imposto tabela 4.495,24. App: redutor 11.743,44 − 0,133145×70.000 = 2.423,29 → devido 2.071,95. Lei: 8.429,73 − 0,095575×70.000 = 1.739,48 → devido 2.755,76 (app subestima R$ 683,81). Rend. 62.000: app devido 0,00 · lei 550,04. Rend. 80.000: app 5.603,40 · lei 5.911,51. Rend. 60.000,01 (lei): 8.429,73 − 5.734,50 = 2.695,23 ≥ imposto 2.694,15 → zero (contínuo), confirmando que a tabela anual não é ×12 da mensal.

Base legal / matemática. Lei 15.270/2025, art. 11-A da Lei 9.250/1995 (tabela de redução do ajuste anual) — https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2025/lei/L15270.htm ; RFB, tabelas 2026 (redução anual até 2.694,15 / 60.000–88.200) — https://www.gov.br/receitafederal/pt-br/assuntos/meu-imposto-de-renda/tabelas/2026

O que foi feito. Constantes IRPF_RED_A=8429.73 / IRPF_RED_COEF=0.095575 (+IRPF_RED_ISENTO=60000, IRPF_RED_FIM=88200) e irpfRedutor reescrito com o degrau do art. 11-A (≤60 mil → redução = imposto; 60–88,2 mil → reta; >88,2 mil → 0), limitado ao imposto. Comentário 'v7.27 sem curto-circuito' removido. Nova função irpfImpostoDevido(rend, ded) (completa × simplificada com redutor) reaproveitada pelo PGBL. tabelasCarregar passa a IGNORAR as chaves antigas irpfRedutorA/irpfRedutorCoef (o tabelas.json publicado ainda traz 11.743,44/0,133145 e sobrescreveria a correção) e lê irpfRedAnualA/irpfRedAnualCoef/irpfRedIsento/irpfRedFim.

Observação. Reproduzido em Node: irpfRedutor(70000,4495.24)=2423.29 (lei 1739.48). Valores conferidos no texto da Lei 15.270/2025 (art. 11-A). DECISÃO DO DONO pendente: publicar no site /tabelas/tabelas.json as chaves novas (irpfSimpCap2026:17640, irpfRedAnualA:8429.73, irpfRedAnualCoef:0.095575, irpfRedIsento:60000, irpfRedFim:88200, irpfTabelaAnual:[...]) e remover as antigas.

🧪 /tmp/test_v1050.js, /tmp/test_v1055.js · confiança do auditor: alta
Alta FIS-02

Teto do desconto simplificado anual desatualizado: 16.754,34 em vez de 17.640,00 (ano-calendário 2026)

Corrigido
📍 IRPF_SIMP_CAP L29786; irpfCalcular L29878; ficha técnica L12261

O problema. A Lei 15.270/2025 alterou o art. 10 da Lei 9.250 (inciso X) fixando o desconto simplificado em R$ 17.640,00 a partir do ano-calendário 2026. O app mantém 16.754,34 e a ficha técnica afirma que o valor 'foi mantido para 2026 (sem reajuste) ✓'. Para rendimentos acima de 83.771,70 (onde o teto é atingido) a base é 885,66 maior que a legal e o imposto sai a maior.

Evidência numérica. Rendimento 100.000, simplificada: app base 83.245,66 → imposto 11.987,80. Correto: base 82.360,00 → 11.744,24. Diferença +243,56 (= 885,66 × 27,5%). Rend. 150.000: app 25.737,80 · correto 25.494,24.

Base legal / matemática. Lei 15.270/2025 (nova redação do art. 10, X, da Lei 9.250/1995: 'R$ 17.640,00 a partir do ano-calendário de 2026') — https://www2.camara.leg.br/legin/fed/lei/2025/lei-15270-26-novembro-2025-798354-publicacaooriginal-177117-pl.html ; RFB tabelas 2026 (desconto simplificado anual 17.640,00) — https://www.gov.br/receitafederal/pt-br/assuntos/meu-imposto-de-renda/tabelas/2026

O que foi feito. IRPF_SIMP_CAP=17640.00 (art. 10, X, Lei 9.250 — AC 2026). Chave remota antiga irpfSimpCap ignorada; nova irpfSimpCap2026. Help do IRPF (L11954) e laudo atualizados.

Observação. Reproduzido (constante 16.754,34). O valor 11.744,34 difere do 11.744,24 do auditor porque agora a tabela anual oficial é usada (FIS-21).

🧪 /tmp/test_v1050.js (100 mil → deduções 17.640,00, base 82.360,00, imposto 11.744,34) · confiança do auditor: alta
Média FIS-03

Card 'Completa' ignora o inciso 'até 60.000 → redução igual ao imposto' e mostra imposto devido para renda isenta

Corrigido
📍 irpfRedutor L29844-29847; irpfCalcular L29876-29879 e L29936

O problema. Sem o degrau legal (rend ≤ 60.000 → redução = imposto), a fórmula linear dá redução menor que o imposto de quem tem poucas deduções na Completa. O card Completa exibe imposto devido >0 para renda de até R$ 60 mil ao mesmo tempo em que o rodapé diz 'isento pela Lei 15.270'. O resultado final escapa por causa do min(completa, simplificada), mas o card e a alíquota 'marginal' mostrados são falsos.

Evidência numérica. Rend. 60.000/ano, sem deduções: imposto tabela 5.595,24; app redutor = min(5.595,24; 11.743,44 − 7.988,70 = 3.754,74) → card Completa 'IMPOSTO DEVIDO R$ 1.840,50'. Lei (art. 11-A, 1ª faixa): redução = imposto → 0,00.

Base legal / matemática. Lei 15.270/2025, art. 11-A Lei 9.250, 1ª linha da tabela ('até R$ 2.694,15, de modo que o imposto devido seja zero') — https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2025/lei/L15270.htm

O que foi feito. Mesma correção da FIS-01 (branch rend ≤ 60.000 → redução = imposto). Rodapé 'isento' agora cita o art. 11-A.

Observação. Reproduzido: irpfRedutor(60000,5595.24)=3754.74 no original.

🧪 /tmp/test_v1050.js (sal 5.000: ambos os cards 0,00 e redutor da completa 5.595,34) · confiança do auditor: alta
Alta FIS-04

Tipo 'Pensão alimentícia' é tributado no carnê-leão — rendimento declarado não tributável pelo STF (ADI 5422) e pela RFB

Corrigido
📍 select cl-tipo L1625-1631 (option value='pensao'); clRender L29486-29492 (soma todos os tipos em rend); clImposto L29383

O problema. O app oferece 'Pensão alimentícia' como rendimento sujeito a carnê-leão e soma esse valor à base mensal como qualquer outro. Desde a ADI 5422 (STF, jun/2022) a pensão alimentícia recebida por acordo/decisão judicial ou escritura não sofre IRPF; a RFB adequou a IN 1500/2014 (IN RFB 2141/2023) e o rendimento é informado como isento/não tributável na DIRPF, sem carnê-leão.

Evidência numérica. Lançamento tipo 'pensao' de R$ 6.000 em março, sem outras rendas: app → imposto 574,29 − redutor 179,75 = DARF R$ 394,54. Correto: R$ 0,00 (não incidência). Um alimentando que segue o app paga imposto indevido todo mês.

Base legal / matemática. STF ADI 5422 (não incidência de IRPF sobre pensão alimentícia); RFB, 'Receita Federal esclarece a não incidência do IR sobre pensão alimentícia' — https://www.gov.br/receitafederal/pt-br/assuntos/noticias/2022/outubro/receita-federal-esclarece-a-nao-incidencia-do-imposto-de-renda-sobre-pensao-alimenticia ; perguntas frequentes RFB — https://www.gov.br/receitafederal/pt-br/acesso-a-informacao/perguntas-frequentes/imposto-de-renda/dirpf/pensao-alimenticia/o-que-mudou-para-quem

O que foi feito. Nova apuração canônica clCalcAno/clCalcMes: lançamentos tipo 'pensao' ficam fora da base (rendIsento), mostrados com selo 'isento — ADI 5422' e card 'PENSÃO ALIMENTÍCIA (ISENTA)'. Option do select renomeada 'Pensão alimentícia (isenta — só registro)' com tooltip. Dashboard (L16273/16311), Agenda (L21358) e DRE (L29228) passaram a usar clCalcMes (uma linha cada, com fallback).

Observação. Reproduzido: pensão 6.000 → DARF 394,54 no original. Lançamentos antigos com tipo 'pensao' não são apagados — só deixam de ser tributados.

🧪 /tmp/test_v1051.js · confiança do auditor: alta
Média FIS-05

Vencimento exibido é o último dia do mês, não o último dia ÚTIL — em 4 meses de 2026 o app mostra data posterior ao prazo legal

Corrigido
📍 clRender L29496-29497 (vencStr=new Date(anoVis, m+1, 0)); info-bar L1605

O problema. O código comenta 'aproximado: último dia' e imprime o último dia-calendário do mês seguinte. A lei manda pagar até o último dia útil. Quando o dia 31/28/30 cai em fim de semana ou feriado, quem paga na data mostrada paga em atraso: multa de mora 0,33% por dia (até 20%) + juros Selic + 1%.

Evidência numérica. Rendimentos de dez/2025 → app 'vence 31/01/2026' (sábado); prazo real 30/01/2026. Idem jan/2026 → 28/02 (sábado, real 27/02), abr/2026 → 31/05 (domingo, real 29/05), set/2026 → 31/10 (sábado, real 30/10). DARF de R$ 1.000 pago na segunda 02/02/2026: multa 3 dias × 0,33% = 9,90 + juros 1% = 10,00 → R$ 19,90 de acréscimo evitável.

Base legal / matemática. Lei 8.383/1991, art. 6º, II (carnê-leão: até o último dia útil do mês subsequente) — https://www.planalto.gov.br/ccivil_03/leis/l8383.htm ; Lei 9.430/1996, art. 61 (multa 0,33%/dia até 20% e juros Selic) — https://www.planalto.gov.br/ccivil_03/leis/l9430.htm

O que foi feito. Novas funções clPascoa (Meeus/Butcher), clFeriadoNacional (fixos + Carnaval 2ª/3ª, Sexta Santa, Corpus Christi) e clUltimoDiaUtil(ano, mesIdx). clRender/clCalcAno usam o último dia útil do mês seguinte; a Agenda marca o DARF do carnê-leão nesse dia (L21358: dia = calc.venc.getDate()).

Observação. Nome próprio para não colidir com rvUltimoDiaUtil do corretor fiscal. Feriados estaduais/municipais não considerados (dito na tela).

🧪 /tmp/test_v1051.js (30/01, 27/02, 31/03, 30/04, 29/05, 30/10/2026; Páscoa 05/04/2026; feriados móveis) · confiança do auditor: alta
Baixa FIS-06

DARF pendente vencido é mostrado pelo valor nominal — sem multa de mora nem juros Selic

Corrigido
📍 clRender L29533-29551 (PENDENTE = totalDarf − totalPago); dica L9377

O problema. O app sabe o mês de competência e se o DARF foi pago, mas 'PENDENTE' soma o valor original mesmo meses depois do vencimento. Quem paga pelo valor mostrado paga a menos e gera saldo devedor na RFB. O próprio app cita a regra (dica L9377: '0,33% ao dia + Selic') mas não a aplica.

Evidência numérica. DARF de jan/2026 de R$ 1.000 (venc. 27/02/2026) consultado em 10/09/2026: app 'PENDENTE R$ 1.000,00'. Correto: multa 20% (teto, >61 dias) = 200,00 + juros Selic acumulada mar–ago/2026 + 1% (set) — com Selic ~1,1%/mês ≈ 7,6% → ~R$ 76 → ≈ R$ 1.276.

Base legal / matemática. Lei 9.430/1996, art. 61 §§1º-3º — https://www.planalto.gov.br/ccivil_03/leis/l9430.htm ; SICALC (RFB) para o valor oficial

O que foi feito. clAcrescimos(darf, venc, pgto, selicMes): multa 0,33%/dia (máx. 20%) + juros = Selic média mensal × meses cheios entre vencimento e pagamento + 1% no mês do pagamento (0 se pago no próprio mês do vencimento). clRender mostra 'Vencido há N dias: DARF atualizado ≈ …' com link ao SICALC e o card PENDENTE traz o total atualizado. Campo cfg.selic ('Selic média mensal p/ atraso', padrão 1,10) em clSaveCfg.

Observação. Não há função/série 4390 da Selic no app → campo manual, como instruído.

🧪 /tmp/test_v1051.js (1.000 venc 27/02 pago 10/03 → 1.046,30; pago 10/09 → 1.276,00) · confiança do auditor: alta
Média FIS-07

Livro-caixa é deduzido de aluguéis, exterior e de qualquer tipo, e sem o limite da receita mensal da atividade

Corrigido
📍 clRender L29477 (dedFixa = dep×189,59 + inss + pensao + livro aplicada a todo mês) e L29489-29490

O problema. As despesas de livro-caixa só podem ser deduzidas dos rendimentos do trabalho NÃO assalariado (autônomo), limitadas à receita mensal dessa atividade (excesso vai para os meses seguintes até dezembro). O app aplica o valor fixo do livro-caixa a todos os tipos de lançamento (aluguel, exterior) e sem o limite. Aluguel tem deduções próprias (IPTU, condomínio, taxa de administração) que o app não oferece.

Evidência numérica. Único lançamento: aluguel de PF R$ 8.000 em maio, livro-caixa 2.000/mês: app base 6.000 → imposto 6.000×27,5% − 908,73 = 741,27 (sem redutor). Correto (livro-caixa não aplicável; desconto simplificado 607,20): base 7.392,80 → 1.124,29. Subestima R$ 383,02/mês.

Base legal / matemática. Lei 8.134/1990, art. 6º (livro-caixa: trabalho não assalariado; §§ — limite à receita mensal, excesso até dezembro) — https://www.planalto.gov.br/ccivil_03/leis/l8134.htm ; RIR/2018 (Decreto 9.580) art. 42 (deduções do aluguel: impostos, condomínio, administração) — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/decreto/d9580.htm

O que foi feito. clCalcAno: livro-caixa só abate tipo 'pf' (trabalho não assalariado), limitado à receita dessa atividade no mês; excedente transportado para os meses seguintes do ano (zera em janeiro); novo campo 'Despesas do imóvel alugado' (cfg.imovel) abatendo só aluguéis; dependentes/INSS/pensão paga abatem o total. Tela mostra 'livro-caixa excedente → próximo mês'. Tooltips e help atualizados.

Observação. O excedente só acumula em meses que têm lançamento (meses vazios não aparecem na apuração) — conservador.

🧪 /tmp/test_v1051.js (aluguel 8.000 + livro 2.000 → base 8.000, DARF 1.124,29; pf 1.500 → 1.500 no mês e 500 transportados) · confiança do auditor: alta
Média FIS-08

Tooltip manda somar o 13º salário em 'Outras rendas anuais' — o 13º é tributado exclusivamente na fonte e não entra no ajuste anual

Corrigido
📍 input ir-outras L1559 (title='13º, aluguéis recebidos, carnê-leão etc.'); irpfCalcular L29860

O problema. O 13º tem tributação exclusiva/definitiva na fonte (com o redutor do art. 3º-A §3º aplicado separadamente pela fonte) e não compõe os rendimentos tributáveis sujeitos ao ajuste anual. Seguindo a orientação da tela, o usuário infla a renda anual, perde a faixa de isenção de 60 mil e vê imposto a pagar inexistente.

Evidência numérica. Salário 5.000/mês + 13º 5.000 lançado em 'outras': rendAnual 65.000. App (simplificada): imposto tabela 3.594,12 − redutor 3.089,01 = 505,11 'A PAGAR'. Correto: rendimentos do ajuste = 60.000 → imposto 0; 13º tributado na fonte pela tabela mensal com redução (5.000 − 607,20 → 312,89 − 312,89 = 0).

Base legal / matemática. Lei 8.134/1990, art. 16 (13º: tributação exclusiva na fonte); Lei 15.270/2025, art. 3º-A §3º Lei 9.250 (redução também no 13º) — https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2025/lei/L15270.htm ; RIR/2018 art. 700 — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/decreto/d9580.htm

O que foi feito. Tooltip de 'Outras rendas anuais' manda NÃO incluir o 13º; novo campo 'ir-13' com IR exclusivo na fonte = clImposto(13º − dependentes, 13º).darf, exibido à parte e fora da renda do ajuste (abate do IRPFM).

Observação. INSS sobre o 13º não é informado pelo usuário → IR do 13º levemente conservador.

🧪 /tmp/test_v1050.js (sal 5.000 + 13º 5.000 → renda do ajuste 60.000, IR do 13º 0,00; 13º 10.000 → 1.674,29) · confiança do auditor: alta
Alta FIS-09

'Economia de IR' do PGBL ignora o redutor da Lei 15.270 e o desconto simplificado — promete economia a quem já paga zero

Corrigido
📍 prevRender L25397-25404 (economiaIR = irpfImpostoTabela(rend) − irpfImpostoTabela(rend − dedutível))

O problema. A economia é calculada pela diferença de imposto de tabela bruta, sem o redutor anual (rend ≤ 60 mil → imposto zero; até 88,2 mil parcial) e sem comparar com a simplificada (se o contribuinte fica na simplificada, o PGBL não deduz nada). Quem ganha até R$ 5.000/mês é induzido a contratar PGBL 'para economizar IR' que não existe; PGBL tributa o TOTAL no resgate, então o erro custa dinheiro.

Evidência numérica. Renda bruta 5.000/mês (60.000/ano), aporte PGBL 600/mês (7.200 = 12%): app 'economia de IR ≈ R$ 1.821,12/ano' (5.595,24 − 3.774,12). Correto: imposto com e sem PGBL = 0 (Lei 15.270, rend ≤ 60 mil) → economia 0,00. Renda 7.000/mês (84.000), sem outras deduções, PGBL 840/mês (10.080): app 12.195,24 − 9.423,24 = 2.772,00. Correto: com redutor (8.429,73 − 0,095575×84.000 = 401,43) completa sem PGBL 11.793,81, com PGBL 9.021,81, mas a simplificada (dedução 16.800) dá 7.173,81 nos dois casos → contribuinte fica na simplificada e o PGBL não deduz nada → economia 0,00.

Base legal / matemática. Lei 15.270/2025 art. 11-A Lei 9.250 — https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2025/lei/L15270.htm ; Lei 9.250/1995 art. 8º II 'e' e art. 10 (PGBL só na completa; desconto simplificado substitui todas as deduções) — https://www.planalto.gov.br/ccivil_03/leis/l9250.htm

O que foi feito. prevRender (bloco da economia): economiaIR = irpfImpostoDevido(rend, dedOutras).devido − irpfImpostoDevido(rend, dedOutras+dedutível).devido (completa × simplificada, tabela anual + redutor art. 11-A); mostra o motivo ('você já é isento', 'simplificada continua melhor') e o comparativo com/sem PGBL; campo opcional 'outras deduções da completa' (cfg.dedoutras).

Observação. O caso de teste do auditor (renda 180 mil, PGBL 21.600 → 5.940) estava ERRADO: a simplificada (17.640) já cobre a maior parte; a economia real é (21.600 − 17.640) × 27,5% = 1.089,00 — foi esse o valor testado.

🧪 /tmp/test_v1052.js · confiança do auditor: alta
Alta FIS-10

Tabela regressiva aplicada com um único prazo (anos até a idade-alvo) a todos os aportes — subestima o IR de PGBL em ~50%

Decisão do dono
📍 prevCalcPlano L25281-25302 (aliq = prevAliqRegressiva(anos); ir = baseIR×aliq); prevAliqRegressiva L25304-25311

O problema. No regime regressivo cada aporte tem seu próprio prazo de acumulação (PEPS): os aportes dos últimos 2 anos pagam 35%, de 2–4 anos 30%, etc. O app aplica ao saldo INTEIRO a alíquota do aporte mais antigo (10% se o horizonte ≥10 anos), inclusive aos aportes feitos na véspera do resgate. Como no PGBL o IR incide sobre o total, o valor líquido projetado fica muito acima do real.

Evidência numérica. PGBL, saldo 0, aporte 1.000/mês, 8% − 1% adm, 15 anos: FV 301.548,26. App: IR 10% = 30.154,83 → líquido 271.393. Correto (aporte anual k tributado pela alíquota do seu prazo 15−k): IR = 57.383,58 → líquido 244.165 (renda 4% cai de 904,64 para 813,88/mês). VGBL mesmo cenário: app 12.154,83 · correto 18.383,58.

Base legal / matemática. Lei 11.053/2004, art. 1º (alíquotas decrescentes em função do prazo de acumulação de cada aporte) — https://www.planalto.gov.br/ccivil_03/_ato2004-2006/2004/lei/l11.053.htm ; IN SRF 588/2005 (prazo de acumulação contado aporte a aporte, PEPS)

O que foi feito. Nada — prevCalcPlano/prevAliqRegressiva pertencem ao corretor de simuladores (instrução explícita de não mexer).

Observação. Reproduzível (aliquota única para todos os aportes). Deixado para o outro corretor; minha FIS-11 usa apenas r.fv/r.rendimento de prevCalcPlano, que não mudam com a correção por coortes.

🧪 — · confiança do auditor: alta
Média FIS-11

Regime progressivo tratado como 15% definitivo — os 15% na fonte são antecipação; o imposto final segue a tabela anual (até 27,5%)

Parcial
📍 prevCalcPlano L25295 (aliq = trib==='progressivo' ? 0.15 : ...); nota L25370 'IR 15% na fonte'; select pv-trib L25447

O problema. No regime progressivo o resgate sofre 15% na fonte como ANTECIPAÇÃO e o valor entra na declaração de ajuste, onde é tributado pela tabela progressiva (27,5% marginal para resgates relevantes). Um resgate único de PGBL grande paga muito mais de 15%. Além disso, pela Lei 14.803/2024 a opção pelo regime só é feita no primeiro resgate/benefício, não na contratação — a tela exige escolha ao cadastrar o plano sem avisar.

Evidência numérica. PGBL progressivo, FV 301.548 resgatado de uma vez: app IR 45.232 (15%). Ajuste anual (sem outras rendas, tabela anual 2026): 301.548×27,5% − 10.904,66 = 72.021 → IR final ~72 mil (26.789 a mais que o app). Em renda mensal de 800/mês o imposto seria 0 — a alíquota 15% fixa erra nos dois sentidos.

Base legal / matemática. Lei 11.053/2004, art. 3º (15% na fonte como antecipação do ajuste) — https://www.planalto.gov.br/ccivil_03/_ato2004-2006/2004/lei/l11.053.htm ; Lei 14.803/2024 (opção até o primeiro resgate/benefício) — https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2024/lei/L14803.htm

O que foi feito. Sem tocar em prevCalcPlano: prevRender ganhou um bloco 'Regime progressivo' por plano progressivo com (a) resgate único → IR pela tabela anual (irpfImpostoDevido, base = total no PGBL / rendimento no VGBL) ao lado dos 15% na fonte; (b) renda de 4% → IR mensal pela tabela + redutor 2026 (clImposto). Rótulo do select trocado para 'Regime (escolhido no 1º resgate)' com tooltip da Lei 14.803/2024.

Observação. Parcial porque a projeção principal (fvLiquido/renda) continua com 15% fixo dentro de prevCalcPlano (fora do meu escopo). O auditor esperava 72.021 (completa sem deduções); com o desconto simplificado o correto para quem não tem outras rendas é 67.170,11.

🧪 /tmp/test_v1052.js (FV 301.548,26 → IR anual 67.170,11 na simplificada; renda 1.005,16/mês → 0) · confiança do auditor: alta
Média FIS-12

Aposentadoria do INSS exige 20 anos de contribuição de todo homem e ignora regras de transição — quem já era filiado em 2019 precisa de 15

Corrigido
📍 prevCalcINSS L25245-25261 (contribMin = sexo==='f'?15:20; coef 60+2×(contrib−20))

O problema. A regra de 65 anos + 20 anos (homem) do art. 19 da EC 103 vale só para quem se filiou ao RGPS APÓS 13/11/2019. Para os já filiados (a maioria dos usuários adultos) vale a transição do art. 18: 65/62 anos + 15 anos de contribuição para ambos os sexos. Também não há as transições por pontos (art. 15), idade progressiva (16), pedágio 50% (17) e 100% (20), que costumam dar aposentadoria antes e com coeficiente maior.

Evidência numérica. Homem, 65 anos, 17 anos de contribuição, filiado antes de 13/11/2019, média 4.000: app 'faltam 3 anos — aposenta aos 68', coeficiente 60% → R$ 2.400 só aos 68. Correto: já preenche a transição do art. 18 (65 anos + 15 de contribuição) → aposenta hoje com 60% = R$ 2.400. Mulher, 58 anos, 34 anos de contribuição: app manda esperar 62 anos; pela regra dos pontos (art. 15: 92 pontos em 2026, 30 anos mínimos) já pode, com coeficiente 60% + 2×19 = 98%.

Base legal / matemática. EC 103/2019, arts. 15, 16, 17, 18, 19, 20 e 26 §§2º e 5º — https://www.planalto.gov.br/ccivil_03/constituicao/emendas/emc/emc103.htm

O que foi feito. prevCalcINSS reescrito: regra geral (art. 19: 65/62 + 20/15) para todos e, com o checkbox 'Já contribuía antes de 13/11/2019' (cfg.antes2019), as transições por idade (art. 18: 65/62 + 15 p/ ambos), pontos (art. 15: 2026 = 103/93, +1/ano até 105/100, com 35/30 anos), idade progressiva (art. 16: 2026 = 64,5/59,5, +6 meses/ano) e pedágio 100% (art. 20: 60/57 + 35/30 + pedágio, 100% da média). Escolhe a que aposenta antes (empate → maior benefício); devolve regra/extra/opcoes além do contrato antigo. Tela mostra a regra e as alternativas; rodapé atualizado.

Observação. Os números do auditor (92/102 pontos e 59/64 anos) eram os de 2025; em 2026 são 93/103 e 59,5/64,5 (EC 103 arts. 15 §1º e 16 §1º). Pedágio 50% (fator previdenciário), professores e especial não simulados (dito na tela). Tempo em 13/11/2019 é estimado supondo contribuição contínua.

🧪 /tmp/test_v1052.js · confiança do auditor: alta
Média FIS-13

Lucro Real: base do IRPJ/CSLL não deduz ISS, PIS/COFINS e INSS patronal — regime aparece mais caro do que é

Corrigido
📍 rtSimular L31234-31240 (lucro = fat − desp − folha; irpjLR/csllLR sobre esse lucro)

O problema. Tributos incidentes sobre a receita (ISS, ICMS, PIS/COFINS) e os encargos patronais são despesas dedutíveis na apuração do Lucro Real. O app calcula IRPJ/CSLL sobre receita − despesas − folha sem subtraí-los, inflando o total do Lucro Real e podendo apontar como 'melhor' um regime que não é.

Evidência numérica. Valores padrão da tela (serviços 30.000, folha 5.000, desp 8.000, ISS 3%, INSS 26,8%): app lucro 17.000 → IRPJ 2.550 + CSLL 1.530 + PIS/COFINS 2.035 + ISS 900 + INSS 1.340 = 8.355/mês. Correto: lucro tributável 17.000 − 900 − 2.035 − 1.340 = 12.725 → IRPJ 1.908,75 + CSLL 1.145,25 → total 7.329/mês (app superestima 1.026/mês = 12.312/ano).

Base legal / matemática. Lei 8.981/1995, art. 41 (tributos dedutíveis na determinação do lucro real pelo regime de competência) — https://www.planalto.gov.br/ccivil_03/leis/l8981.htm ; RIR/2018 art. 352 — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/decreto/d9580.htm

O que foi feito. rtSimular: lucroReal = fat − desp − folha − ISS − ICMS − PIS/COFINS − INSS patronal (cálculo dos tributos movido para antes); linha 'Lucro real (rec − desp − folha − tributos dedutíveis)'.

Observação. Reproduzido em Node (8.355 no original).

🧪 /tmp/test_v1053.js (padrão → 12.725,00 e total 7.329,00) · confiança do auditor: alta
Média FIS-14

Simples Nacional sem o Anexo IV (construção, vigilância, limpeza, advocacia) — CPP fora da guia

Corrigido
📍 RT_ANEXOS L31142-31147 (só I, II, III, V); rtSimular L31191-31196

O problema. Serviços do §5º-C do art. 18 da LC 123 (construção civil, vigilância, limpeza/conservação, advocacia) vão obrigatoriamente ao Anexo IV, cujas alíquotas são menores mas NÃO incluem a CPP (INSS patronal 20% + RAT pago por fora). O app enquadra qualquer serviço em III ou V pelo fator r e diz 'INSS patronal já dentro da guia', errando nos dois sentidos para essas atividades.

Evidência numérica. Empresa de limpeza, RBT12 360.000 (30.000/mês), folha 12.000/mês (fator r 40%): app → Anexo III faixa 2: (360.000×11,2% − 9.360)/360.000 = 8,6% → DAS 2.580, 'INSS patronal já na guia'. Correto: Anexo IV faixa 2: (360.000×9% − 8.100)/360.000 = 6,75% → DAS 2.025 + CPP 12.000×~22% (20% + RAT) = 2.640 → 4.665/mês.

Base legal / matemática. LC 123/2006, art. 18 §5º-C e Anexo IV (4,5%; 9%−8.100; 10,2%−12.420; 14%−39.780; 22%−183.780; 33%−828.000) — https://www.planalto.gov.br/ccivil_03/leis/lcp/lcp123.htm ; Resolução CGSN 140/2018 art. 25 — https://normas.receita.fazenda.gov.br

O que foi feito. RT_ANEXOS.IV adicionado (tabela oficial LC 123); checkbox 'rt-anexo4' força anexoServ='IV' e soma CPP = folha × (20% + RAT 2%) por fora, com linha e aviso próprios. window._rtUlt ganha anexo4.

Observação. RAT médio de 2% (RT_ANEXO4_RAT) — o real varia de 1% a 3% pela atividade.

🧪 /tmp/test_v1053.js (rtSimplesAliq('IV',360000) → 9% / 6,75% / faixa 2; limpeza → 4.665,00) · confiança do auditor: alta
Baixa FIS-15

Presumido/Real não modelam o pró-labore obrigatório (INSS 11% do sócio até o teto, patronal 20% sem RAT/terceiros, IRRF) nem o IRRF/INSS na comparação com o MEI

Corrigido
📍 rtSimular L31215-31248; input rt-folha L3014; rt-inss L3018 (26,8% sobre toda a 'folha')

O problema. No Presumido e no Real o sócio precisa retirar pró-labore com INSS de 11% retido (teto 8.475,55 → máx. 932,31) e a empresa recolhe 20% patronal sobre ele (sem RAT e terceiros, que só incidem sobre empregados). O app aplica 26,8% sobre 'folha + pró-labore' indistintamente e não mostra o custo do 11% + IRRF do sócio, que pesa na comparação com o MEI (5% do mínimo) e com o Simples (pró-labore só com o 11%).

Evidência numérica. Pró-labore 5.000 (única 'folha'): app INSS patronal 5.000×26,8% = 1.340. Correto: 5.000×20% = 1.000 (patronal) + 550 retidos do sócio (11%) + IRRF do sócio: base 5.000 − 550 − 607,20? (simplificado 607,20 > 550) = 4.392,80 → 312,89 − redutor 312,89 = 0. Custo empresa 1.000 (não 1.340).

Base legal / matemática. Lei 8.212/1991, art. 21 e art. 22, I e III (20% sobre pró-labore de contribuinte individual; RAT só sobre empregados) — https://www.planalto.gov.br/ccivil_03/leis/l8212cons.htm ; Portaria MPS/MF 13/2026 (teto 8.475,55) — https://www.legisweb.com.br/legislacao/?id=489284

O que foi feito. Campo opcional 'Pró-labore dos sócios' (rt-prolabore); 'rt-folha' virou 'Salários de empregados'. Presumido/Real: INSS patronal = salários × % informado + pró-labore × 20%; linhas informativas 'INSS do sócio 11% (retido)' (até o teto) e 'IRRF do sócio' (clImposto) em Simples/Presumido/Real, sem somar ao total da empresa. Fator R usa salários + pró-labore.

Observação. Padrões da tela inalterados (pró-labore 0).

🧪 /tmp/test_v1053.js (pró-labore 5.000 → patronal 1.000, sócio 550, IRRF 0; 10.000 → 932,31 e 1.584,88) · confiança do auditor: alta
Média FIS-16

ITCMD com alíquota única digitada — EC 132/2023 tornou a progressividade obrigatória e 20+ estados já tributam por faixas

Corrigido
📍 invSimular L31282-31296 (itcmd = base×itcmdPct); tooltip inv-itcmd L3034

O problema. O simulador aplica um percentual fixo ao monte-mor. Após a EC 132/2023 (art. 155 §1º VI CF) a maioria dos estados tem tabela progressiva por faixa (RJ 4–8%, RS 0–6%, SC 1–7%, BA 4/6/8%, DF 4/5/6%, GO/CE/MT/TO 2–8%, PE 2–8% desde 01/01/2026, PR 4% linear, SP 4% e MG 5% ainda fixos). Para heranças médias/grandes em RJ, SC, RS, BA etc. o imposto fixo digitado erra por dezenas de milhares de reais; o app já reconheceu o item como pendente (v10.26).

Evidência numérica. Monte-mor 2.000.000 no RJ: app com 4% → 80.000; tabela RJ (Lei 7.174/2015, faixas em UFIR-RJ ≈ 4% até ~R$ 320 mil, 4,5%, 5%, 6%, 7%, 8% acima de ~R$ 1,6 mi) → ≈ R$ 118 mil (progressivo por parcela). Em SC (1/3/5/7%): 1.000.000 → app 4% = 40.000 vs progressivo por parcela (Lei 13.136/2004) ≈ 50.400. Usuário em SP/MG: sem erro (fixo).

Base legal / matemática. CF art. 155 §1º VI (EC 132/2023) — https://www.planalto.gov.br/ccivil_03/constituicao/emendas/emc/emc132.htm ; SP Lei 10.705/2000 art. 16 (4%) — https://www.al.sp.gov.br/repositorio/legislacao/lei/2000/lei-10705-28.12.2000.html ; mapa 2026 por UF — https://taxup.com.br/solucoes/planejamento-tributario/mapa-itcmd-brasil/

O que foi feito. Tabela ITCMD_UF (referência 2026) com SP/MG/PR/ES fixos e RJ (UFIR-RJ 4,9604), SC, RS (UPF-RS 28,3264), PE (LC 563/2025), GO, CE (UFIRCE 6,29872), DF progressivos por parcela e BA por faixa sobre o quinhão; flags modo/porQuinhao/divida; funções itcmdCalcTabela e itcmdCalc (aplica ao quinhão = herança ÷ herdeiros quando o estado tributa por quinhão). Select 'Estado (ITCMD)' (padrão MG) + campo manual para 'Outro'. Tela mostra alíquota efetiva, base, lei e faixas. relColeta('inv') atualizado.

Observação. Fontes: leis estaduais/LC 563-PE, Sefaz e compilações 2026 (taxup, itcmd.com.br). RISCO p/ revisão: faixas do DF (1,64 mi / 3,29 mi) são valores corrigidos pelo INPC citados por compilação, não conferidos no Ato SUREC; isenções estaduais (ex.: RS até 10.509 UPF, GO 20 mil, DF 176 mil) NÃO aplicadas; forma de cálculo do RJ confirmada por exemplo (1 mi → 4,59%); dívidas: só SP/MG marcados como 'não abate'. Default mudou de 4% manual para MG 5%.

🧪 /tmp/test_v1054.js (SC 1 mi → 65.600; RJ 1 mi → 45.862,86; RJ 2 mi → 111.140,06; BA/PE/GO/RS/CE/DF) · confiança do auditor: media
Média FIS-17

Base do ITCMD deduz dívidas do espólio e inclui a meação do cônjuge — SP veda o abatimento e a meação não é herança

Corrigido
📍 invSimular L31292 (base = bens − divida); tooltip inv-divida L3033; invUsarMeusDados L31274-31281 (patrimônio total do app)

O problema. O app diz que 'dívidas abatem da base de cálculo' e calcula ITCMD, honorários e custas sobre (bens − dívidas). Em SP a lei proíbe abater quaisquer dívidas do espólio ou que onerem o bem (o imposto é sobre o valor venal); MG idem (base = valor venal, sem dedução). Por outro lado, ao usar 'meu patrimônio do app' para um casado em comunhão, metade dos bens é meação do sobrevivente — não é transmitida nem tributada — e o app tributa 100%.

Evidência numérica. SP, bens 1.000.000, dívidas 200.000: app ITCMD 4% × 800.000 = 32.000; correto (art. 12 Lei 10.705): 4% × 1.000.000 = 40.000. Casal em comunhão universal, patrimônio 1.000.000, óbito de um: herança = 500.000 → ITCMD SP 20.000; app 40.000 (dobro) e honorários 6% sobre 1 mi.

Base legal / matemática. SP Lei 10.705/2000, art. 12 ('não serão abatidas quaisquer dívidas que onerem o bem transmitido, nem as do espólio') — https://www.al.sp.gov.br/repositorio/legislacao/lei/2000/lei-10705-28.12.2000.html ; CC art. 1.658/1.667 e 1.784 (meação não integra a herança) — https://www.planalto.gov.br/ccivil_03/leis/2002/l10406compilada.htm

O que foi feito. invSimular: campo 'Meação do cônjuge (%)' (sai antes de tudo — não é herança); dívidas só reduzem a base do ITCMD onde ITCMD_UF[uf].divida=true (SP e MG = false), mas sempre reduzem a herança líquida/honorários/custas/sobra. Tooltip das dívidas corrigido. window._invUlt ganha uf, meacao, heranca, baseITCMD, dividaDeduz.

Observação. invUsarMeusDados continua trazendo o patrimônio total — o usuário casado deve informar a meação.

🧪 /tmp/test_v1054.js (SP 1 mi − 200 mil dívidas → ITCMD 40.000, sobra 704.000; meação 50% → 20.000; DF abate) · confiança do auditor: alta
Baixa FIS-18

Com 'Justiça gratuita' marcada, a sucumbência continua somada em 'Se perder' — a exigibilidade fica suspensa (CPC art. 98 §3º)

Corrigido
📍 ajSimular L30572-30586 (perdaTotal = custas + pericia + pro + sucumbencia, independente de gratuita)

O problema. A gratuidade isenta custas e perícia (o app trata) mas também suspende por 5 anos a exigibilidade dos honorários sucumbenciais, que só podem ser cobrados se a situação econômica mudar. O 'valor esperado' e o 'ponto de equilíbrio' do beneficiário ficam pessimistas demais.

Evidência numérica. Causa 100.000, gratuita, sucumbência 10%, êxito 20%, entrada 3.000, p=70%: app perde 13.000 → EV = 0,7×77.000 − 0,3×13.000 = 50.000; breakeven 14,4%. Com sucumbência suspensa: perda 3.000 → EV 53.000; breakeven 3,75%.

Base legal / matemática. CPC (Lei 13.105/2015), art. 98 §§2º e 3º — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2015/lei/l13105.htm

O que foi feito. ajSimular: sucumbExigivel = gratuita ? 0 : sucumbência; perdaTotal/EV/breakeven usam o exigível; card 'Se perder' e nota explicam a suspensão (CPC art. 98 §3º); rótulo do checkbox e relColeta('aj') atualizados; _ajUlt ganha sucumbExigivel.

🧪 /tmp/test_v1054.js (gratuita → perde 3.000, EV 53.000, breakeven 3,75%) · confiança do auditor: alta
Baixa FIS-19

Escopo 2026 ausente: tributação mínima (IRPFM > 600 mil), IRRF 10% sobre dividendos > 50 mil/mês, ganho de capital, bens e direitos, rendimentos isentos

Corrigido
📍 panel-irpf L1544-1600; irpfCalcular L29849-29936

O problema. A aba se apresenta como 'IRPF 2026 — Lei 15.270/2025' mas só implementa a tabela + redutor. A mesma lei criou, a partir de 01/01/2026, a tributação mínima para quem recebe > R$ 600 mil/ano (alíquota = REND/60.000 − 10, até 10% em 1,2 mi, base incluindo dividendos e isentos) e a retenção de 10% sobre dividendos > R$ 50 mil/mês por PJ. Não há campo para dividendos/isentos, ganho de capital (15–22,5%, isenções de 35 mil/440 mil/180 dias, fator de redução) nem bens e direitos — o usuário de alta renda que confia no app não vê o IRPFM.

Evidência numérica. Usuário com salário 120.000 + dividendos 900.000 (lucros de 2026): app → imposto 22.095 (só tabela). Lei: REND = 1.020.000 → alíquota = 1.020.000/60.000 − 10 = 7% → IRPFM = 71.400 − imposto da tabela 22.095 − IRRF 10% dos dividendos já retido (na parte > 50 mil/mês) → imposto adicional relevante que o app não menciona (além dos ~90 mil retidos na fonte pela PJ).

Base legal / matemática. Lei 15.270/2025 (arts. 6º-A e 16-A/16-B da Lei 9.250) — https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2025/lei/L15270.htm ; ganho de capital: Lei 13.259/2016 art. 1º (Lei 8.981 art. 21) — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2016/lei/l13259.htm ; Lei 11.196/2005 art. 40 — https://www.planalto.gov.br/ccivil_03/_ato2004-2006/2005/lei/l11196.htm

O que foi feito. Campos 'Dividendos recebidos 2026+' e 'Outros isentos/exclusivos p/ IRPFM'; função irpfMinimo(REND, devido, irrfDivid, irrfExclusivo): >600 mil → alíquota = REND/60.000 − 10 (máx. 10% em 1,2 mi), adicional = máx(0, bruto − devido − IRRF dividendos − IR do 13º); bloco 'Tributação mínima' na tela; aviso do IRRF de 10% quando dividendos/12 > 50 mil; o adicional entra no saldo a pagar. Constantes IRPFM_* e IRRF_DIVID_*.

Observação. Estimativas: IRRF de dividendos = 10% do total anual quando a média mensal > 50 mil (a regra é por PJ/mês); redutor do art. 16-B e exclusões do §1º não simulados (avisado na tela). Ganho de capital/bens e direitos não implementados (escopo de produto → dono).

🧪 /tmp/test_v1050.js (120 mil + 900 mil → 7% e 71.400) · confiança do auditor: alta
Baixa FIS-20

Ficha técnica e rodapés afirmam conformidade 2026 que não existe ('equivalência exata ×12 ✓', '16.754,34 mantido ✓', '0 correções', 'faixas CLT de 2025')

Corrigido
📍 L12250-12261 e L12272 (laudo embutido); prevRender L25406 ('Faixas de contribuição CLT de 2025'); comentário L29841-29843

O problema. O laudo embutido no app declara '100% das regras tributárias vigentes' e '0 correções', afirmando expressamente que o redutor anual é ×12 do mensal e que o desconto simplificado ficou em 16.754,34 — ambos falsos (FIS-01/02). O rodapé do INSS diz que as faixas são de 2025 embora a tabela do código já seja a de 2026 (Portaria MPS/MF 13/2026). Para um produto vendido a pessoas físicas, o texto induz confiança indevida.

Evidência numérica. L12260: 'Base anual: 11.743,44 − 0,133145 × rendimento ... Equivalência matemática exata (×12) ✓' vs art. 11-A: 8.429,73 − 0,095575×R. L12261: 'desconto simplificado 16.754,34 — valores mantidos para 2026 ✓' vs 17.640,00. L25406 vs prevContribuicaoINSS L25270 (faixas 2.902,84/4.354,27/8.475,55 = 2026).

Base legal / matemática. Lei 15.270/2025 — https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2025/lei/L15270.htm ; Portaria MPS/MF 13/2026 — https://www.legisweb.com.br/legislacao/?id=489284 ; CDC art. 6º III/31 (informação correta) — boa prática

O que foi feito. Laudo embutido (Help › Auditoria): linhas do redutor, tabela anual, deduções, INSS e Simples reescritas com os valores legais e nota 'retificado na v10.33'; '0 correções' e 'Nenhuma correção é necessária' substituídos; 'Prova executada' refeita com 70 mil/60 mil; conclusão reescrita. Help do IRPF e do Carnê-Leão atualizados. Rodapé do INSS: 'Faixas de contribuição CLT de 2026 (Portaria MPS/MF 13/2026)'.

Observação. As strings 11.743,44/16.754,34 ainda aparecem no laudo apenas como 'valor anterior, retificado' e em comentários de código.

🧪 /tmp/test_v1055.js · confiança do auditor: alta
Baixa FIS-21

Imposto anual calculado como 12× a tabela mensal difere em centavos da tabela anual oficial (parcelas a deduzir arredondadas)

Corrigido
📍 irpfImpostoTabela L29832-29836

O problema. A tabela anual oficial de 2026 tem parcelas a deduzir 2.185,92 / 4.729,91 / 8.105,85 / 10.904,66, que não são exatamente 12× as mensais (182,16×12 = 2.185,92 ✓; 394,16×12 = 4.729,92; 675,49×12 = 8.105,88; 908,73×12 = 10.904,76). O app anualiza a mensal, gerando diferença de até R$ 0,10 contra o programa da Receita.

Evidência numérica. Base 100.000: oficial 100.000×27,5% − 10.904,66 = 16.595,34; app (8.333,33×27,5% − 908,73)×12 = 16.595,24 (−0,10). Base 50.000: oficial 3.144,15; app 3.144,12.

Base legal / matemática. RFB, tabela progressiva anual AC 2026 — https://www.gov.br/receitafederal/pt-br/assuntos/meu-imposto-de-renda/tabelas/2026

O que foi feito. IRPF_TABELA_A (faixas 29.145,60/33.919,80/45.012,60/55.976,16; parcelas 2.185,92/4.729,91/8.105,85/10.904,66); irpfImpostoTabela e irpfAliquotaMarginal usam a anual; chave remota irpfTabelaAnual em tabelasCarregar.

Observação. Reproduzido (16.595,24 no original). Números conferidos na tabela anual AC 2026 da RFB (via cidesp/fia).

🧪 /tmp/test_v1050.js (100.000 → 16.595,34; 29.145,60 → 0; 50.000 → 3.144,15) · confiança do auditor: alta
Baixa FIS-22

Carnê-leão 'dos aluguéis' no dashboard é recalculado isolando só os aluguéis com TODAS as deduções — dedução em dobro e progressividade ignorada

Corrigido
📍 L26315-26330 (d.clAluguelMes = clImposto(rendAluguel − ded, rendAluguel).darf)

O problema. Para mostrar a renda líquida de imóveis, o app recalcula o carnê-leão só sobre os lançamentos de aluguel do mês, aplicando de novo dependentes/INSS/pensão/livro-caixa (já usados no cálculo do mês inteiro) e sem a progressividade dos outros rendimentos do mesmo mês. O 'líquido de aluguéis' fica superestimado quando há outros rendimentos de PF no mês.

Evidência numérica. Mês com trabalho PF 6.000 + aluguel 4.000, sem deduções: carnê-leão do mês (10.000 − 607,20 → 1.674,29 − 0 redutor... base 9.392,80 → 1.674,29) ; app atribui ao aluguel clImposto(4.000, 4.000) = 200,39 − 200,39 (redutor) = 0 → 'aluguel líquido 4.000'. Proporcional correto: 1.674,29 × 4.000/10.000 = 669,72 → líquido 3.330,28.

Base legal / matemática. matemática (tabela progressiva é sobre o total mensal — Lei 7.713/1988 art. 8º)

O que foi feito. planejDados (L26335): d.clAluguelMes = darf do mês (clCalcMes) × aluguéis ÷ rendimentos tributáveis do mês, em vez de recalcular o carnê-leão só sobre os aluguéis com todas as deduções.

🧪 /tmp/test_v1051.js (pf 6.000 + aluguel 4.000 → 669,72) · confiança do auditor: alta

Simuladores financeiros (financiamentos, IOF, consórcio, Tesouro, CDB, IPVA, previdência)

23 achados · 4 alta · 10 média · 9 baixa
Alta SIM-01

IOF diário calculado sobre o principal INTEIRO por min(n×30,365) dias, e não por parcela (tranche)

Corrigido
📍 faCalc L31443-31463 (dias=Math.min(n*30,365); iof=base*(0.0038+0.000082*dias))

O problema. O app aplica 0,0082%/dia sobre TODO o valor financiado pelo prazo do contrato (teto 365 dias). O Decreto 6.306/2007 art. 7º, I, 'b' manda aplicar a alíquota diária sobre 'o valor do principal de cada uma das parcelas', pelos dias de cada parcela (§1º: teto de 365 dias por valor de principal). Resultado: IOF superestimado em prazos curtos e o comparativo de prazos fica distorcido (12 meses mostra IOF quase igual ao de 60 meses).

Evidência numérica. 60.000 − 20.000 entrada = 40.000, 1,49% a.m., tarifa 900 → app: IOF 12m = 40.000×(0,38%+0,0082%×360) = R$ 1.332,80; 48m = R$ 1.349,20 · correto (Σ amort_k × 0,0082% × min(30k,365) + 0,38%×principal; base só 40.000): 12m = R$ 808,93; 24m = R$ 1.102,98; 36m = R$ 1.199,97; 48m = R$ 1.247,71; 60m = R$ 1.275,77. Em 12 meses a parcela cai de R$ 3.869,49 para R$ 3.824,73 e o CET de 2,37% para 2,18% a.m. (recomputado em /tmp/rev2/simuladores/recompute.py; faCalc reproduzida em Node com os mesmos números).

Base legal / matemática. Decreto 6.306/2007 art. 7º, I, 'b' e §1º (alíquota PF 0,0082% a.d. — Decreto 8.392/2015; adicional 0,38% §15; decretos 12.466/12.499 de 2025 alteraram só PJ; ADC 96) — https://www.planalto.gov.br/ccivil_03/_ato2007-2010/2007/decreto/d6306.htm

O que foi feito. Reproduzido (IOF 12m = 1.332,80 ≈ 48m 1.349,20). faCalc reescrita com faIOF(P,i,n): 0,0082%/dia sobre o principal de CADA parcela da Price (teto 365 dias por parcela) + 0,38% (Decreto 6.306 art. 7º I 'b', §1º e §15 — PF conferido no texto vigente; decretos de 2025 só mudaram PJ). Novo helper global simTIR(pv, parcelas) para o CET. Texto de ajuda do IOF reescrito.

Observação. IOF 48m = 1.316,86 e 12m = 844,21 (base principal+tarifa+IOF); recomputo independente no próprio teste.

🧪 /tmp/test_v1057.js · confiança do auditor: alta
Alta SIM-02

Tabela IPVA_UF desatualizada para 2026 (PR 1,9%, AM 1,5%, PE 2,4%, MS 3%) — preset e ranking 'mesmo veículo em cada estado' errados

Corrigido
📍 IPVA_UF L30618-30620; ipvaPreset L30626; ranking em ipvaCalcular L30684-30698

O problema. A constante traz PR 3,5%, AM 3%, PE 3%, MS 3,5% para carros. Em 2026 o Paraná cobra 1,9% (menor do país), o Amazonas reduziu 50% (para 1,5%), Pernambuco mantém 2,4% e MS 3%. O preset da UF e o ranking dos 27 estados (que diz 'seu estado é o Nº mais barato' e 'no X pagaria Y a menos') saem errados mesmo sem o usuário editar nada.

Evidência numérica. Carro de R$ 60.000 → app PR: 3,5% = R$ 2.100,00 · correto 1,9% = R$ 1.140,00 (dif R$ 960); AM: app R$ 1.800 · correto R$ 900; PE: app R$ 1.800 · correto R$ 1.440; MS: app R$ 2.100 · correto R$ 1.800. O ranking aponta AC/ES/SC/TO (2%) como mais baratos quando AM (1,5%) e PR (1,9%) são.

Base legal / matemática. PR: https://www.fazenda.pr.gov.br/Pagina/Calcule-agora-seu-IPVA-2026 (alíquota 1,9%) · AM: https://www.sefaz.am.gov.br/noticias/31873 (redução de 50%) · PE: https://www.sefaz.pe.gov.br/Noticias-Destaque/Paginas/Governo-de-Pernambuco-divulga-calend%C3%A1rio-do-IPVA-2026-com-al%C3%ADquota-mantida-em-2,4,-a-menor-do-Nordeste.aspx · MS 3%: sefaz.ms.gov.br/ipva (portal em restrição eleitoral; confirmado em fonte secundária) · panorama: https://www.cnnbrasil.com.br/auto/ipva-2026-saiba-quais-estados-cobram-os-impostos-mais-caros-e-baratos/

O que foi feito. Reproduzido (preset PR 3,5; ranking apontava AC). IPVA_UF revisada para 2026 (PR 1,9/1,9; AM 2/1 — LC 280/2025 cortou 50%: carros >1.000 cm³ 4%→2%, ≤1.000 cm³ 1,5%; PE 2,4; MS 3; DF 3,5/2; BA moto 1; CE moto 1,5; demais confirmadas), IPVA_UF_ANO=2026 com aviso na tela (e alerta se o ano corrente for maior), IPVA_DESC com desconto à vista das UFs conferidas (SP/RJ/MG 3, PR 6, MS 15, AL 5, CE 10, AM 10, SC 5, DF 5) aplicado no preset.

Observação. Portais oficiais em restrição eleitoral (MS, AM, PR retornaram 403/redirect); alíquotas cruzadas em ≥2 fontes (CNN, Numerando, gringo, autopapo/LC 280). Usei AM=2% (carro típico >1.000 cm³) e não 1,5% como o auditor. Motos de MT/RO/RR/AL não conferidas individualmente.

🧪 /tmp/test_v1058.js · confiança do auditor: alta
Alta SIM-03

Aluguel de ações: pro-rata LINEAR por dias corridos (÷252), IR fixo 15% e 'Estimativa mensal' igual ao valor do período

Corrigido
📍 calcAluguel L9159-9182

O problema. rendimentoPeriodo = patrimônio × taxa/252 × dias, com 'dias' lidos como corridos ('Período (dias)', placeholder 30) — mistura base 252 (dias úteis) com dias corridos e usa juros simples. O IR é fixo em 15%, mas a remuneração do doador é tributada como renda fixa com tabela regressiva (22,5% até 180 dias). 'Estimativa mensal' = rendimentoPeriodo/30*30 = o próprio valor do período (para 90 dias mostra o trimestre como 'mensal'). O próprio app já faz certo em btcCalcular (L28464: (1+taxa)^(du/252)−1 e btcAliquotaIR).

Evidência numérica. 1.000 × R$ 28,50, 3,5% a.a., 30 dias → app: receita bruta R$ 118,75, IR 15% R$ 17,81, líquido R$ 100,94, 'estimativa anual' R$ 997,50 · correto (≈21 d.u.; 28.500×[(1,035)^(21/252)−1] = R$ 81,82; IR 22,5% = R$ 18,41; líquido R$ 63,41) — receita superestimada 45%, líquido 59%. Com 90 dias: 'Estimativa mensal' mostra R$ 356,25 (deveria ≈ R$ 118,75).

Base legal / matemática. IN RFB 1585/2015 art. 73 (remuneração do emprestador tributada como renda fixa, alíquotas regressivas do art. 6º) — espelho do texto: https://www.normaslegais.com.br/legislacao/instrucao-normativa-1585-2015.htm; Lei 11.033/2004 art. 1º (22,5/20/17,5/15%) — https://www.planalto.gov.br/ccivil_03/_ato2004-2006/2004/lei/l11033.htm; base 252 d.u. é a convenção B3 do BTC.

O que foi feito. Corrigido por fix_fiscal (FIS-12 dele: calcAluguel com base 252, IR regressivo por dias, mensal = 21 d.u.). Não toquei em calcAluguel nesta cópia.

Observação. corrigido por fix_fiscal

🧪 /tmp/test_v1046.js (do fix_fiscal) · confiança do auditor: alta
Alta SIM-04

Tabela regressiva aplicada com UMA alíquota (do prazo total) sobre todo o saldo — a lei conta o prazo de cada aporte

Corrigido
📍 prevCalcPlano L25281-25303 e prevAliqRegressiva L25304-25311

O problema. aliq = prevAliqRegressiva(anos) usa o prazo do plano inteiro e aplica a todos os recursos. Pela Lei 11.053 art. 1º §3º o prazo de acumulação é o tempo entre CADA aporte e o resgate: em um plano de 20 anos com aportes mensais, os aportes dos últimos 2 anos pagam 35%, dos 2-4 anos 30% etc. O app cobra 10% de tudo e subestima o IR (mais grave no PGBL, onde a base é o total).

Evidência numérica. Aporte R$ 1.000/mês, 6% a.a., 20 anos, regressivo → app: FV 441.427,09, IR VGBL = 10% × 201.427 = R$ 20.142,71; PGBL = 10% × 441.427 = R$ 44.142,71 · correto (por aporte, FV mensal 453.438,63): IR VGBL = R$ 25.941,97 (alíquota média 12,15% sobre o rendimento); PGBL = R$ 68.191,97 — o app subestima o IR do PGBL em R$ 24.049 (35%).

Base legal / matemática. Lei 11.053/2004 art. 1º, incisos I-VI e §3º ('prazo de acumulação é o tempo decorrido entre o aporte de recursos ... e o pagamento') — https://www.planalto.gov.br/ccivil_03/_ato2004-2006/2004/lei/l11053.htm

O que foi feito. Reproduzido (IR PGBL 44.142,71 com 10% em tudo). prevCalcPlano projeta aporte a aporte: aporte k rende N−k meses e paga prevAliqRegressiva((N−k)/12) (Lei 11.053 art. 1º §3º); saldo inicial com prazo = anos + p.saldoAnos (opcional, padrão 0). `aliq` devolvido passa a ser a alíquota EFETIVA média; irNota do prevRender (1 linha) mostra 'IR regressivo por aporte ≈ X% médio'. Progressivo (FIS-11): 15% na fonte = antecipação (irFonte); IR final = irpfImpostoDevido(base,0).devido se existir (cópia do irpf), senão irpfImpostoTabela(base), senão 15%.

Observação. PGBL 1.000/mês 6% 20a → IR 68.191,97; VGBL 25.941,97 (iguais ao auditor). test_v1036 codificava a alíquota única de 10% e a renda antiga — reescrito com os valores recomputados (FV 693.561,67; IR 93.026,43 / 45.776,43). Cobre FIS-10 do irpf_pj.

🧪 /tmp/test_v1059.js; /tmp/test_v1036.js ajustado · confiança do auditor: alta
Média SIM-05

Rentabilidade do IPCA+ somada linearmente (taxa + IPCA) em vez de Fisher (1+taxa)(1+IPCA)−1

Corrigido
📍 calcTesouro L18314 (taxaEfetiva=(taxa+ipca)/100) e renderTesouro L18232 ((t.taxa+4.5)/100)

O problema. O título paga IPCA+taxa de forma composta: o fator é (1+IPCA)×(1+taxa). Somar os dois subestima a rentabilidade nominal, e o erro cresce com o prazo.

Evidência numérica. IPCA+ 6,24% com IPCA 4,5% → app 10,74% a.a.; correto 1,0624×1,045−1 = 11,02% a.a. R$ 1.000 por 10 anos: app R$ 2.773,61 · correto R$ 2.844,75 (−R$ 71). IPCA+ 7% com IPCA 6%: app 13,00% · correto 13,42%.

Base legal / matemática. matemática (regra de Fisher; metodologia de precificação do Tesouro IPCA+: VNA×fator)

O que foi feito. Reproduzido (2.773,61). tdTaxaEfetiva(tipo,taxa,ipca) = (1+taxa)(1+IPCA)−1 usada no card (renderTesouro) e em calcTesouro; comparativo mostra a nominal composta.

Observação. IPCA+ 6,24 / 4,5% / 10 anos → 2.844,75.

🧪 /tmp/test_v1060.js · confiança do auditor: alta
Média SIM-06

Faixas do IR por MESES (≤6/≤12/≤24) — em 12 meses (365 dias) a lei já dá 17,5% e em 24 meses (730 dias) 15%

Corrigido
📍 tdIR L18204-18209; rótulo 'até 12m/até 24m' em calcTesouro L18359

O problema. A tabela legal é em dias (180/360/720). 12 meses = 365 dias > 360 → 17,5%, mas tdIR(12) devolve 20%; 24 meses = 730 dias > 720 → 15%, mas tdIR(24) devolve 17,5%; 6 meses ≈ 182 dias > 180 → 20%, app 22,5%. Os presets do simulador (24 meses por padrão) caem exatamente nas bordas erradas.

Evidência numérica. Prefixado 11,5%, R$ 1.000, 24 meses → lucro 243,23; IR app 17,5% = R$ 42,56 · correto 15% = R$ 36,48 (dif R$ 6,08 = 2,5% do lucro). 12 meses: app 20% · correto 17,5%.

Base legal / matemática. Lei 11.033/2004 art. 1º (até 180 dias 22,5%; 181-360 20%; 361-720 17,5%; >720 15%) — https://www.planalto.gov.br/ccivil_03/_ato2004-2006/2004/lei/l11033.htm; MP 1.303/2025 caducou, tabela mantida em 2026 (https://investnews.com.br/investimentos/com-o-fim-da-mp-1-303-veja-como-fica-o-ir-sobre-cada-tipo-de-investimento/)

O que foi feito. Reproduzido (tdIR(12)=20%, tdIR(24)=17,5%). Novas irRegressivoDias(d) e irRegressivoFaixa(d) (Lei 11.033 art. 1º, em dias); tdIR(meses) converte meses×365/12; rótulo do detalhamento em dias.

Observação. 24 meses (730 d) → 15% = 36,48.

🧪 /tmp/test_v1060.js · confiança do auditor: alta
Média SIM-07

Custódia inconsistente entre o card (0,2% só no 1º ano) e o simulador (0,2%×anos sobre o valor inicial); ignora isenção do Tesouro Selic até R$ 10 mil

Corrigido
📍 renderTesouro L18234 (1000*0.002*Math.min(anos,1)); calcTesouro L18307 (custodia=0.002*min(anos,1)+0.002*max(anos-1,0) ≡ 0.002*anos) e L18322 (custodiaR=valor*custodia)

O problema. O card do título cobra custódia de no máximo 1 ano (para um IPCA+ 2045 cobra 0,2% em 19 anos), o simulador cobra 0,2% por ano mas sobre o valor investido (a B3 cobra sobre o valor de mercado, provisionada diariamente). Para Tesouro Selic não aplica a isenção dos primeiros R$ 10 mil.

Evidência numérica. R$ 1.000 por 19 anos a 11,02%: card = R$ 2,00; calcTesouro = R$ 38,00; sobre valor de mercado ≈ R$ 114. Tesouro Selic R$ 5.000 por 2 anos: app cobra R$ 20,00 · correto R$ 0,00 (isento até R$ 10 mil).

Base legal / matemática. B3 — Tarifas de Tesouro Direto: 0,20% a.a. sobre o valor dos títulos, provisionada diariamente; Tesouro Selic isento até R$ 10.000 por CPF (cobra só o excedente) — https://www.b3.com.br/pt_br/produtos-e-servicos/tarifas/tarifas-de-tesouro-direto/

O que foi feito. Reproduzido (card 2,00 p/ 19 anos; simulador 4,00 e Selic 5 mil pagando 20,00). tdCustodia(valor,taxaEf,meses,index): 0,20% a.a./12 sobre o saldo capitalizado mês a mês (saldo médio), Selic só sobre o excedente de R$ 10 mil (tabela B3 conferida); usada no card e no simulador.

Observação. Prefixado 11,5% 24m → 4,47 (o '≈4,25' do auditor era aproximação); Selic 5.000 → 0,00; IPCA+ 2045 → >38.

🧪 /tmp/test_v1060.js · confiança do auditor: alta
Média SIM-08

Período 'Diário': prazo arredondado para meses inteiros — até 15 dias vira 0 meses (juros zero) e 45 dias vira 1 mês

Corrigido
📍 calcJuros L9101-9106 (nMeses=Math.round(n*12/porAno))

O problema. Com tipo 'diario' o prazo em dias é convertido a meses e ARREDONDADO. O montante é C×(1+i_mensal)^nMeses; para n<16 dias dá nMeses=0 e o resultado é o próprio capital; para 45 dias rende 30,4 dias; para 200 dias rende 7 meses (213 dias).

Evidência numérica. C=10.000, 0,05% ao dia: 10 dias → app R$ 10.000,00 (juros 0) · correto 10.000×1,0005^10 = R$ 10.050,11; 45 dias → app R$ 10.153,21 · correto R$ 10.227,49; 200 dias → app R$ 11.123,02 · correto R$ 11.051,43.

Base legal / matemática. matemática

O que foi feito. Reproduzido (10 dias → juros 0; 200 dias → 11.123,02). calcJuros capitaliza o capital n períodos na taxa do período (C×(1+i)^n) e o aporte mensal pela taxa mensal equivalente com prazo em meses fracionário (sem Math.round); linha única quando o prazo é menor que 1 mês.

Observação. 10 d → 10.050,11; 45 d → 10.227,49; 200 d → 11.051,43. Caso mensal/anual do v10.27 inalterado (test_v1034 passa).

🧪 /tmp/test_v1061.js · confiança do auditor: alta
Média SIM-09

Alíquota de IR escolhida à mão (padrão 22,5%) sem vínculo com o prazo digitado; sem IOF regressivo (<30 dias) nem opção LCI/LCA isenta

Corrigido
📍 calcCDI L9130-9157; select cdi-ir L2179

O problema. O campo 'Período (dias)' não seleciona a faixa de IR; o select nasce em 22,5%. Quem digita 365 dias e não mexe no select paga 22,5% em vez de 17,5%. Também não há IOF para resgates antes de 30 dias nem a opção de produto isento (LCI/LCA/CRI/CRA), que é a comparação mais comum de '% do CDI'.

Evidência numérica. R$ 10.000, CDI 14,9%, 110%, 365 dias → bruto R$ 1.639,00; app (select padrão) IR 22,5% = R$ 368,77, líquido R$ 1.270,22 · correto 17,5% (361-720 dias) = R$ 286,82, líquido R$ 1.352,17 (dif R$ 81,95).

Base legal / matemática. Lei 11.033/2004 art. 1º — https://www.planalto.gov.br/ccivil_03/_ato2004-2006/2004/lei/l11033.htm; IOF regressivo Decreto 6.306/2007 art. 32 §? (tabela do Anexo, 96%→0 em 30 dias) — https://www.planalto.gov.br/ccivil_03/_ato2007-2010/2007/decreto/d6306.htm; isenção LCI/LCA Lei 12.024/2009 mantida em 2026 (MP 1.303 caducou)

O que foi feito. Reproduzido (365 dias com 22,5%). cdiAutoIR(): select segue o prazo digitado (oninput) e no Calcular, salvo escolha manual (onchange marca _cdiIrManual); opção 'Isento (LCI/LCA/CRI/CRA)' (isenção mantida em 2026, MP 1.303 caducou); IOF regressivo <30 dias pela tabela do Anexo do Decreto 6.306 (IOF_RF_TABELA / iofRendaFixaPct) sobre o rendimento, antes do IR; rótulo 'dias corridos'.

Observação. 20 dias → IOF 33% do rendimento; 365 → 17,5% = 286,82.

🧪 /tmp/test_v1061.js · confiança do auditor: alta
Média SIM-10

Duelo consórcio × financiamento compara parcelas nominais SEM reajuste da carta/parcelas contra juros nominais do financiamento

Corrigido
📍 consSimular L30892-30916 (totalPago=carta+admR+frR+adeR+segR; parcFin Price nominal)

O problema. As obrigações do consorciado são percentuais do preço do bem (Lei 11.795 art. 27 §1º), reajustados pelo índice do grupo (INCC/IPCA/tabela do fabricante); o app soma as parcelas em valores de hoje e as compara com um financiamento a juros nominais (que já embutem inflação). O consórcio sai sistematicamente 'mais barato' por uma diferença que é, em parte, só a inflação não contabilizada. Item já apontado na auditoria anterior e mantido de propósito — segue a proposta de correção.

Evidência numérica. Carta 80.000, 80 meses, adm 18%, FR 2%, seguro 0,04% → app: parcela 1.232,00, total R$ 98.560,00 vs financiamento 1,6% a.m. total R$ 142.394,03 ('consórcio R$ 43.834 mais barato'). Com reajuste de 5% a.a. o total nominal do consórcio é R$ 113.767,46 (última parcela 1.651,00; carta final 107.207,65). Alternativa em termos reais: financiamento a taxa real (1,016/1,004074−1 = 1,188% a.m.) → total R$ 124.378,70. A vantagem real cai de 43,8 mil para ≈ 26 mil.

Base legal / matemática. Lei 11.795/2008 art. 27 §1º (obrigações em percentual do preço do bem) — https://www.planalto.gov.br/ccivil_03/_ato2007-2010/2008/lei/l11795.htm; matemática (Fisher)

O que foi feito. Reproduzido (total 98.560 nominal vs 142.394). Campo 'Reajuste anual (% a.a.)' (cons-reaj; preset auto 4 / imóvel 5 / serviços 4 em consPreset); parcela_k = base×(1+r)^floor((k−1)/12) (Lei 11.795 art. 27 §1º), carta final e última parcela exibidas, taxa embutida pela TIR do fluxo reajustado (simTIR), nota nominal×nominal no duelo. Rodapé e relColeta atualizados.

Observação. Com 5% a.a. o desembolso vai a 112.229,04 (o 113.767,46 do auditor usava seguro sobre a carta cheia).

🧪 /tmp/test_v1062.js · confiança do auditor: alta
Média SIM-11

Seguro prestamista cobrado sobre a carta CHEIA todos os meses e fundo de reserva tratado como custo perdido

Corrigido
📍 consSimular L30903-30905 (segR=carta*(seg/100)*prazo; totalPago inclui frR integral)

O problema. O seguro prestamista incide sobre o saldo devedor (percentual do que falta pagar), que decresce mês a mês — cobrar 0,04% sobre 100% da carta por 80 meses dobra o custo. O fundo de reserva pertence ao grupo e o saldo remanescente é rateado/devolvido no encerramento (Lei 11.795 art. 27 §2º e art. 32) — não é integralmente custo.

Evidência numérica. Carta 80.000, 80 m, seguro 0,04% a.m. → app R$ 2.560,00 · sobre saldo decrescente Σ 80.000×(1−(k−1)/80)×0,04% = R$ 1.296,00 (app cobra 97% a mais). Fundo de reserva 2% = R$ 1.600 lançado 100% como acréscimo.

Base legal / matemática. Lei 11.795/2008 art. 27 §2º e art. 32 (prestação de contas e disponibilidades remanescentes) — https://www.planalto.gov.br/ccivil_03/_ato2007-2010/2008/lei/l11795.htm; prática de mercado (seguro sobre saldo devedor)

O que foi feito. Reproduzido (seguro 2.560). Seguro prestamista = Σ saldo devedor (carta reajustada×(1−(k−1)/prazo))×seg%; fundo de reserva pago (frPago) sai do custo do duelo (custoLiq = desembolso − FR devolvido, art. 27 §2º/32) com nota na tela; cards e relatório separam desembolso e custo líquido.

Observação. adm 0/FR 0/seg 0,04 → 1.296,00.

🧪 /tmp/test_v1062.js · confiança do auditor: media
Média SIM-12

Simulador não mostra CET; a 'Taxa efetiva a.m.' exibida é só o juro, embora a parcela some seguros e tarifa

Corrigido
📍 finSimular L31563-31573 (rótulo 'Taxa efetiva: … a.m.'); finCalcSAC/PRICE L30027-30060

O problema. O card 'Renda mínima' exibe 'Taxa efetiva: 0,8355% a.m.' (juros puros), enquanto a parcela inclui seguros (R$ 80) e taxa de administração (R$ 25). A norma define o CET como a taxa que iguala os fluxos INCLUINDO juros, tarifas, tributos e seguros, expresso em % ao ano. Sem o CET o usuário não consegue comparar a simulação com a proposta do banco (que é obrigado a informá-lo).

Evidência numérica. 400.000, entrada 80.000, 10,5% a.a., 30 anos, seguro 80, adm 25 → app: 'Taxa efetiva 0,8355% a.m.' · CET (TIR dos fluxos +320.000; −parcelas): SAC 0,8805% a.m. = 11,09% a.a.; PRICE 0,8723% a.m. = 10,98% a.a. (recomputado). O simulador de automóvel já calcula CET por bisseção — o imobiliário não.

Base legal / matemática. Resolução CMN 4.881/2020 arts. 2º-4º (CET inclui juros, tarifas, tributos, seguros; expresso em taxa percentual anual; fórmula de fluxo de caixa) — https://www.bcb.gov.br/content/estabilidadefinanceira/especialnor/Resolu%C3%A7%C3%A3o4881.pdf (revogou a Res. 3.517/2007 a partir de 1/2/2021)

O que foi feito. Reproduzido ('Taxa efetiva 0,8355%'). finCET(p,sistema) = TIR (simTIR) de [financiado; −parcelas com juros+seguros+tarifa], em % a.a. (Res. CMN 4.881 arts. 2º-4º; TR fora, art. 5º); cada card SAC/PRICE mostra 'CET ≈ X% a.a. (Y% a.m.)', linha da renda renomeada para 'Juros: 0,8355% a.m. (10,5% a.a. efetiva)' + CETs; relColeta com CET; rodapé legal novo.

Observação. SAC 11,09% / PRICE 10,98% a.a. com 105/mês; 10,50% sem encargos.

🧪 /tmp/test_v1063.js · confiança do auditor: alta
Média SIM-13

Não modela TR sobre o saldo devedor nem MIP/DFI proporcionais (seguro fixo em R$ por 30 anos)

Corrigido
📍 finLerEntradas L30012-30025; finCalcSAC L30027 (parcela = A + juros + seguro + admin)

O problema. A maioria dos contratos SFH/SBPE é 'TR + x% a.a.': a TR corrige o saldo devedor mensalmente antes dos juros. O app não tem campo de TR e assume seguro constante em reais, quando o MIP é percentual do saldo devedor (e cresce com a idade) e o DFI percentual do valor do imóvel — com seguro fixo o total de 'Seguros+taxas' (R$ 37.800) e a parcela final ficam errados.

Evidência numérica. SAC 320.000, 10,5% a.a., 360 m: total sem TR R$ 840.393,79; com TR de 0,1% a.m. sobre o saldo (recálculo da amortização a cada mês) R$ 969.588,34 — diferença R$ 129.194,55 (15%) que a tela nunca mostra.

Base legal / matemática. Lei 8.177/1991 art. 18 (TR como índice de correção nos contratos do SFH); Resolução CMN 4.881/2020 art. 5º (indexadores ficam fora do CET mas devem ser informados) — https://www.bcb.gov.br/content/estabilidadefinanceira/especialnor/Resolu%C3%A7%C3%A3o4881.pdf

O que foi feito. Reproduzido (sem TR; seguro fixo). Campos fin-tr (% a.m. sobre o saldo, SAC recalcula A=saldo/(n−m+1), PRICE recalcula a prestação), fin-idade → finMipPreset() (tabela simplificada por faixa etária em finMipPorIdade), fin-mip (% do saldo/mês) e fin-dfi (% do imóvel/mês) substituem 'Seguros/mês (R$)'; finLerEntradas devolve mip/dfi/tr/idade; tabela e CSV ganham coluna Seguros.

Observação. TR 0,1% → SAC 969.588,51 (auditor 969.588,34, dif = centavos); MIP 0,02% → 64,00 → 0,18. Faixas de MIP são valores de referência de mercado (dito na tela) — o dono pode querer calibrar com a apólice de um banco.

🧪 /tmp/test_v1063.js · confiança do auditor: alta
Baixa SIM-14

Selo 'MENOR CUSTO' e 'você economiza R$ X' comparam somas nominais — à taxa do contrato o valor presente é idêntico

Corrigido
📍 finSimular L31519 (econSAC = price.totalPago − sac.totalPago) e L31551-31556

O problema. A soma das parcelas do SAC é sempre menor porque amortiza mais cedo; descontadas à taxa do contrato, as duas séries valem exatamente R$ 320.000. Apresentar R$ 210 mil como 'economia' induz o usuário a ignorar que a diferença é fluxo de caixa (parcela inicial 26% maior), não custo.

Evidência numérica. Defaults: SAC total 840.393,79 vs PRICE 1.050.992,42 → 'economiza R$ 210.598,62'. VP de ambos a 0,8355% a.m. = R$ 320.000 (+ VP dos R$ 105/mês fixos, igual nos dois).

Base legal / matemática. matemática (equivalência de fluxos)

O que foi feito. Reproduzido (selo MENOR CUSTO + 'economiza 210.598'). Selo passa a 'MENOR CET'; caixa diz 'X paga R$ Y a menos em juros nominais, com 1ª parcela R$ Z maior (+W%)' e explica a equivalência a valor presente; relColeta idem.

🧪 /tmp/test_v1063.js · confiança do auditor: alta
Baixa SIM-15

IOF calculado só sobre (valor − entrada): tarifa financiada e o próprio IOF financiado ficam fora da base; CET destacado em % a.m.

Corrigido
📍 faCalc L31447-31449

O problema. Quando tarifa e IOF são financiados eles integram o principal entregue (art. 7º I 'b') e também sofrem IOF — os bancos calculam iterativamente. O app cobra IOF só sobre a base. O card principal mostra o CET em % a.m. e o a.a. em letra menor; a norma manda expressar em taxa anual.

Evidência numérica. n=48: IOF por tranche sobre 40.000 = R$ 1.247,71; sobre principal 40.900+IOF (iterado) = R$ 1.316,86 (dif R$ 69,15); parcela 1.237,47 vs 1.237,45 — impacto pequeno, mas o rótulo 'como os bancos fazem' não é verdadeiro.

Base legal / matemática. Decreto 6.306/2007 art. 7º I 'b' — https://www.planalto.gov.br/ccivil_03/_ato2007-2010/2007/decreto/d6306.htm; Res. CMN 4.881/2020 art. 4º parágrafo único (CET em taxa anual)

O que foi feito. faCalc itera o IOF sobre base+tarifa+IOF (converge em <8 passos); card principal, tabela de prazos (colunas IOF e CET a.a.) e relColeta mostram o CET em % a.a. primeiro.

Observação. 48m → 1.316,86 (igual ao auditor). 1 parcela sem tarifa passa de 250,40 a 251,98 (IOF sobre o IOF financiado).

🧪 /tmp/test_v1057.js · confiança do auditor: media
Baixa SIM-16

Rodapé cita 'Lei 10.931' para o direito de amortizar sem custo; recusa juros = 0 e ignora 'extra por mês' no cenário reduzir parcela

Corrigido
📍 HTML L3089 (texto 'pela Lei 10.931'); qaSimular L31344 (if j<=0 return) e L31360 (pmt2 ignora E)

O problema. A Lei 10.931/2004 trata de patrimônio de afetação/CCB, não do direito à quitação antecipada. O direito à redução proporcional dos juros é do CDC art. 52 §2º e a vedação de tarifa é da Res. CMN 3.516 art. 1º (o art. 2º, da forma de cálculo do valor presente, foi substituído pela Res. CMN 5.004/2022). Além disso o simulador recusa contratos a 0% (comum em lojas) e o cenário 'reduzir parcela' não usa o 'extra por mês'.

Evidência numérica. Saldo 200.000, 0,84% a.m., 240 m, extra 500: parcela 1.940,66, total 465.757,43, novo prazo 140 m, economia R$ 125.594,24 (recomputado — números da tela corretos). Com juros 0 → 'Informe saldo, juros e prazo' em vez de mostrar prazo/parcela.

Base legal / matemática. CDC (Lei 8.078/1990) art. 52 §2º; Res. CMN 3.516/2007 art. 1º (vigente) — https://normativos.bcb.gov.br/Lists/Normativos/Attachments/48006/Res_3516_v3_L.pdf; Res. CMN 5.004/2022

O que foi feito. Reproduzido (juros 0 → mensagem; extra ignorado no reduzir-parcela). qaSimular aceita j=0 (pmt=S/n), arredonda a centavos, e no cenário reduzir-parcela recalcula a parcela mês a mês com o extra (pmt2Final, pagoParcela); rodapé: CDC art. 52 §2º, Res. CMN 3.516 art. 1º e Res. 5.004/2022 (sem 'Lei 10.931').

Observação. 200 mil/0,84%/240/extra 500 continua 1.940,66 · 140 meses · economia ≈125.594.

🧪 /tmp/test_v1062.js, /tmp/test_v1057.js (grep 10.931) · confiança do auditor: alta
Média SIM-17

Aportes mensais capitalizados como UM aporte anual no fim do ano e taxa de administração subtraída linearmente

Corrigido
📍 prevCalcPlano L25282-25288 (aporteAno=aporteMes*12; fv=…+aporteAno*((1+r)^anos−1)/r)

O problema. O aporte mensal é somado e capitalizado só a partir do fim de cada ano, perdendo em média 5,5 meses de rendimento por aporte; o correto é usar a taxa mensal equivalente (1+r)^(1/12)−1 com 12×anos aportes. A taxa de administração é subtraída da rentabilidade (rent − adm) quando incide sobre o patrimônio: (1+rent)(1−adm)−1.

Evidência numérica. R$ 1.000/mês, 6% a.a., 20 anos → app FV R$ 441.427,09 · mensal equivalente R$ 453.438,63 (−R$ 12.011, −2,7%). rent 10%, adm 1%: app 9,00% · correto 8,90%.

Base legal / matemática. matemática

O que foi feito. Em prevCalcPlano: r=(1+rent)(1−adm)−1, im=(1+r)^(1/12)−1, N=12×anos aportes mensais postecipados capitalizados mês a mês (FV = Σ aporte×(1+im)^(N−k)).

Observação. FV 453.438,63; rent 10/adm 1 → 8,90%.

🧪 /tmp/test_v1059.js · confiança do auditor: alta
Baixa SIM-18

Comparativo 'CDB/CDI 100%' usa (taxa do título − 0,1) mesmo para IPCA+/Prefixado; catálogo fixo inclui título já vencido (IPCA+ Juros 2026)

Parcial
📍 calcTesouro L18329-18331 (cdiAno=(taxa-0.1)/100); TESOURO_TITULOS L18188-18201; tdAnos L18211

O problema. Para um IPCA+ 6,24% o 'CDB/CDI 100%' vira 6,14% a.a. — não é o CDI. O catálogo é estático (taxas e vencimentos de uma data antiga) e em 10/09/2026 o 'IPCA+ Juros Sem. 2026' (venc. 15/08/2026) aparece com 0 anos, IR 22,5% e 'Rent. líq. +0,00%'. Títulos com cupom são simulados como bullet (sem IR nos cupons).

Evidência numérica. index='ipca', taxa 6,24 → linha '🏦 CDB/CDI 100% (6,14% a.a.)'; card ipcajuros2026: anos=max(0,negativo)=0 → meses=0 → tdIR=22,5%, rentLiq=0,00%.

Base legal / matemática. boa prática

O que foi feito. renderTesouro filtra TESOURO_TITULOS por tdAnos>0 (grid e select); comparativo usa o novo campo 'CDI atual (% a.a.)' (td-sim-cdi, padrão 14,9) para todos os indexadores; rodapé do card avisa que as taxas do catálogo são de referência.

Observação. Parcial: catálogo continua estático (taxas/vencimentos antigos) e títulos com cupom são simulados como bullet — buscar a API do Tesouro/atualizar o catálogo é decisão do dono (custo/rede).

🧪 /tmp/test_v1060.js · confiança do auditor: alta
Baixa SIM-19

Tabela mostra meses mas o aviso diz 'Mostrando primeiros 24 de N períodos' na unidade escolhida

Corrigido
📍 calcJuros L9111-9118 e L9128 (showN<n ? 'Mostrando primeiros ${showN} de ${n} períodos')

O problema. showN é contado em meses (nMeses) e n na unidade do select: com 'Anual' e n=10 a tela diz 'Mostrando primeiros 24 de 10 períodos' e as linhas 'Período 1..24' são meses.

Evidência numérica. tipo='anual', n=10 → nMeses=120, showN=24 → texto 'Mostrando primeiros 24 de 10 períodos'.

Base legal / matemática. boa prática

O que foi feito. Aviso 'Mostrando primeiros N de M meses' usa nMeses; cabeçalho 'Mês'.

Observação. A evidência do auditor (n=10 anual) na verdade não exibia aviso (24<10 falso); reproduzido com n=30 anual ('24 de 30 períodos').

🧪 /tmp/test_v1061.js · confiança do auditor: alta
Baixa SIM-20

Lado 'financiamento' do duelo sem IOF, tarifa e seguro, enquanto o consórcio inclui seguro e fundo de reserva

Corrigido
📍 consSimular L30910-30912 (parcFin = Price puro sobre a carta)

O problema. O financiamento comparado é uma Price limpa; o simulador de automóvel do mesmo app já inclui IOF (0,38%+0,0082%/dia) e tarifa. A assimetria favorece o financiamento em ~R$ 1.300-2.300 em uma carta de 80 mil.

Evidência numérica. Carta 80.000, 80 m, 1,6% a.m.: app total financiamento R$ 142.394,03 (parcela 1.779,93); com IOF por tranche financiado (R$ 2.728,44, iterado sobre principal+tarifa+IOF) e tarifa 900 → parcela R$ 1.860,65 e total R$ 148.852,37 (+R$ 6.458 que o duelo omite).

Base legal / matemática. boa prática (consistência interna); Decreto 6.306/2007 art. 7º

O que foi feito. Lado financiamento do duelo = faCalc(carta,0,jur,prazo,tarifa) (IOF por parcela + tarifa, campo cons-fin-tarifa padrão 900) + seguro prestamista sobre o saldo devedor com o mesmo % do consórcio; mostra IOF, tarifa, seguro e CET a.a.

Observação. Parcela 1.860,65 e IOF 2.728,44 iguais ao auditor.

🧪 /tmp/test_v1062.js · confiança do auditor: media
Baixa SIM-21

Parcelas não são arredondadas a centavos nem há resíduo na última — totais diferem em centavos do extrato bancário

Corrigido
📍 finCalcSAC L30027-30042, finCalcPRICE L30044-30060, faCalc L31451, consSimular L30906, qaSimular L31347

O problema. Os bancos arredondam cada parcela a R$ 0,01 e ajustam a última; o app soma valores com casas infinitas e exibe 'Total pago' com precisão falsa. Diferença é pequena (≤ n × 0,005), mas gera divergências de centavos entre a tabela/CSV exportado e o contrato.

Evidência numérica. PRICE 320.000, 0,8355% a.m., 360 m: pmt = 2.814,4212… → parcela real 2.814,42; soma app 1.050.992,42 vs 360×2.919,42 = 1.050.991,20 (dif R$ 1,22).

Base legal / matemática. boa prática

O que foi feito. finCalcSAC/PRICE: amort/juros/seguro/parcela a centavos (_finR2) e amortização da última = saldo; totais somam as parcelas arredondadas; CSV idem. faCalc: pmt a centavos. consSimular: parcelas/seguro a centavos. qaSimular: pmt/juros/amort a centavos.

Observação. No PRICE a última parcela absorve o resíduo (ex.: 2.927,38 vs 2.919,42) — comportamento de contrato.

🧪 /tmp/test_v1063.js (saldo final 0,00 e Σ CSV == total), /tmp/test_v1057.js · confiança do auditor: alta
Baixa SIM-22

Recomputados e corretos (sem achado): regra dos 4%, conversão real a.a.→a.m. geométrica, Correção pelo BCB (meses inicial e final inclusivos), KM rodado, funil, obra

Sem correção necessária
📍 fiSimular L31403-31441; cvCorrigir L17222-17300; kmSimular L31019; fnSimular L31077; obSimular L30811

O problema. Registro de verificação. Independência: alvo = gasto×12/ret, im=(1+r)^(1/12)−1, loop com aporte postecipado — P=100k, ap=3.000, gasto 5.000, 6% real, 4% → alvo 1.500.000, 224 meses (18a 8m), renda no alvo 5.000 (confere). Correção de Valores: encadeia (1+t/100) de mIni a mFim inclusive — mesma convenção da Calculadora do Cidadão ('São usados no cálculo os índices da data inicial e da data final'; ex.: IPCA 01/2003→01/2003 = 1,0225). KM/funil/obra são aritmética direta, conferida. Não existe simulador 'Cripto DCA' no hub (SIM_HUB L8908-8934) — item do escopo inexistente.

Evidência numérica. ver recompute.py seções K e BCB: https://www3.bcb.gov.br/CALCIDADAO/publico/exibirFormCorrecaoValores.do?method=exibirFormCorrecaoValores

Base legal / matemática. matemática; metodologia BCB (link acima)

O que foi feito. Nada — registro de verificação do auditor (sem achado). Não adicionei a nota sugerida na Independência para não alterar fiSimular sem necessidade.

Observação. Sem achado; nada a corrigir.

🧪 /tmp/test_v1033.js (Correção de Valores, existente) · confiança do auditor: alta
Baixa SIM-23

Rodapés legais desatualizados/ausentes: quitação cita 'Lei 10.931'; CET não referencia a Res. CMN 4.881/2020 (a Res. 3.517 do briefing está revogada)

Corrigido
📍 HTML L3089 (rodapé quitação), L3172-3173 (rodapé finauto 'CET estimado'); código não cita nenhuma norma de CET (grep '3.517|4.881' = 0 ocorrências)

O problema. O código não cita norma para o CET e o rodapé da quitação cita lei errada. A Res. CMN 3.517/2007 (referida no briefing desta auditoria) foi revogada em 1/2/2021 pela Res. CMN 4.881/2020, que mantém o CET em taxa anual incluindo tarifas, tributos e seguros e exige planilha antes da contratação; o art. 2º da Res. 3.516 (forma do valor presente na quitação) foi substituído pela Res. CMN 5.004/2022, permanecendo vigente o art. 1º (vedação de tarifa).

Evidência numérica. grep -n '3.517\|3.516\|4.881' WORK918.html → 0 linhas; L3089 contém 'pela Lei 10.931'. https://normativos.bcb.gov.br/Lists/Normativos/Attachments/48005/Res_3517_v4_L.pdf indica revogação pela Res. 4.881.

Base legal / matemática. Res. CMN 4.881/2020 — https://www.bcb.gov.br/content/estabilidadefinanceira/especialnor/Resolu%C3%A7%C3%A3o4881.pdf; Res. CMN 3.516/2007 (art. 1º vigente) — https://normativos.bcb.gov.br/Lists/Normativos/Attachments/48006/Res_3516_v3_L.pdf

O que foi feito. Rodapés: quitação (CDC art. 52 §2º / Res. 3.516 art. 1º / Res. 5.004), financiamento de automóvel e imobiliário citam a Res. CMN 4.881/2020 (CET anual com juros, IOF/tarifas, seguros; TR fora), Lei 8.177/1991 para a TR; descrição do hub SIM_HUB do finauto.

Observação. grep '10.931' = 0; '3.517' continua 0 (revogada, não citada).

🧪 /tmp/test_v1057.js, /tmp/test_v1063.js · confiança do auditor: alta

Carteira, TWR × índices, estatística, opções, valuation e Análise Completa

16 achados · 2 alta · 9 média · 5 baixa
Alta CV-01

Datas da mFinance (T00:00:00Z) viram o DIA ANTERIOR no fuso do Brasil: Ibovespa sai '—' ou deslocado 1 dia e o período impresso começa num domingo

Corrigido
📍 estFetchHistoryBruto L32438 (date=Math.floor(Date.parse(c.date)/1000)); benchISO L19826; benchSerie L19836-19843; benchRodar L19870 (dias) e L19938 (retIbov); eixo X do gráfico técnico L32651-32654

O problema. A mFinance devolve 'date':'2026-08-11T00:00:00Z' (confirmado hoje via curl). estFetchHistoryBruto guarda o epoch de meia-noite UTC e benchSerie converte com benchISO(), que subtrai getTimezoneOffset(): num navegador em UTC−3 a chave vira '2026-08-10'. O Ibovespa (^BVSP) vem do Yahoo com timestamps 13:00Z/16:00Z e fica na data certa. Resultado: o calendário 'dias' da carteira está deslocado −1 dia em relação ao índice; quando o 1º pregão é segunda-feira, dias[0] é domingo e ibov[dias[0]] não existe → retIbov=null ('—'); nos demais casos o Ibov é medido de D0−1 a D1−1. O mesmo epoch alimenta o eixo X do gráfico técnico (getDate local), que rotula o candle de segunda com a data de domingo.

Evidência numérica. Recomputado com TZ=America/Sao_Paulo e dados reais baixados em 10/09/2026 (PETR4 mFinance 12m + ^BVSP Yahoo 1y), executando benchSerie/benchRodar extraídos do código: janela 6mo → app: período 01/03/2026 (domingo) a 08/09/2026, Ibovespa '—' (retIbov=null); correto (chaves UTC): 02/03 a 09/09, Ibovespa −1,94%, carteira +17,72%. Janela começando numa terça (03/03): app Ibov 0,00% (usa fechamento de 02/03 a 08/09) · correto +1,38%. Unitário: benchSerie([{date:Date.parse('2026-08-11T00:00:00Z')/1000}]) → {'2026-08-10'} ; com 13:00Z → {'2026-08-11'}. Eixo X técnico para o mesmo candle: '10/8'.

Base legal / matemática. matemática / alinhamento de datas (ECMAScript Date.parse de string ISO com 'Z' é UTC)

O que foi feito. Reproduzido com os dados reais do auditor (petr4_12m + bvsp_1y, TZ=America/Sao_Paulo): dias[0]=01/03 (domingo), retIbov=null. benchISO agora usa a data civil UTC (toISOString), nova benchEpochCivil() leva 'AAAA-MM-DDT00:00:00Z' da mFinance para MEIO-DIA UTC em estFetchHistoryBruto (corrige todos os consumidores) e o eixo X do gráfico técnico usa getUTCDate/getUTCMonth. Aviso explícito quando o Ibovespa fica sem cotação nas datas.

Observação. Depois da correção: 02/03→09/09, Ibov −1,94%, carteira +17,72% (iguais ao recálculo do auditor). Scripts em /tmp/rev2/fix_cart/ (extract_bench.js, cen.js).

🧪 /tmp/test_v1064.js · confiança do auditor: alta
Média CV-02

TWR trata cada operação do Histórico (que é um round-trip fechado: entrada+saída) como mudança de posição — 'Valor no início' vira R$ 0 e aparecem aportes fantasmas

Corrigido
📍 benchRodar L19878-19899 (compras/qtdEm) e L19904-19915 (fluxo); salvarOp L3658-3676 (toda operação salva tem entrada E saída); calcAcoes L3602-3607

O problema. salvarOp só grava operações calculadas com entrada e saída (calcAcoes exige os dois) — ou seja, trades já encerrados, com 'tipo' Compra/Venda indicando apenas a direção. benchRodar, porém, lê essas operações como compras (+q) e vendas (−q) que alteram a quantidade mantida (qtdEm = qtdFinal − movimentos posteriores). Um day trade 'Compra' de 100 PETR4 em D zera a posição antes de D e lança um aporte de 100×preço; um 'Venda' (short) de 100 dobra a posição inicial e lança uma retirada. Só quando o usuário clicou 'Transferir para Carteira' a operação representa de fato uma compra aberta.

Evidência numérica. Sintético (benchCore extraído, 10 pregões, papel sobe 100→110, carteira 100 ações desde antes): op 'Compra' 100 em 06/08 (day trade encerrado) → app: TWR +7,84%, Valor no início R$ 0,00, Aportes R$ 10.200, 7 trechos · correto: +10,00%, início R$ 10.000, aportes 0. Op 'Venda' 100 (short encerrado) → app: Valor no início R$ 20.000 (dobrado), Retiradas R$ 10.200 · correto: R$ 10.000 e 0. O TWR sai certo apenas no caso do short porque o fluxo e o valor cancelam.

Base legal / matemática. matemática (TWR: só fluxos externos reais — aportes/retiradas — entram no denominador)

O que foi feito. Reproduzido (op 'Compra' 100 do Histórico → início R$ 0, aporte fantasma 10.200). Núcleo do TWR extraído para benchCore(papeis, hsRaw, ibovHist, movs, provs); benchRodar só busca séries e desenha. Operações do Histórico NÃO entram mais (round-trip fechado). Posição = itens da Carteira (compra na dataCompra — novo campo 'Data da compra' em portAdd/editPortfolio — ou no dia local de 'added') + movimentos em dt_port_movs (novo registro: portMovGravar). Fluxo valorado pelo preço negociado (compra: preço pago; venda: preço de venda) quando plausível (≤25% do fechamento cru do dia), senão abertura do trecho; lista de fluxos considerados impressa na tela.

Observação. transferToCarteira não foi tocado (fiscal): posição NOVA transferida já entra certo (added = data da transferência); no ramo 'existente' (aumento de quantidade) NÃO há movimento gravado e o acréscimo é tratado como comprado na data do lote — pedir ao dono uma linha `portMovGravar(existente,'compra',qtd,preco)` nesse ramo. test_v1024 cena 2 codificava a regra antiga (op do Histórico = compra) e foi ajustado; test_v1037 passou a ler String(benchCore).

🧪 /tmp/test_v1064.js (cv02/cv02b) e /tmp/test_v1024.js (cena 2 reescrita para o novo modelo) · confiança do auditor: alta
Média CV-03

Papel vendido some do TWR: `filter(qtd>0)` + remoção da Carteira apagam o retorno realizado e a retirada (item deixado sem correção na v10.27)

Corrigido
📍 benchRodar L19853 (`papeis=(portfolio||[]).filter(p=>p && (parseFloat(p.qtd)||0)>0)`) e L19860-19864 (só busca histórico de `papeis`); portRemove L13731-13738; saveEditPortfolio L11085 (qtd<=0 recusado)

O problema. A Carteira não admite posição zerada (edição exige qtd>0; venda total = ✕ remover). Como benchRodar monta o universo só a partir de `portfolio`, um papel vendido no meio do período não tem histórico buscado, não entra em `valor` nem gera fluxo negativo: o ganho (ou a perda) realizado desaparece e o TWR mede só os sobreviventes (viés de sobrevivência). Vendas parciais também não geram retirada porque não há registro de movimento.

Evidência numérica. Sintético (benchCore extraído): AAAA3 100 ações a R$ 100 (flat) + BBBB3 100 ações que sobem 10→15 e são vendidas em 10/08, papel removido da Carteira → app: TWR 0,00%, Valor no início R$ 10.000, Retiradas R$ 0 · correto (TWR independente com V=[11.000 … 11.500] e retirada de 1.500 em 10/08): +4,55%, início R$ 11.000, retirada R$ 1.500. Incluindo BBBB3 com qtdFinal 0 no próprio código atual, o app já daria +3,64% (ainda subestimado porque valora o fluxo em pxIni=14 e não no preço de venda 15).

Base legal / matemática. matemática (TWR = Π V_i/(V_{i−1}+F_i) − 1 sobre TODOS os ativos que estiveram na carteira no período)

O que foi feito. Reproduzido (BBBB3 vendido/removido some: TWR 0%, retirada 0). Universo = carteira ∪ tickers com movimento na janela (filter(qtd>0) removido). portRemove grava movimento 'remocao' (q negativo, preço atual ou de compra, lote/loteData/lotePreco), saveEditPortfolio grava 'compra'/'venda' quando a quantidade muda, portVender (novo) grava 'venda'. benchCore reconstrói a compra de lotes já removidos a partir do lote do movimento e mantém o papel no valor até a data da venda; a venda vira retirada pelo preço negociado.

Observação. Cenário do auditor (AAAA3 flat + BBBB3 10→15 vendido a 15 em 10/08): início 11.000, retirada 1.500, TWR = produto dos trechos com o ganho realizado (4,94% na série linear que usei; o 4,55% do auditor depende da série sintética dele). Edição de quantidade 'sem evento' registra compra pelo preço implícito (novo total − antigo)/Δqtd.

🧪 /tmp/test_v1064.js (cv03), /tmp/test_v1068.js (movimentos) · confiança do auditor: alta
Média CV-04

TWR usa fechamento NÃO ajustado e ignora proventos: desdobramento vira −50% e carteira de pagadoras de dividendos perde ~DY contra o Ibovespa, que é índice de retorno total

Corrigido
📍 estFetchHistoryBruto L32438 (mFinance close) e L32454 (Yahoo `indicators.quote[0].close`, não `adjclose`); benchRodar L19900-19915 (valor = qtdEm×close, sem proventos)

O problema. A série usada é o 'close' cru. Num desdobramento 2:1 o preço cai pela metade e, como qtdEm aplica a quantidade FINAL da carteira a todos os dias, o valor pré-evento dobra (ou, se o usuário não atualizou a qtd, o pós-evento cai à metade) — em qualquer caso o TWR registra −50% sem nenhuma perda econômica. Dividendos/JCP recebidos (registrados em Proventos) não entram no valor, enquanto o Ibovespa é calculado pela B3 com reinvestimento de proventos.

Evidência numérica. Sintético (benchCore): SPLT3 2:1 em 10/08 (close 20 → 10), carteira com qtd 200 após o evento → app: TWR −50,00%, Valor no início R$ 4.000 · correto: 0,00% e R$ 2.000. Proventos: papel flat a R$ 100 que paga R$ 3,00 → app 0,00% vs retorno total +3,00%; para PETR4 (DY 12m 7,78% na mFinance hoje) a comparação com o Ibovespa fica ~7,8 p.p. mais pobre em 12 meses.

Base legal / matemática. matemática / metodologia do Ibovespa (índice de retorno total, B3: https://www.b3.com.br/pt_br/market-data-e-indices/indices/indices-amplos/ibovespa.htm)

O que foi feito. Reproduzido (split 2:1 → −50%; provento 3,00 → 0%). estFetchHistoryBruto guarda c.adj (Yahoo adjclose) nos candles e estFetchHistory(tk, range, {adj:true}) prefere o proxy_yahoo (ajustado a splits e proventos) para o TWR, com mFinance como reserva. benchSerie usa c.adj quando existe (benchSerie(h,true) = cru, para valorar fluxos com o fator adj/cru). Série CRUA: proventos do módulo Proventos (dividendo/JCP/rendimento/amortização, data-com na janela) entram como caixa a partir do 1º pregão após a data-com; salto > 40% entre fechamentos num fator redondo (2,3,4,5,6,8,10,1,5 e inversos, tolerância 2%) sem evento registrado é tratado como desdobramento (série reescalada) com aviso na tela. Eventos registrados (bonificação/grupamento) ajustam a quantidade histórica e são ignorados quando a série já é ajustada.

Observação. A tela informa quais papéis usam preço ajustado (retorno total, como o Ibovespa) e quais usam preço cru + proventos registrados. Risco residual: queda real de exatamente ~50%/~66,7% num dia seria lida como split (há aviso). Ações americanas: o app já misturava US$ (série) com R$ (carteira) — não alterei; para elas o fluxo usa o fechamento da série.

🧪 /tmp/test_v1064.js (cv04a/b/c) · confiança do auditor: alta
Alta CV-05

Patrimônio líquido negativo vira nota 100 'forte' em P/VP, D/PL e Dív.Líq/PL, e ROA da mFinance (56,77% na AZUL4) entra sem sanidade: empresa em recuperação judicial sai com Saúde 50 e nota geral 63 'mediano'

Corrigido
📍 acNotaInd L16755-16764 (só P/L e EV/EBITDA ≤0 são excluídos); FUND_INDS L20019/L20021 (dpl/dlpl bench 'low'); fundFundMerge L20178 (debtToEquity=dlpatrim×100), L20211 (netDebtToEquity ← dlpatrim), L20213 (só sobrescreve D/PL bruto se patrimLiq>0), L20199 (ROA: mFinance antes de lucro12m/ativo); fundChip L20038 (mesma régua na aba Fundamentos)

O problema. Para indicadores com dir='low', acNotaInd calcula t=(ruim−v)/(ruim−bom): um valor NEGATIVO (P/VP<0, D/PL<0 por PL negativo) dá t>1 → nota 100, o melhor possível. Só P/L e EV/EBITDA têm o guard 'prejuízo'. Além disso fundFundMerge prefere o ROA da mFinance, que para empresas com PL negativo devolve lixo (AZUL4: ROA 56,77%, ROE 57,23%, P/VP 254.621), em vez de recalcular LL/Ativo com o balanço do Fundamentus que já tem em mãos. D/PL 'bruto' cai silenciosamente para a dívida LÍQUIDA quando PL≤0 (L20213 vs L20178).

Evidência numérica. Caso real recomputado hoje (Fundamentus detalhes.php?papel=AZUL4 + mfinance /indicators/AZUL4, funções acNotaInd/acNotaDim/fundFundMerge extraídas): PL −5,15 bi, P/VP −0,06, Dív.Líq/PL −3,91, ROE −31,5%, LL 12m 1,62 bi, Ativo 30,78 bi. App: pvp=−0,06 → nota 100; dpl=−3,91 → 100; dlpl=−3,91 → 100; roa=56,77% (mFinance) → 100; Saúde=(0+100+100+0)/4=50 'mediano'; Valuation 100; Rentabilidade 63; NOTA GERAL 63 'mediano'. Correto (PL≤0 → P/VP, D/PL, DL/PL 'n/a'; ROA=1,62/30,78=5,27% → nota (5,27−3)/5=45): Saúde=(0+0)/2=0 'fraco', Rentabilidade=(0+100+45+50)/4=49, geral=(100+49+0+0+100)/5=50. Interpolação conferida: ROE 10% → 50; P/L 20 → 50; P/VP 1,5 → 50.

Base legal / matemática. matemática / boa prática de análise fundamentalista (P/VP e D/PL não têm significado com PL ≤ 0; ROA = LL/Ativo total)

O que foi feito. Reproduzido com azul.html/azul_mf.json do auditor (Saúde 50, geral 63). Nova acPLNegativo(f,key,v): P/VP<0 ou D/PL<0, ou PL≤0 (bookValue; sem ele, P/VP<0) → pvp/dpl/dlpl/roe recebem nota 0 (acNotaInd ganhou 3º parâmetro f) e rótulo 'PL negativo' em linhaInd; fundChip da aba Fundamentos marca '✗ PL negativo'. Div.Líq/PL negativa com PL positivo (caixa líquido) continua nota 100. fundFundMerge: ROA da mFinance só vale se |ROA|≤40% e, havendo balanço, coerente com LL/Ativo (mesmo sinal, fator <3); senão LL/Ativo. D/PL bruto com PL≤0 fica negativo (não cai mais para a dívida líquida).

Observação. AZUL4 depois: ROA 5,27% (nota 45), Saúde 0, Rentabilidade 49, Valuation 67 (P/L e EV/EBITDA 100, P/VP 0), geral 43. Divergência consciente do auditor: usei nota 0 (não null) conforme a diretriz. test_v1042 (BBAS3 ROA 0,59% da mFinance coerente com o balanço) continua passando.

🧪 /tmp/test_v1065.js · confiança do auditor: alta
Média CV-06

Emolumento B3 do tomador usa o modelo antigo (0,25% a.a. composto sobre o volume); a B3 cobra hoje % da taxa do contrato com piso e teto, pro-rata linear por dias úteis — e o IR é calculado por dias corridos aproximados (du×365/252)

Corrigido
📍 btcCalcular L28464-28477 (emol = volume×((1+emolAA/100)^(du/252)−1); diasCorridos=Math.round(du×365/252)); btcAliquotaIR L28443-28448; input btc-emol L2004-2005 (default 0,25% a.a.)

O problema. A política vigente de tarifação de empréstimo de ativos da B3 é 'i = α × Taxa do contrato', limitado por piso e teto, aplicado linearmente: LF = Q × C × (n/252) × i. Para operações eletrônicas normais os três componentes (negociação 2,0%, CCP 15,5%, TTA 2,5%) somam α = 20% da taxa do contrato, piso 2,5 bps e teto 70 bps a.a. Não há mais tarifa fixa de 0,25% a.a. nem mínimo de R$ 10 (o código também não aplica mínimo). A remuneração composta base 252 [(1+i)^(du/252)−1] está de acordo com a B3 e o IR regressivo (22,5/20/17,5/15%) está certo, mas a faixa é escolhida por um número de dias corridos ESTIMADO a partir dos dias úteis, quando o contrato tem data de início real (c.data) e poderia ter data-fim.

Evidência numérica. Recomputado (btcCalcular extraído): 1.000 ações × R$ 40 = R$ 40.000, 63 du. Taxa 5% a.a. → app emol R$ 24,98; B3 atual: i=min(max(20%×5%; 0,025%); 0,70%)=0,70% → 40.000×63/252×0,007 = R$ 70,00. Taxa 0,5% → app R$ 24,98; B3: 0,10% a.a. → R$ 10,00. Taxa 10% → app R$ 24,98; B3 R$ 70,00 (teto). Remuneração do doador: 40.000×(1,05^(63/252)−1)=R$ 490,89 (bate). IR: 124 du → app 180 dias corridos → 22,5%; 126 du → 183 → 20%: a alíquota depende do calendário real (feriados), não da razão 365/252.

Base legal / matemática. B3 — Tarifas de Empréstimo de Ativos (fórmula i = α×Taxa Contrato com Floor/Cap; n = dias úteis; LF = Q×C×(n/252)×i): https://www.b3.com.br/pt_br/produtos-e-servicos/tarifas/tarifas-de-emprestimo-de-ativos/ ; alteração dos CAPs em 14/11/2022: https://clientes.b3.com.br/w/alteracao-do-cap-da-politica-de-tarifacao-para-emprestimo-de-ativos ; IR regressivo: IN RFB 1.585/2015 art. 46 (Lei 11.033/2004 art. 1º) https://www.normaslegais.com.br/legislacao/instrucao-normativa-1585-2015.htm ; B3 FAQ (tomador paga a tarifa; IR retido na fonte): https://www.b3.com.br/pt_br/produtos-e-servicos/emprestimo-de-ativos/renda-variavel/perguntas-frequentes/

O que foi feito. Reproduzido (emol R$ 24,98 para qualquer taxa). Conferida a tabela vigente da B3 (WebFetch em b3.com.br/…/tarifas-de-emprestimo-de-ativos/: eletrônica α 20%, piso 2,5 bps, teto 70 bps; direta 20,5%/5/95; balcão 30%/5/120; LF = Q×C×(n/252)×i). Novas BTC_TARIFAS, btcTarifaB3 e btcDiasCorridos; btcCalcular mantém a assinatura (o 5º parâmetro passa a ser o tipo 'eletronica'|'direta'|'balcao'; número antigo = eletrônica) e ganhou dataIni/dataFim opcionais: IR regressivo por dias corridos reais (início + du dias úteis seg-sex, ou data-fim). Campo 'Emol. B3 (% a.a.)' e o do modal de edição viraram select de tipo; os 8 chamadores passam c.data; o card mostra dias corridos, alíquota e tarifa.

Observação. 40.000 × 63 d.u.: taxa 5% → R$ 70,00; 0,5% → 10,00; 10% → 70,00; doador bruto 490,89 (batem com o auditor). 01/03→31/08 = 183 d → 20%; →28/08 = 180 d → 22,5%. Sem calendário de feriados da B3 no app, a data-fim estimada usa seg-sex (mais preciso que 365/252); o dono pode informar data-fim explícita.

🧪 /tmp/test_v1066.js · confiança do auditor: alta
Média CV-07

Entrada de retornos em texto separada por vírgula: usuário brasileiro que digita '2,1; -1,5' tem cada número partido em dois e recebe um beta silenciosamente errado

Corrigido
📍 calcBeta L8664-8672 (`parse=s=>s.split(',').map(v=>parseFloat(v.trim())).filter(v=>!isNaN(v))`); inputs beta-ativo/beta-ibov L1924/L1928 (type=text)

O problema. O parser divide pela vírgula e depois faz parseFloat de cada pedaço, sem validar sobras. '2,1; -1,5; 3,2' vira [2, 1, 5, 2] (parseFloat('1; -1')=1). Como o mesmo erro acontece nas duas séries, os tamanhos coincidem e nenhum toast dispara — o resultado é apresentado com 4 casas decimais e interpretação ('Defensivo', 'Beta negativo').

Evidência numérica. Recomputado em Node com o parse do código: Ativo '2,1; -1,5; 3,2; -0,8; 1,9; -2,3' / Ibov '1,8; -1,2; 2,9; -0,5; 1,6; -1,9' → séries [2,1,5,2,8,9,3]×[1,1,2,5,1,1,9] (n=7) → β = −0,1959 'Beta negativo — movimento inverso'. Correto (numpy.cov, ddof=1): β = 1,1752, R² = 99,8%. Fórmulas de cov/var/R²/alfa conferidas com numpy para entrada bem formada (idênticas).

Base legal / matemática. boa prática / locale pt-BR (vírgula decimal)

O que foi feito. Reproduzido ('2,1; -1,5; …' → β −0,1959). Nova betaParseRetornos: ';' ou quebra de linha separam (vírgula decimal); ponto presente → vírgula/espaço separam; '2,1, -1,5' (vírgula+espaço) → decimal pt-BR; senão inteiros separados por vírgula. Token inválido → toast e abort. A tela mostra as séries lidas (n períodos). Rótulos dos campos orientam ponto-e-vírgula.

Observação. β = 1,1752 (numpy) nos 3 formatos. '2,1,-1,5' (sem espaço nenhum) continua ambíguo e é lido como inteiros — a lista interpretada aparece na tela para o usuário conferir.

🧪 /tmp/test_v1066.js · confiança do auditor: alta
Média CV-08

D₁ é preenchido com os dividendos dos ÚLTIMOS 12 meses (D₀) sem multiplicar por (1+g): preço justo subestimado em g

Corrigido
📍 gordonPreencher L6856 (`dEl.value=dv.div12m.toFixed(2)`; texto 'D₁ = proventos reais dos últimos 12 meses'); gordonCalcular L5463-5477 (justo=d1/(k−g)); rótulo L2362-2364 ('D₁ é o dividendo esperado nos PRÓXIMOS 12 meses')

O problema. O modelo de Gordon é P = D₁/(k−g) com D₁ = D₀×(1+g). A busca automática coloca D₀ (div12m do Yahoo) no campo D₁ e, no mesmo passo, preenche g com o CAGR dos dividendos — logo o app já sabe g e deveria projetar. Bazin também usa a média (dv.media) e o rótulo do campo diz 'próximos 12 meses'.

Evidência numérica. D₀=R$ 2,00, g=5%, k=12% → app: 2,00/0,07 = R$ 28,57 · correto: 2,10/0,07 = R$ 30,00 (−4,8%). Com g=8% (teto aplicado pelo app): 2,00/0,04 = 50,00 vs 2,16/0,04 = 54,00 (−7,4%). A margem exibida (justo−preço)/justo muda de sinal para preços entre 28,57 e 30,00.

Base legal / matemática. matemática (Gordon, 1956/1959: P₀ = D₀(1+g)/(k−g))

O que foi feito. Reproduzido (D₀ 2,00, g 5%, k 12% → 28,57). gordonPreencher define g antes de D₁ e preenche D₁ = div12m × (1+g) (ou D₀ pelo yield × (1+g)); rótulo 'D₁ = últimos 12 meses (R$ x,xx) × (1+g)'; tooltips do campo e da busca atualizados.

Observação. Testes antigos v957/v958/v959/v962/v963 codificavam D₁ = D₀ cru (21,43 / 33,25) e foram ajustados para 22,43 / 36,00 e D₁×(1+g).

🧪 /tmp/test_v1066.js · confiança do auditor: alta
Média CV-09

Índice 'E[R]' da TIEI não é uma esperança de retorno: papel valendo METADE do preço sai 'expectativa positiva' e papel no preço justo sai 'FORTEMENTE POSITIVA' (item deixado sem correção)

Corrigido
📍 tieiCalcular L5922-5931 (ganho=(vi/pr)×ps×(1+g); perda=(1−ps)×(1−pe); er=ganho−perda; rótulos er≥1/≥0,5/≥0)

O problema. O termo de ganho é um MÚLTIPLO (VI/Preço ≈ 1,x) e o de perda é uma PROBABILIDADE; a diferença não tem unidade e o limiar 'E[R] ≥ 1 = fortemente positiva' corresponde a upside zero. Um papel caro (VI < P) com P(sucesso) alta ainda dá E[R] > 0. O card 'P(sucesso) de equilíbrio' herda o mesmo problema.

Evidência numérica. Recomputado: VI = P, Ps=100%, g=0, Pe=0 → E[R] = 1,000 → 'EXPECTATIVA FORTEMENTE POSITIVA' (upside real 0%). VI = 0,5×P (ação vale metade), Ps=100% → E[R] = 0,500 → 'expectativa positiva'. VI = 1,2×P, Ps=60% → E[R] = 0,32 'fraca', enquanto a esperança de retorno seria 0,6×20% − 0,4×L.

Base legal / matemática. matemática (esperança = Σ p_i × retorno_i, todos os termos em unidade de retorno)

O que foi feito. Reproduzido (VI=P → 1,000 'FORTEMENTE POSITIVA'; VI=P/2 → 0,500 'positiva'). tieiCalcular reescrita em unidade de retorno: upside = VI/P×(1+g) − 1, severidade = 1 − P(erro), E[R] = P(sucesso)×upside − (1−P(sucesso))×severidade; limiares ≥30% forte, ≥10% positiva, ≥0 fraca, <0 negativa; P(sucesso) de equilíbrio = sev/(upside+sev) só quando há margem (senão '—' + 'sem margem de segurança'). Fórmula reescrita nos 5 textos (info-bar, hub, manual F1, dica).

Observação. É a 'teoria exclusiva' do dono — mantive o nome e a estrutura (ganho × probabilidade − custo do erro), só corrigindo as unidades. test_v964 (goldens 0,665/30,8%/1,664) ajustado para +6,5%/54,9%/+106,4%.

🧪 /tmp/test_v1066.js · confiança do auditor: media
Média CV-10

'Fórmula Mágica de Greenblatt' ranqueia por P/L + ROE (não EV/EBIT + ROIC), inclui bancos/seguradoras, ranqueia só dentro dos 120 menores P/L e o 'desempate por DY' anunciado não existe no código

Corrigido
📍 magicaBuscar L7031-7061 (colunas price_earnings_ttm/return_on_equity; range:[0,120] ordenado por P/L; score=rankPL+rankROE; sort só por score); magicaPintar L7091 (texto 'Dividend Yield … critério de desempate')

O problema. Greenblatt define earnings yield = EBIT/EV e return on capital = EBIT/(capital de giro líquido + ativo fixo líquido), exclui financeiras e utilities e ranqueia o universo inteiro. O app usa lucro líquido e ROE (ambos distorcidos por alavancagem: PL pequeno infla ROE e reduz P/L), aplica a bancos — cujo ROE alto e P/L baixo os leva ao topo — e só pede 120 papéis já ordenados por P/L, de modo que o 'ranking de ROE' é relativo a um subconjunto viesado. A TradingView expõe enterprise_value_ebit_ttm e return_on_invested_capital, usados aliás em grahamDados/mFinance no próprio app.

Evidência numérica. Universo de 3 papéis: A (P/L 5, ROE 30%, banco), B (P/L 8, ROE 20%), C (P/L 12, ROE 25%): app → A score 2 (1º). Regra de Greenblatt: A excluído (financeira); B vs C por EV/EBIT e ROIC. Com o corte range:[0,120], um papel com P/L 15 e ROIC 40% (que Greenblatt pontuaria bem) nem entra no universo. Sort estável por score sem chave secundária: empates ficam na ordem de P/L da TradingView, não por DY.

Base legal / matemática. boa prática / literatura (J. Greenblatt, 'The Little Book That Beats the Market', cap. 6 e apêndice: EBIT/EV e EBIT/(NWC+Net Fixed Assets); exclusão de financeiras e utilities)

O que foi feito. Reproduzido (colunas P/L+ROE, range 120 ordenado por P/L, sem desempate). Conferi no scanner da TradingView (curl) que 'enterprise_value_to_ebit_ttm', 'return_on_invested_capital', 'sector'/'industry' e 'dividends_yield_current' existem (o nome 'enterprise_value_ebit_ttm' sugerido pelo auditor devolve null). magicaBuscar pede o universo inteiro (range 400, sem sort) com essas colunas; nova magicaRanquear (testável) exclui Finance/bancos/seguradoras/investimento/real estate/utilities e fracionário (…F), ranqueia EV/EBIT crescente + ROIC decrescente e desempata por DY. Tabela, textos, manual e cache (v:2) atualizados.

Observação. Universo real hoje: 432 papéis com EV/EBIT>0 e ROIC>0 → 139 após exclusões/fracionário. Sem filtro de liquidez/market cap (o original também não tinha) — microcaps podem liderar; sugestão ao dono: filtro de volume médio.

🧪 /tmp/test_v1067.js · confiança do auditor: alta
Baixa CV-11

Convenções misturadas: T em dias corridos/365, 'VI diária' dividida por √252, taxa tratada como contínua e VI ignora dividend yield — prêmio, gregas, paridade put-call e convergência da VI estão corretos

Corrigido
📍 calcBS L8600 (T=dias/365); blackScholes L8567-8589 (theta/365; r contínuo); calcIV L8728 (T=dias/365), L8735 e L8775 (blackScholes(...,0,tipo) sem q), L8777 (σ/√252 'VI diária')

O problema. Recomputei blackScholes e calcIV contra scipy (norm.cdf/brentq): preço, Δ, Γ, Θ, V, ρ coincidem em 1e-5 (A&S 7.1.26) e C−P = S·e^{−qT} − K·e^{−rT} bate. Ficaram inconsistências de convenção: (a) σ anual é obtido com T em dias corridos, mas 'VI diária' usa √252 (dias úteis) — mistura de bases; (b) o campo 'Taxa livre de risco (% a.a.)' com placeholder 10,75 (DI efetiva) é usado como taxa contínua sem ln(1+r); (c) calcIV não tem campo de dividendos embora calcBS tenha, então a VI de um papel com DY alto sai deslocada; (d) rótulo 'CDI ~10,75%' fixo.

Evidência numérica. ATM S=K=100, 30 d, r=10%, prêmio 4,00 → σ = 31,40% (scipy 31,40%). VI diária app = 31,40/√252 = 1,978%; consistente com T=dias/365 seria 31,40/√365 = 1,644% (+20%); 10 dias corridos = 6 úteis → σ_252 = σ_365×√(10/365 ÷ 6/252) = 1,073×σ (7% de diferença). r: ln(1,1075)=10,21% vs 10,75% usado → Δprêmio ≈ ρ×0,54 = 0,023 em 4,07 (0,6%).

Base legal / matemática. matemática (Black-Scholes-Merton; convenção B3: dias úteis/252 e DI efetiva → r contínua = ln(1+DI))

O que foi feito. Reproduzido (T=dias/365, VI diária √252, DI usada como contínua, VI sem q). Convenção única B3: T = dias ÚTEIS/252 (rótulos e placeholders 21), r contínua = ln(1+DI) em calcBS e calcIV, theta por dia útil (/252) em blackScholes (única usuária), calcIV ganhou campo 'iv-div' (q) usado no Newton (vega = bs.vega×100), na bissecção e no resultado; a convenção é impressa na tela dos dois.

Observação. Paridade C−P = S·e^(−qT) − K·e^(−rT) conferida (1e-14); call ATM 21 d.u., σ 30%, DI 10,75% = 3,8794. test_v1034 gerava o prêmio de referência com 30/365 (0,9598) e passou a gerar com o próprio blackScholes na nova convenção.

🧪 /tmp/test_v1067.js · confiança do auditor: alta
Média CV-12

Bonificação, desdobramento e venda parcial só são representáveis editando a quantidade com o mesmo preço médio: 'Investido' infla com dinheiro que não existe e o custo médio fica errado

Corrigido
📍 saveEditPortfolio L11079-11097 (preço mantido; qtd>0 obrigatório); editPortfolio preview L11062-11066 (diff = novo total − antigo, mostrado como variação do investimento); portRender L13903-13905 (inv=qtd×preco); transferToCarteira L3936-3941

O problema. Não há evento de bonificação/split/venda: o único caminho é ✎ editar a quantidade. O modal mantém `preco` e recalcula 'Total investido' = qtd×preco, então uma bonificação de 10% vira +10% de capital investido (e o ganho/perda cai artificialmente); um split 2:1 dobra o investido se só a quantidade for alterada; uma venda parcial mantém o PM corretamente mas o resultado realizado não fica registrado em lugar nenhum (nem no Histórico, nem no IR). Complementa FIS-21 (PM sem custos e venda somada como compra).

Evidência numérica. 100 ações a R$ 20 (investido R$ 2.000). Bonificação 10% → usuário edita qtd 110 → app: investido 110×20 = R$ 2.200 (+R$ 200 fantasma), PM 20,00 · correto: investido R$ 2.000, PM = 2.000/110 = R$ 18,18 (Lei 9.249/95 art. 10 §1º: custo das ações bonificadas = parcela do lucro/reserva capitalizado; se a companhia não atribuir custo, o PM cai). Split 2:1 → qtd 200 → app investido R$ 4.000 vs correto R$ 2.000 / PM 10,00.

Base legal / matemática. Lei 9.249/1995 art. 10 §1º (custo de aquisição das ações recebidas em bonificação) https://www.planalto.gov.br/ccivil_03/leis/l9249.htm ; IN RFB 1.585/2015 (custo médio ponderado das ações, capítulo de renda variável) https://www.normaslegais.com.br/legislacao/instrucao-normativa-1585-2015.htm ; matemática do preço médio

O que foi feito. Reproduzido (editar qtd 110 → investido 2.200). No modal de edição: seção 'Desdobramento/bonificação · Venda parcial'. portEventoAplicar(p, fator, custo) (pura) + portEvento: qtd×fator, PM÷fator, investido igual; com custo atribuído (Lei 9.249/95 art. 10 §1º) PM = (qtd×PM + novas×custo)/(qtd+novas); grava movimento sem dinheiro. portVender(q, preço, data): baixa a quantidade mantendo o PM, resultado = q×(preço−PM), grava movimento 'venda' (encerra a posição se vender tudo). Preview da edição avisa quando só a quantidade mudou. Campo 'Data da compra' em portAdd (dataCompra) e no modal; saveEditPortfolio preserva added/us/precoUSD/cambioCompra.

Observação. decisao_dono parcial: a venda registrada aqui NÃO vai para o Histórico/IR (salvarOp é fiscal e ficou intocado) — o modal avisa 'para o IR, lance no Histórico'. Recomendo ao dono ligar portVender ao salvarOp (venda × PM) numa versão fiscal.

🧪 /tmp/test_v1068.js · confiança do auditor: alta
Baixa CV-13

'No período' e '%' do gráfico da carteira somam aportes como se fossem ganho

Corrigido
📍 cartGrafRender L23067-23071 (varAbs=d1−d0; varPct=varAbs/d0); cartHistRegistrar L22952-22966 (grava v e inv por dia)

O problema. A variação do período compara o valor de mercado final com o inicial sem descontar a mudança do investido (pts[i].inv está gravada e não é usada). Comprar um papel novo aparece como rentabilidade.

Evidência numérica. Dia 1: v=10.000, inv=10.000; dia 2: compra de R$ 10.000 de outro papel, preços parados → v=20.000, inv=20.000 → app 'No período +R$ 10.000 (+100,00%)' · correto: 0,00% (ou +R$ 0). Com Δinv disponível: varAbs = (d1−dInv1) − (d0−dInv0) = 0.

Base legal / matemática. matemática

O que foi feito. Reproduzido (10.000→20.000 com aporte de 10.000 → '+R$ 10.000 (+100%)'). cartGrafRender: varAbs = (v_fim − inv_fim) − (v_ini − inv_ini), card renomeado 'Resultado no período' com o aporte/retirada do período mostrado separado ('líquido de aportes (+R$ x)').

Observação. Não reaproveitei o TWR aqui (a série diária do gráfico não tem fluxos datados por papel).

🧪 /tmp/test_v1068.js · confiança do auditor: alta
Baixa CV-14

'Retorno esperado' é UM caminho de Math.random (muda a cada clique); estRunRisco alinha ativo × Ibov por posição (não por data), usa CDI fixo de 10,75% e σ populacional

Corrigido
📍 runSimulador L19540-19573 (simular() com Math.random; esperado=simular(acerto,n) exibido como 'Retorno esperado' e 'Capital final (esperado)'); estRunRisco L34140-34146 (closesI.slice(-closesA.length)), L34165-34170 (cdiDia fixo)

O problema. O simulador rotula como 'esperado' o resultado de uma única trajetória aleatória; o valor correto é a média (ou mediana) de muitas simulações ou a fórmula fechada (capital0×(1+E)^n para risco composto). No risco, a série do Ibovespa é cortada pelo tamanho da série do ativo e casada índice a índice: hoje mFinance (248 pregões desde 11/09/2025) e Yahoo ^BVSP (250 desde 09/09/2025) coincidem após o corte, mas qualquer feriado/lacuna diferente entre fontes desloca todos os retornos e derruba o beta; o Sharpe usa CDI hardcoded quando bcbTaxas() já existe no app.

Evidência numérica. runSimulador com acerto 50%, R:R 2, risco 2% fixo, n=100: E[lucro por trade]=0,02×10.000×(0,5×2−0,5)=R$ 100 → esperado ≈ R$ 20.000 (+100%); duas execuções consecutivas do código podem mostrar +60% e +140%. Alinhamento: retA e retI de comprimentos iguais só porque min(248,250) corta o início do Ibov — verificado com as datas reais (nenhuma lacuna interna hoje).

Base legal / matemática. matemática / estatística

O que foi feito. Reproduzido (88%/70%/100% em 3 cliques). runSimulador: cada cenário = média de R=300 caminhos (curva média, capital final médio, drawdown mediano), percentis 10/50/90, % de ruína e fórmula fechada (fixo: C0×(1+n·r·EM); composto: C0×(1+r·EM)^n) na tela. estRunRisco: ativo × Ibov casados por data civil (benchSerie + interseção; reserva posicional se <30 datas) e CDI anualizado dos últimos 12 meses do BCB via bcbTaxas (série 4391) com reserva 10,75%; fonte impressa no card do Sharpe.

Observação. Ativo idêntico ao Ibov com um pregão faltando: beta 1,000 (antes 0,357 com série ruidosa). Média de 300 caminhos tem erro-padrão ~2 p.p. no cenário do teste — o valor ainda varia levemente entre cliques (por design de Monte Carlo), a fórmula fechada ao lado é fixa.

🧪 /tmp/test_v1069.js · confiança do auditor: alta
Baixa CV-15

Foto mensal do patrimônio usa mês em UTC (após 21h do último dia cai no mês seguinte) e a chave semanal de Dow quebra a semana na virada do ano

Corrigido
📍 patHistSnapshot L25849 (`new Date().toISOString().slice(0,7)`); rentRealRender L17348-17360 (usa esses meses com _acumula); dowAnalisar L7191 (chave = getFullYear()+'-'+semana epoch)

O problema. toISOString é UTC: em 31/08 às 22:00 (BRT) a foto é gravada como '2025-09' e a 'Rentabilidade real × CDI' compara com o CDI de um mês a mais/menos. Em Dow, a semana Seg 29/12/2025–Sex 02/01/2026 gera duas chaves ('2025-2922' e '2026-2922'), criando uma barra semanal extra que pode virar topo/fundo falso.

Evidência numérica. new Date('2025-08-31T22:00:00-03:00').toISOString().slice(0,7) = '2025-09'. Chave semanal: Math.floor((Date.parse('2025-12-29')/864e5+4)/7) = Math.floor((Date.parse('2026-01-02')/864e5+4)/7) mas getFullYear difere → 2 barras.

Base legal / matemática. matemática / calendário

O que foi feito. Reproduzido (31/08 22h BRT → '2026-09'; 2 barras na virada do ano). patHistSnapshot e fechamentoAuto (mesma chave dt_pat_hist) usam ano-mês LOCAL; chave semanal de dowAnalisar = Math.floor((t/86400000+4)/7) sem o ano.

Observação. fechamentoAuto continua usando toISOString em 'dt_fech_ultimo' (só evita repetir no mês) — irrelevante para o cálculo.

🧪 /tmp/test_v1069.js · confiança do auditor: alta
Baixa CV-16

Recomputados e corretos: Black-Scholes e gregas, paridade put-call, VI (Newton+bisseção), Beta/R²/alfa (entrada bem formada), Graham, Bazin (teto), VPA screener, Pearson dos pares, régua de Dow, _acumula com mesAntes, TWR com aporte transferido, guard do risco/retorno, ativo sem preço no 1º dia

Sem correção necessária
📍 blackScholes L8567; calcIV L8720; calcBeta L8664; grahamVI L5734; bazinCalcular L5447; vpaPintar L7000; parCorrelacao L5514; dowAnalisar régua L7213-7232; benchRodar L19944-19955; calcRisco L3700; benchRodar L19874-19878

O problema. Itens da lista de correções anteriores e demais fórmulas do escopo conferidos com numpy/scipy e séries sintéticas; nenhuma regressão encontrada.

Evidência numérica. BS ATM 100/100/30d/10%/σ32%: app 4,06756 vs scipy 4,06756; Δ 0,5539; Θ −0,0745; vega 0,1133; ρ 0,0422. Put 35/40/45d/14,65%/q5%: 4,79379 vs 4,79379. VI de 5 casos (inclusive prêmio 0,001 OTM) convergiu igual ao brentq (≤3e-6). C−P = 3,35769 = S·e^{−qT}−K·e^{−rT}. Beta numpy 1,17518 = app. TWR com aporte transferido (op com flag de compra real): trechos batem com Π V_i/(V_{i−1}+F_i).

Base legal / matemática. matemática

O que foi feito. Item informativo do auditor (verificações sem achado). Nada alterado; blackScholes/calcIV/calcBeta/Graham/Bazin/VPA/Pearson/Dow/_acumula continuam cobertos pelos testes antigos (v1034, v1037, v940, v952…) que rodei no arquivo corrigido.

Observação. Regressões pré-existentes não relacionadas: test_v1046 e test_v1049 falham IGUAL no WORK918.html original (dependem de _lastAluguel/baseAcao de outros corretores); test_v1025 idem; test_v1026 é sensível a carga (passa sozinho).

🧪 — · confiança do auditor: alta

Código e segurança (app, service worker, PHP, licença, loja)

17 achados · 5 alta · 8 média · 4 baixa
Alta SEG-01

XSS armazenado massivo: dados do usuário e de terceiros vão crus para innerHTML em ~40 pontos

Corrigido
📍 renderGastos L20756, renderReceitas L21130, renderPatrimonio L18837, portRender L13888, renderHistory L4237, wlRender L33xx, fornRender L34534, clientesRender L25161-25189, renderProventos L13123, renderCot L5232, renderNotaCorretagem L14151, renderTesouro L18219, renderSites L9315, renderCustomSites L9358, renderPriceAlerts L16072, metasRender/L25993, recibos L27957

O problema. As funções de render montam HTML com template strings interpolando campos de texto sem escapar (`${g.desc}`, `${a.nome}`, `${f.nome}`, `${c.nome}`, `${w.nota}`, `${s.nome}` etc.). O app NÃO tem uma função de escape geral aplicada no render (só há um `esc` local em L22610 usado num único lugar). Todo campo digitado é salvo em localStorage e reexibido cru; um valor com `<img src=x onerror=...>` executa JavaScript ao renderizar a aba.

Evidência numérica. Prova com Playwright (/opt/pw-browsers, file:///home/claude/WORK918.html): semeei localStorage com `<img src=x onerror=window.__xss.push(campo)>` em dezenas de campos e naveguei pelas abas. Payloads que EXECUTARAM: gastos.desc, gastos.obs, receitas.descricao/fonte/obs, patrimonio.nome/obs, portfolio.ticker, operations.data/tipo/ticker, watchlist.ticker/nota, proventos.ticker, agenda.desc, fiis.ticker, price_alert.ticker, metas.nome, lic.nome, forn.nome/email/banco/categoria/contato/endereco/pix/obs/nome-de-arquivo, cli.nome/doc/seg/email/fone/end/obs/contato/pix/nome-de-arquivo, sites.nome. Ou seja: qualquer descrição de gasto/receita, nota de watchlist, nome de fornecedor/cliente ou nome de arquivo anexado dispara execução. O script injetado roda com acesso total ao localStorage — que contém licença, dt_root_hash, dt_brapi_token, dt_ejs_pubkey e o token do Telegram — permitindo exfiltração dos dados financeiros e dos segredos.

Base legal / matemática. OWASP A03:2021 Injection / Stored XSS; boa prática (DOM sanitization)

O que foi feito. Helpers globais esc(s) (& < > " '), escJs(s) (literal JSON já escapado p/ onclick="fn(${escJs(x)})") e escUrl(u) (só http/https em href) no 1º bloco <script>. Aplicados em ~330 interpolações/concatenações de dados do usuário em innerHTML/insertAdjacentHTML/document.write: renderGastos/Receitas/Patrimonio, portRender, renderHistory, wlRender, fornRender/fornOpenModal/fornViewFile, cliRender/cliOpenModal, renderProventos, renderCot, renderNotaCorretagem, renderTesouro, renderSites/CustomSites, renderPriceAlerts, metasRender, renderAgenda, fiisRender, btcRender, recibos (reciboAbrir/reciboImprimir), simcart, TIEI/estudos, Telegram (tgRender e mensagens HTML), cadastro (cadUsuarioAbrir, cadRender, root-modal), licCarimboHtml/licSeloAtualizar, anexos, relatórios impressos. toast() e inlineConfirm() passaram a escapar a mensagem (toastSafe: só <b>/<br> passam). Locais `const esc=` que colidiam foram renomeados (escKey/escEl/escCsv/escTxt).

Observação. Antes: 58 campos executavam <img onerror> (gastos, receitas, patrimônio, carteira, histórico, watchlist, proventos, agenda, FIIs, alertas, metas, sites, fornecedores, clientes, recibos, BTC, licença, root_name, arquivos). Depois: 0 (19 payloads literais na tela). Não escapei ids (Date.now()/'rc_...') usados em onclick — um backup malicioso com id contendo aspas ainda quebraria o JS do onclick (sem execução de tags); e não revisei 100% dos ~500 innerHTML estáticos: o teste cobre as telas com dados do usuário.

🧪 /tmp/test_v1071.js · confiança do auditor: alta
Alta SEG-02

XSS via resposta de rede: nome da empresa/cotação de proxy, Brapi e Fundamentus injetam HTML

Corrigido
📍 mmdCotProxy L7280 (shortName→cotQuotes), renderCot L5280 (${d.shortName||d.longName}), fetchTape/renderTape L16888, acAnalisar L16829 e L20185 (fd.empresa→longName), L20104 tvInfo innerHTML, L32910

O problema. O campo `nome`/`shortName`/`empresa` vindo de proxy_cot.php, proxy_fund.php (Fundamentus), Brapi e mFinance é colocado cru em innerHTML nos cards de cotação, na fita (tape), na Análise Completa e no autocomplete. Essas fontes são de terceiros e o proxy usa CORS `*` + cache em diretório temporário compartilhado (ver SEG-11), então um upstream comprometido, um MITM ou um vizinho de hospedagem que envenene o cache injeta script em todos os clientes.

Evidência numérica. Playwright interceptando as URLs e devolvendo `nome/shortName/empresa` = `<img src=x onerror=...>`: executaram cot.nome (renderCot), brapi.shortName (fita) e fund.empresa (Análise Completa → setSe('longName')). Confirmado window.__xss populado ao abrir Cotações/Carteira e ao rodar acAnalisar('PETR4').

Base legal / matemática. OWASP A03:2021; boa prática

O que foi feito. shortName/longName/nome/empresa/setor vindos de proxy_cot, proxy_fund/Fundamentus, Brapi, mFinance, Yahoo, FIPE, RSS (title/link) e lista de aparelhos do servidor passam por esc()/escUrl() em renderCot, tape, acAnalisar (info por textContent + esc no cabeçalho), fundamentos (fundEscolher/autocomplete), VPA/Fórmula Mágica (x.nome/x.setor), TIEI (d.nome), commodities/FIPE.

Observação. Defesa em profundidade no PHP (strip_tags) já feita por fix_site (SEG-11).

🧪 /tmp/test_v1071.js (mock de proxy_cot nome, Brapi shortName/longName, Fundamentus empresa/setor) · confiança do auditor: alta
Alta SEG-03

LIC_SECRET embarcado no app permite gerar licenças vitalícias grátis para qualquer e-mail

Corrigido (servidor)
📍 WORK918.html L25652 const LIC_SECRET='...'; licChave L25654; config.php define('LIC_SECRET',...) L (idêntico)

O problema. A chave é HMAC-truncado `sha256(email|validade|LIC_SECRET)`. O LIC_SECRET está em texto puro no HTML distribuído (confirmado idêntico em WORK918.html, out1032/my_money_dre_v1032.html e no payload base64 do index.html) e é o MESMO segredo do servidor. Qualquer pessoa que abra o arquivo (todo comprador tem o .html; está também no download público) extrai o segredo e gera chaves vitalícias válidas para qualquer e-mail — que o próprio servidor (ativar.php, licenca_status.php, baixar.php) aceita como legítimas, pois compartilham o segredo.

Evidência numérica. grep confirma `LIC_SECRET='K7f2...'` presente no HTML distribuído; sha256 do valor no app == sha256 do valor em config.php (comparados sem imprimir). Com o segredo, `sha256(email+'||'+seg).slice(0,20)` em grupos de 4 = licença vitalícia aceita. Ou seja: pirataria trivial + download livre dos instaladores (baixar.php usa a mesma verificação).

Base legal / matemática. Boa prática (nunca embarcar segredo de verificação no cliente). É esperado que licenciamento client-side seja contornável; o problema é o servidor confiar no MESMO segredo exposto.

O que foi feito. Lado app: licServidorCheck agora consulta licenca_status.php por POST e trata {status:'bloqueada', motivo:'reembolso'|'nao_emitida'} com a mensagem certa (licMsgBloqueio); ativarRotina trata {situacao:'bloqueada'} de ativar.php igual (remove dt_licenca + licAbrirAtivacao(msg)).

Observação. LIC_SECRET intocado.

🧪 /tmp/test_v1076.js · confiança do auditor: alta
Alta SEG-04

Plano oculto 'teste' de R$ 1,00 é comprável por qualquer um e gera licença vitalícia real

Corrigido (servidor)
📍 config.php $PLANOS['teste']=>1.00; comprar.php L (if(!isset($PLANOS[$plano])) …); webhook.php L (gera licChave independente do plano)

O problema. comprar.php aceita qualquer `plano` que exista em $PLANOS, e $PLANOS inclui 'teste' => R$ 1,00 (comentado como 'só para você testar, não aparece na loja'). Mas a validação é só `isset($PLANOS[$plano])`, então `comprar.php?plano=teste` cria a preferência de R$ 1,00 no Mercado Pago. Ao pagar R$ 1, o webhook gera e envia a mesma chave provisória→vitalícia de qualquer plano (a geração não depende do plano/preço).

Evidência numérica. config.php: `'teste' => ['nome'=>'... teste de integração','preco'=>1.00]`. comprar.php: `if (!isset($PLANOS[$plano])) { header('Location: index.html'); exit; }` — 'teste' passa. webhook.php gera `licChave($email, licValidadeProvisoria())` e depois enviar_definitivas.php manda a vitalícia — sem checar o plano. Resultado: produto de R$ 297–597 vendido por R$ 1 a quem adivinhar/vazar `plano=teste`.

Base legal / matemática. Boa prática / prejuízo financeiro direto

O que foi feito. Servidor (decisao_dono registrada por fix_site). Nada no app.

🧪 - · confiança do auditor: alta
Média SEG-05

Assinatura x-signature do webhook desativada (MP_WEBHOOK_SECRET vazio) — endpoint aceita notificações não verificadas

Corrigido (servidor)
📍 config.php L13 define('MP_WEBHOOK_SECRET',''); mpAssinaturaValida() L; webhook.php L (if(!mpAssinaturaValida(...)))

O problema. O código de validação da assinatura está correto conforme a doc oficial do MP (manifesto `id:<data.id>;request-id:<x-request-id>;ts:<ts>;` com HMAC-SHA256 e hash_equals). PORÉM MP_WEBHOOK_SECRET está vazio em config.php e `mpAssinaturaValida` faz `if (MP_WEBHOOK_SECRET==='') return true;` — logo NENHUMA notificação é verificada. A liberação real ainda depende de consultar a API (/v1/payments) com token e exigir status 'approved' + idempotência, então forjar uma liberação exige um payment id realmente aprovado; mesmo assim o endpoint fica exposto a floods/replays e perde a defesa em profundidade que o próprio código implementa.

Evidência numérica.

Base legal / matemática. Mercado Pago — Validação de origem de notificações (x-signature). https://www.mercadopago.com.br/developers/pt/docs/your-integrations/notifications/webhooks (seção 'validar origem'). Formato do manifesto e HMAC-SHA256 conferem com o código.

O que foi feito. Servidor. Nada no app.

🧪 - · confiança do auditor: alta
Alta SEG-06

SW serve proxy_cot/proxy_fund/proxy_yahoo em cache-first PARA SEMPRE — cotações e fundamentos congelam

Corrigido
📍 out1032/sw.js (VER='mmdre-v10.32'); fetch handler: navigate/.html = network-first, TODO o resto same-origin = cache-first sem expiração

O problema. O handler de fetch só trata como 'fresco' (network-first) navegações e URLs terminando em .html. Todo o resto same-origin cai no ramo cache-first: `caches.match(req).then(r=>r||fetch(...))`. Os proxies (proxy_cot.php, proxy_fund.php, proxy_yahoo.php) ficam sob /app/ (mesmo escopo do SW, /app/) e terminam em .php → são interceptados e, na primeira resposta, gravados no cache e servidos de lá indefinidamente, ignorando o Cache-Control: max-age=120 que os PHP mandam. O usuário passa a ver preço/indicadores congelados na primeira leitura até limpar o cache do navegador.

Evidência numérica. Playwright servindo /app/index.html + /app/proxy_cot.php via HTTP local, SW registrado em /app/sw.js: o servidor recebeu 1 requisição; o app leu `em` = [1,1,1,1] em 4 fetches subsequentes de proxy_cot.php?t=PETR4 → resposta velha servida do cache do SW, nunca revalidada.

Base legal / matemática. Boa prática (correção de dados exibidos)

O que foi feito. gera_index.js passou a GERAR o sw.js (não copia mais out1023/sw.js): navegações/HTML = rede primeiro com reserva do cache; manifest/ícones/sw = cache primeiro; qualquer *.php, path com 'proxy_' ou URL com query = rede sempre (cache:'no-store'), cache só como socorro offline (503 JSON se nem isso); outros arquivos same-origin = rede primeiro; hosts externos não são interceptados.

Observação. Rodar gera_index.sh para publicar; o test_v1072 gera o sw.js pelo gerador a cada execução.

🧪 /tmp/test_v1072.js (servidor HTTP local + SW real: 4 leituras → em=1,2,3,4; com o sw.js antigo do out1032 → em=1,1,1,1, reproduzido) · confiança do auditor: alta
Média SEG-07

Estouro de quota (5 MB) descarta a gravação: dado adicionado some no reload; add lança exceção não tratada

Corrigido
📍 gfAdicionar L20675, patAdd L18636, rcAdd, wlAdd etc. — `localStorage.setItem(...)` sem try; interceptor global L27771 relança o erro

O problema. O interceptor global de setItem (L27771) mostra um toast 'ARMAZENAMENTO CHEIO' MAS RELANÇA o erro (`throw err`). As funções de cadastro fazem `lista.unshift(x); localStorage.setItem(...)` — quando a quota estoura, o item já entrou no array em memória, o setItem lança, a exceção sobe e interrompe a função (o toast de sucesso nem aparece), e no próximo reload o dado somem porque não foi persistido. Em condição de quota quase cheia, adições silenciosamente não persistem.

Evidência numérica. Playwright: enchi o localStorage até QuotaExceededError (~5,24 MB) e chamei gfAdicionar() com um gasto válido → resultado: `thrown: QuotaExceededError`, nenhum toast, `salvo_no_storage: 0`, item só na memória. Após reload, o gasto desaparece.

Base legal / matemática. Boa prática (durabilidade de dados)

O que foi feito. lsSet(k,v): try/catch, nunca lança; em QuotaExceeded chama lsQuotaAviso (toast de 60 s + faixa fixa #ls-quota-faixa) e devolve false; a faixa some na 1ª gravação que volta a funcionar. TODAS as 232 chamadas localStorage.setItem do app viraram lsSet (o dado fica em memória). backupAplicarDados/anexos e cliSave, que dependiam do throw, passaram a testar o retorno. Interceptor global não duplica o toast quando a gravação veio pelo lsSet.

Observação. Original reproduzido: QuotaExceededError lançado por gfAdicionar.

🧪 /tmp/test_v1073.js (storage cheio até 200 bytes → gfAdicionar não lança, item em memória, toast+faixa, lsSet false; libera espaço → lsSet true e gasto persistido) · confiança do auditor: alta
Média SEG-08

Sem listener de 'storage': duas abas sobrescrevem dados uma da outra (last-write-wins) com perda total

Corrigido
📍 grep 'addEventListener(\'storage\'' = 0 ocorrências; todos os módulos mantêm cópia em memória (gfGastos, portfolio…) e regravam o array inteiro

O problema. Cada aba carrega o array em memória no boot e, a cada alteração, grava o array COMPLETO de volta. Não há sincronização entre abas (nenhum handler do evento 'storage'). Abrir o app em duas abas e editar em ambas faz a última gravação sobrescrever tudo que a outra aba adicionou — perda silenciosa de dados.

Evidência numérica. Playwright, mesmo contexto (localStorage compartilhado), duas abas: aba A adiciona 'Aluguel', aba B (que carregou antes) adiciona 'Luz' → localStorage final = ['Luz (aba B)']; o 'Aluguel' da aba A foi perdido.

Base legal / matemática. Boa prática (consistência)

O que foi feito. Listener 'storage' no último bloco <script>: para 20 chaves principais (dt_gastos, dt_receitas, dt_patrimonio, dt_portfolio, dt_operations, dt_watchlist, dt_proventos, dt_agenda(+pagos), dt_fiis(+prov), dt_fornecedores, dt_clientes, dt_metas, dt_price_alerts, dt_btc, dt_recibos, dt_sites, dt_tesouro_hist, dt_cripto) recarrega a variável em memória via lsJson e redesenha; toast informativo 1×/10 s.

Observação. Edição simultânea do MESMO item ainda é last-write-wins (aceitável para app mono-usuário).

🧪 /tmp/test_v1074.js (duas abas: Aluguel/Luz/Internet — original perdia 'Aluguel') · confiança do auditor: alta
Média SEG-09

Chaves de dados inicializadas em escopo de módulo com JSON.parse sem try/catch — um valor corrompido derruba módulos inteiros

Corrigido
📍 L20661 let gfGastos=JSON.parse(localStorage.getItem('dt_gastos')||'[]'); L18530 patAtivos; L32355 _watchlist; L20920 rcReceitas; L18396 tdHistorico; L17604 criptoSlugs; L15233 _rssFloatActive; L26xx dt_bcb/dt_taxa_td

O problema. Enquanto os loaders do boot (L4623, L5107) usam try/catch, as declarações de nível de módulo com `let X = JSON.parse(...)` NÃO têm try/catch. Se a chave estiver corrompida (ex.: gravação truncada por quota, backup mal formado, edição manual), o JSON.parse lança durante a avaliação do bloco <script>, abortando esse bloco inteiro e deixando funções e variáveis subsequentes indefinidas — o módulo quebra em cascata.

Evidência numérica. Playwright: com dt_gastos='{corrompido' e licença ativa, o bloco lança 'Expected property name…' e depois: renderGastos → 'Cannot access gfMesAtual before initialization', renderReceitas → 'rcReceitas…', renderDashboard → 'gfGastos is not defined', backupMontar → 'APP_VERSION is not defined', renderAgenda → '_agMes…', dreRender → 'clDados…'. Ou seja, uma única chave corrompida derruba dashboard, DRE, agenda, backup e mais. Mesmo teste com dt_patrimonio e dt_watchlist quebra os respectivos módulos.

Base legal / matemática. Boa prática (robustez)

O que foi feito. lsJson(k,def): JSON.parse em try/catch; se corrompida grava <k>_corrompido, remove a chave e devolve o padrão (console.warn + window._lsCorrompidas). 138 ocorrências de JSON.parse(localStorage.getItem('k')||'…') trocadas por lsJson (todas as declarações de nível de módulo).

Observação. Original reproduzido: 8 módulos quebrados em cascata.

🧪 /tmp/test_v1073.js (dt_gastos/dt_patrimonio/dt_watchlist/dt_receitas/dt_fornecedores corrompidas → todos os módulos renderizam, arrays vazios, chaves em *_corrompido) · confiança do auditor: alta
Média SEG-10

Sem CSP com script-src no app e 1144 handlers inline — o XSS de SEG-01/02 não tem nenhuma mitigação de camada

Corrigido
📍 NOVO/app/.htaccess (CSP só frame-ancestors/object-src/base-uri); WORK918.html: 1144 atributos onclick/oninput/onchange/onpaste/onerror inline

O problema. O .htaccess do app define CSP apenas `frame-ancestors 'self'; object-src 'none'; base-uri 'self'` — sem default-src/script-src. Logo, script injetado (SEG-01/02) executa sem obstáculo e pode fazer fetch para qualquer origem (exfiltração). Adicionar uma CSP eficaz é inviável hoje porque o app depende de 1144 handlers de evento inline e de vários innerHTML — 'unsafe-inline' seria obrigatório, anulando a proteção.

Evidência numérica. grep: 0 ocorrências de script-src/default-src no header CSP; 1144 ocorrências de eventos inline em WORK918.html. XSS provado em SEG-01 executou livremente e conseguiu ler todo o localStorage.

Base legal / matemática. OWASP Secure Headers; boa prática

O que foi feito. <meta http-equiv=Content-Security-Policy> no <head>: default-src 'self'; script-src 'self' 'unsafe-inline' cdnjs jsdelivr; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; connect-src 'self' data: blob: (o app faz fetch(dataURL) para converter anexos em Blob) + os 23 hosts usados em fetch (proxy próprio, Brapi, Yahoo, mFinance, Fundamentus, TradingView, Binance, CoinGecko, Coinbase, Kraken, BCB×3, ViaCEP, FIPE, Telegram, rss2json, EmailJS, Tesouro, allorigins, codetabs, corsproxy, CDNs); frame-src 'self' data: blob:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; form-action 'self'. Removidos os 3 usos de eval/new Function (acao de CAD_HUB/IMP_HUB/PP_PASSOS viraram funções) → sem 'unsafe-eval'.

Observação. RISCO: qualquer host novo em fetch precisa entrar no connect-src (o teste lista os que faltarem). Em produção, o servidor também pode mandar o mesmo header (fix_site .htaccess). Regressão achada na suíte (test_v1030: anexos → Telegram) corrigida acrescentando data:/blob: ao connect-src.

🧪 /tmp/test_v1075.js (CSP ativa em file://; CDN carrega; fetch a proxy/Brapi/BCB passa; fetch a host fora da lista bloqueado; 0 violações navegando 12 abas) · confiança do auditor: alta
Média SEG-11

Cache dos proxies em sys_get_temp_dir compartilhado com nome previsível — envenenamento/leitura entre tenants e sem cache negativo (DoS)

Corrigido (servidor)
📍 proxy_cot.php mmd_cache_arq() L; proxy_fund.php $cf=sys_get_temp_dir().'/mmdre_fund_'.$tk; proxy_yahoo.php $cacheFile em sys_get_temp_dir()

O problema. Em hospedagem compartilhada (HostGator), sys_get_temp_dir() costuma ser /tmp comum a vários sites. Os arquivos têm nome previsível (mmdre_cot_PETR4.json etc.) e são gravados/lidos sem verificação de dono nem HMAC. Um vizinho de hospedagem (ou processo com acesso a /tmp) pode gravar mmdre_cot_PETR4.json com JSON malicioso (ex.: nome com `<img onerror>`, que casa com SEG-02) ou preços falsos, servidos a todos os clientes. Além disso, mmd_cotacao() NÃO grava cache negativo: ticker de formato válido mas inexistente (ZZZZ9) dispara 2–4 buscas upstream (mFinance+Yahoo, timeouts de 5–7 s) a CADA requisição → derruba justamente a cota que o proxy existe para proteger e trava workers PHP.

Evidência numérica. Leitura do código: proxy_cot grava `@file_put_contents($arq,...)` só em sucesso; no caminho de falha retorna null sem escrever. Regex do ticker `^[\^]?[A-Z0-9.\-]{1,12}$` aceita infinitos inexistentes. Nome de cache = ticker cru no path de /tmp compartilhado.

Base legal / matemática. OWASP (DoS / cache poisoning); boa prática

O que foi feito. Servidor.

🧪 - · confiança do auditor: media
Média SEG-12

Cofre na nuvem: cifra derivada de segredo público, ausência de config não bloqueia, e PII em CSV/logs (LGPD)

Corrigido (servidor)
📍 backup_nuvem.php mmd_licenca_ok() (`if($segredo==='') return true`); WORK918.html bkDerivar L4872 (chave AES = PBKDF2('MMDRE|'+licença)); config.php licRegistrar → licencas/licencas.csv; downloads.log; webhook.log

O problema. (a) O backup 'cifrado' é derivado da CHAVE DE LICENÇA, que por sua vez é derivável de e-mail+LIC_SECRET (SEG-03). Um atacante que extrai LIC_SECRET gera a licença da vítima, chama backup_nuvem.php?acao=baixar com email+chave, baixa o blob e o decifra (PBKDF2 da chave que ele mesmo gerou) — a confidencialidade do cofre depende de um segredo público. (b) Se config.php não carregar, mmd_licenca_ok retorna true e o cofre aceita qualquer um. (c) licencas.csv guarda e-mail+plano+paymentId em claro; downloads.log e webhook.log guardam e-mails e chaves — dados pessoais (LGPD) sem minimização; protegidos só por .htaccess (Require all denied), sem cifra em repouso.

Evidência numérica. backup_nuvem.php: `function mmd_licenca_ok($email,$chave,$segredo){ if($segredo==='') return true; ...}`. bkDerivar usa 'MMDRE|'+chave (chave = licença determinística). licencas.csv linha = data;email;plano;chave;paymentId.

Base legal / matemática. LGPD Lei 13.709/2018 arts. 6 (necessidade/segurança) e 46 (medidas de segurança). https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm

O que foi feito. Servidor. No app o Cofre já cifra com senha do usuário (cofreCifrar); adicionado o aviso 'Concordo e enviar' (JUR-02).

Observação. bkDerivar do 'Backup Seguro' local continua derivando da licença — decisao_dono (fora do escopo desta rodada).

🧪 /tmp/test_v1076.js · confiança do auditor: media
Média SEG-13

autoteste.php, enviar_definitivas.php e diag em query string protegidos só por segredos fracos na URL

Corrigido (servidor)
📍 autoteste.php (?chave=t4m9x2k7), enviar_definitivas.php (?chave=d7k4p2w9), comprar.php (?diag=660882b629cfc720), ativar.php relatorio (chave_admin fallback '660882b629cfc720')

O problema. Ferramentas de teste/admin seguem no pacote de produção, protegidas por segredos curtos passados em query string — que vazam em logs de acesso, histórico, Referer e são força-brutáveis sem rate limit. autoteste.php cria preferências no MP, pode disparar e-mail (&email=1) e grava resultado_teste.html público. enviar_definitivas.php dispara o envio em massa das chaves definitivas. ativar.php?acao=relatorio&chave_admin=660882b629cfc720 (fallback hardcoded, pois DIAG_CHAVE não está definido em config.php) devolve TODOS os e-mails de clientes e contagem de aparelhos — enumeração de base de clientes.

Evidência numérica. grep: DIAG_CHAVE não existe em config.php → ativar.php usa o literal '660882b629cfc720'. autoteste.php linha `if(($_GET['chave']??'')!=='t4m9x2k7')`. Mesmo literal em comprar.php?diag. Sem throttling em nenhum.

Base legal / matemática. OWASP A01 Broken Access Control; boa prática

O que foi feito. Servidor.

🧪 - · confiança do auditor: alta
Baixa SEG-14

licenca_status.php e ativar.php sem rate limit; revogação por reembolso facilmente evitável

Corrigido (servidor)
📍 licenca_status.php (GET e,c); ativar.php; WORK918.html licServidorCheck L25718 (1×/dia, só bloqueia em status:'bloqueada')

O problema. licenca_status.php responde a qualquer par e-mail+chave (formato válido) sem throttling, permitindo sondar quais chaves estão bloqueadas por reembolso. Do lado do cliente, licServidorCheck só bloqueia com resposta EXPLÍCITA 'bloqueada' e engole qualquer falha de rede como 'ok' — quem reembolsou e bloqueia o domínio no /etc/hosts (ou fica offline) mantém o app funcionando. É uma decisão de produto tolerante, mas registro: a revogação é best-effort e contornável.

Evidência numérica. licenca_status.php não tem sleep/limite; licServidorCheck: `catch(e){}` e só age em j.status==='bloqueada'. Combinado com SEG-06 (se estivesse no escopo do SW seria cacheado), a revogação é frágil.

Base legal / matemática. Boa prática

O que foi feito. Servidor; lado app: POST em licenca_status e tratamento de 'bloqueada' por motivo (ver SEG-03).

🧪 /tmp/test_v1076.js · confiança do auditor: media
Baixa SEG-15

Análise Completa faz cotação, Fundamentus e Brapi em série; fetchT sem retry

Corrigido
📍 acAnalisar L16827-16839 (await mmdCotProxy → await fundFundamentus → await fetchT Brapi); fetchT L9570

O problema. acAnalisar encadeia três awaits independentes em série (cotação do proxy, fundamentos do proxy Fundamentus/mFinance, e Brapi complementar). Como não dependem um do outro, poderiam rodar em paralelo com Promise.allSettled, cortando a latência ~2–3×. fetchT implementa timeout mas nenhuma retentativa; as fontes gratuitas falham com frequência.

Evidência numérica. Leitura: os três `try{ await ... }catch{}` são sequenciais em acAnalisar; cada um com timeout de 9–12 s → pior caso ~30 s.

Base legal / matemática. Boa prática (desempenho)

O que foi feito. acAnalisar: mmdCotProxy, fundFundamentus e Brapi disparam juntos com Promise.allSettled; a mescla mantém a precedência antiga (proxy → Fundamentus/mFinance → Brapi só preenche o que faltou).

Observação. Sem retry no fetchT (não era exigido).

🧪 /tmp/test_v1076.js (3 fontes de 1,5 s → 3,0 s em vez de ≥4,5 s) + /tmp/test_v1040.js continua passando · confiança do auditor: alta
Baixa SEG-16

Ofuscação base64 do index não é proteção; sem hash de integridade publicado e cache do Cloudflare pode servir versão velha

Corrigido (servidor)
📍 gera_index.js (payload base64 + document.write); out1032/index.html; versao.html; LEIA-ME (purge Cloudflare)

O problema. O index.html carrega o app inteiro como base64 e faz document.write — isso é só ofuscação/empacotamento, trivialmente reversível (atob) e NÃO protege o código nem o LIC_SECRET embutido (SEG-03). O build confere sha256(payload)==sha256(fonte) internamente (bom), mas esse hash não é publicado para o cliente verificar integridade (nem SRI nos <script> de CDN). O próprio LEIA-ME admite que /comprar/ com barra final serve cópia velha até 'Purge Everything' no Cloudflare — HTML sensível (preços, links) pode ficar stale.

Evidência numérica. gera_index.js: BOOT faz `document.write(atob(_C.join('')))`. <script src=cdnjs .../pdf.min.js> e jsdelivr email.min.js sem atributo integrity (SRI). LEIA-ME: 'Sem isso, alguns endereços continuam servindo a copia ANTIGA'.

Base legal / matemática. Boa prática (integridade / SRI)

O que foi feito. SHA-256 publicado por fix_site. SRI nos <script> de CDN não foi adicionado.

Observação. decisao_dono: SRI exige fixar hash de pdf.min.js/email.min.js (jsdelivr @4 é tag flutuante → quebraria ao atualizar); fixar versão exata antes.

🧪 - · confiança do auditor: alta
Baixa SEG-17

Senha root padrão '101010' semeada e importável via backup FULL (dt_root_hash restaurável)

Corrigido
📍 L14668/L13096 seed dt_root_hash = sha256('101010'); backupAplicarDados L5085 (backup.full restaura dt_root_hash e dt_root_name)

O problema. O app semeia a senha de administrador '101010' (sha256 confirmado = 2a057642...). Quem não trocar fica com senha trivial. Além disso, um backup 'FULL' malicioso restaura dt_root_hash (senha do atacante) e dt_root_name (vetor de XSS — SEG-01), pois BACKUP_EXCLUIR é ignorado quando backup.full é true.

Evidência numérica. echo -n 101010 | sha256sum == valor semeado. backupAplicarDados: `.filter(([k])=>backup.full || !BACKUP_EXCLUIR.includes(k))`.

Base legal / matemática. Boa prática

O que foi feito. A senha de fábrica (hash de 101010) deixa de ser aceita como senha root: licCadastrarSenhaAbrir(obrigatorio, depois) sem 'Deixar para depois' é chamada na ativação, ao entrar pelo splash com a senha de fábrica e em rootAuth (a ação continua depois de cadastrar). Reset RESETAR mantém-se, mas leva à troca obrigatória no próximo acesso. Backup FULL: dt_root_hash só é restaurado se o usuário marcar a caixa 'Substituir também a senha root' no modal de restauração (backup._restaurarSenha); demais chaves restauram normalmente.

Observação. A pré-configuração ainda semeia o hash de fábrica (compatibilidade com rootIsConfigured), mas ele nunca serve para autenticar ações.

🧪 /tmp/test_v1076.js · confiança do auditor: alta

Jurídico — LGPD, CDC / Decreto 7.962, CVM, Marco Civil, tributário do vendedor

24 achados · 6 alta · 12 média · 6 baixa
Alta JUR-01

Política e Termos afirmam que o app 'não transmite nenhum dado', mas ativar.php e licenca_status.php recebem e-mail, chave, id/nome do aparelho e hash do IP todo dia

Corrigido
📍 WORK918.html ativarConsultar L4366-4375, ativarRotina L4377-4392, licServidorCheck L25711-25730; /tmp/NOVO/comprar/ativar.php L98-130; politica_privacidade.html L26, L33; termos embutidos termosTexto L24629

O problema. O app envia por POST a ativar.php (1× por dia, em silêncio) email, chave, aparelho (UUID persistente em localStorage) e nome do aparelho, e por GET a licenca_status.php email+chave. O servidor grava por licença um JSON com e-mail, data de criação, 'visto' e um hash de 12 hex do IP salgado com a chave (ativar.php L119-124) — dado pessoal pseudonimizado (art. 13 §4º LGPD), reversível por força bruta no espaço IPv4. A Política (§2, 'Elas nunca são transmitidas para nós', 'não exigimos cadastro on-line') e os Termos embutidos (cl. 11: 'O Software não coleta nem transmite dados pessoais ao Licenciante') afirmam o contrário. O mesmo vale para o resumo em destaque da Política (L26).

Evidência numérica. Entrada: usuário ativa a licença e abre o app no dia seguinte → app: POST https://mymoneydre.com.br/comprar/ativar.php body='acao=situacao&email=x@y.com&chave=AAAA-...&aparelho=<uuid>&nome=Windows · computador' (L4369-4371) e GET licenca_status.php?e=x@y.com&c=AAAA-... (L25718-25719); servidor grava mmdre_aparelhos/<sha256>.json com {email, aparelhos:{uuid:{nome, criado, visto, ip:hash12}}} · correto: a Política deve listar esse tratamento (finalidade antipirataria/contagem de aparelhos, base legal, retenção), e o app deve informar na tela de ativação.

Base legal / matemática. LGPD art. 6º VI (transparência), art. 9º I-VII, art. 13 §4º, art. 18 VII; Marco Civil art. 7º VIII e IX — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm ; https://www.planalto.gov.br/ccivil_03/_ato2011-2014/2014/lei/l12965.htm

O que foi feito. Política §2/§3 descrevem a validação diária (e-mail, chave, id/nome do aparelho, código do IP, finalidade, base legal art. 7º V/IX, retenção 120 dias/12 meses); ativar.php expurga aparelhos sem uso >365 dias e apaga registros de licenças revogadas (mmdRetencao); texto para os termos embutidos e tela de ativação em fix_site_textos_app.md.

Observação. Itens (3) e (4) são no app — textos entregues.

🧪 /tmp/test_v1070_site.js · confiança do auditor: alta
Alta JUR-02

Cofre na nuvem armazena backup cifrado + metadados no servidor do vendedor, mas a Política diz 'Não temos servidor de dados' e 'Não temos acesso a nenhum backup'; sem prazo de retenção e sem expurgo

Corrigido
📍 /tmp/NOVO/app/backup_nuvem.php L78-131; WORK918.html COFRE_URL L4423, cofrePost L4457-4463; politica_privacidade.html L33, L61; comprar/index.html L126, L148, L164

O problema. O plano Completo vende 'Cofre cifrado na nuvem + backup automático'. backup_nuvem.php grava por licença um blob AES-GCM (cifrado no navegador, ok) de até 8 MB, a versão anterior (.prev) e um JSON de metadados {em, versao, itens, bytes, aparelho}; a pasta pode cair em sys_get_temp_dir() (L82) compartilhado. Não há prazo de retenção nem exclusão automática ao reembolso/cancelamento; a exclusão depende de o titular chamar acao=apagar. A Política afirma que não existe servidor de dados (§2 L33) e que 'Não temos acesso a nenhum backup' (§6 L61) — o vendedor tem posse do blob (dado pessoal cifrado continua sendo dado pessoal; o operador é a Clínica do Micro/HostGator).

Evidência numérica. Entrada: usuário do plano Completo ativa o cofre → app envia POST backup_nuvem.php acao=enviar chave, email, blob(base64), versao, itens, aparelho (L4457) → servidor grava <sha256(email|chave)>.blob/.json/.prev em <docroot>/../mmdre_cofres (L80-91, L99-108) · correto: Política deve descrever cofre (o que é guardado, que não pode ser lido sem a senha, base legal, retenção, como apagar) — hoje diz o oposto.

Base legal / matemática. LGPD art. 5º I e III (dado pessoal, anonimização ≠ cifra), art. 9º II (duração), art. 15-16 (término do tratamento), art. 46 — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm ; Marco Civil art. 7º VIII e X — https://www.planalto.gov.br/ccivil_03/_ato2011-2014/2014/lei/l12965.htm

O que foi feito. Política §8 (Cofre) com o texto proposto; §2 deixou de dizer 'não temos servidor de dados'; backup_nuvem sem /tmp; expurgo 12 meses e reembolsados; erros.log só com hash.

Observação. Aviso 'Concordo e enviar' no app: texto entregue.

🧪 harness + /tmp/test_v1070_site.js · confiança do auditor: alta
Alta JUR-03

Páginas de venda carregam GTM + GA4 + Google Ads (antes do aceite) enquanto a Política afirma 'não usamos cookies de rastreamento' e 'não integramos ferramentas de análise'

Corrigido
📍 /tmp/NOVO/index.html L4-30; /tmp/NOVO/comprar/index.html L4-30, L86-87, L183-209; politica_privacidade.html L33, L42, L47

O problema. As duas páginas de venda injetam gtm.js (GTM-NLKKXLLN), gtag/js (G-0HT1DNMZ7G) e o pixel de conversão Google Ads (AW-18403637646) no <head>, antes de qualquer interação. O Consent Mode inicia 'denied', mas o script é carregado e o Google recebe pings sem cookie (IP, URL, user-agent) mesmo sem aceite — logo a frase do banner 'sem o seu aceite, nada é coletado' (comprar/index.html L188-189) é falsa. A Política de Privacidade não menciona Google, GTM, GA4, Ads, cookies de terceiros, nem transferência internacional (Google LLC/EUA), e afirma o contrário (§2 L33 'não usamos cookies de rastreamento, não integramos ferramentas de análise de comportamento'; §3 L42 'sem cookies próprios de rastreamento'). O aceite fica em localStorage sem data/versão (não há prova do consentimento — art. 8º §2º).

Evidência numérica. Entrada: visitante abre https://mymoneydre.com.br/ sem clicar em nada → navegador faz GET https://www.googletagmanager.com/gtm.js?id=GTM-NLKKXLLN e /gtag/js?id=G-0HT1DNMZ7G (L19-24) → Google recebe IP + URL. Política: 0 ocorrências de 'Google', 'GTM', 'Analytics', 'Ads'. Correto: só carregar tags após aceite OU declarar com precisão os pings sem cookie; listar Google na seção 'Com quem compartilhamos' e na seção de cookies.

Base legal / matemática. LGPD art. 7º I e IX, art. 8º §§1º-2º (ônus da prova do consentimento), art. 9º V, art. 33 (transferência internacional) — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm ; Res. CD/ANPD 19/2024 (transferências internacionais) — https://www.gov.br/anpd/pt-br/acesso-a-informacao/institucional/atos-normativos/regulamentacoes_anpd/resolucao-cd-anpd-no-19-de-23-de-agosto-de-2024 ; Guia Orientativo ANPD 'Cookies e proteção de dados pessoais' (2022) — https://www.gov.br/anpd/pt-br/documentos-e-publicacoes/guia-orientativo-cookies-e-protecao-de-dados-pessoais.pdf ; CDC art. 37 §1º (informação falsa no banner) — https://www.planalto.gov.br/ccivil_03/leis/l8078compilado.htm

O que foi feito. index.html e comprar/index.html: scripts GTM/gtag/Ads saíram do <head>; carregarGoogle() só roda em mmdreCookies(true) ou no boot se localStorage.mmdre_cookies for JSON {aceito:true, em:ISO, versaoPolitica:'2026-09-10'} (valores antigos 'aceito'/'recusado' são ignorados e o aviso volta); removido o <noscript><iframe> do GTM; banner com o texto proposto; link 'Cookies' no rodapé (mmdreCookiesReabrir) apaga a escolha, reexibe o aviso e manda consent denied; Política §6 (cookies _ga/_gcl, prazos, Google LLC/EUA, Res. 19/2024) e §4.

🧪 /tmp/test_v1070_site.js (rota interceptada: 0 requisições a googletagmanager antes do aceite; gtm.js após Aceitar; JSON com 'em') · confiança do auditor: alta
Alta JUR-04

Cláusula 3 dos Termos condiciona o reembolso a 'análise de uso' e pode recusá-lo — nula; e o 'aviso prévio e claro' que ela invoca não existe na página de compra

Corrigido
📍 /tmp/NOVO/comprar/termos_uso.html L30-36; comprar.php L29-41; obrigado.php L63; config.php licEnviarEmail L199-205; termos embutidos L24616

O problema. A cláusula 3 diz que, se a chave for ativada e houver 'utilização regular do software', 'o reembolso poderá ser recusado, nos termos admitidos para conteúdo digital já consumido', e que 'Este aviso é apresentado na página de compra, antes do pagamento'. Não há no direito brasileiro exceção ao art. 49 para conteúdo digital consumido (a exceção existe na Diretiva 2011/83/UE, não no CDC); o art. 49 é norma de ordem pública e a cláusula viola o art. 51, I e XV. Além disso, o aviso de comprar.php (L30-38), a página obrigado.php (L63) e o e-mail (L201-205) prometem 'devolução integral' e 'chave expira sozinha' SEM qualquer condição de uso — a oferta vincula (art. 30) e prevalece a interpretação mais favorável (art. 47). Os Termos embutidos no app (cl. 8, L24616) também garantem 7 dias 'com devolução integral' sem condição — documentos contraditórios.

Evidência numérica. Entrada: cliente ativa a chave no dia 1, lança operações e pede reembolso no dia 5 → Termos cl. 3: reembolso 'poderá ser recusado' · comprar.php L32-33 mostrado antes do pagamento: 'Você tem 7 dias para se arrepender, com devolução integral (art. 49 do CDC)' · correto: devolução integral e imediata, monetariamente atualizada (art. 49 §ún.), sem análise de uso.

Base legal / matemática. CDC art. 49 caput e parágrafo único, art. 30, art. 47, art. 51 I, IV e XV — https://www.planalto.gov.br/ccivil_03/leis/l8078compilado.htm ; Decreto 7.962/2013 art. 5º — https://www.planalto.gov.br/ccivil_03/_ato2011-2014/2013/decreto/d7962.htm

O que foi feito. termos_uso.html cl. 3 reescrita: 7 dias do recebimento da chave, integral, monetariamente atualizado, sem análise de uso, prorroga em fim de semana, formulário/e-mail/WhatsApp, estorno em 1 dia útil; comprar.php, obrigado.php e baixar.php com a mesma regra; arrependimento.php criado.

Observação. E-mail de entrega (config.php) ainda diz '7 dias' sem detalhe — texto pronto no LEIA-ME item K.

🧪 /tmp/test_v1070_site.js (grep 'análise de uso|poderá ser recusado' = 0) · confiança do auditor: alta
Alta JUR-05

TIEI 2.0 emite 'Decisão: Compra Forte / Compra Moderada / Evitar' com sizing ('100% do alvo'), ranking de 20 ações e 'preço justo' com 'margem de segurança' — conteúdo típico de relatório de análise; o disclaimer atual (0,56rem) não basta

Decisão do dono
📍 WORK918.html tiei2Calcular L6564-6588 (dec/alvo), tieiEstudoSalvar L6388, ranking em lote L6677-6705, Fórmula Mágica L7056-7091, Graham L5760-5765, help L12201; termos embutidos cl. 6 L24608; comprar/index.html L167, L179

O problema. O app produz, para um valor mobiliário específico, rótulos imperativos ('Compra Forte', 'Evitar') e alocação ('100% do alvo', '50% do alvo', '25% do alvo'), ranking top-20 por score, preço justo 'abaixo do valor intrínseco ✓' — em modo AUTO as 7 notas são preenchidas pelo próprio app a partir de dados de mercado (L6726-6760), com pouco poder de parametrização do usuário. A CVM (Ofício-Circular SIN 2/2019, reproduzido em gov.br/investidor) entende que 'serviços de estratégias padronizadas por meio de sistemas automatizados ou algoritmos... com o objetivo de indicar oportunidades e momentos apropriados para realizar operações com valores mobiliários' são análise de valores mobiliários, privativa de analista credenciado (Res. CVM 20 art. 1º §1º e art. 2º). A Clínica do Micro não é analista credenciada nem consultora (Res. 19). O aviso 'Estudo educacional... não é recomendação' em 0,56rem (≈9 px) não descaracteriza o conteúdo. O site diz 'Não somos casa de análise e não recomendamos operações' (L167/L553) — contradito pela tela.

Evidência numérica. Entrada: TIEI 2.0 com PETR4, modo AUTO → app: «Decisão TIEI: Compra Forte — 100% do alvo» (L6568, L6584) e toast 'score 82,0 · Compra Forte' (L6397); ranking 'Top 20' ordenado por score (L6678). Correto (Res. CVM 20 art. 1º §1º): texto/estudo sobre valor mobiliário específico que possa influenciar decisão = relatório de análise; sem analista credenciado → não emitir recomendação.

Base legal / matemática. Res. CVM 20/2021 arts. 1º §§1º-2º, 2º, 3º (consolidada c/ Res. 179/23 e 216/24) — https://conteudo.cvm.gov.br/export/sites/cvm/legislacao/resolucoes/anexos/001/resol020consolid.pdf ; Ofício-Circular CVM/SIN 2/2019 — https://www.gov.br/cvm/pt-br/assuntos/noticias/2019-1/esclarecimentos-sobre-atividade-de-analista-de-valores-mobiliarios-26d13f699fff4cef8f1d3e808a6453dd e https://www.gov.br/investidor/pt-br/investir/como-investir/profissionais-do-mercado/robos-de-investimentos ; Res. CVM 19/2021 art. 1º (consultoria = orientação individualizada — não é o caso, mas o rótulo de alocação se aproxima) — https://conteudo.cvm.gov.br/export/sites/cvm/legislacao/resolucoes/anexos/001/resol019consolid.pdf ; Lei 6.385/76 art. 27 (atividade privativa)

O que foi feito. Site: FAQ/rodapé passam a dizer que a Clínica do Micro não é analista/consultora CVM; termos cl. 5 com o aviso completo. Telas do app não são minhas — texto do aviso, rótulos neutros e gate 'Entendi' em fix_site_textos_app.md §5.

Observação. Implementação no app pelo outro corretor.

🧪 /tmp/test_v1070_site.js · confiança do auditor: alta
Alta JUR-06

Fluxo de venda não emite NFS-e (LC 116 item 1.05 / ISS BH) embora a página prometa 'nota fiscal'; comprovante do Mercado Pago não substitui a nota

Decisão do dono
📍 /tmp/NOVO/comprar/webhook.php L59-64; obrigado.php L15-19; config.php licRegistrar L178-181; index.html L171, L478, L554; comprar/index.html L103, L169

O problema. Ao aprovar o pagamento, o webhook gera a chave, grava licencas.csv (data;email;plano;chave;paymentId) e envia o e-mail — nada emite NFS-e nem coleta CPF/nome do tomador. Licenciamento de software é serviço do item 1.05 da LC 116/2003 (ISS — STF ADI 1945/5659), com incidência no município do prestador (BH). Belo Horizonte aderiu ao Emissor Nacional NFS-e com obrigatoriedade escalonada até jan/2026 para todas as PJ prestadoras (inclusive Simples). O comprovante do Mercado Pago é documento de pagamento, não documento fiscal (Lei 9.609 art. 9º §ún. cita o 'documento fiscal' como prova de licença regular). A página anuncia 'CNPJ desde 2001 · nota fiscal' e 'Nota fiscal emitida' (art. 30/37 CDC se não ocorrer). Além disso, a Lei 12.741/2012 exige no documento fiscal o valor aproximado dos tributos, e, no ano-teste de 2026 da reforma (LC 214/2025 art. 348), a CBS 0,9%/IBS 0,1% devem ser destacados no documento fiscal para dispensa do recolhimento.

Evidência numérica. Entrada: venda de R$ 397,00 aprovada → código: linha em licencas.csv + e-mail (webhook.php L60-62); nenhuma chamada a API de NFS-e; nenhum campo CPF. Correto: NFS-e Nacional (Emissor Nacional / API) com tomador (nome, CPF opcional para PF), código de serviço 1.05, base R$ 397,00, ISS BH (alíquota conforme Lei Municipal 8.725/2003 / Simples), tributos aproximados (Lei 12.741) e destaque CBS/IBS 2026. Snapshot: comprar/licencas/ vazio e nenhuma NF gerada por código — confiança média (emissão manual pode existir fora do código).

Base legal / matemática. LC 116/2003 lista anexa item 1.05 — https://www.planalto.gov.br/ccivil_03/leis/lcp/lcp116.htm ; STF ADI 1945 e 5659 (2021); PBH — adesão ao Emissor Nacional NFS-e (obrigatoriedade até jan/2026) — https://prefeitura.pbh.gov.br/noticias/belo-horizonte-adere-ao-emissor-nacional-de-nota-fiscal-de-servico-eletronica ; Lei 12.741/2012 art. 1º — https://www.planalto.gov.br/ccivil_03/_ato2011-2014/2012/lei/l12741.htm ; LC 214/2025 art. 348 (2026: IBS 0,1% + CBS 0,9%, dispensa condicionada às obrigações acessórias) — https://www.planalto.gov.br/ccivil_03/leis/lcp/lcp214.htm ; Lei 9.609 art. 9º §ún. — https://www.planalto.gov.br/ccivil_03/leis/l9609.htm ; CDC art. 6º III e 37 — https://www.planalto.gov.br/ccivil_03/leis/l8078compilado.htm

O que foi feito. Site deixou de prometer nota fiscal automática: selo 'CNPJ desde 2001 · nota fiscal' → 'Empresa registrada desde 2001 · CNPJ'; card e FAQ → 'nota fiscal de serviço mediante solicitação'; termos cl. 9 explica. Nenhuma emissão implementada.

Observação. Recomendação (LEIA-ME item G): licenciamento = serviço item 1.05 LC 116 (ISS/BH); emitir NFS-e por venda no Emissor Nacional NFS-e (gov.br/nfse) ou integrar a API, enviar em até 5 dias úteis; confirmar com o contador Simples/alíquota e destaque CBS/IBS 2026 (LC 214 art. 348); coletar nome/CPF opcional no checkout.

🧪 /tmp/test_v1070_site.js · confiança do auditor: media
Média JUR-07

Existem dois contratos diferentes (termos_uso.html e 'Termos de Licença v1.1' embutidos no app) com regras conflitantes; cláusula 9 limita responsabilidade ao valor pago e exclui lucros cessantes (art. 51, I)

Parcial
📍 /tmp/NOVO/comprar/termos_uso.html L21-51; WORK918.html termosTexto L24577-24638 (cl. 4 L24596, cl. 8 L24616, cl. 9 L24618, cl. 11 L24627-24632, cl. 12 L24634)

O problema. O comprador aceita os termos do site em comprar.php e, ao ativar, é obrigado a aceitar outro termo (v1.1) com: contagem do prazo de arrependimento 'da entrega da chave' (site: 'da contratação'); 'cancelamento imediato da licença, sem devolução de valores' (cl. 4); limitação da responsabilidade total ao valor pago e exclusão de lucros cessantes (cl. 9 — vedada pelo art. 51, I, para consumidor pessoa física); afirmação de que não há transmissão de dados (cl. 11 — ver JUR-01); atualizações 'pelo período contratado' vs site '12 primeiros meses'. Contratos contraditórios violam o dever de informação prévia (art. 46) e se interpretam contra o fornecedor (art. 47). O termo do app não é disponibilizado antes da compra (só após ativação).

Evidência numérica. termos_uso.html L32: '7 dias corridos, contados da contratação' × app L24616: '7 dias corridos da entrega da chave' × CDC art. 49: 'assinatura ou recebimento' (o que for posterior, na prática o recebimento). App cl. 9 L24618: 'responsabilidade total do Licenciante limita-se ao valor pago' → art. 51, I: nula para consumidor PF.

Base legal / matemática. CDC art. 46, 47, 49, 51 I e IV, art. 54 §4º — https://www.planalto.gov.br/ccivil_03/leis/l8078compilado.htm ; Lei 9.609 art. 9º (contrato de licença) — https://www.planalto.gov.br/ccivil_03/leis/l9609.htm

O que foi feito. Termo único datado (versão 2026-09-10) publicado em termos_uso.html, linkado no checkout, no obrigado.php e no baixar.php; cl. 6 (responsabilidade) e cl. 4 (vedações com direito de defesa) reescritas; versão dos termos gravada em licencas/aceites.csv e no metadata da preferência MP. Texto para o app (termosTexto) entregue.

Observação. Coluna extra no licencas.csv não adicionada (formato lido por 5 scripts) — usei aceites.csv separado.

🧪 /tmp/test_v1070_site.js · confiança do auditor: alta
Média JUR-08

enviar_definitivas.php envia a chave vitalícia exatamente 7×24h após o registro — antes do fim legal do prazo de arrependimento (CC art. 132: exclui o dia do começo; prorroga se cair em dia não útil); e licenca_status.php nunca bloqueia a chave definitiva reembolsada

Corrigido
📍 /tmp/NOVO/comprar/config.php L56-57; enviar_definitivas.php L25, L36-41; licenca_status.php L30

O problema. LIC_DIAS_DEFINITIVA=7 e corte=time()-7*86400: chave registrada em 03/09 10:00 recebe a definitiva no cron de 10/09 10:01, mas o consumidor pode desistir até 10/09 23:59 (dia 7 contado excluindo o dia do recebimento) e, se o 7º dia cair em sábado/domingo, até o próximo dia útil. Se o reembolso vier depois do envio da definitiva, licenca_status.php ignora linhas 'DEF-' (L30) e o app continua liberado para sempre — prejuízo do vendedor e incentivo a litígio. A provisória de 10 dias também expira antes de um estorno pedido na sexta e processado na segunda.

Evidência numérica. Recomputado: registro 03/09/2026 10:00 → cron envia a partir de 10/09 10:00; fim legal do prazo 10/09 23:59:59 → janela de 14 h em que a definitiva já saiu mas a desistência é válida. Registro sexta 04/09 15:00 → fim legal 11/09 23:59 (sexta), provisória expira 14/09.

Base legal / matemática. CDC art. 49 — https://www.planalto.gov.br/ccivil_03/leis/l8078compilado.htm ; Código Civil art. 132 caput e §1º (contagem de prazos) — https://www.planalto.gov.br/ccivil_03/leis/2002/l10406compilada.htm

O que foi feito. comum.php licDataLiberacaoDefinitiva(): 7 dias contados do fim do dia do registro (CC art. 132), prorroga sáb/dom para segunda, +1 dia de margem → envio a partir de D+9 (ex.: 03/09 → 12/09; 05/09 sáb → 16/09); enviar_definitivas.php usa isso em vez de time()-7*86400; provisória passa a valer no mínimo 14 dias (licValidadeProvisoriaSegura) em webhook/obrigado; licenca_status.php e baixar.php bloqueiam DEF- reembolsada (licRevogada + pidBase sem prefixo).

Observação. config.php LIC_DIAS_PROVISORIA=10 fica inconsistente só no comentário — LEIA-ME pede trocar para 14.

🧪 harness CLI: venda D-8 aguarda, D-10 envia, reembolsada bloqueia; DEF- reembolsada depois → bloqueada · confiança do auditor: alta
Média JUR-09

Etapa de contratação (comprar.php) identifica o fornecedor só por nome e CNPJ; endereço sem CEP/loja no rodapé; e-mail de entrega não traz o contrato nem link para os Termos; prazo de entrega para PIX/boleto não informado

Corrigido
📍 /tmp/NOVO/comprar/comprar.php L43; index.html L564; comprar/index.html L177; config.php licEnviarEmail L210-249; comprar/index.html L134, L159

O problema. Art. 2º do Decreto exige, em local de destaque, nome empresarial, CNPJ, endereço físico e eletrônico, preço total e condições. Na tela onde o contrato é firmado (comprar.php) consta apenas 'Clínica do Micro Ltda · CNPJ' (L43); o rodapé do site traz 'Av. Getúlio Vargas, 54 — Funcionários, Belo Horizonte/MG' sem CEP e sem 'Lojas 3, 4 e 5' (a Política, L29, tem o endereço completo). Art. 4º III: o contrato deve ser disponibilizado 'em meio que permita sua conservação e reprodução, imediatamente após a contratação' — o e-mail de entrega não anexa nem linka os Termos. 'Chave enviada na hora' (L134) — para PIX é quase imediato, boleto compensa em 1-3 dias úteis (obrigado.php L65-66 admite) — informar o prazo na oferta (art. 31 CDC).

Evidência numérica. comprar.php L43: 'Pagamento processado pelo Mercado Pago (PIX, cartão ou boleto)<br>Clínica do Micro Ltda · CNPJ 04.275.304/0001-12' — sem endereço físico/eletrônico. E-mail (L210-225): 0 ocorrências de 'termos' ou 'contrato'.

Base legal / matemática. Decreto 7.962/2013 art. 2º I-V, art. 4º I-III — https://www.planalto.gov.br/ccivil_03/_ato2011-2014/2013/decreto/d7962.htm ; CDC art. 31 e 6º III — https://www.planalto.gov.br/ccivil_03/leis/l8078compilado.htm

O que foi feito. comprar.php: bloco 'Fornecedor' com razão social, CNPJ, endereço com lojas/CEP, e-mail, telefone, preço total, meio de pagamento e prazo de entrega (PIX/cartão minutos; boleto 3 dias úteis); rodapés de index.html/comprar/index.html com Lojas 3, 4 e 5 e CEP; textos de 'chave enviada na hora' trocados; obrigado.php e baixar.php linkam Termos (versão) e Política.

Observação. E-mail de entrega com link do contrato/PDF: licEnviarEmail está no config.php → texto pronto (LEIA-ME K), decisao_dono.

🧪 /tmp/test_v1070_site.js · confiança do auditor: alta
Média JUR-10

'O escolhido por 8 em 10', selo 'MAIS VENDIDO', 'laudo oficial' de auditoria com '100% das regras tributárias' e '0 correções' — sem base comprovável e contraditos pela própria versao.html (19 correções depois)

Corrigido
📍 /tmp/NOVO/index.html L130, L523; comprar/index.html L65, L143; versao.html L39, L76; WORK918.html help 'Auditoria dos Cálculos' L12241-12252; /tmp/NOVO/auditoria/laudo_tributario.pdf

O problema. Art. 36 §ún.: o fornecedor deve manter os dados fáticos que embasam a mensagem publicitária. A loja é recente (config.php de 26/08/2026; licencas/ vazio no snapshot) — '8 em 10' e 'MAIS VENDIDO' não têm base demonstrável. O 'Laudo de Auditoria Tributária' é autoemitido (cabeçalho 'Clínica do Micro Ltda'), sem auditor independente identificado, e afirma '49/49 verificações conferem', '100% das regras tributárias vigentes', '0 correções necessárias' (v9.22, 27/08/2026); a própria versao.html L39 registra que a v10.27 trouxe '19 correções' com DARF que 'cobrava imposto indevido', e a auditoria atual ainda encontra itens não corrigidos (TIEI, ITCMD progressivo, IOF por tranche — ver relatórios FIS/INV). Chamar de 'laudo oficial' (versao.html L76) e 'homologação' induz a erro sobre qualidade/garantia (art. 37 §1º).

Evidência numérica. index.html L523: 'O escolhido por 8 em 10' (sem fonte, sem período, sem n); laudo PDF p.1: '100% das regras tributárias vigentes para o ano-calendário 2026' × versao.html L39: 'v10.27 — revisão geral de cálculos (19 correções)... antes cobrava imposto indevido'.

Base legal / matemática. CDC art. 36 parágrafo único, art. 37 §§1º e 3º, art. 31 — https://www.planalto.gov.br/ccivil_03/leis/l8078compilado.htm

O que foi feito. Removidos 'O escolhido por 8 em 10' (→ 'Indicado para quem opera na B3') e selo 'MAIS VENDIDO' (→ 'RECOMENDADO') nas duas páginas; versao.html: 'laudo oficial'/'Homologação' → 'relatório interno de verificação (não é auditoria independente...)'; termos cl. 5 idem.

Observação. PDF auditoria/laudo_tributario.pdf e help do app não alterados (decisao_dono: capa com ressalva).

🧪 /tmp/test_v1070_site.js · confiança do auditor: alta
Média JUR-11

Aviso pré-compra em 10,9 px e aceite em 9,9 px (comprar.php); Termos em 14,4 px; cláusulas limitativas sem destaque — abaixo do 'corpo doze' exigido

Corrigido
📍 /tmp/NOVO/comprar/comprar.php L29 (.68rem), L41 (.62rem); termos_uso.html L10 (.9rem), L23-35; WORK918.html termosTexto L24580 (.72rem)

O problema. O art. 54 §3º exige contratos de adesão 'com caracteres ostensivos e legíveis, cujo tamanho da fonte não será inferior ao corpo doze' (12 pt ≈ 16 px) e o §4º exige destaque para cláusulas que limitem direitos. O bloco com as condições (arrependimento, versão contratada, upgrades pagos) está em .68rem = 10,9 px; a declaração de aceite em .62rem = 9,9 px; os Termos do site em .9rem = 14,4 px e o termo embutido no app em .72rem = 11,5 px. Contraste está adequado (≥ 5,5:1).

Evidência numérica. Cálculo: 0,68rem × 16px = 10,88 px; 0,62rem = 9,92 px; 0,9rem = 14,4 px; 0,72rem = 11,52 px — todos < 16 px (corpo 12). Contraste #8892a4/#0b0e15 = 6,15:1 (ok).

Base legal / matemática. CDC art. 54 §3º (red. Lei 11.785/2008) e §4º — https://www.planalto.gov.br/ccivil_03/leis/l8078compilado.htm

O que foi feito. comprar.php: aviso legal 1rem (16px), aceite .9rem, fornecedor .85rem, rótulos; termos_uso.html p/li 1rem, body 16px, cláusulas limitativas (4 e 5) em caixa 'ATENÇÃO — LIMITAÇÃO DE DIREITO'; politica p/li 1rem, tabela .9rem; obrigado .mini .85rem; arrependimento 1rem.

Observação. Modal do app (.72rem) → 1rem: instrução entregue.

🧪 /tmp/test_v1070_site.js (getComputedStyle ≥16px termos/política/aviso; ≥12px aceite/fornecedor) · confiança do auditor: alta
Média JUR-12

E-mail + chave trafegam em GET (baixar.php, licenca_status.php) e vão parar em logs/histórico; endpoint 'relatorio' de ativar.php lista todos os e-mails de clientes com segredo literal fixo comparado sem hash_equals; enviar_definitivas.php aceita disparo web com chave fixa de 8 caracteres

Corrigido
📍 /tmp/NOVO/comprar/baixar.php L4, L9-10, L111; config.php licLinkDownload L100-102; licenca_status.php L16-17; WORK918.html L25718-25719; ativar.php L44-58 (relatorio), L45; enviar_definitivas.php L11; comprar.php L7, L97

O problema. Links de download carregam ?e=<email>&c=<chave> — ficam em access logs do Apache/HostGator, Cloudflare, histórico do navegador e em qualquer encaminhamento do e-mail (Referrer-Policy strict-origin-when-cross-origin mitiga só o referer). licenca_status.php também expõe e-mail em GET diariamente. ativar.php?acao=relatorio devolve a lista completa de e-mails/uso de todos os licenciados mediante um literal hexadecimal fixo (o mesmo usado como ?diag= em comprar.php), comparação com '!==' (sem hash_equals, sem rate limit); enviar_definitivas.php dispara pela web com chave literal de 8 caracteres. Um vazamento de qualquer desses literais = incidente (art. 48) com dados de todos os clientes.

Evidência numérica. config.php L101: BASE_URL.'/baixar.php?e='.rawurlencode(email).'&c='.rawurlencode(chave) → URL completa com dado pessoal; ativar.php L45: at_in('chave_admin') !== (defined('DIAG_CHAVE') ? DIAG_CHAVE : '<literal fixo>') → lista JSON de e-mails (L52-58). Nomes dos segredos: DIAG_CHAVE (não definido em config.php → usa o literal), chave de enviar_definitivas.php (literal L11).

Base legal / matemática. LGPD art. 46, art. 47, art. 48; Res. CD/ANPD 2/2022 art. 13 (política simplificada de segurança) — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm ; https://www.gov.br/anpd/pt-br/acesso-a-informacao/institucional/atos-normativos/regulamentacoes_anpd/resolucao-cd-anpd-no-2-de-27-de-janeiro-de-2022 ; Marco Civil art. 7º VII — https://www.planalto.gov.br/ccivil_03/_ato2011-2014/2014/lei/l12965.htm

O que foi feito. baixar.php aceita ?t=<token opaco> (licencas/tokens.csv) e converte o link antigo ?e=&c= em token com 303; links dos arquivos usam token; obrigado.php usa licLinkDownloadToken; licenca_status.php aceita POST (GET mantido para o app v10.32); relatório/diag/cron só POST com segredo do config.php e hash_equals; logs sem chave completa (mmdMascChave) nem e-mail (mmdMascEmail); acessos.log só com hash.

Observação. licLinkDownload (config.php) ainda gera ?e=&c= no e-mail → 1 linha para o dono trocar (LEIA-ME C). Apache LogFormat sem query não é configurável por .htaccess (decisao_dono/HostGator). App para POST: texto entregue.

🧪 harness: 303 para ?t=; página por token; grep e-mail nos logs = 0 · confiança do auditor: alta
Média JUR-13

App envia IP + tickers da carteira/CEP a proxies CORS de terceiros (allorigins, codetabs, corsproxy.io, thingproxy, r.jina.ai, rss2json, cors.workers.dev), Yahoo, Brapi, TradingView, Binance, CoinGecko, BCB, ViaCEP, FIPE e ao próprio servidor — Política diz 'sem enviar nenhuma informação pessoal'

Parcial
📍 WORK918.html L5880-5886, L7124-7130, L9603, L9966-9998, L10066-10070, L10394-10399, L15342-15374, L20122, L23366, L24823 (viacep), L26370-26372, L32475-32481, L34895-34897, L35036-35038, L35115-35116, L7276-7278 (proxy_cot/fund próprio); politica_privacidade.html L51-57

O problema. O endereço IP é dado pessoal (LGPD art. 5º I; Marco Civil art. 5º VIII/art. 7º). Cada consulta de cotação/gráfico/notícia sai do navegador do usuário para serviços estrangeiros de terceiros com a URL completa (que revela os tickers da carteira — informação sobre a vida financeira) e, para CEP, o endereço consultado (viacep). Muitos desses proxies são gratuitos, sem contrato, sem política de privacidade verificável e podem registrar/revender tráfego. O servidor mymoneydre.com.br (proxy_cot.php?t=PETR4,VALE3,...) também recebe a lista de tickers do usuário junto com o IP (access log). A Política §5 lista só Brapi/TradingView/Yahoo/BCB/FIPE/câmbio e afirma que nada pessoal é enviado; não trata de transferência internacional (art. 33) nem da retenção desses logs.

Evidência numérica. Entrada: app aberto com carteira PETR4/VALE3 → app: GET https://corsproxy.io/?url=https%3A%2F%2Fquery1.finance.yahoo.com%2F...PETR4.SA (L5880) e GET https://mymoneydre.com.br/app/proxy_cot.php?t=PETR4,VALE3 (L7276) → IP do usuário + tickers no log do corsproxy.io (EUA) e no access log HostGator/Cloudflare. Política §5: 'sem enviar nenhuma informação pessoal junto' — incorreto quanto ao IP.

Base legal / matemática. LGPD art. 5º I, art. 9º V, art. 33 (transferência internacional), art. 6º III (necessidade) — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm ; Res. CD/ANPD 19/2024 — https://www.gov.br/anpd/pt-br/acesso-a-informacao/institucional/atos-normativos/regulamentacoes_anpd/resolucao-cd-anpd-no-19-de-23-de-agosto-de-2024 ; Marco Civil art. 7º VII-VIII — https://www.planalto.gov.br/ccivil_03/_ato2011-2014/2014/lei/l12965.htm

O que foi feito. Política §7 lista o servidor próprio, todas as fontes e os proxies públicos nominalmente, IP/tickers, transferência internacional e retenção de 6 meses dos access logs; termos cl. 8.

Observação. Roteamento das buscas do app pelo proxy próprio é no app (decisao_dono/outro corretor).

🧪 /tmp/test_v1070_site.js · confiança do auditor: alta
Média JUR-14

E-mails de clientes gravados em erros.log, webhook.log, downloads.log e licencas.csv sem prazo de expurgo; Política declara 5 anos só para o 'registro da venda'; guarda de 6 meses dos registros de acesso (Marco Civil) depende de configuração não verificável na HostGator/Cloudflare

Corrigido
📍 /tmp/NOVO/comprar/comprar.php L73-75; webhook.php L12, L64; baixar.php L56-57; config.php L142-143, L178-181; ativar.php L119-130; politica_privacidade.html L38-39

O problema. webhook.log grava 'email=... chave=...' a cada venda (L64); erros.log grava e-mail + plano em falhas de checkout (L74) inclusive de quem NÃO comprou (tentativas frustradas — sem base legal de 'execução de contrato' concluída, apenas procedimentos preliminares, art. 7º V); downloads.log grava e-mail a cada download; nada é expurgado. A Política promete: e-mail 'enquanto a licença existir (vitalícia)' e registro da venda '5 anos', mas não cobre logs, tentativas de compra, aparelhos, cofre, cookies. O Marco Civil art. 15 exige guardar os registros de acesso a aplicações (IP + data/hora) por 6 meses sob sigilo — o vendedor é provedor de aplicações PJ com fins econômicos; a retenção real dos access logs (cPanel roda logs mensalmente; Cloudflare grátis não retém) não está definida — nem para mais (sigilo) nem para menos (6 meses).

Evidência numérica. comprar.php L73-75: file_put_contents('licencas/erros.log', data|CHECKOUT FALHOU|plano|<email>|motivo) — dado de não cliente, sem prazo. Política L38-39: só 'e-mail' e 'registro da venda'.

Base legal / matemática. LGPD art. 7º V, art. 9º II, art. 15 I, art. 16 — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm ; Marco Civil art. 15 caput (6 meses) e art. 16 — https://www.planalto.gov.br/ccivil_03/_ato2011-2014/2014/lei/l12965.htm ; Decreto 12.975/2026 (registro de IP e porta lógica de origem) — https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2026/decreto/d12975.htm

O que foi feito. E-mail mascarado em webhook.log, erros.log (checkout falhou), downloads.log, smtp.php, reembolsos.txt; mmdRetencao() (diária no cron enviar_definitivas + ocasional nos endpoints): erros/webhook/acessos.log 180 dias, downloads.log 365, aparelhos e cofres 365 dias após último uso e os revogados, ratelimit/cache/locks velhos; Política §3 com todas as categorias e prazos.

Observação. Retenção dos access logs do Apache/Cloudflare (6 meses, Marco Civil) depende do cPanel 'Archive logs' — decisao_dono.

🧪 harness: linha 2025 podada, cofre >400 dias apagado · confiança do auditor: alta
Média JUR-15

Política anuncia 'Encarregado (DPO)' sem identificá-lo (caixa genérica financeiro@) — ou identifica o encarregado, ou declara a dispensa de pequeno porte mantendo o canal

Decisão do dono
📍 /tmp/NOVO/comprar/politica_privacidade.html L30; WORK918.html L24631

O problema. Art. 41 §1º: 'A identidade e as informações de contato do encarregado deverão ser divulgadas publicamente, de forma clara e objetiva'. A Res. CD/ANPD 2/2022 art. 11 dispensa agentes de pequeno porte de indicar encarregado, desde que mantenham canal de comunicação com o titular — mas se a empresa afirma ter DPO, precisa identificá-lo (pessoa natural ou jurídica) — Res. CD/ANPD 18/2024. A caixa 'financeiro@' mistura cobrança e privacidade; prazos de resposta: a Política diz 15 dias (o pequeno porte tem prazo em dobro para o art. 18 e 15 dias para a declaração simplificada do art. 19 — ok, mas explicitar).

Evidência numérica. Política L30: 'Encarregado pelo tratamento de dados (DPO) e canal do titular: financeiro@clinicadomicro.com.br' — sem nome/razão social do encarregado.

Base legal / matemática. LGPD art. 41 caput e §§1º-3º — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm ; Res. CD/ANPD 2/2022 arts. 2º, 11, 14-15 — https://www.gov.br/anpd/pt-br/acesso-a-informacao/institucional/atos-normativos/regulamentacoes_anpd/resolucao-cd-anpd-no-2-de-27-de-janeiro-de-2022 ; Lei 15.352/2026 (ANPD passa a ser Agência Nacional de Proteção de Dados) — https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2026/lei/l15352.htm

O que foi feito. Política §1: 'Encarregado pelo tratamento de dados pessoais (art. 41): [NOME DO ENCARREGADO] · financeiro@clinicadomicro.com.br · (31) 3221-8025', prazos art. 18/19 conforme Res. 2/2022; 'Agência Nacional de Proteção de Dados (ANPD)'.

Observação. Dono: preencher o nome ou trocar pela dispensa de pequeno porte (texto da opção B no juridico.json).

🧪 /tmp/test_v1070_site.js · confiança do auditor: alta
Média JUR-16

Essencial não lista '12 meses de atualizações' mas Termos e FAQ prometem 1º ano para todos; suporte WhatsApp só no Profissional × Termos cl. 7 para todos; instaladores desktop (só Completo) são entregues a qualquer chave; pasta arquivos/ vazia → 'Instaladores em atualização'; instaladores v9.18/v10.23 × app v10.32

Parcial
📍 /tmp/NOVO/comprar/index.html L136-152, L165; index.html L516-533, L547; termos_uso.html L22, L24, L48; baixar.php L12-30, L159-165; comprar/arquivos/ (vazio)

O problema. A oferta vincula o fornecedor (art. 30) e deve ser precisa (art. 31). Pontos divergentes: (a) Essencial: '12 meses de atualizações' não aparece na lista (L138) mas L152/L165 e Termos cl. 1 dizem que todo plano inclui 1º ano; (b) 'Suporte via WhatsApp' vendido como diferencial do Profissional (L143) mas Termos cl. 7 e todas as páginas de erro oferecem WhatsApp a qualquer cliente; (c) 'Versão desktop' é exclusiva do Completo (L148) mas baixar.php entrega Windows/Mac/Linux a qualquer chave válida e o e-mail diz 'BAIXE O PROGRAMA (Windows, Mac ou Linux)' para todos — ou o Completo não tem exclusividade real (informação enganosa a quem pagou R$ 200 a mais) ou o Essencial recebe algo não contratado; (d) no snapshot, comprar/arquivos/ está vazio: quem comprou o Completo vê 'Instaladores em atualização' (art. 35 — recusa de cumprimento da oferta); (e) catálogo cita MyMoneyDRE_Setup_v9.18.exe e o LEIA-ME v10.23 enquanto o app web é v10.32 — cliente desktop recebe versão defasada sem aviso.

Evidência numérica. comprar/index.html L138 (Essencial): 'Licença vitalícia · Versão site completa · 28 módulos + 27 simuladores · Alertas no Telegram' — sem atualizações; L152: 'Atualizações após o 1º ano: R$ 97/ano' (pressupõe 1º ano para todos). baixar.php L12-13: $valido só depende de e-mail+chave, não do plano.

Base legal / matemática. CDC art. 30, 31, 35, 37 §1º — https://www.planalto.gov.br/ccivil_03/leis/l8078compilado.htm ; Lei 9.609 art. 8º (suporte técnico durante a validade técnica da versão) — https://www.planalto.gov.br/ccivil_03/leis/l9609.htm

O que foi feito. Termos cl. 1 remetem à descrição do plano vigente na compra (art. 30); cl. 7: suporte e-mail/telefone para todos, WhatsApp conforme plano; versão contida nos instaladores referida a versao.html; baixar.php aponta a versão.

Observação. decisao_dono: alinhar listas dos planos (Essencial sem '12 meses de atualizações' vs obs), exclusividade real do desktop no Completo (baixar.php não gate por plano — dado do plano está no csv, fácil de implementar quando decidir), publicar instaladores antes de vender o Completo.

🧪 /tmp/test_v1070_site.js · confiança do auditor: alta
Média JUR-17

Política menciona o art. 48 mas sem prazo (3 dias úteis — Res. CD/ANPD 15/2024), sem registro interno de incidentes e sem procedimento; não há cláusula de incidente com os operadores (HostGator, Cloudflare, Mercado Pago)

Corrigido
📍 /tmp/NOVO/comprar/politica_privacidade.html L64

O problema. A Res. CD/ANPD 15/2024 fixa 3 dias úteis para comunicar à ANPD e aos titulares incidentes que possam acarretar risco ou dano relevante, e exige registro dos incidentes (inclusive dos não comunicados) — com avaliação de risco considerando se os dados estavam cifrados. Os dados sob risco aqui: e-mails + chaves (licencas.csv/logs), lista de aparelhos, blobs do cofre (cifrados — mitigante). Não há procedimento nem modelo de comunicação.

Evidência numérica. Política L64: 'Em caso de incidente de segurança relevante, comunicaremos os titulares afetados e a ANPD, conforme o art. 48' — sem prazo, canal ou conteúdo mínimo (art. 48 §1º).

Base legal / matemática. LGPD art. 48 caput e §1º — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm ; Res. CD/ANPD 15/2024 (3 dias úteis; registro) — https://www.gov.br/anpd/pt-br/canais_atendimento/agente-de-tratamento/comunicado-de-incidente-de-seguranca-cis

O que foi feito. Política §9 com prazo de 3 dias úteis (Res. CD/ANPD 15/2024), conteúdo mínimo, registro interno de incidentes, cifra do cofre como mitigante.

Observação. Modelo de incidentes.md/e-mail não criado (documento interno — decisao_dono).

🧪 /tmp/test_v1070_site.js · confiança do auditor: alta
Média JUR-18

Arrependimento só por e-mail/WhatsApp, sem ferramenta no site, sem confirmação automática de recebimento (§1º) e sem comando imediato de estorno ao Mercado Pago (§2º)

Corrigido
📍 /tmp/NOVO/comprar/termos_uso.html L36; comprar.php L30-38; webhook.php L40-45 (só reage a refund já feito no MP)

O problema. O art. 5º exige informar de forma clara e ostensiva os meios de exercer o arrependimento, que 'poderá ser exercido pela mesma ferramenta utilizada para a contratação' (§1º, 1ª parte), com confirmação imediata do recebimento (§1º) e comunicação imediata à administradora do cartão/instituição para estorno (§2º). Hoje o cliente escreve para um e-mail e depende de ação manual; o código só registra reembolso após o Mercado Pago informar 'refunded'.

Evidência numérica. termos_uso.html L36: 'basta escrever para contato@... ou chamar no WhatsApp' — nenhum endpoint; grep -c 'refund' comprar/*.php → só leitura de status em webhook.php L40.

Base legal / matemática. Decreto 7.962/2013 art. 5º caput e §§1º-3º — https://www.planalto.gov.br/ccivil_03/_ato2011-2014/2013/decreto/d7962.htm ; CDC art. 49 §ún. — https://www.planalto.gov.br/ccivil_03/leis/l8078compilado.htm

O que foi feito. Novo comprar/arrependimento.php: valida e-mail + chave/paymentId no licencas.csv, grava licencas/arrependimentos.csv com protocolo, e-mail automático de confirmação (cópia para EMAIL_COPIA), calcula no_prazo/fora_do_prazo, e com ARREPENDIMENTO_ESTORNO_AUTO=true pede o estorno em /v1/payments/{id}/refunds e revoga a chave; linkado em termos, comprar.php, obrigado.php, baixar.php e rodapés.

Observação. Estorno automático fica desligado por padrão (movimenta dinheiro) — decisao_dono ligar.

🧪 harness: POST válido → protocolo + csv; inválido → mensagem; /tmp/test_v1070_site.js · confiança do auditor: alta
Baixa JUR-19

E-mail de entrega identifica remetente e CNPJ, mas não traz endereço físico, link dos Termos/Política nem aviso de que é mensagem transacional; não há e-mail de marketing (ok) — recomendar rodapé padrão

Decisão do dono
📍 /tmp/NOVO/comprar/config.php licEnviarEmail L210-249, L261-265; smtp.php L74-81

O problema. Não existe disparo de marketing (bom: a Política L48 promete não fazê-lo sem consentimento). O transacional está identificado ('My Money DRE <contato@mymoneydre.com.br>', Reply-To, Message-ID, CNPJ) mas omite endereço físico e links contratuais (ver JUR-09) e envia a chave em texto puro (aceitável, mas avisar para não encaminhar). Se um dia houver newsletter: opt-in específico (art. 7º I / art. 8º §4º LGPD) e link de descadastro em toda mensagem.

Evidência numérica. config.php L224-225 (texto) e L247-248 (HTML): 'Suporte: contato@... - (31) 3221-8025 / Clinica do Micro Ltda - CNPJ ... - Belo Horizonte/MG' — sem endereço, sem link de termos.

Base legal / matemática. Decreto 7.962 art. 2º e 4º III — https://www.planalto.gov.br/ccivil_03/_ato2011-2014/2013/decreto/d7962.htm ; LGPD art. 7º I e 8º §4º (marketing) — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm ; boa prática (CAPEM / RFC 8058 List-Unsubscribe para marketing)

O que foi feito. E-mail de desistência já sai com rodapé completo (endereço, CEP, 'e-mail transacional'). O e-mail de entrega é montado no config.php (não editável por mim): rodapé pronto no LEIA-ME item K.

🧪 — · confiança do auditor: alta
Baixa JUR-20

Site: imagens com alt (28/28), lang e contraste adequados; faltam <label> nos campos, fontes < 11 px, sem skip-link e sem declaração de acessibilidade — registrar

Parcial
📍 /tmp/NOVO/index.html (28 <img> com alt); comprar/comprar.php L26-27 (inputs só com placeholder); WORK918.html L25743-25746 (ativação sem label); comprar/index.html L65 (.6rem)

O problema. Art. 63 exige acessibilidade nos sítios de empresas com sede no país conforme as melhores práticas internacionais (WCAG 2.x / eMAG). Verificado: alt em todas as imagens da página de vendas; lang=pt-BR; contraste texto/fundo ≥ 5,5:1 (medido: #8892a4/#151a26 = 5,54; #c8cedb/#0b0e15 = 12,2). Pendências: inputs de e-mail/chave só com placeholder (leitores de tela perdem o rótulo ao digitar); fontes de 9,6-11,5 px em avisos legais; sem 'pular para o conteúdo'; dependência de emoji como ícone sem aria-hidden; o app não foi auditado (fora de escopo — registrar).

Evidência numérica. Contagem: <img> = 28, alt = 28, alt="" = 0; aria-* em index.html = 1, comprar/index.html = 0; comprar.php L26: <input type="email" name="email" required placeholder="Seu melhor e-mail"> sem <label>.

Base legal / matemática. Lei 13.146/2015 art. 63 — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2015/lei/l13146.htm ; Decreto 9.296/2018 (eMAG) — boa prática WCAG 2.2 AA

O que foi feito. comprar.php: <label for> nos dois e-mails, autocomplete; banner com role=dialog/aria-label; fontes mínimas subidas (.85rem+) nas páginas legais e no checkout.

Observação. Skip-link e aria-hidden nos emojis da home não feitos (baixo risco).

🧪 /tmp/test_v1070_site.js · confiança do auditor: alta
Baixa JUR-21

Termos vedam 'distribuir cópias'/'copiar' sem ressalvar a cópia de salvaguarda (art. 6º, I) e a integração técnica (art. 6º, IV); e não explicitam o prazo de validade técnica da versão para fins de suporte (art. 8º)

Corrigido
📍 /tmp/NOVO/comprar/termos_uso.html L39; WORK918.html termosTexto cl. 4 L24592-24596, cl. 7 L24611-24614

O problema. A Lei do Software garante ao usuário legítimo uma cópia de salvaguarda e integração ao sistema para uso próprio; cláusulas genéricas contra 'copiar' podem ser lidas como abusivas (CDC art. 51 XV). O art. 8º obriga o comercializador a prestar serviços técnicos durante a 'validade técnica' da versão — os Termos falam em 'período do plano' sem dizer qual é a validade técnica (data em que a versão deixa de ter suporte), o que importa para a 'licença vitalícia' sem atualizações.

Evidência numérica. termos_uso.html L39: 'distribuir cópias do software; descompilar...' sem exceção do art. 6º; termos app L24593: 'Copiar, distribuir ou disponibilizar o Software, no todo ou em parte'.

Base legal / matemática. Lei 9.609 art. 6º I e IV, art. 8º, art. 9º — https://www.planalto.gov.br/ccivil_03/leis/l9609.htm ; Lei 9.610 — https://www.planalto.gov.br/ccivil_03/leis/l9610.htm ; CDC art. 51 XV — https://www.planalto.gov.br/ccivil_03/leis/l8078compilado.htm

O que foi feito. Termos cl. 4 com cópia de salvaguarda e integração (Lei 9.609 art. 6º I e IV) e cl. 7 com validade técnica de 12 meses (art. 8º).

🧪 /tmp/test_v1070_site.js · confiança do auditor: alta
Baixa JUR-22

Política diz que CPF 'não passa por nós', mas o objeto de pagamento devolvido pela API do Mercado Pago ao webhook/obrigado.php contém payer (e-mail, identificação) e o termo do app cita 'CPF/CNPJ quando informados'; emissão de NF exigirá CPF — alinhar

Parcial
📍 /tmp/NOVO/comprar/webhook.php L34, L54; obrigado.php L9-13; politica_privacidade.html L40, L46; WORK918.html L24630

O problema. mpGet('/v1/payments/{id}') retorna o JSON completo do pagamento (inclui payer.identification quando o comprador informou CPF no checkout) — o servidor recebe e descarta (só usa e-mail/external_reference), mas 'não passa por nós' é impreciso; erros.log pode gravar até 300 caracteres da resposta (config.php L140) em falhas. Com a emissão de NFS-e (JUR-06) o CPF passará a ser tratado com base no art. 7º, II.

Evidência numérica. config.php mpGuardarErro L139-143: substr($resposta,0,300) gravado em erros.log em erro HTTP — pode conter dados do pagador.

Base legal / matemática. LGPD art. 9º, art. 7º II e V — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm

O que foi feito. Política §3: dados de pagamento — o que a API do MP devolve (e-mail, id, nome/CPF se informados) e uso só para nota fiscal.

Observação. mpGuardarErro (config.php) ainda grava até 300 chars da resposta em erros.log — decisao_dono: não gravar corpo em /v1/payments.

🧪 /tmp/test_v1070_site.js · confiança do auditor: alta
Baixa JUR-23

Aceite dos Termos e Política por botão único sem registro de versão/data no servidor; não há necessidade de consentimento LGPD (base contratual), mas falta prova do aceite contratual

Corrigido
📍 /tmp/NOVO/comprar/comprar.php L40-41; config.php licRegistrar L178-181

O problema. O botão 'Aceitar os termos e ir para o pagamento' (aceite por clique) é válido para contratos de adesão eletrônicos, mas o servidor não guarda a versão dos Termos vigente, a data/hora e o IP do aceite — sem isso, a prova da ciência prévia (CDC art. 46) fica frágil. A Política não exige consentimento (bases: art. 7º II e V) — correto não pedir checkbox de 'consinto com o tratamento'.

Evidência numérica. licencas.csv: 'data;email;plano;chave;paymentId' — sem versão dos termos nem carimbo do aceite; o POST de comprar.php não passa nenhum campo de aceite ao Mercado Pago (external_reference = plano|email).

Base legal / matemática. CDC art. 46 e 54 — https://www.planalto.gov.br/ccivil_03/leis/l8078compilado.htm ; Decreto 7.962 art. 4º II e III — https://www.planalto.gov.br/ccivil_03/_ato2011-2014/2013/decreto/d7962.htm ; LGPD art. 7º V (sem consentimento necessário) — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm

O que foi feito. comprar.php: hidden termos_v/politica_v, licAceiteRegistrar → licencas/aceites.csv (data;email;plano;termos_v;politica_v;ip_hash;user_agent) antes de criar a preferência; metadata {termos_v, politica_v, aceite_em} na preferência MP; declaração de aceite avisa o registro.

🧪 harness: aceites.csv + metadata no POST stubado · confiança do auditor: alta
Baixa JUR-24

Checklist consolidado da Política de Privacidade frente ao art. 9º LGPD e ao Marco Civil — itens 'atende', 'parcial' e 'não atende' com a redação de substituição

Corrigido
📍 /tmp/NOVO/comprar/politica_privacidade.html (todo)

O problema. Atende: I finalidade (§3 tabela) e base legal por dado; III/IV identificação e contato do controlador com CNPJ e endereço (§1); VII direitos do art. 18 (§8); menores (§9); alterações (§10); HTTPS (§7). Parcial: II duração (só e-mail/venda — faltam logs, aparelhos, cofre, cookies, tentativas de compra — JUR-14); V compartilhamento (falta Google, proxies de cotação, HostGator/Cloudflare como operadores, Telegram, transferência internacional — JUR-03/13); encarregado sem identidade (JUR-15); incidente sem prazo (JUR-17). Não atende: afirmações falsas de que o app não transmite nada, de que não há servidor de dados nem ferramentas de análise/cookies (JUR-01/02/03); VI responsabilidades dos agentes (não há parágrafo dizendo quem é controlador e quem é operador em cada fluxo: Mercado Pago = controlador independente do pagamento; HostGator/Cloudflare/Google = operadores/controladores conjuntos); ausência de seção sobre dados de terceiros que o usuário insere (clientes/fornecedores com CPF nos recibos — o usuário é controlador desses dados; o app é ferramenta).

Evidência numérica. Resumo da Política L26: 'o aplicativo My Money DRE não coleta, não transmite e não armazena nenhum dado seu' × código: ativar.php (diário), licenca_status.php (diário), backup_nuvem.php (cofre), GTM/GA4/Ads (site).

Base legal / matemática. LGPD art. 9º I-VII, art. 18, art. 39 (operador), art. 41 — https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm ; Marco Civil art. 7º VI e VIII — https://www.planalto.gov.br/ccivil_03/_ato2011-2014/2014/lei/l12965.htm

O que foi feito. Política reescrita: resumo novo, §5 papéis (controladora/operadores/Google/dados de terceiros do usuário), duração por categoria, compartilhamento completo, art. 33, direitos, incidente, cookies, versão datada e histórico de versões.

🧪 /tmp/test_v1070_site.js (25 itens verificados) · confiança do auditor: alta

Decisões que ficam com o dono

  1. FIS-10 — Tabela regressiva aplicada com um único prazo (anos até a idade-alvo) a todos os aportes — subestima o IR de PGBL em ~50%
    Reproduzível (aliquota única para todos os aportes). Deixado para o outro corretor; minha FIS-11 usa apenas r.fv/r.rendimento de prevCalcPlano, que não mudam com a correção por coortes.
  2. JUR-05 — TIEI 2.0 emite 'Decisão: Compra Forte / Compra Moderada / Evitar' com sizing ('100% do alvo'), ranking de 20 ações e 'preço justo' com 'margem de segurança' — conteúdo típico de relatório de análise; o disclaimer atual (0,56rem) não basta
    Implementação no app pelo outro corretor.
  3. JUR-06 — Fluxo de venda não emite NFS-e (LC 116 item 1.05 / ISS BH) embora a página prometa 'nota fiscal'; comprovante do Mercado Pago não substitui a nota
    Recomendação (LEIA-ME item G): licenciamento = serviço item 1.05 LC 116 (ISS/BH); emitir NFS-e por venda no Emissor Nacional NFS-e (gov.br/nfse) ou integrar a API, enviar em até 5 dias úteis; confirmar com o contador Simples/alíquota e destaque CBS/IBS 2026 (LC 214 art. 348); coletar nome/CPF opcional no checkout.
  4. JUR-15 — Política anuncia 'Encarregado (DPO)' sem identificá-lo (caixa genérica financeiro@) — ou identifica o encarregado, ou declara a dispensa de pequeno porte mantendo o canal
    Dono: preencher o nome ou trocar pela dispensa de pequeno porte (texto da opção B no juridico.json).
  5. JUR-19 — E-mail de entrega identifica remetente e CNPJ, mas não traz endereço físico, link dos Termos/Política nem aviso de que é mensagem transacional; não há e-mail de marketing (ok) — recomendar rodapé padrão
    E-mail de desistência já sai com rodapé completo (endereço, CEP, 'e-mail transacional'). O e-mail de entrega é montado no config.php (não editável por mim): rodapé pronto no LEIA-ME item K.

Testes

Suíte de regressão: 138 testes de tela (Playwright/Chromium) passam no fonte e no build compilado da v10.33. Novos nesta revisão: test_v1043 a test_v1076 (33 arquivos) + test_v1070_site (loja); ajustados por codificarem a regra antiga: v957–v964 (Gordon/TIEI), v1024, v1025, v1027, v1034, v1035, v1036, v1037, v1052.

Gerado em 11/09/2026 02:28 · Clínica do Micro Ltda · Este relatório é técnico; não substitui parecer de contador ou advogado.