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.
- 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.
- 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.
- 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.
- Redirects 301 (item 6.5). Mapa pronto neste documento. Plugin sugerido: Redirection, ~30 min para colar todos.
- 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.
- 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.
- Turnstile + OG image + Schema Markup (item 6.7). Três tarefas pequenas que sobem qualidade de SEO e segurança.
- 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
- Criar conta em
cloudflare.com/sign-up (com o e-mail institucional).
- Em "Add a site", colar
esprodouro.com. Escolher o plano Free.
- 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.
- 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.
- No fim, a Cloudflare entrega dois nameservers (ex.:
nina.ns.cloudflare.com, walt.ns.cloudflare.com). Trocar na PTISP para esses dois nameservers. Salvar.
- Aguardar o e-mail de confirmação ("Active"). Pode levar até 24h, mas costuma vir em 1-2h.
- No painel Cloudflare →
SSL/TLS, configurar para Full (strict). O site já serve por HTTPS na PTISP, este modo evita warnings.
- 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.
- Em
Plugins → Adicionar novo, procurar o plugin (a sugestão é "GTM4WP", mas outros equivalentes servem). Instalar e activar.
- Em
Definições → Google Tag Manager, colar o ID GTM-GC77GFQN no campo "Google Tag Manager ID". Guardar.
- Em
Container code, escolher a opção Codeless injection (default). O plugin trata do <head> e do <body> sozinho.
- 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.
- 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
| Evento | Quando dispara | Onde aparece | Prioridade |
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.
- Em
Plugins → Adicionar novo, procurar o plugin escolhido (sugestão: "Redirection"). Instalar e activar.
- O setup wizard pergunta se quer monitorizar mudanças de permalink — responder "sim" (evita 404 quando o slug de um curso mudar no futuro).
- Em
Ferramentas → Redirection → Redirects, colar os 18 redirects (mapa abaixo). Tipo: URL only, código 301 - Moved Permanently.
- Confirmar que cada redirect funciona: abrir cada URL "de" em janela anónima, deve cair na URL "para".
- 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 from | 301 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 from | 301 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
- Em
Aparência → Editor → Menus e dentro do Elementor, substituir todos os internal links para os slugs novos (/tecnico-cozinha/, etc.). O 301 funciona, mas internal link directo poupa 1 hop.
- Submeter os dois sitemaps actualizados no Google Search Console (logo que o GSC ficar reivindicado):
esprodouro.com/sitemap-page.xml e esprodouro.com/sitemap-post-type-page.xml.
- Confirmar que
esprodouro.com/cursos/ continua a listar todos os cursos com os slugs novos.
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)
- Em
Fluent Forms → Todos os formulários, abrir o form com 22 campos (ID 10).
- 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.
- Segundo checkbox declarativo do encarregado. Arrastar Single Checkbox. Texto: declaração do encarregado para menores de 16. Marcar Required.
- 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.
- 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".
- 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"
- 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).
- 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é)
- Adicionar checkbox RGPD (mesmo texto).
- Trocar o tipo do campo "Telefone/Whatsapp" para
type="tel" + autocomplete="tel".
- Corrigir o erro de digitação visível ao utilizador: a label hoje diz "Tefefone/Whatsapp". Trocar para "Telefone/Whatsapp".
- Adicionar
autocomplete="name" e autocomplete="email" nos campos correspondentes.
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âmetro | Origem | Como 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.
- No editor do
fluentform_10 (e do fluentform_11), arrastar Hidden Field da paleta. Repetir 7 vezes (uma por parâmetro).
- 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)
- Os Smart Codes do Fluent Forms para query string são
{get.NOME_DO_PARAMETRO} — entram entre chavetas no campo Default Value.
| Field Name | Default 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
- Abrir uma URL de teste com UTMs:
https://esprodouro.com/formulario/?utm_source=teste&utm_medium=manual&utm_campaign=teste-atribuicao.
- Abrir DevTools → Application → Cookies. Procurar
esp_attr; deve conter as três UTMs em JSON.
- Navegar para outra página do site, voltar ao formulário. As UTMs continuam disponíveis (ainda no cookie).
- 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.
- 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.
- Dentro do painel Cloudflare, ir a
Turnstile → Add site. Domínio: esprodouro.com. Modo: Managed (o mais transparente para o utilizador).
- A Cloudflare entrega um Site Key e um Secret Key.
- No Fluent Forms, em
Fluent Forms → Configurações Globais → reCAPTCHA & hCaptcha & Turnstile, colar o Site Key e o Secret Key.
- No editor de cada formulário (10 e 11), arrastar o campo Turnstile para o final, acima do botão "Submeter".
- 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
- Imagem 1200×630 (proporção 1.91:1 — formato recomendado pelo Facebook/WhatsApp). Formato PNG ou JPG, abaixo de 300 KB.
- Deve conter o logótipo ESPRODOURO, uma chamada de captação ("Candidaturas 2026/27 abertas") e fundo com cor de marca. O Luiz designer entrega como peça do briefing de criativos (item 4.3).
- Upload em
Multimédia → Adicionar novo. Guardar o URL (formato https://esprodouro.com/wp-content/uploads/2026/05/og-formulario.jpg).
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.
- Editar a página
/formulario/ no WordPress.
- No painel lateral do Slim SEO, em Social Open Graph → Image, escolher a imagem 1200×630 da biblioteca de média.
- 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.
- 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.
- 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
| Schema | Onde aplicar | O 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.
- Em
Plugins → Adicionar novo, procurar o plugin escolhido. Instalar e activar.
- Em
Click to Chat → Settings (ou equivalente), colar o número 351939409349 (sem o + e sem espaços).
- Em Pre-filled Message, colar: "Olá, vi a página da ESPRODOURO e queria saber mais sobre o curso de [Curso]."
- Em Display Settings → Position: bottom-right (canto inferior direito).
- Em Display Settings → Show on Devices: marcar Desktop + Mobile.
- Em Display Settings → Show On: All pages.
- 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
- Tap target ≥48×48 px — WCAG 2.5.5 Target Size. O botão flutuante de 56×56 px atende.
aria-label="Falar pelo WhatsApp" — necessário porque o botão não tem texto visível, apenas ícone.
- Não cobrir o botão de submeter o formulário. Em mobile, na LP
/formulario/, verificar que o botão flutuante não fica sobreposto ao botão "Enviar inscrição" quando o utilizador scroll-down até ao fim do form. Se acontecer, esconder o botão na rota /formulario/ via opção do plugin ou regra CSS.
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
- Cloudflare na frente. Em terminal:
curl -sI https://esprodouro.com/ | grep cf-ray. Deve devolver um header cf-ray: ....
- 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.
- 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).
- 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/.
- 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.
- 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.
- 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.
- Schema validado. Colar
https://esprodouro.com/ no Rich Results Test. Deve detectar EducationalOrganization e Organization sem erros.
- 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.