Secção 01 · Abertura

O checklist técnico do site da ESPRODOURO

Oito ajustes que destravam toda a operação digital — do Cloudflare ao redirect 301, do consentimento RGPD ao botão de WhatsApp em mobile. Endereçado ao Luís Teixeira e ao Tiago, com a stack actual da escola identificada e os três IDs (GTM, GA4, Pixel) já entregues.

A apresentação principal lista os itens 6.1 a 6.8 como tarefas do Luís Teixeira; quase todos têm a indicação "depende de 4.1". Este documento é o 4.1: o detalhe técnico de cada um, na ordem certa (sequência de processo, não de numeração), com os links das documentações oficiais e a orientação específica para a stack WordPress + Blocksy + Elementor que o site já corre.

Os três IDs já criados pela Sawi

Os três IDs abaixo já existem nas contas da ESPRODOURO. Não há nada a criar — só a colar nos sítios certos seguindo as secções deste documento.

Google Tag Manager
GTM-GC77GFQN
Google Analytics 4
G-C3CFC1JXJN
Meta Pixel
2074168610111539
Ajustes mapeados
8
Itens 6.1 a 6.8 da apresentação principal
Stack identificada
WP
Blocksy · Elementor · Fluent Forms · Slim SEO · Complianz · Hummingbird
Redirects 301 prontos
18
14 cursos + 4 LPs · mapa pronto a colar
IDs já criados
3
GTM · GA4 · Pixel Meta

A stack confirmada no site

Identificada por inspecção mecânica do esprodouro.com em 18-19/mai/2026 (Fases 0, 1, 2 do SCAN). Todos os passos abaixo já contam com esta configuração — não há instalação base nova a fazer.

CMS
WordPress · tema Blocksy + page builder Elementor
Formulários
Fluent Forms (form 10 — captação, 22 campos; form 11 — contacto, 4 campos)
Privacidade
Complianz GDPR instalado (banner publicado, hoje sem trackers a gerir)
SEO
Slim SEO (gere meta tags, canonical, JSON-LD base; suporta Custom Schema)
Cache · CDN
Hummingbird (page cache no disco) · sem CDN hoje (DNS resolve directamente para a PTISP)
Tradução
TranslatePress (pt-PT + en-GB)
Plugins extra
Popup Maker · WP Job Openings · miniOrange OTP · SupportCandy

Sobre as sugestões de plugin neste documento

Sempre que o documento sugerir um plugin específico, é uma sugestão — escolhida pelo critério de popularidade, manutenção activa e gratuidade. Qualquer outro plugin que cumpra a mesma função serve igualmente. Se o Luís Teixeira já estiver familiarizado com uma alternativa, pode usá-la sem problema. O resultado final é o que importa: GTM no <head>/<body>, redirect 301 a devolver o código 301, checkbox RGPD obrigatório no formulário, etc.

Princípio que vale para tudo

Single source of truth: o GTM. Nenhum tracker (GA4, Pixel, Ads) vai colado directamente ao tema ou via plugin "ligar o Google ao site". Tudo passa pelo GTM. Isto evita duplicação de eventos, simplifica auditoria e permite ligar/desligar trackers sem tocar no código.

Secção 02 · Sequência

A ordem certa de executar

A ordem aqui é a do processo, não a da numeração. Cloudflare vem primeiro porque a propagação de DNS pode demorar 24h — começar agora destrava tudo o resto.

  1. Cloudflare antes de tudo (item 6.3). A activação altera os nameservers do domínio na PTISP e leva até 24h a propagar. Começar agora não corta tráfego e destrava todos os ajustes seguintes — em especial o Turnstile (6.7) que vive na conta Cloudflare.
  2. GTM instalado no site (item 6.1). É o canal pelo qual todos os trackers vão entrar. Sem GTM, nada do que vem a seguir tem onde encaixar. O ID já existe: GTM-GC77GFQN.
  3. Pixel e GA4 dentro do GTM (item 6.2). A Sawi configura as tags assim que o GTM estiver no ar; o Luís Teixeira só precisa garantir o acesso ao container. IDs já criados.
  4. Redirects 301 (item 6.5). Mapa pronto neste documento. Plugin sugerido: Redirection, ~30 min para colar todos.
  5. Consentimento RGPD e tipos de campo no formulário (item 6.6). Edição no Fluent Forms + Complianz. Bloqueante jurídico antes da próxima feira.
  6. Campos ocultos de atribuição (item 6.6+, ligado ao 6.6). Sem capturar UTM, fbclid, gclid, fbp e fbc no formulário, não há conversão offline (Google Ads e Meta CAPI) — o investimento em mídia não consegue aprender com os leads que viram alunos.
  7. Turnstile + OG image + Schema Markup (item 6.7). Três tarefas pequenas que sobem qualidade de SEO e segurança.
  8. Botão WhatsApp em mobile (item 6.8). Número confirmado: +351 939409349. A integração com o Tintim fica em aberto, dependente da criação da conta WABA — a Sawi conduz e orienta o detalhe depois.

Quem faz o quê

Luís Teixeira

Operação no WordPress + Cloudflare

Activa o Cloudflare, instala plugins, cola IDs, configura redirects, ajusta o formulário Fluent Forms, activa Turnstile e adiciona Custom Schema no Slim SEO. Ponto focal informático operacional.

Tiago

Site novo + infraestrutura

Acompanha o site novo e a migração de hospedagem (Hetzner CX33, item 4.2). Para este checklist, ajuda na decisão de DNS no Cloudflare e na propagação para o novo servidor quando este entrar no ar.

Sawi

Tags, integrações e validação

Configura as tags GA4 e Pixel dentro do GTM, faz o smoke test de ponta a ponta com o Luís Teixeira, conduz a criação da conta WABA do agente WhatsApp e a integração com o Tintim (item 6.8), e dá luz verde para subir as campanhas (item 2.3 da apresentação principal).

