Business (Catálogo + Conhecimento + Brain Config)
Status: 🟢 Estável Code: backend/app/modules/business UI: frontend/src/app/(admin)/[slug]/admin/business Última revisão deste doc: 2026-05-13 por Felipe + Claude Dependências fortes: crm (contact_id em todas as tabelas relacionadas a pessoa), council + agents (consomem tudo via tools)
1. Identidade
O que faz (uma frase)
Define o catálogo de produtos, base de conhecimento operacional, objeções/cotações/agendamentos por contato e configuração global do Cérebro do tenant (brand_voice, tópicos proibidos, horário de atendimento) — fonte de verdade que Conselho e Agentes consultam pra responder com consistência.
Por que existe (negócio)
Sem Business, agente IA inventa preço, mistura produtos, fala fora do tom da marca, não sabe horário de atendimento. Com Business:
- Catálogo único: Hari sempre cita o preço atualizado de "Mentoria Premium R$ 2.997 mensal" — nunca chuta.
- Brand voice cravada: "Tom direto, sem floreio, sem emoji" — todo agente respeita.
- Forbidden topics: "Não fale sobre política/religião/concorrentes" — proteção legal/reputacional.
- Knowledge base: FAQ "Como cancelo minha assinatura?" → agente responde sem inventar.
- Histórico de objeções: "Já disse 3x que preço é alto" → agente Hari ajusta abordagem.
- Cotações (
price_inquiry): "Cliente X perguntou preço de Y, ainda não converteu" → segmentação automática + retomada.
Por que existe (técnico)
Antes do Business, esses dados ficavam espalhados:
- Catálogo numa planilha externa (sem API).
- Brand voice no docs interno (não consumível por código).
- Cotações em Notion (manual).
Como módulo dedicado:
- Tabelas dedicadas com FK lógica para
crm_contact. - Endpoints CRUD acessíveis por UI + API + tools de IA.
- Schema versionado (Pydantic in/out garante contrato).
- Multi-tenant nativo (slugs unique per tenant).
- TenantBrainConfig: única linha por tenant — pull único pra montar system prompt.
Status atual
- Estável em prod desde Sprint 1.B+1.C (alembic 0095+0096, 2026-05-13). Ver memória [[brain-observatory-sprint-1bc]].
- 8 tools no Conselho consumindo dados business.
Próxima mudança planejada: Embeddings em knowledge_entry (pgvector) pra busca semântica; upload de PDFs em agent_resource com extração automática.
2. Cases de uso reais
Case 1: Hari cita preço de produto consultando catálogo
Situação: Lead pergunta "qual o valor da Mentoria Premium?".
Fluxo:
- Brain de Hari chama tool
read_products(is_active=true). - Tool retorna lista de
business_productcom preços. - Brain encontra "Mentoria Premium" → cita "R$ 2.997 mensal, com 2 sessões 1:1 + acesso à comunidade".
- Se tenant tem
description_mdrico, Brain incorpora na resposta.
Impacto: Sem inventar preço. Se o preço muda, basta editar business_product.price_brl e todo agente passa a citar o novo.
Case 2: Conselheiro Commercial vê histórico de cotações antes de propor ação
Situação: Felipe pergunta no Conselho "como vai a conversão de cotações?"
Fluxo:
- Conselheiro Commercial chama
read_contact_price_inquiriespor contato. - Tool retorna list de
price_inquirycomstatus(asked / converted / declined / ghosted) esummary_by_status. - Brain monta análise: "85 cotações no último mês, 12% converted, 23% ghosted (estes precisam followup)..."
- Propõe ação via
propose_action(intent=send_message, target_agent_slug=clara, target_contact=...)pra fazer followup das ghosted.
Impacto: Felipe aprova um botão e cliente recebe nudge personalizado.
Case 3: Agente Hari evita objeção já registrada
Situação: Lead tem objeção "preço alto" registrada no product_objection (severity=high, ainda unresolved).
Fluxo:
- Brain chama
read_contact_objections(contact_id, unresolved_only=true). - Detecta a objeção.
- Brain ajusta resposta: "Entendo que valor era preocupação. Tenho duas opções: parcelar em 12x ou começar com plano básico R$ 197...".
Impacto: Conversão não morre por agente "robô" repetindo o pitch que já não funcionou.
Case 4: Brain Config define tom da marca cross-agente
Situação: Felipe quer que TODOS os agentes (Hari, Clara, Aurora, etc.) parem de usar emoji.
Fluxo:
- Felipe edita em
/admin/business/brain-config:brand_voice_md = "Tom profissional, sem emoji, sem floreio". PUT /api/business/brain-configupsert emtenant_brain_config.- Todo agente que próximo turn chamar
read_brain_configrecebe o novo brand_voice. - System prompt do brain inclui: "## Voz da marca\nTom profissional, sem emoji, sem floreio".
Impacto: Mudança imediata em todos os agentes sem deploy.
3. Oportunidades de negócio
- Catalog-as-a-Service standalone: vender "Mandir Catalog" como API REST + UI pra empresas com produtos (e-commerce, infoprodutor) que não têm CRM próprio.
- Knowledge base integrada com FAQ público: transformar
knowledge_entryem FAQ pública (subdomainajuda.<tenant>.com.br) com search nativo. Eliminar dependência de Zendesk/HelpScout. - Marketplace de products templates: "pacote curso EAD" (knowledge + products + price tiers + objection responses) por vertical.
- Compliance LGPD pre-built:
forbidden_topics_mdcom templates legais (saúde, financeiro, infantil) — venda como "compliance starter pack". - AI training feedback loop: registrar objeções extraídas automaticamente de conversa → treinar agentes → fechamento maior.
- Resource library com S3: upload PDFs/docs pra
agent_resource.media_storage_path→ OCR + embeddings → agente envia anexo certo na hora certa.
Riscos comerciais:
- Catálogo desatualizado vira passivo (preço errado é dor pública). Mitigação: webhook de alteração + audit log.
- Knowledge base sem governança vira lixo. Mitigação: priority + last_updated.
4. Arquitetura interna
Sem Celery tasks. Sem adapters externos. Apenas modelos + service + routes.
Arquivos do módulo
| Arquivo | Propósito |
|---|---|
models.py | 7 tabelas + constantes (PRODUCT_KINDS, PRICE_INQUIRY_STATUSES, TENANT_CAPABILITIES, OBJECTION_CATEGORIES) |
routes.py | 23 endpoints REST |
schemas.py | Pydantic in/out + aliases backward compat (QuoteIn/QuoteOut = PriceInquiryIn/Out) |
__init__.py | exporta router |
5. Tabelas + relacionamentos
7 tabelas, prefixo varia (business_*, price_*, product_*, knowledge_*, agent_*, appointment, tenant_*).
business_product
Catálogo único do tenant.
| Coluna chave | Tipo | Notas |
|---|---|---|
tenant_id | UUID | idx |
slug | VARCHAR | UNIQUE per tenant |
name | VARCHAR | "Mentoria Premium" |
description_md | TEXT | Markdown |
kind | VARCHAR | digital / physical / service / subscription / package / course / other |
price_brl | DECIMAL | |
currency | VARCHAR | default BRL |
billing_cycle | VARCHAR | mensal / anual / uma_vez |
metadata / tags | JSONB | |
is_active | BOOL | Soft-delete via toggle |
Índices: (tenant, slug) UNIQUE, (tenant, is_active).
price_inquiry
Registro "X perguntou preço de Y" (alias QuoteIn/QuoteOut backward compat).
| Coluna chave | Tipo | Notas |
|---|---|---|
contact_id | UUID | FK lógica → crm_contact |
product_id | UUID | FK lógica → business_product |
deal_id | UUID | Opcional → crm_deal |
quoted_price_brl | DECIMAL | |
channel | VARCHAR | whatsapp (default) / email / web / phone |
status | VARCHAR | asked / converted / declined / ghosted |
sent_at / decided_at | TIMESTAMP(tz) | |
notes | TEXT |
Índices: (tenant, contact_id), (tenant, product_id, status).
Lifecycle: asked → {converted | declined | ghosted}. Imutável após decisão.
product_objection
Objeções/reclamações.
| Coluna chave | Tipo | Notas |
|---|---|---|
contact_id | UUID | FK lógica |
product_id | UUID | Nullable (pode ser objeção genérica) |
quote_id | UUID | Opcional → price_inquiry |
category | VARCHAR | price / timing / trust / feature_missing / competitor / logistics / personal / other |
text | TEXT | |
severity | VARCHAR | low / medium / high |
source | VARCHAR | manual (default) / extracted / inferred |
resolved_at / resolution_md |
Índices: (tenant, contact_id), (tenant, category).
knowledge_entry
Wiki/FAQ/manuais internos.
| Coluna chave | Tipo | Notas |
|---|---|---|
slug | VARCHAR | UNIQUE per tenant |
title / content_md | ||
kind | VARCHAR | article (default) / faq / procedure / template |
source_kind / source_ref | notion / gdrive / confluence / manual (URL/ID externo opcional) | |
tags | JSONB array | |
audience | VARCHAR | all (default) / internal / coach |
language | VARCHAR | default pt-BR |
token_count | INT | Estimativa simples (word count) |
priority | INT | 0-100, default 50, ord DESC |
is_active | BOOL |
Índices: (tenant, slug) UNIQUE, (tenant, kind, is_active).
Busca: ILIKE em title + content_md (sem embeddings hoje).
agent_resource
Anexos que agentes podem enviar (PDFs, links, modelos).
| Coluna chave | Tipo | Notas |
|---|---|---|
slug | VARCHAR | UNIQUE per tenant |
name / description | ||
kind | VARCHAR | pdf / link / template / video / other |
url | TEXT | Opcional |
media_storage_path | TEXT | S3/blob opcional |
tags | JSONB | |
when_to_send_md | TEXT | Instruções pra IA |
usage_count | INT | Contador |
appointment
Agendamentos: aulas, consultas, terapias, eventos.
| Coluna chave | Tipo | Notas |
|---|---|---|
contact_id | UUID | FK lógica |
kind | VARCHAR | class (default) / consultation / therapy / event / other |
title | Opcional | |
scheduled_at | TIMESTAMP(tz) | |
duration_minutes | INT | |
status | VARCHAR | scheduled (default) / completed / no_show / cancelled |
no_show_reason | Quando aplicável | |
notes / metadata |
Índices: (tenant, contact_id, scheduled_at), (tenant, status, scheduled_at).
tenant_brain_config
Config global do "Cérebro" do tenant. Única linha por tenant.
| Coluna chave | Tipo | Notas |
|---|---|---|
tenant_id | UUID | UNIQUE |
vertical | VARCHAR | Deprecated — usar capabilities |
capabilities | JSONB array | sells_physical_products / sells_digital_products / offers_scheduled_service / offers_subscription / offers_hospitality / hosts_events |
brand_voice_md | TEXT | "Tom direto, sem emoji" |
forbidden_topics_md | TEXT | "Não fale sobre política, religião, concorrentes" |
business_hours | JSONB | {"mon": "09:00-17:00", "tue": null, ...} |
llm_budget_daily_usd | DECIMAL | Opcional cap diário |
default_council_model | VARCHAR | Override global do modelo |
auto_apply_patterns | BOOL | default false |
modules_enabled | JSONB array | Override de quais módulos do Suite estão visíveis |
Upsert-only: PUT /api/business/brain-config (cria se não existe, atualiza se sim).
Relacionamentos cross-módulo
| Direção | Outro módulo | Como | Por quê |
|---|---|---|---|
| ↗ Lê | crm_contact | contact_id em price_inquiry/objection/appointment | Contexto do cliente |
| ↗ Lê | crm_deal | deal_id opcional em price_inquiry | Vínculo com pipeline |
| ↘ Escreve via tools | council | 8 tools (read_knowledge, read_products, etc.) | Conselho consulta |
| ↘ Escreve via tools | agents | brain_config + products + objections | System prompt + ajuste de resposta |
| ↗ Lê | intelligence (cross_module) | objections + price_inquiries + appointments | Refresh L1 (churn risk, no_show_rate) |
6. API / Endpoints (23)
Prefixo /api/business. Todos exigem require_tenant_admin.
Products (5)
| Método | Rota | O que faz |
|---|---|---|
| GET | /products | Lista (filtros: is_active, kind, limit) |
| POST | /products | Cria → 201 |
| PATCH | /products/{id} | Update parcial |
| DELETE | /products/{id} | Soft (toggle is_active) ou hard |
Price Inquiries (3)
| Método | Rota | O que faz |
|---|---|---|
| GET | /price-inquiries | Lista (filtros: contact_id, product_id, status, limit) |
| POST | /price-inquiries | Cria → 201 |
| POST | /price-inquiries/{id}/decide | Transição: converted / declined / ghosted (seta decided_at) |
Objections (2)
| Método | Rota | O que faz |
|---|---|---|
| GET | /objections | Lista (filtros: contact_id, category, unresolved, limit) |
| POST | /objections | Cria |
Knowledge (4)
| Método | Rota | O que faz |
|---|---|---|
| GET | /knowledge | Lista (filtros: kind, is_active, limit; ord priority DESC) |
| POST | /knowledge | Cria (calcula token_count) |
| PATCH | /knowledge/{id} | Update (recalcula token_count se content mudar) |
| DELETE | /knowledge/{id} | Hard delete |
Resources (2)
| Método | Rota | O que faz |
|---|---|---|
| GET | /resources | Lista (filtros: kind, is_active) |
| POST | /resources | Cria |
Appointments (3)
| Método | Rota | O que faz |
|---|---|---|
| GET | /appointments | Lista (filtros: contact_id, status, limit) |
| POST | /appointments | Cria → 201 |
| PATCH | /appointments/{id} | Update (status, no_show_reason, notes, scheduled_at) |
Brain Config (1)
| Método | Rota | O que faz |
|---|---|---|
| GET | /brain-config | Read (null se não existe) |
| PUT | /brain-config | Upsert |
Agregados por Contact (3)
| Método | Rota | O que faz |
|---|---|---|
| GET | /price-inquiries-by-contact?contact_id=UUID | count + summary_by_status + lista |
| GET | /objections-by-contact?contact_id=UUID | count + objections |
| GET | /appointments-by-contact?contact_id=UUID | count + summary_by_status + no_show patterns |
7. Tools que outros módulos consomem
Conselho usa 8 tools read-only (em council/cross_module.py):
| Tool | Função interna | O que retorna |
|---|---|---|
read_knowledge(kind, tags, audience, limit, cap=20) | cross_module.read_knowledge | Entries ordenadas por priority DESC |
search_knowledge(query, limit, cap=20) | cross_module.search_knowledge | ILIKE em title + content_md, excerpt 800 chars |
read_products(is_active=true, kind, limit, cap=100) | cross_module.read_products | Catálogo completo com preços |
read_contact_price_inquiries(contact_id, limit, cap=100) | cross_module.read_contact_price_inquiries | Cotações + summary_by_status |
read_contact_objections(contact_id, unresolved_only=false, limit, cap=100) | cross_module.read_contact_objections | Objeções com filtro de resolução |
read_contact_appointments(contact_id, window_days, status, limit, cap=100) | cross_module.read_contact_appointments | Agenda com análise no-show |
read_brain_config() | cross_module.read_brain_config | {found, brand_voice_md, forbidden_topics_md, business_hours, default_council_model, auto_apply_patterns, modules_enabled} |
read_agent_resources() | TBD | Lista de PDFs/links/templates |
Agentes usam principalmente: read_brain_config (no system prompt), read_products (citar preço), read_contact_objections (ajustar abordagem), read_contact_price_inquiries (lembrar cotações abertas).
8. Search/RAG
- knowledge_entry: busca textual simples (ILIKE). SEM embeddings hoje.
- Futuro: pgvector pra busca semântica em KnowledgeEntry; embedding no create/patch.
- FTS: não ativo. ILIKE suficiente para corpus pequeno (<10k docs).
9. Configuração
Env vars
Nenhuma específica do módulo. Usa env globais (MANDIR_CRYPTO_MASTER_KEY para futuras features cifradas).
Kill switches
- Por entidade:
is_activeem product / knowledge / resource (toggle). - Catálogo deletado: soft-delete via
is_active=false(preserva histórico em price_inquiries vinculadas).
10. Operações
Como adicionar produto novo
UI: /admin/business/products → "+ Novo produto" → form (slug, name, kind, price_brl, billing_cycle, description_md).
API: POST /api/business/products body com mesmos campos.
Como atualizar brand voice
UI: /admin/business/brain-config → form com textarea brand_voice_md, forbidden_topics_md, business_hours JSON, capabilities checkboxes.
API: PUT /api/business/brain-config (upsert).
Troubleshooting
Sintoma: Agente cita preço errado
Causa: Cache do tool read_products ou produto antigo is_active=true.
Diagnóstico:
SELECT slug, name, price_brl, is_active FROM business_product WHERE tenant_id=...;
Fix: atualizar preço; se outro produto antigo está ativo, marcar is_active=false.
Sintoma: Agente ignora forbidden_topic
Causa: Brain não chamou read_brain_config no turn (ocorre se contexto já tem conteúdo prévio sem essa info).
Diagnóstico: verificar agents_run.output_payload.tool_calls — busca por read_brain_config.
Fix: próximo turn deve chamar; se persistir, verificar se brain_config não está NULL.
11. Métricas e observabilidade
Logs estruturados-chave
| Logger key | Quando emite |
|---|---|
business.product.created | POST /products |
business.knowledge.updated | PATCH /knowledge/{id} |
business.brain_config.updated | PUT /brain-config |
business.price_inquiry.decided | POST /price-inquiries/{id}/decide |
Sem dashboards próprios — KPIs aparecem em /admin/intelligence/contact-attributes (no_show_rate, objection_count).
12. Limitações e débitos técnicos conhecidos
| # | Item | Impacto | Plano |
|---|---|---|---|
| 1 | Sem embeddings em knowledge_entry | Mid — busca só ILIKE | pgvector planejado |
| 2 | Appointments hard-deleted | Low — sem audit history | Tabela appointment_event futuro |
| 3 | Backward compat QuoteIn/QuoteOut | Low — código antigo ainda usa aliases | Sem urgência de remover |
| 4 | Currency hardcoded BRL | Low — multi-moeda futuro | TBD |
| 5 | business_hours formato livre JSONB | Low — sem validação no backend | UI valida |
| 6 | AgentResource sem upload no UI | Mid — só URL externa hoje | Implementar S3 upload |
| 7 | Sem extraction automática de objections | Mid — manual hoje | Sub-agent futuro: extrair de WA threads |
| 8 | knowledge_entry sem versionamento | Mid — overwrite total | Adicionar history table |
| 9 | price_inquiry status enum hard-coded | Low — adicionar status novo exige migration | OK |
| 10 | scope_observes("business") mandatory pra tools | Low — se conselheiro está fora do scope, tool retorna _omitted("business") | Documentado |
13. Histórico relevante
- 2026-05-13 (alembic 0095+0096) — Sprint 1.B+1.C: módulo business completo (knowledge + products + price_inquiries + objections + appointments + brain_config) deployado. 8 tools no Conselho. Memória [[brain-observatory-sprint-1bc]].
Apêndices
A. Capabilities canônicas
| Capability | Significado |
|---|---|
sells_physical_products | E-commerce físico |
sells_digital_products | Cursos, ebooks, software |
offers_scheduled_service | Consultoria, terapia, aula |
offers_subscription | Assinatura recorrente |
offers_hospitality | Hotel, retiro, evento presencial |
hosts_events | Lives, workshops |
Determina quais templates de agente fazem sentido (memória [[agentes-preenchidos-2026-05-13]]).
B. Glossário
- PriceInquiry / Quote: mesma coisa (alias backward compat). Cotação = registro de pergunta de preço.
- Brain config: config global do "Cérebro" do tenant — única linha por tenant.
- Brand voice: instrução narrativa de tom da marca, injetada em todo system prompt.
- Forbidden topics: lista de tópicos a evitar (proteção legal/reputacional).
- Capability: marcador funcional do que o tenant faz/vende. Determina quais templates de agente sugerir.
- Knowledge entry: artigo/FAQ/procedimento. Conteúdo markdown consultável por agentes.
- Agent resource: anexo (PDF/link/template) que agente pode enviar.