# Spec --- Módulo de rendimiento SEO / Keywords

**Estado:** propuesta funcional y técnica\
**Fuente principal:** Google Search Console API --- Search Analytics\
**Objetivo:** convertir los datos de rendimiento de Google en un módulo
rápido, histórico y accionable, sin depender de consultas en tiempo real
desde la interfaz.

------------------------------------------------------------------------

## 1. Alcance

Este módulo complementa los módulos existentes de:

1.  **Estado de indexación:** inspección de páginas, sitemaps y URLs
    indexadas/no indexadas.
2.  **Lotes de indexación/reindexación:** envío de URLs a los flujos ya
    implementados para solicitar procesamiento por Google.
3.  **Rendimiento SEO / Keywords (este módulo):** descubrir las
    consultas por las que el sitio aparece en Google, almacenar su
    evolución y analizar la relación entre keywords y páginas.

La **fuente de verdad principal** será Google Search Console. La
aplicación no intentará presentar una SERP personalizada como si fuera
el dato oficial de Search Console.

------------------------------------------------------------------------

## 2. Principios de diseño

-   La UI consulta **nuestra base de datos**, no Google en cada carga.
-   Google Search Console se usa como fuente de sincronización.
-   El histórico se almacena con **granularidad diaria**.
-   La primera ejecución realiza un **backfill**, no un seeder.
-   Después del backfill se ejecuta una **sincronización semanal**.
-   Cada sincronización semanal vuelve a consultar una **ventana móvil
    de 14 días** para absorber correcciones o retrasos de Google.
-   Las operaciones de importación y sincronización se ejecutan en
    **jobs/colas**.
-   "Sin datos" es un estado válido; no equivale a error.
-   Los datos observados mediante un fallback de SERP nunca se mezclan
    ni se etiquetan como datos de Search Console.

------------------------------------------------------------------------

## 3. Verificaciones automáticas antes de habilitar el módulo

El usuario no debería necesitar conocer APIs, scopes, códigos HTTP ni
configuración interna. El backend debe verificar lo necesario y traducir
el resultado a estados comprensibles.

### Health checks

Al conectar o validar una propiedad:

1.  Confirmar que existe una conexión Google válida.
2.  Confirmar que el token puede acceder a Search Console.
3.  Confirmar que la propiedad seleccionada es accesible por esa cuenta.
4.  Confirmar que disponemos de permisos suficientes para lectura.
5.  Ejecutar una consulta mínima a **Search Analytics**.
6.  Diferenciar explícitamente:
    -   integración funcional con datos;
    -   integración funcional pero sin datos;
    -   autorización insuficiente;
    -   propiedad no accesible;
    -   credenciales/token inválidos o expirados;
    -   error temporal de Google.

### Estados estándar

-   `ready` --- módulo listo.
-   `syncing` --- importación/sincronización en curso.
-   `action_required` --- el usuario debe corregir conexión, acceso o
    permisos.
-   `no_data` --- integración válida, pero Google no devuelve
    rendimiento para el periodo.
-   `error` --- fallo técnico que requiere reintento o intervención.

La interfaz debe mostrar mensajes accionables. Nunca debería exponer un
`403`, `401` o payload interno como explicación principal al usuario.

------------------------------------------------------------------------

## 4. Backfill inicial

Al activar el módulo por primera vez:

1.  Ejecutar los health checks.
2.  Determinar el rango histórico disponible que se desea importar.
3.  Crear un registro de sincronización con estado `syncing`.
4.  Lanzar un job de backfill.
5.  Consultar Search Analytics de forma paginada.
6.  Solicitar datos con granularidad diaria y las dimensiones necesarias
    para reconstruir:
    -   fecha;
    -   query;
    -   página.
7.  Persistir métricas:
    -   clics;
    -   impresiones;
    -   CTR;
    -   posición media.
8.  Realizar **upsert** mediante una clave natural/única basada en
    propiedad + fecha + query + página (y cualquier dimensión adicional
    que decidamos conservar).
9.  Continuar la paginación hasta terminar los resultados disponibles.
10. Calcular/actualizar agregados necesarios para la UI.
11. Marcar la sincronización como completada.
12. Registrar fecha de cobertura, filas procesadas, páginas API
    consumidas y errores/reintentos.

### Importante

Search Console no garantiza una enumeración absolutamente exhaustiva de
todas las consultas. Por privacidad y limitaciones internas puede omitir
consultas raras y prioriza las filas principales. Por tanto, nuestra
base representa **los datos que Google Search Console expone**, no una
reconstrucción completa de todas las búsquedas realizadas.

------------------------------------------------------------------------

## 5. Sincronización semanal

Frecuencia inicial: **una vez por semana**.

Cada ejecución:

