En la lección anterior protegimos a DocuBot de quienes intentan manipularlo. Esta lección mira en la otra dirección: qué hace DocuBot con los datos que le confían. Cada llamada a la API del proveedor empaqueta el system prompt, el historial de la conversación, la documentación interna recuperada y datos de proyectos, y lo envía a servidores de un tercero. Para un desarrollador esto es una llamada HTTPS más; para la organización es una transferencia de datos con implicaciones contractuales y, a veces, legales. En el módulo 1 (01-04) dejamos la privacidad apuntada a alto nivel con remisión a esta lección; toca cumplir la promesa: entender qué sale exactamente, minimizar lo que sale, conocer las garantías contractuales disponibles y situar todo esto en el marco del GDPR y el AI Act europeo.

⚠️ Advertencia: esta lección es material formativo y no constituye asesoramiento jurídico. La aplicación del GDPR, del AI Act o de cualquier otra norma depende del caso concreto (tipo de datos, finalidad, sector, países implicados). Antes de implantar en producción un sistema que procese datos personales o tenga implicaciones regulatorias, revisa el diseño con los equipos de legal, compliance, protección de datos (DPO) y seguridad de tu organización.

Contenido

  1. Qué sale de tu organización en cada llamada
  2. El ciclo de vida del dato en el proveedor
  3. Minimización de datos: la función de redacción de DocuBot
  4. Garantías contractuales y arquitecturas alternativas
  5. GDPR a alto nivel: lo que un desarrollador debe saber
  6. El AI Act europeo y la transparencia de DocuBot
  7. Decisiones de datos para DocuBot: la tabla viaja / no viaja

Qué sale de tu organización en cada llamada

Empecemos por hacer visible lo invisible. Cuando docubot.py::responder (o bucle_agente) llama a la API, el cuerpo de la petición contiene mucho más que "la pregunta del usuario":

Componente del prompt Origen en DocuBot Qué puede contener
System prompt prompts.py (SYSTEM_DOCUBOT_AGENTE) Instrucciones internas, tono, políticas — propiedad intelectual, rara vez datos personales
Historial de la conversación Conversacion / GestorSesiones (03-03), hasta 10 turnos Todo lo que el usuario haya escrito: nombres, quejas, datos de clientes que pegó sin pensar
Documentación recuperada rag.py::recuperar → chunks de docs_nubelia Contenido de la wiki interna: procedimientos, precios internos, a veces nombres de empleados o clientes
Definiciones de herramientas herramientas.py::HERRAMIENTAS Nombres y descripciones de tus APIs internas (revelan tu arquitectura)
Resultados de herramientas tool_result de consultar_proyecto, etc. Datos operativos de proyectos: estados, responsables, fechas, incidencias
La pregunta actual El usuario Cualquier cosa

Tres observaciones que cambian la perspectiva:

  • El RAG multiplica la exposición. Antes del módulo 4, salía lo que el usuario escribía. Desde el módulo 4, sale también lo que la wiki contiene — aunque el usuario nunca lo vea, porque los chunks viajan en el prompt aunque la respuesta no los cite.
  • El historial acumula. Con el truncado a 10 turnos de 03-03, un dato personal mencionado en el turno 2 reviaja en cada llamada hasta salir de la ventana. Un dato enviado una vez se envía muchas veces.
  • Las herramientas abren una tercera puerta. consultar_proyecto saca datos de la API interna de Nubelia y los pone rumbo al proveedor sin que nadie los haya escrito en el chat.

La disciplina básica es poder responder en todo momento a la pregunta: "si imprimo el prompt completo de esta llamada, ¿qué hay ahí que no debería salir de Nubelia?". Un ejercicio sano es literalmente ese — volcar el prompt final a consola en un entorno de desarrollo y leerlo con ojos de auditor.

El ciclo de vida del dato en el proveedor

