# Resultados, estados y reintentos en Vitest

> La ejecución en la que reporta cada vitest run, cómo se convierten en estados los intentos, los hooks y los archivos que no cargan, los reintentos y las repeticiones, el archivo de resultados para lo que no se pudo enviar, y la asignación de resultados fallidos.

Cada `vitest run` reporta en una ejecución de Probara: una ejecución automatizada nueva por defecto,
cerrada al final, o una existente. Cada ejecución de cada prueba es un resultado en ella, con el
veredicto del propio Vitest. Esta página cubre la ejecución, los estados, los reintentos y las
repeticiones, qué pasa cuando no se pueden enviar los resultados, y quién recibe los fallos.

## La ejecución

Estas opciones describen la ejecución que crea el reporter:

```ts
reporters: [
  'default',
  [
    '@probara/vitest-reporter',
    {
      run: {
        name: 'Nightly regression',
        description: 'Every night on main, against staging.',
        environment: 'staging',
        milestone: 'Sprint 12',
        plan: 'PLAN-2',
        configurations: [
          { group: 'Node', name: '22' },
          { group: 'OS', name: 'Linux' },
        ],
        tags: ['nightly', 'unit'],
      },
    },
  ],
],
```

| Opción               | Variable                  | Qué define                                                                                                 |
| -------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `run.name`           | `PROBARA_RUN_NAME`        | El nombre. Por defecto: el nombre del build de CI (`CI #42`), si no `Automated run <date> <time> UTC`      |
| `run.description`    | `PROBARA_RUN_DESCRIPTION` | La descripción, hasta 2000 caracteres (lo que exceda se recorta, con una advertencia)                      |
| `run.environment`    | `PROBARA_ENVIRONMENT`     | El entorno, por nombre: se crea en el proyecto si ninguno tiene ese nombre                                 |
| `run.milestone`      | `PROBARA_MILESTONE`       | El hito, por ID visible (`M-3`) o nombre exacto                                                            |
| `run.plan`           | `PROBARA_PLAN`            | El plan de pruebas, por ID visible (`PLAN-2`) o nombre exacto: la ejecución empieza con los casos del plan |
| `run.configurations` | `PROBARA_CONFIGURATIONS`  | Valores de configuración por grupo y nombre (`Node=22,OS=Linux`), cada grupo una vez                       |
| `run.tags`           | `PROBARA_RUN_TAGS`        | Etiquetas (`nightly,unit`), hasta 50                                                                       |

La misma ejecución desde el entorno de un paso de CI, con el proyecto en la configuración:

```bash
PROBARA_RUN_NAME="Nightly regression" PROBARA_ENVIRONMENT=staging PROBARA_RUN_TAGS=nightly,unit npx vitest run
```

