Files
2026-10-01 11:00:47 -03:00

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

  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

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 = 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

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