# Feature Specification: Sincronización de sitemaps y monitoreo de cobertura de indexación

**Feature Branch**: `001-gsc-sitemap-coverage`

**Created**: 2026-08-10

**Updated**: 2026-08-10 — se incorporan la configuración guiada del acceso a Google (US1) y el
recorrido guiado de primera vez (US4); las historias previas se renumeran.

**Status**: Draft

**Input**: User description: "MVP de index-relay: plataforma que sincroniza los sitemaps de un
sitio con Google Search Console y monitorea diariamente el estado de indexación de sus URLs, sin
que el dueño del sitio tenga que entrar a la interfaz de Google. Debe poder usarla alguien que no
sabe qué es Google Cloud: la plataforma lo acompaña desde el ingreso hasta el primer lote
encolado."

## User Scenarios & Testing *(mandatory)*

### User Story 1 - Conectar Google sin saber qué es Google Cloud (Priority: P1)

Una persona que nunca entró a Google Cloud necesita dejar la plataforma conectada a sus
propiedades. Entra a la sección de configuración, ve que el acceso a Google está sin configurar y
encuentra una guía dentro de la propia pantalla que la lleva paso a paso: crear un proyecto en
Google Cloud y pegar su identificador, habilitar la API de Search Console, crear una cuenta de
servicio, descargar su archivo de clave y subirlo. Después la plataforma le muestra la dirección
de esa cuenta de servicio y le explica dónde pegarla dentro de Search Console y con qué permiso.
Cada paso tiene un botón que comprueba de verdad si quedó bien hecho, y le dice qué falta cuando
no.

**Why this priority**: sin credencial no hay producto, y es el punto donde se pierde un usuario
que no conoce Google Cloud. Es el primer obstáculo real y el más caro de resolver por soporte.

**Independent Test**: se puede probar entregándole la plataforma a alguien que nunca usó Google
Cloud y verificando que llega a tener la credencial cargada y verificada sin ayuda externa. No
depende de que existan dominios, sitemaps ni inspecciones.

**Acceptance Scenarios**:

1. **Given** una cuenta sin credencial cargada, **When** entra a la configuración, **Then** ve el
   estado "sin configurar", la guía paso a paso y ninguna funcionalidad de dominios habilitada.
2. **Given** el paso de identificador de proyecto, **When** pega un identificador con formato
   inválido, **Then** se le indica el error antes de continuar, sin llamar a Google.
3. **Given** el paso de carga de la clave, **When** sube un archivo que no es una clave de cuenta
   de servicio válida, **Then** se rechaza indicando qué se esperaba, y nada se guarda.
4. **Given** una clave válida cargada, **When** se guarda, **Then** se almacena cifrada, se
   muestra la dirección de la cuenta de servicio y una huella que permite identificarla, y el
   contenido de la clave **no** vuelve a mostrarse nunca.
5. **Given** una credencial cargada pero cuya API de Search Console no fue habilitada en el
   proyecto, **When** pulsa el botón de comprobación, **Then** se le informa exactamente ese
   motivo y el enlace para habilitarla, en vez de un error genérico.
6. **Given** una credencial verificada, **When** la reemplaza por otra, **Then** los dominios
   existentes siguen asociados y sólo cambia la credencial con la que se los consulta.
7. **Given** la sección de configuración, **When** la consulta, **Then** ve los módulos de la
   plataforma y qué credencial tiene asignada cada uno; en el primer corte existe únicamente el
   módulo de Search Console.

---

### User Story 2 - Ver la cobertura real de un sitio grande (Priority: P1)

El operador de un sitio con miles de páginas quiere saber, sin abrir Search Console, cuáles de
sus URLs están indexadas y cuáles no, y por qué motivo. Da de alta el dominio, la plataforma
comprueba que la cuenta de servicio tiene acceso a esa propiedad, lee sus sitemaps para descubrir
las URLs y empieza a inspeccionarlas. En el tablero ve un resumen por estado —indexada,
descubierta sin rastrear, duplicada con canónica distinta, excluida por noindex, error de
servidor, bloqueada— y puede filtrar y exportar las URLs problemáticas.

**Why this priority**: es el valor central y lo único que no se puede obtener a mano en un sitio
grande, porque la interfaz de Google inspecciona de a una URL por vez.

