Todos los errores que esta capa tuvo realmente fueron silenciosos: una colección
que Content descartó porque su nombre no era un identificador de JavaScript, una
página que daba 404 porque un enlace había quedado obsoleto, una navegación que
volvía vacía. Ninguno de los tres dijo nada — la compilación estaba en verde y
el sitio estaba mal. Así que tras la compilación se ejecutan comprobaciones,
todas leyendo lo mismo: la caché de análisis que Content deja atrás.

| Comprobación                                   | Gravedad |
| :--------------------------------------------- | :------- |
| Una colección sin páginas                       | error    |
| Una carpeta que choca con un segmento de URL    | error    |
| Un `title` o `description` ausente              | aviso    |
| Un enlace interno o ancla roto                  | aviso    |
| Lo que una sección generada no pudo leer        | aviso    |
| Qué lleva cada idioma y qué se ha quedado quieto | nota    |

## Por qué la gravedad no es uniforme

Un error cuesta una página que nadie puede alcanzar y que nada en el sitio
explicaría jamás — una colección vacía es un 404 en cada página de una versión.
Un aviso cuesta calidad: la página sigue representándose, y una fuente remota
puede quedarse obsoleta entre una publicación y la siguiente sin que sea culpa
de esta compilación. Hacer fallar la compilación porque el repositorio de otra
persona haya avanzado convertiría una compilación en verde en algo dependiente
de un tercero.

Una **nota** no cuesta nada: es un estado del sitio, no un defecto en él. Una
página sin traducir no es un error, y reportarla como aviso enseñaría a todo el
mundo a leer por encima de los avisos.

## Secciones generadas

Una [sección generada](/concepts/generated-sections) se comprueba como cualquier
otra colección, en sus propios términos. Lo que llega a este informe es una de
tres cosas:

- el **artefacto no estaba ahí** en esa ref, así que la sección no se construyó;
- el tipo **no leyó nada de él**, así que la sección reclama URL y no sirve
  ninguna;
- el tipo **siguió adelante pese a algo** — un `$ref` irresoluble en un documento
  OpenAPI, un encabezado de changelog que parece una release pero no se analiza
  como tal y que por eso pasó a formar parte de la release anterior.

Las tres son avisos, y no es una concesión. Todo lo que un artefacto **local**
puede tener mal — una ruta que no existe, un archivo que el tipo no puede leer en
absoluto, una opción que no conoce — ya hace fallar la compilación mientras se
carga la configuración, mucho antes de que exista este informe: la configuración
del propio sitio es un error que la compilación no debe arrastrar. Lo que queda
aquí es o bien un artefacto **remoto**, que legítimamente puede haberse quedado
obsoleto entre una publicación y la siguiente, o bien una página que se
representó con algo ausente.

::callout{type="tip" title="Antes se imprimían y se perdían"}
Un artefacto se analiza mientras se carga la configuración, así que un aviso
sobre un `$ref` descartado se imprimía antes de que el servidor de desarrollo
terminara de arrancar y ya se había ido con el scroll para cuando alguien miraba
— mientras que todos los demás hallazgos que produce esta capa esperaban en un
único informe. Ahora llegan por el informe, y con él por el panel
[Comprobaciones](/reference/devtools/checks).
::

## El informe de traducciones

Una línea por idioma, en cada compilación:

```
[duxt] translations
[duxt]   de: 118/121 pages, 3 behind the original
[duxt]     "docs/guides/deploying.md" has no de translation.
[duxt]     "docs/de/concepts/sources.md" has not moved since the original changed.
```

La segunda cifra necesita `history: true` en la fuente: las fechas vienen de
git, y una fuente que no ha pedido su historial no tiene ninguna. Sin él el
informe cuenta la cobertura y no dice nada sobre el desfase, en lugar de
adivinarlo a partir de la ausencia.

Existe por un fallo concreto. OpenCode tradujo su documentación a diecisiete
idiomas con un agente en CI, apagó el flujo de trabajo, y en ningún sitio
constaba que las traducciones hubieran dejado de moverse. La capa ya sabe, por
página, qué idiomas la llevan y cuándo cambió cada archivo por última vez; un
informe es lo que convierte eso en algo que alguien ve.

## El mismo informe, como comando

Los hallazgos viven en dos sitios difíciles de entregarle a nadie: un registro
de compilación que ya se ha ido con el scroll, y unos paneles de devtools que
solo existen dentro de un servidor de desarrollo en marcha. `duxt report` es el
tercero — los mismos datos como Markdown en stdout. Viene con el paquete, así
que extender la capa es todo lo que hace falta para tenerlo.

```bash
pnpm exec duxt report            # Markdown
pnpm exec duxt report --json     # los mismos datos, sin representar
pnpm exec duxt report --help     # qué acepta y con qué código sale
```

Lee el `app.config.ts` del sitio, los artefactos que hay junto a él y las
páginas de la caché de análisis de Content — **sin servidor**, así que se
ejecuta en CI y en una tubería. Sale con `1` ante un error y con `0` ante
avisos, que es la misma regla de gravedad que sigue la compilación, y con `2`
cuando no ha entendido la línea de órdenes — una afirmación sobre tu invocación,
no sobre tu documentación.

Tres paneles no tienen equivalente aquí, y a propósito: el índice de búsqueda,
el depurador de rutas y el listado de caché son preguntas sobre un proceso en
marcha, y responderlas desde fuera sería adivinar.

::callout{type="warning" title="Informa del último análisis, no de este segundo"}
Las páginas vienen de la caché de Content, así que una página escrita después de
la última compilación o ejecución de desarrollo no está en ella. Ejecuta una de
las dos primero — el informe lo dice claramente cuando no encuentra caché
alguna, en lugar de reportar todas las colecciones como vacías.
::

## Lo que las comprobaciones saben y tú no

- **Una colisión de carpetas** solo es un problema mientras un prefijo está
  activo. Una carpeta de documentación llamada `v2` está bien en un sitio sin
  versiones e inalcanzable en uno versionado, porque gana el prefijo. La
  compilación sabe cuál es el caso, y el mensaje nombra el archivo y ofrece la
  solución.
- **Un enlace se resuelve como lo resuelve el tema.** Una ruta absoluta escrita
  en una página es relativa a la fuente de esa misma página, así que se prueba
  primero bajo el prefijo de la fuente y después desnuda. Un verificador que
  solo hiciera la forma desnuda informaría de todos los enlaces correctos de un
  sitio multirrepositorio.
- **Un enlace se resuelve en su propio idioma.** Todos los idiomas de una fuente
  sirven rutas de contenido idénticas — la locale va delante de la URL, no en el
  árbol de contenido — y las anclas se derivan del texto del encabezado, así que
  un encabezado alemán tiene un ancla alemana. Una comprobación que resolviera
  solo por la ruta contrastaría un enlace alemán con encabezados ingleses y
  reportaría cada ancla correcta de un sitio traducido. Un enlace a una página
  que un idioma no lleva recurre al original, que es lo que hace el sitio.
- **Las URL externas no se comprueban.** Verificar una metería la red en la
  compilación, y un host ajeno inaccesible no es motivo para que tu
  documentación no se publique.

## Escribe los enlaces como rutas absolutas de documentación

`/concepts/sources`, no `../2.concepts/2.sources.md`. El prefijo bajo el que se
sirve una página se decide al compilar, así que escribir rutas sin él es lo que
permite que el mismo Markdown sirva `/guides/deploying` en un sitio y
`/acme/v2.0.0/guides/deploying` en otro. Es también la única forma que ve el
verificador de enlaces.