# API de consultas guardadas

> Crea, lista, consulta, modifica, elimina y ejecuta las consultas guardadas de la organización, y decide quién ve cada una, a través de la API para integradores.

Una consulta guardada es una [consulta PQL](https://docs.probara.net/es/developers/api/pql/) con un nombre, una descripción opcional y una visualización que indica cómo se dibuja su resultado. Pertenece a la organización, no a un proyecto. Cada una tiene un ULID asignado por el servidor, un creador y una visibilidad: `public` (todos los miembros) o `private` (su creador, los miembros y [grupos de usuarios](https://docs.probara.net/es/developers/api/user-groups/) con acceso concedido, y los propietarios y administradores de la organización).

Las consultas guardadas están disponibles en los planes **Pro** y **Enterprise**, los mismos que las consultas y los dashboards. En cualquier otro plan, todos los endpoints de esta página responden `403 forbidden`.

| Método y ruta                                | Qué hace                                                                                           |
| -------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `GET /api/v1/queries`                        | Lista las consultas guardadas que puedes ver, de la más recientemente modificada a la más antigua. |
| `POST /api/v1/queries`                       | Crea una consulta guardada.                                                                        |
| `GET /api/v1/queries/{savedQueryUlid}`       | Consulta una consulta guardada, con su texto y su visualización.                                   |
| `PUT /api/v1/queries/{savedQueryUlid}`       | Reemplaza todo el estado: texto, visualización, nombre, descripción, visibilidad y accesos.        |
| `DELETE /api/v1/queries/{savedQueryUlid}`    | Elimina la consulta guardada y sus accesos. Responde `204`.                                        |
| `POST /api/v1/queries/{savedQueryUlid}/run`  | Ejecuta el texto guardado con tu propio acceso a los proyectos.                                    |
| `GET /api/v1/queries/{savedQueryUlid}/usage` | Lista los dashboards que dibujan la consulta, para confirmar su eliminación.                       |

## Quién ve y quién cambia una consulta guardada

Un miembro ve una consulta guardada cuando se cumple alguna de estas condiciones:

- es `public`;
- la creó;
- él, o uno de sus grupos de usuarios, tiene acceso concedido a una consulta privada.

Los propietarios y administradores de la organización ven todas las consultas guardadas.

Crear una consulta requiere el permiso `queries.write`: los propietarios, los administradores y los miembros lo tienen, y los lectores pueden consultar las consultas guardadas, pero no crearlas. Solo el creador y los propietarios y administradores de la organización pueden modificar, eliminar o cambiar la visibilidad y los accesos de una consulta, sin importar qué otro acceso tenga el miembro. Si se elimina la cuenta del creador, la consulta permanece y solo los propietarios y administradores pueden modificarla.

Una consulta que no puedes ver responde `404 not_found` en todas las llamadas, así que una consulta privada nunca confirma que existe. Una consulta que puedes ver pero no modificar responde `403 forbidden` en `PUT` y `DELETE`.

## El nombre

Un nombre es único en la organización, sin distinguir mayúsculas de minúsculas: `Fallos de staging` y `fallos DE staging` son el mismo nombre. La regla abarca todas las consultas guardadas de la organización, incluidas las privadas que no puedes ver, así que un `409 conflict` al crear o al renombrar puede indicar que el nombre ya está en uso.

El nombre tiene de 1 a 120 caracteres, sin contar los espacios del principio y del final.

## Crear y reemplazar

`POST /api/v1/queries` recibe:

| Campo           | Regla                                                                                                 |
| --------------- | ----------------------------------------------------------------------------------------------------- |
| `name`          | Obligatorio, de 1 a 120 caracteres.                                                                   |
| `description`   | Opcional, hasta 2.000 caracteres.                                                                     |
| `pql`           | Obligatorio, de 1 a 4.000 caracteres de PQL.                                                          |
| `visualization` | Opcional. `{ "render": "table" }` por defecto. Ver más abajo.                                         |
| `accessMode`    | `public` (por defecto) o `private`.                                                                   |
| `members`       | Opcional. Los ULID de usuario de los miembros de la organización que pueden ver una consulta privada. |
| `groups`        | Opcional. Los ULID de los grupos de usuarios que pueden ver una consulta privada.                     |

`members` y `groups` admiten hasta 200 entradas cada uno, sin duplicados, y deben estar vacíos en una consulta pública. Una consulta privada puede empezar con sus primeros accesos, guardados en la misma petición.

`PUT /api/v1/queries/{savedQueryUlid}` recibe los mismos campos, y todos son obligatorios; envía `description` como `null` para borrarla. Si falta la lista `members` o `groups`, la petición se rechaza, de modo que nunca borra accesos por accidente. Una consulta pública recibe listas vacías, lo que elimina todos los accesos que tuviera.

El objeto `visualization` tiene un `render` y un `series` opcional:

- `render` es uno de `table`, `bar`, `line`, `donut` o `value`.
- `series` indica la columna del resultado que divide un gráfico en una serie por cada valor, o es `null`.

El texto de la consulta se valida al guardarlo, con las mismas comprobaciones de una ejecución y desde tu propia vista de la organización. Un texto que no se analiza o no valida responde `422 pql_invalid`, con cada error y su `span`, como lo hace el [endpoint de consultas](https://docs.probara.net/es/developers/api/pql/#errores). Una construcción que el motor aún no ejecuta responde `422 pql_unsupported`. Una referencia a otra consulta guardada, `in query("nombre")`, se comprueba del mismo modo: un nombre que el autor no puede ver responde `422 pql_invalid`, y un guardado que cerraría un ciclo de referencias se rechaza. En ambos casos no se guarda nada.

## Quién puede ver los accesos

Una consulta devuelve sus accesos, `members` y `groups`, solo a quien puede modificarla. Los demás reciben `null` en ambos. Cada respuesta incluye `canEdit`, para que un cliente sepa si debe ofrecer cambios.

## Listar

`GET /api/v1/queries` pagina las consultas que puedes ver, de la más recientemente modificada a la más antigua. Usa `page` (desde 1) y `pageSize` (hasta 200, 50 por defecto). `q` conserva las consultas cuyo nombre lo contiene, sin distinguir mayúsculas de minúsculas. Cada elemento de la lista incluye los campos de resumen: `ulid`, `name`, `description`, `ownerUserUlid`, `accessMode`, `createdAt`, `updatedAt` y `canEdit`. Consulta una sola para obtener su `pql`, su `visualization` y sus accesos.

## Ejecutar

`POST /api/v1/queries/{savedQueryUlid}/run` recibe un `offset` opcional, de 0 a 10.000, en un cuerpo JSON; envía `{}` para ejecutar la primera página. Responde la página que el [endpoint de consultas](https://docs.probara.net/es/developers/api/pql/) responde para el mismo texto: las mismas filas o grupos, calculados con tu propio acceso a los proyectos, y con `scope.hiddenProjectCount` contando los proyectos que no puedes abrir. Un texto que ya no pasa las comprobaciones, porque cambiaron los campos de la organización, responde `422 pql_invalid` o `422 pql_unsupported`.

## Exportar

`POST /api/v1/queries/{savedQueryUlid}/export` responde las filas de la consulta guardada como CSV, para quien lo pide y con su propio acceso a proyectos. No recibe cuerpo. El CSV sigue las reglas de la [exportación de consultas](https://docs.probara.net/es/developers/api/pql/#exportar-como-csv). Una consulta que quien pide no puede ver responde `404`, y un texto que ya no pasa las comprobaciones responde `422`.

## Uso

`GET /api/v1/queries/{savedQueryUlid}/usage` responde los dashboards con un widget que dibuja la consulta, para ver qué dejaría una eliminación. `dashboards` nombra los que puedes abrir, por ULID y nombre; `hiddenDashboardCount` cuenta los demás, que nunca se nombran. Una consulta que no puedes ver responde `404`. Al eliminar la consulta, esos widgets quedan en el estado de “consulta eliminada”.

## Errores

| Estado | Código              | Cuándo                                                                                                                   |
| ------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `403`  | `forbidden`         | El plan no incluye consultas, tu rol no incluye `queries.write` al crear, o puedes ver una consulta pero no modificarla. |
| `404`  | `not_found`         | No hay ninguna consulta guardada con este ULID que puedas ver.                                                           |
| `409`  | `conflict`          | Otra consulta guardada de la organización tiene este nombre, sin distinguir mayúsculas de minúsculas.                    |
| `422`  | `validation_failed` | El cuerpo o un parámetro de la ruta está mal formado, o se supera un límite.                                             |
| `422`  | `pql_invalid`       | El texto de la consulta no se analiza o no valida. `details.errors` lista cada error.                                    |
| `422`  | `pql_unsupported`   | El texto de la consulta usa una construcción que añade una versión posterior. `details.construct` la nombra.             |

## Auditoría

Cada cambio escribe un evento de auditoría, visible en el [registro de auditoría](https://docs.probara.net/es/developers/api/audit-events/):

- `saved_query.created` cuando se crea una consulta, con su visibilidad.
- `saved_query.updated` cuando cambian su nombre, descripción, texto o visualización. Indica los campos que cambiaron, nunca sus valores.
- `saved_query.access_changed` por cada cambio de visibilidad y de acceso, con su motivo: `mode_toggle`, `member_granted`, `member_cleared`, `group_granted` o `group_revoked`.
- `saved_query.deleted` cuando se elimina una consulta.

Una petición que no cambia nada no escribe ningún evento.