**Independent Test**: con una credencial ya verificada, dar de alta un dominio y confirmar que el
tablero muestra los estados de cobertura fechados. No depende de la sincronización de sitemaps,
ni de las notificaciones, ni del recorrido guiado.

**Acceptance Scenarios**:

1. **Given** un dominio recién dado de alta, **When** el operador consulta su ficha, **Then** ve
   la dirección de la cuenta de servicio que debe autorizar y el estado "esperando acceso".
2. **Given** un dominio cuya propiedad ya autorizó a la cuenta de servicio, **When** pide
   comprobar el acceso, **Then** la plataforma confirma que puede leer la propiedad y el dominio
   queda operativo.
3. **Given** un dominio cuya propiedad **no** autorizó a la cuenta de servicio, **When** se
   comprueba el acceso, **Then** se informa explícitamente la falta de permiso, distinguiéndola
   de una propiedad inexistente y de un fallo temporal.
4. **Given** un dominio operativo con un sitemap que declara 5.000 URLs, **When** finaliza el
   primer ciclo de inspección diaria, **Then** el tablero muestra cuántas URLs se inspeccionaron,
   cuántas quedan pendientes de la primera pasada y el estado de cada una con su fecha de
   obtención.
5. **Given** un conjunto de URLs ya inspeccionadas, **When** filtra por "no indexadas", **Then**
   obtiene la lista con el motivo declarado por Google y puede exportarla.
6. **Given** una URL cuyo estado nunca pudo obtenerse porque se agotó el presupuesto diario,
   **When** la consulta, **Then** se muestra como "sin datos todavía" y nunca como indexada o no
   indexada.

---

### User Story 3 - Que el sitemap se sincronice solo después de cada deploy (Priority: P2)

El responsable técnico no quiere volver a la interfaz de Google cada vez que publica cambios.
Desde su pipeline de despliegue llama a la API pública para avisar que el sitio cambió; la
plataforma vuelve a leer los sitemaps declarados, detecta las diferencias y reenvía a Search
Console sólo los que cambiaron, dejando registro de qué se envió y cuándo.

**Why this priority**: es el dolor operativo que originó el producto, pero requiere que el
dominio ya esté operativo (US2).

**Independent Test**: llamar al endpoint de sincronización con una clave de API sobre un dominio
operativo y confirmar que queda registrado el reenvío y su resultado.

**Acceptance Scenarios**:

1. **Given** un dominio operativo, **When** registra la URL de su sitemap índice, **Then** la
   plataforma descubre los sitemaps hijos y los lista con su cantidad de URLs y su fecha de
   última modificación.
2. **Given** un sitemap ya registrado, **When** el pipeline invoca la sincronización, **Then** se
   reenvían los sitemaps cuyo contenido cambió y se omiten los que no, informando ambas cosas.
3. **Given** un sitemap con error de descarga o contenido inválido, **When** se sincroniza,
   **Then** la operación falla con el motivo y no se reporta ningún envío exitoso.
4. **Given** una llamada repetida por reintento del pipeline, **When** se procesa, **Then** no se
   generan envíos duplicados ni se consume cuota adicional.

---

### User Story 4 - Recorrido guiado de primera vez (Priority: P2)

Alguien que entra por primera vez no sabe por dónde empezar. La plataforma le ofrece un recorrido
paso a paso que hilvana todo el camino: conectar Google, dar de alta el primer dominio,
autorizar la cuenta de servicio en esa propiedad, registrar el sitemap y lanzar el primer lote de
inspección. Cada paso comprueba de verdad su resultado antes de habilitar el siguiente y explica
qué falta cuando algo no está. Puede abandonarlo en cualquier momento y usar los formularios
completos directamente, y puede retomarlo después desde donde lo dejó.

**Why this priority**: es lo que convierte una herramienta para expertos en una que puede usar
alguien sin conocimientos previos. Se entrega junto al MVP, pero necesita que existan los pasos
que va a hilvanar (US1, US2 y US3).

**Independent Test**: entrar con una cuenta nueva y llegar hasta el primer lote encolado sin salir
del recorrido; luego repetir abandonándolo a mitad y comprobar que el modo directo funciona y que
el recorrido se puede retomar en el mismo punto.

**Acceptance Scenarios**:

1. **Given** una cuenta que nunca completó el recorrido, **When** ingresa, **Then** se le ofrece
   el recorrido guiado y también la opción de omitirlo.
