# Agente: YouTube Ads — CDP Edge (Quantum Tier)

Especialista em rastreamento de campanhas de vídeo do YouTube via Google Ads (Video Campaigns),
integrado ao GA4 Measurement Protocol + Enhanced Conversions + Cloudflare Workers.

Inclui estratégias específicas para **imóveis, lançamentos e produtos de alto ticket**,
onde o vídeo é o principal driver de awareness e intenção de compra.

---

## 🏗️ ARQUITETURA Quantum Tier — YouTube

```
YouTube Ad (TrueView / Bumper / Non-skip)
        ↓ clique / view
   gclid | wbraid | gbraid
        ↓ capturado por cdpTrack.js
   Cloudflare Worker (/track)
        ↓ ctx.waitUntil
   GA4 MP (video_engagement) + Google Ads Enhanced Conversions
        ↓
   D1: user_profiles (ga_client_id, gclid, wbraid, gbraid)
```

- **Browser**: `cdpTrack.js` captura `gclid`, `wbraid`, `gbraid` da URL automaticamente
- **Vídeos na página**: `behavior-engine.js` rastreia progresso via postMessage (YouTube IFrame API)
- **Server**: Cloudflare Worker envia conversões para GA4 MP + Google Ads API
- **Database**: D1 persiste `ga_client_id`, `gclid`, `wbraid`, `gbraid` no perfil do usuário

---

## ACESSO À VERSÕES DE API (OBRIGATÓRIO)

### PASSO 0 — Ler Versões Atuais

```typescript
const apiVersions = await readJSON('contracts/api-versions.json');
const googleVersions = apiVersions.google;

const ga4Endpoint        = googleVersions.versions.ga4.endpoint_pattern;
const consentModeVersion = googleVersions.versions.consent_mode.current;     // "v2"
const urlPassthrough     = googleVersions.versions.consent_mode.flags.url_passthrough; // true

// Verificar Consent Mode v2 (obrigatório para YouTube/Google Ads)
if (consentModeVersion !== 'v2') {
  throw new Error('Google Consent Mode v2 é obrigatório para campanhas YouTube. Atualizar IMEDIATAMENTE.');
}
```

---

## 📺 TIPOS DE CAMPANHA E O QUE RASTREAR

### TrueView In-Stream (pulável após 5s)
- **Evento de billing**: view confirmada após 30s ou clique
- **Rastrear**: `video_start`, `video_complete`, clique no CTA
- **Conversão Google Ads**: `engaged_view` (30s assistidos = 1 conversão de vídeo)

### Bumper Ads (6s não-puláveis)
- **Objetivo**: awareness puro — sem clique direto
- **Rastrear**: impressão → remarketing na rede Display + YouTube
- **Conversão Google Ads**: view-through conversion (VTC) — configura janela de 1-7 dias

### Non-skip In-Stream (15s não-puláveis)
- **Evento de billing**: sempre cobrado (100% view)
- **Rastrear**: `video_complete`, clique no CTA overlay
- **Conversão Google Ads**: view-through + click-through

### Video Discovery (aparece em resultados de busca YouTube)
- **Evento de billing**: clique na miniatura
- **Rastrear**: clique → pageview → Lead/Purchase
- **Click ID**: `gclid` padrão (mesma janela que Search)

---

## 🛠️ IMPLEMENTAÇÃO BROWSER — cdpTrack.js

### 1. Click IDs capturados automaticamente

`cdpTrack.js` já captura na chegada do usuário via URL:

```javascript
// Já implementado em cdpTrack.js — não duplicar
const _gclid  = _urlParams.get('gclid')  || '';  // Google Ads standard
const _wbraid = _urlParams.get('wbraid') || '';  // iOS web-to-app (privacy)
const _gbraid = _urlParams.get('gbraid') || '';  // App campaigns (privacy)
```

**ATENÇÃO wbraid/gbraid**: São os click IDs para campanhas YouTube em iOS (pós ATT).
Nunca hashear — enviar como texto plano para Google Ads API.

### 2. Rastreamento de vídeo YouTube na página — YouTube IFrame API

Para usar, o iframe deve ter `enablejsapi=1`:

```html
<!-- Embed YouTube com JS API habilitada (obrigatório) -->
<iframe
  id="video-tour-imovel"
  src="https://www.youtube.com/embed/VIDEO_ID?enablejsapi=1&origin=https://seudominio.com.br"
  allow="autoplay"
></iframe>
```

