---
title: "Actividades de tratamiento · Partner API"
description: "Referencia de los endpoints de actividades de tratamiento de la API Partner: el RAT versionado, sus categorías de datos, destinatarios y publicación."
url: "https://docs.edugoverna.com/actividades-tratamiento"
---
# Actividades de tratamiento

Una actividad de tratamiento describe, en el registro de actividades de tratamiento (RAT), un uso concreto de datos personales: su finalidad, base de licitud, categorías de datos y destinatarios. Es también el aviso que el titular lee al consentir — por eso está **versionada y sellada**: cada versión lleva un checksum de su contenido y, una vez publicada, es inmutable. En esta página revisamos los endpoints para declarar tus actividades como encargado, componer sus versiones y publicarlas. {{ className: 'lead' }}

<Note>
  Todas las rutas de esta página cuelgan de la URL base
  `https://app.edugoverna.com/api/partner/v1/organizations/:organizationId`. Las
  versiones publicadas son las que referencian los endpoints de
  [Consentimientos](/consentimientos) y de [Campañas
  compartidas](/campanas-compartidas).
</Note>

## El modelo de versionado

El flujo típico tiene tres pasos:

1. **Crear la actividad** (`POST /processing-activities`) crea la actividad con su **versión 1 en borrador** (`versionStatus: "draft"`).
2. **Componer el borrador**: adjuntar categorías de datos y destinatarios a esa versión mientras siga en borrador. Una versión publicada rechaza estas escrituras con `409 PROCESSING_ACTIVITY_VERSION_PUBLISHED` — su contenido es lo que los titulares leyeron.
3. **Publicar** (`POST .../publish`) sella la versión, promueve la actividad de `draft` a `active` y la deja disponible para pedir consentimientos.

Para modificar una actividad ya publicada se crea una **versión nueva** (`POST .../versions`), que nace ya publicada, hereda las declaraciones de la versión anterior salvo que se sobrescriban y — por defecto — **invalida los consentimientos vigentes** de las versiones previas, disparando su recaptura. Es la mecánica que garantiza que ningún consentimiento siga vivo sobre un aviso que ya no describe el tratamiento real.

---

## Listar actividades de tratamiento {{ tag: 'GET', label: '/v1/organizations/:organizationId/processing-activities' }}

