@danhachuel/leadmaster 0.1.6 → 0.1.8
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.
- package/CHANGELOG.md +39 -0
- package/backend/__pycache__/main.cpython-311.pyc +0 -0
- package/backend/main.py +36 -0
- package/backend/services/__pycache__/__init__.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/agents.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/crm_agent.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/extractors.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/flags.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/llm.cpython-311.pyc +0 -0
- package/backend/services/__pycache__/storage.cpython-311.pyc +0 -0
- package/backend/tests/__pycache__/conftest.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/__pycache__/test_crm_routes.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/__pycache__/test_process_flags.cpython-311-pytest-8.4.2.pyc +0 -0
- package/backend/tests/test_crm_routes.py +64 -0
- package/frontend/dist/assets/{index-pSbjfRli.css → index-DVFFIa5O.css} +1 -1
- package/frontend/dist/assets/index-ZsRPRMCF.js +90 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-0xtgAs3t.js +0 -90
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,45 @@ O formato é baseado no [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.
|
|
|
6
6
|
A etiqueta visual de versão usa patch de dois dígitos (ex.: `0.1.00`); o registry NPM
|
|
7
7
|
normaliza para a forma canônica (ex.: `0.1.0`).
|
|
8
8
|
|
|
9
|
+
## [0.1.08] — 2026-11-08
|
|
10
|
+
|
|
11
|
+
### Adicionado
|
|
12
|
+
|
|
13
|
+
- **Módulo CRM Kanban implementado conforme especificação:**
|
|
14
|
+
- Rotas: `GET /api/crm/leads`, `POST /api/crm/leads` (criação manual) e
|
|
15
|
+
`PUT /api/crm/leads/{lead_id}/etapa` (movimentação ao soltar o card) —
|
|
16
|
+
persistência no `leads.json` local (seeds de demonstração mantidos).
|
|
17
|
+
- Kanban com **@dnd-kit/core + @dnd-kit/sortable** (multi-contêiner):
|
|
18
|
+
arrastar entre colunas persiste via API; reordenação visual dentro da
|
|
19
|
+
coluna; soltar fora das colunas reverte o movimento.
|
|
20
|
+
- Colunas: **Novos Leads → Em Contato → Proposta → Fechado**.
|
|
21
|
+
- Card mostra Nome, Telefone, Fonte e **Data de Criação**.
|
|
22
|
+
- Botão **"Adicionar Lead"** (gradiente) com modal de criação (campos
|
|
23
|
+
avançados em accordion).
|
|
24
|
+
- UX: card inteiro arrastável (Fitts); **opacity 0.5** no card arrastado e
|
|
25
|
+
**borda tracejada indigo** na coluna de destino (Zeigarnik); colunas
|
|
26
|
+
brancas com sombra suave (padrão do Dashboard).
|
|
27
|
+
- **7 novos testes pytest** para as rotas do CRM (`backend/tests/test_crm_routes.py`):
|
|
28
|
+
listagem, criação, persistência, validação, movimentação, 404 e etapas
|
|
29
|
+
inválidas — isolados com `LEADMASTER_HOME` temporário.
|
|
30
|
+
|
|
31
|
+
## [0.1.07] — 2026-11-08
|
|
32
|
+
|
|
33
|
+
### Adicionado
|
|
34
|
+
|
|
35
|
+
- **Regra permanente (AGENTS.md): PROIBIDO criar versões que abram telas de
|
|
36
|
+
CMD/terminal no computador do usuário.** Nenhum popup de console pode aparecer
|
|
37
|
+
ao rodar o LeadMaster; requisitos técnicos:
|
|
38
|
+
- Node (`child_process`): sempre `windowsHide: true`; `.cmd`/`.bat` via
|
|
39
|
+
`shell: true` + `windowsHide: true`;
|
|
40
|
+
- Python (`subprocess`): sempre `creationflags` com `CREATE_NO_WINDOW`
|
|
41
|
+
(`services/flags.py`);
|
|
42
|
+
- saída sempre em pipes/logs — nunca janelas interativas.
|
|
43
|
+
- QA inclui verificação item a item dos spawns.
|
|
44
|
+
- Estado do código auditado: todos os spawns do launcher já cumprem
|
|
45
|
+
(`windowsHide`) e os subprocessos Python usam `creationflags` — nenhuma
|
|
46
|
+
alteração de código foi necessária.
|
|
47
|
+
|
|
9
48
|
## [0.1.06] — 2026-11-08
|
|
10
49
|
|
|
11
50
|
### Corrigido
|
|
Binary file
|
package/backend/main.py
CHANGED
|
@@ -295,6 +295,42 @@ def aplicar_sugestao(dados: dict):
|
|
|
295
295
|
return {"ok": True, "lead": atualizado}
|
|
296
296
|
|
|
297
297
|
|
|
298
|
+
# ---------------------------------------------------------------------------
|
|
299
|
+
# CRM — Kanban (leads por etapa)
|
|
300
|
+
# ---------------------------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
@app.get("/api/crm/leads")
|
|
303
|
+
def crm_listar_leads():
|
|
304
|
+
"""Todos os leads (persistidos em leads.json) — usado pelo quadro Kanban."""
|
|
305
|
+
return storage.list_leads()
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@app.post("/api/crm/leads", status_code=201)
|
|
309
|
+
def crm_criar_lead(lead_in: LeadIn):
|
|
310
|
+
"""Cria um lead manualmente pelo modal 'Adicionar Lead' do CRM."""
|
|
311
|
+
lead = lead_in.model_dump()
|
|
312
|
+
lead["id"] = storage._novo_id()
|
|
313
|
+
lead["etapa"] = _validar_etapa(lead.get("etapa", "leads"))
|
|
314
|
+
lead["score"] = lead.get("score") if lead.get("score") is not None else crm_agent.calcular_completude(lead)
|
|
315
|
+
storage.add_lead(lead)
|
|
316
|
+
agents.disparar_webhook("lead.criado", {"lead_id": lead["id"], "nome": lead["nome"]})
|
|
317
|
+
return lead
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@app.put("/api/crm/leads/{lead_id}/etapa")
|
|
321
|
+
def crm_mover_lead(lead_id: str, dados: dict):
|
|
322
|
+
"""Move um lead de coluna no Kanban (drag-and-drop)."""
|
|
323
|
+
etapa = dados.get("etapa")
|
|
324
|
+
if not etapa:
|
|
325
|
+
raise HTTPException(400, "Informe a etapa de destino.")
|
|
326
|
+
_validar_etapa(etapa)
|
|
327
|
+
atualizado = storage.update_lead(lead_id, {"etapa": etapa})
|
|
328
|
+
if not atualizado:
|
|
329
|
+
raise HTTPException(404, "Lead não encontrado.")
|
|
330
|
+
agents.disparar_webhook("lead.etapa", {"lead_id": lead_id, "etapa": etapa, "via": "kanban"})
|
|
331
|
+
return atualizado
|
|
332
|
+
|
|
333
|
+
|
|
298
334
|
# ---------------------------------------------------------------------------
|
|
299
335
|
# Campanhas / Disparos
|
|
300
336
|
# ---------------------------------------------------------------------------
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Testes das rotas do CRM Kanban (backend/main.py).
|
|
2
|
+
|
|
3
|
+
Usam TestClient do FastAPI com LEADMASTER_HOME em diretório temporário, para
|
|
4
|
+
nunca tocar nos dados reais do usuário. Rodam iguais no CI (Linux) e local.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from fastapi.testclient import TestClient
|
|
9
|
+
|
|
10
|
+
import main
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture()
|
|
14
|
+
def client(tmp_path, monkeypatch):
|
|
15
|
+
monkeypatch.setenv("LEADMASTER_HOME", str(tmp_path))
|
|
16
|
+
with TestClient(main.app) as c:
|
|
17
|
+
yield c
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_listar_leads(client):
|
|
21
|
+
r = client.get("/api/crm/leads")
|
|
22
|
+
assert r.status_code == 200
|
|
23
|
+
corpo = r.json()
|
|
24
|
+
assert isinstance(corpo, list) and len(corpo) >= 1 # seeds de demonstração
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_criar_lead(client):
|
|
28
|
+
r = client.post("/api/crm/leads", json={"nome": "Padaria Teste", "telefone": "(11) 90000-0000", "fonte": "Manual"})
|
|
29
|
+
assert r.status_code == 201
|
|
30
|
+
lead = r.json()
|
|
31
|
+
assert lead["id"]
|
|
32
|
+
assert lead["etapa"] == "leads"
|
|
33
|
+
# Confirma persistência no JSON local
|
|
34
|
+
lista = client.get("/api/crm/leads").json()
|
|
35
|
+
assert any(l["id"] == lead["id"] for l in lista)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_criar_lead_sem_nome_invalido(client):
|
|
39
|
+
r = client.post("/api/crm/leads", json={"nome": ""})
|
|
40
|
+
assert r.status_code == 422 # validação do modelo Pydantic
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_mover_lead_entre_colunas(client):
|
|
44
|
+
criado = client.post("/api/crm/leads", json={"nome": "Lead Móvel"}).json()
|
|
45
|
+
r = client.put(f"/api/crm/leads/{criado['id']}/etapa", json={"etapa": "proposta"})
|
|
46
|
+
assert r.status_code == 200
|
|
47
|
+
assert r.json()["etapa"] == "proposta"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_mover_lead_inexistente(client):
|
|
51
|
+
r = client.put("/api/crm/leads/nao-existe/etapa", json={"etapa": "fechado"})
|
|
52
|
+
assert r.status_code == 404
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_mover_lead_etapa_invalida(client):
|
|
56
|
+
criado = client.post("/api/crm/leads", json={"nome": "Lead X"}).json()
|
|
57
|
+
r = client.put(f"/api/crm/leads/{criado['id']}/etapa", json={"etapa": "nao_existe"})
|
|
58
|
+
assert r.status_code == 400
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_mover_lead_sem_etapa(client):
|
|
62
|
+
criado = client.post("/api/crm/leads", json={"nome": "Lead Y"}).json()
|
|
63
|
+
r = client.put(f"/api/crm/leads/{criado['id']}/etapa", json={})
|
|
64
|
+
assert r.status_code == 400
|