Que un dato "salga" no es un evento binario; importa qué pasa después. Las preguntas cuya respuesta está en los términos de servicio y acuerdos de tu proveedor (leerlos, no asumirlos — cambian entre proveedores, entre planes del mismo proveedor y con el tiempo):

  • Retención: ¿cuánto tiempo conserva el proveedor los prompts y respuestas? ¿Existe una opción de retención cero o reducida? ¿La retención es distinta para peticiones marcadas por sus filtros de abuso?
  • Uso para entrenamiento: ¿usa el proveedor tus datos para entrenar modelos? En las APIs de pago orientadas a empresa lo habitual es que no por defecto, a diferencia de algunos productos gratuitos de consumo — pero es una cláusula que se verifica en el contrato, no un rumor que se repite.
  • Subencargados y ubicación: ¿en qué región se procesan y almacenan los datos? ¿Qué terceros intervienen?
  • Acceso humano: ¿en qué supuestos puede personal del proveedor leer tus prompts (soporte, revisión de abusos)?

Dos hábitos de ingeniería derivados:

  1. Documenta la respuesta a estas preguntas para el proveedor y plan que uses, con fecha. Es el tipo de evidencia que legal y compliance te pedirán, y la base para reevaluar si cambias de proveedor (el panorama de 01-03).
  2. Trata tus propios logs como parte del ciclo de vida. De nada sirve que el proveedor retenga 30 días si tú guardas los prompts completos indefinidamente en tus trazas. Este hilo — datos personales en logs — lo retomamos en 06-04.

Minimización de datos: la función de redacción de DocuBot

El principio más rentable de toda la lección es el de minimización: no enviar lo que no hace falta para la tarea. Pregunta guía con un ejemplo concreto: cuando un empleado de soporte pide a DocuBot ayuda para redactar la resolución de un ticket, ¿necesita el LLM saber el nombre del cliente? Casi nunca. "El cliente no puede exportar el informe" produce exactamente la misma respuesta que la versión con nombre, email y teléfono — y no expone a nadie.

Cuando el dato viene incrustado en texto libre (el usuario pega el ticket entero en el chat), la minimización se implementa con redacción o seudonimización previa al envío. Una versión pedagógica con expresiones regulares:

# redaccion.py
import re

# Cada patron: (nombre, regex compilada, marcador de sustitucion)
PATRONES_PII = [
    ("email",    re.compile(r"[\w.+-]+@[\w-]+\.[\w.]+"),               "[EMAIL]"),
    ("telefono", re.compile(r"(?:\+?\d{1,3}[ .-]?)?(?:\d[ .-]?){9,12}"), "[TELEFONO]"),
    ("iban",     re.compile(r"\b[A-Z]{2}\d{2}(?:[ ]?[\dA-Z]{4}){3,7}\b"), "[IBAN]"),
    ("tarjeta",  re.compile(r"\b(?:\d[ -]?){13,19}\b"),                 "[TARJETA]"),
]

def redactar(texto: str) -> tuple[str, dict[str, int]]:
    """Sustituye patrones de datos personales por marcadores.
    Devuelve el texto redactado y un recuento por tipo (util para metricas)."""
    recuento = {}
    for nombre, patron, marcador in PATRONES_PII:
        texto, n = patron.subn(marcador, texto)
        if n:
            recuento[nombre] = n
    return texto, recuento

Y su uso, con datos ficticios y genéricos:

mensaje_usuario = (
    "El cliente (contacto: [email protected], "
    "tel. 600 000 000) no puede exportar el informe semanal del proyecto."
)

texto_limpio, recuento = redactar(mensaje_usuario)
print(texto_limpio)
# El cliente (contacto: [EMAIL], tel. [TELEFONO]) no puede exportar
# el informe semanal del proyecto.
print(recuento)   # {'email': 1, 'telefono': 1}

¿Dónde se engancha? En la frontera, antes de que nada viaje: en docubot.py::responder (y en la entrada de bucle_agente), como primer paso sobre el mensaje del usuario; y opcionalmente sobre los tool_result si la API interna devuelve campos sensibles. El recuento no es decorativo: registrado como métrica (sin el contenido), en 06-04 te dirá cuántos datos personales intentan colarse por el chat cada semana.

