# Contratos

> Deriva un JSON Schema de una respuesta que ya funciona y todas las respuestas posteriores se miden contra esa forma — en el panel de respuesta, en el runner de colecciones y por MCP.

## Captúralo, no lo escribas

Nadie escribe un JSON Schema a mano, y por eso el contract testing casi nunca empieza. Mariner invierte el gesto:

1. Envía la petición mientras la API se porta bien.
2. Abre la pestaña **Contract** del editor de peticiones — entre Variables y Post-Response — y pulsa **Derive from response**.
3. Ese es el contrato. Todas las respuestas a partir de ahora se miden contra él.

La pestaña lleva un `•` siempre que la petición promete una forma, así que lo sabes antes de gastar un envío. Abre en **Forma**: una fila por campo con su nombre, un chip de tipo, una marca `REQ` cuando el schema lo exige, y lo demás que exija — `format: date`, los valores de un enum, un rango. Veinte líneas de llaves se convierten en ocho filas que lees en dos segundos. **JSON** está a un segmento de distancia con el documento en crudo, y avisa en cuanto lo que escribes deja de ser JSON válido.

**Re-derive** reemplaza el contrato cuando la API cambia de forma legítima; **Clear** lo quita. Sin suscripción la cabecera muestra un chip *Solo lectura*: el schema sigue en pantalla para leerlo, y editarlo espera a que te suscribas.

El schema es un documento JSON Schema (draft 2020-12), titulado con el nombre de la petición, y sigue siendo editable debajo — pega uno traducido de tu definición de Zod, Pydantic u OpenAPI y funciona igual.

La colección de bienvenida trae uno ya montado: **Contract → GET · Una respuesta bajo contrato**. Envíala y aparece el veredicto; cambia `/json` por `/uuid` en la URL y míralo romperse.

## Qué significa «derivado»

Derivar lee la respuesta que tienes, no la API que te gustaría tener:

- **Solo se exige lo que estaba de verdad.** En un array, una clave presente en todos los elementos muestreados es obligatoria; una que aparece en algunos se describe pero queda opcional.
- **Los campos de más nunca lo rompen.** No se emite `additionalProperties`, así que una API que añade un campo sigue cumpliendo — el contrato caza lo que *desapareció* o *cambió de tipo*, que es lo que de verdad rompe un cliente.
- **Un campo que a veces es nulo** sale como `["string", "null"]` en vez de como una mentira.
- **Un campo cuyas muestras no se pusieron de acuerdo** sale como schema vacío: ahí valida cualquier cosa. Adivinar rechazaría payloads que la API sí envía.
- Los arrays se muestrean hasta 200 elementos y el anidado hasta 8 niveles.

## El veredicto, donde ya estás mirando

- Un chip **CONTRACT** se sitúa junto al estado: verde cuando la respuesta cumple, ámbar con un recuento cuando hay campos desviados, rojo cuando el propio schema no es JSON válido. Púlsalo para saltar a la pestaña.
- La pestaña **Contract** del panel de respuesta lleva un badge ámbar con el número de incumplimientos — encendido incluso cuando estás en otra pestaña, porque su trabajo es avisarte justo desde la pestaña que no estás mirando.
- Esa pestaña es una tabla, no una lista de quejas: cada campo que describe el contrato con **el valor que llegó de verdad** al lado, y los desviados ordenados arriba con el motivo en su propia línea. **Solo diferencias** esconde a la mayoría que cumple, que es lo que hace legible un contrato de cuarenta campos. Clic derecho en una fila para copiar su ruta.
- Cada incumplimiento se lista con su ruta (`$.slideshow.author`) y qué pasó: *Expected string, got integer*, *Required field `id` is missing*, etc. La lista se corta en 100.
- ¿Sin contrato, o una respuesta que no es JSON? No se muestra nada. Un chip permanente de «sin contrato» sobre el 90 % de las peticiones que nunca van a tener uno es exactamente lo que enseña a dejar de leer el strip.

**Un contrato roto nunca hace fallar la petición.** El servidor contestó; el estado es el que es. La validación es informativa y lo dice: señala qué campo se movió y deja el veredicto del envío en manos del HTTP.

## Qué cubre el validador

Estructura (`type`, `properties`, `required`, `items`, `additionalProperties`), valores (`enum`, `const`, `minimum` / `maximum`, `minLength` / `maxLength`, `pattern`, `minItems` / `maxItems`, `uniqueItems`, `multipleOf`) y composición (`anyOf`, `allOf`, `oneOf`, `not`, más `$ref` local dentro de `$defs`).

Sin implementar, a propósito: `$dynamicRef`, `unevaluatedProperties` y la resolución de `$ref` remotos — aparecen en las suites de conformidad, no en un schema derivado de tu propio endpoint. `format` se trata como la anotación que la especificación dice que es: un `"date-time"` que no parsea no es un fallo, porque medio internet incumpliría su propio contrato solo por eso.

## En el runner de colecciones

Ejecuta una colección y cada petición con contrato se comprueba sobre la marcha ([Runner](/es/docs/runner)):

- El resumen añade **Off contract** y **No contract** junto a Passed / Failed, pero solo cuando al menos una petición de la corrida tiene contrato. Cero desvíos sobre nueve peticiones que nadie comprueba no es una buena noticia, así que las dos cifras van siempre juntas.
- Cada fila tiene su celda de contrato: sello verde, recuento ámbar, marca roja para un schema inservible, o `—` cuando no hay nada que comprobar.
- El informe exportado también lo lleva. El CSV tiene una columna `contract` y el JSON un campo `contract`, ambos con `passed`, `failed(n)`, `invalid_schema` o `not_json` — las mismas palabras que recibe un agente por MCP, para que un CI y un agente puedan hablar de la misma corrida sin traducir.

## Desde un agente

El [servidor MCP](/es/docs/mcp) expone el mismo veredicto, calculado por el mismo código:

| Herramienta | Propósito |
|---|---|
| `get_contract` | Leer el documento de schema que promete una petición |
| `set_contract` | Guardar un schema que construiste, o pasar `derive_from_response: true` para enviar una vez y derivarlo. Devuelve el schema anterior |
| `check_contract` | Enviar y comprobar. `variants: "all"` prueba cada variante de body guardada por turno y deja activa la que lo estaba |
| `check_collection_contracts` | «¿Cambió esta API?» para una colección entera; las peticiones sin contrato se informan como no comprobadas, nunca se saltan en silencio |

`send_request` y `run_collection` incluyen el veredicto también, así que un agente que estaba en otra cosa se entera igual de que la forma se movió.

## Dónde vive el schema

El contrato pertenece a la petición, no a una respuesta: es parte de su definición, así que viaja con ella en la [exportación e importación](/es/docs/file-format), en la [sincronización con iCloud](/es/docs/system#sincronización-con-icloud), y es lo que lee `get_contract` por MCP. Si le pasas un paquete `.mariner` a un compañero, los contratos van dentro. Se guarda como documento en crudo — un schema que pegaste desde tu propio repo sale byte a byte igual, sin reformatear.

Una petición sin contrato no escribe ninguna clave, así que el paquete de quien nunca tocó la función conserva exactamente la forma que tenía.

<aside class="callout" data-label="EL MES GRATIS">
Los contratos forman parte de todos los planes, y del <a href="/es/docs/free-and-pro">mes gratis</a>. La colección de bienvenida trae uno ya derivado, así que el veredicto es algo que puedes ver funcionar desde el primer día.
</aside>

---
Source: https://marinerhttp.dev/es/docs/contracts · Mariner docs
