---
title: "Riesgos"
description: "El motor de riesgo por API: catálogo de medidas e infracciones, medidas de mitigación de la organización y el registro de riesgos del Art. 49 d."
url: "https://docs.edugoverna.com/api-organizacion/riesgos"
---
# Riesgos

El motor de riesgo tiene tres piezas: el **catálogo** (medidas de mitigación sugeridas e infracciones de la ley, contenido estático), las **medidas** que tu organización adoptó (`risk_measures`) y el **registro de riesgos** (`risk_register_entries`, el registro del Art. 49 d), donde cada riesgo cruza probabilidad e impacto contra la matriz declarada y las medidas vinculadas reducen el nivel residual. Con una API key todo se lee con `dpia.read`; toda escritura exige `dpia.write` y es **sólo disponible con sesión de la consola**. {{ className: 'lead' }}

La misma matriz 4×4 y metodología (`eg-2026.08`) que usan las [evaluaciones de impacto](/api-organizacion/actividades-tratamiento#evaluaciones-de-impacto-eipd): probabilidad e impacto en escala 1–4 resuelven `low`/`medium`/`high`/`critical`, y el residual se calcula restando las reducciones de las medidas `implemented`/`verified` — nunca un promedio.

El registro completo se descarga en formato de entregable —XLSX o PDF— con [`GET /risks/export`](#exportar-la-matriz), que también se lee con `dpia.read`.

---

## Catálogo {{ tag: 'GET', label: '/api/risks/catalog' }}

<Row>
  <Col>

    Requiere `dpia.read`. Contenido estático, sin parámetros: las medidas sugeridas con sus reducciones esperadas, y el catálogo de infracciones de la ley con su severidad (`leve`, `grave`, `gravisima`) referenciadas por artículo y letra (p. ej. `34ter:g`).

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="GET" label="/api/risks/catalog">

    ```bash {{ title: 'cURL' }}
    curl "https://app.edugoverna.com/api/risks/catalog" \
      -H "x-api-key: {tu_api_key}"
    ```

    ```js
    const { measures, infractions } = await fetch(
      'https://app.edugoverna.com/api/risks/catalog',
      { headers: { 'x-api-key': process.env.EDUGOVERNA_API_KEY } },
    ).then((r) => r.json())
    ```

    </CodeGroup>

    ```json {{ title: 'Respuesta (recortada)' }}
    {
      "measures": [
        {
          "code": "CIFRADO_REPOSO",
          "name": "Cifrado de los datos en reposo",
          "description": "Las bases y respaldos que contienen datos de estudiantes y apoderados quedan cifrados en el almacenamiento.",
          "family": "pseudonymization_encryption",
          "measureType": "technical",
          "suggestedProbabilityReduction": 0,
          "suggestedImpactReduction": 1
        }
      ],
      "infractions": [
        {
          "ref": "34ter:g",
          "severity": "grave",
          "letter": "g",
          "text": "…",
          "schoolRelevance": "…"
        }
      ]
    }
    ```

  </Col>
</Row>

---

## Medidas de la organización {{ tag: 'GET', label: '/api/risks/measures' }}

<Row>
  <Col>

    Requiere `dpia.read`. Las medidas que el colegio declaró, adoptadas del catálogo o propias, con su ciclo de vida y verificación.

    ### Parámetros opcionales

    <Properties>
      <Property name="status" type="string">
        `planned`, `implemented`, `verified` o `retired`.
      </Property>
      <Property name="family" type="string">
        Familia del catálogo (p. ej. `pseudonymization_encryption`).
      </Property>
      <Property name="verificationOverdue" type="string">
        `true` filtra las medidas cuya verificación cumplió más de un año
        escolar (`verificationOverdue: true` en cada fila).
      </Property>
    </Properties>

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="GET" label="/api/risks/measures">

    ```bash {{ title: 'cURL' }}
    curl -G "https://app.edugoverna.com/api/risks/measures" \
      -H "x-api-key: {tu_api_key}" \
      -d status=implemented
    ```

    ```js
    const { measures } = await fetch(
      'https://app.edugoverna.com/api/risks/measures?status=implemented',
      { headers: { 'x-api-key': process.env.EDUGOVERNA_API_KEY } },
    ).then((r) => r.json())
    ```

    </CodeGroup>

    ```json {{ title: 'Respuesta (recortada)' }}
    {
      "measures": [
        {
          "id": "m3a1…",
          "code": "CIFRADO_REPOSO",
          "name": "Cifrado de los datos en reposo",
          "family": "pseudonymization_encryption",
          "measureType": "technical",
          "status": "implemented",
          "implementedAt": "2026-03-10T12:00:00.000Z",
          "verifiedAt": null,
          "evidenceArtifactId": null,
          "origin": "catalog",
          "verificationOverdue": false
        }
      ]
    }
    ```

  </Col>
</Row>

---

## Registro de riesgos {{ tag: 'GET', label: '/api/risks/register' }}

<Row>
  <Col>

    Requiere `dpia.read`. Cada entrada es un riesgo con su área de proceso, las infracciones que podría configurar (`infractionRefs` → `worstSeverity`), sus niveles inherente y residual, y las medidas vinculadas con su reducción esperada. Las entradas pueden nacer manualmente o desde una EIPD (`linkedDpiaAssessmentId` / `assessmentCode`).

    ### Parámetros opcionales

    <Properties>
      <Property name="status" type="string">
        `open`, `mitigating`, `accepted` o `closed`.
      </Property>
      <Property name="processArea" type="string">
        Filtra por área de proceso.
      </Property>
      <Property name="highResidualOnly" type="string">
        `true` deja sólo los riesgos con residual alto o crítico.
      </Property>
    </Properties>

    ### Qué describe cada entrada

    <Properties>
      <Property name="probability / impact" type="1–4">
        La calificación en la escala de la matriz declarada.
      </Property>
      <Property name="probabilityRationale / impactRationale" type="string | null">
        El **fundamento escrito de cada calificación**, por separado. Un número
        sin fundamento no es defendible ante la Agencia, y los dos ejes se
        defienden con argumentos distintos.
      </Property>
      <Property name="affectedSubjectTypes" type="string[]">
        A quién afecta el riesgo: `student`, `guardian`, `staff`, `other`.
        Arreglo, nunca nulo (vacío si no se declaró).
      </Property>
      <Property name="dataCategoryGroup" type="string | null">
        Qué categoría de datos toca: `identification`, `contact`, `academic`,
        `health_pie`, `socioeconomic`, `behavioral_convivencia`,
        `biometric_image` u `other`. Es una etiqueta del registro de riesgos,
        no una referencia al catálogo de categorías del RAT: la entrada
        describe un proceso, no una fila del catálogo.
      </Property>
      <Property name="linkedActivities" type="array">
        Las actividades del RAT que el riesgo toca, cada una con `id`, `code` y
        `name`. Un riesgo real rara vez respeta una sola actividad.
      </Property>
      <Property name="activityName" type="string | null">
        Compatibilidad: el nombre de la **primera** actividad vinculada. Se
        mantiene para no romper integraciones anteriores; para leer el vínculo
        completo usa `linkedActivities`.
      </Property>
    </Properties>

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="GET" label="/api/risks/register">

    ```bash {{ title: 'cURL' }}
    curl -G "https://app.edugoverna.com/api/risks/register" \
      -H "x-api-key: {tu_api_key}" \
      -d highResidualOnly=true
    ```

    ```js
    const { entries } = await fetch(
      'https://app.edugoverna.com/api/risks/register?highResidualOnly=true',
      { headers: { 'x-api-key': process.env.EDUGOVERNA_API_KEY } },
    ).then((r) => r.json())
    ```

    </CodeGroup>

    ```json {{ title: 'Respuesta (recortada)' }}
    {
      "entries": [
        {
          "id": "r9c4…",
          "code": "RSG-007",
          "title": "Acceso indebido a fichas de salud",
          "processArea": "convivencia",
          "infractionRefs": ["34ter:g", "34quater:b"],
          "worstSeverity": "gravisima",
          "probability": 2,
          "impact": 4,
          "probabilityRationale": "El expediente lo abren seis personas y no hay registro de acceso.",
          "impactRationale": "Estigma sobre un estudiante con diagnóstico; no se revierte cambiándolo de curso.",
          "affectedSubjectTypes": ["student"],
          "dataCategoryGroup": "health_pie",
          "inherentLevel": "high",
          "residualLevel": "medium",
          "status": "mitigating",
          "linkedDpiaAssessmentId": "d5e8…",
          "assessmentCode": "EIPD-003",
          "linkedActivities": [
            { "id": "a2f1…", "code": "ACT-007", "name": "Registro de salud escolar" },
            { "id": "a4b6…", "code": "ACT-012", "name": "Convivencia escolar" }
          ],
          "activityName": "Registro de salud escolar",
          "measures": [
            {
              "riskMeasureId": "m3a1…",
              "measureCode": "CIFRADO_REPOSO",
              "measureName": "Cifrado de los datos en reposo",
              "measureStatus": "implemented",
              "expectedProbabilityReduction": 0,
              "expectedImpactReduction": 1
            }
          ]
        }
      ]
    }
    ```

  </Col>
</Row>

---

## Exportar la matriz {{ tag: 'GET', label: '/api/risks/export' }}

<Row>
  <Col>

    Requiere `dpia.read`. Devuelve **el archivo directo** —no un JSON con un enlace— con su `Content-Disposition: attachment`, así que se descarga igual desde una sesión de la consola o con una API key.

    ### Parámetro

    <Properties>
      <Property name="format" type="string">
        `xlsx` (por defecto) o `pdf`. Cualquier otro valor cae en `xlsx`.
      </Property>
    </Properties>

    El libro trae tres hojas: **Matriz** (una fila por entrada del registro, con sus calificaciones, fundamentos, titulares afectados, categoría de datos, actividades vinculadas y medidas), **Metodología** (la matriz 4×4 declarada, las escalas con su glosa y la versión `eg-2026.08`) y **Resumen** (los recuentos por nivel, calculados sobre el residual, o el inherente cuando no hay residual). El PDF lleva el mismo contenido en formato de informe.

    <Note>
      Es un **documento de trabajo con fecha de corte**, no una pieza sellada:
      a diferencia del PDF de una EIPD aprobada, no queda guardado como
      artifact y no lleva folio ni huella. El nombre del archivo lleva la fecha
      (`matriz-riesgos-AAAA-MM-DD.xlsx`), y cada descarga queda en la
      auditoría de la organización.
    </Note>

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="GET" label="/api/risks/export">

    ```bash {{ title: 'cURL' }}
    curl -G "https://app.edugoverna.com/api/risks/export" \
      -H "x-api-key: {tu_api_key}" \
      -d format=xlsx \
      -o matriz-riesgos.xlsx
    ```

    ```js
    const response = await fetch(
      'https://app.edugoverna.com/api/risks/export?format=pdf',
      { headers: { 'x-api-key': process.env.EDUGOVERNA_API_KEY } },
    )
    const blob = await response.blob()
    ```

    </CodeGroup>

    ```text {{ title: 'Respuesta' }}
    HTTP/1.1 200 OK
    Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
    Content-Disposition: attachment; filename="matriz-riesgos-2026-08-27.xlsx"
    ```

  </Col>
</Row>

---

## Escrituras (sesión de la consola)

Todas exigen `dpia.write`, permiso que una API key no puede portar:

| Método y ruta                                         | Qué hace                                                                                          |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `POST /risks/measures`                                | Crea una medida propia (`201 { measure }`).                                                       |
| `POST /risks/measures/adopt`                          | Adopta medidas del catálogo por `codes[]` (`201 { adopted, existing }`).                          |
| `POST /risks/measures/:measureId/status`              | Avanza el ciclo `planned → implemented → verified → retired`.                                     |
| `DELETE /risks/measures/:measureId`                   | Elimina una medida.                                                                               |
| `POST /risks/register`                                | Crea o actualiza una entrada (`201 { id, inherentLevel, residualLevel, warnings }`).              |
| `POST /risks/register/:entryId/status`                | Cambia el estado de la entrada.                                                                   |
| `POST /risks/register/:entryId/measures`              | Vincula una medida y recalcula (`201 { residualLevel }`; una entrada `open` pasa a `mitigating`). |
| `DELETE /risks/register/:entryId/measures/:measureId` | Desvincula y recalcula (`{ residualLevel }`).                                                     |
| `DELETE /risks/register/:entryId`                     | Elimina la entrada.                                                                               |

### Vincular un riesgo con el RAT

`POST /risks/register` acepta `linkedProcessingActivityIds` (arreglo de ids, hasta 20). **Lo que envías es el conjunto**: el servicio calcula la diferencia contra lo que ya estaba vinculado, agrega lo que falta y borra lo que sobra. Enviar `[]` desvincula todo; **omitir el campo deja los vínculos intactos**.

Por compatibilidad se sigue aceptando el `linkedProcessingActivityId` singular de integraciones anteriores: se traduce a un arreglo de un elemento (o a `[]` si viene en `null`), y sólo se mira cuando `linkedProcessingActivityIds` no viene en el cuerpo.

Junto al vínculo, la entrada acepta `probabilityRationale` e `impactRationale` (hasta 2.000 caracteres cada uno), `affectedSubjectTypes` (hasta 8) y `dataCategoryGroup`.

### Códigos de error del registro

<Properties>
  <Property name="RISK_ENTRY_PROCESS_INVALID" type="422">
    El `processArea` no existe en el catálogo de procesos del colegio.
  </Property>
  <Property name="RISK_ENTRY_INFRACTION_REQUIRED" type="422">
    `infractionRefs` llegó vacío. El Art. 49 d) pide identificar qué infracción
    podría cometerse: al menos una.
  </Property>
  <Property name="RISK_ENTRY_INFRACTION_INVALID" type="422">
    Alguna referencia de `infractionRefs` no existe en la ley. `details.unknown`
    las enumera.
  </Property>
  <Property name="RISK_ENTRY_SCORE_INVALID" type="422">
    `probability` o `impact` fuera de la escala 1–4.
  </Property>
  <Property name="RISK_ENTRY_STATUS_INVALID" type="422">
    El `status` no es `open`, `mitigating`, `accepted` ni `closed`.
  </Property>
  <Property name="RISK_ENTRY_SUBJECT_INVALID" type="422">
    Algún valor de `affectedSubjectTypes` no es un tipo de titular conocido
    (`student`, `guardian`, `staff`, `other`). `details.unknown` los enumera.
  </Property>
  <Property name="RISK_ENTRY_DATA_CATEGORY_INVALID" type="422">
    El `dataCategoryGroup` no está en el catálogo de categorías del registro.
  </Property>
  <Property name="NNA_IMPACT_JUSTIFICATION_REQUIRED" type="422">
    La entrada declara `student` en `affectedSubjectTypes`, su
    `dataCategoryGroup` es una categoría sensible (`health_pie`,
    `socioeconomic`, `behavioral_convivencia` o `biometric_image`), el `impact`
    quedó bajo 3 y no viene `impactRationale`. Es el mismo **piso NNA** que
    rige en los escenarios de la EIPD, y muerde igual de asimétrico: sólo con
    `origin: "manual"` (el valor por defecto). Con `template` o `suggested` la
    entrada se guarda y el `201` trae `warnings: ["nna_impact_floor"]`.
    **Nunca altera el puntaje.**
  </Property>
  <Property name="RISK_ENTRY_ACTIVITY_NOT_FOUND" type="404">
    Alguna de las actividades de `linkedProcessingActivityIds` no existe en ese
    colegio. `details.missing` lista los ids. Se verifica **antes de escribir
    nada**: la secuencia no es transaccional en D1, así que un rechazo tardío
    dejaría la entrada creada sin residual recalculado ni evento de auditoría.
  </Property>
</Properties>