Matices que un profesional debe conocer:

  • Redacción ≠ seudonimización. Redactar sustituye por un marcador genérico ([EMAIL]) y es irreversible. Seudonimizar sustituye por un identificador consistente ([CLIENTE_7]) manteniendo una tabla de correspondencia que nunca viaja; permite que el LLM razone sobre "el mismo cliente" en varios turnos y que tú re-personalices la respuesta al recibirla. Más útil, más delicada (la tabla es en sí un dato a proteger).
  • Las regex tienen techo. Cazan formatos (emails, teléfonos, IBANs) pero no nombres propios ni datos contextuales ("el director financiero de la empresa de Zaragoza que nos llamó ayer"). Para eso existen los sistemas NER (reconocimiento de entidades nombradas) y bibliotecas especializadas de detección de PII; la arquitectura es la misma — un filtro en la frontera — con un detector mejor.
  • La minimización también aplica al RAG: la mejor redacción es la que no hace falta. Si la wiki de Nubelia no debería contener datos de clientes, el momento de comprobarlo es la ingesta de 04-03 — el mismo gancho donde 06-01 puso el detector de patrones sospechosos puede ejecutar redactar() o encolar para revisión.

Garantías contractuales y arquitecturas alternativas

La minimización reduce lo que sale; las garantías gobiernan lo que sale de todos modos. De menos a más control:

Opción Qué aporta Coste/contrapartida
API estándar + DPA (Data Processing Agreement / acuerdo de encargo de tratamiento) Marco contractual: el proveedor actúa como encargado, con obligaciones de seguridad, confidencialidad y subencargados declarados Es lo mínimo exigible si viajan datos personales; revisa que esté firmado, no solo publicado
Residencia de datos / región UE El procesamiento y/o almacenamiento ocurre en la región elegida; simplifica el análisis de transferencias internacionales No todos los proveedores/modelos lo ofrecen; a veces con latencia o catálogo reducido
Planes enterprise / vía cloud (los grandes proveedores cloud ofrecen los mismos modelos bajo sus propios marcos contractuales) Retención configurable, compromisos reforzados, integración con el paraguas contractual cloud que la empresa ya tiene firmado Coste y complejidad de contratación
Autoalojamiento de modelos abiertos (panorama de 01-03) Los datos no salen de tu infraestructura: la conversación de esta lección casi desaparece Coste de GPUs y operación, modelos generalmente menos capaces, y la seguridad pasa a ser 100% tu problema

La decisión no es global sino por flujo de datos: DocuBot puede usar la API comercial para preguntas generales sobre documentación pública y exigir región UE (o redacción agresiva) para el flujo que toca tickets con datos de clientes. La arquitectura modular del curso (el cliente encapsulado en cliente.py, con client y MODELO como puntos únicos de configuración) es lo que hace esa decisión barata de implementar.

GDPR a alto nivel: lo que un desarrollador debe saber

El GDPR (Reglamento General de Protección de Datos) aplica cuando se tratan datos personales: cualquier información sobre una persona física identificada o identificable. Nota importante: nombres y emails corporativos ("[email protected]") también son datos personales — el GDPR no distingue entre "datos de empresa" y "datos de consumidor". Lo generalmente aceptado que un desarrollador de DocuBot debe tener en el radar:

  • Roles: Nubelia decide los fines y medios del tratamiento → es responsable; el proveedor del LLM trata los datos por cuenta de Nubelia → típicamente encargado (de ahí el DPA de la sección anterior).
  • Base de legitimación: todo tratamiento necesita una base legal (consentimiento, ejecución de contrato, interés legítimo...). Cuál corresponde a un asistente interno concreto es una decisión del DPO/legal, no del desarrollador — tu trabajo es poder explicar con precisión qué datos se tratan, para qué y por dónde pasan. Los diagramas de flujo de datos que estás construyendo en esta lección son exactamente el insumo que legal necesita.
  • Minimización y limitación de finalidad son principios literales del reglamento — la sección de redacción no era solo buena práctica de ingeniería.
  • Derechos de los interesados (acceso, rectificación, supresión...): si los prompts con datos personales acaban en tus logs y trazas, esos logs entran en el perímetro de un ejercicio de derechos ("borrad lo que tenéis sobre mí"). Diseñar los logs con seudonimización y retención definida (06-04) es lo que hace esos derechos ejercitables sin arqueología.
  • Transferencias internacionales: enviar datos personales a servidores fuera del EEE requiere garantías adecuadas (de ahí el valor práctico de la residencia UE).
  • Evaluaciones de impacto (DPIA): para tratamientos de riesgo alto puede ser obligatoria una evaluación formal previa. Si tu asistente va a tratar categorías especiales de datos (salud, por ejemplo), frena y consulta antes de escribir código.