Los `projects` de Vitest no cambian la ejecución: todos los proyectos de la configuración reportan
en la misma ejecución, y un resultado nombra su proyecto de Vitest en el parámetro `project` cuando
la configuración tiene más de uno
([proyectos de Vitest](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#proyectos-de-vitest)).

### Nombres o ULIDs

El entorno, el hito y las configuraciones también aceptan ULIDs: `run.environmentId`,
`run.milestoneId` y `run.configurationUlids` (`PROBARA_ENVIRONMENT_ID`, `PROBARA_MILESTONE_ID`,
`PROBARA_CONFIGURATION_ULIDS`). Define una sola forma de cada uno, nunca ambas: las dos a la vez son
un problema de configuración que desactiva el envío. Los nombres son la forma que hay que usar con
un token de aplicación, que no puede buscar ULIDs.

- Un **nombre** de entorno desconocido crea el entorno.
- Un hito, un plan o una configuración desconocidos hacen que Probara rechace el primer informe
  (`422`, nombrando el campo): no se registra nada, y el registro dice por qué. Comprueba los
  nombres en Probara.

### Origen de la ejecución

Una ejecución registra de dónde viene: la rama, el commit y la URL del build de CI, leídos de las
variables de GitHub Actions, GitLab CI, CircleCI, Azure Pipelines, Jenkins, Bitbucket Pipelines y
Buildkite
([lo que rellena cada CI](https://docs.probara.net/es/developers/ci-integrations/vitest/ci-providers/#lo-que-rellena-la-detección-del-ci)).
Sustituye un campo con `source` o con su variable, o no envíes ninguno:

```ts
reporters: ['default', ['@probara/vitest-reporter', { source: { branch: 'release/2.4' } }]],
```

| Opción            | Variable            | Valor por defecto |
| ----------------- | ------------------- | ----------------- |
| `source.branch`   | `PROBARA_BRANCH`    | detectado del CI  |
| `source.commit`   | `PROBARA_COMMIT`    | detectado del CI  |
| `source.buildUrl` | `PROBARA_BUILD_URL` | detectado del CI  |
| `source: false`   | —                   | no envía origen   |

El nombre del build de CI también da nombre a la ejecución cuando `run.name` no está definido.

### Una ejecución existente

`run.ulid` (`PROBARA_RUN_ULID`) reporta en una ejecución que ya existe, como una que
`probara run create` creó para los shards de un pipeline
([sharding](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#patrón-2-crea-la-ejecución-reporta-desde-cada-shard-y-ciérrala)),
o una que una persona creó en Probara
([selección de pruebas](https://docs.probara.net/es/developers/ci-integrations/vitest/run-selection/)). El reporter añade
sus resultados a esa ejecución y la deja abierta:

```text
$ PROBARA_RUN_ULID=01J9Z3K4M5N6P7Q8R9S0T1V2W3 npx vitest run
[probara] Sending 3 results of 3 tests (3 passed, 0 failed, 0 skipped, 0 blocked)
[probara] Recorded 3 results (2 new cases, 0 unmatched) in R-1 (open): https://app.probara.net/projects/SHOP/runs/R-1
```

Las opciones de una ejecución nueva se ignoran entonces, con una advertencia: la ejecución conserva
su propio nombre, entorno, hito y configuraciones.

```ts
reporters: ['default', ['@probara/vitest-reporter', { run: { name: 'Nightly regression' } }]],
```

```text
$ PROBARA_RUN_ULID=01J9Z3K4M5N6P7Q8R9S0T1V2W3 npx vitest run
[probara] Ignored name: a reused run (run.ulid) keeps its own
[probara] Sending 3 results of 3 tests (3 passed, 0 failed, 0 skipped, 0 blocked)
[probara] Recorded 3 results (2 new cases, 0 unmatched) in R-1 (open): https://app.probara.net/projects/SHOP/runs/R-1
```

Para obtener el ULID de una ejecución en Probara, abre la ejecución y elige **Copiar ULID de la
ejecución** en **Acciones de la ejecución** (el menú de la cabecera de la ejecución).

### Cierre

| La ejecución                                        | Al final de `vitest`                                             |
| --------------------------------------------------- | ---------------------------------------------------------------- |
| Creada por el reporter                              | Se cierra, una vez enviados todos los resultados y archivos      |
| Reutilizada (`run.ulid`)                            | Queda abierta: la cierra quien la creó                           |
| Creada en una sesión watch (`vitest --watch`)       | Queda abierta: cada nueva ejecución de la sesión reporta en ella |
| Cualquiera, si un informe falló tras los reintentos | Queda abierta: el registro la nombra, y el error dice qué hacer  |

`closeRun` (`PROBARA_CLOSE_RUN`) decide para cada ejecución de un comando que no sea una sesión
watch: `true` cierra también una ejecución reutilizada, `false` deja abierta una creada. `closeRuns`
decide por proyecto (`{ SHOP: true, WEB: false }`) cuando `closeRun` no está definido. Cuando un
informe falla, lo que no se envió puede ir a un [archivo de resultados](#archivo-de-resultados), que
`probara import results` envía más tarde a la misma ejecución, y la cierra como lo habría hecho el
reporter.

- **Modo watch.** `vitest --watch` (y `vitest` a secas en una terminal, que inicia el modo watch)
  crea una ejecución por proyecto de Probara para toda la sesión: cada nueva ejecución de las
  pruebas reporta en ella, y la línea `Sending` cuenta solo esa nueva ejecución. El reporter nunca
  la cierra, y dice una vez cómo hacerlo. Una ejecución cerrada o eliminada en Probara durante la
  sesión no recibe más resultados: el reporter envía los resultados de esa nueva ejecución a una
  ejecución nueva (o los guarda en el archivo de resultados, si hay uno definido), y lo indica
  ([modo watch](https://docs.probara.net/es/developers/ci-integrations/vitest/configuration/#modo-watch)).
- **Shards con el reporter `blob`.** Un shard (`vitest run --shard=1/2`) que escribe un blob no
  envía nada ni crea ninguna ejecución: `vitest --merge-reports` reporta los resultados de todos los
  shards en una sola ejecución, que cierra. Un shard con el reporter junto a `blob` en sus reporters
  lo indica:
  `Sharded run with the blob reporter: the results are reported by vitest --merge-reports`
  ([sharding](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#patrón-1-fusionar-los-informes-blob)).

Cierra una ejecución a mano con la [CLI de Probara](https://docs.probara.net/es/developers/cli/):

```bash
npx @probara/cli run close --run-ulid 01J9Z3K4M5N6P7Q8R9S0T1V2W3
```

Con [varios proyectos de Probara](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#varios-proyectos-de-probara),
cada proyecto tiene su propia ejecución. El nombre, la descripción, el entorno (por nombre) y las
etiquetas van con cada ejecución nueva; el hito, el plan y las configuraciones van solo con la
ejecución del proyecto configurado.

## Estados

Cada intento se envía con el veredicto del propio Vitest, como `passed`, `failed` o `skipped`.
`statusMapping` y `statusFilter` pueden cambiarlo después.

| El intento                                                                                           | Estado                                                         |
| ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| Aprobado                                                                                             | `passed`                                                       |
| Fallido: una aserción, un error, un tiempo agotado, un `beforeEach` o `afterEach` que lanzó un error | `failed`                                                       |
| Fallos de `expect.soft`                                                                              | `failed`, con cada fallo en las notas                          |
| `test.skip`, `skipIf`, `runIf`, un `describe` omitido, una prueba que `.only` o `-t` dejan fuera     | `skipped`                                                      |
| `test.todo`                                                                                          | `skipped`, con la nota `Todo`                                  |
| `context.skip()`; con una nota, `context.skip('why')`                                                | `skipped`; con una nota, la nota `Skipped: why`                |
| `test.fails`, y lanzó el error esperado                                                              | `passed`                                                       |
| `test.fails`, pero pasó                                                                              | `failed`, con el error de Vitest `Expect test to fail`         |
| Una prueba bajo un `beforeAll` que lanzó un error                                                    | `skipped`                                                      |
| Una prueba que Vitest nunca inició, después de `--bail` o Ctrl-C                                     | `skipped`, con la nota `Not run: the test run was interrupted` |

El reporter nunca envía `blocked` por sí solo: mapea un estado a él
([abajo](#mapeo-y-filtro-de-estados)) cuando lo necesites.

```ts
// tests/statuses.test.ts
import { expect, test } from 'vitest';

test('passes', () => {});

test('fails', () => {
  expect(1 + 1).toBe(3);
});

test.skip('is skipped', () => {});

test.todo('is not written yet');

test('skips itself', (context) => {
  context.skip('needs the payment sandbox');
});

test.fails('is a known bug', () => {
  expect(0.1 + 0.2).toBe(0.3);
});
```

```json
[
  { "automationKey": "tests/statuses.test.ts > passes", "status": "passed" },
  { "automationKey": "tests/statuses.test.ts > fails", "status": "failed" },
  { "automationKey": "tests/statuses.test.ts > is skipped", "status": "skipped" },
  {
    "automationKey": "tests/statuses.test.ts > is not written yet",
    "status": "skipped",
    "notes": "Todo"
  },
  {
    "automationKey": "tests/statuses.test.ts > skips itself",
    "status": "skipped",
    "notes": "Skipped: needs the payment sandbox"
  },
  { "automationKey": "tests/statuses.test.ts > is a known bug", "status": "passed" }
]
```

- **Orden.** Los resultados se envían archivo por archivo cuando termina la ejecución, las pruebas
  de cada archivo en el orden en que están escritas, y cada intento de una prueba en el orden en que
  se ejecutó.
- **Los errores** van a las notas del resultado: cada error del intento, incluidos los fallos de
  `expect.soft`, en orden, cada uno con su mensaje y su traza, sin los colores de la terminal,
  después de un [comentario](https://docs.probara.net/es/developers/ci-integrations/vitest/metadata/#comentario) si la
  prueba escribió uno. Un intento aprobado no envía ningún error.
- **Las omisiones no se distinguen.** Vitest reporta `test.skip`, `skipIf`, `runIf`, `.only` y `-t`
  del mismo modo, igual que su salida JUnit: son resultados omitidos sin nota, como los registra
  `probara import junit`.
- **Las pruebas todo** nunca se pierden: son resultados omitidos con la nota `Todo`, así que el caso
  muestra lo que queda por escribir.
- **Los fallos esperados** son los de Vitest: una prueba `test.fails` que lanza un error hace lo que
  debe, así que pasa. Cuando se corrige el fallo, la prueba pasa, Vitest la hace fallar, y Probara
  también, lo que te recuerda quitar `.fails`.
- **Una ejecución interrumpida** (`--bail`, Ctrl-C) reporta igualmente lo que ejecutó. Una prueba
  que Vitest nunca inició se omite con la nota `Not run: the test run was interrupted`; una prueba
  que el propio Vitest omitió porque la ejecución se canceló conserva la nota de Vitest,
  `The test run was aborted by the user.` Un archivo de prueba que Vitest nunca inició no tiene
  ninguna prueba que reportar: una advertencia cuenta esos archivos.

### Errores fuera de una prueba

Un error en un hook de una suite o en el propio archivo hace fallar el archivo en Vitest, aunque sus
pruebas pasen. El reporter envía los resultados fallidos que la salida JUnit de Vitest escribe para
él, con el error en sus notas, así que Probara tampoco muestra el archivo en verde:

| Dónde ocurre el error                                                                                | Qué recibe Probara                                                                               |
| ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| El `beforeAll` o `afterAll` de un `describe`                                                         | Un resultado fallido con el nombre del `describe`: `tests/session.test.ts > session`             |
| Un `beforeAll` o `afterAll` fuera de todo `describe`                                                 | Un resultado fallido con el nombre del archivo: `tests/cleanup.test.ts > tests/cleanup.test.ts`  |
| El archivo de prueba no carga (un error de sintaxis, una importación que lanza un error)             | Un resultado fallido con el nombre del archivo: `tests/broken.test.ts > tests/broken.test.ts`    |
| Un error no gestionado: un rechazo que ninguna prueba esperó, un error lanzado desde un temporizador | Ningún resultado: Vitest lo lista entre sus errores no gestionados, y una advertencia los cuenta |

```ts
// tests/session.test.ts
import { beforeAll, describe, test } from 'vitest';

describe('session', () => {
  beforeAll(() => {
    throw new Error('Could not seed the users');
  });

  test('renews the token', () => {});
});

test('logs out', () => {});
```

```json
[
  { "automationKey": "tests/session.test.ts > session", "status": "failed" },
  { "automationKey": "tests/session.test.ts > session > renews the token", "status": "skipped" },
  { "automationKey": "tests/session.test.ts > logs out", "status": "passed" }
]
```

Los resultados fallidos van primero en su archivo. Sus claves son las que `probara import junit` da
a los mismos fallos en la salida JUnit de Vitest, así que ambas herramientas los registran en el
mismo caso, con nombres raros incluidos: el escritor JUnit de Vitest nombra el fallo de un hook con
su propio `describe`, no con los bloques `describe` que tiene por encima, y el fallo de un archivo
con el archivo, que su clave contiene entonces dos veces.

Un archivo que no carga es un resultado fallido, contenga las pruebas que contenga:

```ts
// tests/broken.test.ts
import { expect, test } from 'vitest';
import { total } from './missing-module';

test('adds the items', () => {
  expect(total([1, 2])).toBe(3);
});
```

```json
[
  {
    "automationKey": "tests/broken.test.ts > tests/broken.test.ts",
    "status": "failed"
  }
]
```

Corrige el archivo: la propia salida de Vitest muestra el error completo.

### Mapeo y filtro de estados

`statusMapping` envía un estado como otro; `statusFilter` no envía ningún resultado con los estados
indicados, después del mapeo. Ambos aceptan `passed`, `failed`, `skipped` y `blocked`:

```ts
reporters: [
  'default',
  [
    '@probara/vitest-reporter',
    { statusMapping: { failed: 'blocked' }, statusFilter: ['skipped'] },
  ],
],
```

```ts
// tests/filters.test.ts
import { expect, test } from 'vitest';

test('passes', () => {});

test('fails', () => {
  expect(true).toBe(false);
});

test.todo('is not written yet');
```

```text
$ npx vitest run
[probara] Sending 2 results of 2 tests (1 passed, 0 failed, 0 skipped, 1 blocked); 1 left out by statusFilter
[probara] Recorded 2 results (2 new cases, 0 unmatched) in R-1 (closed): https://app.probara.net/projects/SHOP/runs/R-1
[probara] Filtered out 1 result by their status (statusFilter): not sent
```

Como variables: `PROBARA_STATUS_MAPPING=failed=blocked,skipped=passed` y
`PROBARA_STATUS_FILTER=skipped,blocked`. Un valor que no es un estado, o un estado mapeado dos
veces, es un problema de configuración que desactiva el envío. `statusFilter: ['skipped']` deja
fuera también las pruebas todo: son resultados omitidos. El mapeo solo cambia lo que registra
Probara: el código de salida de Vitest sigue siendo el de las pruebas.

## Reintentos y repeticiones

Cada ejecución de una prueba es un resultado propio: cada intento que le da el `retry` de Vitest, y
cada vez que `repeats` la vuelve a ejecutar. Una prueba reintentada una vez envía dos resultados, en
el orden en que se ejecutaron, cada uno con su propio estado, su duración, sus errores, sus pasos y
sus archivos.

```ts
// tests/dashboard.test.ts
import { expect, test } from 'vitest';

let loads = 0;

test('loads the dashboard', { retry: 1 }, () => {
  loads += 1;
  // Fails the first time, passes on the retry.
  expect(loads).toBe(2);
});

test('opens the menu', { repeats: 2 }, () => {});
```

```json
[
  { "automationKey": "tests/dashboard.test.ts > loads the dashboard", "status": "failed" },
  {
    "automationKey": "tests/dashboard.test.ts > loads the dashboard",
    "status": "passed",
    "parameters": { "attempt": "2" }
  },
  { "automationKey": "tests/dashboard.test.ts > opens the menu", "status": "passed" },
  {
    "automationKey": "tests/dashboard.test.ts > opens the menu",
    "status": "passed",
    "parameters": { "repeat": "2" }
  },
  {
    "automationKey": "tests/dashboard.test.ts > opens the menu",
    "status": "passed",
    "parameters": { "repeat": "3" }
  }
]
```

```text
$ npx vitest run
[probara] Sending 5 results of 2 tests (4 passed, 1 failed, 0 skipped, 0 blocked)
[probara] Recorded 5 results (2 new cases, 0 unmatched) in R-1 (closed): https://app.probara.net/projects/SHOP/runs/R-1
```

- **Qué muestra Probara.** El caso de la ejecución conserva el resultado de la última ejecución
  (`passed`, aquí), y cada ejecución queda en su historial: una prueba inestable es un caso cuyo
  historial mezcla fallos y aprobados en la misma ejecución. El recuento del registro lo indica:
  más resultados que pruebas.
- **Los parámetros distinguen las ejecuciones.** `attempt` vale `2`, `3`... a partir del segundo
  intento, y `repeat` vale `2`, `3`... a partir de la segunda ejecución de `repeats`. Cada
  repetición reintenta por su cuenta, así que sus intentos vuelven a empezar en 1: el segundo
  intento de la segunda repetición tiene `{ "repeat": "2", "attempt": "2" }`. Ganan a los
  `probara.parameters()` con el mismo nombre, y nunca forman parte de la clave: cada ejecución va al
  mismo caso.
- **Cada intento empieza vacío.** Las llamadas `probara.*`, los pasos, los
  [archivos](https://docs.probara.net/es/developers/ci-integrations/vitest/steps-and-attachments/#probaraattach) y la
  [salida de consola](https://docs.probara.net/es/developers/ci-integrations/vitest/steps-and-attachments/#salida-de-consola)
  pertenecen al intento que los produjo, así que un reintento no hereda lo que registró el primer
  intento.
- **Errores por intento.** Las notas de un intento fallido contienen solo sus propios errores, no
  los de los intentos anteriores.
- **Sin marca de inestable.** El reporter no marca los intentos como inestables: los propios
  intentos son la prueba. La salida JUnit de Vitest, y por tanto `probara import junit`, tiene un
  resultado por prueba, por muchos intentos que haya necesitado.

### Repeticiones

`repeats: 2` ejecuta una prueba tres veces: una, y después dos más. Vitest hace fallar una prueba
así cuando falló cualquiera de sus ejecuciones; Probara recibe cada ejecución con su propio estado,
y ningún resultado adicional para el veredicto de Vitest, que contaría un fallo dos veces. Así, una
prueba repetida cuya última ejecución pasó muestra `passed` en la ejecución aunque Vitest la hiciera
fallar: sus ejecuciones fallidas están en el historial del caso.

### Cuándo se ejecutan los reintentos

Vitest reintenta una prueba fallida en ese momento, antes de la siguiente prueba, con sus hooks
`beforeEach` y `afterEach` alrededor de cada intento. Sea lo que sea lo que define los reintentos
(el `retry` de la configuración, de un `describe` o de una prueba, o `--retry`) y sea cual sea su
condición o su espera, cada intento es un resultado.

| Vitest                                        | Qué recibe Probara                                              |
| --------------------------------------------- | --------------------------------------------------------------- |
| `retry`, `--retry`                            | Un resultado por intento                                        |
| `repeats`                                     | Un resultado por ejecución, y por intento de cada ejecución     |
| `test.fails`                                  | Un resultado: Vitest nunca reintenta una prueba `test.fails`    |
| `test.concurrent` con `retry`                 | Un resultado por intento, cada uno con sus propios errores      |
| Un shard ejecutado con `--reporter=blob` solo | Solo el intento final, con una advertencia en la fusión (abajo) |

Los intentos los registra el archivo de setup que el reporter añade a cada proyecto de Vitest. Donde
no se ejecuta, Vitest sigue reintentando, pero solo se puede enviar el intento final de cada prueba,
y una advertencia dice cuántas pruebas fueron:
`Vitest retried or repeated 1 test whose earlier attempts were not recorded: only its final attempt is reported`.
Eso ocurre cuando Vitest se ejecuta sin el reporter entre sus reporters, como un shard ejecutado con
`--reporter=blob` solo en la línea de comandos, que sustituye los reporters de la configuración:
nombra el reporter junto a él, `--reporter=blob --reporter=@probara/vitest-reporter`
([el reporter va junto a `blob`](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/#el-reporter-va-junto-a-blob)).

### Dejar los reintentos fuera de tus cifras

Para enviar solo el último intento, no reintentes en Vitest: ninguna opción del reporter descarta
intentos. Para que los fallos de las pruebas reintentadas no cuenten, mapéalos
([mapeo de estados](#mapeo-y-filtro-de-estados)), teniendo en cuenta que se aplica a todos los
fallos.

## Archivo de resultados

Cuando un informe no se puede enviar (Probara inalcanzable, la red caída, un informe rechazado), sus
resultados se pierden salvo que los guardes. Define un archivo de resultados, y el reporter escribe
en él lo que no pudo enviar al final de la ejecución; `probara import results` lo envía más tarde, a
las mismas ejecuciones.

```ts
reporters: ['default', ['@probara/vitest-reporter', { resultsFile: 'probara-results.json' }]],
```

O `PROBARA_RESULTS_FILE=probara-results.json`, relativo al directorio en el que se ejecuta `vitest`.

```text
$ PROBARA_RESULTS_FILE=probara-results.json npx vitest run
[probara] Sending 3 results of 3 tests (3 passed, 0 failed, 0 skipped, 0 blocked)
[probara] 3 results were not sent: Probara answered 403 forbidden: <message from Probara>. No run was created or updated
[probara] Wrote the 3 results that were not sent to /work/shop/probara-results.json: send them with probara import results /work/shop/probara-results.json
```

Después, desde cualquier job que tenga el token, envía lo que quedó pendiente:

```bash
npx @probara/cli import results 'probara-results*.json'
```

Pon el patrón entre comillas: lo expande `probara`, y coincide con el archivo y con los hermanos que
se describen abajo. Si no coincide nada, no quedó nada sin enviar: la importación lo indica y sale
con 0.

- **Solo cuando hace falta.** El archivo solo se escribe cuando algo no se envió: si todos los
  resultados llegaron, no hay archivo.
- **A las mismas ejecuciones.** El archivo nombra las ejecuciones a las que pertenecen sus
  resultados: una ejecución que el informe fallido dejó abierta recibe el resto de sus resultados,
  y una ejecución que nunca se creó la crea la importación con el mismo nombre y los mismos ajustes.
  Las ejecuciones se cierran como las habría cerrado el reporter.
- **La importación lo consume.** `probara import results` envía cada archivo por separado, y
  después lo elimina (junto con su carpeta `<name>-attachments/`) cuando se enviaron todos sus
  resultados, o lo reescribe solo con lo que sigue sin enviarse. Importar de nuevo nunca envía un
  resultado dos veces, así que la importación es segura en cualquier pipeline. `--dry-run` nunca
  toca los archivos.
- **Nunca toca un archivo anterior.** Si ya hay un archivo en esa ruta (el de otro shard en un disco
  compartido, o el de una ejecución anterior), el reporter lo deja como está y escribe en el primer
  hermano libre: `probara-results-2.json`, después `-3`, y así sucesivamente, cada uno con su propia
  carpeta de adjuntos. El registro nombra el archivo escrito, y el patrón los importa todos, del más
  antiguo al más reciente.
- **Nunca a medio escribir.** Cada archivo se escribe completo primero en un archivo temporal de la
  misma carpeta (con un nombre que empieza por punto, que el patrón nunca encuentra), y después
  aparece de golpe con su nombre: una importación que se ejecuta a la vez ve el archivo completo o
  no lo ve.

Una segunda ejecución que no puede enviar mientras sigue ahí el archivo de la primera:

```text
$ PROBARA_RESULTS_FILE=probara-results.json npx vitest run
[probara] Sending 3 results of 3 tests (3 passed, 0 failed, 0 skipped, 0 blocked)
[probara] 3 results were not sent: Probara answered 403 forbidden: <message from Probara>. No run was created or updated
[probara] Wrote the 3 results that were not sent to /work/shop/probara-results.json: send them with probara import results /work/shop/probara-results.json
$ PROBARA_RESULTS_FILE=probara-results.json npx vitest run
[probara] Sending 3 results of 3 tests (3 passed, 0 failed, 0 skipped, 0 blocked)
[probara] 3 results were not sent: Probara answered 403 forbidden: <message from Probara>. No run was created or updated
[probara] Wrote the 3 results that were not sent to /work/shop/probara-results-2.json (/work/shop/probara-results.json already exists): send them with probara import results /work/shop/probara-results-2.json
```

### Los adjuntos viajan con el archivo

Cada archivo que lleva un resultado se copia junto al archivo de resultados, en su carpeta
`<name>-attachments/` (`probara-results-attachments/`), y el archivo los nombra de forma relativa a
sí mismo: lo que copió `probara.attach()`, los archivos del `context.annotate()` de Vitest, las
capturas de pantalla de los fallos de las pruebas de navegador y la salida de consola de
`captureOutput`. El archivo nunca apunta al `attachmentsDir` de Vitest, que una ejecución posterior
o un paso de limpieza pueden vaciar. Mueve o descarga juntos el archivo y su carpeta, a cualquier
lugar, e impórtalo desde ahí: un artefacto de CI que descarga otro job conserva sus adjuntos.

```ts
// tests/receipt.test.ts
import { probara } from '@probara/vitest-reporter';
import { test } from 'vitest';

test('prints the receipt', async () => {
  await probara.attach({ name: 'receipt', body: 'Total: 25.00' });
});
```

```text
$ PROBARA_ENABLED=false PROBARA_RESULTS_FILE=probara-results.json npx vitest run
[probara] Wrote 1 result to /work/shop/probara-results.json: send them with probara import results /work/shop/probara-results.json
$ npx @probara/cli import results probara-results.json
[probara] probara-results.json: 1 result
[probara] Results: 1 (1 passed, 0 failed, 0 skipped, 0 blocked)
[probara] Project: SHOP
[probara] Run: new run "Automated run 2026-09-29 14:05 UTC"
[probara] Base URL: https://app.probara.net
[probara] Missing cases: created
[probara] Attachments: on
[probara] Recorded 1 result (1 new case, 0 unmatched) in R-1 (closed): https://app.probara.net/projects/SHOP/runs/R-1
[probara] Attached 1 file to results (0 skipped, 0 failed)
[probara] Deleted probara-results.json and probara-results-attachments: every result was sent
```

Una subida que falla durante la importación no se guarda para otro intento: su resultado ya se
registró, así que ya no está en el archivo, y la carpeta se va con el archivo cuando se enviaron
todos los resultados. La importación lo registra y sale con 1.

### Envío desactivado: guardarlo todo

Con el envío desactivado (`PROBARA_ENABLED=false`, o sin token), el reporter escribe **cada**
intento en el archivo, vínculos con casos incluidos. Ejecuta las pruebas donde no hay token (un
fork, una máquina sin red), e importa el archivo desde un job que lo tenga:

```text
$ PROBARA_ENABLED=false PROBARA_RESULTS_FILE=probara-results.json npx vitest run
[probara] Wrote 3 results to /work/shop/probara-results.json: send them with probara import results /work/shop/probara-results.json
```

El mismo archivo muestra lo que se enviaría sin enviarlo: consulta
[comprobar qué se enviaría](https://docs.probara.net/es/developers/ci-integrations/vitest/troubleshooting/#comprobar-qué-se-enviaría).

### En CI

Guarda los archivos y sus carpetas `<name>-attachments/` como artefactos del job de pruebas, e
impórtalos en un job (o paso) posterior que tenga el token:

```yaml
- name: Run Vitest tests
  run: npx vitest run
  env:
    PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }}
    PROBARA_RESULTS_FILE: probara-results.json
- name: Send what Probara did not get
  if: always()
  run: npx @probara/cli import results 'probara-results*.json'
  env:
    PROBARA_API_TOKEN: ${{ secrets.PROBARA_API_TOKEN }}
```

Un paso posterior en el mismo job también sirve, como arriba: si Probara solo estuvo caído un
momento, la importación envía el resto. Si sigue sin poder, la importación sale con 1 y guarda en
cada archivo lo que no pudo enviar; los archivos que envió desaparecen.

Un archivo de resultados es una entrada de confianza: sus adjuntos se suben desde las rutas que
nombra, así que importa solo los archivos que escribieron tus propios jobs. La importación deja
fuera, con una advertencia, un adjunto cuya ruta relativa sale de la carpeta del archivo; las rutas
absolutas se conservan.

### En modo watch

Una nueva ejecución de una sesión watch que se rechaza porque su ejecución se cerró también se
guarda en el archivo, nombrando esa ejecución, y el registro dice cómo enviarla a una ejecución
abierta con `--run-ulid`
([cuando se cierra la ejecución durante la sesión](https://docs.probara.net/es/developers/ci-integrations/vitest/configuration/#cuando-se-cierra-la-ejecución-durante-la-sesión)).

### El formato

La versión 1 de un documento JSON: el proyecto, las ejecuciones y los ajustes del reporter (nunca
el token), y después cada resultado tal como lo construyó el reporter, antes de `statusMapping` (que
se aplica cuando se envía el archivo). El formato completo está en el
[archivo de resultados de core](https://github.com/cynch-me/probara-sdk/blob/main/packages/core/README.md#results-file),
y la importación (varios archivos y patrones, el resumen JSON, los códigos de salida) se describe en
[`probara import results`](https://github.com/cynch-me/probara-sdk/blob/main/packages/cli/docs/commands.md#probara-import-results).

## Asignar resultados fallidos

`assignFailedTo` nombra a las personas que investigan los fallos. Cada informe pide a Probara que
asigne cada caso de la ejecución que deja fallido, y que todavía no tiene responsable, a una de
ellas, por turnos, para que una ejecución nocturna con fallos llegue a la lista de alguien sin que
nadie tenga que clasificarla antes.

```ts
reporters: [
  'default',
  [
    '@probara/vitest-reporter',
    { assignFailedTo: ['ana@example.com', 'bo@example.com', 'cy@example.com'] },
  ],
],
```

```ts
// tests/checkout.test.ts
import { expect, test } from 'vitest';

test('pays with a card', () => {
  expect('declined').toBe('paid');
});

test('pays with a voucher', () => {
  expect(0).toBe(25);
});

test('shows the total', () => {});
```

Con `ana@example.com` y `bo@example.com` como miembros del proyecto y `cy@example.com` no, los dos
casos fallidos van a Ana y a Bo, y Probara indica que un email no coincidió con nadie:

```text
$ npx vitest run
[probara] Sending 3 results of 3 tests (1 passed, 2 failed, 0 skipped, 0 blocked)
[probara] Probara warned: assignFailedTo: 1 of 3 emails did not match a member who can be assigned in this project
[probara] Recorded 3 results (3 new cases, 0 unmatched) in R-1 (closed): https://app.probara.net/projects/SHOP/runs/R-1
```

O desde el entorno: `PROBARA_ASSIGN_FAILED_TO=ana@example.com,bo@example.com`.

| Regla                                                             | Por qué                                                                      |
| ----------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| Solo los casos de la ejecución que un informe deja en `failed`    | Los resultados aprobados, omitidos y bloqueados no necesitan a nadie         |
| Solo los casos de la ejecución sin responsable                    | Un responsable existente nunca se sustituye: alguien ya se ocupa de él       |
| Solo miembros activos que pueden ejecutar pruebas en el proyecto  | Un caso no se puede asignar a alguien que no puede trabajar en él            |
| Los miembros de la lista se turnan, en el orden en que los listas | Los fallos se reparten, no se acumulan en el primer nombre                   |
| Un email que no coincide con ningún miembro así se omite          | Los resultados se registran igualmente; la advertencia de Probara los cuenta |

- **Decide el último intento.** Una prueba reintentada que falló y después pasó deja su caso de la
  ejecución aprobado, así que no se asigna nada; cada intento fallido sigue siendo un resultado del
  caso.
- **Después de `statusMapping`.** El estado que cuenta es el que registra Probara: con
  `statusMapping: { failed: 'blocked' }`, nada queda fallido, así que no se asigna nada.
- **La advertencia nunca nombra los emails**, solo cuántos se omitieron: el registro de un job de CI
  no es el lugar para listar quién es miembro y quién no. Compara la lista con los miembros del
  proyecto en Probara.
- **Los turnos vuelven a empezar con cada informe**: cada informe (un bloque de hasta 500
  resultados, un shard, una nueva ejecución en modo watch) da su primer caso fallido al primer
  miembro de la lista.
- **Cada informe de la ejecución lo pide**, con la misma lista: una ejecución con varios proyectos
  de Probara lo pide en la ejecución de cada proyecto, y los shards de una ejecución compartida
  asignan cada uno los casos que dejan fallidos.
- **Un archivo de resultados conserva la lista.** `probara import results` la envía con los
  resultados, salvo que `--assign-failed-to` o `PROBARA_ASSIGN_FAILED_TO` nombren a otras personas.

Relacionado: [Configuración](https://docs.probara.net/es/developers/ci-integrations/vitest/configuration/) ·
[Sharding y proyectos](https://docs.probara.net/es/developers/ci-integrations/vitest/sharding/) ·
[Solución de problemas](https://docs.probara.net/es/developers/ci-integrations/vitest/troubleshooting/#la-ejecución-quedó-abierta) ·
Fuente: [runs.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/vitest-reporter/docs/runs.md),
[statuses.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/vitest-reporter/docs/statuses.md),
[retries.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/vitest-reporter/docs/retries.md),
[results-file.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/vitest-reporter/docs/results-file.md),
[assign-failed.md](https://github.com/cynch-me/probara-sdk/blob/main/packages/vitest-reporter/docs/assign-failed.md)
