feat(home): widget "Quem está fora hoje" → main#138
Merged
Warleypablo merged 1 commit intomainfrom Apr 28, 2026
Merged
Conversation
* test(contribuicao-squad): teste 2 — pontual à vista
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(contribuicao-squad): teste 3 — pontual parcelado em 5 meses
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(contribuicao-squad): teste 4 — recorrente + pontual juntos
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(contribuicao-squad): teste 5 — Creators 4 entregas FIFO
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(contribuicao-squad): adiciona EPSILON contra precisão float no simulator
Trata achado I3 do code review: valores reais com centavos podem
gerar resíduos como 1517.9999999999998 que manteriam pontuais
"vivos" indefinidamente. EPSILON = 0.005 (meio centavo) é usado
em ambos os checks de saldo > 0 e sobra <= 0.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): integra simulator A3 no endpoint bulk
Substitui query monolítica de receitas por (1) Query 1 contratos,
(2) Query 2 pagamentos cronológicos, (3) montagem Map<cnpj, ClienteSim>,
(4) loop simulateCliente, (5) agregação para mesesMap mantendo o
mesmo formato do BulkResponse.
parseDbDate normaliza datas para UTC midnight (evita drift de timezone).
FALLBACK_DATA_INICIO trata contratos sem data_inicio (decisão de spec).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(contribuicao-squad): refaz resumo e detalhes por squad via clientesMap
resumoPorSquad e receitasDetalhesPorSquad agora vêm da iteração de
clientesMap (estado pós-simulação) em vez de result.rows da query
antiga removida na Task 7. Estrutura de saída e contrato JSON
permanecem idênticos.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(contribuicao-squad): code review findings I1/I2/M5/M6
- I1: receitasDetalhesPorSquad agora respeita squadFilter (regressão)
- I2: extrai matchesSquadFilter, elimina 3 cópias da fuzzy logic
- M5: mesAtualYYYYMM usa UTC para consistência com simulator
- M6: GROUP BY de pagamentos sem caz.nome (usa MAX), evita
double-count em CNPJs com nomes inconsistentes em caz_clientes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): reconciliação cumulativa A3 de receitas pontuais (#105)
* docs(contribuicao-squad): spec de reconciliação cumulativa de pontuais (A3)
Algoritmo A3: para cada cliente, simular cronologicamente o histórico
de pagamentos. Recorrentes contam cheio quando há pagamento (preserva
comportamento atual); sobra alimenta pontuais em FIFO por data_inicio
até saldo_devedor zerar. Resolve inflação de Tech parcelado e Creators
4 entregas sem precisar de configuração manual.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(contribuicao-squad): plano de implementação reconciliação A3
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): adiciona simulator A3 puro para receitas
Função simulateCliente reconcilia pagamentos do Conta Azul contra
contratos do Clickup. Recorrentes recebem valor cheio; sobra
alimenta pontuais em FIFO por data_inicio até saldo_devedor zerar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(contribuicao-squad): teste 1 — cliente só recorrente
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(contribuicao-squad): teste 2 — pontual à vista
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(contribuicao-squad): teste 3 — pontual parcelado em 5 meses
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(contribuicao-squad): teste 4 — recorrente + pontual juntos
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(contribuicao-squad): teste 5 — Creators 4 entregas FIFO
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(contribuicao-squad): adiciona EPSILON contra precisão float no simulator
Trata achado I3 do code review: valores reais com centavos podem
gerar resíduos como 1517.9999999999998 que manteriam pontuais
"vivos" indefinidamente. EPSILON = 0.005 (meio centavo) é usado
em ambos os checks de saldo > 0 e sobra <= 0.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): integra simulator A3 no endpoint bulk
Substitui query monolítica de receitas por (1) Query 1 contratos,
(2) Query 2 pagamentos cronológicos, (3) montagem Map<cnpj, ClienteSim>,
(4) loop simulateCliente, (5) agregação para mesesMap mantendo o
mesmo formato do BulkResponse.
parseDbDate normaliza datas para UTC midnight (evita drift de timezone).
FALLBACK_DATA_INICIO trata contratos sem data_inicio (decisão de spec).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(contribuicao-squad): refaz resumo e detalhes por squad via clientesMap
resumoPorSquad e receitasDetalhesPorSquad agora vêm da iteração de
clientesMap (estado pós-simulação) em vez de result.rows da query
antiga removida na Task 7. Estrutura de saída e contrato JSON
permanecem idênticos.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(contribuicao-squad): code review findings I1/I2/M5/M6
- I1: receitasDetalhesPorSquad agora respeita squadFilter (regressão)
- I2: extrai matchesSquadFilter, elimina 3 cópias da fuzzy logic
- M5: mesAtualYYYYMM usa UTC para consistência com simulator
- M6: GROUP BY de pagamentos sem caz.nome (usa MAX), evita
double-count em CNPJs com nomes inconsistentes em caz_clientes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(contribuicao-squad): spec + plano para despesas atribuídas por squad
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): backend agrega despesas por squad (salários + freelas)
Adiciona campo despesasPorSquadMensais à resposta do endpoint bulk.
Salários e freelancers agora são atribuídos ao squad de receita
correspondente via findRevenueSquad (mesmo fuzzy match já usado em
salariosDetalhesPorSquad). Mantém despesasMensais como total mensal
para hero/rodapé.
Move stripEmoji/revenueSquadMap/findRevenueSquad para mais cedo no
handler para serem reutilizados pelas novas agregações.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): frontend usa despesa real por squad (sem rateio)
despesaSquadMes e despesaComponenteSquadMes agora fazem lookup
direto em bulkData.despesasPorSquadMensais em vez de ratear o
total pela proporção de receita.
squadRanking.despesaRateada (nome legado) agora é a soma anual
real do squad. Resultado líquido reflete margem real.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): despesas atribuídas por squad real (sem rateio) (#106)
* docs(contribuicao-squad): spec + plano para despesas atribuídas por squad
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): backend agrega despesas por squad (salários + freelas)
Adiciona campo despesasPorSquadMensais à resposta do endpoint bulk.
Salários e freelancers agora são atribuídos ao squad de receita
correspondente via findRevenueSquad (mesmo fuzzy match já usado em
salariosDetalhesPorSquad). Mantém despesasMensais como total mensal
para hero/rodapé.
Move stripEmoji/revenueSquadMap/findRevenueSquad para mais cedo no
handler para serem reutilizados pelas novas agregações.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): frontend usa despesa real por squad (sem rateio)
despesaSquadMes e despesaComponenteSquadMes agora fazem lookup
direto em bulkData.despesasPorSquadMensais em vez de ratear o
total pela proporção de receita.
squadRanking.despesaRateada (nome legado) agora é a soma anual
real do squad. Resultado líquido reflete margem real.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(contribuicao-squad): Contrib % agora é margem de contribuição real
Antes calculava receita_squad / receita_total (share da receita,
nome enganoso). Agora calcula (receita - despesa) / receita do
squad — margem de contribuição real, coerente com o conceito de
"% Contribuição" em P&L.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(contribuicao-squad): Contrib % agora é margem de contribuição real (#107)
Antes calculava receita_squad / receita_total (share da receita,
nome enganoso). Agora calcula (receita - despesa) / receita do
squad — margem de contribuição real, coerente com o conceito de
"% Contribuição" em P&L.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): adiciona iFood (R$ 400 × colab ativo no mês)
Backend agrega contagem de colaboradores ativos por (squad, mês)
em paralelo à agregação de salários. iFood = count × R$ 400.
Atribuído ao squad de receita via findRevenueSquad.
despesasMensais.ifood = total mensal global (todos os colabs)
despesasPorSquadMensais[squad][mes].ifood = só os mapeados
Frontend exibe iFood como 3ª linha do drilldown de despesas
(per-squad e rodapé) e inclui no total da linha de despesa.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): adiciona iFood (R$ 400 × colab ativo no mês) (#108)
Backend agrega contagem de colaboradores ativos por (squad, mês)
em paralelo à agregação de salários. iFood = count × R$ 400.
Atribuído ao squad de receita via findRevenueSquad.
despesasMensais.ifood = total mensal global (todos os colabs)
despesasPorSquadMensais[squad][mes].ifood = só os mapeados
Frontend exibe iFood como 3ª linha do drilldown de despesas
(per-squad e rodapé) e inclui no total da linha de despesa.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(contribuicao-squad): filtro de status inativo robusto
Set antigo ['Cancelado', 'Encerrado', 'Pausado'] não pegava nenhum
contrato real da base porque (a) case-sensitive e (b) valor real é
'cancelado/inativo' (não 'Cancelado'). Resultado: 1099+ contratos
recorrentes cancelados continuavam competindo por pagamento no A3,
engolindo a sobra que deveria ir pra pontuais.
Caso BioPelle: Squadra cancelado em 09/mar seguia registrando
R\$ 2.997/mês, deixando Tech com só R\$ 57,88 em março. Após o fix,
Tech recebe R\$ 1.997 (valorp cheio, saldo zera).
Impacto agregado: -R\$ 750k na receita total anual atribuída
(Squadra -R\$ 295k, Turbo Interno -R\$ 115k, Makers -R\$ 83k).
Novos valores no Set (normalizado com lowercase+trim):
- cancelado/inativo (1099 contratos)
- em cancelamento (44)
- pausado (26)
- cancelado, encerrado, não usar
Triagem (92) e Onboarding (30) seguem como ATIVOS — se cliente
está pagando, é receita real.
Adiciona 2 testes unitários cobrindo o caso real do BioPelle e
variações de case/trim do status.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(contribuicao-squad): filtro de status inativo robusto (#109)
Set antigo ['Cancelado', 'Encerrado', 'Pausado'] não pegava nenhum
contrato real da base porque (a) case-sensitive e (b) valor real é
'cancelado/inativo' (não 'Cancelado'). Resultado: 1099+ contratos
recorrentes cancelados continuavam competindo por pagamento no A3,
engolindo a sobra que deveria ir pra pontuais.
Caso BioPelle: Squadra cancelado em 09/mar seguia registrando
R\$ 2.997/mês, deixando Tech com só R\$ 57,88 em março. Após o fix,
Tech recebe R\$ 1.997 (valorp cheio, saldo zera).
Impacto agregado: -R\$ 750k na receita total anual atribuída
(Squadra -R\$ 295k, Turbo Interno -R\$ 115k, Makers -R\$ 83k).
Novos valores no Set (normalizado com lowercase+trim):
- cancelado/inativo (1099 contratos)
- em cancelamento (44)
- pausado (26)
- cancelado, encerrado, não usar
Triagem (92) e Onboarding (30) seguem como ATIVOS — se cliente
está pagando, é receita real.
Adiciona 2 testes unitários cobrindo o caso real do BioPelle e
variações de case/trim do status.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): receita via caz_vendas_itens + bucket Sem Squad (#111)
* feat(contribuicao-squad): adiciona tabela item_alias_map com seed inicial
Cria cortex_core.item_alias_map para mapear aliases de itens do Conta Azul
para tokens canonicos de servico (performance, gameplan, ecommerce).
Usado pela pipeline da aba Contribuicao por Squad para atribuir receita
correta quando o nome do item no CAZ nao casa literalmente com o servico
do contrato no ClickUp. Seed inicial com 9 aliases curados.
- shared/schema.ts: definicao Drizzle itemAliasMap
- server/db.ts: initializeItemAliasMapTable (create + index parcial + seed)
- server/index.ts: registra init na fase paralela do bootstrap
* refactor(contribuicao-squad): align item_alias_map init with bootstrap swallow pattern
* feat(contribuicao-squad): helpers puros de normalização para match item↔contrato
* docs(contribuicao-squad): agrupa STOPWORDS por categoria semântica
* feat(contribuicao-squad): query de receita por itens com pipeline de match
* refactor(contribuicao-squad): project cnpj_limpo, extract stopwords/threshold constants
* feat(contribuicao-squad): handler bulk usa receita via itens com fallback A3
Integra o pipeline getReceitaPorItens (caz_vendas_itens) no handler
/api/contribuicao-squad/dfc/bulk como fonte primária de receita para
parcelas com venda_id. Parcelas cobertas (>=99% do valor_pago atribuído
aos itens) são excluídas da query do simulador A3 para evitar double-
counting. O loop de merge mantém órfãos (⚠️ Sem Squad) visíveis mesmo
quando um squad filter é aplicado. Response agora inclui fonteDados
com contagem via itens vs via A3.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* refactor(contribuicao-squad): fallback gracioso, denominador correto em fonteDados, antijoin scale-proof
Fixes de code review (I1/I2/I3/M1/M2) no handler /api/contribuicao-squad/dfc/bulk:
- I1: wrap do pipeline getReceitaPorItens em try/catch. Falha transitória
(DB, extensão unaccent, etc) degrada gracioso para A3-only em vez de
quebrar o endpoint inteiro. Novo flag fonteDados.fallbackUsed sinaliza.
- I3: preview query agora filtra venda_origem IN ('VENDA','VENDA_AGENDADA')
AND venda_id IS NOT NULL — mesmo escopo do pipeline de itens. Assim
pctViaItens usa denominador significativo (parcelas ELEGÍVEIS), não
todas as parcelas do ano (RENEGOCIACAO/LANCAMENTO não são atribuíveis
via itens). Rename totalParcelasAno -> totalParcelasElegiveis.
- I2: troca NOT IN (id1..id1000) por antijoin NOT EXISTS (VALUES ...).
Escala melhor com crescimento de parcelasCobertasSet. Fragmento sql``
vazio substitui o placeholder TRUE quando lista está vazia.
- M1: extrai constante SEM_SQUAD_LABEL em receitaPorItens.ts, importa
em routes.ts, elimina string duplicada no runtime compare. Literal SQL
no CTE orfaos mantido com comentário de invariante.
- M2: tipa row shape da preview query como
Array<{ parcela_id: string; valor_pago: string | number }> em vez de any[].
Response contract change: fonteDados.totalParcelas -> totalParcelasElegiveis
e novo fonteDados.fallbackUsed. Task 5 (frontend) vai consumir essa
forma — ok porque Task 5 ainda não começou.
Out-of-scope (follow-ups): M3 (extract merge loop helper), M4 (merge
preview queries), M6 (semântica de mesData.totalParcelas += 1 representando
itens, não parcelas únicas).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): bucket Sem Squad + badge de fonte de dados
- Estende BulkResponse com fonteDados (totalParcelasElegiveis, viaItens,
viaSimuladorA3, pctViaItens, fallbackUsed).
- Adiciona SEM_SQUAD_LABEL e helper isSemSquad ao lado de isOffSquad.
- Aplica destaque amber no header row do squad "Sem Squad" (fundo e cor
de texto) para torná-lo visível sem ser invisível.
- Destaca "Sem Squad" também no dropdown de filtro de squad.
- Adiciona badges de fonte de dados no header: % via itens, % fallback
simulador e aviso se pipeline de itens falhou.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* docs(contribuicao-squad): comentário de sincronia SEM_SQUAD_LABEL frontend/backend
* chore(contribuicao-squad): script de comparação antes/depois por squad
* docs(contribuicao-squad): documenta matchPipeline como reference implementation
* fix(contribuicao-squad): mescla receitaItens em resumoPorSquad e detalhes
O handler já mesclava o pipeline novo no mesesMap, mas os dois agregados
secundários (squadSummaryMap para resumoPorSquad e receitasDetalhesPorSquad
para drill-down por cliente) continuavam sendo populados só pelo simulador
A3, o que fazia com que a receita via caz_vendas_itens sumisse nessas duas
visões do dashboard.
Adiciona dois loops pós-simulador A3 que agregam as linhas do pipeline novo
nos dois maps respeitando o squad filter e o bucket SEM_SQUAD_LABEL, para
manter o comportamento coerente entre todas as seções da página.
Remove também os console.logs [DBG contribuicao-squad] que ficaram como
resíduo de diagnóstico.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(contribuicao-squad): unifica bucket Sem Squad (emoji vs legacy)
Dois caminhos diferentes produziam labels distintos para a mesma ideia
conceitual de "contrato sem squad atribuído":
1. Pipeline novo (caz_vendas_itens): itens órfãos já usavam SEM_SQUAD_LABEL
('⚠️ Sem Squad'), mas a CTE de contratos em receitaPorItens.ts ainda
tinha COALESCE para 'Sem Squad' sem emoji, então um item que casava
com um contrato de squad vazio voltava com o label legacy.
2. Simulator A3 (fallback): montava ContratoSim com squad = row.squad ||
'Sem Squad', também sem emoji.
3. Salários por colaborador: mesmo fallback sem emoji.
Como resultado, o dashboard mostrava dois buckets distintos — um "⚠️ Sem
Squad" com os órfãos do pipeline e um "Sem Squad" legacy com os contratos
sem squad preenchido —, confundindo o usuário e fragmentando o número que
precisa de curadoria.
Adiciona um helper local normalizeSquadLabel() no handler bulk que coerce
'' e 'Sem Squad' para SEM_SQUAD_LABEL, e aplica nas duas fontes legacy
(ContratoSim e salariosPorColab). Também alinha a CTE de contratos do
pipeline novo para emitir '⚠️ Sem Squad' direto da query.
Validado via smoke test local: antes R\$ 259.423 (62) + R\$ 11.042 (5)
em buckets separados; depois R\$ 270.465 (67) em um único bucket, com
total geral de receita inalterado (R\$ 4.078.319,81).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(growth): isolar Instagram OxR por publisher_platform + banner vazio (#110)
* feat(criativos): substituir CPMQL por Reuniões Agendadas nos KPIs
Troca o card de CPMQL pelo card de Reuniões Agendadas na aba de Criativos,
incluindo suporte a comparação de período com variação percentual.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Revert "feat(criativos): substituir CPMQL por Reuniões Agendadas nos KPIs"
This reverts commit 34d6c1dd8734125e08ba92c02b1ba2b331ed1c92.
* fix(growth): corrigir Desvio de métricas % e invertr cores em custos
- Extrair interfaces NaoMQLMetrics e AdsMetrics pro topo do módulo
- Adicionar INVERTED_METRIC_IDS pra métricas onde menor é melhor (CPM, CPL, CAC, no-show, lead time)
- Inverter lógica de cor de % Atingido, Desvio e barra de progresso para métricas invertidas
- Calcular Desvio de métricas % como diferença simples (não escalonar por dias)
- Ocultar Previsão As Is e Recálculo Meta para métricas de %
- Recalcular ORCADO_TOTAL.percRA dinamicamente a partir de leads reais
- Remover filtros cardFilter (MQL/Não-MQL) e revenueFilter da visão consolidada
- Remover imports duplicados de MultiSelect e TIER3_METRIC_IDS
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(meta-sync): adicionar sync por publisher_platform em tabela isolada
Cria meta_ads.meta_insights_by_platform_daily (CREATE TABLE IF NOT EXISTS
em boot via ensureByPlatformTable) e adiciona syncInsightsDailyByPlatform
que passa breakdowns='publisher_platform' na Meta Graph API. A tabela é
independente da meta_insights_daily atual pra zerar risco de regressão
nos dashboards que já consomem os totais agregados.
Extrai parseInsightRow como helper compartilhado entre syncInsightsDaily
e syncInsightsDailyByPlatform pra evitar drift de parsing.
Por que: o endpoint /api/growth/orcado-realizado/instagram precisa do
gasto/impressões/alcance atribuídos exclusivamente ao placement Instagram
(não à soma Facebook+IG+AN+Messenger) pra calcular CPL/CPMQL reais e
separar visualizações orgânicas vs pagas.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(growth): isolar Instagram OxR com investimento pago + banner de estado vazio
Endpoint /api/growth/orcado-realizado/instagram:
- Filtra Meta Ads por publisher_platform='instagram' usando a nova tabela
meta_ads.meta_insights_by_platform_daily (criada no commit anterior).
- Retorna investimentoPago, hasConnection e snapshotCount no payload.
- Suporta múltiplas conexões Instagram ativas (connection_id IN ...).
- Query do Meta Ads envolvida em try/catch pra tolerar tabela ainda inexistente
no primeiro boot antes do sync rodar.
Frontend GrowthOrcadoRealizado:
- buildFunnelMetrics('ig', ...) agora recebe d.investimentoPago como
investimento, habilitando CPL e CPMQL reais no funil Instagram.
- Banner amber renderizado acima da seção Instagram quando não há conexão
ativa ou quando não há snapshots no período — diferencia "zero real"
de "dado faltando". Dark/light mode suportado.
- MetricSection ganha campo opcional banner renderizado em TableRow estilizada.
Por que: antes o funil IG mostrava CPL/CPMQL sempre vazios porque
buildFunnelMetrics recebia null como investimento, e o usuário não
tinha como distinguir zeros reais de ausência de dados.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(spec): auditoria CRM->ERP — diagnóstico end-to-end com 23 categorias
Spec one-shot script gerando relatório Markdown + CSVs anexos.
Cobre vazamento de caixa, sub-cobrança, pós-churn, higiene, status
divergente, cross-CRM e cobertura. Janela 12 meses, multi-empresa
unificada (Turbo Partners + PEIXOTO DEBBANE).
Achados pré-spec: 539/611 deals "ganhos" sem CNPJ no Bitrix; pipeline
"Pós-Ganho/Subir-Ajustar Cobrança" com 0% CNPJ; stage_semantic vazio
em ~99,9% dos deals (bug ETL).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(plan): plano de implementação auditoria CRM->ERP (12 tasks)
Plano TDD bite-sized para o spec aprovado em 2026-04-14. Cobre:
- Task 1: DISCOVERY.md com schema gotchas
- Task 2: helpers (CNPJ normalize, validate módulo 11, format BRL) + tests
- Task 3: runner skeleton + catalog + dry-run
- Tasks 4-10: 23 queries SQL distribuídas em 7 seções
- Task 11: renderer markdown completo
- Task 12: execução final + sanity check
SQL pré-validado contra prod read-only durante plan-writing
(tipo_evento='RECEITA' uppercase, tipo_fatura 100% NULL, status
buckets ClickUp lowercase).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(acessos): remove credential approval permission system
Senhas agora são visíveis para qualquer usuário autenticado. Removidos
hooks de can-bypass/check-access/request-access no frontend, rotas
backend de aprovação/rejeição via WhatsApp e tabela
cortex_core.credential_access_requests (dropada nos dois bancos).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(acessos): remove credential approval permission system
Senhas agora são visíveis para qualquer usuário autenticado. Removidos
hooks de can-bypass/check-access/request-access no frontend, rotas
backend de aprovação/rejeição via WhatsApp e tabela
cortex_core.credential_access_requests (dropada nos dois bancos).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(spec): receita recorrente por centro de custo — design
Spec completa para nova página /financeiro/receita-recorrente com:
- Gráfico composto (barras por tipo + linha contratado)
- 7 cards KPI (MRR, Pontual, Mix, Realizado, Gap, Ticket, Delta)
- Tabela mensal por empresa com drill-down modal
- Query SQL com 3-case split para CCs multi-valor
Validado contra cup_contratos: gap de 2,3% (abril/26).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(plan): receita recorrente por centro de custo — implementation plan
12 tasks cobrindo backend (types, classifier TDD, /resumo query, cards,
/drilldown) e frontend (page skeleton, KpiCards, Chart, Tabela, Modal,
integração final, sanity check).
Cada task com steps atômicos e código completo inline.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): scaffold router, types and nav entry
Adds empty endpoints (/resumo, /drilldown) and menu item under Financeiro.
Next step: implement CC classifier and SQL query.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(receita-recorrente): remove redundant NEW comment on permission key
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): pure CC classifier with unit tests
3-case logic mirroring the SQL query:
1. Single CC → valor_bruto direct
2. Multi-CC same type → valor_bruto direct (bypass CA split bug)
3. Mixed Recorrente+Pontual → positional split from valor_centro_custo
9 test cases covering TURBO and PD naming, edge cases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(receita-recorrente): document isMisto asymmetry and malformed value skip
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): /resumo endpoint com query 3-case split
Endpoint retorna meses[] com recorrente/pontual/não-classif previsto e realizado,
cobertura CC %, MRR contratado (snapshot) e flag is_futuro.
Validado contra snapshot: Mar/26 TURBO recorrente R$ 901.112.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): compara meses como strings ISO evita timezone skew
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): adicionar 7 cards KPI ao endpoint /resumo
Inclui: MRR recorrente atual+delta, pontual atual+delta, mix %, realizado %,
gap vs contratado, ticket médio e contagem novos/churned.
Novos/churned calculados por diferença de sets de id_cliente entre mês
corrente e anterior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): filtra categoria 04.% nas queries de novos/churned
Consistência com a query principal: clientes cujos únicos lançamentos
"recorrentes" no mês são de categoria não-operacional (aportes, transferências)
não devem contar como ativos para o cálculo de novos/churned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): null gap_contratado quando empresa filtrada + realizadoPct
gap_contratado comparava MRR realizado filtrado por empresa contra
cup_contratos total (sem dimensão empresa), produzindo números inválidos.
Retorna null quando há filtro de empresa.
realizadoPct: remove fallback redundante de divisão por 1.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): endpoint /drilldown com clientes por mês/tipo
Retorna lista de parcelas filtradas por mês, tipo e empresa com JOIN
em caz_clientes para resolver nome do cliente. Ordenado por valor_bruto DESC.
Limitação v1: casos mistos (Recorrente;Pontual) são excluídos do drill-down.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): validação de tipo + COALESCE em campos text nullable
- Valida enum de tipo (RECORRENTE/PONTUAL/NAO_CLASSIFICADO) com 400.
- COALESCE em descricao e categoria_nome para evitar null quebrando a UI.
- Simplifica tautologia ILIKE NOT(A AND B) em ILIKE + NOT ILIKE.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): página skeleton com filtros e chamada ao endpoint
Header com seletores de range (6m/12m/YTD) e empresa, loading skeletons,
error state. Componentes visuais ficam para próximas tasks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): queryKey como objeto, @shared alias, Button shadcn
- queryKey[1] deve ser objeto para o queryFn default parsear params
corretamente (antes virava path "/api/.../resumo/data_ini=...").
- Substitui import relativo profundo por @shared/receitaRecorrenteTypes.
- Remove imports CardHeader/CardTitle não utilizados.
- Usa componente Button shadcn no retry (type=button + focus ring).
- Simplifica guard redundante data && !isLoading → data.
- 8 skeletons no grid (2x4) em vez de 7 que deixava célula órfã.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): KpiCards component com 7 métricas
Usa HeroMetric existente. Cores condicionais no gap vs contratado
(verde <3%, âmbar 3-10%, vermelho >10%). Labels e subtítulos
explicativos nos cards mais técnicos.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): KpiCards polish pós-review
- formatDelta: 0% retorna undefined (sem seta verde falsa em meses sem variação).
- Extrai gapStatus helper: single source of truth para color e label.
- Math.max(0, ...) defensivo em novos/churned para evitar -N visível.
- role=region + aria-label no grid para screen readers.
- Remove parâmetro digits não utilizado em formatPct.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): ChartReceitaMensal com ComposedChart
Barras empilhadas (recorrente/pontual/não-classif) + linha MRR contratado
+ linha tracejada de previsto total. Meses futuros com opacity reduzida.
Dark/light mode via ThemeProvider.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): TabelaReceitaMensal com células clicáveis
Tabela com linha por mês × empresa, células de R$ viram botões que
disparam onCellClick para abrir modal de drilldown. Badge de cobertura
CC (verde >=90%, âmbar 70-90%, vermelho <70%). Meses futuros com
opacity-60 e ícone de relógio. Linha de total no rodapé.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): UTC date off-by-one + move CellClickPayload to shared
monthLabel usava new Date("2026-03-01") que é parseado como UTC midnight.
No fuso UTC-3 (Brasil), toLocaleString retorna "fev" para 2026-03-01.
Corrigido para parse manual em data local.
CellClickPayload movido para shared/ para ser reutilizado por Task 11
(DrilldownClientesModal + integração final).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): DrilldownClientesModal com busca client-side
Modal shadcn Dialog que carrega parcelas via React Query enabled quando
aberto e com params válidos. Filtro client-side por cliente ou descrição,
sticky header na tabela, skeleton de loading, tratamento de erro com retry.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): integração final — compõe cards, chart, tabela e modal
Página principal importa todos os subcomponentes, gerencia estado de
modal e filtros, fluxo de click-to-drilldown funcional.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): toggle Competência | Caixa
Novo param "modo" em /resumo e /drilldown que alterna entre:
- competencia (default): aloca receita pelo COALESCE(data_competencia,
data_vencimento), filtro status <> CANCELADO. Regime contábil correto
para MRR/accrual — a receita pertence ao mês do serviço prestado.
- caixa: aloca receita pela data_quitacao, filtro status = QUITADO.
Bate com DFC (entradas no banco). Útil para cross-check com extrato.
Validado Mar/26 caixa: R$ 1.510.940 (vs R$ 1.518.953 do DFC — diff
R$ 7.639 referente a categorias 04.% que a página sempre filtra).
Toggle aparece como primeiro Select no header da página. Subtítulo muda
explicando o regime ativo. Drilldown herda o modo automaticamente.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(receita-recorrente): remove MRR contratado ClickUp + tooltip com total
- Chart: remove linha azul "MRR Contratado (ClickUp)" e tooltip custom
com linha "Total realizado" somando as 3 séries empilhadas.
- KpiCards: remove card "Gap vs Contratado" (e helper gapStatus).
- Tabela: remove coluna "Contratado".
- Subtítulo: sem menção a "vs contratado".
Backend ainda calcula mrr_contratado para não quebrar contrato, mas a UI
deixa de usar — pode ser removido do endpoint numa iteração futura.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): competência usa previsto como métrica principal
Em accrual, o MRR do mês é tudo que os contratos geraram de parcela
naquela competência — pago ou não. Antes a página só mostrava realizado,
o que dava a impressão errada de queda nos meses recentes (parcelas
pendentes ainda não haviam caído).
Mudanças:
- Backend: cards usam *_previsto (em caixa mode previsto==realizado).
- Chart: barras stacked passam a mostrar *_previsto. Linha tracejada
"Previsto total" removida (virou a própria barra).
- Tabela: células clicáveis (Recorrente/Pontual/Não Classif) mostram
previsto em vez de realizado. A coluna "Realizado" permanece como
indicador de cobrança/inadimplência.
- Tooltip: label "Total realizado" renomeado para "Total".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): competência usa data_vencimento (consistente com Metas)
A lógica anterior usava COALESCE(data_competencia, data_vencimento)
assumindo data_competencia como fonte autoritativa do "mês da receita".
Mas o Conta Azul preenche data_competencia de forma inconsistente e o
resto do sistema (Metas de Receita, DRE, DFC) não usa esse campo.
Resultado: 222 parcelas caíam em meses diferentes entre nossa página e
Metas de Receita para abril/26 (R$ 74K de gap direcional).
Agora:
- Competência: data_vencimento strict (= Metas de Receita).
- Caixa: data_quitacao (= DFC). Sem mudança.
Validação abr/26: nossa R$ 1.696.630 vs Metas R$ 1.694.587 (diff 0,12%
residual por caz_parcelas vs caz_receber).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(sdr-assistant): spec V1 IA para confirmação automática de leads
Design aprovado em conversa de brainstorming. Escopo V1: chat interno
no Cortex consultando Bitrix via LLM tool-use (Claude Sonnet 4.6).
ClickUp e HubSpot ficam como V2/V3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(sdr-assistant): plano de implementação V1 com 12 tasks TDD
Plano bite-sized cobrindo: validação de schema, migration, tools SQL,
integração Anthropic tool-use, endpoint, frontend e QA. Referencia
spec de 2026-04-16.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(sdr-assistant): spec V1 IA para confirmação automática de leads
Design aprovado em conversa de brainstorming. Escopo V1: chat interno
no Cortex consultando Bitrix via LLM tool-use (Claude Sonnet 4.6).
ClickUp e HubSpot ficam como V2/V3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(sdr-assistant): plano de implementação V1 com 12 tasks TDD
Plano bite-sized cobrindo: validação de schema, migration, tools SQL,
integração Anthropic tool-use, endpoint, frontend e QA. Referencia
spec de 2026-04-16.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(sdr-assistant): Task 1 concluída — plano atualizado com schema real
Investigação de "Bitrix".crm_deal revelou:
- Colunas diretas (sdr, closer, assigned_by_name) tornam JOINs
com crm_users/crm_closers desnecessários
- data_criacao não existe — nome real é date_create
- stage_semantic é inútil (17/15957 preenchidos) — classificação
fica 100% por string matching em stage_name
- company_name preenchido em só 68% — query filtra IS NOT NULL
Queries das Tasks 5 e 6 reescritas com nomes/lógica reais.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(sdr-assistant): migration da tabela sdr_assistant_usage
Tabela aplicada em dev (cortex_dev) e prod (dados_turbo) com sucesso.
Registra uso do assistente para métricas de adoção e custo.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(sdr-assistant): skeleton da rota com auth + guard interno
POST /api/sdr-assistant/chat protegido por isAuthenticated +
requireInternalCollaborator (department admin/comercial).
Retorna 401 sem auth, 403 para cliente externo, 200 com body skeleton
para colaborador autorizado.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(sdr-assistant): classifyDealStatus com testes (TDD)
Função pura retorna "ativo"|"ganho"|"perdido" a partir de stage_name.
Case-insensitive, aceita variantes PT/EN (Perdido/LOSE, Ganho/WON).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(sdr-assistant): tool searchCompanies + classificação de descartes
- searchCompanies: fuzzy match em company_name/title com limite 10,
filtra company_name IS NOT NULL (32% dos deals são leads iniciais
sem empresa mapeada)
- classifyDealStatus ampliado para capturar stages reais do Bitrix
Turbo: "Descartado/sem fit" (3699 deals) e "Descartado" (1434)
eram classificados como ativo por engano
- "Contrato assinado" agora é ganho; "Congelado" segue como ativo
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(sdr-assistant): tool getCompanyTimeline com 4 testes unitários
Query retorna timeline completa de uma empresa com:
- id, title, stage, categoria, source
- assigned_by_name (nome, não ID — coluna sdr tem IDs inúteis sem JOIN)
- closer, valor_mrr, valor_pontual
- datas convertidas para YYYY-MM-DD
- status classificado (ativo/ganho/perdido)
- motivo_perda preparado para V2 (coluna ainda não existe)
Ordem cronológica decrescente. 18 testes passando no total.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(sdr-assistant): loop tool-use Anthropic com cache ephemeral
- Modelo: claude-sonnet-4-5-20250929
- 2 tools: search_companies, get_company_timeline
- System prompt com formato padrão (🟢 ATIVO / 📜 HISTÓRICO)
- Prompt caching ephemeral reduz custo em requests subsequentes
- Loop até 5 iterações de tool-use
- Stateless: conversa vem no body a cada request
Smoke test real contra "R2x marketing" retornou timeline completa
com 3 deals classificados corretamente e observação contextual.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(sdr-assistant): endpoint POST /chat completo com log + fix tipo user_id
- Handler valida messages array não vazio
- Extrai última user message para campo 'query' do log
- Chama runSdrAssistant e persiste em cortex_core.sdr_assistant_usage
- Tratamento de erro: retorna 500 + loga tentativa no banco (sem tokens)
- Log insert não propaga falhas (melhor perder log que perder resposta)
Migration: user_id mudou de INTEGER para VARCHAR(100) para match com
cortex_core.auth_users.id (era TEXT). Re-aplicada em dev + prod
(tabela estava vazia, sem perda de dados).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(sdr-assistant): página do chat com markdown, dark mode e loading
- Mensagem de boas-vindas inicial
- Bubbles estilo chat (IA à esquerda, SDR à direita)
- ReactMarkdown para renderizar a formatação da IA
- Indicador "Consultando Bitrix..." durante tool call
- Enter envia, Shift+Enter quebra linha
- Botão "+ Nova conversa" limpa o estado local
- Auto-scroll para última mensagem, auto-focus no input
- Dark/light mode completo com classes Tailwind dark: variants
- Stateless: histórico da conversa vai no body a cada request
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(sdr-assistant): rota /dashboard/comercial/sdr-assistant + menu
- App.tsx: import lazy + <Route> na seção Comercial
- nav-config.ts: PERMISSION_KEYS.COM.SDR_ASSISTANT + entry na navbar
com icon MessagesSquare + label em displayNames
Seguindo convenção do Cortex: rotas comerciais ficam em
/dashboard/comercial/* e submenu aparece em NAV_CONFIG.setores.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(sdr-assistant): incluir rota em ALL_ROUTES + auto-migração
- ALL_ROUTES (admins) e DEFAULT_USER_ROUTES agora incluem
/dashboard/comercial/sdr-assistant
- migrateAllowedRoutes auto-adiciona a rota para usuários que já
têm outras rotas comerciais (evita precisar deslogar/logar)
Sem isso o frontend bloqueava a página mesmo com o Route cadastrado
no App.tsx, pois o ProtectedRoute valida contra user.allowedRoutes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(sdr-assistant): dev user precisa de department + rota no allowedRoutes
Ao testar localmente, o endpoint retornava 403 mesmo logado como dev
admin. Causa: deserializeUser do dev-admin-001 não incluía o campo
'department', então o guard requireInternalCollaborator recusava.
Adicionado 'department: "admin"' e '/dashboard/comercial/sdr-assistant'
na allowedRoutes do devUser hardcoded em auth/config.ts.
Smoke test end-to-end OK: R2x marketing → 3 deals, tool_calls corretos,
log gravado em sdr_assistant_usage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-squad): adicionar 6 aliases para reduzir órfãos identificados em jan/26
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* docs(receita-squad): corrigir comentário do mecanismo de idempotência
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(receita-squad): incluir contratos cancelados no matching para preservar histórico
Remove filtro (valorr+valorp>0) da CTE contratos para que contratos cancelados/zerados
participem do matching de itens. Adiciona coluna is_ativo e prioriza contratos ativos
no desempate (is_ativo DESC), resolvendo o caso DOT cujo contrato Performance cancelado
estava gerando itens órfãos indevidamente.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(receita-squad): ratear item_total proporcional ao valor_pago da parcela
Quando uma venda do Conta Azul gera N parcelas, cada parcela puxava todos os
itens da venda inteira no JOIN (overcount). Adiciona LEFT JOIN com caz_vendas
e fórmula de rateio: item_total × (valor_pago / venda.total). Fallback gracioso
se v.total for NULL/0.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(receita-squad): fallback contrato de maior valor para itens sem match
Quando o item não casa em nenhuma das 5 prioridades de matching e o CNPJ tem
contratos ativos no Clickup, atribui ao squad do contrato de maior valor
(prioridade=99). Reduz Sem Squad de ~R$ 61k para ~R$ 36k em jan/26 (apenas
clientes só no Conta Azul ficam órfãos reais).
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* docs(receita-squad): plano de implementação
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(receita-squad): expor causa de atribuição (match/fallback/orphan) na API
Adiciona coluna 'causa' no SELECT final de receitaPorItens.ts e propaga até as
parcelas em routes.ts. Permite que a UI renderize badges distinguindo: match
real, atribuição via fallback de maior valor, CNPJ sem cadastro no Clickup,
ou item que não casou.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(receita-squad): badges visuais para causa de atribuição em cada parcela
Adiciona badges coloridas (vermelha/âmbar/azul) ao lado das parcelas no dashboard
de contribuição por squad para indicar visualmente: cadastro pendente no Clickup,
item sem match nominal, ou atribuição via fallback de maior valor. Match normal
não tem badge para não poluir a UI.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* test(receita-squad): script de validação automatizada Squad vs DFC mensal
Compara mês a mês a soma do pipeline de itens (getReceitaPorItens) vs o cálculo
do DFC direto em caz_parcelas. Threshold configurável via TOLERANCIA_PCT (default 5%).
Exit code 1 se divergência exceder tolerância.
Também corrige bug em receitaPorItens.ts: is_ativo não estava incluído no SELECT
da CTE candidatos, causando erro SQL ao referenciar a coluna no ORDER BY da CTE melhor.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* docs(receita-squad): documentar limite de cobertura de teste de aliases
Aliases ficam em tabela do banco e são aplicados via SQL — não há função
TS pura testável. Validação real fica no script validateSquadVsDFC.ts.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(receita-squad): cobertura total via fallback de parcela inteira
Estende o pipeline para cobrir parcelas RECEITA QUITADO/RECEBIDO_PARCIAL
do ano que não têm itens na tabela caz_vendas_itens (vendas históricas) ou
não têm venda_id (LANCAMENTO_FINANCEIRO, RENEGOCIACAO). Cada parcela órfã
vira 1 linha sintética com item_total = valor_pago - desconto, atribuída
ao squad do contrato ativo de maior valor do CNPJ.
Adiciona causas 'parcela_sem_itens' e 'lancamento_avulso' ao enum.
Permite remover o simulador A3 sem perda de cobertura (Task 12).
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* refactor(receita-squad): remover simulador A3 — pipeline de itens é a única fonte
Após Task 11, o pipeline em receitaPorItens.ts cobre 100% das parcelas RECEITA
QUITADO/RECEBIDO_PARCIAL do ano (validado divergência 0% vs DFC em 2026).
O simulador FIFO (simulator.ts) e o pipeline de fallback no endpoint ficaram
redundantes — apenas duplicavam trabalho e mascaravam regressões.
Mudanças:
- Deleta server/contribuicaoSquad/simulator.ts (e simulator.test.ts)
- Remove imports de simulateCliente, ContratoSim, ClienteSim em routes.ts
- Remove a função parcelasCobertas e a constante COVERAGE_THRESHOLD em receitaPorItens.ts
- Limpa o endpoint /api/contribuicao-squad/dfc/bulk: remove Query 1 (contratos),
Query 2 (pagamentos), construção do clientesMap, loop de simulação e
agregação via contratos simulados. Mantém apenas o loop de receitaItens.
- Remove fonteDados da resposta JSON (campo de telemetria itens vs A3).
Resultado: contribuição por squad bate exatamente com DFC, sem heurística FIFO.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(receita-squad): remover loops órfãos sobre clientesMap deletado
Task 12 removeu o simulator e a construção de clientesMap, mas dois loops
adicionais ainda referenciavam clientesMap (squadSummaryMap e receitasDetalhesPorSquad),
causando ReferenceError no endpoint. Como o pipeline novo já agrega via
receitaItens logo abaixo de cada loop antigo, basta deletar os blocos órfãos.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(sdr-assistant): buscar deals por title quando company_name é NULL
31% dos deals no Bitrix têm company_name NULL — o nome da empresa
só existe no campo title. O filtro WHERE company_name IS NOT NULL
excluía esses deals mesmo que title correspondesse ao padrão ILIKE.
Usa COALESCE(company_name, title) para agrupar e buscar, com
agrupamento case-insensitive (UPPER) para evitar duplicatas.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(sdr-assistant): buscar deals por title quando company_name é NULL (#117)
* docs(contribuicao-squad): spec de reconciliação cumulativa de pontuais (A3)
Algoritmo A3: para cada cliente, simular cronologicamente o histórico
de pagamentos. Recorrentes contam cheio quando há pagamento (preserva
comportamento atual); sobra alimenta pontuais em FIFO por data_inicio
até saldo_devedor zerar. Resolve inflação de Tech parcelado e Creators
4 entregas sem precisar de configuração manual.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(contribuicao-squad): plano de implementação reconciliação A3
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): adiciona simulator A3 puro para receitas
Função simulateCliente reconcilia pagamentos do Conta Azul contra
contratos do Clickup. Recorrentes recebem valor cheio; sobra
alimenta pontuais em FIFO por data_inicio até saldo_devedor zerar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(contribuicao-squad): teste 1 — cliente só recorrente
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(contribuicao-squad): teste 2 — pontual à vista
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(contribuicao-squad): teste 3 — pontual parcelado em 5 meses
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(contribuicao-squad): teste 4 — recorrente + pontual juntos
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(contribuicao-squad): teste 5 — Creators 4 entregas FIFO
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(contribuicao-squad): adiciona EPSILON contra precisão float no simulator
Trata achado I3 do code review: valores reais com centavos podem
gerar resíduos como 1517.9999999999998 que manteriam pontuais
"vivos" indefinidamente. EPSILON = 0.005 (meio centavo) é usado
em ambos os checks de saldo > 0 e sobra <= 0.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): integra simulator A3 no endpoint bulk
Substitui query monolítica de receitas por (1) Query 1 contratos,
(2) Query 2 pagamentos cronológicos, (3) montagem Map<cnpj, ClienteSim>,
(4) loop simulateCliente, (5) agregação para mesesMap mantendo o
mesmo formato do BulkResponse.
parseDbDate normaliza datas para UTC midnight (evita drift de timezone).
FALLBACK_DATA_INICIO trata contratos sem data_inicio (decisão de spec).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(contribuicao-squad): refaz resumo e detalhes por squad via clientesMap
resumoPorSquad e receitasDetalhesPorSquad agora vêm da iteração de
clientesMap (estado pós-simulação) em vez de result.rows da query
antiga removida na Task 7. Estrutura de saída e contrato JSON
permanecem idênticos.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(contribuicao-squad): code review findings I1/I2/M5/M6
- I1: receitasDetalhesPorSquad agora respeita squadFilter (regressão)
- I2: extrai matchesSquadFilter, elimina 3 cópias da fuzzy logic
- M5: mesAtualYYYYMM usa UTC para consistência com simulator
- M6: GROUP BY de pagamentos sem caz.nome (usa MAX), evita
double-count em CNPJs com nomes inconsistentes em caz_clientes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(contribuicao-squad): spec + plano para despesas atribuídas por squad
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): backend agrega despesas por squad (salários + freelas)
Adiciona campo despesasPorSquadMensais à resposta do endpoint bulk.
Salários e freelancers agora são atribuídos ao squad de receita
correspondente via findRevenueSquad (mesmo fuzzy match já usado em
salariosDetalhesPorSquad). Mantém despesasMensais como total mensal
para hero/rodapé.
Move stripEmoji/revenueSquadMap/findRevenueSquad para mais cedo no
handler para serem reutilizados pelas novas agregações.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): frontend usa despesa real por squad (sem rateio)
despesaSquadMes e despesaComponenteSquadMes agora fazem lookup
direto em bulkData.despesasPorSquadMensais em vez de ratear o
total pela proporção de receita.
squadRanking.despesaRateada (nome legado) agora é a soma anual
real do squad. Resultado líquido reflete margem real.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(contribuicao-squad): Contrib % agora é margem de contribuição real
Antes calculava receita_squad / receita_total (share da receita,
nome enganoso). Agora calcula (receita - despesa) / receita do
squad — margem de contribuição real, coerente com o conceito de
"% Contribuição" em P&L.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(contribuicao-squad): adiciona iFood (R$ 400 × colab ativo no mês)
Backend agrega contagem de colaboradores ativos por (squad, mês)
em paralelo à agregação de salários. iFood = count × R$ 400.
Atribuído ao squad de receita via findRevenueSquad.
despesasMensais.ifood = total mensal global (todos os colabs)
despesasPorSquadMensais[squad][mes].ifood = só os mapeados
Frontend exibe iFood como 3ª linha do drilldown de despesas
(per-squad e rodapé) e inclui no total da linha de despesa.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(contribuicao-squad): filtro de status inativo robusto
Set antigo ['Cancelado', 'Encerrado', 'Pausado'] não pegava nenhum
contrato real da base porque (a) case-sensitive e (b) valor real é
'cancelado/inativo' (não 'Cancelado'). Resultado: 1099+ contratos
recorrentes cancelados continuavam competindo por pagamento no A3,
engolindo a sobra que deveria ir pra pontuais.
Caso BioPelle: Squadra cancelado em 09/mar seguia registrando
R\$ 2.997/mês, deixando Tech com só R\$ 57,88 em março. Após o fix,
Tech recebe R\$ 1.997 (valorp cheio, saldo zera).
Impacto agregado: -R\$ 750k na receita total anual atribuída
(Squadra -R\$ 295k, Turbo Interno -R\$ 115k, Makers -R\$ 83k).
Novos valores no Set (normalizado com lowercase+trim):
- cancelado/inativo (1099 contratos)
- em cancelamento (44)
- pausado (26)
- cancelado, encerrado, não usar
Triagem (92) e Onboarding (30) seguem como ATIVOS — se cliente
está pagando, é receita real.
Adiciona 2 testes unitários cobrindo o caso real do BioPelle e
variações de case/trim do status.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(spec): auditoria CRM->ERP — diagnóstico end-to-end com 23 categorias
Spec one-shot script gerando relatório Markdown + CSVs anexos.
Cobre vazamento de caixa, sub-cobrança, pós-churn, higiene, status
divergente, cross-CRM e cobertura. Janela 12 meses, multi-empresa
unificada (Turbo Partners + PEIXOTO DEBBANE).
Achados pré-spec: 539/611 deals "ganhos" sem CNPJ no Bitrix; pipeline
"Pós-Ganho/Subir-Ajustar Cobrança" com 0% CNPJ; stage_semantic vazio
em ~99,9% dos deals (bug ETL).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(plan): plano de implementação auditoria CRM->ERP (12 tasks)
Plano TDD bite-sized para o spec aprovado em 2026-04-14. Cobre:
- Task 1: DISCOVERY.md com schema gotchas
- Task 2: helpers (CNPJ normalize, validate módulo 11, format BRL) + tests
- Task 3: runner skeleton + catalog + dry-run
- Tasks 4-10: 23 queries SQL distribuídas em 7 seções
- Task 11: renderer markdown completo
- Task 12: execução final + sanity check
SQL pré-validado contra prod read-only durante plan-writing
(tipo_evento='RECEITA' uppercase, tipo_fatura 100% NULL, status
buckets ClickUp lowercase).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(acessos): remove credential approval permission system
Senhas agora são visíveis para qualquer usuário autenticado. Removidos
hooks de can-bypass/check-access/request-access no frontend, rotas
backend de aprovação/rejeição via WhatsApp e tabela
cortex_core.credential_access_requests (dropada nos dois bancos).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(spec): receita recorrente por centro de custo — design
Spec completa para nova página /financeiro/receita-recorrente com:
- Gráfico composto (barras por tipo + linha contratado)
- 7 cards KPI (MRR, Pontual, Mix, Realizado, Gap, Ticket, Delta)
- Tabela mensal por empresa com drill-down modal
- Query SQL com 3-case split para CCs multi-valor
Validado contra cup_contratos: gap de 2,3% (abril/26).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(plan): receita recorrente por centro de custo — implementation plan
12 tasks cobrindo backend (types, classifier TDD, /resumo query, cards,
/drilldown) e frontend (page skeleton, KpiCards, Chart, Tabela, Modal,
integração final, sanity check).
Cada task com steps atômicos e código completo inline.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): scaffold router, types and nav entry
Adds empty endpoints (/resumo, /drilldown) and menu item under Financeiro.
Next step: implement CC classifier and SQL query.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(receita-recorrente): remove redundant NEW comment on permission key
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): pure CC classifier with unit tests
3-case logic mirroring the SQL query:
1. Single CC → valor_bruto direct
2. Multi-CC same type → valor_bruto direct (bypass CA split bug)
3. Mixed Recorrente+Pontual → positional split from valor_centro_custo
9 test cases covering TURBO and PD naming, edge cases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(receita-recorrente): document isMisto asymmetry and malformed value skip
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): /resumo endpoint com query 3-case split
Endpoint retorna meses[] com recorrente/pontual/não-classif previsto e realizado,
cobertura CC %, MRR contratado (snapshot) e flag is_futuro.
Validado contra snapshot: Mar/26 TURBO recorrente R$ 901.112.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): compara meses como strings ISO evita timezone skew
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): adicionar 7 cards KPI ao endpoint /resumo
Inclui: MRR recorrente atual+delta, pontual atual+delta, mix %, realizado %,
gap vs contratado, ticket médio e contagem novos/churned.
Novos/churned calculados por diferença de sets de id_cliente entre mês
corrente e anterior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): filtra categoria 04.% nas queries de novos/churned
Consistência com a query principal: clientes cujos únicos lançamentos
"recorrentes" no mês são de categoria não-operacional (aportes, transferências)
não devem contar como ativos para o cálculo de novos/churned.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): null gap_contratado quando empresa filtrada + realizadoPct
gap_contratado comparava MRR realizado filtrado por empresa contra
cup_contratos total (sem dimensão empresa), produzindo números inválidos.
Retorna null quando há filtro de empresa.
realizadoPct: remove fallback redundante de divisão por 1.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): endpoint /drilldown com clientes por mês/tipo
Retorna lista de parcelas filtradas por mês, tipo e empresa com JOIN
em caz_clientes para resolver nome do cliente. Ordenado por valor_bruto DESC.
Limitação v1: casos mistos (Recorrente;Pontual) são excluídos do drill-down.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): validação de tipo + COALESCE em campos text nullable
- Valida enum de tipo (RECORRENTE/PONTUAL/NAO_CLASSIFICADO) com 400.
- COALESCE em descricao e categoria_nome para evitar null quebrando a UI.
- Simplifica tautologia ILIKE NOT(A AND B) em ILIKE + NOT ILIKE.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): página skeleton com filtros e chamada ao endpoint
Header com seletores de range (6m/12m/YTD) e empresa, loading skeletons,
error state. Componentes visuais ficam para próximas tasks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): queryKey como objeto, @shared alias, Button shadcn
- queryKey[1] deve ser objeto para o queryFn default parsear params
corretamente (antes virava path "/api/.../resumo/data_ini=...").
- Substitui import relativo profundo por @shared/receitaRecorrenteTypes.
- Remove imports CardHeader/CardTitle não utilizados.
- Usa componente Button shadcn no retry (type=button + focus ring).
- Simplifica guard redundante data && !isLoading → data.
- 8 skeletons no grid (2x4) em vez de 7 que deixava célula órfã.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): KpiCards component com 7 métricas
Usa HeroMetric existente. Cores condicionais no gap vs contratado
(verde <3%, âmbar 3-10%, vermelho >10%). Labels e subtítulos
explicativos nos cards mais técnicos.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): KpiCards polish pós-review
- formatDelta: 0% retorna undefined (sem seta verde falsa em meses sem variação).
- Extrai gapStatus helper: single source of truth para color e label.
- Math.max(0, ...) defensivo em novos/churned para evitar -N visível.
- role=region + aria-label no grid para screen readers.
- Remove parâmetro digits não utilizado em formatPct.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): ChartReceitaMensal com ComposedChart
Barras empilhadas (recorrente/pontual/não-classif) + linha MRR contratado
+ linha tracejada de previsto total. Meses futuros com opacity reduzida.
Dark/light mode via ThemeProvider.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): TabelaReceitaMensal com células clicáveis
Tabela com linha por mês × empresa, células de R$ viram botões que
disparam onCellClick para abrir modal de drilldown. Badge de cobertura
CC (verde >=90%, âmbar 70-90%, vermelho <70%). Meses futuros com
opacity-60 e ícone de relógio. Linha de total no rodapé.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(receita-recorrente): UTC date off-by-one + move CellClickPayload to shared
monthLabel usava new Date("2026-03-01") que é parseado como UTC midnight.
No fuso UTC-3 (Brasil), toLocaleString retorna "fev" para 2026-03-01.
Corrigido para parse manual em data local.
CellClickPayload movido para shared/ para ser reutilizado por Task 11
(DrilldownClientesModal + integração final).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): DrilldownClientesModal com busca client-side
Modal shadcn Dialog que carrega parcelas via React Query enabled quando
aberto e com params válidos. Filtro client-side por cliente ou descrição,
sticky header na tabela, skeleton de loading, tratamento de erro com retry.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): integração final — compõe cards, chart, tabela e modal
Página principal importa todos os subcomponentes, gerencia estado de
modal e filtros, fluxo de click-to-drilldown funcional.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(receita-recorrente): toggle Competência | Caixa
Novo param "modo" em /resumo e /drilldown que alterna entre:
- competencia (default): aloca receita pelo COALESCE(data_competencia,
data_vencimento), filtro status <> CANCELADO. Regime contábil correto
para MRR/accrual — a receita pertence ao mês do serviço prestado.
- caixa: aloca receita pela data_quitacao, filtro status = QUITADO.
Bate com DFC (entradas no banco). Útil para cross-check com extrato.
Validado Mar/26 caixa: R$ 1.510.940 (vs R$ 1.518.953 do DFC — diff
R$ 7.639 referente a categorias 04.% que a página sempre filtra).
Toggle aparece como primeiro Select no header da página. Subtítulo muda
explicando o regime ativo. Drilldown herda o modo automaticamente.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(recei…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Promove pra main o widget "Quem está fora hoje" (PR #137 já mergeado em staging).
Único commit envolvido:
8f062702— substituição do MiniCalendar pelo widget de indisponibilidades, validado em staging.Test plan
/api/unavailability-requests/todayfuncional🤖 Generated with Claude Code