feat/remove-fornecedores-home
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 37296f758f |
feat: capturar origem de marketing do briefing de contato (MAN-105) (#37)
* feat: capturar origem de marketing do briefing de contato (MAN-105) Adiciona tracking simples da origem (UTM ou referrer, primeiro-toque vence) para satisfazer WEB-05: a origem viaja apenas com o e-mail interno do briefing, sem Lead model, tabela ou superfície de CRM (Fase 2 fora de escopo). Middleware no grupo "web" cobre só as rotas públicas (o painel Filament tem sua própria stack de middleware); lógica de captura/sanitização isolada em App\Domain\Contact para não poluir controller nem view. Fail-open por construção: qualquer falha ao capturar ou ler a origem é logada sem dados pessoais e a submissão segue normalmente. Respeita LGPD (§12.5): nada além da origem é guardado, valores são truncados para não inflar a sessão, e um referrer apontando para o próprio host é descartado por não ser sinal de marketing. Valores não confiáveis (query string, referrer) são sanitizados antes de entrar na sessão e no e-mail em HTML. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code * fix(contato): capturar apenas a origem do referrer, não a URL completa O fallback de referrer em MarketingOrigin::capture() armazenava a URL inteira (incluindo query string e fragmento), não apenas o site de origem. Um link de campanha de e-mail personalizado (ex.: ?email=...&subscriber_id=...) chegava intacto à sessão (persistida em banco via SESSION_DRIVER=database) e ao corpo do e-mail de briefing lido por um humano — indo além do que SPEC.md WEB-05 pede ("origem de marketing") e do §12.5 ("coletar apenas dados necessários"). Agora o referrer é reduzido a esquema+host antes de sanitizar, descartando path, query string e userinfo. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code * test(contato): cobrir first-touch quando a navegação seguinte traz UTM concorrente test_first_touch_wins_when_later_navigation_has_no_utm não exercitava de fato a guarda "primeira captura vence" em CaptureMarketingOrigin — as duas navegações seguintes não carregavam nenhum sinal (nem UTM, nem referrer), então MarketingOrigin::capture() já retornava [] e o early-return por ausência de sinal (não a guarda de sessão) é quem impedia a sobrescrita. Confirmado removendo a guarda: a suíte continuava passando. Este teste novo usa uma segunda navegação com UTM diferente (utm_source=facebook), o que só passa se a guarda de sessão estiver ativa — mutação verificada manualmente antes de escrever a asserção. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code * fix(contato): não capturar origem em sitemap.xml e robots.txt O middleware estava anexado ao grupo `web` inteiro, que também cobre `/sitemap.xml` e `/robots.txt`. Um crawler chegando em `/sitemap.xml?utm_source=...` consumia o slot de primeiro toque com tráfego que nunca vai enviar um briefing, e ainda iniciava sessão em uma rota técnica. As duas rotas passam a ser ignoradas por nome, com teste cobrindo que elas não gravam nada na sessão e que uma visita humana seguinte ainda é capturada normalmente. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code --------- Co-authored-by: manoel.neto <manoel.neto@creditas.com> |
|||
| eed8240487 |
fix(contato): normalizar e-mail e telefone no formulário público de briefing (#31)
* fix(contato): normalizar e-mail e telefone no formulário público de briefing
SPEC.md §12.3 exige normalização de e-mail e telefone no formulário
público, mas o ContactBriefingRequest só validava, sem normalizar
(MAN-104).
E-mail: aplica trim + lowercase em prepareForValidation — evita
duplicatas triviais ("Maria@X.com" vs "maria@x.com") e mantém a
detecção de duplicidade por hash consistente.
Telefone: normaliza para o formato brasileiro legível
"(DD) 9XXXX-XXXX"/"(DD) XXXX-XXXX", extraído para o value object
App\Domain\Contact\BrazilianPhoneNumber por concentrar lógica de
decisão (contagem de dígitos, remoção de DDI 55) que merece teste
isolado. Optou-se pelo formato com máscara em vez de dígitos puros
porque o Mailable ContactBriefing e seus templates (html/text) apenas
imprimem o valor do campo "Telefone/WhatsApp" em uma tabela/lista,
sem link "tel:" nem formatação na view — "(11) 98888-7777" é o que
fica legível para quem recebe o briefing por e-mail, enquanto
"11988887777" é opaco de bater o olho.
Para não descartar informação que o destinatário precisa, o
normalizador só reformata quando a string é composta exclusivamente
por caracteres de telefone; anotações como "(WhatsApp)" ou "falar com
João" junto do número são preservadas como estão. Entradas que não
batem com 10/11 dígitos (após remover DDI) também são preservadas,
apenas com espaços internos colapsados.
Honeypot, rate limit e aceite de privacidade não foram tocados.
Cobertura: teste de unidade para o value object (formatos válidos,
DDI, anotações, formatos não reconhecidos) e teste de feature
provando que uma submissão com e-mail maiúsculo/padded e telefone
"sujo" chega normalizada nos Mailables ContactBriefing e
ContactBriefingConfirmation.
Co-Authored-By: Claude noreply@anthropic.com
AI-Assisted: yes
AI-Tool: claude-code
* fix(contato): evitar fabricar DDD/9º dígito em números não brasileiros
O normalizador aceitava qualquer string de 10 ou 11 dígitos como se
fosse um telefone brasileiro, sem checar se os dois primeiros dígitos
formam um DDD plausível (11-99, nunca com zero em nenhuma posição) ou
se um número de 11 dígitos tem o 9º dígito obrigatório do celular.
Isso fazia números estrangeiros como '2025551234' (EUA) virarem
'(20) 2555-1234' — um número brasileiro plausível, porém inventado,
que destrói silenciosamente o contato real na tabela do e-mail de
briefing. Um '+55' explícito sem DDD (ex.: '+55 98888-7777') também
era lido como DDD 55 em vez de número incompleto.
Passa a validar a forma do DDD e o 9º dígito do celular antes de
formatar, preservando o texto original quando a checagem falha —
mesmo comportamento já usado para números com contagem de dígitos
fora do padrão. Mantém DDD 55 (Rio Grande do Sul) funcionando
normalmente quando o DDD é digitado de fato.
Limitação residual conhecida e aceita: DDDs americanos que colidem
estruturalmente com um DDD brasileiro válido (ex. '2125551234', área
212 de Nova York, bate com DDD 21) continuam sendo formatados como
brasileiros — não há como distinguir os dois casos só pela forma dos
dígitos. Da mesma forma, um '55' sem o '+' explícito (ex.
'55988887777') é ambiguamente tratado como DDD 55 real, já que nada
no texto indica se é código de país ou área.
Co-Authored-By: Claude noreply@anthropic.com
AI-Assisted: yes
AI-Tool: claude-code
---------
Co-authored-by: manoel.neto <manoel.neto@creditas.com>
|