Fernando · Tânia

Decisões de conteúdo e jurídicas

Fernando: cartão de crédito nas contas, aprovação final, eSIM do WhatsApp. Tânia: texto do consentimento RGPD para o checkbox do formulário (item 10.2 da apresentação principal).

Onde não trabalhar

Nenhum dos passos abaixo deve ser feito directamente no functions.php do tema activo (Blocksy) — quando o tema for actualizado, o ajuste perde-se. Quando este documento pedir snippet, sugerimos o plugin Code Snippets (ou equivalente) ou um child theme. A excepção é o GTM, que entra via plugin (sem necessidade de tocar em código).

Item 6.3 · Cloudflare

Activar o Cloudflare grátisComeçar primeiro

O Cloudflare entra na frente do site como CDN + camada de protecção. O plano gratuito chega — não há funcionalidade premium necessária para o que a ESPRODOURO precisa hoje.

O site hoje resolve directamente o IP da hospedagem PTISP — visitante de qualquer geografia paga o caminho completo até Portugal. Pôr Cloudflare na frente acelera a entrega via cache geográfico, mitiga ataques de bots e abre a porta para o Turnstile (anti-spam do formulário, item 6.7) e para regras de cache mais finas no futuro.

Pré-requisitos

Acesso ao registar do domínio
Conta na PTISP (ou nos contactos administrativos do domínio) para alterar os nameservers de ns1.ptisp.pt / ns2.ptisp.pt para os nameservers que a Cloudflare atribuir.
Conta Cloudflare
Pode ser criada com o e-mail institucional da ESPRODOURO. Fernando ou Luís Teixeira como administrador.
Tempo
~30 min de configuração + até 24h para a propagação de DNS estabilizar.

Como activar

  1. Criar conta em cloudflare.com/sign-up (com o e-mail institucional).
  2. Em "Add a site", colar esprodouro.com. Escolher o plano Free.
  3. A Cloudflare faz uma varredura automática dos registos DNS actuais (A, AAAA, MX, CNAME, TXT). Conferir que tudo o que está hoje na PTISP foi capturado — em especial registos de e-mail (MX, SPF/TXT, DKIM) para não cortar o e-mail da escola.
  4. Para cada registo, deixar a "nuvem" laranja activa apenas nos registos do site (esprodouro.com e www). Registos de e-mail (MX e correlatos) ficam com a nuvem cinza (DNS-only) — Cloudflare não faz proxy de e-mail no plano Free.
  5. No fim, a Cloudflare entrega dois nameservers (ex.: nina.ns.cloudflare.com, walt.ns.cloudflare.com). Trocar na PTISP para esses dois nameservers. Salvar.
  6. Aguardar o e-mail de confirmação ("Active"). Pode levar até 24h, mas costuma vir em 1-2h.
  7. No painel Cloudflare → SSL/TLS, configurar para Full (strict). O site já serve por HTTPS na PTISP, este modo evita warnings.
  8. No painel Cloudflare → Rules → Page Rules: não criar regras especiais agora. O default Free é suficiente para o site institucional.

Cuidado com o cache de HTML

Header cache-control: no-store a corrigir antes do go-live da CDN

A auditoria de SEO/performance detectou que o site responde com cache-control: no-store, no-cache, must-revalidate em todas as páginas HTML. Isso é gerado pelo WordPress por causa do PHPSESSID que algum plugin força a emitir (suspeitos: SupportCandy, miniOrange OTP, WP Job Openings). Com Cloudflare na frente, este header bloqueia qualquer cache de HTML — a CDN serve sempre da origem.

