chore: import gti-maquina-comercial history via subtree
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> git-subtree-dir: gti-maquina-comercial git-subtree-mainline:93ca82979egit-subtree-split:b77484946f
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
# Visão Geral
|
||||
|
||||
O Grupo GTI opera uma **máquina comercial local**: um aplicativo (GTI-Comercial.exe)
|
||||
que captura leads B2B das fontes públicas — principalmente a base oficial de CNPJs
|
||||
da Receita Federal — e ajuda a tratá-los e exportá-los para as campanhas de
|
||||
WhatsApp no Z-PRO.
|
||||
|
||||
## Soluções vendidas
|
||||
|
||||
- **ERP e sistemas de gestão** para comércio, serviços, restaurantes, mercados e distribuidoras.
|
||||
- **Gestão de TI** para empresas com suporte, infraestrutura, servidores, redes, backup e segurança.
|
||||
- **Autoatendimento para WhatsApp** com triagem, atendimento automático, vendas e agendamento.
|
||||
|
||||
Cada nicho do catálogo (57 nichos) já vem associado ao interesse provável e às dores
|
||||
típicas, que alimentam a personalização das mensagens.
|
||||
|
||||
## Arquitetura (simples de propósito)
|
||||
|
||||
```
|
||||
GTI-Comercial.exe → servidor Node local (porta 3900) → navegador
|
||||
│
|
||||
├── Captura: base CNPJ por UF + OpenStreetMap + Wikidata (+ Google opcional)
|
||||
├── Banco SQLite local (data/gti.db): leads, score, dedup, histórico
|
||||
└── Tratamento: dashboard, filtros, relatórios e exportações Z-PRO
|
||||
```
|
||||
|
||||
Sem n8n, sem serviços externos obrigatórios: tudo roda no computador da GTI hoje
|
||||
(e vai rodar num container Docker no Proxmox da empresa no futuro, sem mudança de
|
||||
código). O primeiro contato via WhatsApp pode ser automático (ver
|
||||
[08-automacao-envio.md](08-automacao-envio.md)); campanhas maiores e o
|
||||
acompanhamento seguem por fora, no Z-PRO, a partir dos arquivos gerados pelo
|
||||
módulo de tratamento.
|
||||
|
||||
## Premissas
|
||||
|
||||
- Foco inicial na Bahia (Santo Antônio de Jesus e região + capitais), mas o app
|
||||
funciona para **qualquer UF/cidade do Brasil** sem mudança de interface.
|
||||
- A equipe não tem braço para operação manual intensa: o app faz a triagem
|
||||
(score de qualidade dos dados + score comercial por nicho, combinados numa
|
||||
prioridade P1-P4) e evita retrabalho (leads exportados ficam marcados).
|
||||
- O WhatsApp é o canal central de conversão — leads com telefone válido têm prioridade.
|
||||
|
||||
## Resultado esperado
|
||||
|
||||
- Base grande e organizada de leads com WhatsApp (97%+ de cobertura via base CNPJ).
|
||||
- Relatórios por nicho/cidade prontos para campanhas segmentadas.
|
||||
- Histórico de tudo que já foi exportado/enviado, sem duplicar contato.
|
||||
- Dashboard de gestão comercial na tela de Tratamento: percentual da base
|
||||
pronta para abordagem imediata (Potencial Comercial), cobertura da base
|
||||
já trabalhada, distribuição visual por prioridade, produtos recomendados
|
||||
por cidade e meta diária de exportações da equipe.
|
||||
- Interface explica o "porquê" de cada score (composição detalhada no modal
|
||||
do lead), destaca a cidade+produto de maior oportunidade, mostra a
|
||||
evolução diária de exportações e oferece atalhos de um clique para gerar
|
||||
listas de prospecção (só P1, só um produto, só não exportados etc.).
|
||||
- Acesso protegido por login (usuário/senha): o primeiro acesso vem com
|
||||
`admin`/`admin`, que pode cadastrar novos usuários (comercial ou admin)
|
||||
pela tela de Usuários.
|
||||
- Automação de primeiro contato via WhatsApp (opcional, desligada por padrão):
|
||||
manda uma mensagem com o template do produto recomendado pra cada lead ainda
|
||||
não exportado, registra o que foi mandado e aplica tags no contato do Z-PRO —
|
||||
ver [08-automacao-envio.md](08-automacao-envio.md).
|
||||
@@ -0,0 +1,83 @@
|
||||
# Playbook Comercial
|
||||
|
||||
## Oferta De Entrada
|
||||
|
||||
Nome: Diagnostico GTI de Gestao, TI e Atendimento.
|
||||
|
||||
Promessa: em 15 minutos, identificar gargalos em sistema, atendimento, infraestrutura ou processos manuais da empresa.
|
||||
|
||||
Essa oferta reduz resistencia porque nao comeca vendendo ERP, contrato de TI ou automacao diretamente.
|
||||
|
||||
## ICP Principal
|
||||
|
||||
Priorizar empresas de Santo Antonio de Jesus e regiao com qualquer uma destas caracteristicas:
|
||||
|
||||
- Controle por planilha ou papel.
|
||||
- Sistema atual ruim ou insatisfatorio.
|
||||
- Alto volume de WhatsApp.
|
||||
- Muitos computadores ou usuarios.
|
||||
- Problemas frequentes com suporte, rede, backup ou servidor.
|
||||
- Dificuldade com estoque, financeiro, fiscal ou vendas.
|
||||
|
||||
## Segmentos Prioritarios
|
||||
|
||||
- Mercados e supermercados.
|
||||
- Farmacias e drogarias.
|
||||
- Restaurantes e delivery.
|
||||
- Distribuidoras.
|
||||
- Autopecas.
|
||||
- Material de construcao.
|
||||
- Clinicas.
|
||||
- Oficinas.
|
||||
- Escolas.
|
||||
- Lojas de varejo.
|
||||
- Empresas de servicos.
|
||||
|
||||
## Metodo De Atendimento
|
||||
|
||||
Usar SPIN de forma simples:
|
||||
|
||||
- Situacao: entender como a empresa funciona hoje.
|
||||
- Problema: descobrir gargalos.
|
||||
- Implicacao: mostrar o impacto de continuar assim.
|
||||
- Necessidade: conectar a dor a uma solucao GTI.
|
||||
|
||||
## Perguntas Do Diagnostico
|
||||
|
||||
- Como voces controlam vendas, estoque e financeiro hoje?
|
||||
- Qual processo mais toma tempo ou gera retrabalho?
|
||||
- Hoje voces usam sistema, planilha ou controle manual?
|
||||
- Quando da problema de TI, quem resolve?
|
||||
- Existe rotina de backup?
|
||||
- Quantas mensagens chegam por dia no WhatsApp?
|
||||
- Qual o impacto se esse problema continuar por mais 6 meses?
|
||||
- Se a GTI resolver isso, qual resultado seria mais importante?
|
||||
|
||||
## Roteiro Para Lead Quente
|
||||
|
||||
```text
|
||||
Ola, [nome]. Vi aqui seu contato sobre [servico].
|
||||
Pelo que voce informou, parece que existe uma oportunidade clara de melhorar [dor].
|
||||
Quero fazer um diagnostico rapido de 15 minutos para entender sua operacao e te dizer o melhor caminho.
|
||||
Pode ser hoje pela tarde ou amanha pela manha?
|
||||
```
|
||||
|
||||
## Follow-up Manual Em Proposta
|
||||
|
||||
Dia 1:
|
||||
|
||||
```text
|
||||
Oi, [nome]. Conseguiu avaliar a proposta da GTI? Posso te ajudar com alguma duvida tecnica ou comercial?
|
||||
```
|
||||
|
||||
Dia 3:
|
||||
|
||||
```text
|
||||
[nome], queria confirmar se esse projeto ainda faz sentido para agora ou se prefere que eu reprograme o contato para outro momento.
|
||||
```
|
||||
|
||||
Dia 7:
|
||||
|
||||
```text
|
||||
Vou deixar seu atendimento em aberto para retomarmos quando fizer sentido. Se quiser avancar, me responda por aqui.
|
||||
```
|
||||
@@ -0,0 +1,48 @@
|
||||
# LGPD E Regras De WhatsApp
|
||||
|
||||
Este projeto deve ser operado com cuidado para evitar bloqueio de WhatsApp e problemas de privacidade.
|
||||
|
||||
## Regras Basicas
|
||||
|
||||
- Guardar origem do lead.
|
||||
- Registrar consentimento quando existir.
|
||||
- Evitar disparo massivo sem opt-in.
|
||||
- Permitir descadastro ou parada de contato.
|
||||
- Nao comprar listas frias para disparo automatico.
|
||||
- Usar abordagem consultiva, nao agressiva.
|
||||
- Manter historico de data, origem e mensagem enviada.
|
||||
|
||||
## Mensagem De Descadastro
|
||||
|
||||
Toda sequencia pode aceitar palavras como:
|
||||
|
||||
```text
|
||||
PARAR
|
||||
CANCELAR
|
||||
SAIR
|
||||
NAO QUERO
|
||||
```
|
||||
|
||||
Quando uma dessas respostas chegar, o lead deve ser marcado como `Opt-out` no CRM e sair das automacoes.
|
||||
|
||||
## Prospecao Ativa Segura
|
||||
|
||||
Para listas frias locais, a abordagem deve ser manual ou semi-automatizada, com baixo volume e personalizacao.
|
||||
|
||||
Exemplo:
|
||||
|
||||
```text
|
||||
Ola, tudo bem? Aqui e do Grupo GTI, de Santo Antonio de Jesus.
|
||||
Vi que sua empresa atua em [segmento] e queria entender se hoje voces ja usam algum sistema para vendas, estoque ou atendimento.
|
||||
Se nao fizer sentido, pode me avisar que nao envio novas mensagens.
|
||||
```
|
||||
|
||||
## Dados Minimos No CRM
|
||||
|
||||
- Nome.
|
||||
- Empresa.
|
||||
- WhatsApp.
|
||||
- Origem.
|
||||
- Data do primeiro contato.
|
||||
- Consentimento.
|
||||
- Status de opt-out.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Captura de Leads
|
||||
|
||||
A captura procura empresas por **UF + cidades + nichos** nas fontes selecionadas,
|
||||
pontua cada lead (score/classificação) e grava tudo no banco local com
|
||||
deduplicação automática — um lead capturado uma vez nunca é duplicado, em nenhuma
|
||||
execução futura.
|
||||
|
||||
O jeito normal de usar é pela **aba "Capturar Leads" do aplicativo**
|
||||
(GTI-Comercial.exe). Este documento cobre os detalhes e o uso por linha de comando.
|
||||
|
||||
## Fontes
|
||||
|
||||
| Fonte | Custo | Observações |
|
||||
|---|---|---|
|
||||
| `cnpj-base` | grátis | **Principal.** Base oficial da Receita Federal extraída por UF (~97% com telefone). |
|
||||
| `brasilapi` | grátis | Mesma base CNPJ, enriquecida online via BrasilAPI (mais lenta). |
|
||||
| `overpass` | grátis | OpenStreetMap por área da cidade (bounding box automática via geocode). |
|
||||
| `nominatim` | grátis | Busca textual no OpenStreetMap. |
|
||||
| `wikidata` | grátis | Empresas registradas no Wikidata (volume baixo). |
|
||||
| `google-places` | paga | Exige `GOOGLE_MAPS_API_KEY` no `.env`. |
|
||||
| `mock` | — | 3 leads fictícios para testar o fluxo. |
|
||||
|
||||
## Base CNPJ por estado
|
||||
|
||||
Cada UF precisa de uma extração única (~40–60 min) que gera
|
||||
`prospeccao/cnpj/estabelecimentos-<UF>.csv` a partir dos zips brutos da Receita
|
||||
(`prospeccao/cnpj/rfb-raw/`, Brasil inteiro, já baixados). A interface oferece o
|
||||
botão **"Extrair base agora"** quando a UF escolhida ainda não tem base; na linha
|
||||
de comando:
|
||||
|
||||
```bash
|
||||
node scripts/extrair-base-cnpj.mjs --uf=SP
|
||||
```
|
||||
|
||||
A extração cobre **todos os municípios da UF** — depois dela, qualquer cidade do
|
||||
estado pode ser capturada na hora, sem re-extração. Um checkpoint em disco permite
|
||||
retomar de onde parou se o processo for interrompido.
|
||||
|
||||
## Deduplicação e pontuação
|
||||
|
||||
- Chave de duplicidade: **CNPJ válido → WhatsApp válido → empresa+cidade**.
|
||||
- **Score de Qualidade** (0-100, "os dados são confiáveis?"): +30 WhatsApp válido,
|
||||
+20 CNPJ válido, +20 empresa válida, +5 cidade, +10 nicho, +10 CNAE, +5 website.
|
||||
- **Score Comercial** (0-100, "qual a chance de comprar?"): aderência do nicho
|
||||
(alta 40 / média 30 / baixa 20, configurada por nicho em `prospeccao/config.json`)
|
||||
+ website 15 + e-mail 10 + WhatsApp válido 15 + CNPJ válido 20.
|
||||
- **Score Geral** = 60% comercial + 40% qualidade — é o valor usado para ordenar
|
||||
a tela de Tratamento.
|
||||
- **Classificação**: 90-100 Excelente, 75-89 Bom, 55-74 Médio, 35-54 Baixo,
|
||||
0-34 Muito Baixo. A **Prioridade comercial** (P1-P4, mostrada na tela) deriva
|
||||
direto dela: Excelente/Bom ⇒ P1 (atacar já), Médio ⇒ P2 (na semana), Baixo ⇒ P3,
|
||||
Muito Baixo ⇒ P4 (só em campanhas específicas).
|
||||
- Cada nicho tem produtos GTI recomendados automaticamente (ERP, G-Delivery,
|
||||
G-Chat, Sistema de Ponto) que aparecem no card do lead. Se o catálogo de
|
||||
aderência/produtos do `config.json` mudar, rode `npm run recalc-scores` para
|
||||
recalcular todos os leads já capturados.
|
||||
|
||||
## Linha de comando (uso avançado)
|
||||
|
||||
```bash
|
||||
node scripts/capturar-leads.mjs --sources=mock
|
||||
node scripts/capturar-leads.mjs --sources=cnpj-base --uf=BA --cities="Ilheus,Itabuna"
|
||||
node scripts/capturar-leads.mjs --sources=cnpj-base,overpass --uf=BA --cities=all --segments=all
|
||||
node scripts/capturar-leads.mjs --help
|
||||
```
|
||||
|
||||
Parâmetros úteis: `--segments=a,b,c` (nichos pelo nome), `--segment-limit=N`,
|
||||
`--city-limit=N`, `--max=N` (resultados por consulta nas fontes de API),
|
||||
`--query-limit=N` (teto total de consultas).
|
||||
|
||||
## Ritmo e boas práticas (fontes OpenStreetMap)
|
||||
|
||||
Os servidores públicos do Overpass/Nominatim têm limites de uso. O app já aplica
|
||||
espera entre consultas (`OVERPASS_REQUEST_DELAY_MS`), failover entre endpoints e
|
||||
cooldown após falhas — falha de uma cidade não derruba a captura, só pula.
|
||||
@@ -0,0 +1,133 @@
|
||||
# Automação de envio WhatsApp (Z-PRO)
|
||||
|
||||
Manda automaticamente a primeira mensagem de WhatsApp pro produto/serviço
|
||||
recomendado de cada lead, usando um template por produto, e registra tudo
|
||||
(quem recebeu, qual produto, quando) pra nunca mandar a mesma coisa duas
|
||||
vezes pro mesmo lead.
|
||||
|
||||
## Como funciona
|
||||
|
||||
- Roda **sozinha, dentro do próprio servidor**, a cada 20 minutos, enquanto
|
||||
o app estiver ligado — não depende de Agendador de Tarefas do Windows nem
|
||||
de cron. Isso é de propósito: o projeto hoje roda local, mas vai ser
|
||||
migrado pra um container Docker no Proxmox da empresa, e um agendador do
|
||||
sistema operacional teria que ser reconfigurado nesse dia. O timer interno
|
||||
funciona idêntico nos dois ambientes.
|
||||
- Só considera leads **ainda não exportados** (`exported_at` vazio). Assim
|
||||
que um lead vira oportunidade manual no Z-PRO (exportação normal da tela
|
||||
de Tratamento), a automação para de mandar mensagem pra ele — quem assumiu
|
||||
o lead foi um vendedor, não o robô.
|
||||
- Cada lead tem uma lista de produtos recomendados por nicho (ex.: ERP 5⭐,
|
||||
G-Delivery 3⭐). Hoje a automação só tenta o **produto principal** (o de
|
||||
maior estrela), uma única vez por lead. Existe uma segunda etapa pronta
|
||||
(mas desligada) pra tentar o próximo produto da lista se não houver
|
||||
resposta depois de alguns dias — ver `outreach.tentarProximoProduto`
|
||||
abaixo.
|
||||
- Manda uma mensagem por número, com controle nosso de quem já recebeu o
|
||||
quê — não pelo `bulkSendMessageWithVariable` usado na exportação manual
|
||||
em massa, que depende de um CSV com substituição posicional de variável.
|
||||
Cada produto tem um **canal** próprio (ver seção abaixo).
|
||||
- Depois de mandar, aplica duas tags no contato do Z-PRO:
|
||||
`GTI P1`/`GTI P2`/`GTI P3`/`GTI P4` (prioridade do lead) e
|
||||
`GTI Oferta: <produto>` (o que foi ofertado) — pra quem for atender depois
|
||||
já ver esse contexto direto na conversa.
|
||||
|
||||
## Canal oficial x não-oficial
|
||||
|
||||
Cada produto/template tem um seletor de canal em Configurações:
|
||||
|
||||
- **Não-oficial (texto livre)** — usa `bulkIndividual`, funciona numa
|
||||
conexão QR Code/Baileys do Z-PRO. Manda o texto do template com
|
||||
`{empresa}`/`{cidade}`/`{segmento}` substituídos. É o padrão, e o risco
|
||||
aqui é o de sempre com WhatsApp não-oficial: possibilidade de bloqueio do
|
||||
número por comportamento automatizado (por isso o limite diário e o
|
||||
intervalo aleatório entre envios).
|
||||
- **Oficial (Meta, template aprovado)** — usa a API oficial do WhatsApp
|
||||
(Cloud API/WABA) via Z-PRO. Aqui **não dá pra mandar texto livre**: o
|
||||
WhatsApp só permite iniciar uma conversa com um **template pré-aprovado**
|
||||
pelo Meta Business Manager (fora da janela de 24h de uma conversa já
|
||||
iniciada pelo cliente). Por isso o campo vira "Nome do template aprovado"
|
||||
(tem que ser **exatamente** o nome cadastrado no Meta) + "Idioma" (ex.:
|
||||
`pt_BR`).
|
||||
|
||||
**A aprovação do template em si é feita fora deste app** — no Meta
|
||||
Business Manager (ou no painel do Z-PRO, se ele tiver essa tela) — o
|
||||
Z-PRO só expõe o *envio* usando um template que já existe, não a criação.
|
||||
Cadastre e espere aprovar lá antes de configurar o nome aqui.
|
||||
|
||||
Limitação atual: o envio oficial aqui é só nome+idioma, sem variáveis
|
||||
dentro do corpo do template aprovado (a API do Z-PRO tem endpoints
|
||||
`templateBody`/`templateMarketingBody` que parecem aceitar variáveis, mas
|
||||
o formato exato não está documentado na collection — se for necessário,
|
||||
precisa testar contra a instância real antes de automatizar).
|
||||
|
||||
## Configurar pela primeira vez
|
||||
|
||||
1. **Credenciais do Z-PRO**: no painel do Z-PRO, vá em Admin > API e gere um
|
||||
token (Base URL, API ID, Token e WhatsApp ID). Na tela, abra
|
||||
**⚙️ Configurações > Credenciais Z-PRO (WhatsApp)**, preencha os 4 campos
|
||||
e clique em **Testar conexão** (salva e faz uma chamada real e barata pra
|
||||
confirmar que as credenciais estão certas, sem mandar nenhuma mensagem).
|
||||
Sem essas 4 credenciais a automação não manda nada, mesmo ligada.
|
||||
|
||||
Alternativa pra quem prefere não guardar o token no banco local (ex.: em
|
||||
produção no Docker/Proxmox, gerenciando segredo por variável de
|
||||
ambiente): deixe os 4 campos da tela em branco e defina
|
||||
`ZPRO_BASE_URL`/`ZPRO_API_ID`/`ZPRO_BEARER_TOKEN`/`ZPRO_WHATSAPP_ID` no
|
||||
`.env` — a tela vazia cai automaticamente pro `.env`. Se a tela tiver
|
||||
algum valor preenchido, ele tem prioridade sobre o `.env`.
|
||||
2. Na mesma tela, em **Automação de envio (WhatsApp)**: revise o texto de
|
||||
cada template (um por produto — `{empresa}`, `{cidade}` e `{segmento}`
|
||||
são substituídos automaticamente), ajuste o **limite por dia** se
|
||||
quiser, e marque **Ativada**. Salve.
|
||||
|
||||
Antes de ativar, vale mandar um teste manual (próxima seção) pro seu
|
||||
próprio WhatsApp.
|
||||
|
||||
## Configurações (`settings`, chave → o que faz)
|
||||
|
||||
| Chave | Padrão | O que faz |
|
||||
|---|---|---|
|
||||
| `outreach.ativo` | `false` | Interruptor geral. Começa desligado de propósito. |
|
||||
| `outreach.mensagensPorDia` | `10` | Máximo de envios automáticos por dia (soma de todos os produtos). |
|
||||
| `outreach.diasEntreProdutos` | `5` | Dias de espera antes de tentar o próximo produto (só usado se a cascata estiver ligada). |
|
||||
| `outreach.tentarProximoProduto` | `false` | Liga a cascata: quando `false`, só tenta o produto principal uma vez. |
|
||||
| `outreach.horaInicio` / `outreach.horaFim` | `08:00` / `18:00` | Só manda mensagem dentro desse horário. |
|
||||
|
||||
Tudo isso é editável pela tela de Configurações (os dois primeiros) ou
|
||||
diretamente via `PUT /api/settings` (todos).
|
||||
|
||||
## Disparo manual (fora do ciclo automático)
|
||||
|
||||
Pra testar ou forçar um lote sem esperar o próximo ciclo de 20 min:
|
||||
|
||||
```bash
|
||||
npm run outreach
|
||||
```
|
||||
|
||||
Respeita as mesmas regras do ciclo automático (`outreach.ativo`, horário
|
||||
comercial, limite diário) — não manda nada que a tela não mostraria.
|
||||
|
||||
## Enviar pra um número específico
|
||||
|
||||
Em **Configurações > Enviar mensagem manual**: digite o número (com DDI+DDD,
|
||||
ex.: `5511999999999`), escolha o canal (texto livre ou template oficial já
|
||||
aprovado) e clique em Enviar. Não depende de lead cadastrado nem de produto.
|
||||
|
||||
No canal **Não-oficial (texto livre)**, o app usa `createTicket` para criar uma
|
||||
conversa visível no painel do Z-PRO. O ticket usa o canal informado em
|
||||
`WhatsApp ID` e o usuário de `Responsável` nas configurações.
|
||||
|
||||
No canal **Oficial**, o app usa o endpoint de template aprovado (`template`) e
|
||||
também espera um `ticketId` na resposta.
|
||||
|
||||
Toda tentativa fica registrada no mesmo histórico dos envios automáticos
|
||||
(marcado como origem "manual") e **conta pro limite diário**, já que o risco de
|
||||
bloqueio é do número de WhatsApp usado, não de quem disparou a mensagem.
|
||||
|
||||
## Onde fica o histórico
|
||||
|
||||
Toda tentativa de envio (sucesso ou erro) vira uma linha na tabela
|
||||
`outreach_sends` (lead, produto, mensagem exata mandada, status, quando).
|
||||
Um resumo do dia (enviados hoje, por produto, últimos 20 envios) fica
|
||||
disponível em `GET /api/outreach/status`.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,213 @@
|
||||
# Campanhas de Outreach — GTI Comercial
|
||||
|
||||
Data: 2026-08-01
|
||||
Status: aprovado pelo usuário, pendente de plano de implementação
|
||||
|
||||
## Contexto e motivação
|
||||
|
||||
A Máquina Comercial Pessoal tem um subsistema de campanhas de outreach (fila com
|
||||
preview/aprovação, variantes A/B/C, escalonamento semanal de limite diário, pausa
|
||||
automática por saúde, classificação de estágio comercial, agendamento de
|
||||
demonstração) construído sobre o envio direto via Baileys. A GTI Comercial já tem
|
||||
sua própria automação de outreach (`src/outreach/run.mjs` + `scheduler.mjs`),
|
||||
que dispara um lote a cada 20 minutos, respeitando `produto_principal` de cada
|
||||
lead, canal oficial (WABA/template aprovado) ou não-oficial (texto livre via
|
||||
Z-PRO), e grava tudo em `outreach_sends`.
|
||||
|
||||
O pedido: portar a mesma metodologia de campanhas da Pessoal para a Comercial,
|
||||
mas adaptada a duas diferenças reais entre os projetos:
|
||||
|
||||
1. **Meio de conexão**: a Comercial não fala com o WhatsApp diretamente (Baileys);
|
||||
ela passa pelo Z-PRO (`src/outreach/zpro-client.mjs`), que oferece dois canais
|
||||
distintos — não-oficial (`sendText`/`bulkIndividual`, texto livre) e oficial
|
||||
(`sendTemplateWaba`, só templates pré-aprovados no Meta Business Manager, sem
|
||||
texto livre nem variáveis além das que o próprio template já define).
|
||||
2. **Catálogo de negócio**: a Pessoal tem 2-3 campanhas fixas, hardcoded em
|
||||
`campaign-catalog.mjs`, cada uma amarrada a um segmento específico. A
|
||||
Comercial tem 57 segmentos e 4 produtos (G-Chat, Sistema de Ponto, ERP,
|
||||
G-Delivery) — catálogo fixo não cabe nessa escala. Aqui, campanha precisa ser
|
||||
um registro que o operador cria pela tela (produto + segmentos + textos),
|
||||
não código.
|
||||
|
||||
Decisões já validadas com o usuário:
|
||||
- Campanhas rodam **ao lado** do `runOutreachBatch` existente, sem substituí-lo.
|
||||
- Variantes A/B/C só funcionam no canal **não-oficial** (o oficial não aceita
|
||||
texto livre, então não há o que variar).
|
||||
- Pacote completo: escalonamento semanal, fila/preview/aprovação, variantes com
|
||||
eleição de vencedora, pausa automática por saúde, classificação de estágio
|
||||
comercial, agendamento de demonstração.
|
||||
- Classificação de estágio e registro de resposta são **manuais** por enquanto —
|
||||
não existe endpoint de webhook de resposta do Z-PRO implementado nesta base.
|
||||
|
||||
## Arquitetura
|
||||
|
||||
Novo diretório `src/outreach/campaign-*.mjs`, paralelo a `run.mjs`/`scheduler.mjs`
|
||||
— nenhum dos dois é modificado. Único ponto de integração: `outreach_sends`
|
||||
ganha um novo valor de `origem` (`'campanha'`, além dos já existentes
|
||||
`'automatico'`/`'manual'`) e a seleção de candidatos de campanha exclui leads
|
||||
recentemente contatados para o mesmo produto **por qualquer origem**, evitando
|
||||
contato duplicado entre a automação existente e as campanhas novas.
|
||||
|
||||
```
|
||||
src/outreach/
|
||||
run.mjs (existente, intocado)
|
||||
scheduler.mjs (existente, intocado)
|
||||
zpro-client.mjs (existente, intocado — reusado)
|
||||
templates.mjs (existente, intocado — renderTemplate reusado)
|
||||
campaign-catalog.mjs (novo — CRUD de outreach_campaigns/variants)
|
||||
campaign-queue.mjs (novo — seleção de candidatos, preview, aprovação)
|
||||
campaign-worker.mjs (novo — envio das linhas aprovadas)
|
||||
campaign-metrics.mjs (novo — saúde, auto-pausa, comparação de variantes)
|
||||
campaign-scheduler.mjs (novo — timer próprio, chama campaign-worker)
|
||||
```
|
||||
|
||||
## Modelo de dados
|
||||
|
||||
Migração nova em `src/db.mjs` (mesmo padrão das migrações v5-v7 já existentes:
|
||||
`ALTER TABLE`/`CREATE TABLE IF NOT EXISTS` guardado por versão do schema).
|
||||
|
||||
```sql
|
||||
CREATE TABLE outreach_campaigns (
|
||||
id INTEGER PRIMARY KEY,
|
||||
nome TEXT NOT NULL,
|
||||
produto TEXT NOT NULL,
|
||||
segmentos_json TEXT NOT NULL, -- ["Oficina", "Restaurante", ...]
|
||||
status TEXT NOT NULL DEFAULT 'rascunho', -- rascunho|rodando|pausado|concluido
|
||||
pause_reason TEXT,
|
||||
limite_diario_atual INTEGER NOT NULL DEFAULT 10,
|
||||
semana_iniciada_em TEXT, -- controla quando escalar o limite
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE outreach_campaign_variants (
|
||||
id INTEGER PRIMARY KEY,
|
||||
campaign_id INTEGER NOT NULL REFERENCES outreach_campaigns(id),
|
||||
code TEXT NOT NULL, -- A|B|C
|
||||
mensagem TEXT NOT NULL,
|
||||
ativo INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
CREATE TABLE outreach_campaign_queue (
|
||||
id INTEGER PRIMARY KEY,
|
||||
campaign_id INTEGER NOT NULL REFERENCES outreach_campaigns(id),
|
||||
lead_id INTEGER NOT NULL REFERENCES leads(id),
|
||||
variant_id INTEGER NOT NULL REFERENCES outreach_campaign_variants(id),
|
||||
segmento TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'preview', -- preview|approved|cancelled|reserved|sent|unknown|error
|
||||
outreach_send_id INTEGER REFERENCES outreach_sends(id),
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE outreach_lead_states (
|
||||
lead_id INTEGER PRIMARY KEY REFERENCES leads(id),
|
||||
stage TEXT NOT NULL, -- interessado|sem_interesse|opt_out|venda_fechada|...
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE outreach_stage_events (
|
||||
id INTEGER PRIMARY KEY,
|
||||
lead_id INTEGER NOT NULL REFERENCES leads(id),
|
||||
campaign_id INTEGER REFERENCES outreach_campaigns(id),
|
||||
stage TEXT NOT NULL,
|
||||
note TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE outreach_appointments (
|
||||
id INTEGER PRIMARY KEY,
|
||||
lead_id INTEGER NOT NULL REFERENCES leads(id),
|
||||
kind TEXT NOT NULL, -- demonstracao|teste_g_delivery|...
|
||||
scheduled_at TEXT,
|
||||
note TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE outreach_opt_out (
|
||||
numero TEXT PRIMARY KEY,
|
||||
motivo TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
ALTER TABLE outreach_sends ADD COLUMN campaign_queue_id INTEGER REFERENCES outreach_campaign_queue(id);
|
||||
```
|
||||
|
||||
`outreach_opt_out` não existe hoje na Comercial (a Pessoal tem um equivalente
|
||||
mais complexo por causa da equivalência de números 8/9 dígitos). Aqui basta uma
|
||||
tabela simples por `numero` já normalizado (`onlyDigits`), sem a complexidade de
|
||||
variantes de número da Pessoal — a Comercial já normaliza números na validação
|
||||
(`validatePhone`/`onlyDigits`). Está incluída porque uma fila que recontata o
|
||||
mesmo lead ao longo de semanas sem checar opt-out é um risco real de spam/banimento,
|
||||
não porque foi pedida explicitamente — sinalizando aqui para confirmação.
|
||||
|
||||
## Fluxo
|
||||
|
||||
1. **Criar campanha**: operador escolhe produto, segmentos (lista vindo de
|
||||
`SELECT DISTINCT segmento, COUNT(*) FROM leads WHERE produto_principal = ?
|
||||
GROUP BY segmento` — mesmo padrão de facet já usado em outros lugares do
|
||||
projeto), escreve 1-3 variantes de mensagem (texto livre, mesmas variáveis
|
||||
de `templates.mjs`: `{empresa}`, `{produto}`, `{cidade}`, `{segmento}`),
|
||||
define limite diário inicial.
|
||||
2. **Gerar fila**: candidatos = leads do produto/segmentos escolhidos, com
|
||||
`telefone_valido=1`, sem envio recente (`outreach_sends`, qualquer origem)
|
||||
para o mesmo produto, sem opt-out. Distribuição round-robin entre variantes
|
||||
ativas. Linhas entram como `preview` — nada é enviado.
|
||||
3. **Revisar e aprovar**: tela lista a fila em preview (empresa, telefone,
|
||||
variante, mensagem já renderizada); operador aprova em lote ou individualmente
|
||||
→ status `approved`. Só linhas aprovadas entram no worker.
|
||||
4. **Envio**: `campaign-worker.mjs`, rodando no `campaign-scheduler.mjs` (timer
|
||||
próprio, mesmo padrão do `scheduler.mjs` existente — `setInterval` com
|
||||
`.unref()`), pega linhas `approved` respeitando `limite_diario_atual` da
|
||||
campanha, revalida o lead na hora (ainda não opt-out, ainda no segmento),
|
||||
envia via `zproClient.sendText`, grava em `outreach_sends`
|
||||
(`origem='campanha'`, `campaign_queue_id` preenchido), atualiza a linha da
|
||||
fila para `sent`/`error`.
|
||||
5. **Escalonamento semanal**: a cada 7 dias corridos sem pausa por saúde desde
|
||||
`semana_iniciada_em`, `limite_diario_atual` dobra (10→20→40...), até um teto
|
||||
configurável.
|
||||
6. **Saúde/auto-pausa** (`campaign-metrics.mjs`, mesmos 3 critérios da Pessoal,
|
||||
adaptados às tabelas daqui):
|
||||
- taxa de erro de envio > 10% com pelo menos 10 tentativas no dia → pausa
|
||||
`high_failure_rate`
|
||||
- taxa de estágio negativo/opt-out > 10% com pelo menos 20 contatos → pausa
|
||||
`high_negative_rate`
|
||||
- taxa de resposta < 5% com pelo menos 50 contatos → pausa `low_reply_rate`
|
||||
(mesmos limiares numéricos já usados em `campaign-metrics.mjs` da Pessoal)
|
||||
Pausa muda `status` para `pausado`; o worker para de pegar novas linhas
|
||||
dessa campanha até o operador retomar manualmente.
|
||||
7. **Variantes e eleição de vencedora**: após um mínimo de 40 envios por
|
||||
variante, a tela mostra taxa de resposta por variante e sinaliza a de melhor
|
||||
desempenho — indicativo, não desliga variantes sozinho.
|
||||
8. **Classificação de estágio e agendamento**: 100% manual por enquanto —
|
||||
operador marca o estágio do lead pela tela (gera `outreach_stage_events` +
|
||||
atualiza `outreach_lead_states`) e cria agendamentos (`outreach_appointments`)
|
||||
manualmente. Sem webhook de resposta do Z-PRO nesta fase.
|
||||
|
||||
## Prevenção de contato duplicado com a automação existente
|
||||
|
||||
`runOutreachBatch` (`run.mjs`) já verifica `lastEnviado` antes de mandar. A
|
||||
seleção de candidatos de campanha faz a checagem simétrica: exclui qualquer
|
||||
lead com `outreach_sends` para o mesmo produto nos últimos N dias, **indepen-
|
||||
dente de `origem`**. Isso garante que uma campanha nunca contata quem o lote
|
||||
automático já contatou recentemente para aquele produto, e vice-versa — os
|
||||
dois sistemas competem pelo mesmo "direito de contato" por produto, não
|
||||
disparam em paralelo para o mesmo lead.
|
||||
|
||||
## Fora de escopo (explicitamente adiado)
|
||||
|
||||
- Webhook de resposta do Z-PRO / classificação automática de estágio.
|
||||
- Envio de variantes pelo canal oficial (impossível hoje — WABA não aceita
|
||||
texto livre).
|
||||
- Equivalência de números 8/9 dígitos ou detecção de número compartilhado
|
||||
(não existe na Comercial hoje e não foi pedido).
|
||||
|
||||
## Testes
|
||||
|
||||
Mesma disciplina da Pessoal: um arquivo de teste por módulo novo
|
||||
(`campaign-catalog`, `campaign-queue`, `campaign-worker`, `campaign-metrics`),
|
||||
cobrindo pelo menos: geração de fila respeita segmentos/produto/exclusões,
|
||||
aprovação só libera linhas aprovadas para o worker, worker respeita limite
|
||||
diário e revalida antes de enviar, auto-pausa dispara nos 3 critérios e
|
||||
bloqueia novas linhas, escalonamento semanal avança e não avança durante
|
||||
pausa, prevenção de contato duplicado entre `origem='automatico'` e
|
||||
`origem='campanha'`.
|
||||
Reference in New Issue
Block a user