2. **Given** un recorrido en curso, **When** abandona la sesión y vuelve más tarde, **Then**
   retoma en el mismo paso, con los pasos ya cumplidos marcados.
3. **Given** un paso cuyo requisito no está cumplido, **When** intenta avanzar, **Then** no se
   habilita el siguiente y se le indica exactamente qué falta y dónde resolverlo.
4. **Given** un usuario que omitió el recorrido, **When** usa los formularios completos, **Then**
   dispone de toda la funcionalidad sin restricción, y el recorrido queda disponible para
   retomarlo.
5. **Given** un recorrido completado, **When** vuelve a ingresar, **Then** no se le vuelve a
   ofrecer y accede directamente al tablero.
6. **Given** cualquier paso del recorrido, **When** lo consulta, **Then** encuentra la guía de lo
   que debe hacer del lado de Google, con los nombres exactos de las pantallas y opciones que va
   a encontrar allí.

---

### User Story 5 - Enterarse de los problemas sin entrar a la plataforma (Priority: P3)

La plataforma inspecciona automáticamente un lote de URLs por día, agrupa el trabajo en lotes
visibles con su progreso, y manda un correo cuando un lote termina y cuando aparece un cambio de
cobertura relevante, por ejemplo URLs que estaban indexadas y dejaron de estarlo.

**Why this priority**: convierte una herramienta de consulta en un servicio de vigilancia, pero
no es imprescindible para demostrar valor.

**Independent Test**: forzar un lote sobre un dominio monitoreado y verificar que reporta progreso
y que se envía el correo al terminar.

**Acceptance Scenarios**:

1. **Given** un dominio monitoreado, **When** arranca el ciclo diario, **Then** se crea un lote
   visible con su cantidad de URLs, su progreso y su estado.
2. **Given** un lote en curso, **When** lo consulta, **Then** ve cuántas URLs se procesaron,
   cuántas fallaron y cuánto presupuesto diario queda.
3. **Given** un lote que termina, **When** se completa, **Then** se envía un correo con el
   resumen y el enlace al detalle.
4. **Given** una URL que figuraba como indexada, **When** una inspección posterior la reporta
   como no indexada, **Then** el cambio se registra y entra en la alerta de cobertura.
5. **Given** un dominio con las notificaciones desactivadas, **When** ocurren cambios, **Then** no
   se envían correos por ese dominio.

---

### User Story 6 - Operar la plataforma desde adentro (Priority: P4)

Quien opera la plataforma necesita ver las cuentas, revisar el consumo de cuota de un dominio,
revocar el acceso a una propiedad y cerrar sesiones activas ante una sospecha de compromiso.

**Why this priority**: necesario para operar y responder a incidentes, sin valor directo para el
uso diario.

**Independent Test**: ejecutar cada acción administrativa sobre una cuenta de prueba y verificar
su efecto y su registro de auditoría.

**Acceptance Scenarios**:

1. **Given** un administrador autenticado, **When** busca una cuenta, **Then** ve sus dominios,
   su consumo del período y el estado de acceso de cada propiedad.
2. **Given** una cuenta con sesiones abiertas, **When** cierra sus sesiones, **Then** el usuario
   debe volver a autenticarse en todos sus dispositivos.
3. **Given** un dominio operativo, **When** revoca su acceso, **Then** el monitoreo se detiene y
   su estado pasa a "acceso revocado", conservando el historial.
4. **Given** cualquier acción administrativa, **When** se ejecuta, **Then** queda registrada con
   autor, fecha y motivo.

---

### Edge Cases

- **La clave subida es de otro tipo**: un archivo de credenciales de aplicación de escritorio o
  una API key en vez de una clave de cuenta de servicio debe rechazarse indicando exactamente qué
  archivo se esperaba, porque es la confusión más frecuente.
- **La API de Search Console no está habilitada en el proyecto**: la comprobación debe
  identificar ese caso concreto y ofrecer dónde habilitarla, en vez de reportar un fallo genérico.
- **El identificador de proyecto no corresponde a la clave subida**: se detecta al comprobar y se
  informa la discrepancia.
- **La clave se revoca desde Google Cloud**: las operaciones se detienen con un estado claro y se
  pide cargar una credencial nueva, sin borrar historial.
- **El sitio tiene más URLs que el presupuesto diario**: se muestra el tiempo estimado del ciclo
  completo y nunca se presenta una cobertura parcial como total.