Insistimos: esto es un mapa para conversar con los expertos, no la conversación entera.

El AI Act europeo y la transparencia de DocuBot

El AI Act (Reglamento Europeo de Inteligencia Artificial) regula los sistemas de IA con un enfoque basado en riesgo: obligaciones proporcionales a la categoría del sistema.

Categoría Idea general Ejemplos típicos citados por la norma
Riesgo inaceptable Prohibidos Manipulación subliminal dañina, social scoring generalizado
Alto riesgo Requisitos estrictos (gestión de riesgos, documentación, supervisión humana, registro) IA en selección de personal, crédito, infraestructuras críticas, ciertos usos en educación/sanidad/justicia
Riesgo limitado Obligaciones de transparencia Chatbots y asistentes conversacionales
Riesgo mínimo Sin obligaciones específicas Filtros de spam, IA en videojuegos

Un asistente interno de documentación como DocuBot encaja en principio en el terreno de las obligaciones de transparencia — pero la clasificación concreta depende del caso y del uso real, y decidirla corresponde a legal/compliance, no a esta lección: el mismo motor, aplicado a decidir sobre personas (contrataciones, evaluaciones), cambiaría de casilla. Además, los proveedores de los modelos de propósito general tienen sus propias obligaciones bajo la norma, distintas de las tuyas como integrador.

Lo que sí es accionable ya para DocuBot, y barato:

  • El usuario debe saber que habla con una IA. Nada de disfrazar a DocuBot de compañero humano: la interfaz lo identifica como asistente automático.
  • Honestidad sobre los límites: la frase anti-alucinación del curso — "No encuentro esa información en la documentación disponible." — y el campo confianza del contrato JSON son, además de calidad, transparencia: el sistema comunica su incertidumbre en lugar de aparentar omnisciencia.
  • Supervisión humana donde hay acción: el human-in-the-loop de crear_con_confirmacion() (05-02) es exactamente el patrón de supervisión que la regulación favorece. Buenas prácticas de ingeniería y expectativas regulatorias apuntan en la misma dirección — no es casualidad.
# En la interfaz de DocuBot: transparencia como requisito, no como adorno
MENSAJE_BIENVENIDA = (
    "Hola, soy DocuBot, el asistente automatico de documentacion de Nubelia. "
    "Soy un sistema de IA: puedo equivocarme y mis respuestas pueden requerir "
    "verificacion. Indico siempre las fuentes en las que me baso."
)

Decisiones de datos para DocuBot: la tabla viaja / no viaja

La síntesis operativa de la lección es un inventario de flujos con una decisión explícita por dato. Esta tabla (adaptada a tu caso) es también un entregable excelente para la conversación con el DPO:

Dato ¿Viaja al proveedor? Cómo Justificación
Pregunta del usuario Sí, redactada redactar() en la frontera de responder Necesaria para la tarea; los datos personales incrustados, no
Historial (10 turnos) Sí, redactado en origen Se redacta al entrar en Conversacion, así nunca reviaja sin limpiar El historial reenvía cada dato muchas veces
System prompt Sí, tal cual prompts.py Sin datos personales por diseño; se revisa en cada cambio de PROMPT_VERSION
Chunks de la wiki Sí, tal cual rag.py La wiki no debe contener datos de clientes; se verifica en la ingesta (04-03)
Nombre/email del empleado que pregunta No Se identifica la sesión con un seudónimo (GestorSesiones); la identidad real no entra en el prompt El LLM no necesita saber quién pregunta
Datos de contacto de clientes en tickets No (viaja el texto redactado) redactar() + seudonimización si hace falta consistencia El LLM no necesita el nombre para redactar la resolución
Resultados de consultar_proyecto Sí, filtrados La herramienta devuelve solo los campos necesarios (estado, hitos), no la ficha completa Minimización aplicada al diseño de la herramienta
Claves de API, secretos Jamás Variables de entorno, nunca en prompts ni en código Sin excepciones
Logs y trazas propios No viajan al proveedor, pero son tratamiento Seudonimizados y con retención definida Se desarrolla en 06-04

