@danhachuel/leadmaster 0.1.17 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/backend/__pycache__/main.cpython-311.pyc +0 -0
  3. package/backend/services/__pycache__/__init__.cpython-311.pyc +0 -0
  4. package/backend/services/__pycache__/agents.cpython-311.pyc +0 -0
  5. package/backend/services/__pycache__/crm_agent.cpython-311.pyc +0 -0
  6. package/backend/services/__pycache__/extractors.cpython-311.pyc +0 -0
  7. package/backend/services/__pycache__/flags.cpython-311.pyc +0 -0
  8. package/backend/services/__pycache__/llm.cpython-311.pyc +0 -0
  9. package/backend/services/__pycache__/storage.cpython-311.pyc +0 -0
  10. package/backend/tests/__pycache__/conftest.cpython-311-pytest-8.4.2.pyc +0 -0
  11. package/backend/tests/__pycache__/test_campaigns_routes.cpython-311-pytest-8.4.2.pyc +0 -0
  12. package/backend/tests/__pycache__/test_cnpj_extractor.cpython-311-pytest-8.4.2.pyc +0 -0
  13. package/backend/tests/__pycache__/test_crm_routes.cpython-311-pytest-8.4.2.pyc +0 -0
  14. package/backend/tests/__pycache__/test_instagram_extractor.cpython-311-pytest-8.4.2.pyc +0 -0
  15. package/backend/tests/__pycache__/test_launcher_protections.cpython-311-pytest-8.4.2.pyc +0 -0
  16. package/backend/tests/__pycache__/test_maps_extractor.cpython-311-pytest-8.4.2.pyc +0 -0
  17. package/backend/tests/__pycache__/test_metrics.cpython-311-pytest-8.4.2.pyc +0 -0
  18. package/backend/tests/__pycache__/test_process_flags.cpython-311-pytest-8.4.2.pyc +0 -0
  19. package/backend/tests/test_metrics.py +112 -0
  20. package/frontend/dist/assets/{index-BWyDEt5a.js → index-DFVTLg-7.js} +4 -4
  21. package/frontend/dist/index.html +1 -1
  22. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -37,6 +37,37 @@ normaliza para a forma canônica (ex.: `0.1.0`).
37
37
  falha se `process.stdin.resume();` desaparecer, se `.unref()` aparecer, se o
38
38
  ciclo de vida vinculado for removido ou se os handlers globais sumirem.
39
39
 
40
+ ## [0.1.18] — 2026-11-09
41
+
42
+ ### Adicionado
43
+
44
+ - **Zero states no Painel (Efeito Zeigarnik)** — com base vazia a tela não
45
+ mostra mais gráficos em branco:
46
+ - `LeadsStageChart`: **"Nenhum dado ainda. Comece extraindo leads!"** com
47
+ orientação aos extratores (Maps/Instagram/CNPJ);
48
+ - `CampaignsDoughnut`: **"Nenhuma campanha disparada ainda."** com orientação
49
+ a Disparos;
50
+ - ambos em bloco tracejado discreto, sem quebrar o layout (altura preservada).
51
+ - **Testes das métricas (`backend/tests/test_metrics.py`)** — 4 novos testes da
52
+ rota `GET /api/metrics` com dados conhecidos em `LEADMASTER_HOME` temporário
53
+ (seeds de demo neutralizados):
54
+ - base vazia: totais zerados e taxas padrão (100% entrega / 0% erro);
55
+ - leads conhecidos: `leads_total` e `leads_por_etapa` exatos por coluna;
56
+ - consolidação de campanhas: enviadas/erros somados e taxas proporcionais
57
+ (150/162 → 92,6% e 7,4%);
58
+ - reflexo do Kanban: mover lead de coluna via `PUT /api/crm/leads/{id}/etapa`
59
+ recalcula `leads_por_etapa` na hora.
60
+ - Suíte completa: **44 passed**.
61
+
62
+ ### Auditoria
63
+
64
+ - **`GET /api/metrics` (dinâmica):** auditada contra a especificação — já lê
65
+ `leads.json`/`campaigns.json` em tempo real e devolve `leads_total`,
66
+ `leads_por_etapa` (leads/em_contato/proposta/fechado), totais consolidados
67
+ das campanhas, `taxa_entrega`/`taxa_erro` (fallback 100%/0% sem campanhas) e
68
+ `conexao` (geral/llm/openoutreach). Nenhuma alteração de cálculo necessária —
69
+ agora com cobertura de testes e zero states na UI.
70
+
40
71
  ## [0.1.17] — 2026-11-09
41
72
 
42
73
  ### Corrigido
