---
title: "Modelo de prevención"
description: "El programa de cumplimiento del Art. 49 por API: checklist, declaraciones con evidencia, certificación ante la Agencia y expediente sellado."
url: "https://docs.edugoverna.com/api-organizacion/mpi"
---
# Modelo de prevención

El **Modelo de Prevención de Infracciones** de los Arts. 49 a 53 es un programa de cumplimiento **voluntario**: no tenerlo no es una infracción, y tenerlo certificado opera como atenuante. El módulo lo empaqueta en tres piezas: un **checklist** de los contenidos mínimos, alimentado por los registros que el colegio ya lleva; las **declaraciones** de lo que ocurre fuera del producto, con su evidencia; y el **expediente sellado**, una foto inmutable con huella verificable. {{ className: 'lead' }}

<Note>
  El módulo `mpi` **no viene en ningún plan**: se contrata como complemento y se
  habilita por organización. Sin él, todas las rutas de esta página responden
  `403 MODULE_NOT_LICENSED`.
</Note>

Sólo `mpi.read` es asignable a una API key. `mpi.manage` (declarar y certificar) y `mpi.export` (sellar) son **exclusivas de sesión de la consola** por diseño: una declaración la hace una persona, no una llave, y sellar el expediente tiene que ser atribuible — el mismo corte que separa `transparency.write` de `transparency.publish`.

Las marcas de tiempo de estas rutas son **milisegundos epoch**, no ISO 8601 como en el resto de la API.

---

## Checklist de cumplimiento {{ tag: 'GET', label: '/api/mpi/readiness' }}

<Row>
  <Col>

    Requiere `mpi.read`. Devuelve los **11 requisitos** del reglamento agrupados en 8 letras, cada uno con su veredicto y una oración de evidencia.

    Los requisitos son de dos clases, y la distinción es el corazón del módulo:

    - **`derived` (5)** — `responsable` (a), `delegado` (b), `caracterizacion` (c), `riesgos` (d) y `protocolos` (e). Se computan en lectura desde el RAT, el registro de riesgos, los encargados y el acta del delegado. **No se pueden declarar a mano**: un checklist que deja marcar «RAT completo» con el RAT vacío es exactamente el papel que el Art. 49 no quiere.
    - **`attestation` (6)** — `dpd_plan_anual` (b), `reporte_interno` (f), `regimen_interno` (g), `adopcion_contratos`, `adopcion_difusion` y `publicidad_registro`. Son hechos que ocurren fuera del producto (el anexo en los contratos de trabajo, la difusión al personal) y que sólo una persona puede afirmar.

    ### Veredictos

    <Properties>
      <Property name="complete">
        El requisito está cubierto por evidencia real.
      </Property>
      <Property name="attention">
        Hay cobertura parcial o algo que caducó.
      </Property>
      <Property name="incomplete">
        Sin evidencia. Un colegio recién creado está `incomplete`, nunca
        `complete`: cero evidencia no es estar al día.
      </Property>
      <Property name="no_access">
        El principal no tiene permiso sobre la superficie de origen. **No es un
        cero**: un rol parcial no puede concluir «pendiente» sobre lo que no ve.
      </Property>
    </Properties>

    ### El estado de la certificación

    `certification.state` se **deriva en lectura** — no hay columna de estado — y se resuelve en este orden: `revoked` > `expired` > `expiring_soon` (90 días antes) > `certified`. Sin certificación registrada, `none`.

    ### `sourceFingerprint`

    Huella de las filas fuente. Compárala con la del último expediente sellado para saber si el expediente ya no refleja los registros. Es por huella y no por fecha a propósito: editar y deshacer no cuenta como desactualizado.

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="GET" label="/api/mpi/readiness">

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

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

    </CodeGroup>

    ```json {{ title: 'Respuesta (recortada)' }}
    {
      "readiness": {
        "requirements": [
          {
            "key": "riesgos",
            "status": "complete",
            "detail": "12 riesgos en el registro, ninguno crítico sin medida."
          },
          {
            "key": "adopcion_difusion",
            "status": "incomplete",
            "detail": "Sin declaración de difusión al personal."
          }
        ],
        "attestations": [
          {
            "requirementKey": "adopcion_contratos",
            "status": "declared",
            "declaredAt": 1787923200000,
            "declaredByName": "Encargada de datos",
            "note": "Anexo incorporado en la renovación 2026.",
            "evidenceArtifactId": "a91c…"
          }
        ],
        "certification": {
          "state": "none",
          "current": null
        },
        "sourceFingerprint": "9f2b…"
      }
    }
    ```

  </Col>
</Row>

---

## Expedientes sellados {{ tag: 'GET', label: '/api/mpi/dossiers' }}

