Initial commit
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,114 @@
|
||||
# LeadScraper — Leia-me
|
||||
|
||||
Motor de busca e prospecção de leads via automação de navegador (Google Maps), com backend em Java, frontend próprio, banco de dados PostgreSQL e execução em Docker.
|
||||
|
||||
Este documento é o ponto de entrada do projeto. Leia-o antes de começar qualquer etapa.
|
||||
|
||||
---
|
||||
|
||||
## 1. Estrutura do projeto
|
||||
|
||||
```
|
||||
leadscraper/
|
||||
├── LEIA-ME.md ← este arquivo
|
||||
├── ARQUITETURA-SCHEMA.md ← schema completo do banco + stack tecnológica + módulos
|
||||
├── PROGRESSO.md ← tracker de progresso, atualizado a cada etapa concluída
|
||||
└── etapas/
|
||||
├── 01-setup-projeto-docker-flyway.md
|
||||
├── 02-modelagem-entidades-jpa.md
|
||||
├── 03-motor-scraper-playwright.md
|
||||
├── 04-normalizador-padronizacao.md
|
||||
├── 05-pool-proxies-anti-bloqueio.md
|
||||
├── 06-api-rest-backend.md
|
||||
├── 07-frontend-setup-dashboard.md
|
||||
├── 08-frontend-integracao-api.md
|
||||
├── 09-docker-compose-deploy.md
|
||||
└── 10-testes-validacao-final.md
|
||||
```
|
||||
|
||||
## 2. Como este projeto deve ser executado
|
||||
|
||||
1. **Leia `ARQUITETURA-SCHEMA.md` por completo** antes de iniciar qualquer etapa — ele é a fonte de verdade sobre schema de banco, tecnologias e módulos. Nenhuma etapa deve divergir do que está lá sem atualizar o documento primeiro.
|
||||
2. **Siga as etapas em `etapas/` na ordem numérica.** Cada arquivo é autocontido: traz objetivo, contexto necessário e critérios de aceite. Não pule etapas — cada uma assume que a anterior foi concluída.
|
||||
3. **Antes de iniciar uma etapa, abra `PROGRESSO.md`** e confira se a etapa anterior está marcada como concluída. Se não estiver, resolva pendências antes de avançar.
|
||||
4. **Ao concluir uma etapa, atualize `PROGRESSO.md` imediatamente** — marque status, data e qualquer observação relevante (ex: decisão técnica tomada, desvio do plano original).
|
||||
5. Se durante a execução de uma etapa for necessário mudar algo do schema ou da arquitetura definida em `ARQUITETURA-SCHEMA.md`, **atualize o documento primeiro** (versionando, ex: v1.0 → v1.1) e só depois prossiga — nunca deixe o código divergir silenciosamente do documento de arquitetura.
|
||||
6. Novas funcionalidades que surgirem depois do projeto em produção (ex: exportação avançada, novo canal de contato) devem virar **novos arquivos de etapa numerados** (11, 12...) ou migrations incrementais — nunca reescrever o schema base do zero.
|
||||
|
||||
## 3. Pré-requisitos de ambiente
|
||||
|
||||
- Java 21 (JDK)
|
||||
- Docker + Docker Compose
|
||||
- Node.js 20+ (apenas para build do frontend)
|
||||
- Maven (ou o wrapper `mvnw` incluso no projeto backend)
|
||||
|
||||
## 4. Visão geral rápida do sistema
|
||||
|
||||
- **Backend (Java/Spring Boot):** expõe API REST, roda o motor de scraping (Playwright), aplica as regras de padronização e persiste os leads.
|
||||
- **Frontend (React/Vite):** dashboard para disparar novas buscas, acompanhar status, listar/filtrar/exportar leads.
|
||||
- **Banco (PostgreSQL):** armazena leads, ramos, buscas, proxies e histórico de contato.
|
||||
- **Docker Compose:** orquestra os três serviços acima para subir o ambiente completo com um único comando.
|
||||
|
||||
Detalhes completos de cada peça estão em `ARQUITETURA-SCHEMA.md`.
|
||||
|
||||
## 5. Subida completa com Docker Compose
|
||||
|
||||
1. Crie um `.env` a partir do exemplo:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. Edite o `.env` antes de usar em produção:
|
||||
|
||||
- `DB_PASSWORD`: use uma senha forte.
|
||||
- `JWT_SECRET`: use uma chave longa, com pelo menos 32 caracteres.
|
||||
- `API_URL`: mantenha `/api` quando o frontend Nginx fizer proxy para o backend no mesmo compose. Para publicar backend e frontend em domínios separados, use a URL pública do backend, por exemplo `https://api.seudominio.com`.
|
||||
- `FRONTEND_PORT`, `BACKEND_PORT`, `SERVER_PORT` e `DB_PORT`: ajuste se as portas `8081`, `8082` ou `5433` já estiverem ocupadas.
|
||||
- `BUSCA_WORKER_ENABLED`: use `false` se quiser subir o ambiente sem processar buscas automaticamente.
|
||||
|
||||
3. Suba o ambiente completo:
|
||||
|
||||
```bash
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
O Compose sobe PostgreSQL, backend e frontend. O backend espera o healthcheck do banco, aplica as migrations do Flyway no startup e expõe a API em `http://localhost:8082` por padrão. O frontend fica em `http://localhost:8081` por padrão e encaminha chamadas `/api/*` para o backend interno.
|
||||
|
||||
Se você alterar `DB_PASSWORD` depois que o volume `postgres_data` já foi criado, o Postgres continuará usando a senha definida na primeira inicialização. Para trocar a senha em ambiente local descartável, recrie o volume; em produção, faça a rotação de senha pelo próprio banco antes de atualizar o `.env`.
|
||||
|
||||
## 6. Deploy em produção
|
||||
|
||||
O deploy oficial usa `escavador.seconectai.com.br`, Caddy para HTTPS automático e `docker-compose.prod.yml`.
|
||||
|
||||
Para instalar os comandos globais no servidor:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://producao-gitea.0jo7zj.easypanel.host/tiago/Prospeccao_SeConectai/raw/branch/main/scripts/bootstrap-commands.sh | sudo bash
|
||||
```
|
||||
|
||||
Primeira instalação:
|
||||
|
||||
```bash
|
||||
seconectaiescavador.install
|
||||
```
|
||||
|
||||
Atualização depois de novos commits:
|
||||
|
||||
```bash
|
||||
seconectaiescavador.update
|
||||
```
|
||||
|
||||
Veja detalhes em `DEPLOY.md`.
|
||||
|
||||
Credenciais iniciais de desenvolvimento:
|
||||
|
||||
- Email: `tiago27.dossantos@gmail.com`
|
||||
- Senha: `10031992`
|
||||
|
||||
## 7. Ordem de leitura recomendada para quem for executar
|
||||
|
||||
1. `LEIA-ME.md` (este arquivo)
|
||||
2. `ARQUITETURA-SCHEMA.md`
|
||||
3. `PROGRESSO.md` (ver o que já foi feito, se houver histórico)
|
||||
4. `etapas/01-...md` em diante, sequencialmente
|
||||
@@ -0,0 +1,32 @@
|
||||
# LeadScraper — Progresso do Projeto
|
||||
|
||||
**Instrução para quem estiver executando:** antes de iniciar uma etapa, confira aqui se a etapa anterior está `Concluída`. Ao terminar uma etapa, atualize a linha correspondente imediatamente — não deixe para atualizar em lote no final.
|
||||
|
||||
Status possíveis: `Não iniciada` | `Em andamento` | `Concluída` | `Bloqueada`
|
||||
|
||||
| # | Etapa | Arquivo | Status | Data conclusão | Observações |
|
||||
|---|-------|---------|--------|-----------------|-------------|
|
||||
| 01 | Setup do projeto, Docker e Flyway | `etapas/01-setup-projeto-docker-flyway.md` | Concluída | 2026-09-21 | Esqueleto Spring Boot, Docker Compose Postgres, application.yml, Flyway V1 e pacotes base criados. `mvn test` passou; Postgres subiu via Docker Compose; aplicação conectou e registrou `V1__init` em `flyway_schema_history`. |
|
||||
| 02 | Modelagem das entidades JPA | `etapas/02-modelagem-entidades-jpa.md` | Concluída | 2026-09-21 | Migrations `V2` a `V8`, entidades JPA, enums, repositories e teste de persistência implementados. `mvn test` passou com Postgres local; Flyway registrou `V1` a `V8` com sucesso. |
|
||||
| 03 | Motor de scraper com Playwright | `etapas/03-motor-scraper-playwright.md` | Em andamento | 2026-09-21 | Dependência Playwright instalada, Chromium baixado, serviços do scraper, DTO bruto, orquestrador e testes de parser implementados. `mvn test` passou. Pendente validação manual contra Google Maps real com 3 buscas. |
|
||||
| 04 | Normalizador e padronização de dados | `etapas/04-normalizador-padronizacao.md` | Concluída | 2026-09-21 | Normalizadores de nome, telefone, CNPJ, ramo e hash implementados, com `LeadNormalizerService` e `LeadNormalizadoDTO`. `mvn test` passou. Observação: o scraper atual ainda não fornece CNPJ bruto, então o serviço mantém CNPJ `null` até esse dado existir na extração. |
|
||||
| 05 | Pool de proxies e anti-bloqueio | `etapas/05-pool-proxies-anti-bloqueio.md` | Concluída | 2026-09-21 | `ProxyPoolService`, `BloqueioDetector`, `BackoffService`, delays humanizados e fingerprint variável integrados ao `BuscaOrchestrator`. Migration `V9` adiciona `proxies.em_uso`; arquitetura atualizada para v1.1. `mvn test` passou com Postgres via Docker, executado fora do sandbox para acessar `localhost:5432`. Observação: etapa 03 ainda mantém pendência de validação manual contra Google Maps real. |
|
||||
| 06 | API REST do backend | `etapas/06-api-rest-backend.md` | Concluída | 2026-09-21 | JWT, endpoints de auth/buscas/leads/ramos, exportação CSV, Swagger via springdoc, worker agendado e persistência com deduplicação por `place_id`/`hash_dedup` implementados. `mvn test` passou com Postgres local, executado fora do sandbox para acessar `localhost:5432`. Observação: persistência fim-a-fim com dados reais depende da validação manual do Google Maps pendente na etapa 03. |
|
||||
| 07 | Setup do dashboard frontend | `etapas/07-frontend-setup-dashboard.md` | Concluída | 2026-09-21 | Projeto React + Vite + TypeScript criado em `frontend/`, com Tailwind, componentes base estilo shadcn/ui, TanStack Query, React Hook Form + Zod, React Router e Recharts. Layout com sidebar/header, rotas protegidas por autenticação mockada e páginas mockadas implementadas. `npm run build` passou. `npm install` exigiu execução fora do sandbox para baixar pacotes. |
|
||||
| 08 | Integração frontend com a API | `etapas/08-frontend-integracao-api.md` | Concluída | 2026-09-22 | Cliente HTTP com JWT, autenticação real, buscas, leads, CSV, curadoria e dashboard integrados. Backend recebeu `PATCH /leads/{id}/ramo` e `GET /leads/resumo` para fechar curadoria e agregados. `npm run build` passou; Postgres subiu via Docker Compose; `mvn test` passou fora do sandbox com acesso a `localhost:5432`. |
|
||||
| 09 | Docker Compose e deploy | `etapas/09-docker-compose-deploy.md` | Concluída | 2026-09-22 | Dockerfiles do backend/frontend, Nginx com proxy `/api`, Compose com Postgres/backend/frontend, healthcheck, `.env.example` e documentação implementados. Validado com `docker compose -p leadscraper_stage09 --env-file .env.example up --build -d` em volume limpo: containers subiram, frontend respondeu 200, login direto e via `/api` responderam 200, Flyway aplicou V1 a V11 automaticamente. Observação: volume Postgres antigo mantém a senha original; para trocar senha em ambiente descartável é preciso recriar volume. |
|
||||
| 10 | Testes e validação final | `etapas/10-testes-validacao-final.md` | Concluída | 2026-09-22 | Validação final executada no stack Docker reconstruído. `mvn test` passou com 16 testes; `npm run build` passou. Smoke test via Nginx `/api`: frontend 200, login 200, endpoint protegido sem token 403, com token 200, export CSV 200. Busca real `padaria` em `Campinas, SP` concluiu com 92 encontrados em aproximadamente 65s; repetição concluiu com 54 encontrados e o total persistido permaneceu 86, validando deduplicação. Curadoria validada com criação de ramo e `PATCH /leads/{id}/ramo`, reduzindo leads sem ramo de 86 para 85. Segurança revisada: `JWT_SECRET` não possui fallback hardcoded no runtime e o formulário de login não preenche senha padrão; credencial bootstrap permanece apenas como dev seed documentado. Observação: Playwright baixou navegadores adicionais no primeiro uso e registrou aviso de dependências para Firefox/WebKit, mas o fluxo Chromium/Google Maps concluiu com sucesso. |
|
||||
|
||||
## Regras de preenchimento
|
||||
|
||||
- Nunca avance para a próxima etapa com a anterior em `Bloqueada` sem registrar o motivo em "Observações"
|
||||
- Se uma etapa exigir mudança no schema ou na arquitetura definida em `ARQUITETURA-SCHEMA.md`, registre isso em "Observações" e atualize a versão do documento de arquitetura correspondente
|
||||
- Se surgirem etapas novas depois da 10 (funcionalidades futuras), adicione novas linhas numeradas (11, 12...) em vez de renumerar as existentes
|
||||
|
||||
## Log de decisões técnicas relevantes
|
||||
|
||||
*(preencher conforme o projeto avança — decisões que fugirem do planejado originalmente em `ARQUITETURA-SCHEMA.md` devem ser registradas aqui com data e motivo)*
|
||||
|
||||
- 2026-09-21: Etapa 05 adicionou `proxies.em_uso` para reserva concorrente com `FOR UPDATE SKIP LOCKED`, evitando que duas buscas usem o mesmo proxy ao mesmo tempo.
|
||||
- 2026-09-22: Etapa 10 removeu fallback hardcoded de `JWT_SECRET` no runtime; ambientes locais/Docker agora precisam informar a variável explicitamente.
|
||||
- 2026-09-22: Adicionada migration `V12` para atualizar o usuário bootstrap para `tiago27.dossantos@gmail.com`, mantendo compatibilidade com volumes Flyway já aplicados.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Etapa 01 — Setup do projeto, Docker e Flyway
|
||||
|
||||
## Objetivo
|
||||
Criar o esqueleto do projeto backend em Spring Boot, configurar a conexão com PostgreSQL via Docker e deixar o Flyway pronto para rodar as migrations que virão nas próximas etapas.
|
||||
|
||||
## Contexto
|
||||
Este é o ponto de partida do projeto. Nenhum código de negócio é escrito aqui — o foco é ambiente funcional: projeto sobe, conecta no banco, Flyway aplica a primeira migration vazia com sucesso.
|
||||
|
||||
Consulte `ARQUITETURA-SCHEMA.md` (seção 1 e 2) para a stack e estrutura de pacotes a seguir.
|
||||
|
||||
## Tarefas
|
||||
|
||||
1. Criar projeto Spring Boot 3.x (via Spring Initializr ou manualmente) com as dependências:
|
||||
- `spring-boot-starter-web`
|
||||
- `spring-boot-starter-data-jpa`
|
||||
- `spring-boot-starter-validation`
|
||||
- `spring-boot-starter-security`
|
||||
- `flyway-core`
|
||||
- `postgresql` (driver)
|
||||
- `lombok`
|
||||
2. Criar a estrutura de pacotes conforme definido em `ARQUITETURA-SCHEMA.md` seção 2 (`config`, `domain`, `repository`, `scraper`, `normalizer`, `proxy`, `service`, `controller`, `security`, `worker`) — pode começar com pacotes vazios, serão populados nas próximas etapas
|
||||
3. Criar `docker-compose.yml` inicial (apenas serviço `postgres`, os demais serviços virão na etapa 09):
|
||||
```yaml
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_DB: leadscraper
|
||||
POSTGRES_USER: leadscraper
|
||||
POSTGRES_PASSWORD: <definir via .env>
|
||||
ports:
|
||||
- "5433:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
4. Configurar `application.yml` com datasource apontando para o Postgres do Docker, e Flyway habilitado
|
||||
5. Criar `src/main/resources/db/migration/V1__init.sql` vazio (ou só com um comentário) para validar que o Flyway roda corretamente na subida da aplicação
|
||||
6. Criar arquivo `.env.example` com as variáveis necessárias (senha do banco, etc.) — nunca commitar `.env` real
|
||||
7. Subir o Postgres via `docker compose up -d postgres` e validar que o Spring Boot conecta e aplica a migration inicial sem erro
|
||||
|
||||
## Critérios de aceite
|
||||
|
||||
- [ ] Projeto Spring Boot compila e sobe sem erros
|
||||
- [ ] `docker compose up -d postgres` sobe o banco corretamente
|
||||
- [ ] Aplicação conecta no Postgres e o Flyway registra a migration `V1__init` na tabela `flyway_schema_history`
|
||||
- [ ] Estrutura de pacotes criada conforme arquitetura
|
||||
- [ ] `.env.example` documentado
|
||||
@@ -0,0 +1,33 @@
|
||||
# Etapa 02 — Modelagem das entidades JPA
|
||||
|
||||
## Objetivo
|
||||
Implementar as migrations Flyway completas do schema e as entidades JPA correspondentes, conforme definido em `ARQUITETURA-SCHEMA.md` seção 4.
|
||||
|
||||
## Contexto
|
||||
Esta etapa traduz o schema SQL já definido na arquitetura para migrations Flyway numeradas e entidades Java. Não altere o schema aqui — se algo precisar mudar, volte primeiro em `ARQUITETURA-SCHEMA.md`, versione (v1.1) e só então implemente.
|
||||
|
||||
## Tarefas
|
||||
|
||||
1. Criar as migrations Flyway, uma tabela por arquivo, na ordem de dependência (por causa das foreign keys):
|
||||
- `V2__create_usuarios.sql`
|
||||
- `V3__create_ramos.sql`
|
||||
- `V4__create_proxies.sql`
|
||||
- `V5__create_buscas.sql`
|
||||
- `V6__create_leads.sql`
|
||||
- `V7__create_telefones.sql`
|
||||
- `V8__create_historico_contato.sql`
|
||||
2. Usar exatamente o DDL definido em `ARQUITETURA-SCHEMA.md` seção 4 (colunas, tipos, constraints, índices) — não improvisar campos novos sem atualizar o documento antes
|
||||
3. Criar as entidades JPA em `domain/`, uma classe por tabela: `Usuario`, `Ramo`, `Proxy`, `Busca`, `Lead`, `Telefone`, `HistoricoContato`
|
||||
- Usar `@Enumerated(EnumType.STRING)` para os campos de status (mais seguro que String livre) — criar enums correspondentes: `StatusBusca`, `StatusLead`, `StatusProxy`
|
||||
- Mapear os relacionamentos (`@ManyToOne`, `@OneToMany`) conforme as foreign keys do schema
|
||||
4. Criar as interfaces `repository/` (Spring Data JPA) para cada entidade: `LeadRepository`, `BuscaRepository`, `RamoRepository`, `ProxyRepository`, etc.
|
||||
- Adicionar métodos de consulta que já sabemos que serão necessários: `findByHashDedup`, `findByCnpj`, `findByStatus`, `findByBuscaId`
|
||||
5. Rodar a aplicação e validar que todas as migrations aplicam em sequência sem erro em um banco limpo
|
||||
|
||||
## Critérios de aceite
|
||||
|
||||
- [ ] Todas as migrations da lista aplicam em sequência em um banco limpo
|
||||
- [ ] Entidades JPA mapeiam corretamente para as tabelas (validar com um teste simples de save/find por entidade)
|
||||
- [ ] Enums de status criados e usados nas entidades correspondentes
|
||||
- [ ] Repositories criados com os métodos de consulta essenciais
|
||||
- [ ] Nenhuma divergência entre o schema SQL aplicado e o definido em `ARQUITETURA-SCHEMA.md`
|
||||
@@ -0,0 +1,33 @@
|
||||
# Etapa 03 — Motor de scraper com Playwright
|
||||
|
||||
## Objetivo
|
||||
Implementar o módulo `scraper/` responsável por abrir o navegador, buscar no Google Maps pelo termo e localização informados, rolar o painel de resultados até esgotar os leads e extrair os dados brutos de cada card.
|
||||
|
||||
## Contexto
|
||||
Nesta etapa ainda **não** há rotação de proxy nem delays humanizados sofisticados (isso é a etapa 05) — o objetivo aqui é o motor de extração funcionar de ponta a ponta em um ambiente controlado, sem se preocupar ainda com bloqueio. Também não há padronização dos dados ainda (etapa 04) — os dados saem brutos, exatamente como capturados da página.
|
||||
|
||||
## Tarefas
|
||||
|
||||
1. Adicionar a dependência do Playwright for Java ao projeto e rodar o comando de instalação dos browsers (`mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install chromium"` ou equivalente)
|
||||
2. Criar `scraper/GoogleMapsScraperService`, responsável por:
|
||||
- Abrir uma nova instância de navegador Chromium (headless configurável via propriedade, para permitir debug visual em dev)
|
||||
- Navegar até `google.com/maps`, localizar o campo de busca, digitar `termo_busca + localizacao` e submeter
|
||||
- Aguardar o painel de resultados lateral carregar
|
||||
3. Criar `scraper/ScrollExtractorService`, responsável por:
|
||||
- Rolar o painel de resultados (não a página inteira) em incrementos, aguardando carregamento entre cada scroll
|
||||
- Detectar o fim da lista (quando o número de resultados carregados para de crescer após N tentativas de scroll)
|
||||
4. Criar `scraper/LeadRawExtractor`, responsável por:
|
||||
- Para cada card de resultado, extrair via seletor: nome, categoria (texto bruto), endereço (texto bruto), telefone (texto bruto), site, `place_id` (se disponível na URL/atributo do elemento), avaliação
|
||||
- Retornar uma lista de DTOs `LeadBrutoDTO` (sem nenhuma padronização ainda)
|
||||
5. Criar `scraper/BuscaOrchestrator`, que:
|
||||
- Recebe uma `Busca` (entidade), executa os passos acima em sequência, e retorna a lista de `LeadBrutoDTO` extraída
|
||||
- Captura exceções de forma que uma falha em um card não interrompa a extração dos demais
|
||||
6. Testar manualmente com pelo menos 3 combinações diferentes de termo/localização, incluindo um caso com poucos resultados (< 10) e um com muitos (> 50, para validar o scroll)
|
||||
|
||||
## Critérios de aceite
|
||||
|
||||
- [ ] Motor consegue abrir o Maps, buscar e extrair resultados de uma busca simples de ponta a ponta
|
||||
- [ ] Scroll continua até esgotar os resultados reais (validado manualmente comparando com o número exibido no Maps)
|
||||
- [ ] Extração captura nome, telefone bruto, endereço bruto, categoria bruta, site e `place_id` quando disponíveis
|
||||
- [ ] Falha em um único card não derruba a extração dos demais
|
||||
- [ ] Testado com pelo menos 3 buscas diferentes de ramos/localizações distintas
|
||||
@@ -0,0 +1,43 @@
|
||||
# Etapa 04 — Normalizador e padronização de dados
|
||||
|
||||
## Objetivo
|
||||
Implementar o módulo `normalizer/` que transforma os `LeadBrutoDTO` extraídos na etapa 03 em dados padronizados prontos para persistência, conforme as regras definidas em `ARQUITETURA-SCHEMA.md` seção 5.
|
||||
|
||||
## Contexto
|
||||
Esta etapa não persiste nada ainda — produz um `LeadNormalizadoDTO` a partir do `LeadBrutoDTO`. A persistência com deduplicação é a etapa 06 (junto com a API).
|
||||
|
||||
## Tarefas
|
||||
|
||||
1. Criar `normalizer/NomeNormalizer`:
|
||||
- Trim + colapsar espaços múltiplos
|
||||
- Title Case, preservando em maiúsculo as siglas societárias: `LTDA`, `ME`, `EIRELI`, `S/A`, `S.A.`
|
||||
- Remover caracteres de controle/emoji
|
||||
2. Criar `normalizer/TelefoneNormalizer`:
|
||||
- Extrair apenas dígitos do texto bruto
|
||||
- Assumir DDI `+55` se ausente
|
||||
- Validar tamanho (DDD de 2 dígitos + número de 8 ou 9 dígitos); se inválido, retornar `null` no campo normalizado mas manter o bruto
|
||||
- Formatar em E.164 (`+5543999999999`)
|
||||
3. Criar `normalizer/CnpjValidator`:
|
||||
- Extrair apenas dígitos (14 posições)
|
||||
- Implementar validação de dígito verificador (algoritmo módulo 11)
|
||||
- Se inválido, descartar o CNPJ (gravar `null`) em vez de persistir um valor incorreto
|
||||
4. Criar `normalizer/RamoMapper`:
|
||||
- Buscar na tabela `ramos.aliases` (JSONB) se a categoria bruta já tem mapeamento conhecido
|
||||
- Se encontrar, retornar o `ramo_id` correspondente
|
||||
- Se não encontrar, retornar `ramo_id = null` e manter a categoria bruta em `categoria_bruta` para curadoria manual posterior
|
||||
5. Criar `normalizer/DedupHashCalculator`:
|
||||
- Calcular `hash_dedup = SHA-256(nomeNormalizado + "|" + telefoneE164)`
|
||||
6. Criar `normalizer/LeadNormalizerService` que orquestra os passos acima e produz o `LeadNormalizadoDTO` completo a partir de um `LeadBrutoDTO`
|
||||
7. Escrever testes unitários para cada normalizador, cobrindo casos-limite:
|
||||
- Telefone sem DDD, com formatação variada (parênteses, traços, espaços)
|
||||
- CNPJ com dígito verificador inválido
|
||||
- Nome com sigla societária em posições diferentes
|
||||
- Categoria bruta sem mapeamento conhecido em `ramos`
|
||||
|
||||
## Critérios de aceite
|
||||
|
||||
- [ ] Todos os normalizadores implementados e cobertos por testes unitários com casos-limite
|
||||
- [ ] CNPJ inválido nunca é persistido como válido
|
||||
- [ ] Telefone sempre sai em E.164 quando válido, ou `null` quando não for possível normalizar
|
||||
- [ ] Ramo sem mapeamento conhecido não quebra o fluxo — apenas fica pendente de curadoria
|
||||
- [ ] `hash_dedup` calculado de forma determinística e reproduzível para o mesmo par nome+telefone
|
||||
@@ -0,0 +1,36 @@
|
||||
# Etapa 05 — Pool de proxies e anti-bloqueio
|
||||
|
||||
## Objetivo
|
||||
Implementar o módulo `proxy/` e integrar rotação de proxies, delays humanizados e detecção de bloqueio ao motor de scraping da etapa 03, conforme `ARQUITETURA-SCHEMA.md` seção 6.
|
||||
|
||||
## Contexto
|
||||
Esta etapa assume que a tabela `proxies` já existe (etapa 02) e que o motor de scraping básico já funciona (etapa 03). Aqui o foco é blindar o motor contra bloqueio, não mudar a lógica de extração em si.
|
||||
|
||||
## Tarefas
|
||||
|
||||
1. Criar `proxy/ProxyPoolService`:
|
||||
- Selecionar um proxy `ativo` disponível, priorizando os com `ultima_utilizacao` mais antiga (distribuição de uso mais uniforme)
|
||||
- Marcar o proxy como "em uso" durante a execução de uma busca (para não ser selecionado concorrentemente por outra busca simultânea)
|
||||
- Ao final da busca, atualizar `ultima_utilizacao`
|
||||
2. Criar `proxy/BloqueioDetector`:
|
||||
- Verificar, após cada navegação, se a página retornada é uma página de captcha/"unusual traffic" (por texto/seletor característico)
|
||||
- Se detectar bloqueio: incrementar `total_bloqueios` do proxy, mudar status para `degradado` (ou `bloqueado` se ultrapassar um limite configurável de bloqueios), e sinalizar para o orchestrator tentar novamente com outro proxy
|
||||
3. Criar `proxy/BackoffService`:
|
||||
- Implementar backoff exponencial: se buscas consecutivas falharem por bloqueio, aumentar o intervalo de espera antes da próxima tentativa
|
||||
- Expor uma configuração de limite de buscas por hora por proxy (auto-throttling), configurável via `application.yml`
|
||||
4. Criar `scraper/DelayHumanizadoService`:
|
||||
- Gerar intervalos aleatórios (dentro de faixas configuráveis) entre ações: digitação, scroll, troca de busca
|
||||
- Aplicar scroll em passos pequenos e variáveis, nunca um salto direto
|
||||
5. Integrar fingerprint variável por sessão:
|
||||
- Configurar o contexto do Playwright (`BrowserContext`) com User-Agent, viewport e timezone variáveis por sessão de busca, coerentes com a região do proxy usado
|
||||
6. Integrar todos os módulos acima ao `BuscaOrchestrator` da etapa 03, sem alterar a lógica de extração de dados em si
|
||||
7. Atualizar `Busca` para gravar `proxy_id` usado e, em caso de erro, o `detalhe_erro`
|
||||
|
||||
## Critérios de aceite
|
||||
|
||||
- [ ] Proxy é selecionado do pool e liberado corretamente ao final de cada busca
|
||||
- [ ] Bloqueio é detectado e o proxy correspondente é marcado como `degradado`/`bloqueado` automaticamente
|
||||
- [ ] Backoff exponencial ativa após falhas consecutivas, sem exigir intervenção manual
|
||||
- [ ] Delays e scroll não seguem padrão fixo (validar observando os logs/tempos entre execuções)
|
||||
- [ ] Fingerprint (User-Agent, viewport, timezone) varia entre sessões de busca
|
||||
- [ ] `busca.proxy_id` e `busca.detalhe_erro` são preenchidos corretamente
|
||||
@@ -0,0 +1,41 @@
|
||||
# Etapa 06 — API REST do backend
|
||||
|
||||
## Objetivo
|
||||
Expor os endpoints REST que o frontend vai consumir: autenticação, disparo de buscas, consulta/listagem de leads, exportação e curadoria de ramos.
|
||||
|
||||
## Contexto
|
||||
Esta etapa também implementa a persistência com deduplicação (que ainda não existia nas etapas anteriores) e o worker que processa buscas pendentes de forma assíncrona.
|
||||
|
||||
## Tarefas
|
||||
|
||||
1. Criar `security/` com autenticação JWT:
|
||||
- `POST /auth/login` — recebe email/senha, retorna token JWT
|
||||
- Proteger todos os demais endpoints exigindo o token
|
||||
2. Criar `worker/BuscaWorker`:
|
||||
- Processo assíncrono (`@Scheduled` com polling, ou `@Async`) que busca registros `buscas` com status `pendente`, executa o `BuscaOrchestrator` (etapas 03+05) e, ao final, aplica o `LeadNormalizerService` (etapa 04) em cada lead extraído
|
||||
- Antes de persistir, checar duplicidade por `hash_dedup` e `place_id`; se já existir, atualizar o vínculo com a nova busca em vez de duplicar
|
||||
- Atualizar `busca.status`, `busca.total_encontrados`, `busca.finalizado_em` ao concluir (ou `erro` + `detalhe_erro` em caso de falha)
|
||||
3. Criar `controller/BuscaController`:
|
||||
- `POST /buscas` — cria uma nova busca (termo, localização, ramo opcional) com status `pendente`
|
||||
- `GET /buscas` — lista buscas (paginado, filtrável por status)
|
||||
- `GET /buscas/{id}` — detalhe de uma busca específica, incluindo status e total de leads encontrados
|
||||
4. Criar `controller/LeadController`:
|
||||
- `GET /leads` — lista leads paginada, com filtros por ramo, cidade/UF, status, busca de origem
|
||||
- `GET /leads/{id}` — detalhe de um lead
|
||||
- `PATCH /leads/{id}/status` — atualizar status do lead (novo, contatado, qualificado, convertido, descartado)
|
||||
- `GET /leads/export` — exportação em CSV dos leads filtrados
|
||||
5. Criar `controller/RamoController`:
|
||||
- `GET /ramos` — lista ramos cadastrados
|
||||
- `POST /ramos` — cria novo ramo (curadoria manual)
|
||||
- `PATCH /ramos/{id}/aliases` — adiciona um alias a um ramo existente (para a fila de curadoria de categorias sem mapeamento)
|
||||
- `GET /leads/sem-ramo` — lista leads com `ramo_id = null` pendentes de curadoria
|
||||
6. Documentar a API (Swagger/OpenAPI via springdoc) para facilitar a integração do frontend
|
||||
|
||||
## Critérios de aceite
|
||||
|
||||
- [ ] Login retorna JWT válido e endpoints protegidos rejeitam requisição sem token
|
||||
- [ ] Criar uma busca via API resulta, em segundos/minutos, em leads persistidos sem duplicação
|
||||
- [ ] Listagem de leads suporta paginação e os filtros definidos
|
||||
- [ ] Exportação CSV funciona com os filtros aplicados
|
||||
- [ ] Fila de curadoria de ramos (`/leads/sem-ramo`) reflete corretamente os leads sem mapeamento
|
||||
- [ ] Documentação Swagger acessível e atualizada
|
||||
@@ -0,0 +1,37 @@
|
||||
# Etapa 07 — Setup do dashboard frontend
|
||||
|
||||
## Objetivo
|
||||
Criar o esqueleto do frontend (React + Vite + TypeScript) com a estrutura de páginas e componentes base, ainda sem integração real com a API (usar dados mockados).
|
||||
|
||||
## Contexto
|
||||
Esta etapa é só estrutura visual e navegação. A integração real com o backend é a etapa 08.
|
||||
|
||||
## Tarefas
|
||||
|
||||
1. Criar projeto com `npm create vite@latest` (template `react-ts`)
|
||||
2. Instalar e configurar: Tailwind CSS, shadcn/ui, TanStack Query, React Hook Form + Zod, React Router, Recharts
|
||||
3. Criar estrutura de pastas:
|
||||
```
|
||||
src/
|
||||
├── pages/
|
||||
│ ├── LoginPage.tsx
|
||||
│ ├── DashboardPage.tsx (visão geral: total de leads, buscas recentes, gráfico por ramo)
|
||||
│ ├── NovaBuscaPage.tsx (formulário para disparar nova busca)
|
||||
│ ├── LeadsPage.tsx (listagem/filtro/exportação de leads)
|
||||
│ ├── BuscasPage.tsx (histórico e status de buscas)
|
||||
│ └── CuradoriaRamosPage.tsx (fila de leads sem ramo mapeado)
|
||||
├── components/
|
||||
├── hooks/
|
||||
├── lib/
|
||||
└── types/
|
||||
```
|
||||
4. Implementar layout base (sidebar de navegação + header) com shadcn/ui
|
||||
5. Criar as páginas acima com dados mockados (arrays estáticos) só para validar layout e navegação
|
||||
6. Configurar roteamento protegido (redireciona para login se não autenticado — pode usar um mock de autenticação nesta etapa)
|
||||
|
||||
## Critérios de aceite
|
||||
|
||||
- [ ] Projeto sobe com `npm run dev` sem erros
|
||||
- [ ] Todas as páginas listadas existem e são navegáveis pela sidebar
|
||||
- [ ] Layout responsivo básico funcional (não precisa ser pixel-perfect nesta etapa)
|
||||
- [ ] Dados mockados suficientes para visualizar tabelas, formulário e gráfico preenchidos
|
||||
@@ -0,0 +1,33 @@
|
||||
# Etapa 08 — Integração do frontend com a API
|
||||
|
||||
## Objetivo
|
||||
Substituir os dados mockados da etapa 07 por integração real com a API REST do backend (etapa 06).
|
||||
|
||||
## Tarefas
|
||||
|
||||
1. Criar `lib/api.ts` com cliente HTTP configurado (axios ou fetch wrapper), incluindo injeção automática do token JWT nas requisições
|
||||
2. Implementar autenticação real: `LoginPage` chama `POST /auth/login`, armazena token (ex: em memória + refresh via contexto React, evitar localStorage para o token se possível por segurança)
|
||||
3. Integrar `NovaBuscaPage`:
|
||||
- Formulário (React Hook Form + Zod) envia `POST /buscas`
|
||||
- Após criar, redirecionar para `BuscasPage` mostrando a busca recém-criada com status `pendente`
|
||||
4. Integrar `BuscasPage`:
|
||||
- `GET /buscas` com paginação
|
||||
- Polling (TanStack Query com `refetchInterval`) para atualizar status das buscas `pendente`/`em_execucao` automaticamente até virarem `concluida`/`erro`
|
||||
5. Integrar `LeadsPage`:
|
||||
- `GET /leads` com os filtros (ramo, cidade/UF, status, busca de origem) refletidos na URL (query params) para permitir compartilhar/voltar filtros
|
||||
- Botão de exportação chamando `GET /leads/export` e disparando download do CSV
|
||||
- Ação de atualizar status do lead via `PATCH /leads/{id}/status`
|
||||
6. Integrar `CuradoriaRamosPage`:
|
||||
- `GET /leads/sem-ramo`
|
||||
- Ação para vincular a um ramo existente ou criar novo ramo com alias, chamando os endpoints de `RamoController`
|
||||
7. Integrar `DashboardPage` com dados agregados reais (pode exigir um endpoint adicional simples no backend para contagens, se não existir ainda — registrar essa necessidade em `PROGRESSO.md` caso apareça)
|
||||
8. Tratar estados de loading/erro em todas as páginas (skeleton states, mensagens de erro amigáveis)
|
||||
|
||||
## Critérios de aceite
|
||||
|
||||
- [ ] Login real funcional, com logout e expiração de sessão tratados
|
||||
- [ ] Criar busca pelo frontend resulta em leads aparecendo em `LeadsPage` sem necessidade de reload manual (polling funcionando)
|
||||
- [ ] Filtros de `LeadsPage` refletidos na URL e persistentes ao navegar
|
||||
- [ ] Exportação CSV funcional a partir do frontend
|
||||
- [ ] Curadoria de ramos funcional de ponta a ponta
|
||||
- [ ] Estados de loading/erro tratados em todas as integrações
|
||||
@@ -0,0 +1,64 @@
|
||||
# Etapa 09 — Docker Compose e deploy
|
||||
|
||||
## Objetivo
|
||||
Consolidar os três serviços (postgres, backend, frontend) em um único `docker-compose.yml` capaz de subir o ambiente completo com um comando.
|
||||
|
||||
## Tarefas
|
||||
|
||||
1. Criar `Dockerfile` do backend (multi-stage build: build com Maven, runtime com JRE 21 slim), incluindo a instalação das dependências do Playwright (Chromium) na imagem final
|
||||
2. Criar `Dockerfile` do frontend (multi-stage: build com Node, servir estático via Nginx)
|
||||
3. Consolidar o `docker-compose.yml` final:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_DB: leadscraper
|
||||
POSTGRES_USER: ${DB_USER}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
backend:
|
||||
build: ./backend
|
||||
environment:
|
||||
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/leadscraper
|
||||
SPRING_DATASOURCE_USERNAME: ${DB_USER}
|
||||
SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "8082:8082"
|
||||
|
||||
frontend:
|
||||
build: ./frontend
|
||||
environment:
|
||||
VITE_API_URL: ${API_URL}
|
||||
depends_on:
|
||||
- backend
|
||||
ports:
|
||||
- "8081:80"
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
|
||||
4. Criar `.env.example` na raiz com todas as variáveis referenciadas (`DB_USER`, `DB_PASSWORD`, `JWT_SECRET`, `API_URL`)
|
||||
5. Validar que `docker compose up --build` sobe os três serviços do zero, aplica as migrations automaticamente (Flyway roda no startup do backend) e o frontend consegue falar com o backend
|
||||
6. Documentar no `LEIA-ME.md` o comando de subida completa e como configurar o `.env` para produção (trocar senhas padrão, ajustar `VITE_API_URL` para o domínio real)
|
||||
|
||||
## Critérios de aceite
|
||||
|
||||
- [ ] `docker compose up --build` sobe o ambiente completo em uma máquina limpa
|
||||
- [ ] Migrations aplicam automaticamente sem passo manual
|
||||
- [ ] Frontend acessível na porta configurada e conseguindo autenticar/consultar dados via backend
|
||||
- [ ] Variáveis sensíveis (senha do banco, JWT secret) vêm de `.env`, nunca hardcoded nos Dockerfiles ou no compose
|
||||
- [ ] `LEIA-ME.md` atualizado com instruções de subida
|
||||
@@ -0,0 +1,40 @@
|
||||
# Etapa 10 — Testes e validação final
|
||||
|
||||
## Objetivo
|
||||
Validar o sistema de ponta a ponta rodando inteiramente via Docker, cobrindo o fluxo completo desde disparo de busca até exportação de leads, e revisar pontos de manutenção antes de considerar o projeto pronto para uso real.
|
||||
|
||||
## Tarefas
|
||||
|
||||
1. **Teste de fluxo completo (ambiente Docker):**
|
||||
- Subir o ambiente do zero (`docker compose up --build`)
|
||||
- Logar no dashboard
|
||||
- Disparar uma nova busca por um ramo/localização real
|
||||
- Acompanhar o status até `concluida`
|
||||
- Validar na listagem de leads que os dados vieram padronizados (telefone em E.164, CNPJ válido quando presente, nome formatado)
|
||||
- Exportar CSV e conferir os dados
|
||||
2. **Teste de deduplicação:**
|
||||
- Rodar a mesma busca (ou uma busca sobreposta, ex: mesmo ramo em raio maior) duas vezes
|
||||
- Confirmar que leads já existentes não duplicam, apenas atualizam o vínculo de busca
|
||||
3. **Teste de resiliência do scraper:**
|
||||
- Simular/observar um cenário de bloqueio (ou revisar os logs de uma execução real que tenha batido em captcha)
|
||||
- Confirmar que o proxy foi marcado como `degradado`/`bloqueado` e que o backoff funcionou
|
||||
4. **Teste de curadoria de ramos:**
|
||||
- Rodar uma busca com um termo cuja categoria ainda não tem alias mapeado
|
||||
- Confirmar que o lead aparece em `/leads/sem-ramo` e que vincular um ramo manualmente funciona e reflete na listagem principal
|
||||
5. **Revisão de segurança básica:**
|
||||
- Confirmar que endpoints protegidos realmente exigem token válido
|
||||
- Confirmar que senhas/segredos não estão hardcoded em nenhum arquivo versionado
|
||||
6. **Revisão de performance:**
|
||||
- Rodar uma busca com muitos resultados (50+) e medir tempo total do fluxo completo
|
||||
- Identificar gargalos óbvios (ex: scroll muito lento, N+1 queries na listagem de leads) e registrar como itens de melhoria futura em `PROGRESSO.md`, mesmo que não sejam corrigidos nesta etapa
|
||||
7. **Atualizar `PROGRESSO.md`** marcando todas as etapas como concluídas e registrando quaisquer pendências conhecidas para uma futura etapa 11+
|
||||
|
||||
## Critérios de aceite
|
||||
|
||||
- [ ] Fluxo completo validado rodando 100% via Docker, sem passos manuais fora do documentado
|
||||
- [ ] Deduplicação confirmada em cenário real de busca repetida
|
||||
- [ ] Comportamento de bloqueio/backoff observado e confirmado funcionando
|
||||
- [ ] Curadoria de ramos validada de ponta a ponta
|
||||
- [ ] Nenhum segredo hardcoded encontrado na revisão
|
||||
- [ ] Gargalos de performance conhecidos documentados (mesmo que não resolvidos ainda)
|
||||
- [ ] `PROGRESSO.md` refletindo o estado real final do projeto
|
||||
Reference in New Issue
Block a user