- **Se quita a la cuenta de servicio de una propiedad**: el dominio pasa a "acceso perdido" con
  una acción para volver a comprobarlo.
- **La propiedad existe con otra forma**: un sitio puede estar registrado como propiedad de
  dominio o como prefijo de URL; se resuelve cuál corresponde y se informa si ninguna coincide.
- **Google responde con límite excedido**: se reencola con espera creciente y el lote queda
  parcial, nunca completo.
- **El sitemap declara URLs de otro dominio**: se ignoran y se informan como fuera de alcance.
- **El sitemap está comprimido o excede 50.000 URLs por archivo**: se procesa el formato
  comprimido y se respetan los límites del estándar.
- **Una URL desaparece del sitemap**: deja de inspeccionarse pero conserva su último estado y su
  historial, marcada como retirada.
- **El usuario abandona el recorrido a mitad de un paso**: lo ya hecho queda guardado; no se
  pierde ni se duplica al retomar.

## Requirements *(mandatory)*

### Functional Requirements

**Cuentas y acceso a la plataforma**

- **FR-001**: El sistema MUST permitir iniciar sesión con correo electrónico y contraseña.
- **FR-002**: El sistema MUST permitir ver las sesiones activas y cerrarlas.
- **FR-003**: El sistema MUST modelar la pertenencia de cada credencial, dominio, sitemap, lote y
  clave de API a una cuenta, y MUST impedir el acceso a datos de otra cuenta.

**Configuración del acceso a Google**

- **FR-004**: El sistema MUST ofrecer una sección de configuración que muestre el estado de la
  conexión con Google y, cuando no esté configurada, la guía para resolverlo.
- **FR-005**: El sistema MUST guiar la creación del proyecto en Google Cloud, la habilitación de
  la API de Search Console, la creación de la cuenta de servicio y la descarga de su clave, con
  los nombres exactos de las pantallas y opciones que el usuario va a encontrar allí.
- **FR-006**: La guía MUST indicar que se requiere una **cuenta de servicio con su archivo de
  clave**, y MUST advertir explícitamente que una API key de Google no sirve para este fin.
- **FR-007**: El sistema MUST validar el identificador de proyecto por formato antes de cualquier
  llamada externa, y MUST validar que el archivo subido es una clave de cuenta de servicio bien
  formada antes de guardarlo.
- **FR-008**: El sistema MUST almacenar el material secreto de la credencial **cifrado**, con la
  clave de cifrado gestionada fuera del repositorio. El identificador de proyecto y la dirección
  de la cuenta de servicio se guardan en claro por no ser secretos.
- **FR-009**: El sistema MUST NOT volver a mostrar el contenido de la clave después de guardarla,
  y MUST mostrar en su lugar una huella que permita identificar de cuál se trata.
- **FR-010**: El sistema MUST ofrecer una comprobación de la credencial que distinga, como
  mínimo: clave inválida o revocada, API de Search Console no habilitada en el proyecto,
  discrepancia entre el proyecto declarado y la clave, y ausencia de propiedades accesibles.
- **FR-011**: El sistema MUST mostrar la dirección de la cuenta de servicio y explicar que debe
  agregarse en Search Console con permiso de **propietario**, advirtiendo que un permiso menor no
  habilita la inspección de URLs.
- **FR-012**: El sistema MUST permitir reemplazar la credencial sin perder los dominios ni el
  historial asociados.
- **FR-013**: El sistema MUST organizar las credenciales por módulo de la plataforma, con una
  credencial activa por módulo y cuenta. En el primer corte existe únicamente el módulo de Search
  Console; el modelo MUST admitir módulos adicionales sin migración, incluida la posibilidad de
  que la misma credencial sirva a varios módulos.
- **FR-014**: El sistema MUST admitir un único proyecto de Google por cuenta en el primer corte, y
  MUST NOT impedir por modelo la existencia de varios más adelante.
- **FR-054**: Cada paso de la guía MUST mostrar un **ejemplo del dato que el usuario debe traer**,
  con su forma reconocible: cómo se ve un identificador de proyecto, cómo se ve la dirección de
  una cuenta de servicio, y cómo se ve el archivo de clave.