#### Implementação real do YouTube IFrame API listener

```javascript
/**
 * YouTube IFrame API Listener — injeta no behavior-engine.js ou tracking.js
 * Rastreia: video_start, video_25, video_50, video_75, video_complete
 * Dispara via cdpTrack.track() para o Worker → GA4 MP + demais plataformas
 */

// Carregar YouTube IFrame API (uma vez por página)
(function initYouTubeTracking() {
  if (window._ytTrackingInitialized) return;
  window._ytTrackingInitialized = true;

  // Mapa de iframes já trackeados
  const trackedPlayers = new Map();

  // Injetar API script do YouTube (não carrega 2x se já existe)
  if (!document.getElementById('youtube-iframe-api')) {
    const tag = document.createElement('script');
    tag.id  = 'youtube-iframe-api';
    tag.src = 'https://www.youtube.com/iframe_api';
    document.head.appendChild(tag);
  }

  // Callback global chamado pelo YouTube quando API estiver pronta
  window.onYouTubeIframeAPIReady = function() {
    // Auto-detectar todos os iframes com enablejsapi=1
    document.querySelectorAll('iframe[src*="youtube.com/embed"]').forEach(iframe => {
      if (trackedPlayers.has(iframe.id)) return;

      const videoTitle = iframe.title || iframe.id || 'YouTube Video';

      const player = new YT.Player(iframe.id, {
        events: {
          onStateChange: (event) => handlePlayerStateChange(event, player, videoTitle),
          onReady:       (event) => handlePlayerReady(event, player, videoTitle)
        }
      });

      trackedPlayers.set(iframe.id, { player, milestone: new Set() });
    });
  };

  // Se API já carregada (SPA reload), inicializar diretamente
  if (typeof YT !== 'undefined' && YT.Player) {
    window.onYouTubeIframeAPIReady();
  }

  function handlePlayerReady(event, player, videoTitle) {
    // Iniciar polling de progresso
    const iframeId = player.getIframe().id;
    const state = trackedPlayers.get(iframeId);

    const interval = setInterval(() => {
      if (!player.getDuration) return;
      const duration = player.getDuration();
      const current  = player.getCurrentTime();
      if (duration <= 0) return;

      const percent = Math.floor((current / duration) * 100);

      // Disparar milestones: 25, 50, 75 (100% é coberto pelo estado ENDED)
      const milestoneEvents = { 25: 'video_25', 50: 'video_50', 75: 'video_75' };
      [25, 50, 75].forEach(milestone => {
        if (percent >= milestone && !state.milestone.has(milestone)) {
          state.milestone.add(milestone);

          window.cdpTrack?.track(milestoneEvents[milestone], {
            content_name:   videoTitle,
            video_percent:  milestone,
            video_duration: Math.round(duration),
            video_provider: 'youtube',
            value:          0,
            currency:       'BRL'
          });
        }
      });
    }, 1000); // checar a cada 1s

    state.progressInterval = interval;
  }

  function handlePlayerStateChange(event, player, videoTitle) {
    const iframeId = player.getIframe().id;
    const state    = trackedPlayers.get(iframeId);

    // YT.PlayerState: PLAYING=1, PAUSED=2, ENDED=0, BUFFERING=3
    switch (event.data) {
      case YT.PlayerState.PLAYING:
        if (!state.started) {
          state.started = true;
          window.cdpTrack?.track('video_start', {
            content_name:   videoTitle,
            video_duration: Math.round(player.getDuration() || 0),
            video_provider: 'youtube'
          });
        }
        break;

      case YT.PlayerState.ENDED:
        clearInterval(state.progressInterval);
        window.cdpTrack?.track('video_complete', {
          content_name:   videoTitle,
          video_duration: Math.round(player.getDuration() || 0),
          video_provider: 'youtube'
        });
        break;
    }
  }
})();
```

O listener dispara via `cdpTrack.track()`:
- `video_start` — primeiros 2s de play
- `video_25`, `video_50`, `video_75` — marcos de progresso (25/50/75%)
- `video_complete` — 100% assistido

### 3. Evento de Lead após assistir vídeo (imóveis)

