Acceptance Brief: Integración Google Ads

LeadFlow SaaS — CRM B2B para agencias de marketing
● Draft Full Acceptance Brief ⚠ Riesgo Alto Aprobación requerida antes de implementar
Rev 1
2026-06-18
Equipo Engineering
Product — LeadFlow
Next.js 14 · Supabase · tRPC
Goal
Los usuarios autenticados pueden conectar su cuenta de Google Ads via OAuth2 y consultar en el dashboard las conversiones de sus campañas activas desglosadas por campaña, sin que otros usuarios puedan acceder a sus datos ni tokens.
Scope

In Scope

  • Flujo OAuth2 de conexión con Google Ads (consent screen)
  • Almacenamiento cifrado de refresh tokens por usuario
  • Ingesta de conversiones desde Google Ads API v17
  • Vista de conversiones en dashboard filtrada por campaña
  • Desconexión / revocación de la integración
  • Indicador de estado de conexión en UI de ajustes

Out of Scope

  • Gestión de pujas o cambios en campañas desde LeadFlow
  • Integración con Google Analytics (ya existe, vía GA4)
  • Ingesta histórica anterior a la fecha de conexión
  • Soporte multi-cuenta MCC de Google Ads
  • Exportación CSV de datos de Google Ads
  • Webhooks o alertas automáticas de conversiones
Context

Hechos técnicos descubiertos

  • NextAuth v5 gestiona sesiones JWT; account tabla en Supabase almacena providers OAuth existentes (GA4)
  • Tabla conversions tiene columnas lead_id, source, amount, created_at; solo inserciones manuales hoy
  • tRPC con middleware de auth valida session.user.id en cada procedimiento
  • Tests E2E con Playwright; vitest para unit/integration (~40% cobertura)
  • Entorno de staging en Vercel Preview separado de producción
  • Google Ads API requiere scope adwords + Customer ID por cuenta

Restricciones de producto / negocio

  • Solo usuarios en plan Pro o superior pueden conectar integraciones (suministrado por Product)
  • Los tokens de terceros deben cifrarse en reposo — política de seguridad interna (pendiente confirmar mecanismo)
  • SLA de frescura de datos: conversiones deben actualizarse al menos cada hora (pendiente confirmar)
  • Customer ID de Google Ads lo introduce el usuario manualmente — no hay descubrimiento automático
  • Política de retención de tokens: revocar y eliminar si el usuario cancela la integración o la cuenta

Supuestos a confirmar

  • El máximo de conversiones por cuenta es manejable con paginación estándar de la API (< 50k/mes)
  • El campo amount de conversiones en Google Ads corresponde al valor de conversión, no al gasto
  • Staging tiene Client ID/Secret de Google Ads separados de producción
  • No existe aún ninguna columna campaign_id en conversions; requiere migración
Risk Review
Área de riesgo ¿Aplica? Tratamiento requerido
Seguridad / privacidad Cifrado AES-256 de refresh tokens en Supabase; nunca exponer tokens en respuestas tRPC; auditoría de scope mínimo OAuth
Datos persistentes / migración Migración para añadir campaign_id y google_ads_conversion_id a tabla conversions; script rollback requerido; compatibilidad con inserciones manuales existentes
Efectos externos / coste Google Ads API tiene cuotas por día; tests en sandbox de Google Ads; nunca llamar a API de producción desde entorno de pruebas sin autorización explícita
Compatibilidad / API NextAuth account schema debe extenderse sin romper providers existentes (GA4); contrato de respuesta del dashboard no debe cambiar para campos previos
UX / accesibilidad Flujo OAuth abre popup/redirect; necesita manejo de error si usuario deniega permiso; estado de conexión visible; revisión manual en móvil
Concurrencia / idempotencia El job de ingesta horaria debe ser idempotente: no duplicar conversiones ya importadas (upsert por google_ads_conversion_id)
Acceptance Criteria
AC-001 Conexión OAuth2 con Google Ads completa correctamente Required

Usuario Pro autenticado, sin integración Google Ads activa, en página Ajustes → Integraciones

Hace clic en "Conectar Google Ads", introduce su Customer ID y completa el consent screen de Google

La integración aparece con estado "Conectado" en UI; el refresh token queda almacenado cifrado en Supabase; no se devuelve el token raw en ninguna respuesta tRPC

Exponer el refresh token en la respuesta HTTP, logs de Vercel o localStorage; conectar si el plan del usuario no es Pro o superior

Test E2E Playwright en entorno staging (Google Ads sandbox); inspección manual de la tabla accounts en Supabase staging para verificar cifrado; revisión de logs de Vercel para ausencia de token

Solo staging; Client ID/Secret de Google sandbox; nunca producción

AC-002 Ingesta de conversiones sin duplicados Required

Usuario con integración activa; job horario se ejecuta dos veces seguidas (simulado en test)

El job llama a la API de Google Ads y hace upsert en tabla conversions