Fíjate en el patrón de las filas "No": casi nunca la solución es un filtro heroico de última hora, sino diseñar el flujo para que el dato no llegue a acercarse al prompt (la herramienta devuelve menos campos; la sesión usa seudónimos). La mejor redacción es la que no tiene nada que redactar.

Errores Comunes y Consejos

  • Creer que "solo sale la pregunta del usuario". Sale el prompt completo: system, historial, chunks del RAG, definiciones y resultados de herramientas. Haz el ejercicio de volcar un prompt real (de desarrollo) y leerlo entero.
  • Asumir los términos del proveedor en lugar de leerlos. "Seguro que no entrenan con mis datos" no es una política de privacidad. Léelos para tu plan concreto, documenta la respuesta con fecha, y revísala cuando cambies de proveedor o de plan.
  • Redactar la entrada y olvidar las otras puertas. El historial (que reenvía datos antiguos), los tool_result y los chunks del RAG son vías de entrada tan reales como el mensaje del usuario. Redacta en origen, no solo en la puerta principal.
  • Confundir redacción con anonimización. Un texto con [EMAIL] puede seguir identificando a una persona por el contexto ("la directora de la oficina de Cuenca"). La redacción por patrones reduce el riesgo; no lo elimina, y rara vez convierte los datos en anónimos en sentido estricto (por eso el término prudente es seudonimización).
  • Tratar el cumplimiento como un sprint final. La minimización afecta al diseño de herramientas, sesiones y logs; retrofitearla es mucho más caro que diseñarla. Y al revés: no dejes que la parálisis regulatoria bloquee todo — la mayor parte de esta lección (minimizar, redactar, DPA, transparencia) es sota, caballo y rey aceptado en la industria.
  • Jugar a abogado. Tu entregable como desarrollador es un sistema minimizador y un mapa preciso de flujos de datos; la base de legitimación, la clasificación bajo el AI Act y la DPIA son decisiones de legal/DPO. Llega a esa reunión con la tabla viaja/no-viaja hecha y serás su desarrollador favorito.

Ejercicios

Ejercicio 1: auditoría de un prompt

Un empleado de soporte mantiene esta conversación con DocuBot (datos ficticios):

Turno 1: "El cliente Ejemplo S.L. (CIF ficticio, contacto: [email protected]) dice que le hemos cobrado dos veces la cuota de marzo." Turno 2: "¿Puedes decirme cómo se tramita un abono?"

En el turno 2, DocuBot usa RAG (recupera 3 chunks de la wiki de facturación) y responde. Enumera todo lo que viaja al proveedor en la llamada del turno 2, señala qué elementos contienen datos que la minimización debería haber tratado, y explica por qué el problema del turno 1 sigue presente en el turno 2.

Ejercicio 2: ampliar la redacción con seudonimización consistente

Modifica redaccion.py para que los emails no se sustituyan todos por [EMAIL], sino por seudónimos consistentes ([EMAIL_1], [EMAIL_2]...) dentro de un mismo texto: el mismo email recibe siempre el mismo marcador, emails distintos reciben marcadores distintos. Devuelve también la tabla de correspondencia (que nunca viajaría al proveedor).

Ejercicio 3: clasificar flujos de datos

Para cada dato nuevo, decide (como en la tabla viaja/no-viaja) si debería viajar, no viajar o viajar redactado, y justifica: (a) el nombre del proyecto interno ("Atlas") en una pregunta sobre su estado; (b) el salario de un empleado mencionado en un ticket de la categoría facturacion; (c) el identificador de sesión que GestorSesiones usa para agrupar la conversación.

Soluciones

Ejercicio 1:

Viaja en el turno 2: (1) el system prompt completo (SYSTEM_DOCUBOT_HERRAMIENTAS o el que aplique); (2) el historial: el turno 1 íntegro — con nombre de la empresa cliente, CIF y email — más la respuesta previa del asistente, porque Conversacion reenvía los últimos 10 turnos; (3) la pregunta del turno 2; (4) los 3 chunks de la wiki de facturación; (5) si opera como agente, las definiciones de herramientas.

Debería haberse tratado: los datos del cliente del turno 1 (nombre, CIF, email → redacción/seudonimización). El punto clave es el tercero: aunque el turno 2 sea inocente, el turno 1 reviaja dentro del historial en cada llamada hasta salir de la ventana de 10 turnos. Por eso la redacción debe aplicarse al entrar el mensaje en la conversación (en origen), no solo al último mensaje: un dato que entró sin redactar contamina todas las llamadas siguientes.