<Row>
  <Col>

    Devuelve una página de actividades de la organización, de la más reciente a la más antigua, cada una con su versión vigente (`currentVersion`) y su política de retención.

    Permiso requerido: `partner.processing_activities.read` · Alcance de conexión: `processing_activities:read`.

    ### Campos del RAT ampliado

    <Properties>
      <Property name="treatmentRole" type="string">
        El rol de la organización en la actividad: `controller`, `processor`
        (encargado o mandatario, Art. 15 bis) o `both`. **Siempre viene**: la
        columna tiene `controller` por defecto.
      </Property>
      <Property name="actingOnBehalfOf" type="string | null">
        Por cuenta de quién se trata, cuando el rol no es `controller`.
      </Property>
      <Property name="sensitiveLawfulBasis" type="string | null">
        La base de licitud propia de los datos sensibles, distinta del régimen
        general de `lawfulBasis`: `express_consent`, `legal_authorization`,
        `manifestly_public`, `vital_or_health_emergency`,
        `judicial_or_rights_defense` o `nonprofit_members`.
      </Property>
      <Property name="includesProfiling" type="boolean">
        Elaboración de perfiles **sin** decisiones con efectos jurídicos. Es
        distinta de `includesAutomatedDecisions` y se lee aparte.
      </Property>
      <Property name="currentVersion.sensitiveLegalBasisDetail" type="string | null">
        El fundamento escrito de esa base sensible, dentro de la versión.
      </Property>
    </Properties>

    <Note>
      El rol importa más en este carril que en ningún otro: un partner
      normalmente trata **por cuenta del colegio** (encargado, Art. 15 bis), y
      sin `treatmentRole` + `actingOnBehalfOf` su RAT no puede decirlo. Los
      cinco campos se escriben al crear la actividad (y
      `sensitiveLegalBasisDetail` también al crear una versión); no existe un
      `PATCH` de partner sobre la actividad, así que un cambio posterior de rol
      se hace desde la consola del colegio.
    </Note>

    ### Parámetros de consulta opcionales

    <Properties>
      <Property name="limit" type="integer">
        Tamaño de página. Por defecto 100, máximo 250.
      </Property>
      <Property name="cursor" type="string">
        Cursor de la página anterior (`meta.nextCursor`). Ver
        [Paginación](/paginacion).
      </Property>
    </Properties>

  </Col>
  <Col sticky>

    <CodeGroup title="Request" tag="GET" label="/v1/.../processing-activities">

    ```bash {{ title: 'cURL' }}
    curl -G https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/processing-activities \
      -H "Authorization: Bearer {keyId}.{secret}" \
      -d limit=50
    ```

    ```js
    const response = await fetch(
      'https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/processing-activities?limit=50',
      { headers: { Authorization: `Bearer ${keyId}.${secret}` } },
    )
    const { processingActivities, meta } = await response.json()
    ```

    </CodeGroup>

    ```json {{ title: 'Response' }}
    {
      "processingActivities": [
        {
          "id": "pa_71b2",
          "organizationId": "org_2f7a",
          "code": "plataforma-lectura",
          "name": "Plataforma de fomento lector",
          "status": "active",
          "treatmentRole": "controller",
          "actingOnBehalfOf": null,
          "lawfulBasis": "consent",
          "sensitiveLawfulBasis": null,
          "consentRequired": true,
          "involvesSensitiveData": false,
          "involvesMinorData": true,
          "includesAutomatedDecisions": false,
          "includesProfiling": false,
          "includesInternationalTransfers": false,
          "includesMonitoring": false,
          "isHighRisk": false,
          "riskLevel": "medium",
          "currentVersionNumber": 2,
          "currentVersion": {
            "id": "3f2e1d0c-9b8a-4765-b432-1a0f9e8d7c6b",
            "versionNumber": 2,
            "versionStatus": "published",
            "title": "Plataforma de fomento lector",
            "consentModel": "bundled",
            "noticeChecksum": "sha256:…",
            "publishedAt": "2026-07-10T14:00:00.000Z",
            "sensitiveLegalBasisDetail": null,
            "invalidatesPreviousConsents": true,
            "ageTransitionPolicy": "recertify_on_threshold_change"
          },
          "retentionPolicy": null,
          "dpiaAssessmentCount": 0
          // ...
        }
      ],
      "meta": {
        "totalMatchedCount": 3,
        "limit": 50,
        "returnedCount": 3,
        "nextCursor": null,
        "hasMore": false
      }
    }
    ```

  </Col>
</Row>

---

## Crear una actividad de tratamiento {{ tag: 'POST', label: '/v1/organizations/:organizationId/processing-activities' }}

