Initial commit
This commit is contained in:
@@ -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