Quando o Cloudflare entrar, identificar qual plugin força o PHPSESSID (desactivar um de cada vez em homologação, verificar headers com curl -sI https://esprodouro.com/) e ajustar. Como alternativa imediata, na regra de cache da Cloudflare permitir cache de HTML para visitantes anónimos (ignorar o cookie PHPSESSID em URLs públicas).

Coexistência com o Hummingbird

O Hummingbird já corre como page cache no disco do WordPress — não é necessário desligar. As duas camadas funcionam bem juntas: Hummingbird gera o HTML rápido na origem, Cloudflare entrega-o ao visitante a partir do nó geográfico mais próximo. Em Hummingbird → Caching → Page Caching → Settings, confirmar que o "Cache HTTP headers" não está a re-emitir no-store.

Item 6.1 · Google Tag Manager

Instalar o GTM no sitedestrava 6.2 e 2.3

O Google Tag Manager (GTM) é um único conjunto de snippets que se cola no site uma vez e passa a permitir gerir todas as outras tags (GA4, Pixel, Ads, conversões) sem voltar a tocar no código. O container já existe — só falta colar.

ID do container GTM (já criado pela Sawi)
GTM-GC77GFQN

Os dois snippets que vão no site

O Google exige duas peças: a primeira no <head> (o mais alto possível), a segunda imediatamente depois da abertura do <body>. Cópia exacta, pronta a colar:

Snippet do <head>

<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-GC77GFQN');</script>
<!-- End Google Tag Manager -->

Snippet do <body> (imediatamente após a abertura)

<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-GC77GFQN"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->

Como colar no WordPress (caminho sugerido)

Editar directamente o tema activo (Blocksy) é desaconselhável porque o ajuste se perde no próximo update. O caminho mais limpo é via plugin. Sugestão: "GTM4WP — Google Tag Manager for WordPress" (Thomas Geiger / DuracellTomi), ~700k instalações. Qualquer outro plugin que injecte HTML no wp_head e wp_body_open serve igualmente — fica à escolha do Luís Teixeira.

  1. Em Plugins → Adicionar novo, procurar o plugin (a sugestão é "GTM4WP", mas outros equivalentes servem). Instalar e activar.
  2. Em Definições → Google Tag Manager, colar o ID GTM-GC77GFQN no campo "Google Tag Manager ID". Guardar.
  3. Em Container code, escolher a opção Codeless injection (default). O plugin trata do <head> e do <body> sozinho.
  4. Confirmar que está activo: abrir https://esprodouro.com/ em janela anónima, abrir o DevTools → Network, filtrar por gtm.js. Deve aparecer um pedido a www.googletagmanager.com/gtm.js?id=GTM-GC77GFQN com status 200.
  5. Avisar a Sawi. A partir daqui a configuração das tags acontece dentro do GTM, não no site.

Caminho alternativo · Code Snippets ou child theme

Se o Luís Teixeira preferir não instalar plugin específico de GTM, pode colar os dois snippets via Code Snippets (plugin genérico) ou directamente num child theme do Blocksy. Os hooks WordPress certos são wp_head (prioridade alta) para o snippet do <head> e wp_body_open para o snippet do <body>. A maior parte dos temas modernos (incluindo o Blocksy) dispara wp_body_open automaticamente.

// Snippet PHP — para colar via Code Snippets ou child theme
add_action('wp_head', function () { ?>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-GC77GFQN');</script>
<!-- End Google Tag Manager -->
<?php }, 1);

add_action('wp_body_open', function () { ?>
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-GC77GFQN"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
<?php }, 1);

O que não fazer

Não usar plugins que pedem para "ligar o GA4 ao WordPress" em modo de instalação automática (como o oficial "Site Kit by Google"), porque colam o GA4 fora do GTM e geram contagem duplicada assim que o GTM também o servir. Site Kit pode ficar instalado apenas para o Search Console e PageSpeed, mas o tracking de marketing fica todo via GTM.

Item 6.2 · Pixel e GA4

Pixel Meta e GA4 — via GTM, não no sitedepende de 6.1

O Pixel Meta e o GA4 nunca entram no código do site directamente. Os dois são configurados como tags dentro do GTM. Os dois IDs já existem nas contas da ESPRODOURO. O Luís Teixeira só precisa garantir que o GTM (item 6.1) esteja activo — a Sawi faz o resto.

Google Analytics 4 (já criado pela Sawi)
G-C3CFC1JXJN
Meta Pixel (já criado pela Sawi)
2074168610111539

Quem faz o quê

Sawi: configura as tags GA4 (Configuration + generate_lead + form_start + whatsapp_click + phone_click) e as tags Meta Pixel (PageView + Lead + Contact) dentro do GTM com os IDs acima, publica o container e corre o teste de ponta a ponta com o Luís Teixeira.

Luís Teixeira: confirma que o GTM continua activo no site, dá acesso de editor/admin ao container GTM à Sawi, e verifica que o banner Complianz aceita os trackers (passa a renderizar logo que tenha algo a gerir).

Por que não colar GA4 ou Pixel directamente no site

O Pixel da Meta é instalado a partir do Events Manager da Meta com um snippet JS para o <head>. O GA4 é instalado a partir da interface do Google Analytics com outro snippet JS. Não fazer isto. A partir do momento em que o GTM dispara as tags GA4 e Pixel correspondentes, ter os dois snippets também colados no site significa que cada page_view e cada Lead é contado em duplicado — relatórios sobem 2× sem causa real, o que invalida qualquer leitura.

Eventos que vão ser disparados

EventoQuando disparaOnde aparecePrioridade
page_view Em todas as páginas (built-in do GA4) GA4 · Meta Pixel (PageView) Secundário
form_start Primeiro focusin num campo de qualquer Fluent Form GA4 Secundário (engagement)
generate_lead Submissão bem-sucedida de fluentform_10 ou fluentform_11 (com parâmetro form_id) GA4 (generate_lead) · Meta Pixel (Lead) · Google Ads (importado do GA4) Primário · única conversão a optimizar
whatsapp_click Clique em a[href*="wa.me"] ou a[href*="api.whatsapp.com"] GA4 · Meta Pixel (Contact) Secundário (sinal de funil, não optimização)
phone_click Clique em a[href^="tel:"] GA4 Secundário

Consent Mode v2 — a integração com o Complianz

Por estar a operar no mercado europeu, GA4 e Pixel precisam respeitar o consentimento do utilizador (RGPD + ePrivacy). O Complianz já está instalado no site e tem integração nativa com o GTM via Consent Mode v2. Quando o utilizador rejeita os cookies, o GA4 envia eventos sem identificadores (modelagem do Google preenche a lacuna parcialmente) e o Pixel não dispara. A Sawi configura os defaults no GTM.

Bug do banner Complianz a verificar em homologação

Hoje, sem trackers a gerir, o Complianz carrega apenas o CSS cookieblocker.min.css e não renderiza o banner. Quando GA4 e Pixel entrarem via GTM, o banner deve passar a aparecer automaticamente — mas é um comportamento a confirmar no dia, não a presumir.

Item 6.5 · Redirects 301

18 redirects 301 — mapa completo

Hoje convivem no site três gerações de páginas de curso e cinco LPs de captação, todas indexáveis. O Google escolhe a "errada" e o sinal de SEO de cada curso fica fragmentado em até três URLs. Resolver isto leva ~30 min.

Plugin sugerido

Sugestão: "Redirection" (John Godley, ~2M instalações), padrão da indústria para 301 em WordPress. Gratuito, com importação/exportação CSV, regex e log de hits. Qualquer outro plugin que faça redirect 301 no template_redirect do WordPress serve igualmente — fica à escolha do Luís Teixeira.

  1. Em Plugins → Adicionar novo, procurar o plugin escolhido (sugestão: "Redirection"). Instalar e activar.
  2. O setup wizard pergunta se quer monitorizar mudanças de permalink — responder "sim" (evita 404 quando o slug de um curso mudar no futuro).
  3. Em Ferramentas → Redirection → Redirects, colar os 18 redirects (mapa abaixo). Tipo: URL only, código 301 - Moved Permanently.
  4. Confirmar que cada redirect funciona: abrir cada URL "de" em janela anónima, deve cair na URL "para".
  5. Replicar os 14 redirects do bloco 1 também para as URLs /en/ (versão inglesa via TranslatePress).

Bloco 1 · 14 redirects das páginas de curso~20 min

Geração 2024 (slugs /curso-profissional-de-tecnico-*) e geração mai/2025 (slugs /tecnico-a-*) para a geração mai/2026 (slugs /tecnico-*).

301 from301 to
/curso-profissional-de-tecnico-de-cozinha-pastelaria//tecnico-cozinha/
/curso-profissional-de-tecnico-vitivinicola//tecnico-enologia/
/curso-profissional-de-tecnico-de-comunicacao-e-servico-digital//tecnico-comunicacao/
/curso-profissional-de-tecnico-de-gestao//tecnico-gestao/
/curso-profissional-de-tecnico-de-eletronica-automacao-e-comando//tecnico-eletronica/
/curso-profissional-de-tecnico-de-turismo//tecnico-turismo/
/curso-profissional-de-tecnico-auxiliar-de-saude//cursos/ (curso fora da oferta actual)
/tecnico-a-de-cozinha-pastelaria//tecnico-cozinha/
/tecnico-a-vitivinicola//tecnico-enologia/
/tecnico-a-de-comunicacao-e-servico-digital//tecnico-comunicacao/
/tecnico-a-de-gestao//tecnico-gestao/
/tecnico-a-de-eletronica-automacao-e-comando//tecnico-eletronica/
/tecnico-a-de-vendas-e-marketing//tecnico-comunicacao/ (decisão Fernando — ver nota)
/tecnico-a-auxiliar-de-saude//cursos/ (fora da oferta actual)

Decisão pendente do Fernando

O slug /tecnico-a-de-vendas-e-marketing/ foi descontinuado ou fundido com o curso de Comunicação? Default sugerido: 301 para /tecnico-comunicacao/. Se "Vendas e Marketing" foi mesmo descontinuado e a oferta actual não tem equivalente, redireccionar para /cursos/.

Bloco 2 · 4 redirects das LPs antigas~5 min

Hoje convivem cinco LPs de captação. A LP nova (/formulario/) leva o tráfego das feiras mas tem uma fracção da autoridade que teria se as outras quatro lhe entregassem o sinal.

301 from301 to
/registar-inscricao//formulario/
/inscreve-te-aqui//formulario/
/esprodouro-escola-profissional-do-alto-douro/inscricao-de-alunos//formulario/
/esprodouro-escola-profissional-do-alto-douro/inscricao-de-formadores//inscricao-de-formadores/ (criar página dedicada, público distinto)

Depois de aplicar os redirects

Item 6.6 · Formulário · RGPD e tipos de campo

Consentimento RGPD e tipos de input no Fluent Formsbloqueante jurídico

A LP /formulario/ capta leads em parte menores de idade (14-17) e hoje não tem consentimento RGPD funcional, não tem o tipo de campo certo para telefone nem para data de nascimento, e não tem autocomplete em nenhum campo visível. Tudo isto resolve-se no editor do Fluent Forms.

O texto do consentimento

O texto do checkbox de consentimento é responsabilidade da Tânia (item 10.2 da apresentação principal). Modelo sugerido enquanto não chega o texto final:

"Autorizo a ESPRODOURO a tratar os dados acima para resposta ao meu pedido de informação e contacto subsequente sobre o processo de matrícula. Conheço a Política de Privacidade e sei que posso retirar este consentimento a qualquer momento por e-mail para geral@esprodouro.pt."

Para os candidatos menores de 16 anos (RGPD art. 8), incluir um segundo checkbox declarativo do encarregado de educação. O texto também fica com a Tânia.

Como ajustar o fluentform_10 (form de captação)

  1. Em Fluent Forms → Todos os formulários, abrir o form com 22 campos (ID 10).
  2. Adicionar checkbox RGPD obrigatório. Arrastar GDPR Agreement da paleta para o final do formulário, acima do botão "Submeter". Colar o texto da Tânia. Marcar Required.
  3. Segundo checkbox declarativo do encarregado. Arrastar Single Checkbox. Texto: declaração do encarregado para menores de 16. Marcar Required.
  4. Trocar o tipo do campo Telefone. Apagar o campo "Telefone" actual (que está como type="number") e arrastar Phone / Number da paleta. Em Field Settings, escolher input mask phone (gera type="tel"). Adicionar atributo autocomplete="tel" em Advanced Options → HTML Attributes.
  5. Trocar o tipo do campo Data de Nascimento. Apagar o campo actual (type="text") e arrastar Date / Time. Em Field Settings, escolher formato YYYY-MM-DD. Adicionar atributo autocomplete="bday".
  6. Adicionar autocomplete aos restantes campos visíveis. Em cada campo, em Advanced Options → HTML Attributes, adicionar:
    • Nome → autocomplete="name"
    • Email → autocomplete="email"
    • Local de Residência → autocomplete="address-level2"
  7. Renomear o campo legado. O campo "Pretendes estudar connosco em 2026/2027?" tem hoje nome técnico pretendes_estudar_connosco_em_2020. Renomear para pretendes_estudar_connosco (sem ano hardcoded). Verificar integrações Zapier/Make que dependam do nome antigo (provavelmente nenhuma).
  8. Guardar. Submeter um teste com e-mail interno; confirmar que a submissão entra na base e que o Complianz aceita o consentimento.

O mesmo ajuste no fluentform_11 (form de rodapé)

O que isto resolve

Risco jurídico

RGPD funcional

Consentimento expresso + checkbox do encarregado para menores. A base de dados de leads passa a estar em conformidade. Cross-ref: art. 6 e art. 8 do RGPD.

Mobile UX

Teclado certo, preenchimento automático

type="tel" abre teclado numérico com +; type="date" abre date picker nativo; autocomplete oferece o nome/email guardados no telemóvel.

Acessibilidade

WCAG 1.3.5 (AA)

Identify Input Purpose — exigência AA explícita para campos de identificação pessoal.

Item 6.6+ · Atribuição offline

Capturar UTM, fbclid, gclid, fbp e fbc no formuláriodestrava conversão offline

Sem estes cinco campos ocultos no formulário, quando um lead vira aluno matriculado o Google Ads e o Meta não conseguem ligar essa matrícula ao anúncio que originou o clique. As campanhas continuam a optimizar para o lead, não para o aluno — e a escola fica sem capacidade de pedir mais matrículas em vez de mais leads.

Os cinco identificadores são injectados como hidden fields no Fluent Forms antes da submissão. Ao chegarem ao webhook (Make/Zapier para o CRM), viajam com o lead até à matrícula. Quando o aluno confirma matrícula, o webhook devolve a conversão ao Google Ads (Enhanced Conversions for Leads) e à Meta (CAPI offline).

Os cinco parâmetros e onde vivem

ParâmetroOrigemComo capturar no form
utm_source · utm_medium · utm_campaign · utm_content · utm_term URL · todos os anúncios e links partilhados levam UTMs (regra interna da Sawi) Smart Code do Fluent Forms · directo da query string
gclid URL · Google Ads adiciona automaticamente ao redirect (auto-tagging ON) Smart Code do Fluent Forms · directo da query string
fbclid URL · Meta adiciona automaticamente ao link do anúncio Smart Code do Fluent Forms · directo da query string
_fbp (Pixel Browser ID) Cookie · gerado pelo Pixel Meta no document.cookie do visitante Pequeno snippet JS que lê o cookie e preenche o hidden field
_fbc (Pixel Click ID) Cookie · gerado pelo Pixel Meta com base no fbclid da URL de entrada Pequeno snippet JS que lê o cookie e preenche o hidden field

Passo 1 — UTM, gclid e fbclid (via Fluent Forms nativo)

O Fluent Forms tem suporte nativo para preencher hidden fields a partir da URL via Smart Codes (também chamados Dynamic Default Values). É funcionalidade do plugin gratuito — não é preciso versão Pro.

  1. No editor do fluentform_10 (e do fluentform_11), arrastar Hidden Field da paleta. Repetir 7 vezes (uma por parâmetro).
  2. Para cada hidden field, preencher os dois campos:
    • Field Name: o nome do parâmetro (ex.: utm_source)
    • Default Value: o Smart Code correspondente (ver tabela abaixo)
  3. Os Smart Codes do Fluent Forms para query string são {get.NOME_DO_PARAMETRO} — entram entre chavetas no campo Default Value.
Field NameDefault Value (Smart Code)
utm_source{get.utm_source}
utm_medium{get.utm_medium}
utm_campaign{get.utm_campaign}
utm_content{get.utm_content}
utm_term{get.utm_term}
gclid{get.gclid}
fbclid{get.fbclid}

Limitação importante

O Smart Code {get.utm_source} só funciona se o utilizador chegou ao formulário com a UTM ainda no URL. Se ele entrou na home com UTM, navegou para outras páginas e só depois caiu no formulário, a UTM já se perdeu. A solução é guardar as UTMs num cookie no primeiro hit e ler do cookie no submit. Ver passo 3 abaixo.

Passo 2 — _fbp e _fbc (via snippet JS)

O _fbp e o _fbc são cookies que o Pixel Meta cria no browser do visitante. Não chegam pela URL. Para os capturar, basta um snippet JS pequeno colado no wp_footer (via plugin Code Snippets, child theme, ou através do GTM como Custom HTML tag).

No editor do Fluent Forms, criar dois hidden fields adicionais com Field Name fbp e fbc (deixar Default Value vazio — vai ser preenchido por JS).

Cola o snippet abaixo via Code Snippets, child theme ou Custom HTML tag no GTM (recomendado para manter consistência com o princípio "tudo via GTM"):

<script>
(function () {
  function getCookie(name) {
    var match = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)');
    return match ? match.pop() : '';
  }

  function fillHidden(formSelector) {
    document.querySelectorAll(formSelector).forEach(function (form) {
      var fbp = form.querySelector('input[name="fbp"]');
      var fbc = form.querySelector('input[name="fbc"]');
      if (fbp) fbp.value = getCookie('_fbp');
      if (fbc) fbc.value = getCookie('_fbc');
    });
  }

  // Preenche ao carregar a página
  document.addEventListener('DOMContentLoaded', function () {
    fillHidden('.frm-fluent-form');
  });

  // Repete antes de cada submit (cookies podem ter mudado durante a sessão)
  document.addEventListener('submit', function (e) {
    if (e.target && e.target.matches('.frm-fluent-form')) {
      fillHidden('.frm-fluent-form');
    }
  }, true);
})();
</script>

