name: chatwoot-painel-kanban-integration
description: "Build a Kanban dashboard inside Chatwoot that mirrors Painel do Corretor stages, with bidirectional sync: Chatwoot webhook → Painel API, and Painel polling → Chatwoot labels."
category: devops
Chatwoot + Painel do Corretor — Kanban Integration
Architecture
`
┌─────────────────────┐ webhook ┌──────────────────────────────┐
│ Chatwoot (VPS) │ ───────────────► │ Edge Function chatwoot-webhook │
│ conversation_new │ │ → INSERT chatwoot_painel_map │
└─────────────────────┘ │ → POST /api/crm/negocios │
▲ └──────────────────────────────┘
│ polling 15min │
│ ┌──────────────────────────────────┘
│ ▼
┌─────────────────────┐ polling ┌──────────────────────────────┐
│ Painel do Corretor │ ◄─────────────── │ Edge Function sync-painel-to-chatwoot │
│ (api.paineldocorretor) │ │ → GET /api/crm/negocios │
└─────────────────────┘ │ → PUT conversation labels │
│ └──────────────────────────────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────────────────────────┐
│ Chatwoot Kanban Dashboard App (React, served at /kanban/) │
│ 6 columns: Novos Leads / Qualificação / Reunião agendada / │
│ Proposta Enviada / Remarketing / Fechado │
└──────────────────────────────────────────────────────────────────┘
`
Etapas (Painel stages) — 10 colunas no Kanban
Etapas (Panel stages) — 10 colunas no Kanban
`
Novos Leads | Qualificação | Remarketing | Reunião agendada |
Proposta Enviada p/cliente | Análise Seguradora | Aguardando Pagamento |
Fechado | Declinado | Sem Interesse
`
Nota: etapa é TEXT (nome da etapa, ex: "Novos Leads"). Mapeamento:
`
1 → novos_leads
2 → aguard_retorno
3 → conversao
4 → aguard_aceitacao
5 → proposta_enviada
6 → aguard_proposta
7 → aguard_seguro
8 → vendido
9 → sem_interesse
`
Cores por etapa:
`
1 (Novos Leads): #6B7280 (cinza)
2 (Aguard. Retorno): #F59E0B (âmbar)
3 (Conversão): #3B82F6 (azul)
4 (Aguard. Aceitação):#8B5CF6 (roxo)
5 (Proposta Enviada): #06B6D4 (cyan)
6 (Aguard. Proposta): #EAB308 (amarelo)
7 (Aguard. Seguro): #10B981 (verde)
8 (Vendido): #22C55E (verde forte)
9 (Sem Interesse): #EF4444 (vermelho)
`
Database Table
`sql
CREATE TABLE public.chatwoot_painel_map (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
account_id INTEGER DEFAULT 1,
contato TEXT NOT NULL, -- nome do lead
telefone TEXT, -- normalizado: só números, sem + (Chatwoot tem, Panel NÃO)
email TEXT, -- Panel tem email, Chatwoot NÃO
seguimento TEXT, -- ex: "SEGURO DE VIDA"
etapa TEXT DEFAULT 'Novos Leads', -- nome da etapa no Panel (ex: "Novos Leads", "Remarketing")
etapa_anterior TEXT,
origem TEXT, -- fonte do lead
ultima_interacao TIMESTAMPTZ,
painel_negocio_id UUID, -- ID do lead no Panel (para PUT etapa)
chatwoot_conversation_id BIGINT,
criado_em TIMESTAMPTZ DEFAULT now(),
atualizado_em TIMESTAMPTZ DEFAULT now(),
UNIQUE(painel_negocio_id)
);
`
Coluna correta: contato (NÃO nome), telefone (NÃO chatwoot_contact_phone), etapa é INTEGER (não TEXT).
RLS Policies (required for REST API access)
`bash
npx supabase db query "CREATE POLICY \"Allow all read\" ON public.chatwoot_painel_map FOR SELECT USING (true);" --linked
npx supabase db query "CREATE POLICY \"Allow all insert\" ON public.chatwoot_painel_map FOR INSERT WITH CHECK (true);" --linked
npx supabase db query "CREATE POLICY \"Allow all update\" ON public.chatwoot_painel_map FOR UPDATE USING (true);" --linked
`
Edge Functions
chatwoot-webhook
- - Endpoint:
https://PROJECT.supabase.co/functions/v1/chatwoot-webhook - - Trigger: Chatwoot webhook (registered via API)
- - Events:
conversation_created,conversation_status_changed - - Auth: needs
apikey + Authorization: Bearer ANON_KEYheaders - - Container Chatwoot usa imagem customizada
548710d17cd7(não a padrão chatwoot) - - Vue arquivos montados via volume em
/app— mudanças persistem mas requerem rebuild - - Feature flags Chatwoot usam bits (potências de 2): BIT 0=1, BIT 1=2, BIT 9=512
- - Kanban route usa
DashboardAppFrame(mesmo componente que Dashboard Apps nativos) - -
location /kanban/assets/comalias /var/www/.../dist/→ requisita/kanban/assets/index-CfPsvTx-.js→ mapeia paradist/assets/index-CfPsvTx-.js→ 404 (arquivo não existe lá) - -
location /kanban/assets/comalias /var/www/.../dist/assets/→ requisita/kanban/assets/index-CfPsvTx-.js→ mapeia paradist/assets/assets/index-CfPsvTx-.js→ 404 (duplica/assets/) - - Define
frameSrccomo a URL registrada do dashboard app - - Envia postMessage
{ message: 'fetch_token', webhookUrl }para o iframe - - Espera resposta do iframe com
{ message: 'form_toggle', data: { id, authToken } } - - Se o iframe não responder, o frame fica em branco permanentemente
- - Menu "Kanban Comercial" no sidebar → existe em
Sidebar.vuelinha ~506 ✅ - -
SIDEBAR.KANBANlocale pt_BR → existe ✅ - - Rota
kanban_comercial→ existe emkanban.routes.js✅ - - Standalone
/kanban/→ funciona ✅ - - Chatwoot: 1.098 contatos únicos com telefone
- - Panel
crm_leads_duplicate: 1.000 leads (942 "Novos Leads", 36 "Remarketing", 17 "Sem Interresse") - - React + TypeScript + Vite + @hello-pangea/dnd
- - Served at
/kanban/via nginx alias on Chatwoot VPS - - Supabase Edge Functions (Deno)
- - Chatwoot REST API v1
- - Kanban app:
/var/www/chatwoot-kanban/ - - Edge Functions:
/var/www/comercialrs/supabase/functions/chatwoot-webhook/ - - nginx config:
/etc/nginx/sites-available/chatwoot - - Chatwoot token:
2AfB8VhS79FTmxG1dL8774RJ - - Chatwoot account: 1
- - Painel ApiKey:
iMHa6T8M0XfRuS7AJep1lcgOwUdCgZ6KV7a9uWXV4Bg0 - - Painel base URL:
https://api.paineldocorretor.net/api - - Supabase anon key suffix:
ckswk(NÃOcleswk— key comcleswkdá 401 Invalid API key em todas as tabelas) - - Key completa:
eyJ_REDACTED_SUPABASE - - crm_leads_duplicate: 5.181 regs (synced 1min). chatwoot_painel_map: 5.032 regs (99% sem_interesse, NÃO é atualizado pelo workflow atual — só o workflow n8n ID fuVLvmFke1C7GskY popula crm_leads_duplicate)
- - Kanban EM BRANCO: app em /kanban/ mostra página vazia — o dist JS está pequeno (292KB) e o Supabase anon key com suffix
cleswkdá 401 em todas as requests. Corrigir key parackswkprimeiro. - - Supabase project:
dauftiqcvgaydddoxqhh
Webhook registration (via Chatwoot API):
`bash
curl -X POST "https://chat.rochasalesseguros.com.br/api/v1/accounts/1/webhooks" \
-H "api_access_token: CHATWOOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"webhook": {
"url": "https://PROJECT.supabase.co/functions/v1/chatwoot-webhook",
"events": ["conversation_created", "conversation_status_changed"],
"account_id": 1,
"inbox_id": 16
}
}'
`
Chatwoot Dashboard App registration
Funciona via API (não é preciso manual):
`bash
curl -X POST "https://chat.rochasalesseguros.com.br/api/v1/accounts/1/dashboard_apps" \
-H "api_access_token: CHATWOOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Kanban ComercialRS",
"content": [{"type": "frame", "url": "https://chat.rochasalesseguros.com.br/kanban/"}]
}'
`
Conteúdo deve ser [{type: "frame", url: "..."}] (array com type: "frame").
Importante: Dashboard Apps aparecem apenas em Settings → Integrations → Dashboard Apps — NÃO aparecem no menu lateral do sidebar no Chatwoot self-hosted 4.12.1.
Criar labels no Chatwoot (via API, 10 labels)
`bash
for etapa in "novos_leads" "qualificacao" "remarketing" "reuniao_agendada" "proposta_enviada" "analise_seguradora" "aguardando_pagamento" "fechado" "declinado" "sem_interesse"; do
curl -s -X POST "https://chat.rochasalesseguros.com.br/api/v1/accounts/1/labels" \
-H "api_access_token: CHATWOOT_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"title\": \"etapa-$etapa\"}"
done
`
Chatwoot Labels (for etapa sync)
Painel etapa → Chatwoot label (formato: etapa-NOME):
`
novos_leads → etapa-novos_leads
qualificacao → etapa-qualificacao
reuniao_agendada → etapa-reuniao_agendada
proposta_enviada → etapa-proposta_enviada
remarketing → etapa-remarketing
analise_seguradora → etapa-analise_seguradora
aguardando_pagamento → etapa-aguardando_pagamento
fechado → etapa-fechado (também resolve conversa)
declinado → etapa-declinado (também resolve conversa)
sem_interesse → etapa-sem_interesse
`
Kanban no Sidebar — Solução Alternativa (Self-hosted)
Chatwoot self-hosted NÃO expõe Dashboard Apps como itens de menu no sidebar — isso é exclusivo do Chatwoot Cloud. O workaround é adicionar uma rota interna no Vue SPA que carrega o iframe.
Arquivos a criar/modificar no container Chatwoot (root-rails-1):
1. Locale (tradução do menu):
Arquivo: /app/app/javascript/dashboard/i18n/locale/pt_BR/settings.json
Adicionar na seção SIDEBAR:
`json
"KANBAN": "Kanban Comercial"
`
2. Rota do Kanban:
Arquivo: /app/app/javascript/dashboard/routes/dashboard/kanban/kanban.routes.js
`javascript
import { frontendURL } from '../../../helper/URLHelper';
const meta = { permissions: ['administrator', 'agent'] };
export const routes = [{
path: frontendURL('accounts/:accountId/kanban'),
name: 'kanban_comercial',
component: () => import('./pages/KanbanPage.vue'),
meta,
}];
`
3. Página wrapper com iframe:
Arquivo: /app/app/javascript/dashboard/routes/dashboard/kanban/pages/KanbanPage.vue
`vue
import DashboardAppFrame from 'dashboard/components/widgets/DashboardApp/Frame.vue';
export default {
components: { DashboardAppFrame },
data: () => ({ title: window.I18n.t('SIDEBAR.KANBAN') }),
};
`
4. Registrar rota no dashboard.routes.js:
`javascript
import { routes as kanbanRoutes } from './kanban/kanban.routes';
// ... dentro do children array:
...kanbanRoutes,
`
5. Adicionar item ao sidebar (Sidebar.vue):
No computed navigationItems, adicionar:
`javascript
{
icon: 'kanban',
label: this.$t('SIDEBAR.KANBAN'),
to: frontendURL(accounts/${this.accountId}/kanban),
}
`
6. Rebuild assets:
`bash
docker exec root-rails-1 bundle exec rails assets:precompile
ou restart:
docker restart root-rails-1
`
Notas importantes
nginx — Configuração para App Estático no Chatwoot VPS
O nginx no host (NÃO dentro do container Docker) serve tanto o Chatwoot Rails quanto o app Kanban estático. Problemas críticos descobertos por tentativa e erro:
Problema 1: X-Frame-Options NÃO bloqueia iframes no mesmo domínio
X-Frame-Options: SAMEORIGIN permite iframes no mesmo domínio. Não precisa desabilitar.
`bash
curl -sI https://chat.rochasalesseguros.com.br/ | grep -i x-frame
SAMEORIGIN = OK para iframe no mesmo domínio
`
Problema 2: Assets com hash do Vite —location alias com /kanban/assets/ dá 404
O problema real: O Vite build outputs index.html com src="/kanban/assets/index-CfPsvTx-.js" (hash reference), mas o arquivo JS está fisicamente em dist/index-CfPsvTx-.js (raiz do dist, NÃO em dist/assets/).
Dois erros comuns:
Solução CORRETA — SEM location /kanban/assets/ separado:
`nginx
location /kanban/ {
alias /var/www/chatwoot-kanban/dist/;
try_files $uri $uri/ /kanban/index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
`
O alias /var/www/.../dist/ com try_files $uri faz o nginx procurar dist/kanban/assets/index-CfPsvTx-.js → que existe porque o Vite outputou o JS na raiz do dist como index-CfPsvTx-.js E o HTML referencia /kanban/assets/index-CfPsvTx-.js. Wait — isso ainda não fecha.
Entender como o Vite organiza os assets:
`
dist/
index.html ← referencia src="/kanban/assets/index-CfPsvTx-.js"
index-CfPsvTx-.js ← JS está NA RAIZ do dist, NÃO em dist/assets/
`
Com base: '/kanban/' no vite.config.ts, o HTML gerado referencia /kanban/assets/index-XXX.js. Mas o arquivo físico está em dist/index-XXX.js (raiz). O alias /var/www/chatwoot-kanban/dist/ com try_files $uri interpreta $uri como /kanban/assets/index-CfPsvTx-.js e procura dist/kanban/assets/index-CfPsvTx-.js — que NÃO existe.
Solução REAL — rebuild após verificar base em vite.config.ts:
`bash
Verificar vite.config.ts — base DEVE ser '/kanban/' e outDir 'dist'
cat /var/www/chatwoot-kanban/vite.config.ts
export default defineConfig({ base: '/kanban/', build: { outDir: 'dist' } })
Rebuild com Node 20 (NÃO usar Node 18 — causa build incompleto)
source ~/.nvm/nvm.sh && nvm use 20 && npm run build
`
Verificar estrutura do dist:
`bash
ls /var/www/chatwoot-kanban/dist/
Deve mostrar: index.html index-CfPsvTx-.js (JS na RAIZ, não em assets/)
`
Se o JS estiver na raiz (dist/index-CfPsvTx-.js) mas o HTML referencia /kanban/assets/, o nginx NÃO consegue servir corretamente com alias simples. Solução: fazer o rebuild e verificar onde o Vite coloca o JS.
Se Vite coloca JS em dist/assets/ (com subdir assets/):
`nginx
location ~ ^/kanban/assets/(.*)$ {
alias /var/www/chatwoot-kanban/dist/assets/$1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
location /kanban/ {
alias /var/www/chatwoot-kanban/dist/;
try_files $uri $uri/ /kanban/index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
`
Se Vite coloca JS na RAIZ do dist:
`nginx
location /kanban/ {
alias /var/www/chatwoot-kanban/dist/;
try_files $uri $uri/ /kanban/index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
`
Testar e recarregar:
`bash
nginx -t && nginx -s reload
`
Verificar que os assets estão sendo servidos:
`bash
Listar assets no dist
ls /var/www/chatwoot-kanban/dist/
Testar acesso direto ao JS
curl -sI https://chat.rochasalesseguros.com.br/kanban/assets/index-CfPsvTx-.js | head -1
Deve retornar 200, não 404
`
Configuração nginx CONFIRMADA FUNCIONANDO:
`nginx
location /kanban/assets/ {
alias /var/www/chatwoot-kanban/dist/;
try_files $uri =404;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
location /kanban/ {
alias /var/www/chatwoot-kanban/dist/;
try_files $uri $uri/ /kanban/index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
`
Verificado: curl -sI "https://chat.rochasalesseguros.com.br/kanban/assets/index-CfPsvTx-.js" → HTTP/2 200 ✅
佈署 após modificar arquivos Vue no container
Mudanáncias em arquivos Vue dentro do container (/app/app/javascript/dashboard/...) não são aplicadas automaticamente em produção. Seguir esta ordem:
`bash
1. Modificar os arquivos Vue dentro do container
docker exec root-rails-1 sh -c "sed -i '...' /app/app/javascript/dashboard/..."
2. Recompilar assets (ruby precisa estar ativo)
docker exec root-rails-1 sh -c "RAILS_ENV=production bundle exec rails assets:precompile 2>&1 | tail -20"
⚠️ Demora 2-3 minutos
3. Reiniciar o container para aplicar
docker restart root-rails-1
4. Verificar no browser (Ctrl+F5 para forçar reload)
`
###Rebuild do Kanban app (Node 20 obrigatório)
O Kanban app React/Vite precisa ser buildado com Node 20 no VPS:
`bash
No host (não dentro do container Docker):
cd /var/www/chatwoot-kanban
source ~/.nvm/nvm.sh && nvm use 20 && npm run build
⚠️ Node 18 causa build incompleto → página em branco
`
Chatwoot DashboardApp — Importante
Dashboard Apps registrados via Settings → Integrations → Dashboard Apps aparecem como apps externos, NÃO no menu lateral do sidebar no Chatwoot self-hosted 4.12.1. O workaround da rota interna Vue (kanban_comercial) é necessário para adicionar ao sidebar.
O componente DashboardAppFrame (usado por KanbanPage.vue):
App Kanban standalone precisa responder ao postMessage OU usar query params diretos:
`
https://chat.rochasalesseguros.com.br/kanban/?account_id=1&token=
`
Sem isso, o iframe carrega o HTML mas não autentica o usuário.
iframe no Chatwoot — SOLUÇÃO CONFIRMADA (maio 2026)
Problema resolvido: Kanban aparecia no menu mas iframe ficava em branco. Causa: DashboardAppFrame.vue usa v-if="hasOpenedAtleastOnce" que só vira true via watcher de isVisible. Watchers de Vue não disparam na inicialização — passam true estaticamente e o watcher nunca muda → iframe nunca aparece.
Solução (confirmada funcionando em produção): Modificar KanbanPage.vue:
`vue
data() { return { isVisible: false }; },
mounted() {
this.$nextTick(() => { setTimeout(() => { this.isVisible = true; }, 100); });
}
`
Inicializa false, muda para true após 100ms → watcher dispara → iframe aparece.
Deploy:
`bash
1. Copiar arquivo
cat /tmp/KanbanPage.vue | docker exec -i root-rails-1 sh -c 'cat > /app/app/javascript/dashboard/routes/dashboard/kanban/pages/KanbanPage.vue'
2. Recompilar assets (2-3 min)
docker exec root-rails-1 sh -c 'RAILS_ENV=production bundle exec rails assets:precompile 2>&1 | tail -3'
3. Restart
docker restart root-rails-1 && sleep 30
`
Verificações prévias (já OK neste projeto):
Limitações Conhecidas
1. Painel GET/PATCH ausentes — GET /api/crm/negocios retorna 405 (Cloudflare), PATCH não existe (500). Sync bidirecional completo depende da Trindade adicionar esses endpoints.
2. Chatwoot self-hosted — Dashboard Apps não aparecem no sidebar (cloud-only). Solução via rota Vue + iframe ✅ (resolvido).
3. Kanban move no Chatwoot — funciona e cria registro no chatwoot_painel_map. Mover etapa no Kanban tenta PATCH /api/crm/negocios/{id} → falha (500) até Trindade implementar.
4. Migração de leads existentes — não é possível via API (sem GET). Workaround: planilha exportada do Painel importa via script manual.
Cross-Reference Challenge (CRÍTICO)
Chatwoot contacts: têm telefone (+55XXXXXXXXXXX), NÃO têm email
Panel leads (crm_leads_duplicate): têm email, NÃO têm telefone (só 43/1000 têm telefone)
→ Cruzar por email, não por telefone. Adicionar chatwoot_contact_email ao map.
- Chatwoot: buscar contatos com email via GET /api/v1/accounts/1/contacts?per_page=100
- Panel: Contato.Email em crm_leads_duplicate
- Cruzar: email do Panel == email do Chatwoot
Dados reais (maio 2026):
- Com telefone: 43
- Só email: 724
- Sem contato: 233
Painel GraphQL — Dicas Críticas (descobertas em produção, maio 2026)
telefones é um escalar [String!]!, NÃO um objeto:
`graphql
ERRADO — "selections on scalars are never allowed"
contato { telefones { numero } }
CERTO — telefones é array de strings
contato { telefones } # retorna ["551199999999", "1133334444"]
`
Query completa para buscar TODOS os leads por etapa:
`graphql
query FetchAllLeads($etapa1: NegociosFilterInput!) {
negocios_1: negociosElastic(request: $etapa1) {
quantidade
items {
id nome contato { id nome email telefones }
etapa { id nome } produto { nome }
etiquetas { nome } valor
}
}
}
use take: 10000 para buscar todos (não 10 como no painel)
variáveis: { "busca": null, "take": 10000, "conditions": [{"field": "etapaId", "value": "UUID-DA-ETAPA"}] }
`
Etapa UUIDs (confirmados em produção):
`
Novos Leads: 60008110-e9b1-4512-b514-ab1539ea1a1e
Qualificação: 885d4f95-99e5-42a0-8150-b494c42c0e34
Remarketing: 6699997d-6978-4679-8b4c-eaab9917fefd
Reunião Agendada: 4646a3e9-b21a-4339-b75b-6997fa3dbb76
Proposta Enviada: 4e61480b-b4dc-4b80-b0c8-dd3ddf8e43b2
Análise Seguradora: 29511da1-1e40-4e9c-8b14-a268ccbdc1d0
Aguardando Pgto: 2584da59-4c1e-4fcb-8cba-8374acd896a7
Negócios Fechados: df8db361-5b4c-4e66-baac-afcfd9fbe50a
Declinado: 70150b84-6864-4cff-aae1-83195a182d8d
Sem Interesse: 63f4b9df-55d6-4ff0-a42c-af5ff2ff367b
`
Populando chatwoot_painel_map via REST API (5.000+ registros)
Problemas conhecidos e soluções:
1. NOT NULL constraint em chatwoot_contact_phone: leads sem telefone precisam de placeholder único
`python
phone = normalize_phone(telefones)
if not phone:
phone = f"SEMFONE-{uuid.uuid4().hex[:8]}" # ex: SEMFONE-a3f2b1c4
`
2. UNIQUE constraint em chatwoot_contact_phone: múltiplos leads compartilham telefone
- deduplicar por painel_negocio_id (não por telefone)
- leads sem telefone → placeholder único com UUID
- leads com telefone existente → suffix UUID: 55119999-{uuid4}
3. PostgREST paginação: retorno máximo de 1.000 registros por запрос
`python
# Usar offset para buscar todos
for offset in range(0, 10000, 1000):
req = f"{URL}/rest/v1/table?limit=1000&offset={offset}"
`
4. Inserir em lotes de 500 via POST REST com Prefer: return=minimal
Painel API — Endpoints Confirmados
PUT etapa (CONFIRMADO por n8n workflow "Google Calendar → Atualizar Etapa CRM"):
`
PUT https://api.paineldocorretor.net/api/crm/negocios/etapas/{ETAPA_ID}
Headers: ApiKey: iMHa6T8M0XfRuS7AJep1lcgOwUdCgZ6KV7a9uWXV4Bg0
Body: [negocio_uuid] ← ARRAY de UUIDs, ex: ["uuid-do-lead"]
`
ETAPA_ID — encontrado nos nodes JSON do workflow n8n OM3XQcDRjYUB69mL:
`
Consultar n8n DB: docker exec n8n-postgres psql -U n8n -d n8n
→ SELECT nodes FROM workflow_entity WHERE id = 'OM3XQcDRjYUB69mL';
`
GraphQL (JWT auth, NÃO ApiKey):
`
POST https://api.paineldocorretor.net/graphql
Authorization: Bearer
`
Painel API endpoints (status maio 2026)
`
GET /api/crm/negocios → 405 (Cloudflare bloqueia)
POST /api/crm/negocios → 200 ✅ (criar lead)
PATCH /api/crm/negocios/{id} → 500 (não implementado)
POST /api/crm/negocios/buscar → 405 (não existe)
`