<Row>
  <Col>

    Crea la actividad y su versión 1 **en borrador**. Responde `201` con la actividad serializada; el evento de auditoría `created` queda en el registro de la organización. Si el `code` ya existe, `409 PROCESSING_ACTIVITY_CODE_CONFLICT`.

    Permiso requerido: `partner.processing_activities.write` · Alcance de conexión: `processing_activities:write`.

    ### Atributos requeridos

    <Properties>
      <Property name="code" type="string">
        Código estable de la actividad (2–80). Es la llave con la que luego
        referencias la actividad al pedir consentimientos.
      </Property>
      <Property name="name" type="string">
        Nombre de la actividad (2–160).
      </Property>
    </Properties>

    ### Atributos opcionales

    <Properties>
      <Property name="treatmentRole" type="string">
        `controller`, `processor` o `both`. Por defecto `"controller"`.
      </Property>
      <Property name="actingOnBehalfOf" type="string">
        Por cuenta de quién se trata (máx. 300). El servidor lo normaliza a
        `null` cuando el rol es `controller`.
      </Property>
      <Property name="lawfulBasis" type="string">
        Base de licitud. Por defecto `"consent"`.
      </Property>
      <Property name="sensitiveLawfulBasis" type="string">
        Base del régimen sensible. Sólo se conserva con
        `involvesSensitiveData: true`.
      </Property>
      <Property name="consentRequired" type="boolean">
        Si exige consentimiento. Por defecto `true`.
      </Property>
      <Property name="involvesSensitiveData" type="boolean">
        Trata datos sensibles. Por defecto `false`.
      </Property>
      <Property name="involvesMinorData" type="boolean">
        Trata datos de menores. Por defecto `true`.
      </Property>
      <Property name="includesAutomatedDecisions" type="boolean">
        Incluye decisiones automatizadas. Por defecto `false`.
      </Property>
      <Property name="includesProfiling" type="boolean">
        Elabora perfiles sin decisión con efectos jurídicos. Por defecto
        `false`. Es una bandera distinta de la anterior.
      </Property>
      <Property name="includesInternationalTransfers" type="boolean">
        Incluye transferencias internacionales. Por defecto `false`.
      </Property>
      <Property name="includesMonitoring" type="boolean">
        Incluye monitoreo u observación sistemática. Por defecto `false`.
      </Property>
      <Property name="isHighRisk" type="boolean">
        Marca de alto riesgo. Por defecto `false`.
      </Property>
      <Property name="retentionPolicyId" type="string">
        Política de retención de la organización (UUID). Si no existe,
        `404 RETENTION_POLICY_NOT_FOUND`.
      </Property>
      <Property name="metadata" type="object">
        Objeto libre.
      </Property>
      <Property name="currentVersion" type="object">
        Contenido de la versión 1: `versionLabel`, `title`, `purposeSummary`,
        `plainLanguageNotice` (el aviso en lenguaje claro, máx. 5000),
        `legalBasisDetail`, `sensitiveLegalBasisDetail` (el fundamento de la
        base sensible, máx. 2000), `consentModel` (por defecto `"bundled"`; con
        `"granular"` se exige `consentOptions`), `consentOptions` (hasta 20
        opciones `{ code, label, description, sortOrder }`),
        `retentionSummary`, `transferSummary`, `automationSummary`,
        `invalidatesPreviousConsents` (por defecto `true`),
        `requiresGuardianAuthorization` (por defecto `false`),
        `ageTransitionPolicy` y `metadata`.
      </Property>
      <Property name="currentVersion.requiresGuardianAuthorization" type="boolean">
        Marca las actividades que además necesitan la autorización escrita del
        apoderado (salidas, giras). La usa el colegio en su consola: la API de
        socios sólo la guarda y la devuelve, nunca abre por ella solicitudes del
        apoderado ni emite eventos `consent.*` adicionales.
      </Property>
      <Property name="currentVersion.ageTransitionPolicy" type="string">
        Qué pasa cuando el titular cruza la edad en que cambia quién decide (14
        años para datos generales, 16 para sensibles). Quién decide se fija al
        emitir cada solicitud, y un «sí» dado por quien correspondía a esa fecha
        nunca se anula por cumplir años: no hay barrido que lo revise.
        `recertify_on_threshold_change` (por defecto) vuelve a mirarlo cuando
        se actúa sobre la actividad: una solicitud **pendiente** se redirige a
        quien decide hoy en su próximo recordatorio o reenvío, o al intentar
        firmarla (`409 CONSENT_AUTHORITY_CHANGED`), con el webhook
        `consent.recaptured` (`context.reason = "age_threshold"`); y una
        campaña nueva le pide la decisión al titular aunque el apoderado ya
        hubiera otorgado, sin anular ese «sí» hasta que el titular decida.
        `keep_until_expiry`: nada de eso; la decisión del apoderado se reutiliza
        hasta que se revoque.
      </Property>
    </Properties>

  </Col>
  <Col sticky>

    <CodeGroup title="Request" tag="POST" label="/v1/.../processing-activities">

    ```bash {{ title: 'cURL' }}
    curl https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/processing-activities \
      -H "Authorization: Bearer {keyId}.{secret}" \
      -H "Content-Type: application/json" \
      -d '{
        "code": "plataforma-lectura",
        "name": "Plataforma de fomento lector",
        "currentVersion": {
          "purposeSummary": "Cuentas de estudiante para asignar lecturas y medir avance.",
          "plainLanguageNotice": "Usaremos el nombre y curso de tu hijo/a para crear su cuenta de lectura…"
        }
      }'
    ```

    ```js
    const response = await fetch(
      'https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/processing-activities',
      {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${keyId}.${secret}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          code: 'plataforma-lectura',
          name: 'Plataforma de fomento lector',
          currentVersion: {
            purposeSummary:
              'Cuentas de estudiante para asignar lecturas y medir avance.',
            plainLanguageNotice:
              'Usaremos el nombre y curso de tu hijo/a para crear su cuenta de lectura…',
          },
        }),
      },
    )
    const activity = await response.json()
    ```

    </CodeGroup>

    ```json {{ title: 'Response' }}
    {
      "id": "pa_71b2",
      "code": "plataforma-lectura",
      "name": "Plataforma de fomento lector",
      "status": "draft",
      "lawfulBasis": "consent",
      "consentRequired": true,
      "currentVersionNumber": 1,
      "currentVersion": {
        "id": "v1_8e4d",
        "versionNumber": 1,
        "versionStatus": "draft",
        "title": "Plataforma de fomento lector",
        "consentModel": "bundled",
        "noticeChecksum": "sha256:…",
        "publishedAt": null,
        "invalidatesPreviousConsents": true,
        "ageTransitionPolicy": "recertify_on_threshold_change"
      },
      "retentionPolicy": null
      // ...
    }
    ```

  </Col>