Passo 3 — Persistir UTMs num cookie (recomendado)

Para garantir que a UTM da primeira visita sobrevive à navegação até o utilizador chegar ao formulário, cola este segundo snippet no mesmo sítio do anterior. Guarda as UTMs num cookie por 90 dias e preenche os hidden fields a partir do cookie quando o formulário é submetido.

<script>
(function () {
  var UTM_KEYS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term', 'gclid', 'fbclid'];
  var COOKIE_NAME = 'esp_attr';
  var COOKIE_DAYS = 90;

  function setCookie(name, value, days) {
    var expires = new Date();
    expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000);
    document.cookie = name + '=' + encodeURIComponent(value) + ';expires=' + expires.toUTCString() + ';path=/;SameSite=Lax';
  }

  function getCookie(name) {
    var match = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)');
    return match ? decodeURIComponent(match.pop()) : '';
  }

  // 1. Ler UTMs do URL actual; se houver alguma, gravar no cookie
  var params = new URLSearchParams(window.location.search);
  var fromUrl = {};
  var hasAny = false;
  UTM_KEYS.forEach(function (key) {
    var v = params.get(key);
    if (v) { fromUrl[key] = v; hasAny = true; }
  });
  if (hasAny) {
    setCookie(COOKIE_NAME, JSON.stringify(fromUrl), COOKIE_DAYS);
  }

  // 2. Ao carregar a página, preencher hidden fields do Fluent Forms
  function fillFromCookie() {
    var raw = getCookie(COOKIE_NAME);
    if (!raw) return;
    var data;
    try { data = JSON.parse(raw); } catch (e) { return; }
    document.querySelectorAll('.frm-fluent-form').forEach(function (form) {
      UTM_KEYS.forEach(function (key) {
        var input = form.querySelector('input[name="' + key + '"]');
        if (input && !input.value && data[key]) input.value = data[key];
      });
    });
  }
  document.addEventListener('DOMContentLoaded', fillFromCookie);
  document.addEventListener('submit', function (e) {
    if (e.target && e.target.matches('.frm-fluent-form')) fillFromCookie();
  }, true);
})();
</script>

