CRM
Status: 🟢 Estável (mega-módulo, em produção desde Suite v2) Code: backend/app/modules/crm UI: frontend/src/app/(admin)/[slug]/admin/crm Última revisão deste doc: 2026-05-13 por Felipe + Claude Dependências fortes: fonte canônica de pessoa do Suite — usado por: whatsapp (lookup), email (recipients), members (sync), attribution (UTM resolution), intelligence (L1 input), council/agents (tools)
1. Identidade
O que faz (uma frase)
Gestão completa de relacionamento com contatos (pessoas), deals (oportunidades por pipeline), activities (tarefas humanas), accounts (CNPJs B2B), tags + scoring + custom fields + webhooks + API keys — fonte única de verdade de "pessoa" no Mandir, consumida por todos os outros módulos.
Por que existe (negócio)
CRM é o coração operacional. Sem ele:
- Email não tem destinatário consolidado.
- Agentes IA não sabem quem é quem.
- Attribution não tem onde guardar UTM.
- Intelligence não tem
crm_contact_idpra agregar.
Com CRM integrado:
- Contato chega (form/whatsapp/import) → CRM resolve identidade (email/phone/IG) → resto do Suite usa o mesmo
crm_contact_id. - Deals em pipeline customizável (drag-and-drop kanban) → forecast.
- Tags + scoring rules calculam
crm_contact.scoreem tempo real (50+ event_types seeded). - Custom fields permitem personalização sem migration.
- Accounts (B2B) hierárquicos (matriz → filiais).
Por que existe (técnico)
- Multi-tenant com soft-delete (
deleted_at). - Sem FK cross-módulo — relacionamento via service layer (UUIDs soltos).
- Scoring async — eventos (
crm_contact_event) atualizam score em background. - Identity resolution via fuzzy matching (memória [[brain-architecture]]).
- Webhooks outbound (per-tenant) pra integrações externas.
Status atual
Em prod desde Suite v2. Funcionalidades maduras. Próxima mudança: integração mais profunda com intelligence.contact_attribute (L1) + identity resolution via embeddings.
2. Cases de uso reais
Case 1: Lead via Instagram DM
Hari (agente) recebe DM IG → instagram.service.resolve_or_create_contact_from_ig(ig_user_id, username, name) → CRM cria/resolve contato → vincula instagram_thread.contact_id → próximas DMs já com identidade.
Case 2: Felipe move deal no kanban
UI /admin/crm/deals → arrasta card "Mentoria Premium R$ 2997" de "Proposta" pra "Negociação" → PATCH /deals/{id}/advance → crm.deal.stage_changed event → intelligence atualiza padrão de conversão.
Case 3: Custom field "professor responsável" por tenant
Felipe cria crm_custom_field_definition(entity=contact, name="Professor", field_type=select, options=["Felipe", "Vivian"]). Adiciona valor pra cada contato. read_contact_360 retorna o valor pro Conselho.
Case 4: Bulk apply tag a 100 contatos
POST /api/crm/contacts/bulk body {action: "apply_tag", tag_id: "vip", contact_ids: [...]} → Service aplica em batch → emite eventos.
3. Oportunidades de negócio
- CRM standalone: vender "Mandir CRM" como subset do Suite — competidor direto de HubSpot Free / Pipedrive Essential com diferencial IA + LGPD nativa.
- Vertical pipelines: templates pré-configurados por vertical (e-commerce, infoprodutor, consultoria).
- B2B com accounts hierárquicos: matriz/filial é diferencial sobre CRMs SMB.
- AI brief / next-action: já existem endpoints (
/contacts/{id}/ai-brief,/deals/{id}/ai-next-action) — empacotar como "AI Sales Assistant" premium.
4. Arquitetura interna
Arquivos
models.py(~640 linhas) — 17 tabelas.service.py(~2600 linhas) — lógica de negócio.routes.py— 85+ endpoints.scoring.py— 50+ event_types seeded com defaults.tasks.py—crm.sla_check.
Tasks Celery
| Task | Schedule | O que faz |
|---|---|---|
crm.sla_check | Beat | Notifica deals com stage.sla_days vencido (cria crm_notification) |
5. Tabelas (17)
Contatos / Tags / Eventos (4)
crm_contact ⭐
| Coluna chave | Notas |
|---|---|
tenant_id | idx |
email / phone / instagram_user_id | Lookup |
full_name / first_name / last_name | |
lifecycle_stage | subscriber / lead / customer / vendor |
source / entry_door | Origem |
utm_* (5 cols) | Snapshot original |
attribution_* (cadeia completa) | Sprint 1.A |
score / lead_score / engagement_score | Async via eventos |
purchase_intent_level / sentiment_trend / conversation_health_score | Preenchido por intelligence.run_relationship_health_task |
assigned_to | UUID staff |
email_opt_in / optout_email / optout_whatsapp / confirmed_at | LGPD |
is_active / is_blocked / blocked_reason | |
is_merged_into | Soft-merge target |
short_id | Humano-readable |
metadata_ | JSONB livre |
deleted_at | Soft-delete |
Constraint: UNIQUE(tenant_id, email). Índice (tenant_id, lifecycle_stage).
crm_tag + crm_contact_tag
m:n com applied_at. UNIQUE (contact_id, tag_id). CASCADE delete.
crm_contact_event
Fato do sistema (audit + scoring trigger).
| Coluna | Notas |
|---|---|
event_type | email.opened, billing.payment.received, etc. |
source_module | |
score_delta | Aplicado em crm_contact.score async |
metadata_ | JSONB |
occurred_at |
Deals / Pipeline (3)
crm_deal_stage: pipeline customizável,category(awareness/consideration/decision/negotiation),is_terminal(won/lost),sla_days.crm_deal: title, status (open/won/lost), value/currency, probability, contact_id, stage_id, account_id, billing_account_id, seller_entity_id, lost_reason, next_action.crm_activity: task/call/meeting/note vinculado a contact + deal.
Scoring + Custom Fields (3)
crm_scoring_rule: event_type, delta, label. Seeded com 50+ defaults.crm_custom_field_definition: entity (contact/deal), field_type (text/number/boolean/date/select/multiselect/url), options.crm_custom_field_value: value_text/number/bool/date/json. UNIQUE(field_id, contact_id)ou(field_id, deal_id).
Notifications + Webhooks + API Keys + Templates + Goals (5)
crm_notification: in-app per recipient.crm_webhook: outbound (eventos JSONB array).crm_api_key: key_prefix + key_hash UNIQUE, scopes JSONB.crm_email_template: body_text/html/design.crm_goal: OKR per assignee, metric, target_value, period.
Accounts (B2B) (2)
crm_account: name, legal_name, cnpj/cpf, account_type (holding/matriz/filial/autonomo), parent_account_id (self-FK), industry. UNIQUE(tenant_id, cnpj).crm_contact_account: m:n contact↔account com role (decisor/comprador/financeiro/tecnico/influenciador), is_primary.
Relacionamentos cross-módulo
| Direção | Outro módulo | Como |
|---|---|---|
| ↗ Lê | attribution | attach_attribution() em create_contact (lazy import) |
| ↘ Escreve | members | Members sync _sync_crm_lifecycle() (status → lifecycle_stage) |
| ↘ Escreve | intelligence | run_relationship_health popula purchase_intent_level, sentiment_trend, conversation_health_score |
| ↗ Lê eventos | email, whatsapp, billing, forms, diary | Via crm_contact_event + scoring.get_delta() |
| ↘ Emite events | intelligence, billing (dunning), todos | 10+ event_types |
6. API / Endpoints (85+)
Prefixo /api/crm. Lista compacta:
Contatos (12)
GET /contacts (search + filters), POST /contacts, GET /contacts/stats, POST /contacts/bulk (patch massivo, max 1000), GET /contacts/export.csv, POST /contacts/import (CSV, on_duplicate=skip|update), GET /contacts/{id}, PATCH /contacts/{id}, DELETE /contacts/{id} (soft), GET /contacts/{id}/context (drawer 360), GET /contacts/{id}/timeline (activities + events), GET /contacts/{id}/custom-fields.
Tags (5)
POST/DELETE /contacts/{id}/tags/{tag_id}, GET/POST/PATCH/DELETE /tags.
Deals (14)
GET /deals (filters), POST/PATCH/DELETE, POST /deals/bulk, GET /deals/export.csv, POST /deals/{id}/advance, /set-category, /reopen, /clone.
Stages + Pipelines (5)
GET/POST/PATCH/DELETE /deal-stages, GET /pipelines.
Activities (5)
GET/POST/PATCH/DELETE /activities, GET /activities/{id}.
Custom fields (4)
GET/POST /custom-fields, POST /custom-field-values, GET /{contacts|deals}/{id}/custom-fields.
Outros
- Email templates (5).
- Goals (4) — current_value + progress_pct calculados em tempo real.
- Notifications (3).
- Webhooks (5).
- API keys (3).
- Accounts (5).
- Contact↔Account links (3).
- Reports (6) — funnel, revenue forecast, activity summary, won/lost, pipeline velocity, rep performance.
- Global search (1) —
/search?q=&limit=. - AI assistants (2) —
/contacts/{id}/ai-brief,/deals/{id}/ai-next-action.
7. Eventos emitidos / consumidos
Emite
crm.contact.{created,updated,deleted}crm.deal.{created,stage_changed,won,lost,deleted}crm.activity.{created,updated,deleted}
Consome (via crm_contact_event)
50+ event_types seeded — email.opened, email.clicked, whatsapp.message.received, billing.payment.received, forms.submitted, etc.
8. Configuração
Env vars
| Var | Default | Propósito |
|---|---|---|
MANDIR_CRM_IMPORT_BATCH_SIZE | 100 | CSV import chunk |
MANDIR_CRM_GLOBAL_SEARCH_LIMIT | 50 | Limite query global |
9. Operações + Troubleshooting
Como criar tenant novo
Backend scoring.seed_for_tenant(tenant_id) popula 50+ scoring rules default. UI /admin/crm/settings mostra todas, permite ajustar deltas.
Sintoma: Score não atualizando
Causa: crm_contact_event não está sendo criado por algum módulo.
Diagnóstico: SELECT event_type, COUNT(*) FROM crm_contact_event WHERE tenant_id=... GROUP BY event_type;
Fix: verificar se módulo emit_event correto.
Sintoma: Bulk operation timeout
Causa: > 1000 registros. Fix: chunk no client.
10. Limitações e débitos técnicos
| # | Item |
|---|---|
| 1 | Soft-delete não cascade automático |
| 2 | Terminal stages bloqueiam PATCH |
| 3 | Scoring async |
| 4 | Custom fields duais (contact XOR deal) |
| 5 | Account hierarchy 1 nível |
| 6 | Attribution snapshot |
| 7 | Sem identity resolution via embeddings |
11. Histórico
Em prod desde Suite v2 (2026-05-04). Funcionalidades adicionadas iterativamente: accounts B2B, custom fields, scoring rules, AI brief.