diff --git a/docs/plans/captura-novidades-home-campanha-ui-draft-desktop.png b/docs/plans/captura-novidades-home-campanha-ui-draft-desktop.png new file mode 100644 index 00000000..776de3e8 Binary files /dev/null and b/docs/plans/captura-novidades-home-campanha-ui-draft-desktop.png differ diff --git a/docs/plans/captura-novidades-home-campanha-ui-draft-mobile.png b/docs/plans/captura-novidades-home-campanha-ui-draft-mobile.png new file mode 100644 index 00000000..ea9fc281 Binary files /dev/null and b/docs/plans/captura-novidades-home-campanha-ui-draft-mobile.png differ diff --git a/docs/plans/captura-novidades-home-campanha-ui-draft-sucesso.png b/docs/plans/captura-novidades-home-campanha-ui-draft-sucesso.png new file mode 100644 index 00000000..d04f6eb5 Binary files /dev/null and b/docs/plans/captura-novidades-home-campanha-ui-draft-sucesso.png differ diff --git a/docs/plans/captura-novidades-home-campanha-ui-draft.html b/docs/plans/captura-novidades-home-campanha-ui-draft.html new file mode 100644 index 00000000..96dfac97 --- /dev/null +++ b/docs/plans/captura-novidades-home-campanha-ui-draft.html @@ -0,0 +1,237 @@ + + + + + + + UI draft — Captura de novidades na home de campanha + + +
+

+ Visitante da home de campanha demonstra apoio e deixa o contato para receber comunicações + (e-mail ou grupos de WhatsApp) — CTA do hero leva à seção de captura na própria página. +

+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Quero apoiar +
+
+ Receba novidades +
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+ Nome completo* +
+
+ WhatsApp (com DDD)* +
+
+ E-mail (opcional) +
+
+
+ Estado (opcional) +
+
+ Cidade (opcional) +
+
+
+ Comentário (opcional) +
+
+
+
+ ✓ +
+
+ Quero fazer parte do time: receber novidades com frequência, ser + adicionado(a) aos grupos de WhatsApp da campanha e participar das ações. +
+
+

+ Sem marcar, você recebe apenas comunicações esporádicas da campanha — sem grupos e + sem envios frequentes. +

+
+
+
+
+ QUERO RECEBER NOVIDADES +
+
+

+ Ao enviar, você demonstra apoio à campanha e escolhe como quer acompanhar: comunicações + frequentes e grupos de WhatsApp (marcando a opção acima) ou comunicações esporádicas — + conforme a Política de Privacidade (LGPD). +

+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ Quero apoiar +
+
+ Receba novidades +
+
+
+ +
+
+
+
+
+
+ Nome completo* +
+
+ WhatsApp (com DDD)* +
+
+ E-mail (opcional) +
+
+
+ Estado (opcional) +
+
+ Cidade (opcional) +
+
+
+
+
+ Comentário (opcional) +
+
+
+
+
+ ✓ +
+
+ Quero fazer parte do time: receber novidades com frequência, ser + adicionado(a) aos grupos de WhatsApp da campanha e participar das ações. +
+
+

+ Sem marcar, você recebe apenas comunicações esporádicas da campanha — sem grupos e sem + envios frequentes. +

+
+
+
+ QUERO RECEBER NOVIDADES +
+
+

+ Ao enviar, você demonstra apoio à campanha e escolhe como quer acompanhar: comunicações + frequentes e grupos de WhatsApp (marcando a opção acima) ou comunicações esporádicas — + conforme a Política de Privacidade (LGPD). +

+
+
+ +
+
+
+
+

+ Você vai receber as novidades da campanha. Enquanto isso, acompanhe as bandeiras da + campanha abaixo. +

