account tabla en Supabase almacena providers OAuth existentes (GA4)conversions tiene columnas lead_id, source, amount, created_at; solo inserciones manuales hoysession.user.id en cada procedimientovitest para unit/integration (~40% cobertura)adwords + Customer ID por cuentaamount de conversiones en Google Ads corresponde al valor de conversión, no al gastocampaign_id en conversions; requiere migración| Área de riesgo | ¿Aplica? | Tratamiento requerido |
|---|---|---|
| Seguridad / privacidad | SÍ | 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 | SÍ | 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 | SÍ | 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 | SÍ | NextAuth account schema debe extenderse sin romper providers existentes (GA4); contrato de respuesta del dashboard no debe cambiar para campos previos |
| UX / accesibilidad | SÍ | 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 | SÍ | El job de ingesta horaria debe ser idempotente: no duplicar conversiones ya importadas (upsert por google_ads_conversion_id) |
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
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
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
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
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
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
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
| 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 |
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