Como confirmar que está a funcionar

  1. Abrir uma URL de teste com UTMs: https://esprodouro.com/formulario/?utm_source=teste&utm_medium=manual&utm_campaign=teste-atribuicao.
  2. Abrir DevTools → Application → Cookies. Procurar esp_attr; deve conter as três UTMs em JSON.
  3. Navegar para outra página do site, voltar ao formulário. As UTMs continuam disponíveis (ainda no cookie).
  4. Submeter o formulário com e-mail de teste. Em Fluent Forms → Entries → All Submissions, abrir a submissão e confirmar que utm_source=teste, utm_medium=manual, utm_campaign=teste-atribuicao aparecem como campos preenchidos.
  5. Se o Pixel já estiver activo (via GTM), abrir DevTools → Application → Cookies, procurar _fbp. Submeter o formulário; o campo fbp da submissão deve aparecer com um valor tipo fb.1.1716....

O que isto destrava na operação de mídia

Google Ads · Enhanced Conversions for Leads

Conversão offline ligada ao clique

Quando o lead vira aluno matriculado, o CRM dispara webhook com gclid + email hashed + valor da matrícula. Google Ads atribui a matrícula à campanha/palavra-chave de origem. Sem gclid no form, não há linkagem.

Meta CAPI · Offline Events