- **FR-055**: El paso de carga de la clave MUST mostrar un ejemplo anonimizado del archivo
  esperado, resaltando los campos que permiten reconocerlo —el tipo `service_account`, el
  identificador de proyecto, el identificador de la clave y la dirección de la cuenta de
  servicio—, de modo que el usuario pueda confirmar que descargó el archivo correcto **antes** de
  subirlo. El ejemplo MUST NOT contener material secreto real.
- **FR-056**: Cada paso de la guía MUST indicar tanto el objetivo del paso como la ruta concreta
  dentro de las pantallas de Google donde encontrar el dato, para que siga siendo útil aunque
  Google cambie la disposición de su interfaz.

**Recorrido guiado**

- **FR-015**: El sistema MUST detectar que una cuenta nunca completó la puesta en marcha y
  ofrecerle un recorrido guiado que abarque desde la conexión con Google hasta el primer lote
  encolado.
- **FR-016**: El recorrido MUST persistir su progreso, MUST poder retomarse en el mismo paso y
  MUST poder omitirse, quedando disponible para retomarlo después.
- **FR-017**: Cada paso del recorrido MUST comprobar su requisito de forma efectiva antes de
  habilitar el siguiente, y MUST indicar qué falta y dónde resolverlo cuando no se cumple.
- **FR-018**: Omitir el recorrido MUST NOT restringir ninguna funcionalidad: los formularios
  completos MUST ofrecer las mismas operaciones en una sola pantalla por cada objeto.
- **FR-019**: El recorrido MUST ser idempotente respecto de los objetos que crea: retomarlo no
  duplica dominios, sitemaps ni lotes.

**Dominios y acceso a la propiedad**

- **FR-020**: El sistema MUST permitir dar de alta un dominio indicando su dirección y la forma de
  su propiedad en Search Console.
- **FR-021**: El sistema MUST comprobar el acceso a la propiedad antes de habilitar operaciones, y
  MUST distinguir falta de permiso, propiedad inexistente y error temporal del proveedor.
- **FR-022**: El sistema MUST revalidar periódicamente el acceso, MUST detener las operaciones
  sobre un dominio cuyo acceso se perdió y MUST notificarlo, conservando el historial.
- **FR-023**: El sistema MUST exponer el estado de cada dominio dentro de un conjunto acotado
  (esperando acceso, operativo, acceso perdido, acceso revocado, suspendido).

**Sitemaps**

- **FR-024**: El sistema MUST permitir registrar uno o más sitemaps por dominio y MUST descubrir
  los sitemaps hijos declarados en un índice.
- **FR-025**: El sistema MUST leer los sitemaps, extraer sus URLs y detectar diferencias respecto
  de la última lectura, informando altas, bajas y modificaciones.
- **FR-026**: El sistema MUST reenviar a Search Console únicamente los sitemaps cuyo contenido
  cambió desde el último envío exitoso, registrando fecha y resultado.
- **FR-027**: El sistema MUST rechazar de forma explícita, con el motivo, los sitemaps
  inaccesibles, mal formados o con URLs ajenas al dominio.

**Monitoreo de cobertura**

- **FR-028**: El sistema MUST consultar el estado de indexación de las URLs y MUST persistir, por
  URL, el estado informado, el motivo declarado y la fecha de obtención.
- **FR-029**: El sistema MUST distinguir entre una URL con estado conocido y una sin consultar, y
  MUST NOT presentar una URL sin datos como indexada o no indexada.
- **FR-030**: El sistema MUST respetar un presupuesto máximo de inspecciones por día por dominio,
  configurable y fijado por defecto en 2.000, reservando una porción para el uso manual.
- **FR-031**: El sistema MUST priorizar qué URLs inspecciona cuando el dominio excede el
  presupuesto, y MUST exponer el criterio y el tiempo estimado del ciclo completo.
- **FR-032**: El sistema MUST agrupar el trabajo en lotes con estado y progreso consultables, y
  MUST marcar como parcial todo lote que no haya podido completarse.
- **FR-033**: El sistema MUST permitir filtrar y exportar las URLs por estado y por motivo.
- **FR-034**: El sistema MUST registrar el historial de cambios de estado por URL.

**Notificaciones**

- **FR-035**: El sistema MUST enviar un correo cuando un lote finaliza, con su resultado y el
  enlace al detalle.
- **FR-036**: El sistema MUST enviar un correo ante un cambio de cobertura relevante: pérdida de
  indexación, errores de servidor o bloqueos de rastreo.