Después de dos ejecuciones, el conteo de filas en conversions para ese usuario es idéntico al de la primera ejecución; los valores numéricos no se duplican

Insertar filas duplicadas por reintento; modificar conversiones manuales existentes (source ≠ 'google_ads')

Test de integración con Vitest: mock de Google Ads API devuelve 3 conversiones fijas; ejecutar job 2x; SELECT COUNT(*) en Supabase test debe ser 3, no 6

Base de datos de test aislada; nunca base de datos de producción

AC-003 Aislamiento de datos entre usuarios Required

Usuario A y Usuario B tienen integraciones Google Ads independientes con Customer IDs distintos

Usuario A consulta el endpoint tRPC conversions.list

La respuesta contiene exclusivamente las conversiones asociadas al user_id de Usuario A

Devolver datos, Customer ID ni tokens del Usuario B; exponer metadatos de campañas ajenas

Test de integración Vitest: dos usuarios con seed de datos separados; llamada autenticada como Usuario A no devuelve ningún row con user_id de Usuario B

Test DB aislada; revisar política RLS de Supabase para tabla conversions

AC-004 Dashboard muestra conversiones filtradas por campaña Required

Usuario con integración activa y al menos 2 campañas con conversiones importadas

Abre el dashboard y selecciona una campaña en el selector de filtro

La tabla/gráfico de conversiones muestra solo las filas donde campaign_id coincide con la campaña seleccionada; el total en el KPI refleja únicamente esas conversiones

Alterar los datos de leads ni el pipeline de ventas del dashboard al aplicar el filtro; romper el selector de fuente GA4 existente

Test E2E Playwright: seed de 2 campañas × 5 conversiones; seleccionar campaña A; verificar que se renderizan exactamente 5 filas y el total es el correcto

Staging con datos sintéticos

AC-005 Migración de esquema con rollback funcional Required

Base de datos staging con registros existentes en tabla conversions (inserciones manuales)

Ejecutar migración de añadir columnas campaign_id y google_ads_conversion_id; a continuación ejecutar rollback

Tras migración: filas existentes mantienen todos sus valores; nuevas columnas son nullable. Tras rollback: tabla idéntica al estado previo; ninguna fila perdida

Eliminar o alterar datos de conversiones manuales existentes; bloquear la tabla más de 1s en staging

Script de migración en entorno CI con Supabase local; verificar conteo de filas antes/después; ejecutar rollback y comparar checksum de la tabla

Solo staging/CI; nunca producción sin aprobación previa; backup previo documentado

AC-006 Desconexión revoca el token y elimina datos de la integración Important

Usuario con integración activa hace clic en "Desconectar Google Ads"

Confirma la acción en el modal de confirmación

La integración desaparece de la UI; el refresh token es eliminado de Supabase; se llama a la API de revocación de Google (oauth2.googleapis.com/revoke)

Conservar el token en ninguna tabla; eliminar las conversiones históricas ya importadas (solo se desconecta, no se borran datos de negocio)

Test de integración: mock del endpoint de revocación; verificar que el token ya no existe en tabla accounts; verificar que conversiones históricas siguen presentes

Staging; mock de Google revocation endpoint

AC-007 Usuario en plan Free no puede acceder a la integración Required

Usuario autenticado con plan Free visita Ajustes → Integraciones

Intenta hacer clic en "Conectar Google Ads" (o llama directamente al endpoint tRPC)

La UI muestra el botón deshabilitado con tooltip "Disponible en plan Pro"; el endpoint tRPC devuelve error 403 si se llama directamente

Iniciar el flujo OAuth; almacenar ningún dato de la cuenta de Google del usuario

Test unitario del middleware de plan; test E2E con usuario Free (Playwright); test de integración llamando al endpoint tRPC con JWT de usuario Free

Staging

Blocking Decisions
Verification Plan
Criterio Evidencia de verificación Entorno Estado
AC-001 E2E Playwright + inspección manual tabla accounts Staging / Google sandbox Pending
AC-002 Test integración Vitest: mock API, 2x ejecución, COUNT(*) Test DB aislada / CI Pending
AC-003 Test integración Vitest: dos usuarios, query filtrada Test DB aislada / CI Pending
AC-004 E2E Playwright: seed 2 campañas × 5 conv., filtro + conteo Staging Pending
AC-005 Script migración + rollback en CI con Supabase local CI (Supabase local) Pending
AC-006 Test integración: mock revocation + verificar tabla + datos históricos Staging Pending
AC-007 Test unitario middleware + E2E Playwright usuario Free + tRPC 403 Staging / CI Pending
Rubric de calidad — todos los checks pasan

Cada criterio tiene scenario, resultado observable y método de verificación

Ningún término vago ("seguro", "correcto") sin evidencia observable

Restricciones de negocio marcadas como suministradas o supuestos, no inferidas del código

Scope explícito con ítems out-of-scope nombrados

Blocking decisions limitadas a lo que afecta seguridad o corrección