Matrícula como evento offline para o Pixel

O CRM envia ao Meta o evento Purchase ou SubmitApplication com fbp, fbc e email hashed. O algoritmo da Meta passa a optimizar campanhas para matrículas reais, não para leads.

O loop fica fechado quando entrar o CRM (Fase 10)

Esta captura é o pré-requisito técnico para o loop de atribuição completo, que se fecha quando a Fase 10 (CRM) entregar a stack. O Luís Teixeira não precisa fazer mais nada aqui — a Sawi cuida da integração webhook → Google Ads + Meta CAPI assim que o CRM existir.

Item 6.7 · Turnstile · OG image · Schema

Três ajustes que sobem qualidade

Três tarefas pequenas, agrupadas porque vivem na mesma área do site: anti-spam no formulário, imagem de preview quando o link é partilhado no WhatsApp, e dados estruturados para Google.

Turnstile · anti-spam invisível no formulário

O Turnstile da Cloudflare substitui o "I'm not a robot" do Google reCAPTCHA. Para o utilizador é invisível na maior parte dos casos (sem puzzles, sem clique). Para o operador, gere-se directamente da conta Cloudflare (já criada na secção 6.3) e não exige conta separada Google.

  1. Dentro do painel Cloudflare, ir a Turnstile → Add site. Domínio: esprodouro.com. Modo: Managed (o mais transparente para o utilizador).
  2. A Cloudflare entrega um Site Key e um Secret Key.
  3. No Fluent Forms, em Fluent Forms → Configurações Globais → reCAPTCHA & hCaptcha & Turnstile, colar o Site Key e o Secret Key.
  4. No editor de cada formulário (10 e 11), arrastar o campo Turnstile para o final, acima do botão "Submeter".
  5. Submeter um teste. Em mobile, o widget aparece como uma caixa pequena "verificado" automaticamente.

OG image · preview de link no WhatsApp

Quando o aluno digitaliza o QR code da feira, abre o link no telemóvel e o partilha no WhatsApp do encarregado de educação, o preview vai cego: sem thumbnail, sem identidade visual, e com título genérico "Landing Page – ESPRODOURO". A causa é a ausência de og:image nas LPs.

O que produzir

Como colar em cada página relevante

O Slim SEO já gere as meta tags Open Graph globalmente, mas hoje não tem imagem default e a LP /formulario/ está sem imagem própria.

  1. Editar a página /formulario/ no WordPress.
  2. No painel lateral do Slim SEO, em Social Open Graph → Image, escolher a imagem 1200×630 da biblioteca de média.
  3. Repetir para as 7 páginas de curso (/tecnico-cozinha/, /tecnico-enologia/, …) com a imagem 1200×630 que corresponda. Se ainda não houver imagens específicas por curso, usar a default da escola.
  4. Em Slim SEO → Definições → Social, colar uma Default OG image (a mesma imagem 1200×630 institucional) — fica como fallback de qualquer página que não tenha imagem própria.
  5. Validar em developers.facebook.com/tools/debug colando a URL /formulario/. Se aparecer ainda a versão antiga em cache, clicar em "Scrape Again".

Caveat sobre a meta description da home

