duxt emite **un** hook. Es un hook de compilación de Nuxt, se dispara una vez
por compilación y te entrega todas las páginas del sitio como registros de
búsqueda planos.

```ts [modules/my-search.ts]
import { defineNuxtModule } from '@nuxt/kit';
import type { DuxtSearchRecord } from '@kirchdev/duxt/search-records';

export default defineNuxtModule({
  setup(_options, nuxt) {
    nuxt.hook('duxt:search:records', async (records) => {
      await upload(records);
    });
  }
});
```

Un ejemplo completo de uno — el esquema de la colección, el upsert, la
eliminación de lo que ya no está y la clave que jamás debe llegar a una página —
está en [Envía tus páginas a Typesense](/add-ons/typesense).

## `duxt:search:records`

```ts
(records: readonly DuxtSearchRecord[]) => void | Promise<void>
```

**Cuándo se dispara.** Después de que Content haya analizado cada fuente, cada
versión, cada idioma y cada sección generada — incluidos los repositorios
remotos que descargó — y **antes** de que Nitro prerenderice o empaquete nada.
Un listener todavía puede escribir un archivo en `public/` y verlo aparecer en
la salida.

**Qué es un registro.** Una página, más uno por cada encabezado que tenga.

| Campo     | Tipo     | Qué contiene                                                               |
| :-------- | :------- | :------------------------------------------------------------------------- |
| `id`      | `string` | La identidad del registro, y una clave primaria válida en cualquier motor  |
| `url`     | `string` | Dónde aterriza quien lee — segmento de idioma y ancla incluidos             |
| `title`   | `string` | El encabezado que cubre, o el título de la página en el primero            |
| `content` | `string` | Las palabras bajo ese encabezado, sin marcado y con espacios normalizados  |
| `source`  | `string` | De qué fuente procede, como segmentos de URL sin la versión                |
| `version` | `string` | La etiqueta de versión, donde la fuente tenga versiones — si no, ausente    |
| `locale`  | `string` | El idioma en que está escrito el **contenido**                             |

`id` es un resumen hexadecimal en lugar de la URL, porque Meilisearch solo
acepta `A-Za-z0-9_-` como identificador de documento. Se deriva de `url`, así
que es igual en cualquier máquina y se mantiene mientras la página siga donde
está — que es justo lo que permite a un proveedor hacer upsert en vez de
reconstruir.

`source` es una **identidad, no una dirección**: `/` para la documentación
servida en la raíz, `/harbour` para un segundo repositorio, `/harbour/api` para
una sección generada dentro de él. La versión queda fuera a propósito, para que
una faceta construida sobre `source` sobreviva a una publicación y `version` a
su lado sea lo que reduce a una sola.

`locale` es el idioma del contenido, no el idioma de interfaz en que alguien
está leyendo. Un sitio con fuentes sin traducir obtiene **un** conjunto de
registros aunque sirva siete idiomas: el mismo párrafo indexado siete veces es
una respuesta repetida.

::callout{type="warning" title="La carga está congelada"}
Construye o sube tu propio índice a partir de ella. No reescribas un registro ni
ordenes el array en su sitio: todos los demás listeners reciben el mismo objeto,
y una edición al pasar la heredaría el siguiente sin que nada lo indique.
::

### Lo que deliberadamente no lleva

- **Ningún extracto.** Un fragmento es una decisión sobre una lista de
  resultados, y la lista de resultados es tuya.
- **Ningún nombre de colección.** Eso es la contabilidad interna del índice de
  duxt, y un documento con esa clave se rompe el día en que una fuente gana un
  idioma.
- **Ninguna etiqueta visible.** `label`, `name` y el rótulo de versión son prosa
  para quien lee, traducida por idioma. Un registro no lo lee nadie.

### Lo que queda fuera del índice

Una página que escribió [`search: false`](/reference/frontmatter) no produce
ningún registro — la misma exclusión que ya respetan el diálogo de búsqueda, la
herramienta MCP `search_docs` y ambos índices llms. Tampoco la produce una
fuente traducida a un idioma que `duxt.locales` no sirve, porque indexarla sería
ofrecer a quien lee una URL que devuelve 404.

Donde un sitio sirve **dos regiones de un mismo idioma** sobre un solo árbol —
`pt-PT` y `pt-BR` leyendo la misma carpeta `pt/` —, los registros apuntan a la
primera. Ambas URL llevan las mismas palabras, así que indexar las dos
duplicaría el índice para decir lo mismo dos veces; un proveedor que quiera
llevar a quien lee a su propia región cambia el segmento, algo que duxt no
puede hacer porque no sabe quién está buscando.

Y cuando nadie escucha, duxt no construye carga alguna: un sitio sin proveedor
no paga nada por que el hook exista.

## Por qué solo hay uno

Una extensión distribuible de duxt es **una capa de Nuxt**, no un objeto plugin:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  extends: ['@kirchdev/duxt', 'duxt-typesense']
});
```

Eso ya lleva componentes, valores por defecto de `app.config`, colecciones de
`content.config.ts`, rutas de servidor, herramientas MCP y un módulo propio, en
un paquete con su propia versión. Una API `duxt.plugins` reimplementaría la
resolución de capas dentro de una capa — y un registro en el que un plugin
escribe al importarse choca contra el mismo muro que dio forma a
[`sectionTypes`](/reference/configuration): `content.config.ts` y el módulo de
duxt los cargan **cargadores distintos**, así que un registro mutable en el que
escribieran ambos serían dos registros. Un hook de compilación de Nuxt no tiene
ese problema, porque solo la mitad del módulo llega a verlo.

Así que la superficie se mantiene pequeña a propósito, y cada una de las otras
vías sigue siendo la respuesta correcta a su propia pregunta:

| Quieres cambiar                        | Usa                                                              |
| :------------------------------------- | :---------------------------------------------------------------- |
| Un color, una tipografía, un espaciado | Un token CSS — [Sobrescribir el tema](/guides/override-the-theme)  |
| Lo que dibuja un componente            | Sombrearlo, o un slot donde exista                                |
| Lo que el sitio dice de sí mismo       | La clave `duxt` de `app.config` — [Configuración](/reference/configuration) |
| Un nuevo tipo de sección generada      | `sectionTypes` — [Fuentes](/reference/sources)                     |
| Lo que una página dibuja a su alrededor | Frontmatter — [Frontmatter](/reference/frontmatter)               |
| Dónde se indexa el texto del sitio     | Este hook                                                         |

::callout{type="tip" title="Pedir un segundo hook"}
El nombre de un hook es superficie pública: renombrarlo es un cambio
incompatible, y uno sin usar ya no se puede retirar nunca. Por eso uno nuevo
llega cuando existe un consumidor que no se puede escribir sin él — no por
adelantado, y no porque una costura parezca que algún día hará falta.
::