duxt informa de lo que los lectores hacen con un sitio de documentación —
buscar, copiar un comando, probar un endpoint, responder «¿Te ha resultado útil
esta página?» — a **una única función que escribes tú**. No entrega ningún
proveedor, ningún SDK y ningún script, y un sitio que no haya escrito esa
función no informa de nada y no descarga nada extra.

::callout{type="info" title="Apagado hasta que lo enciendas"}
No hay destino por defecto, porque una capa de documentación no puede elegir uno
para los lectores de otra persona. Sin `duxt.analytics.track`, cada punto de
llamada de abajo alcanza una función que no existe y se detiene.
::

## Pasos

::steps

1. **Escribe la función de retorno.** Vive en `app.config.ts`, junto al resto de
   tu configuración de duxt, y recibe un evento por interacción:

   ```ts [app/app.config.ts]
   export default defineAppConfig({
     duxt: {
       analytics: {
         track(event) {
           // `event.name` es el discriminante; `event.context` dice en qué
           // página, locale, fuente y versión ocurrió.
           console.log(event.name, event.context.path);
         }
       }
     }
   });
   ```

   La función de retorno se ejecuta **solo en el navegador**. Una compilación y
   un prerrenderizado no la llaman nunca, así que un renderizado estático no te
   llena la analítica con páginas que abrió un rastreador.

2. **Envíalo adonde ya vive tu analítica.** Lo que exponga tu proveedor — un SDK
   que ya esté en la página, o una ruta propia para que el navegador del lector
   nunca hable con un tercero que no elegiste:

   ```ts [app/app.config.ts]
   export default defineAppConfig({
     duxt: {
       analytics: {
         track(event) {
           const { name, context, ...properties } = event;

           // Tu proveedor, tus nombres de propiedad, tu barrera de
           // consentimiento.
           window.plausible?.(name, { props: { ...context, ...properties } });
         }
       }
     }
   });
   ```

   ::callout{type="warning" title="El consentimiento es tuyo, no de duxt"}
   duxt no pone ninguna cookie, no almacena nada, no acumula nada en búfer y no
   muestra ningún aviso de consentimiento — no tiene nada que preguntar, porque
   nada sale de la página salvo a través de tu función. Eso también significa
   que **la barrera es tuya**: si tu política de privacidad exige consentimiento
   antes de que esto llegue a un proveedor, compruébalo dentro de `track` y sal
   antes de tiempo.
   ::

3. **Elige los eventos que quieres y descarta el resto.** La unión es cerrada,
   así que un `switch` la cubre de forma exhaustiva y lo que no nombres
   simplemente no se envía:

   ```ts [app/app.config.ts]
   track(event) {
     if (event.name === 'search' && event.results === 0) {
       // El único informe que casi siempre quiere un sitio de documentación:
       // un término que alguien buscó y que el sitio no supo responder.
       report('docs-search-miss', { query: event.query, ...event.context });
     }
   }
   ```

4. **Comprueba que llega un evento**, con el contexto que esperabas — bajo un
   prefijo de versión y de locale, no solo en la URL desnuda.

::

## Los eventos

Cada evento tiene un `name`, sus propios campos y un `context`.

| `name` | Se emite cuando | Lleva |
| :--- | :--- | :--- |
| `search` | Se estabiliza una consulta no vacía tras el antirrebote — incluida una que no encontró nada | `query`, `results`, `approximate` |
| `search-result` | Un lector abre un resultado | `query`, `rank`, `to`, `collection` |
| `copy` | Se copia un ejemplo de código propio de la capa | `kind`, `language`, `manager`, `sample` |
| `api-request` | Se asienta una petición de prueba | `operation`, `method`, `path`, `outcome`, `statusClass`, `duration` |
| `feedback` | Un lector responde «¿Te ha resultado útil esta página?» | `helpful` |

`search` se emite **una vez por consulta estabilizada**, no por pulsación: el
antirrebote descarta lo que nadie terminó de escribir, y un resultado al que
otro más nuevo deja obsoleto no se informa nunca, porque el lector no llegó a
verlo.

`copy` cubre los ejemplos de código propios de la capa — un bloque delimitado,
un `::code-group`, un bloque `::package-managers`, una muestra de petición en el
cliente de pruebas — y `kind` dice cuál. **«Copiar la página como Markdown» no
está en esta familia** y no emite nada: es la página, no un ejemplo dentro de
ella.

`api-request` describe la operación **tal y como la declara el documento
OpenAPI**. `path` es la plantilla (`/pets/{id}`), nunca la URL a la que se
llamó.

### El contexto

| Campo | Notas |
| :--- | :--- |
| `path` | La ruta de la documentación, sin el prefijo de locale, la cadena de consulta ni el fragmento |
| `locale` | El locale en el que se leyó la página |
| `source` | De qué fuente salió la página — ausente en un sitio de una sola fuente |
| `version` | Qué versión — ausente donde la fuente solo tiene una |

## Lo que nunca se envía

Esta lista es el sentido de la función, así que conviene decirlo claro. Ningún
evento lleva:

- una credencial, cabecera o token de API;
- un cuerpo de petición o de respuesta, ni una URL de petición concreta;
- el texto que se copió;
- el contenido representado de un resultado de búsqueda;
- un identificador de navegador, de sesión o de lector de ningún tipo;
- la cadena de consulta o el fragmento de la propia ruta de la página.

duxt tampoco **transmite, acumula en búfer ni persiste** nada por su cuenta. En
la capa no hay cola, ni reintento, ni almacenamiento, ni llamada de red: tu
función es lo único que sale de la página.

Nunca se espera a tu función de retorno, y lo que lance o con lo que rechace se
descarta — un proveedor caído no debe llevarse por delante la búsqueda, la
copia, la opinión ni la petición.

## Opiniones de página, por las dos vías

`feedback` es la misma respuesta que `DuxtPageFeedback` ha emitido siempre. El
evento `@feedback` del componente y su slot por defecto no cambian, así que
[envolverlo](/guides/collect-page-feedback) sigue funcionando y ninguna de las
dos vías necesita a la otra — usa la función de retorno de analítica si quieres
un único destino para todo, y la envoltura si la opinión por sí sola va a un
sitio propio.

## Lista de comprobación

- [ ] `duxt.analytics.track` está definido, y no hizo falta nada más para activarlo
- [ ] El destino es tuyo — un proveedor que elegiste, o una ruta propia
- [ ] El consentimiento se comprueba dentro de `track`, si tu política lo pide
- [ ] Llegó un evento con la ruta, el locale, la fuente y la versión que esperabas
- [ ] Nada de lo que reenvías identifica al lector