1.  Toma como rango los **últimos 14 días**.
2.  Consulta nuevamente esos días en Google.
3.  Hace upsert sobre los registros diarios existentes.
4.  Inserta los días nuevos.
5.  Actualiza agregados y variaciones.
6.  Registra la ejecución y sus métricas.

El solape permite corregir datos que hayan llegado tarde o hayan
cambiado después de una sincronización anterior.

### Datos recientes

Google indica que los datos pueden tener retraso y que los más recientes
pueden ser preliminares. Por eso el módulo no debe asumir que "ayer" es
necesariamente definitivo. El solape de 14 días nos da margen amplio
para reconciliación.

------------------------------------------------------------------------

## 6. Modelo conceptual de datos

### `search_console_properties`

-   id
-   site/domain
-   google property identifier
-   connection/account reference
-   module status
-   last successful sync
-   coverage start
-   coverage end

### `seo_keyword_daily`

Registro diario de rendimiento.

-   property_id
-   date
-   query
-   page
-   clicks
-   impressions
-   ctr
-   position
-   timestamps

Índice único recomendado:

`property_id + date + query + page`

### `seo_tracked_keywords`

Keywords que el usuario decide seguir explícitamente.

-   property_id
-   keyword
-   source (`google_discovered` / `custom`)
-   tracked
-   created_at

Una keyword custom puede existir aunque todavía no tenga datos de Search
Console.

### `seo_sync_runs`

-   property_id
-   type (`initial_backfill` / `weekly_sync`)
-   requested_start
-   requested_end
-   started_at
-   finished_at
-   status
-   rows_processed
-   api_requests
-   error summary

### `seo_serp_observations`

Separada de Search Console.

-   property_id
-   keyword
-   target domain/page
-   observed_at
-   observed_position
-   result_url
-   checked_depth
-   status
-   metadata

Nunca usar esta tabla para alterar el histórico oficial de Search
Console.

------------------------------------------------------------------------

## 7. Vista general --- Keywords

La pantalla principal debe responder rápidamente:

> ¿Por qué búsquedas estoy apareciendo en Google y cómo está cambiando
> mi rendimiento?

### Tabla sugerida

Por keyword:

-   Keyword
-   Posición actual/referencia
-   Δ 1 día
-   Δ 3 días
-   Δ 7 días
-   Δ 15 días
-   Impresiones
-   Clics
-   CTR
-   Página principal asociada
-   Número de páginas que aparecen para esa keyword
-   Estado de seguimiento
-   Acción para abrir detalle

### Variaciones de posición

Mostrar las ventanas:

-   **1D**
-   **3D**
-   **7D**
-   **15D**

Convención UX recomendada:

-   `+4` = mejoró cuatro posiciones.
-   `-3` = perdió tres posiciones.
-   `—` = no existe información comparable.
-   Tooltip con los dos valores usados para calcular la variación.

La fórmula debe orientarse a que un número positivo signifique mejora:

`delta = position_previous - position_current`

Ejemplo: de posición 12 a posición 7 → `+5`.

No confundir "posición media" con una comprobación exacta de SERP en
tiempo real.

### Filtros útiles

-   Buscar keyword.
-   Página/URL.
-   Solo tracked.
-   Con/sin datos.
-   Rango temporal.
-   Ganadores/perdedores.
-   Mínimo de impresiones.

------------------------------------------------------------------------

## 8. Agregar keywords custom

La UI debe permitir añadir una keyword manualmente como objetivo.

Flujo:

1.  Usuario introduce la keyword.
2.  Se guarda en `seo_tracked_keywords` con `source=custom`.
3.  La aplicación busca si ya existen datos históricos de Search Console
    para esa query.
4.  Si existen, se muestra inmediatamente su histórico.
5.  Si no existen, se muestra `No data from Google`.
6.  La keyword permanece monitorizada y futuras sincronizaciones pueden
    comenzar a poblarla cuando Google exponga datos.

Esto permite definir objetivos SEO antes de que Search Console muestre
actividad.

------------------------------------------------------------------------

## 9. Vista de detalle de una keyword

La pantalla de detalle debería responder:

> ¿Cómo está evolucionando esta keyword, qué tráfico genera y qué URLs
> de mi sitio compiten/aparecen por ella?

### Cabecera

-   Keyword.
-   Estado: Google data / no Google data / custom.
-   Posición de referencia.
-   Δ 1D / 3D / 7D / 15D.
-   Clics.
-   Impresiones.
-   CTR.

### Gráfica principal

Serie temporal diaria de:

-   posición media;
-   impresiones;
-   clics;
-   CTR.

La UX puede permitir activar/desactivar métricas para evitar una gráfica
saturada.

### Páginas asociadas

Mostrar las URLs que Google relaciona con la keyword:

-   URL;
-   posición;
-   impresiones;
-   clics;
-   CTR;
-   evolución;
-   porcentaje del rendimiento de la query atribuible a esa URL.