<Row>
  <Col>

    Requiere `mpi.read`. El historial de versiones selladas, de la más reciente a la más antigua. Cada versión congela el estado del checklist en un JSON canónico y lo firma con SHA-256.

    Un expediente está **desactualizado** cuando su `sourceFingerprint` no coincide con el que devuelve el checklist. `catalogVersion` sella además la versión del catálogo de requisitos con que se construyó, igual que `methodologyVersion` en la matriz de riesgos.

    <Note>
      Sellar **no exige el checklist completo ni una certificación vigente**. El
      expediente congela el estado que haya, con sus brechas a la vista, y eso
      también es evidencia: lo que se acredita es el programa, no la perfección.
    </Note>

    El PDF de una versión se descarga con `GET /mpi/dossiers/:id/document`, donde `:id` es el id de **la versión sellada**, no del expediente. Responde el archivo con `x-artifact-checksum` y `Content-Disposition: attachment`.

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="GET" label="/api/mpi/dossiers">

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

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

    </CodeGroup>

    ```json {{ title: 'Respuesta (recortada)' }}
    {
      "dossiers": [
        {
          "id": "d47e…",
          "versionNumber": 2,
          "checksum": "3a7f…",
          "sourceFingerprint": "9f2b…",
          "catalogVersion": "eg-2026.08",
          "documentArtifactId": "b18c…",
          "notes": "Antes de la solicitud de certificación.",
          "sealedAt": 1787923200000,
          "sealedByUserId": "u_01…"
        }
      ]
    }
    ```

  </Col>
</Row>

---

## Escrituras (sesión de la consola)

Ninguna de estas rutas acepta `x-api-key`. Declarar, certificar y sellar son actos de una persona identificada, y la respuesta a una llamada sin sesión es `401 SESSION_REQUIRED`.

| Método y ruta                              | Permiso       | Qué hace                                                                                                    |
| ------------------------------------------ | ------------- | ----------------------------------------------------------------------------------------------------------- |
| `PUT /mpi/attestations/:key`               | `mpi.manage`  | Declara un requisito `attestation`, con `note` y `evidenceArtifactId` opcionales.                            |
| `POST /mpi/attestations/:key/evidence`     | `mpi.manage`  | Declara **y adjunta** en un paso: `multipart/form-data` con `file` y `note` opcional (máx. 15 MB). `201`.    |
| `GET /mpi/attestations/:key/evidence`      | `mpi.read`    | Recupera el documento de respaldo. Inline, con `x-artifact-checksum`; la lectura queda auditada.             |
| `DELETE /mpi/attestations/:key`            | `mpi.manage`  | Deshace la declaración.                                                                                     |
| `POST /mpi/certifications`                 | `mpi.manage`  | Registra la certificación ante la Agencia. Vence a **3 años calendario**. `201 { certification }`.           |
| `POST /mpi/certifications/:id/revoke`      | `mpi.manage`  | Revoca la certificación registrada; exige `reason`.                                                          |
| `POST /mpi/dossiers`                       | `mpi.export`  | Sella una versión nueva (`versionNumber` = máx + 1) con `notes` opcional. `201 { dossier }`.                 |
| `GET /mpi/dossiers/:id/document`           | `mpi.read`    | Descarga el PDF de una versión sellada.                                                                      |

`certifiedAt` no admite fechas futuras: una certificación registrada «para 2030» quedaría vigente tres años desde una fecha que nunca ocurrió.

### Códigos de error del módulo

<Properties>
  <Property name="MPI_REQUIREMENT_NOT_ATTESTABLE" type="422">
    La clave corresponde a un requisito `derived`, que se computa desde los
    registros y no se declara a mano. Se valida **antes** de guardar el archivo:
    al revés, una clave inválida dejaba la evidencia huérfana en el
    almacenamiento y recién entonces respondía.
  </Property>
  <Property name="MPI_EVIDENCE_FILE_REQUIRED" type="422">
    El multipart llegó sin `file`, o el archivo venía vacío.
  </Property>
  <Property name="MPI_EVIDENCE_TOO_LARGE" type="400">
    El documento supera los 15 MB, el mismo techo que el acta del delegado.
  </Property>
  <Property name="MPI_EVIDENCE_NOT_FOUND" type="404">
    Esa declaración no tiene evidencia adjunta, o el artefacto no pertenece a la
    organización.
  </Property>
  <Property name="MPI_DOSSIER_NOT_FOUND" type="404">
    No existe una versión sellada con ese id en esta organización.
  </Property>
  <Property name="MPI_DOSSIER_DOCUMENT_MISSING" type="404">
    La versión existe pero no tiene documento generado.
  </Property>
  <Property name="SESSION_REQUIRED" type="401">
    La ruta exige sesión de la consola: `mpi.manage` y `mpi.export` no son
    portables por una API key.
  </Property>
</Properties>