A auditoria de SEO encontrou na meta description da home o texto literal ** (markdown vazado). Esse texto é replicado em og:description e no JSON-LD. Quando a home é partilhada hoje no WhatsApp, o preview mostra Alojamento GRÁTIS ** Visitas de Estudo GRÁTIS. Editar em Páginas → Home → Slim SEO (lateral) → Meta Description, remover os **, guardar. Slim SEO regenera o og:description automaticamente.

Schema Markup · dados estruturados para Google

Hoje o site só serve schemas genéricos (WebSite, Organization, WebPage, BreadcrumbList) — entregues automaticamente pelo Slim SEO. Faltam os schemas específicos do sector educativo, que permitem rich snippets na SERP do Google e melhor desambiguação entre cursos.

Os quatro schemas a adicionar

SchemaOnde aplicarO que destrava
EducationalOrganization Home (/) Rich result específico do sector + entidade reconhecida pelo Google como escola (não como empresa genérica).
Course Cada uma das 7 páginas de curso (/tecnico-cozinha/, etc.) Rich result com nome do curso, duração, fornecedor (ESPRODOURO), idioma. Desambigua entre páginas de curso e listagem.
LocalBusiness + Place Página /contactos/ (e secundariamente home) Aparecer no pacote local do Google para queries com intenção geográfica ("escola profissional Lamego", "escola Vila Real").
FAQPage LP /formulario/ Perguntas frequentes (matrícula, residência, transporte, refeições) — rich result de FAQ na SERP, mais espaço ocupado.

Como entregar no WordPress

O Slim SEO tem suporte nativo a Custom Schema. Em Slim SEO → Definições → Schema, cada template (home, página de curso, LP, contactos) recebe o seu bloco de JSON-LD. Slim SEO injecta no <head> ao lado dos schemas genéricos.

Exemplo de EducationalOrganization para a home (a Sawi pode entregar o JSON pronto, com base no Validador do schema.org):

{
  "@context": "https://schema.org",
  "@type": "EducationalOrganization",
  "name": "ESPRODOURO — Escola Profissional do Alto Douro",
  "url": "https://esprodouro.com/",
  "logo": "https://esprodouro.com/wp-content/uploads/2026/04/logo.svg",
  "image": "https://esprodouro.com/wp-content/uploads/2026/05/og-default.jpg",
  "telephone": "+351 254 481 033",
  "email": "geral@esprodouro.pt",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "Largo do Côrro",
    "addressLocality": "São João da Pesqueira",
    "postalCode": "5130-321",
    "addressCountry": "PT"
  },
  "sameAs": [
    "https://www.facebook.com/esprodouro",
    "https://www.instagram.com/esprodouro"
  ]
}

Validar cada bloco em validator.schema.org e depois em search.google.com/test/rich-results antes de publicar.

Item 6.8 · WhatsApp mobile

Botão WhatsApp visível e clicável em mobile

Hoje não existe nenhum link wa.me/... clicável no site (auditoria mecânica em 19/mai/2026 — zero ocorrências em todas as páginas). O número de telefone aparece apenas no rodapé como tel:. Para o público adolescente, que vive em WhatsApp, falta o atalho mais natural.

Número WhatsApp confirmado
+351 939 409 349

Integração com o Tintim (conduzida pela Sawi)

A integração do agente WhatsApp com o Tintim depende de como a conta WABA (WhatsApp Business API) for criada para este número. Esta peça fica em aberto neste documento — a Sawi conduz a criação da WABA e orienta o detalhe (callback, número verificado, perfis de utilizador) num passo seguinte. O Luís Teixeira não precisa esperar por isso para colocar o botão no site: o botão arranca como link wa.me/... simples, e a integração Tintim acontece por trás sem mexer no link.

Botão flutuante (recomendado)