- **FR-037**: El sistema MUST permitir activar o desactivar las notificaciones por dominio, y MUST
  agrupar las alertas para no enviar más de un correo por dominio y por día.

**API pública y documentación**

- **FR-038**: El sistema MUST exponer una API pública autenticada por clave que permita, como
  mínimo: gestionar la credencial de un módulo y comprobarla, dar de alta y consultar dominios,
  comprobar el acceso a una propiedad, registrar sitemaps, disparar la sincronización, encolar
  lotes, consultar el estado de un lote y consultar la cobertura.
- **FR-039**: Toda operación de negocio disponible en la interfaz MUST estar disponible también en
  la API pública.
- **FR-040**: El sistema MUST publicar documentación navegable y actualizada, con ejemplos de uso
  desde un pipeline de despliegue.
- **FR-041**: El sistema MUST permitir crear, nombrar, listar y revocar claves de API de la
  plataforma, mostrando su fecha de último uso. Estas claves se almacenan **hasheadas** y su valor
  completo se muestra una única vez.
- **FR-042**: Las operaciones que consumen cuota o generan trabajo MUST ser idempotentes ante
  reintentos.
- **FR-043**: Los errores de la API MUST ser estructurados, con código estable y, cuando
  corresponda, el presupuesto alcanzado.

**Administración interna**

- **FR-044**: El sistema MUST ofrecer un panel interno para consultar cuentas, dominios, consumo
  del período y estado de acceso.
- **FR-045**: El panel interno MUST permitir revocar el acceso a un dominio y cerrar las sesiones
  de una cuenta.
- **FR-046**: Toda acción administrativa MUST quedar registrada con autor, fecha y motivo.

**Honestidad del reporte**

- **FR-047**: El sistema MUST NOT presentar como estado de indexación ningún dato que no provenga
  de una consulta a Search Console, y MUST mostrar su fecha de obtención.
- **FR-048**: El sistema MUST NOT afirmar, en su interfaz, sus correos, su guía o su
  documentación, que una URL será indexada ni en cuánto tiempo.

**Capacidades cableadas y apagadas**

- **FR-049**: El sistema MUST modelar planes, límites y suscripción desde el primer día, con un
  plan sin límites por defecto.
- **FR-050**: El sistema MUST invocar un punto de verificación de límites en cada operación que en
  el futuro estará limitada, aunque hoy autorice siempre.
- **FR-051**: El sistema MUST controlar por configuración el encendido de la facturación y de la
  prueba de propiedad por registro TXT, ambas apagadas por defecto, y MUST NOT exponer ninguna
  capacidad apagada.
- **FR-052**: El sistema MUST registrar el consumo por cuenta y por período desde el primer día.
- **FR-053**: El modelo de datos MUST admitir más de una credencial por cuenta y más de un módulo,
  para no requerir migración al incorporar acceso delegado por usuario u otros servicios.

**Cierre de huecos detectados en el diseño de interfaz**

Requisitos incorporados tras el análisis de experiencia de uso de las doce vistas. Cada uno
completa un requisito ya aprobado que había quedado sin contrato, sin modelo o sin pantalla; el
detalle de cada hallazgo está en [ux-checklist.md](./ux-checklist.md), sección *Huecos detectados*.

- **FR-057**: El sistema MUST permitir editar la configuración de un dominio —su interruptor de
  notificaciones y su presupuesto diario de inspección—, tanto desde la interfaz como desde la API.
  Sin esto, FR-030 y FR-037 no tienen forma de ejercerse.
- **FR-058**: La exportación de cobertura MUST tener definido su formato, sus filtros aplicables y
  su comportamiento según el volumen: descarga inmediata por debajo de un umbral, y generación
  asíncrona con aviso por encima. Un sitio de cientos de miles de URLs no puede resolverse con una
  descarga directa.
- **FR-059**: El sistema MUST registrar por cada sesión su agente de usuario, su dirección de
  origen y su última actividad, y MUST exponerlos. Sin esos datos, la decisión de cuál sesión
  cerrar es una apuesta.
- **FR-060**: El sistema MUST permitir consultar el historial de estados de una URL concreta, con
  cada cambio y su fecha de obtención. Es la única forma de verificar en pantalla el caso que
  dispara una alerta: una URL que estaba indexada y dejó de estarlo.