@@ -0,0 +1,112 @@
1
+ """Testes das métricas do Painel — GET /api/metrics.
2
+
3
+ Popula os JSONs de teste (LEADMASTER_HOME em diretório temporário) com dados
4
+ conhecidos e verifica o cálculo em tempo real: totais de leads, distribuição
5
+ por etapa, consolidação das campanhas (enviadas/erros/taxas) e status da
6
+ conexão.
7
+ """
8
+
9
+ from fastapi.testclient import TestClient
10
+
11
+ import main
12
+ from services import storage
13
+
14
+
15
+ def _client(tmp_path, monkeypatch):
16
+ monkeypatch.setenv("LEADMASTER_HOME", str(tmp_path))
17
+ # Cria os arquivos vazios ANTES do lifespan: evita os seeds de demo que o
18
+ # backend semeia na primeira execução — assim o teste parte de base zerada.
19
+ storage.save_leads([])
20
+ storage.save_campaigns([])
21
+ return TestClient(main.app)
22
+
23
+
24
+ def _lead(nome: str, etapa: str, telefone: str) -> dict:
25
+ return {
26
+ "nome": nome,
27
+ "telefone": telefone,
28
+ "email": "",
29
+ "fonte": "teste",
30
+ "etapa": etapa,
31
+ "empresa": "",
32
+ "cidade": "",
33
+ "endereco": "",
34
+ "score": 50,
35
+ "tags": [],
36
+ "observacoes": "",
37
+ "extras": {},
38
+ "criado_em": storage.now_iso(),
39
+ "atualizado_em": storage.now_iso(),
40
+ }
41
+
42
+
43
+ def test_metrics_sem_dados(tmp_path, monkeypatch):
44
+ # Base vazia: nada de quebrar — taxas padrão (100% entrega, 0% erro).
45
+ with _client(tmp_path, monkeypatch) as client:
46
+ r = client.get("/api/metrics")
47
+ assert r.status_code == 200
48
+ m = r.json()
49
+ assert m["leads_total"] == 0
50
+ assert m["leads_por_etapa"] == {"leads": 0, "em_contato": 0, "proposta": 0, "fechado": 0}
51
+ assert m["total_enviadas"] == 0
52
+ assert m["total_erros"] == 0
53
+ assert m["taxa_entrega"] == 100.0
54
+ assert m["taxa_erro"] == 0.0
55
+ assert m["campanhas"] == {"enviadas": 0, "erros": 0, "total": 0}
56
+ assert m["conexao"]["geral"] is True
57
+ assert isinstance(m["conexao"]["llm"], bool)
58
+ assert isinstance(m["conexao"]["openoutreach"], bool)
59
+
60
+
61
+ def test_metrics_com_leads_conhecidos(tmp_path, monkeypatch):
62
+ monkeypatch.setenv("LEADMASTER_HOME", str(tmp_path))
63
+ storage.save_leads([])
64
+ storage.save_campaigns([])
65
+ storage.import_leads(
66
+ [
67
+ _lead("Lead A", "leads", "(11) 90000-0001"),
68
+ _lead("Lead B", "leads", "(11) 90000-0002"),
69
+ _lead("Lead C", "em_contato", "(11) 90000-0003"),
70
+ _lead("Lead D", "proposta", "(11) 90000-0004"),
71
+ _lead("Lead E", "fechado", "(11) 90000-0005"),
72
+ ],
73
+ dedup=False,
74
+ )
75
+ with TestClient(main.app) as client:
76
+ m = client.get("/api/metrics").json()
77
+ assert m["leads_total"] == 5
78
+ assert m["leads_por_etapa"] == {"leads": 2, "em_contato": 1, "proposta": 1, "fechado": 1}
79
+
80
+
81
+ def test_metrics_consolida_campanhas(tmp_path, monkeypatch):
82
+ monkeypatch.setenv("LEADMASTER_HOME", str(tmp_path))
83
+ storage.save_leads([])
84
+ storage.save_campaigns(
85
+ [
86
+ {"id": "c1", "nome": "Campanha 1", "status": "concluida", "enviadas": 90, "erros": 10},
87
+ {"id": "c2", "nome": "Campanha 2", "status": "concluida", "enviadas": 60, "erros": 2},
88
+ ]
89
+ )
90
+ with TestClient(main.app) as client:
91
+ m = client.get("/api/metrics").json()
92
+ assert m["total_enviadas"] == 150
93
+ assert m["total_erros"] == 12
94
+ assert m["taxa_entrega"] == 92.6 # 150 / 162 * 100
95
+ assert m["taxa_erro"] == 7.4 # 12 / 162 * 100
96
+ assert m["campanhas"] == {"enviadas": 150, "erros": 12, "total": 2}
97
+
98
+
99
+ def test_metrics_reflete_alteracoes_no_kanban(tmp_path, monkeypatch):
100
+ # Mover um lead de etapa (rota do CRM) muda a contagem no /api/metrics.
101
+ with _client(tmp_path, monkeypatch) as client:
102
+ criado = client.post(
103
+ "/api/crm/leads",
104
+ json={"nome": "Lead Kanban", "telefone": "(11) 90000-0009", "fonte": "teste"},
105
+ ).json()
106
+ m1 = client.get("/api/metrics").json()
107
+ assert m1["leads_por_etapa"]["leads"] == 1
108
+
109
+ client.put(f"/api/crm/leads/{criado['id']}/etapa", json={"etapa": "fechado"})
110
+ m2 = client.get("/api/metrics").json()
111
+ assert m2["leads_por_etapa"] == {"leads": 0, "em_contato": 0, "proposta": 0, "fechado": 1}
112
+ assert m2["leads_total"] == 1