Esto permite detectar canibalización o cambios de URL dominante.

### Histórico

Tabla diaria opcional para auditoría:

-   fecha;
-   posición;
-   variación;
-   impresiones;
-   clics;
-   CTR.

### Acciones

-   Track / untrack.
-   Abrir página.
-   Ir al módulo de indexación/inspección de esa URL.
-   Ejecutar fallback SERP cuando no haya datos, si la función está
    habilitada.

------------------------------------------------------------------------

## 10. Vista inversa --- Detalle de una página

Dado que almacenamos `query + page + date`, también podemos ofrecer:

> ¿Para qué keywords aparece esta URL?

Por URL:

-   principales keywords;
-   posición;
-   impresiones;
-   clics;
-   CTR;
-   variaciones 1D/3D/7D/15D;
-   evolución temporal.

Esto conecta naturalmente el nuevo módulo con el módulo existente de
indexación.

------------------------------------------------------------------------

## 11. Fallback opcional --- "Check SERP"

El fallback no sustituye Search Console.

Solo debe utilizarse de forma puntual, preferiblemente mediante acción
explícita del usuario cuando Google no tiene datos para una keyword.

### Flujo

1.  Usuario pulsa `Check SERP`.
2.  Backend crea un job.
3.  Worker consulta resultados de Google para la keyword.
4.  Analiza los resultados buscando el dominio/URL objetivo.
5.  Si no aparece, continúa hasta la siguiente página mientras no supere
    el límite configurado.
6.  Se detiene al:
    -   encontrar el resultado;
    -   alcanzar la profundidad máxima;
    -   recibir un bloqueo/error.
7.  Guarda la observación en `seo_serp_observations`.
8.  La UI la muestra como **SERP observation**, nunca como Search
    Console position.

### Restricciones

-   No ejecutarlo automáticamente para todas las keywords.
-   No convertirlo inicialmente en un rank tracker masivo.
-   Aplicar rate limiting y cola.
-   Mantener separada su procedencia en datos y UI.
-   Si Google cambia markup, comportamiento o mecanismos
    anti-automatización, el módulo principal debe seguir funcionando sin
    este fallback.

------------------------------------------------------------------------

## 12. Search Console API --- forma de consulta

Endpoint lógico principal:

**Search Console API → Search Analytics → `query`**

Las dimensiones se combinan según la vista requerida.

Ejemplos conceptuales:

### Mapa general

`dimensions: ["query"]`

### Keyword ↔ página

`dimensions: ["query", "page"]`

### Histórico diario

`dimensions: ["date", "query", "page"]`

La API permite un `rowLimit` de hasta **25,000 filas por request** y
paginación mediante `startRow`.

Para el backfill debemos paginar hasta recibir menos filas que el límite
o una respuesta sin filas.

------------------------------------------------------------------------

## 13. Consideraciones de exactitud

### Posición

La posición de Search Console es una métrica agregada de impresiones
reales; no significa necesariamente "si busco ahora mismo desde mi
computador apareceré exactamente en X".

La SERP puede variar por ubicación, dispositivo, momento e
historial/contexto del usuario.

### Privacidad y filas omitidas

Google puede ocultar consultas poco frecuentes para proteger privacidad
y no garantiza todas las filas posibles.

Por tanto:

-   `no_data` no significa necesariamente "Google jamás mostró la
    página".
-   No debemos fabricar ceros donde Google simplemente no devuelve
    información.
-   Los totales agregados pueden no coincidir exactamente con la suma de
    todas las queries almacenadas.

### Zona horaria

Search Console etiqueta sus datos diarios según la zona horaria de
California. Guardar explícitamente la fecha reportada por Google y
evitar reinterpretarla como si hubiese sido generada en la zona horaria
del usuario.

------------------------------------------------------------------------

## 14. UX de sincronización

En lugar de botones técnicos, usar lenguaje de producto.

Ejemplo de tarjeta de estado:

**SEO Performance**\
`Ready`\
Última sincronización: 23 Aug 2026\
Datos disponibles hasta: 22 Aug 2026\
Próxima sincronización: semanal

Acciones:

-   `Sync now` --- opcional para administradores.
-   `Reconnect Google` --- solo cuando sea necesario.
-   `View sync details` --- diagnóstico avanzado.

Durante el primer backfill:

**Importing Google Search performance**\
"Estamos construyendo tu histórico. Puedes seguir usando el resto de la
plataforma."

La UI no debería bloquear toda la aplicación mientras corre el job.

------------------------------------------------------------------------

## 15. Errores y recuperación

Cada job debe ser idempotente.

Recomendaciones:

-   retries con backoff para errores temporales;
-   upserts para poder repetir rangos;
-   checkpoints/paginación recuperable en backfills grandes;
-   logging por sync;
-   mutex/lock por propiedad para evitar dos syncs simultáneos;
-   timeout razonable por request;
-   no borrar histórico válido si una sincronización falla.

Un fallo semanal debe conservar el último dataset válido y mostrar un
estado degradado, no dejar el módulo vacío.

------------------------------------------------------------------------

## 16. Métricas derivadas que podemos añadir

Sin pedir nuevos datos a Google:

-   ganadores/perdedores por posición;
-   keywords nuevas;
-   keywords que desaparecieron del periodo observable;
-   crecimiento de impresiones;
-   crecimiento de clics;
-   cambios de CTR;
-   top keywords por URL;
-   top URLs por keyword;
-   posible canibalización;
-   keywords con muchas impresiones y CTR bajo;
-   keywords cerca de primera página / posiciones objetivo;
-   tendencia 7D vs periodo anterior;
-   tendencia 15D vs periodo anterior.

Estas métricas se calculan sobre nuestra base y no consumen cuota
adicional de Google.

------------------------------------------------------------------------

## 17. MVP recomendado

### Incluir

-   Health checks automáticos.
-   Backfill inicial.
-   Sync semanal con solape de 14 días.
-   Persistencia diaria query + page.
-   Vista general de keywords.
-   Variaciones 1D / 3D / 7D / 15D.
-   Detalle de keyword.
-   Relación keyword ↔ páginas.
-   Vista inversa página ↔ keywords.
-   Keywords custom/tracked.
-   Estados de integración y sincronización.
-   Fallback SERP manual y separado, si decidimos habilitarlo en la
    primera versión.

### Dejar para evolución

-   Alertas automáticas.
-   Scoring SEO propio.
-   Forecasting.
-   Comparación entre múltiples propiedades.
-   Segmentación avanzada por país/dispositivo.
-   Rank tracking SERP automático.
-   Recomendaciones generadas por IA.

------------------------------------------------------------------------

## 18. Criterios de aceptación

El módulo se considera funcional cuando:

-   [ ] Detecta automáticamente si la integración de Search Console está
    lista.
-   [ ] Explica de forma accionable cualquier problema de
    permisos/conexión.
-   [ ] Ejecuta el backfill sin depender de mantener abierta la
    interfaz.
-   [ ] Guarda histórico diario sin duplicados.
-   [ ] La sincronización semanal reconsulta los últimos 14 días.
-   [ ] La UI funciona exclusivamente sobre nuestra base de datos.
-   [ ] Se puede listar el mapa de keywords expuesto por Google.
-   [ ] Cada keyword muestra variaciones 1D, 3D, 7D y 15D cuando existen
    datos comparables.
-   [ ] Se puede abrir el histórico de una keyword.
-   [ ] Se pueden consultar sus páginas asociadas.
-   [ ] Se pueden consultar las keywords asociadas a una página.
-   [ ] Se pueden crear keywords custom aunque Google aún no tenga
    datos.
-   [ ] `no_data` se distingue de un error.
-   [ ] Las observaciones SERP, si existen, se distinguen
    inequívocamente de Search Console.
-   [ ] Una sincronización fallida no destruye el último dataset válido.

------------------------------------------------------------------------

## 19. Decisiones cerradas

-   **Fuente primaria:** Google Search Console.
-   **Lectura UI:** base de datos propia.
-   **Granularidad almacenada:** diaria.
-   **Primera carga:** backfill.
-   **Frecuencia inicial:** semanal.
-   **Solape:** 14 días.
-   **Comparadores principales:** 1D / 3D / 7D / 15D.
-   **Keywords custom:** sí.
-   **Fallback SERP:** puntual, separado y opcional.
-   **Jobs/colas:** sí para backfill, sync y fallback.
-   **Tiempo real contra Google para renderizar vistas:** no.

------------------------------------------------------------------------

## 20. Referencias oficiales

-   Google Search Console API --- Search Analytics `query`:
    https://developers.google.com/webmaster-tools/v1/searchanalytics/query
-   Search Console --- Performance report:
    https://support.google.com/webmasters/answer/7576553
-   Search Console --- About Search Console data:
    https://support.google.com/webmasters/answer/96568

------------------------------------------------------------------------

## 21. Próximo paso técnico

Convertir este documento funcional en un spec de implementación con:

1.  migraciones/tablas definitivas;
2.  contratos de servicios/repositorios;
3.  payloads exactos de Search Analytics;
4.  algoritmo de paginación;
5.  jobs y scheduler;
6.  cálculo exacto de variaciones 1D/3D/7D/15D;
7.  endpoints internos;
8.  componentes/pantallas;
9.  tests unitarios e integración;
10. estrategia de reintentos y observabilidad.

Ese spec puede entregarse directamente a un agente de desarrollo para
implementar el módulo por etapas.