Botão fixo no canto inferior direito do ecrã, visível em todas as páginas. Cor verde WhatsApp (#25D366), ícone redondo. Em mobile, fica grande o suficiente para ser tocado com o polegar (tap target ≥48×48 px — WCAG 2.5.5).

Como adicionar — via plugin (sugestão)

Há vários plugins gratuitos no repositório WordPress que fazem isto sem código. Sugestões: "Click to Chat – HoliThemes" (~200k instalações) ou "WP WhatsApp Chat" (QuantumCloud, ~100k instalações). Qualquer outro que coloque um link wa.me/... flutuante no rodapé serve igualmente.

  1. Em Plugins → Adicionar novo, procurar o plugin escolhido. Instalar e activar.
  2. Em Click to Chat → Settings (ou equivalente), colar o número 351939409349 (sem o + e sem espaços).
  3. Em Pre-filled Message, colar: "Olá, vi a página da ESPRODOURO e queria saber mais sobre o curso de [Curso]."
  4. Em Display Settings → Position: bottom-right (canto inferior direito).
  5. Em Display Settings → Show on Devices: marcar Desktop + Mobile.
  6. Em Display Settings → Show On: All pages.
  7. Guardar. Abrir esprodouro.com/ em telemóvel; confirmar que o botão aparece, que toca o ecrã sem ambiguidade e que abre a app WhatsApp com a mensagem pré-preenchida.

Como adicionar — sem plugin (alternativa)

Para quem prefere snippet leve, criar via Code Snippets (sugestão de plugin que injecta PHP/CSS no wp_footer sem tocar no tema), ou em child theme:

add_action('wp_footer', function () { ?>
<a href="https://wa.me/351939409349?text=Olá,%20vi%20a%20página%20da%20ESPRODOURO%20e%20queria%20saber%20mais."
   class="wa-float" aria-label="Falar pelo WhatsApp" target="_blank" rel="noopener">
  <svg width="32" height="32" viewBox="0 0 24 24" fill="white" aria-hidden="true">
    <path d="M20.52 3.48A11.93 11.93 0 0 0 12.04 0C5.46 0 .12 5.34.12 11.92c0 2.1.55 4.15 1.59 5.96L0 24l6.27-1.64a11.9 11.9 0 0 0 5.77 1.47h.01c6.58 0 11.92-5.34 11.92-11.92 0-3.19-1.24-6.18-3.45-8.43zM12.04 21.7h-.01a9.8 9.8 0 0 1-5-1.37l-.36-.21-3.72.97.99-3.63-.23-.37a9.74 9.74 0 0 1-1.5-5.16c0-5.4 4.4-9.8 9.83-9.8 2.63 0 5.09 1.02 6.95 2.88a9.76 9.76 0 0 1 2.88 6.95c0 5.4-4.4 9.8-9.83 9.8zm5.39-7.34c-.3-.15-1.76-.87-2.03-.97-.27-.1-.47-.15-.67.15-.2.3-.77.97-.94 1.17-.17.2-.34.22-.64.07-.3-.15-1.27-.47-2.42-1.5-.9-.8-1.5-1.78-1.68-2.08-.17-.3-.02-.46.13-.61.13-.13.3-.34.45-.51.15-.17.2-.3.3-.5.1-.2.05-.37-.02-.52-.07-.15-.67-1.62-.92-2.22-.24-.58-.49-.5-.67-.51-.17-.01-.37-.01-.57-.01-.2 0-.52.07-.79.37-.27.3-1.04 1.02-1.04 2.48 0 1.46 1.07 2.88 1.22 3.08.15.2 2.1 3.2 5.08 4.48.71.31 1.26.49 1.69.63.71.22 1.36.19 1.87.12.57-.09 1.76-.72 2-1.41.25-.7.25-1.29.17-1.41-.07-.13-.27-.2-.57-.35z"/>
  </svg>
</a>
<style>
.wa-float {
  position: fixed;
  bottom: 22px;
  right: 22px;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: #25D366;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 14px rgba(0,0,0,.25);
  z-index: 9999;
  transition: background .15s ease, transform .15s ease;
}
.wa-float:hover { background: #1FB855; transform: scale(1.05); }
</style>
<?php });

Acessibilidade e mobile

Como rastrear o clique

O evento whatsapp_click é disparado automaticamente pelo GTM (configuração feita pela Sawi como parte do item 6.2). O listener é genérico: a[href*="wa.me"], a[href*="api.whatsapp.com"]. Não há configuração extra a fazer no plugin do botão.

Secção 11 · Encerramento

O que confirmar antes de dar por fechado

Oito testes mecânicos rápidos que validam, de uma só vez, que todos os ajustes acima estão a funcionar. Cada teste leva menos de 1 minuto.

Smoke test final

  1. Cloudflare na frente. Em terminal: curl -sI https://esprodouro.com/ | grep cf-ray. Deve devolver um header cf-ray: ....
  2. GTM no ar. Abrir https://esprodouro.com/ em janela anónima. DevTools → Network → filtro gtm.js. Deve aparecer pedido a googletagmanager.com/gtm.js?id=GTM-GC77GFQN com 200.
  3. Pixel + GA4 a disparar. Com o GTM Preview Mode aberto, submeter um lead de teste no /formulario/. Confirmar evento generate_lead no GA4 DebugView e no Meta Events Manager (Test Events).
  4. Redirects 301 a funcionar. Em terminal: curl -sI https://esprodouro.com/curso-profissional-de-tecnico-de-cozinha-pastelaria/ | head -5. Deve devolver HTTP/2 301 seguido de location: https://esprodouro.com/tecnico-cozinha/.
  5. Formulário com RGPD + tipos certos. Abrir /formulario/ em telemóvel. Tocar no campo "Telefone" — deve abrir teclado numérico com tecla +. Tocar no campo "Data de Nascimento" — deve abrir date picker nativo. O checkbox de consentimento deve estar visível e bloquear o "Submeter" se não estiver marcado.
  6. Atribuição offline. Abrir https://esprodouro.com/formulario/?utm_source=teste&utm_medium=smoke&utm_campaign=checklist. Submeter o form com e-mail interno. Em Fluent Forms → Entries, confirmar que utm_source=teste, utm_medium=smoke, utm_campaign=checklist aparecem; se o Pixel estiver activo, confirmar também fbp preenchido.
  7. Preview de link no WhatsApp. Partilhar https://esprodouro.com/formulario/ num WhatsApp de teste. O preview deve mostrar imagem 1200×630 + título não-genérico.
  8. Schema validado. Colar https://esprodouro.com/ no Rich Results Test. Deve detectar EducationalOrganization e Organization sem erros.
  9. WhatsApp button em mobile. Abrir qualquer página em telemóvel. Botão verde no canto inferior direito, toca sem ambiguidade, abre a app WhatsApp com a mensagem pré-preenchida no número +351 939 409 349.

Tempo total estimado

Cloudflare
~30min
+24h de propagação DNS
GTM (6.1)
10min
Plugin + colar ID
Pixel · GA4 (6.2)
0min
Lado da Sawi, no GTM
Redirects (6.5)
~30min
18 entradas no plugin escolhido
RGPD · form (6.6)
~1h
Depois do texto da Tânia chegar
Atribuição (6.6+)
~30min
Hidden fields + 2 snippets JS
Turnstile · OG · Schema (6.7)
~2h
Depende da imagem do Luiz
WhatsApp (6.8)
15min
Plugin sugerido ou snippet
Trabalho do Luís Teixeira
~5h
Cumulativo, distribuível ao longo de 1 semana
A seguir

Quando os ajustes estiverem fechados

Avisar a Sawi pelo WhatsApp do grupo. A Sawi entra para configurar as tags GA4 e Pixel dentro do GTM (item 6.2), correr o smoke test acima em conjunto com o Luís Teixeira, conduzir a criação da WABA do agente WhatsApp e a integração com o Tintim, e dar luz verde para subir as campanhas Google Ads e Meta Ads (item 2.3 da apresentação principal). Sem este checklist fechado, as feiras continuam a captar leads invisíveis ao tracking.