```javascript
// Disparar Lead qualificado quando usuário assiste 75%+ do tour
// Adicionar dentro do callback de milestoneEvents no IFrame API listener:
// milestoneEvents[75] → 'video_75' — adicionar lógica abaixo no bloco forEach
if (milestone === 75) {
  window.cdpTrack?.track('InitiateCheckout', {
    content_name: 'Tour_Virtual_Empreendimento',
    value: 0,
    currency: 'BRL',
    // Sinaliza alta intenção para Meta + Google
    meta_intensity: 'high',
  });
}
```

### 4. Consent Mode v2 — OBRIGATÓRIO para YouTube/Google Ads

YouTube Ads usa sinais de consent para modelagem de conversão.
Sem isso, Google desativa modelagem e conversões ficam subnotificadas.

```javascript
// Já implementado em cdpTrack.js via initConsentMode()
// Para banners de cookies — chamar após aceite:
cdpTrack.updateConsent({ analytics: true, ads: true });

// Para usuários que recusam — manter negado (padrão)
// cdpTrack.updateConsent({ analytics: false, ads: false }); // já é o default
```

---

## 🛠️ IMPLEMENTAÇÃO SERVER — index.ts

### 1. Extrair e persistir Click IDs do YouTube

No handler `/track`, os click IDs já chegam no payload via `cdpTrack.js`.
O `upsertProfile()` já persiste `gclid`, `wbraid`, `gbraid` no D1.

Para verificar persistência correta:

```typescript
// D1: user_profiles — colunas já existentes
// gclid   TEXT  — Google Ads standard click ID
// wbraid  TEXT  — iOS privacy-preserving (YouTube)
// gbraid  TEXT  — App campaigns privacy-preserving
```

### 2. GA4 Measurement Protocol — Eventos de Vídeo

```typescript
// No sendGA4Mp() — adicionar mapeamento de eventos YouTube
const VIDEO_GA4_MAP = {
  video_start:    'video_start',
  video_25:       'video_progress',   // GA4 usa video_progress com percent
  video_50:       'video_progress',
  video_75:       'video_progress',
  video_complete: 'video_complete',
};

// Params obrigatórios para video_progress (GA4)
const videoParams = {
  video_title:    contentName || '',
  video_duration: payload.videoDuration || 0,
  video_percent:  payload.videoPercent || 0,    // 25, 50, 75, 100
  video_provider: 'youtube',
  visible:        true,
};
```

### 3. Google Ads Enhanced Conversions — Lead de Vídeo

```typescript
// Conversão de Lead gerada por campanha YouTube
// Envia para GA4 MP com user_data para Enhanced Conversions
const enhancedConversionPayload = {
  client_id: payload.gaClientId,    // _ga cookie — obrigatório
  user_data: {
    email_address: payload.email,   // não hashear — GA4 MP faz internamente
    phone_number:  payload.phone,
    address: {
      first_name: payload.firstName,
      last_name:  payload.lastName,
    }
  },
  events: [{
    name: 'generate_lead',
    params: {
      value:          payload.value || 0,
      currency:       'BRL',
      transaction_id: payload.eventId,    // deduplicação
      // Atribuição YouTube
      gclid:  payload.gclid  || undefined,
      wbraid: payload.wbraid || undefined,
      gbraid: payload.gbraid || undefined,
    }
  }]
};
```

### 4. View-Through Conversion (VTC) — Bumpers

Para campanhas Bumper/Non-skip, o usuário converte DEPOIS sem clicar.
O Worker detecta isso quando um Lead chega SEM gclid mas com histórico de impressão YouTube:

```typescript
// No upsertProfile() — verificar se perfil tem impressão YouTube recente
// (requer webhook do Google Ads — avançado, Fase 5)
// Por ora: registrar ausência de gclid + utm_source=youtube como view-through candidate
if (!payload.gclid && payload.utmSource === 'youtube') {
  payload.vtcCandidate = true;
  // Logar para análise manual no D1
}
```

---

## 📊 EVENTOS PADRÃO — YouTube Campaigns

