---
title: "Actividades de tratamiento · API de organización"
description: "El RAT por API: actividades y versiones selladas con checksum, publicación que dispara recaptura de consentimientos, y las evaluaciones de impacto (EIPD)."
url: "https://docs.edugoverna.com/api-organizacion/actividades-tratamiento"
---
# Actividades de tratamiento

El registro de actividades de tratamiento (RAT) es el inventario legal de todo lo que el colegio hace con datos personales. Cada **actividad** (`processing_activity`) describe una finalidad; su contenido vive en **versiones selladas** (`processing_activity_versions`), y sobre las actividades de alto riesgo se levantan **evaluaciones de impacto** (`dpia_assessments`, EIPD, Art. 15 ter). Por API puedes leer todo el registro y escribir el RAT; la autoría de EIPD es de consola. {{ className: 'lead' }}

La misma superficie para integradores partner está en [Actividades de tratamiento (Partner API)](/actividades-tratamiento); el registro de riesgos asociado, en [Riesgos](/api-organizacion/riesgos).

## El modelo de versionado

Una actividad publica su contenido en versiones **inmutables y monotónicas**:

- `versionNumber` crece de a uno; la actividad apunta a la vigente con `currentVersionNumber`.
- Cada versión queda **sellada con `noticeChecksum`** — SHA-256 sobre el contenido normativo (finalidad, aviso en lenguaje claro, base legal, modelo de consentimiento y sus opciones, resúmenes de retención/transferencia/automatización). Si el texto cambia, cambia el checksum: es la prueba de qué vio el apoderado.
- Publicar una versión nueva **puede invalidar los consentimientos anteriores** (`invalidatesPreviousConsents`): las solicitudes `pending`/`granted` de versiones previas pasan a `invalidated` y se dispara la [recaptura](/api-organizacion/consentimientos#solicitudes-sueltas-y-recaptura). La respuesta te entrega los ids afectados.
- Lo que no envíes en la versión nueva (`dataCategories`, `recipients`, `consentOptions`) se **arrastra** desde la versión anterior; lo que envíes la reemplaza.
- Publicar cuenta como revisión: fija `lastReviewedAt`, calcula el próximo `reviewDueAt` y reabre las EIPD aprobadas de la actividad.

---

## Listar actividades {{ tag: 'GET', label: '/api/processing-activities' }}

<Row>
  <Col>

    Requiere `processing_activities.read`. Pagina con `limit` y `cursor`. Cada actividad llega con su `currentVersion`, su política de retención y el conteo de EIPD (`dpiaAssessmentCount`).

    ### Campos principales

    <Properties>
      <Property name="code / name" type="string">
        Folio y nombre de la actividad.
      </Property>
      <Property name="status" type="string">
        `draft`, `active` o `archived`.
      </Property>
      <Property name="treatmentRole" type="string">
        El rol de la organización en esa actividad: `controller`
        (responsable), `processor` (encargado o mandatario, Art. 15 bis) o
        `both`. **Tiene valor por defecto `controller`, así que siempre viene
        en la respuesta**, incluso en actividades creadas antes de que el
        campo existiera.
      </Property>
      <Property name="actingOnBehalfOf" type="string | null">
        Por cuenta de quién se trata, cuando el rol es `processor` o `both`.
        En `controller` el servidor lo normaliza a `null`: un responsable no
        actúa por cuenta de nadie.
      </Property>
      <Property name="lawfulBasis" type="string">
        Base de licitud (por defecto `consent`). La finalidad no es un campo
        de la actividad: vive en la versión como `purposeSummary`.
      </Property>
      <Property name="sensitiveLawfulBasis" type="string | null">
        La base de licitud **propia de los datos sensibles**, que es un
        régimen distinto del general: `express_consent`,
        `legal_authorization`, `manifestly_public`,
        `vital_or_health_emergency`, `judicial_or_rights_defense` o
        `nonprofit_members`. Sólo tiene sentido con
        `involvesSensitiveData: true`; si la actividad deja de tratar datos
        sensibles, el servidor la limpia a `null`.
      </Property>
      <Property name="riskLevel / isHighRisk" type="string / boolean">
        Nivel de riesgo declarado; con las banderas
        `involvesSensitiveData`, `involvesMinorData`,
        `includesAutomatedDecisions`, `includesProfiling`,
        `includesInternationalTransfers`, `includesMonitoring`,
        `isLargeScale`.
      </Property>
      <Property name="currentVersion" type="object | null">
        La versión vigente: `versionNumber`, `versionStatus`, `title`,
        `purposeSummary`, `plainLanguageNotice`, `legalBasisDetail`,
        `sensitiveLegalBasisDetail`, `consentModel`
        (`bundled` | `granular`), `noticeChecksum`, `publishedAt`…
      </Property>
      <Property name="reviewDueAt / lastReviewedAt" type="timestamp">
        El ciclo de revisión anual del RAT.
      </Property>
    </Properties>

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="GET" label="/api/processing-activities">

    ```bash {{ title: 'cURL' }}
    curl -G "https://app.edugoverna.com/api/processing-activities" \
      -H "x-api-key: {tu_api_key}" \
      -d limit=25
    ```

    ```js
    const { processingActivities, meta } = await fetch(
      'https://app.edugoverna.com/api/processing-activities?limit=25',
      { headers: { 'x-api-key': process.env.EDUGOVERNA_API_KEY } },
    ).then((r) => r.json())
    ```

    </CodeGroup>

    ```json {{ title: 'Respuesta (recortada)' }}
    {
      "processingActivities": [
        {
          "id": "a2f1…",
          "code": "ACT-004",
          "name": "Comunicaciones académicas",
          "status": "active",
          "treatmentRole": "controller",
          "actingOnBehalfOf": null,
          "lawfulBasis": "consent",
          "sensitiveLawfulBasis": null,
          "consentRequired": true,
          "involvesSensitiveData": false,
          "involvesMinorData": true,
          "includesAutomatedDecisions": false,
          "includesProfiling": false,
          "riskLevel": "medium",
          "isHighRisk": false,
          "currentVersionNumber": 2,
          "currentVersion": {
            "versionNumber": 2,
            "versionStatus": "published",
            "title": "v2 · salidas pedagógicas",
            "purposeSummary": "Informar y coordinar actividades académicas…",
            "consentModel": "bundled",
            "noticeChecksum": "9b1e…",
            "publishedAt": "2026-07-30T14:00:00.000Z"
          },
          "retentionPolicy": null,
          "dpiaAssessmentCount": 0,
          "reviewDueAt": "2027-07-30T14:00:00.000Z"
        }
      ],
      "meta": {
        "totalMatchedCount": 9,
        "limit": 25,
        "returnedCount": 9,
        "nextCursor": null,
        "hasMore": false
      }
    }
    ```

  </Col>
</Row>

El detalle (`GET /processing-activities/:processingActivityId`) agrega el historial `versions[]` (con `consentRequestCount` por versión), los `consentRequestCounts` de la actividad —el radio de impacto de la próxima publicación invalidante— y expande `currentVersion` con sus `dataCategories[]` y `recipients[]`.

---

## Publicar una versión nueva {{ tag: 'POST', label: '/api/processing-activities/:id/versions' }}

<Row>
  <Col>

    Requiere `processing_activities.write`. Acuña y **publica de inmediato** la versión siguiente (sellada con su `noticeChecksum`). Todos los campos son opcionales: lo omitido se arrastra de la versión anterior.

    Si el screening del Art. 15 ter exige EIPD y la actividad no tiene una viva, la publicación se rechaza con `409 DPIA_REQUIRED`; si la tiene pero sin descripción, necesidad o proporcionalidad, con `409 DPIA_INCOMPLETE` (ver [la EIPD mínima del núcleo](/api-organizacion/gobernanza#la-eipd-minima-del-nucleo)).

    ### Atributos opcionales frecuentes

    <Properties>
      <Property name="title / purposeSummary / plainLanguageNotice" type="string">
        El contenido normativo que verá el titular.
      </Property>
      <Property name="legalBasisDetail / sensitiveLegalBasisDetail" type="string">
        El fundamento de la base de licitud, y —cuando la actividad trata datos
        sensibles— el de la base sensible declarada en la actividad. Este
        último **sólo entra al `noticeChecksum` cuando trae contenido**, para
        no invalidar los sellos acuñados antes de que el campo existiera.
      </Property>
      <Property name="consentModel" type="string">
        `bundled` o `granular`; con `granular`, define `consentOptions[]`
        (`{ code, label, description? }`, hasta 20).
      </Property>
      <Property name="dataCategories / recipients" type="array">
        Categorías de datos y destinatarios de la versión (hasta 100 c/u);
        reemplazan los de la versión anterior si se envían.
      </Property>
      <Property name="invalidatesPreviousConsents" type="boolean">
        Si la publicación invalida los consentimientos previos y dispara la
        recaptura.
      </Property>
      <Property name="requiresGuardianAuthorization" type="boolean">
        Si la actividad necesita además la autorización escrita del apoderado
        (salidas, giras). Cuando un titular decide por sí mismo según su edad,
        junto a su solicitud se abre otra para el apoderado (ver
        [autorización del apoderado](/api-organizacion/consentimientos#autorizacion-del-apoderado)).
        Heredado de la versión anterior; por defecto `false`.
      </Property>
      <Property name="ageTransitionPolicy" type="string">
        `recertify_on_threshold_change` (por defecto: al cruzar los 14 o los
        16 años se invalida el consentimiento y se pide de nuevo a quien ahora
        decide) o `keep_until_expiry` (se mantiene hasta que venza o se
        revoque). Heredado de la versión anterior; la Consola lo muestra en la
        versión vigente y lo edita al publicar. Un partner puede fijarlo por su
        API, por eso conviene revisarlo.
      </Property>
    </Properties>

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="POST" label="/api/processing-activities/:id/versions">

    ```bash {{ title: 'cURL' }}
    curl -X POST "https://app.edugoverna.com/api/processing-activities/a2f1…/versions" \
      -H "x-api-key: {tu_api_key}" \
      -H "Content-Type: application/json" \
      -d '{
        "title": "v3 · incluye fotografías",
        "purposeSummary": "Comunicaciones académicas y registro fotográfico de actividades.",
        "invalidatesPreviousConsents": true
      }'
    ```

    ```js
    const { activity, invalidatedConsentRequestIds } = await fetch(
      'https://app.edugoverna.com/api/processing-activities/a2f1…/versions',
      {
        method: 'POST',
        headers: {
          'x-api-key': process.env.EDUGOVERNA_API_KEY,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          title: 'v3 · incluye fotografías',
          purposeSummary:
            'Comunicaciones académicas y registro fotográfico de actividades.',
          invalidatesPreviousConsents: true,
        }),
      },
    ).then((r) => r.json())
    ```

    </CodeGroup>

    ```json {{ title: 'Respuesta · 201 (recortada)' }}
    {
      "activity": {
        "id": "a2f1…",
        "currentVersionNumber": 3,
        "currentVersion": {
          "versionNumber": 3,
          "versionStatus": "published",
          "noticeChecksum": "c7a2…"
        }
      },
      "invalidatedConsentRequestIds": ["b9d2…", "b9d3…"]
    }
    ```

  </Col>
</Row>

---

## Las demás rutas del RAT

| Método y ruta                            | Permiso                       | Qué hace                                                                                             |
| ---------------------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------- |
| `POST /processing-activities`            | `processing_activities.write` | Crea la actividad con su v1 en borrador (`409 PROCESSING_ACTIVITY_CODE_CONFLICT` si el folio choca). |
| `PATCH /processing-activities/:id`       | `processing_activities.write` | Edita los atributos de la actividad.                                                                 |
| `POST /processing-activities/:id/review` | `processing_activities.write` | Registra la revisión anual sin publicar versión.                                                     |
| `DELETE /processing-activities/:id`      | `processing_activities.write` | Elimina una actividad (sin consentimientos vivos).                                                   |

Tanto `POST` como `PATCH` aceptan `treatmentRole`, `actingOnBehalfOf`, `sensitiveLawfulBasis` e `includesProfiling`, con las normalizaciones del servidor: `actingOnBehalfOf` se limpia al pasar el rol a `controller`, y `sensitiveLawfulBasis` se limpia al dejar `involvesSensitiveData` en `false`.

---

## Exportar el RAT {{ tag: 'GET', label: '/api/processing-activities/export' }}

<Row>
  <Col>

    Requiere `processing_activities.read` — es una **lectura**, así que funciona igual con sesión de la consola o con una API key. Responde **el archivo directo** con su `Content-Disposition: attachment`, no un JSON con un enlace.

    ### Parámetro

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

    El libro trae dos hojas: **RAT** (una fila por actividad, con carátula del responsable, rol, bases de licitud, banderas de riesgo, categorías, destinatarios, retención y estado de la evaluación de impacto) y **Criterios** (los vocabularios y las reglas con que se llenó, incluidas las letras del Art. 15 ter y la versión de la metodología de riesgo). 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. La fecha va en el nombre del archivo
      (`rat-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/processing-activities/export">

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

    ```js
    const response = await fetch(
      'https://app.edugoverna.com/api/processing-activities/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="rat-2026-08-27.xlsx"
    ```

  </Col>
</Row>

<Note>
  La ruta se resuelve antes que `/processing-activities/:processingActivityId`,
  así que `export` nunca se lee como el id de una actividad.
</Note>

---

## Evaluaciones de impacto (EIPD) {{ tag: 'GET', label: '/api/dpia' }}

<Row>
  <Col>

    Con una API key las EIPD son de **sólo lectura** (`dpia.read`): toda la autoría —crear, editar escenarios, recomputar, aprobar— exige `dpia.write`, que es un permiso de sesión.

    `GET /dpia` responde `{ assessments }` (filtros `processingActivityId`, `status`, `reviewOverdue=true`); cada evaluación trae su folio (`assessmentCode`), estado (`draft`, `in_review`, `approved`, `under_revision`), niveles de riesgo inherente y residual, y `methodologyVersion` — la metodología vigente es `eg-2026.08`: una **matriz declarada 4×4** (probabilidad × impacto, valores 1–4) que resuelve `low`/`medium`/`high`/`critical`, agregando siempre el peor escenario, nunca un promedio. `GET /dpia/:assessmentId` agrega los `scenarios` con sus valores inherentes y residuales.

  </Col>
  <Col sticky>

    <CodeGroup title="Solicitud" tag="GET" label="/api/dpia/screening">

    ```bash {{ title: 'cURL' }}
    curl -G "https://app.edugoverna.com/api/dpia/screening" \
      -H "x-api-key: {tu_api_key}" \
      -d processingActivityId=a2f1…
    ```

    ```js
    const screening = await fetch(
      'https://app.edugoverna.com/api/dpia/screening?processingActivityId=a2f1…',
      { headers: { 'x-api-key': process.env.EDUGOVERNA_API_KEY } },
    ).then((r) => r.json())
    ```

    </CodeGroup>

    ```json {{ title: 'Respuesta (recortada)' }}
    {
      "activity": { "id": "a2f1…", "name": "Videovigilancia patios", "code": "ACT-011" },
      "screening": {
        "level": "required",
        "letters": ["c"],
        "factors": ["declared_high_risk"],
        "reasons": ["La actividad declara observación sistemática…"]
      },
      "letters": [
        {
          "letter": "c",
          "legalText": "…",
          "schoolMeaning": "Cámaras u observación sistemática de espacios…",
          "reason": "…"
        }
      ],
      "existingAssessmentId": null,
      "existingAssessmentCode": null,
      "suggestedTemplates": [
        { "key": "cctv", "name": "Videovigilancia escolar", "whenToUse": "…" }
      ]
    }
    ```

  </Col>
</Row>

### Cómo leer el screening

`existingAssessmentId` y `existingAssessmentCode` apuntan a la evaluación **viva** más reciente de la actividad (`draft`, `in_review`, `approved` o `under_revision`); una archivada o reemplazada no cuenta. Si vienen con valor, lo que corresponde es continuar esa evaluación, no abrir otra.

`level` es la respuesta (`required`, `recommended` o `not_required`) y llega acompañada de por qué:

- **`letters`** son los casos que el Art. 15 ter *"requiere siempre"*: `a` decisiones automatizadas con efectos jurídicos significativos · `b` tratamiento masivo o a gran escala · `c` monitoreo sistemático de una zona de acceso público · `d` datos sensibles bajo una excepción del consentimiento. Cualquiera de ellas deja `level: "required"`.
- **`factors`** son las señales del inciso general: `declared_high_risk`, `sensitive_data`, `international_transfer`, `profiling`, `minor_data`.

`minor_data` y `profiling` son **agravantes, no gatillos**. Por sí solos dejan la evaluación en `recommended`; acompañando a cualquier otra señal, entran a `reasons` y la evaluación queda `required`.

Que `includesProfiling` no dispare por sí solo es deliberado: la letra a) exige *"efectos jurídicos significativos"*, y elaborar perfiles sin decidir nada sobre el estudiante no la configura. Eso sí, obliga igualmente a **informarlo** en la política pública (Art. 14 ter l), que pide declarar la existencia de decisiones automatizadas *incluida la elaboración de perfiles*). Un colegio que además decide sobre esos perfiles marca `includesAutomatedDecisions`, y ahí sí es la letra a).

### El piso de impacto para NNA

Al escribir un escenario (`POST /dpia/:assessmentId/scenarios`, sesión + `dpia.write`) rige un piso: si la evaluación cuelga de una actividad con `involvesSensitiveData: true`, el escenario declara `affectedSubjectTypes` con `student` y el `inherentImpact` queda **bajo 3 (Significativo)**, hay que fundamentar la calificación por escrito en `impactRationale`.

La regla es **asimétrica según el origen de la fila**:

| `origin` | Qué pasa |
| --- | --- |
| `manual` (por defecto) | Sin `impactRationale`, la escritura se rechaza con `422 NNA_IMPACT_JUSTIFICATION_REQUIRED`. |
| `template` · `suggested` | El escenario se guarda y la respuesta `201` trae `warnings: ["nna_impact_floor"]`. Llegan pre-puntuadas y la persona las revisa igual. |

El piso **nunca altera el puntaje**: no sube el impacto ni cambia el nivel. Sólo exige que la calificación baja quede defendida, que es lo que la hace sostenible ante la Agencia en los dos sentidos.

Los escenarios aceptan además `probabilityRationale` e `impactRationale` como fundamento separado de cada eje (hasta 2.000 caracteres cada uno), distintos de la `justification` general del escenario.

### Rutas de EIPD

| Método y ruta                                                                                                                                     | Permiso      | Qué hace                                                              |
| ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | --------------------------------------------------------------------- |
| `GET /dpia`                                                                                                                                       | `dpia.read`  | Lista las evaluaciones.                                               |
| `GET /dpia/:assessmentId`                                                                                                                         | `dpia.read`  | Detalle con escenarios de riesgo.                                     |
| `GET /dpia/screening?processingActivityId=…`                                                                                                      | `dpia.read`  | El test del Art. 15 ter: si la actividad exige EIPD y por qué letras. |
| `GET /dpia/templates`                                                                                                                             | `dpia.read`  | Plantillas disponibles (videovigilancia, plataformas, etc.).          |
| `POST /dpia` · `PATCH /dpia/:id` · escenarios · medidas · `recompute` · `approval` · `revision` · `reviews` · `document` · `templates/:key/adopt` | `dpia.write` | **Sólo disponible con sesión de la consola.**                         |

`POST /dpia`, `PATCH /dpia/:id` y `POST /dpia/templates/:key/adopt` aceptan `interviewConversationId` (uuid): la conversación de la entrevista que redactó la evaluación, que queda en `metadata.interviewConversationId`. El `adopt` acepta además `summary`, `necessityAssessment`, `proportionalityAssessment` y `mitigationPlan`, que reemplazan el borrador de la plantilla sin marcar ninguna sección como revisada. Al editar, una sección queda marcada «editada» sólo si su texto cambió de verdad.

<Note>
  La disponibilidad de la EIPD depende del plan contratado: en organizaciones
  sin el módulo licenciado estas rutas responden `403 MODULE_NOT_LICENSED`.
</Note>

Los escenarios de una EIPD comparten catálogo de medidas y matriz con el [registro de riesgos](/api-organizacion/riesgos).
