* 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>
2.6 KiB
2.6 KiB