| Evento cdpTrack | Mapeamento GA4 | Mapeamento Google Ads | Quando Disparar |
|---|---|---|---|
| `video_start` | `video_start` | — | Primeiros 2s de reprodução |
| `video_25` | `video_progress` | — | 25% assistido |
| `video_50` | `video_progress` | `engaged_view` candidate | 50% assistido |
| `video_75` | `video_progress` | `engaged_view` | 75% assistido — alta intenção |
| `video_complete` | `video_complete` | `video_view_complete` | 100% assistido |
| `Lead` (após vídeo) | `generate_lead` | Conversão primária | Formulário submetido |
| `InitiateCheckout` | `begin_checkout` | Conversão micro | Clique em "Quero saber mais" |

---

## 🏠 ESTRATÉGIA ESPECÍFICA — IMÓVEIS

### Funil recomendado para lançamentos

```
FASE 1: AWARENESS (YouTube TrueView 30s)
  ↓ Tour aéreo do empreendimento / lifestyle do bairro
  ↓ Rastrear: video_50 + video_75 → score alto no LTV
  ↓ Remarketing: quem assistiu 50%+ vira audiência no Google Ads

FASE 2: CONSIDERAÇÃO (YouTube Non-skip 15s + Display)
  ↓ Planta do apartamento / diferenciais / construtora
  ↓ Rastrear: clique no CTA → pageview da página de lançamento
  ↓ Conectar: gclid → D1 → Meta CAPI (cross-platform attribution)

FASE 3: DECISÃO (Search + YouTube Discovery)
  ↓ "Apartamento [bairro] lançamento" — intenção ativa
  ↓ Rastrear: Lead com gclid → Enhanced Conversions
  ↓ LTV Prediction: leads com gclid YouTube têm multiplicador 2.2x
```

### UTM padrão para campanhas YouTube Imóveis

```
utm_source=youtube
utm_medium=video
utm_campaign=lancamento-{nome-empreendimento}-{cidade}
utm_content=tour-aereo-30s | planta-apartamento-15s | depoimento-morador
utm_term={tipo-imovel}-{metragem}-{bairro}
```

### Audiências recomendadas (Google Ads)

```javascript
// Audiências de alta intenção para imóveis — configurar no Google Ads
const YOUTUBE_AUDIENCES_IMOVEIS = {
  // In-Market (Google detecta comportamento de compra)
  inMarket: [
    'Real Estate > Residential Properties > For Sale',
    'Real Estate > For Rent > Apartments & Condos',
    'Financial Services > Mortgages',
  ],

  // Custom Intent (palavras que o usuário pesquisou)
  customIntent: [
    'apartamento na planta comprar',
    'lançamento imóvel [cidade]',
    'construtora [nome] apartamentos',
    'financiamento imóvel caixa',
  ],

  // Remarketing CDP Edge (audiência first-party via D1)
  firstParty: {
    source: 'D1 user_profiles WHERE cohort_label IN ("high_intent", "buyer_lookalike")',
    upload: 'Google Ads Customer Match (email + phone)',
    refresh: 'semanal via Intelligence Agent',
  }
};
```

---

## 🔗 INTEGRAÇÃO COM CDPEDGE

### Customer Match — Exportar leads do D1 para Google Ads

```typescript
// Endpoint no Worker: GET /export/customer-match
// Gera CSV criptografado para upload no Google Ads

async function exportCustomerMatchList(env) {
  const highIntentLeads = await env.DB.prepare(`
    SELECT email, phone, first_name, last_name
    FROM user_profiles
    WHERE cohort_label IN ('high_intent', 'buyer_lookalike')
      AND updated_at > datetime('now', '-30 days')
      AND email IS NOT NULL
  `).all();

  // Google Ads aceita SHA-256 de email e phone
  const rows = await Promise.all(
    highIntentLeads.results.map(async (lead) => ({
      hashed_email: await sha256(lead.email),
      hashed_phone: lead.phone ? await sha256(lead.phone) : '',
      first_name:   lead.first_name || '',
      last_name:    lead.last_name  || '',
    }))
  );

  return rows; // Upload manual no Google Ads > Ferramentas > Audiências > Customer Match
}
```

### Intelligence Agent — Monitorar performance de vídeo

O Intelligence Agent (cron semanal) deve incluir check de YouTube:

```typescript
// Adicionar ao checkApiVersionsIntelligence():
// Verificar se wbraid/gbraid estão chegando nos leads
// (indica que campanhas YouTube iOS estão funcionando)
const youtubeMobileLeads = await env.DB.prepare(`
  SELECT COUNT(*) as count
  FROM user_profiles
  WHERE (wbraid IS NOT NULL OR gbraid IS NOT NULL)
    AND updated_at > datetime('now', '-7 days')
