9.0 KiB
9.0 KiB
LeadScraper — Arquitetura e Schema (v1.1)
Documento de arquitetura de referência. Qualquer mudança de schema ou de stack deve ser refletida aqui primeiro (versionar para v1.1, v1.2...) antes de alterar código.
1. Stack tecnológica
Backend
- Java 21
- Spring Boot 3.x — REST API, injeção de dependência, configuração
- Spring Data JPA + Hibernate — persistência
- Flyway — migrations versionadas do banco
- PostgreSQL 16 — banco relacional principal
- Playwright for Java — automação de navegador (Chromium headless) para o scraping do Google Maps
- Spring Security + JWT — autenticação da API (dashboard interno, não público)
- Bean Validation (Jakarta Validation) — validação de entrada (CNPJ, telefone, etc.)
- Lombok — redução de boilerplate
- Maven — build e gerenciamento de dependências
Frontend
- React 18 + Vite + TypeScript
- Tailwind CSS + shadcn/ui — componentes e estilo
- TanStack Query — cache e sincronização com a API
- React Hook Form + Zod — formulários e validação
- Recharts — gráficos simples (ex: leads por ramo, buscas por dia)
Infraestrutura
- Docker + Docker Compose — orquestração de todos os serviços
- Nginx — servir o build estático do frontend dentro do container
- Volumes Docker — persistência de dados do PostgreSQL entre reinicializações
2. Arquitetura em módulos (backend)
com.seconectai.leadscraper
├── config/ → segurança, CORS, configuração do Playwright
├── domain/ → entidades JPA (Lead, Ramo, Busca, Proxy, Telefone, HistoricoContato)
├── repository/ → interfaces Spring Data JPA
├── scraper/ → motor de automação do navegador (busca, scroll, extração bruta)
├── normalizer/ → padronização de nome, telefone, CNPJ, ramo
├── proxy/ → gestão do pool de proxies, rotação, backoff
├── service/ → orquestração das regras de negócio
├── controller/ → endpoints REST
├── security/ → autenticação JWT
└── worker/ → processamento assíncrono das buscas em fila (status pendente → em_execucao → concluida)
3. Fluxo de execução de uma busca
- Usuário informa
termo_busca+localizacaopelo frontend - Backend cria um registro em
buscascom statuspendente workerpega a busca pendente, muda status paraem_execucao, seleciona um proxy disponível no poolscraperabre o navegador com o proxy configurado, executa a busca no Maps, faz scroll gradual com delays humanizados até esgotar os resultados, extrai dados brutos de cada cardnormalizerpadroniza cada lead extraído (nome, telefone, CNPJ, ramo) e calculahash_dedupserviceverifica duplicidade (porhash_dedupeplace_id); se já existe, atualiza vínculo com a nova busca em vez de duplicar- Leads novos são persistidos;
busca.total_encontradosé atualizado; status muda paraconcluida(ouerrose algo falhar, com detalhe do erro registrado) - Frontend consulta o status via polling (TanStack Query) e exibe os leads assim que disponíveis
4. Schema do banco de dados
4.1 usuarios — acesso ao dashboard
CREATE TABLE usuarios (
id BIGSERIAL PRIMARY KEY,
nome VARCHAR(150) NOT NULL,
email VARCHAR(150) NOT NULL UNIQUE,
senha_hash VARCHAR(255) NOT NULL,
papel VARCHAR(20) NOT NULL DEFAULT 'admin', -- admin | operador
criado_em TIMESTAMPTZ NOT NULL DEFAULT now()
);
4.2 ramos — taxonomia normalizada de categorias
CREATE TABLE ramos (
id BIGSERIAL PRIMARY KEY,
nome_canonico VARCHAR(100) NOT NULL UNIQUE,
categoria_pai_id BIGINT REFERENCES ramos(id),
aliases JSONB NOT NULL DEFAULT '[]',
criado_em TIMESTAMPTZ NOT NULL DEFAULT now()
);
4.3 proxies — pool de proxies para rotação
CREATE TABLE proxies (
id BIGSERIAL PRIMARY KEY,
endereco VARCHAR(255) NOT NULL,
tipo VARCHAR(20) DEFAULT 'residencial', -- residencial | datacenter
status VARCHAR(20) DEFAULT 'ativo', -- ativo | degradado | bloqueado
total_bloqueios INTEGER DEFAULT 0,
em_uso BOOLEAN NOT NULL DEFAULT false,
ultima_utilizacao TIMESTAMPTZ,
criado_em TIMESTAMPTZ NOT NULL DEFAULT now()
);
4.4 buscas — cada execução do motor de busca
CREATE TABLE buscas (
id BIGSERIAL PRIMARY KEY,
termo_busca VARCHAR(150) NOT NULL,
localizacao VARCHAR(150) NOT NULL,
ramo_id BIGINT REFERENCES ramos(id),
proxy_id BIGINT REFERENCES proxies(id),
status VARCHAR(20) NOT NULL DEFAULT 'pendente', -- pendente | em_execucao | concluida | erro
total_encontrados INTEGER DEFAULT 0,
detalhe_erro TEXT,
iniciado_em TIMESTAMPTZ,
finalizado_em TIMESTAMPTZ,
criado_por BIGINT REFERENCES usuarios(id),
criado_em TIMESTAMPTZ NOT NULL DEFAULT now()
);
4.5 leads — tabela principal
CREATE TABLE leads (
id BIGSERIAL PRIMARY KEY,
place_id VARCHAR(150),
razao_social VARCHAR(200),
nome_fantasia VARCHAR(200) NOT NULL,
cnpj CHAR(14),
telefone_e164 VARCHAR(20),
telefone_bruto VARCHAR(40),
email VARCHAR(150),
site VARCHAR(255),
endereco_completo TEXT,
logradouro VARCHAR(200),
numero VARCHAR(20),
bairro VARCHAR(100),
cidade VARCHAR(100),
uf CHAR(2),
cep CHAR(8),
latitude NUMERIC(10,7),
longitude NUMERIC(10,7),
ramo_id BIGINT REFERENCES ramos(id),
categoria_bruta VARCHAR(150),
fonte VARCHAR(30) NOT NULL DEFAULT 'google_maps',
busca_id BIGINT REFERENCES buscas(id),
status VARCHAR(20) NOT NULL DEFAULT 'novo', -- novo | contatado | qualificado | convertido | descartado
hash_dedup VARCHAR(64) NOT NULL UNIQUE,
criado_em TIMESTAMPTZ NOT NULL DEFAULT now(),
atualizado_em TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_leads_ramo ON leads(ramo_id);
CREATE INDEX idx_leads_cidade_uf ON leads(cidade, uf);
CREATE INDEX idx_leads_busca ON leads(busca_id);
CREATE UNIQUE INDEX idx_leads_cnpj_unique ON leads(cnpj) WHERE cnpj IS NOT NULL;
4.6 telefones (opcional — múltiplos telefones por lead)
CREATE TABLE telefones (
id BIGSERIAL PRIMARY KEY,
lead_id BIGINT NOT NULL REFERENCES leads(id) ON DELETE CASCADE,
numero_e164 VARCHAR(20) NOT NULL,
tipo VARCHAR(20) DEFAULT 'principal',
UNIQUE (lead_id, numero_e164)
);
4.7 historico_contato — pipeline de acompanhamento
CREATE TABLE historico_contato (
id BIGSERIAL PRIMARY KEY,
lead_id BIGINT NOT NULL REFERENCES leads(id) ON DELETE CASCADE,
canal VARCHAR(30), -- whatsapp | ligacao | email
resultado VARCHAR(50),
observacao TEXT,
criado_em TIMESTAMPTZ NOT NULL DEFAULT now()
);
5. Regras de padronização (resumo — detalhado na etapa 04)
- Nome: trim, colapsar espaços, Title Case preservando siglas societárias (LTDA, ME, EIRELI, S/A)
- Telefone: apenas dígitos, assumir DDI +55 se ausente, formato final E.164, manter bruto para auditoria
- CNPJ: apenas dígitos, validar dígito verificador (módulo 11) antes de gravar
- Ramo: nunca gravar categoria bruta como definitiva — mapear via tabela
ramos.aliases; sem mapeamento conhecido,ramo_id = NULLe categoria fica emcategoria_brutapara curadoria manual - Deduplicação:
hash_dedup = SHA-256(nome_normalizado + '|' + telefone_e164);place_idcomo critério secundário
6. Anti-bloqueio (resumo — detalhado na etapa 05)
- Pool de proxies residenciais com rotação por sessão de busca (não por requisição)
- Delays humanizados aleatórios entre ações e entre buscas
- Scroll gradual em passos pequenos, não pulo direto
- Fingerprint variável (User-Agent, viewport, timezone) por contexto de navegador
- Detecção de captcha/bloqueio com backoff exponencial e desativação temporária do proxy afetado
- Limite de volume auto-imposto por proxy/hora
7. Docker Compose — visão geral dos serviços
services:
postgres: # banco de dados
backend: # API Spring Boot + motor de scraping
frontend: # build React servido via Nginx
Detalhes completos do docker-compose.yml estão na etapa 09.
8. Histórico de versões deste documento
- v1.1 (atual) — adiciona
proxies.em_usopara reserva concorrente do pool de proxies na etapa 05 - v1.0 — schema inicial completo: leads, ramos, buscas, proxies, telefones, historico_contato, usuarios; stack backend/frontend/infra definida