- **FR-061**: El sistema MUST exponer la fecha de revocación de una clave de API y MUST seguir
  listando las claves revocadas, separadas y sin acciones. Es el dato que se busca después de un
  incidente.
- **FR-062**: El listado de dominios MUST incluir, por dominio, un resumen de cobertura con su
  denominador —cuántas URLs tienen dato sobre el total— y la fecha del último ciclo, para poder
  decidir a cuál entrar sin recorrerlos uno por uno.
- **FR-063**: El sistema MUST definir un único mecanismo de actualización del trabajo en curso,
  con su frecuencia y su condición de corte, y MUST dejar de consultar cuando el lote alcanza un
  estado terminal o la pestaña deja de estar visible.
- **FR-064**: El sistema MUST mostrar un aviso de nivel cuenta, presente en toda la interfaz,
  cuando la credencial deje de servir o cuando haya dominios con acceso perdido, con su acción de
  resolución. Un dominio no puede presentarse como operativo si la credencial de la cuenta ya no
  funciona.
- **FR-065**: El sistema MUST declarar la zona horaria en la que presenta las fechas y MUST
  mostrarla junto a los datos fechados. Toda la honestidad del producto se apoya en la fecha de
  obtención; sin zona declarada, ese dato es ambiguo.

### Key Entities

- **Cuenta**: identidad del usuario; correo, credenciales propias, sesiones activas, plan
  asignado y progreso de puesta en marcha.
- **Proyecto de Google**: proyecto de Google Cloud declarado por la cuenta; su identificador no es
  secreto.
- **Módulo**: capacidad de la plataforma que requiere credencial. En el primer corte sólo existe
  el de Search Console.
- **Credencial**: clave de cuenta de servicio asociada a un proyecto y asignada a uno o más
  módulos. Guarda cifrado su material secreto, y en claro la dirección de la cuenta de servicio y
  una huella para identificarla. Incluye su estado de verificación y el motivo del último fallo.
- **Progreso de puesta en marcha**: paso actual del recorrido guiado, pasos cumplidos y si fue
  omitido o completado.
- **Dominio**: sitio a gestionar; forma de propiedad, estado de acceso, presupuesto de inspección
  y configuración de notificaciones.
- **Sitemap**: archivo declarado o descubierto; ubicación, tipo, cantidad de URLs, huella de
  contenido y fecha del último envío exitoso.
- **URL**: dirección de un dominio descubierta por sitemap; incluye si sigue presente en él.
- **Registro de cobertura**: estado de indexación de una URL en un momento dado, con el motivo
  declarado por Google y su fecha de obtención.
- **Lote**: agrupación de trabajo con su origen, progreso, resultado y consumo de presupuesto.
- **Presupuesto de inspección**: cupo diario por dominio, con lo consumido, lo reservado y lo
  restante.
- **Plan** y **Registro de consumo**: límites asignables y consumo acumulado, aunque no se cobre.
- **Clave de API**: credencial de integración de la cuenta, almacenada hasheada.
- **Notificación**: correo enviado, con tipo, dominio asociado y estado de entrega.
- **Registro de auditoría**: acción administrativa con autor, fecha y motivo.

## Alcance postergado *(cableado y apagado)*

Según el principio VI de la constitución, lo diferido se modela y se cablea apagado, nunca se
omite.

| Capacidad | Motivo de la postergación | Qué queda cableado |
|---|---|---|
| Planes de pago y cobro | Sin uso real no hay datos para dimensionar escalones ni precio | Modelo de plan y límites, punto de verificación que siempre autoriza, registro de consumo |
| Límites por nivel gratuito | Misma razón | Los mismos puntos de verificación, con plan sin límites por defecto |
| Prueba de propiedad por registro TXT | Sin cobro por dominio, la autorización en Search Console prueba la propiedad de forma más fuerte | Campos de verificación en el dominio y su comprobación, apagados |
| Acceso delegado por usuario final | Agrega pantalla de consentimiento y revisión de la aplicación ante Google | Modelo de múltiples credenciales por cuenta y por módulo |
| Módulos adicionales de Google | Fuera del alcance del primer corte | Modelo de módulos y su asignación de credencial |
| Varios proyectos de Google por cuenta | Innecesario para el primer corte | El modelo no lo impide; la restricción vive en la validación |
| Envío por Indexing API | Su alcance oficial se limita a ofertas de empleo y transmisiones en vivo | Nada. Prohibido por el principio I |
| IndexNow y otros buscadores | Fuera del alcance declarado | Nada |

