# 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