+
+
+
+
+ + diff --git a/docs/plans/captura-novidades-home-campanha.md b/docs/plans/captura-novidades-home-campanha.md new file mode 100644 index 00000000..b6c8ddc0 --- /dev/null +++ b/docs/plans/captura-novidades-home-campanha.md @@ -0,0 +1,90 @@ +# Captura de novidades da campanha na home pública + +Status: registrado +Atualizado em: 2026-08-19 +Issue: #93 +Priority: P1 +Model: composer-2.5 +Impeccable: B — encaixe na home de campanha (CTA do hero + nova seção de captura) +Rascunho UI: docs/plans/captura-novidades-home-campanha-ui-draft.html + PNGs embutidos abaixo +Appetite: ~1–1,5 dia eng; seção de captura na home + CTA no hero + ação transacional com consentimento novo fail-closed +Responsável: — + +## Intenção + +O site público de campanha (home `jorgesolla1313.com.br`) hoje converte visitante em **doação** (CTA "Quero apoiar") e em **comunidade WhatsApp** (`/mandato-no-whatsapp`), mas não captura o visitante que demonstra apoio e quer acompanhar a campanha sem necessariamente doar. Sem esse degrau, o tráfego orgânico e pago da home sai sem deixar contato, e o time não tem lista própria de interesse para comunicações (novidades, agenda, ações) e para grupos de WhatsApp da campanha. A campanha está em janela eleitoral (propaganda iniciada 16/08, 1º turno 04/10) — cada visitante não capturado é audiência perdida para as semanas decisivas. + +A captura tem dupla função, confirmada com produto: o contato é **um sinal de apoio** a Solla (a pessoa se dispõe a receber comunicações da campanha) **e** a porta de entrada para comunicações por e-mail **ou inclusão em grupos de WhatsApp da campanha** — por isso o telefone é campo obrigatório. + +## Persona e fluxo + +- **Persona / contexto:** visitante da home de campanha (eleitor simpatizante ou indeciso que quer acompanhar), navegando no celular ou no desktop; demonstra apoio deixando o contato para receber comunicações. +- **Job principal:** deixar o contato em segundos (nome + WhatsApp) para receber novidades da campanha, sem sair da página, escolhendo o nível de engajamento. +- **Fluxo desejado:** + 1. Chega na home e vê o CTA secundário do hero apontando para a captura (substitui "Conhecer bandeiras"). + 2. Preenche nome + WhatsApp (obrigatórios) e, opcionalmente, e-mail, estado, cidade e comentário, numa seção da própria home. + 3. Decide o nível de engajamento num toggle **pré-selecionado** "Quero fazer parte do time": marcado = comunicações frequentes + inclusão em grupos de WhatsApp da campanha + participação nas ações; desmarcado = apenas comunicações esporádicas, sem grupos e sem envios frequentes. + 4. Envia; a seção vira confirmação in-place ("Inscrição confirmada"). + 5. O registro vira um contato com consentimento próprio **e a escolha do toggle gravada** (nível "time" vs "esporádico"), visível no admin junto das demais listas públicas. +- **Anti-goals de produto:** segundo cadastro de pessoa paralelo a `Contact`; gravar `supporter` (registro nominal interno da campanha — bloqueado pelo lote jurídico da Onda 0; este fluxo é captura pública com consentimento próprio, não o registro de apoiador interno); virar mini-CRM com segmentação/fase do funil; mesclar com o fluxo `/mandato-no-whatsapp` (comunidade do mandato, consentimento `whatsapp-inscricao` — canal e semântica diferentes). + +### Rascunho UI (B) + +![Rascunho UI — desktop](captura-novidades-home-campanha-ui-draft-desktop.png) + +![Rascunho UI — mobile](captura-novidades-home-campanha-ui-draft-mobile.png) + +![Rascunho UI — sucesso](captura-novidades-home-campanha-ui-draft-sucesso.png) + +Fonte iterável: `docs/plans/captura-novidades-home-campanha-ui-draft.html`. + +## Objetivo e aceite + +- Visitante da home consegue demonstrar apoio e deixar **nome + WhatsApp** (obrigatórios) para receber comunicações da campanha, com e-mail, estado, cidade e comentário opcionais, e confirmação imediata na própria página. +- **Toggle "Quero fazer parte do time" pré-selecionado**: marcado = comunicações frequentes + inclusão em grupos de WhatsApp da campanha; desmarcado = apenas comunicações esporádicas (sem grupos, sem envios frequentes). A **escolha fica gravada no registro** — o admin distingue quem é "time" de quem é "esporádico". +- O CTA secundário do hero ("Conhecer bandeiras") vira o atalho para a seção de captura — as bandeiras continuam acessíveis na navegação da própria home (âncora `#bandeiras` já existe; o fluxo de captura não pode escondê-las). +- O registro é gravado como contato + vínculo de assinatura com **consentimento novo** (não reutiliza `whatsapp-inscricao`), no padrão fail-closed dos fluxos públicos existentes: sem o(s) texto(s) de consentimento aprovado(s) configurado(s) no admin, o form recusa a captura (nenhum dado é gravado). +- O(s) texto(s) do consentimento cobre(m) os dois níveis — "fazer parte do time" (comunicações frequentes + grupos de WhatsApp) e "esporádico" — aprovado(s) pelo jurídico antes da ativação. +- Guardrail LGPD: nenhuma captura antes do(s) documento(s) de consentimento (chave estável nova) existir(em) no admin — o executor resolve por chave, não por ID; a estrutura exata do consentimento (um texto com dois níveis vs. dois textos) é decisão de implementação junto do jurídico. + +## Dados (intenção) + +- **Vou apresentar dados?** Não — a captura é o dado; o consumo (quantidade de inscritos, crescimento) é leitura do admin existente, sem superfície nova. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** home `src/app/(frontend)/(home)/page.tsx` (nova seção + composição), hero `src/components/CampaignHero.tsx` (troca do CTA secundário), componente de formulário cliente em `src/components/` no padrão `WhatsappForm` (campos `NameInput`/`PhoneInput`/`EmailInput`/`StateSelect`/`CitySelect` já existem e são reutilizáveis), action em `src/app/(frontend)/actions/` no padrão `submitWhatsapp` (transação `Contact` + `Subscription` com consent por chave estável, `requireConsentByKey` fail-closed), chave nova em `src/lib/campaignConsentKeys.ts`. +- **Precedente a olhar:** `src/app/(frontend)/actions/submitWhatsapp.ts` (transação + consent), `src/components/WhatsappForm.tsx` (form e inputs), `src/lib/schemas/whatsapp-form.ts` (schema base `contactSchema` — obrigatoriedade difere: aqui nome + telefone), `/mandato-no-whatsapp` (página irmã — **não** mesclada nesta fatia). +- **Risco de acoplamento:** a home é superfície compartilhada do trilho S (últimos itens S1–S6) — toca o mesmo arquivo que o item S8 (pixel) tocará; serializado por dependência. + +## Dependências + +- Nenhuma dura. **S10** (pixel da home) depende deste item para disparar `Lead` no sucesso do form. + +## Fora de escopo + +- Mescla com `/mandato-no-whatsapp` ou reuso do consentimento `whatsapp-inscricao`. +- Gravação em `supporter` (registro nominal interno da campanha) — permanece bloqueado pelo lote jurídico da Onda 0. +- Segmentação, tags de interesse, double opt-in por e-mail, integração com ferramenta de e-mail marketing — anotar como gatilho futuro se marketing pedir. +- Redesign do hero ou da home (mudança de um CTA + uma seção). + +## Rabbit holes de produto + +- **Mini-CRM.** Se alguém "só completar": campos extras, segmentação, fases de funil. **Corte neste item:** os 6 campos do rascunho (nome + WhatsApp obrigatórios; e-mail, estado, cidade, comentário opcionais) e nada mais; o vínculo reutiliza o modelo de assinatura existente. +- **"Já que o telefone é obrigatório, vira apoiador do sistema interno".** Puxa o registro nominal bloqueado juridicamente. **Corte:** o registro vive nas listas públicas de contato/assinatura com consentimento próprio; `supporter` só quando o lote jurídico liberar. +- **"Um consentimento só para tudo" ou "dois consents mas um só registro".** A escolha do toggle tem que sobreviver no dado, senão o time não sabe quem pode entrar em grupo. **Corte:** a escolha fica gravada e visível no admin; a forma (um texto com os dois níveis vs. dois textos) é decisão de implementação com o jurídico. +- **Form igual ao do WhatsApp.** Copiar a semântica do "comunidade do mandato" arrasta canal e consentimento errados. **Corte:** consentimento novo; fluxos permanecem irmãos, não gêmeos. +- **Double opt-in / verificação de e-mail.** Vira infraestrutura de e-mail marketing. **Corte:** confirmação in-place sem envio de e-mail; gatilho futuro se marketing pedir. + +## Questões em aberto (produto) + +- **Texto(s) do consentimento?** Novo texto cobrindo os dois níveis — "fazer parte do time" (comunicações frequentes + grupos de WhatsApp + participação nas ações) e "esporádico" (comunicação direta, sem grupos/sem frequência) — com chave estável nova, resolvida fail-closed. **Recomendação:** o jurídico aprova o(s) texto(s) e o admin cria o(s) documento(s) antes da ativação — o app não captura sem ele(s); a chave exata e a estrutura (um vs. dois textos) ficam para a implementação junto do jurídico. _(decisões de campos/local/toggle/Lead já confirmadas com produto em 2026-08-19; só o texto jurídico fica pendente de aprovação)_ + +## Referências + +- Rascunho UI (gate): `docs/plans/captura-novidades-home-campanha-ui-draft.html` + PNGs embutidos acima +- `src/app/(frontend)/actions/submitWhatsapp.ts` — padrão de escrita transacional com consent fail-closed +- `src/components/WhatsappForm.tsx`, `src/lib/schemas/whatsapp-form.ts` — padrão de form cliente e schema +- `src/lib/campaignConsentKeys.ts` — keys estáveis existentes +- `src/components/CampaignHero.tsx` — CTA "Conhecer bandeiras" (linha ~104–114) a substituir +- `AGENTS.md` — `Contact` como pessoa normalizada + joins, consentimento por chave estável, transações multi-collection, bloqueio jurídico de `supporter` (Onda 0) diff --git a/docs/plans/pixel-meta-pagina-campanha.md b/docs/plans/pixel-meta-pagina-campanha.md new file mode 100644 index 00000000..3f970d15 --- /dev/null +++ b/docs/plans/pixel-meta-pagina-campanha.md @@ -0,0 +1,76 @@ +# Pixel do Meta (Facebook) na página pública de campanha + +Status: registrado +Atualizado em: 2026-08-19 +Issue: #94 +Priority: P1 +Model: composer-2.5 +Impeccable: A — N/A (campo único no admin replicando precedente aceito + script invisível ao visitante; zero superfície pública nova) +Rascunho UI: N/A — sem UI +Appetite: ~0,5–1 dia eng; campo admin + injeção do base code na home + `Lead` no sucesso do form de captura +Responsável: — + +## Intenção + +O time de campanha paga roda tráfego no Meta direcionado à home pública de campanha, mas hoje não vê nada: sem Pixel, o Events Manager não registra visitas nem conversões dessa página, e as campanhas otimizam às cegas (sem retargeting, sem evento de conversão). Nos abaixo-assinados o time já configura o ID do Pixel por documento (`petition.tracking.facebookPixelId`, entregue 2026-07-21); a home de campanha precisa do mesmo instrumento em nível de site — um campo no admin para o ID numérico do Dataset, injetado na página pública de campanha, e o evento `Lead` quando a captura de novidades (S9) for concluída. Em janela eleitoral (1º turno 04/10), cada semana sem atribuição é verba paga sem feedback. + +Este item revisita a decisão adiada do plano `pixel-meta-abaixo-assinado` ("Pixel / Dataset global em SiteSettings — revisitar quando o mesmo ID for colado em ≥3 petições e o admin reclamar de repetição"): produto agora pede explicitamente um Pixel de site para a página de campanha — o gatilho foi puxado, o precedente segue intacto para os abaixo-assinados. + +## Persona e fluxo + +- **Persona / contexto:** marketing digital / analista de campanha paga no Meta, no admin; quer ver visitas e conversões da home de campanha no Events Manager do Dataset. +- **Job principal:** configurar um Pixel no admin (colar só o ID numérico) e ver a home de campanha alimentar o Dataset sem colar JavaScript. +- **Fluxo desejado:** + 1. Editor no `/admin` cola o ID numérico do Pixel no campo de rastreamento das configurações do site. + 2. A home pública de campanha carrega o base code do Meta (`PageView`) quando o ID está presente e válido. + 3. Quando o visitante conclui a captura de novidades (S9), exatamente um `Lead` é disparado. + 4. O Events Manager mostra PageViews e Leads da página de campanha para otimização/retargeting. +- **Anti-goals de produto:** colar snippet HTML no admin (XSS); Pixel vazando PageViews do resto do site (notícias, etc.); CAPI/Advanced Matching nesta fatia; cookie banner/CMP nesta fatia. + +## Objetivo e aceite + +- Campo opcional "ID do Pixel do Meta" nas configurações do site (admin), com a mesma validação dos abaixo-assinados (somente ID numérico, 5–20 dígitos, sem HTML) e a mesma normalização no save. +- Quando o ID está configurado e válido, a home pública de campanha carrega o base code + `PageView`; sem ID, nada é carregado (fail-closed, zero impacto). +- Quando a captura de novidades (S9) é concluída com Pixel ativo, dispara exatamente um `Lead` (nunca no page load) — mesmo padrão da petição. +- Guardrails: nunca persistir nem executar HTML/JS do editor; sem env var obrigatória; cache da home revalidado pela edição do admin (o hook de revalidação do global já existe). + +## Dados (intenção) + +- **Vou apresentar dados?** Não — o dado (PageView/Lead) vive no Meta Events Manager; o app só emite o evento. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** global `src/globals/SiteSettings.ts` (campo único, provável dono — o plano anterior já apontava `SiteSettings` para um Pixel global; a revalidação `revalidateGlobal('site-settings')` já existe), home `src/app/(frontend)/(home)/page.tsx` (injeção do ``), `src/components/MetaPixel.tsx` + `src/lib/facebookPixel.ts` (reuso direto do precedente), form de captura do S9 (disparo do `Lead` no sucesso, no padrão `PetitionForm`). +- **Precedente a olhar:** `docs/plans/pixel-meta-abaixo-assinado.md` (decisões travadas: persistir só o ID, nunca snippet; `Lead` só no sucesso; validação e normalização reutilizadas), `src/collections/Petition.ts` (grupo `tracking` + `validate` + `beforeChange`), `src/app/(frontend)/abaixo-assinado/[id]/page.tsx` (injeção condicional). +- **Risco de acoplamento:** toca a home (mesmo arquivo do S9) e o `SiteSettings` (global lido por outras superfícies do site) — serializado por dependência; a leitura do global na home deve ser compatível com o caching da página (decisão de implementação, não de produto). + +## Dependências + +- **S9** (captura de novidades) — necessária só para o `Lead` no sucesso do form; o `PageView` na home é independente. Registrada como dependência para o `Lead` sair junto. + +## Fora de escopo + +- Pixel nas demais páginas do site (notícias, artigos, privacidade) — o pedido é a página pública de campanha; se marketing quiser site-wide, item futuro. +- Conversions API (CAPI) / Advanced Matching / dedup `eventID` — herda os gatilhos do plano da petição. +- Cookie banner/CMP, GTM, TikTok/Google pixels. +- Uniqueness de Dataset — mesmo ID pode aparecer na petição e na home. + +## Rabbit holes de produto + +- **Colar snippet HTML "como o Meta manda".** XSS + HTML no CMS. **Corte:** campo só ID + description + validação server-side, idêntico ao precedente aceito. +- **Pixel em tudo.** "Já que o campo existe, coloca em todas as páginas". **Corte:** só a home de campanha; site-wide é decisão separada de produto. +- **CAPI "já que estamos no servidor".** Secrets, hashing PII, dedup, retries. **Corte:** fora, herda os gatilhos do plano da petição. + +## Questões em aberto (produto) + +- **Evento `Lead` no sucesso do form de captura?** Confirmado com produto em 2026-08-19: **sim** — PageView no load + `Lead` no sucesso da captura (S9), réplica do precedente da petição. +- **Onde o campo mora?** **Opções:** (a) global `SiteSettings` (Configurações do site) | (b) novo global dedicado de rastreamento. **Recomendação:** (a) — dono existente com revalidação pronta; o plano da petição já o apontava como destino natural de um Pixel global. _(assumido — validar com produto)_ + +## Referências + +- `docs/plans/pixel-meta-abaixo-assinado.md` — precedente integral (decisões, gatilhos, rejeitados) +- `src/collections/Petition.ts` — campo `tracking.facebookPixelId` + validação/normalização +- `src/components/MetaPixel.tsx` + `src/lib/facebookPixel.ts` — base code e `trackMetaLead` (reuso) +- `src/globals/SiteSettings.ts` — global provável dono do campo +- `src/app/(frontend)/(home)/page.tsx` — home pública de campanha +- S9 — form de captura de novidades (dependência do `Lead`)