Ejercicio 2:

import re

EMAIL_RE = re.compile(r"[\w.+-]+@[\w-]+\.[\w.]+")

def seudonimizar_emails(texto: str) -> tuple[str, dict[str, str]]:
    """Sustituye cada email por un marcador consistente [EMAIL_n].
    Devuelve el texto y la tabla de correspondencia (privada: NO viaja)."""
    tabla: dict[str, str] = {}

    def _reemplazo(match: re.Match) -> str:
        email = match.group(0)
        if email not in tabla:
            tabla[email] = f"[EMAIL_{len(tabla) + 1}]"
        return tabla[email]

    return EMAIL_RE.sub(_reemplazo, texto), tabla

texto = ("Escriben [email protected] y [email protected]; "
         "responded a [email protected] con copia al segundo.")
limpio, tabla = seudonimizar_emails(texto)
print(limpio)
# Escriben [EMAIL_1] y [EMAIL_2]; responded a [EMAIL_1] con copia al segundo.

Cómo funciona: re.sub acepta una función de reemplazo; la función consulta/rellena tabla, de modo que el mismo email siempre obtiene el mismo marcador. Así el LLM puede razonar "responded a [EMAIL_1]" con coherencia, y tu código puede re-personalizar la respuesta invirtiendo la tabla — que se queda en tu infraestructura, porque es en sí misma un dato personal a proteger.

Ejercicio 3:

  • (a) Viaja tal cual. "Atlas" es un identificador interno de proyecto, no un dato personal; sin él, ni el RAG ni consultar_proyecto pueden funcionar. Riesgo residual: confidencialidad empresarial (no privacidad), cubierta por el DPA.
  • (b) No viaja. Es un dato personal de categoría sensible en la práctica (información económica de una persona identificable) y el LLM no lo necesita para tramitar nada: se redacta ([IMPORTE] o eliminación del pasaje) antes del envío, y su presencia en un ticket debería además disparar una revisión del proceso (¿por qué hay salarios en tickets de soporte?).
  • (c) No viaja (y no necesita hacerlo). El identificador de sesión es fontanería de tu lado: GestorSesiones lo usa para recuperar el historial, pero el prompt no tiene por qué contenerlo. Si algún día hiciera falta correlacionar (p. ej. para depurar con el proveedor), viajaría un identificador seudónimo y opaco, nunca uno que revele la identidad del empleado — y esa correlación reaparece, bien hecha, en los logs de 06-04.

Conclusión

DocuBot ya tiene las dos caras del casco: la 06-01 lo protege de quien quiere manipularlo; esta lección protege a los demás de lo que DocuBot podría contar. Las ideas que deben quedarse contigo: en cada llamada viaja el prompt completo — system, historial que reenvía el pasado, chunks del RAG, datos de herramientas —, no "la pregunta"; el ciclo de vida del dato en el proveedor (retención, entrenamiento, región) se lee en los términos y se documenta, no se asume; la minimización es la palanca del desarrollador — la función redactar() en la frontera, herramientas que devuelven solo los campos necesarios, sesiones seudónimas — y la mejor redacción es la del dato que nunca se acercó al prompt; los DPA, la residencia UE y el autoalojamiento son la escalera de garantías cuando el dato viaja de todos modos; y el marco normativo (GDPR y AI Act, sin jugar a abogados) premia exactamente lo que el curso ya practicaba: transparencia de que se habla con una IA, honestidad sobre la incertidumbre y supervisión humana en las acciones. Queda una pregunta incómoda que ni la seguridad ni la privacidad responden: todo esto está muy bien, pero ¿DocuBot funciona? ¿Cómo sabes que el cambio de prompt de ayer no rompió veinte respuestas que antes salían bien? En 02-04 y 04-05 evaluamos a mano y prometimos automatizarlo; la próxima lección salda esa deuda: unit tests para el código determinista, tests de contrato para las salidas del modelo, el runner que ejecuta el conjunto completo — adversariales incluidos — y un LLM haciendo de juez calibrado contra tu patrón oro.

© Copyright 2026. Todos los derechos reservados