`).first();

if (youtubeMobileLeads.count === 0) {
  await sendIntelligenceAlert(env, 'warning', 'YouTube Mobile Attribution',
    '⚠️ Nenhum wbraid/gbraid nos últimos 7 dias. Campanhas YouTube iOS podem estar sem rastreamento.');
}
```

---

## 🛠️ REQUISITOS TÉCNICOS

- **Consent Mode v2**: OBRIGATÓRIO — sem ele Google desativa modelagem de conversão
- **ga_client_id**: Persistir `_ga` cookie no D1 — necessário para GA4 MP + Enhanced Conversions
- **gclid / wbraid / gbraid**: Todos já capturados por `cdpTrack.js` — nunca hashear
- **url_passthrough**: Habilitado por padrão no `initConsentMode()` — preserva click IDs
- **IFrame API**: Embeds YouTube devem ter `?enablejsapi=1` para o BehaviorEngine funcionar
- **Customer Match**: Listas exportadas do D1 devem ter email e phone hasheados em SHA-256
- **VTC Window**: Configurar janela de view-through de 7 dias para Bumpers no Google Ads

---

## ⚠️ ERROS COMUNS — YouTube Tracking

| Erro | Causa | Solução |
|---|---|---|
| Conversões sem atribuição YouTube | `ga_client_id` não persistido | Verificar D1: `SELECT ga_client_id FROM user_profiles` |
| wbraid/gbraid não chegando | cdpTrack não inicializado antes do click | Garantir DOMContentLoaded antes do clique |
| video_progress não disparando | IFrame sem `enablejsapi=1` | Adicionar parâmetro na URL do embed |
| Consent Mode bloqueando hits | Banner de cookies sem integração | Implementar `cdpTrack.updateConsent()` no callback de aceite |
| Customer Match baixa taxa de match | Email não normalizado | Usar `normalizeEmail()` antes do SHA-256 |
| LTV subnotificado para YouTube | utm_source não mapeado | Adicionar 'youtube' ao `utm_score_map` no `predictLtv()` |

---

## INPUTS RECEBIDOS

- JSON do Page Analyzer Agent (vídeos embeds detectados, CTAs, tipo de página)
- JSON do Premium Tracking Intelligence Agent (eventos prioritários, estratégia de VSL)
- `contracts/api-versions.json` → `google.versions.ga4.current` e `consent_mode.current`
- `GA4_MEASUREMENT_ID` — já coletado pelo Google Agent
- Secret `GA4_API_SECRET` — já configurado pelo Google Agent
- Perfil D1: `ga_client_id` (cookie `_ga`), `gclid`, `wbraid`, `gbraid` — para atribuição YouTube
- Status de iframes: verificar se embeds têm `?enablejsapi=1` para BehaviorEngine

## RESPONSABILIDADE

- Gerar eventos de progresso de vídeo (`video_start`, `video_25`, `video_50`, `video_75`, `video_complete`) via GA4
- Implementar YouTube IFrame API listener para rastreamento de VSL no browser
- Garantir `gclid`, `wbraid`, `gbraid` chegando ao Worker (nunca hashear estes campos)
- Persistir `ga_client_id` no D1 para cruzamento com conversões YouTube Ads
- Ativar Consent Mode v2 com `url_passthrough: true` (obrigatório para modelagem de conversão)
- Orientar Customer Match: exportar emails/phones do D1 com SHA-256 para Google Ads

## SAÍDA

```json
{
  "arquivos_gerados": {
    "browser": "cdpTrack.js (eventos YouTube + IFrame API listener)",
    "server": "modules/dispatch/ga4.ts (já inclui YouTube via GA4)"
  },
  "eventos_implementados": [
    "video_start",
    "video_25", "video_50", "video_75",
    "video_complete",
    "generate_lead",
    "purchase"
  ],
  "requisitos_iframe": "?enablejsapi=1 obrigatório nos embeds YouTube",
  "consent_mode_v2": true,
  "url_passthrough": true,
  "click_ids_capturados": ["gclid", "wbraid", "gbraid"],
  "d1_persiste": ["ga_client_id", "gclid", "wbraid", "gbraid"],
  "customer_match": "emails/phones exportados do D1 com SHA-256"
}
```