## Success Criteria *(mandatory)*

### Measurable Outcomes

- **SC-001**: Una persona que nunca usó Google Cloud deja la credencial cargada y verificada
  siguiendo únicamente la guía de la plataforma, en menos de 15 minutos y sin ayuda externa.
- **SC-002**: Ninguna comprobación de credencial devuelve un error genérico: los cuatro fallos de
  FR-010 se distinguen entre sí con una acción concreta cada uno.
- **SC-003**: El usuario pasa de ingresar por primera vez a tener su primer lote encolado en menos
  de 30 minutos, contando el tiempo dedicado a las pantallas de Google.
- **SC-004**: Para un sitio de 5.000 URLs, se obtiene la lista completa de URLs no indexadas con
  su motivo dentro de los 3 días de dado de alta el dominio.
- **SC-005**: El 100% de los estados de cobertura mostrados provienen de una consulta registrada a
  Search Console y llevan su fecha de obtención visible.
- **SC-006**: El sistema nunca supera el cupo diario de un dominio; en 30 días de operación
  continua no se registra ningún bloqueo por exceso de consultas atribuible a la plataforma.
- **SC-007**: Una sincronización disparada desde un pipeline devuelve su resultado en menos de 30
  segundos para un índice de hasta 50 archivos.
- **SC-008**: Reintentar cualquier operación de la API tres veces produce el mismo resultado que
  ejecutarla una vez.
- **SC-009**: Retomar el recorrido guiado tras abandonarlo no crea objetos duplicados en ningún
  paso.
- **SC-010**: Ante la pérdida de acceso a una propiedad, el usuario recibe aviso y puede
  restablecerla en menos de 2 minutos conservando el historial.
- **SC-011**: Un desarrollador que nunca vio la plataforma integra la sincronización a su pipeline
  usando solo la documentación pública, en menos de 30 minutos.
- **SC-012**: El material secreto de una credencial no aparece nunca en pantallas, respuestas de
  la API, correos ni registros, después de haberse guardado.
- **SC-013**: Ninguna pantalla, correo o documento afirma que la plataforma indexará una URL o
  acelerará su indexación, ni menciona una capacidad apagada.
- **SC-014**: Encender la facturación en una versión posterior no requiere migrar datos existentes
  ni modificar los flujos de alta ni de encolado: sólo cambiar configuración y cargar límites.
- **SC-015**: Tras 30 días de uso real, el registro de consumo permite responder con datos cuántas
  URLs y cuántas inspecciones diarias consume un sitio típico.

## Assumptions

- **Fuente de verdad**: el estado de indexación proviene exclusivamente de las consultas de
  inspección de URL de Search Console, que son de solo lectura. La plataforma no puede forzar la
  indexación de una página común y no lo promete.
- **Tipo de credencial**: se usa una cuenta de servicio, no acceso delegado por usuario ni API
  key. Una API key de Google no da acceso a datos privados de una propiedad.
- **Permiso requerido**: la cuenta de servicio debe figurar como propietario de la propiedad en
  Search Console; con permisos menores la inspección de URLs no funciona.
- **Cifrado**: el material de la clave se cifra de forma reversible porque debe usarse para firmar
  cada llamada. El hash sólo se aplica a las claves de API de la plataforma, que únicamente se
  comparan.
- **Cupo de inspección**: se asume el límite publicado de 2.000 consultas por día por propiedad,
  modelado como configuración.
- **Criterio de priorización**: primero las URLs nunca inspeccionadas, luego las problemáticas,
  luego rotación por antigüedad del dato.
- **Frecuencia**: un ciclo automático por día por dominio.
- **Retención**: el historial de cobertura se conserva 12 meses.
- **Cambio relevante**: paso de indexada a no indexada, errores de servidor y bloqueos de rastreo.
- **Guía de Google**: las pantallas de Google Cloud y Search Console cambian con el tiempo, así
  que la guía se escribe describiendo el objetivo de cada paso además del camino exacto, y su
  contenido vive en un solo lugar para poder actualizarlo sin tocar el flujo.
- **Idioma**: interfaz, guía y correos en español para el primer corte.
- **Escala del primer corte**: decenas de dominios y cientos de miles de URLs acumuladas.
- **Registro de cuentas**: en el primer corte las cuentas se crean desde el panel interno.