</Row>

---

## Obtener una actividad de tratamiento {{ tag: 'GET', label: '/v1/organizations/:organizationId/processing-activities/:processingActivityId' }}

<Row>
  <Col>

    Devuelve la ficha completa de una actividad: su versión vigente con las categorías de datos, destinatarios y opciones de consentimiento declaradas, el historial de versiones (`versions`) y el recuento de solicitudes de consentimiento por versión (`consentRequestCounts`) — el radio de impacto que conviene mirar antes de publicar una versión que invalida consentimientos.

    Permiso requerido: `partner.processing_activities.read` · Alcance de conexión: `processing_activities:read`.

    Si la actividad no pertenece a la organización, responde `404 PROCESSING_ACTIVITY_NOT_FOUND`.

  </Col>
  <Col sticky>

    <CodeGroup title="Request" tag="GET" label="/v1/.../processing-activities/pa_71b2">

    ```bash {{ title: 'cURL' }}
    curl https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/processing-activities/pa_71b2 \
      -H "Authorization: Bearer {keyId}.{secret}"
    ```

    ```js
    const response = await fetch(
      'https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/processing-activities/pa_71b2',
      { headers: { Authorization: `Bearer ${keyId}.${secret}` } },
    )
    const activity = await response.json()
    ```

    </CodeGroup>

    ```json {{ title: 'Response' }}
    {
      "id": "pa_71b2",
      "code": "plataforma-lectura",
      "name": "Plataforma de fomento lector",
      "status": "active",
      "currentVersionNumber": 2,
      "currentVersion": {
        "id": "3f2e1d0c-9b8a-4765-b432-1a0f9e8d7c6b",
        "versionNumber": 2,
        "versionStatus": "published",
        "consentModel": "bundled",
        "dataCategories": [
          {
            "id": "adc_11",
            "dataCategoryId": "dc_a1",
            "subjectType": "student",
            "requiresConsent": true,
            "isSensitiveOverride": null,
            "retentionPolicyId": null,
            "categoryCode": "identificacion-basica",
            "categoryName": "Identificación básica",
            "categoryIsSensitive": false,
            "categorySensitivityLevel": "standard"
          }
        ],
        "recipients": [
          {
            "id": "ar_31",
            "recipientType": "processor",
            "name": "Proveedor de hosting",
            "legalBasis": "contrato de encargo",
            "isInternationalTransfer": false,
            "transferDestinationId": null,
            "destinationName": null,
            "destinationCountryCode": null
          }
        ],
        "consentOptions": []
      },
      "versions": [
        { "versionNumber": 2, "versionStatus": "published" },
        { "versionNumber": 1, "versionStatus": "published" }
      ],
      "consentRequestCounts": {
        // totales de solicitudes por versión
      }
      // ...
    }
    ```

  </Col>
</Row>

---

## Crear una versión nueva {{ tag: 'POST', label: '/v1/organizations/:organizationId/processing-activities/:processingActivityId/versions' }}

