Initial commit

This commit is contained in:
tiago
2026-10-01 11:00:47 -03:00
commit 9d2ab731ee
149 changed files with 11351 additions and 0 deletions
+217
View File
@@ -0,0 +1,217 @@
# 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
1. Usuário informa `termo_busca` + `localizacao` pelo frontend
2. Backend cria um registro em `buscas` com status `pendente`
3. `worker` pega a busca pendente, muda status para `em_execucao`, seleciona um proxy disponível no pool
4. `scraper` abre 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 card
5. `normalizer` padroniza cada lead extraído (nome, telefone, CNPJ, ramo) e calcula `hash_dedup`
6. `service` verifica duplicidade (por `hash_dedup` e `place_id`); se já existe, atualiza vínculo com a nova busca em vez de duplicar
7. Leads novos são persistidos; `busca.total_encontrados` é atualizado; status muda para `concluida` (ou `erro` se algo falhar, com detalhe do erro registrado)
8. 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
```sql
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
```sql
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
```sql
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
```sql
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
```sql
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)
```sql
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
```sql
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 = NULL` e categoria fica em `categoria_bruta` para curadoria manual
- **Deduplicação:** `hash_dedup = SHA-256(nome_normalizado + '|' + telefone_e164)`; `place_id` como 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
```yaml
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_uso` para 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