<Row>
  <Col>

    Crea la versión siguiente de la actividad, **ya publicada**, y la deja como versión vigente. Todos los campos son opcionales: lo que se omite se hereda de la versión anterior — incluidas sus categorías de datos, destinatarios y opciones de consentimiento. Responde `201` con `{ activity, invalidatedConsentRequestIds }`.

    Si la versión vigente es un borrador nunca publicado (una actividad recién creada), esta llamada la **publica en el lugar**: misma versión y mismo número, sin crear una siguiente, y sin invalidar consentimientos porque no hay versiones anteriores.

    Permiso requerido: `partner.processing_activities.write` · Alcance de conexión: `processing_activities:write`.

    Cuando `invalidatesPreviousConsents` es `true` (el valor heredado por defecto), las solicitudes `pending` y `granted` de las versiones anteriores pasan a `invalidated`, sus decisiones otorgadas se revocan y cada titular queda marcado para **recaptura** — los IDs afectados vuelven en `invalidatedConsentRequestIds`. Publica una versión nueva sólo cuando el cambio de aviso realmente lo amerite.

    ### Atributos opcionales

    <Properties>
      <Property name="versionLabel" type="string">
        Etiqueta legible de la versión (máx. 80).
      </Property>
      <Property name="title" type="string">
        Título del aviso (máx. 160).
      </Property>
      <Property name="purposeSummary" type="string">
        Resumen de la finalidad (máx. 2000).
      </Property>
      <Property name="plainLanguageNotice" type="string">
        Aviso en lenguaje claro (máx. 5000).
      </Property>
      <Property name="legalBasisDetail" type="string">
        Detalle de la base de licitud (máx. 1000).
      </Property>
      <Property name="sensitiveLegalBasisDetail" type="string">
        Fundamento escrito de la base del régimen sensible (máx. 2000). 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` se exige `consentOptions`.
        Para volver de granular a bundled envía `consentModel: "bundled"`
        junto con `consentOptions: []` — si omites `consentOptions` se copian
        las opciones de la versión anterior, y una versión `bundled` con
        opciones se rechaza con `422 CONSENT_OPTIONS_NOT_ALLOWED`.
      </Property>
      <Property name="consentOptions" type="array">
        Hasta 20 opciones `{ code, label, description, sortOrder }`. Si se
        omite, se copian las de la versión anterior.
      </Property>
      <Property name="retentionSummary" type="string">
        Resumen de retención (máx. 1000).
      </Property>
      <Property name="transferSummary" type="string">
        Resumen de transferencias (máx. 1000).
      </Property>
      <Property name="automationSummary" type="string">
        Resumen de decisiones automatizadas (máx. 1000).
      </Property>
      <Property name="invalidatesPreviousConsents" type="boolean">
        Si publica invalidando los consentimientos previos. Heredado de la
        versión anterior; `true` por defecto. La recaptura que dispara emite
        `consent.recaptured` con `context.reason = "notice_version"`.
      </Property>
      <Property name="requiresGuardianAuthorization" type="boolean">
        Si la actividad requiere además la autorización del apoderado. Heredado
        de la versión anterior; `false` por defecto. Sólo informativo para
        socios: las solicitudes del apoderado son internas del colegio.
      </Property>
      <Property name="ageTransitionPolicy" type="string">
        `recertify_on_threshold_change` o `keep_until_expiry`. Heredado de la
        versión anterior. Ver
        [la creación de la actividad](#crear-una-actividad-de-tratamiento).
      </Property>
      <Property name="retentionPolicyId" type="string">
        Política de retención (UUID).
      </Property>
      <Property name="metadata" type="object">
        Objeto libre.
      </Property>
    </Properties>

  </Col>
  <Col sticky>

    <CodeGroup title="Request" tag="POST" label="/v1/.../processing-activities/pa_71b2/versions">

    ```bash {{ title: 'cURL' }}
    curl https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/processing-activities/pa_71b2/versions \
      -H "Authorization: Bearer {keyId}.{secret}" \
      -H "Content-Type: application/json" \
      -d '{
        "versionLabel": "2026-2",
        "purposeSummary": "Se agrega el módulo de audiolibros al servicio.",
        "invalidatesPreviousConsents": true
      }'
    ```

    ```js
    const response = await fetch(
      'https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/processing-activities/pa_71b2/versions',
      {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${keyId}.${secret}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          versionLabel: '2026-2',
          purposeSummary: 'Se agrega el módulo de audiolibros al servicio.',
          invalidatesPreviousConsents: true,
        }),
      },
    )
    const { activity, invalidatedConsentRequestIds } = await response.json()
    ```

    </CodeGroup>

    ```json {{ title: 'Response' }}
    {
      "activity": {
        "id": "pa_71b2",
        "code": "plataforma-lectura",
        "status": "active",
        "currentVersionNumber": 3,
        "currentVersion": {
          "versionNumber": 3,
          "versionStatus": "published",
          "versionLabel": "2026-2",
          "publishedAt": "2026-08-25T12:30:00.000Z",
          "invalidatesPreviousConsents": true
        }
        // ...
      },
      "invalidatedConsentRequestIds": [
        "c4a1b2d3-e5f6-4a7b-8c9d-0e1f2a3b4c5d"
      ]
    }
    ```

  </Col>
</Row>

---

## Declarar una categoría de datos {{ tag: 'POST', label: '/v1/organizations/:organizationId/processing-activities/:processingActivityId/versions/:processingActivityVersionId/data-categories' }}

<Row>
  <Col>

    Vincula una categoría de datos a una versión **en borrador**. Puedes referenciar una categoría existente por `dataCategoryId` o crear una nueva en la misma llamada con `newDataCategory`; omitir ambas responde `422 VALIDATION_ERROR`.

    Permiso requerido: `partner.processing_activities.write` · Alcance de conexión: `processing_activities:write`.

    El vínculo es idempotente sobre la tupla (versión, categoría, `subjectType`): repetir la declaración devuelve el vínculo existente en lugar de fallar. Sobre una versión publicada, `409 PROCESSING_ACTIVITY_VERSION_PUBLISHED`.

    ### Atributos opcionales

    <Properties>
      <Property name="dataCategoryId" type="string">
        UUID de una categoría existente de la organización.
      </Property>
      <Property name="newDataCategory" type="object">
        Categoría a crear: `code` y `name` (requeridos), `description`,
        `categoryGroup`, `subjectType`, `sensitivityLevel`, `isSensitive`,
        `requiresExpressConsent`, `defaultRetentionDays`.
      </Property>
      <Property name="subjectType" type="string">
        Tipo de titular al que aplica. Por defecto `"student"`.
      </Property>
      <Property name="requiresConsent" type="boolean">
        Si esta categoría requiere consentimiento dentro de la actividad. Por
        defecto `true`.
      </Property>
      <Property name="isSensitiveOverride" type="boolean">
        Sobrescribe la sensibilidad de la categoría en esta actividad
        (`null` = usar la de la categoría).
      </Property>
      <Property name="retentionPolicyId" type="string">
        Política de retención específica para esta categoría.
      </Property>
    </Properties>

  </Col>
  <Col sticky>

    <CodeGroup title="Request" tag="POST" label="/v1/.../versions/v1_8e4d/data-categories">

    ```bash {{ title: 'cURL' }}
    curl https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/processing-activities/pa_71b2/versions/v1_8e4d/data-categories \
      -H "Authorization: Bearer {keyId}.{secret}" \
      -H "Content-Type: application/json" \
      -d '{
        "newDataCategory": {
          "code": "avance-lector",
          "name": "Avance lector",
          "description": "Libros leídos y tiempo de lectura."
        },
        "subjectType": "student"
      }'
    ```

    ```js
    const response = await fetch(
      'https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/processing-activities/pa_71b2/versions/v1_8e4d/data-categories',
      {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${keyId}.${secret}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          newDataCategory: {
            code: 'avance-lector',
            name: 'Avance lector',
            description: 'Libros leídos y tiempo de lectura.',
          },
          subjectType: 'student',
        }),
      },
    )
    const link = await response.json()
    ```

    </CodeGroup>

    ```json {{ title: 'Response' }}
    {
      "id": "adc_12",
      "organizationId": "org_2f7a",
      "processingActivityVersionId": "v1_8e4d",
      "dataCategoryId": "dc_b7",
      "subjectType": "student",
      "requiresConsent": true,
      "isSensitiveOverride": null,
      "retentionPolicyId": null,
      "createdAt": "2026-08-25T12:30:00.000Z"
    }
    ```

  </Col>
</Row>

---

## Declarar un destinatario {{ tag: 'POST', label: '/v1/organizations/:organizationId/processing-activities/:processingActivityId/versions/:processingActivityVersionId/recipients' }}

<Row>
  <Col>

    Declara un destinatario de los datos en una versión **en borrador** (un subencargado, un servicio interno, un destino de transferencia internacional). Responde `201` con el destinatario creado. Sobre una versión publicada, `409 PROCESSING_ACTIVITY_VERSION_PUBLISHED`.

    Permiso requerido: `partner.processing_activities.write` · Alcance de conexión: `processing_activities:write`.

    ### Atributos requeridos

    <Properties>
      <Property name="name" type="string">
        Nombre del destinatario (2–160).
      </Property>
    </Properties>

    ### Atributos opcionales

    <Properties>
      <Property name="recipientType" type="string">
        Tipo de destinatario. Por defecto `"internal"`.
      </Property>
      <Property name="legalBasis" type="string">
        Base que habilita la comunicación de datos (máx. 160).
      </Property>
      <Property name="isInternationalTransfer" type="boolean">
        Si implica transferencia internacional. Por defecto `false`.
      </Property>
      <Property name="notes" type="string">
        Notas (máx. 1000).
      </Property>
    </Properties>

  </Col>
  <Col sticky>

    <CodeGroup title="Request" tag="POST" label="/v1/.../versions/v1_8e4d/recipients">

    ```bash {{ title: 'cURL' }}
    curl https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/processing-activities/pa_71b2/versions/v1_8e4d/recipients \
      -H "Authorization: Bearer {keyId}.{secret}" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Proveedor de hosting",
        "recipientType": "processor",
        "legalBasis": "contrato de encargo"
      }'
    ```

    ```js
    const response = await fetch(
      'https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/processing-activities/pa_71b2/versions/v1_8e4d/recipients',
      {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${keyId}.${secret}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          name: 'Proveedor de hosting',
          recipientType: 'processor',
          legalBasis: 'contrato de encargo',
        }),
      },
    )
    const recipient = await response.json()
    ```

    </CodeGroup>

    ```json {{ title: 'Response' }}
    {
      "id": "ar_32",
      "organizationId": "org_2f7a",
      "processingActivityVersionId": "v1_8e4d",
      "recipientType": "processor",
      "name": "Proveedor de hosting",
      "legalBasis": "contrato de encargo",
      "isInternationalTransfer": false,
      "transferDestinationId": null,
      "partnerAccountId": null,
      "notes": null,
      "createdAt": "2026-08-25T12:30:00.000Z"
    }
    ```

  </Col>
</Row>

---

## Publicar una versión {{ tag: 'POST', label: '/v1/organizations/:organizationId/processing-activities/:processingActivityId/versions/:processingActivityVersionId/publish' }}

<Row>
  <Col>

    Sella una versión en borrador: la marca `published`, la deja como versión vigente y promueve la actividad de `draft` a `active`. Desde ese momento la versión es inmutable y puede referenciarse al crear solicitudes de consentimiento. La operación es **idempotente**: republicar una versión ya publicada es un no-op que devuelve la versión tal cual.

    Permiso requerido: `partner.processing_activities.write` · Alcance de conexión: `processing_activities:write`.

    Publicar cuenta como revisión del RAT: actualiza el reloj de revisión anual de la actividad. Si la versión declara `consentModel: "granular"` sin opciones de consentimiento, la publicación se rechaza — el mismo control que rige al crearla.

  </Col>
  <Col sticky>

    <CodeGroup title="Request" tag="POST" label="/v1/.../versions/v1_8e4d/publish">

    ```bash {{ title: 'cURL' }}
    curl -X POST https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/processing-activities/pa_71b2/versions/v1_8e4d/publish \
      -H "Authorization: Bearer {keyId}.{secret}"
    ```

    ```js
    const response = await fetch(
      'https://app.edugoverna.com/api/partner/v1/organizations/org_2f7a/processing-activities/pa_71b2/versions/v1_8e4d/publish',
      {
        method: 'POST',
        headers: { Authorization: `Bearer ${keyId}.${secret}` },
      },
    )
    const version = await response.json()
    ```

    </CodeGroup>

    ```json {{ title: 'Response' }}
    {
      "id": "v1_8e4d",
      "organizationId": "org_2f7a",
      "processingActivityId": "pa_71b2",
      "versionNumber": 1,
      "versionStatus": "published",
      "title": "Plataforma de fomento lector",
      "consentModel": "bundled",
      "noticeChecksum": "sha256:…",
      "publishedAt": "2026-08-25T12:30:00.000Z",
      "invalidatesPreviousConsents": true
      // ...
    }
    ```

  </Col>
</Row>
