← Todo el material
Curso Avanzado 23 de agosto de 2026

Evals — Módulo 12: hacer que el programa sobreviva al primer trimestre

Módulo final del Curso de Evals: los programas de evals no mueren por razones técnicas. Por qué mueren — nadie es dueño del dataset, el costo se dispara, los umbrales se relajan hasta ser decorativos — y cómo evitarlo: propiedad nominal, presupuesto real por corrida y cómo bajarlo, gates por criticidad y la cadencia de qué corre en cada PR, de noche, antes de release y cada mes.

☰ Índice del contenido

En el módulo 11 cerramos el circuito: producción alimenta al dataset, el dataset protege a producción. Con eso el curso está técnicamente completo — tienes todo lo necesario para construir un programa de evaluación serio. Y sin embargo, la mayoría de los programas de evals que arrancan con este nivel de sofisticación están muertos en abril.

No mueren por razones técnicas. Nadie apaga un programa de evals; simplemente se apaga solo. El dataset deja de crecer. Alguien agrega un @pytest.mark.skip “por esta semana” y la semana no termina nunca. El umbral baja de 0.90 a 0.85 y después a 0.80, cada vez con una buena razón. Seis meses después existe una carpeta evals/ que nadie mira, un job en CI que siempre está verde porque no exige nada, y un equipo convencido de que “sí, tenemos evals”. Este módulo trata sobre eso, que es el problema de verdad.

La tesis del módulo

Un programa de evals no se cae: se erosiona. Cada decisión que lo mata es razonable por sí sola — bajar un umbral para desbloquear un release urgente, saltarse una corrida para ahorrar plata, dejar el dataset “para la próxima”. La defensa no es más rigor técnico: son tres cosas administrativas — un dueño con nombre, un presupuesto explícito y umbrales que solo se cambian por escrito.

Las tres muertes

Muerte 1: nadie es dueño del dataset

Es la más frecuente y la más silenciosa. El golden dataset lo arma quien monta la suite, con un empujón inicial de entusiasmo, y después queda huérfano. “Lo mantiene el equipo” significa, en la práctica, que no lo mantiene nadie: es el clásico problema del bien común, donde una tarea que beneficia a todos y no está asignada a nadie no se hace.

Los síntomas llegan en este orden:

  1. El dataset deja de crecer. Se congela en la versión que tenía cuando el que lo armó cambió de proyecto.
  2. Aparecen casos rotos — el negocio cambió el precio, la política o el flujo, y el caso espera una respuesta que ya no es correcta.
  3. Alguien “arregla” el caso roto cambiando la respuesta esperada por la que el sistema da hoy. Ese es el punto de no retorno: el dataset dejó de ser la verdad y pasó a ser un registro de lo que el sistema hace.
  4. El gate empieza a fallar por casos obsoletos, todos aprenden a ignorarlo, y el gate muere.

Por qué el dueño no puede ser “el equipo”. Porque el dataset no es un artefacto técnico: es la definición escrita de qué significa que el producto funcione bien. Esa definición no la puede sostener quien escribe el código — la puede sostener quien responde por el producto. El dueño natural es el product owner del agente o el experto de dominio que firma qué es correcto, no la persona de plataforma que montó el runner.

Cómo se ve la propiedad en concreto, y todo esto es aburrido a propósito:

  • Un nombre en el README de la carpeta evals/. Una persona, no un equipo, no un alias de correo.
  • CODEOWNERS apuntando al dataset, para que cualquier cambio pase por esa persona.
  • Un ritual con fecha: la revisión trimestral del dataset, agendada, donde se botan los casos obsoletos y se incorporan los de producción.
  • Regla escrita: cambiar una respuesta esperada requiere justificación en el PR. “El sistema ahora responde otra cosa” no es justificación; “el negocio cambió la política el 12 de marzo, acá está el documento” sí lo es.

Cuidado con el arreglo silencioso. El momento en que alguien edita la respuesta esperada de un caso para que el test pase es el momento exacto en que tu dataset deja de medir algo. Es indistinguible de un buen mantenimiento salvo por la justificación escrita. Por eso la justificación es la regla, no un formalismo.

Muerte 2: el costo se dispara

Los evals con juez cuestan plata real, y el costo crece con el producto de tres cosas que todas tienden a subir: número de casos × métricas por caso × frecuencia de corrida. Nadie presupuesta eso al empezar porque al empezar son 40 casos y una métrica.

Hagamos la aritmética explícita, porque el número sorprende. Digamos que la suite tiene 300 casos y tres métricas de juez. Cada juicio consume del orden de 2.000 tokens de entrada y 300 de salida. Son 900 llamadas por corrida.

        DE DÓNDE VIENE LA FACTURA

  casos × métricas × llamadas del juez = llamadas por corrida
   300  ×    3     ×        1          =      900

  llamadas/corrida × corridas/día = llamadas/día
        900        ×      20      =   18.000

  Y ese "20" es el número que nadie decidió:
  son los PRs del día × las veces que cada uno reintenta.

  ┌────────────────────────────────────────────┐
  │ El costo NO crece por el tamaño del        │
  │ dataset. Crece por la FRECUENCIA, que      │
  │ nadie fijó y que sube sola con el equipo.  │
  └────────────────────────────────────────────┘

Fíjate dónde está la palanca. Duplicar el dataset duplica el costo; pero pasar de 5 a 20 corridas diarias lo cuadruplica, y eso ocurre sin que nadie tome una decisión — simplemente entraron dos personas más al equipo y abren más PRs. Por eso el control tiene que estar en la frecuencia y en qué corre cuándo, no en recortar casos.

Cómo bajar el costo sin bajar la protección. Cuatro palancas, en orden de rendimiento:

PalancaCómoAhorro típicoQué pierdes
Deterministas siempre, juez con criterioTodo lo asserteable sin LLM se assertea sin LLMEnormeNada
Juez sobre muestra en PRs60 casos en el PR, los 300 de noche~80% del costo diarioDetección tardía en algunos casos
Caché de resultadosSi el prompt y la entrada no cambiaron, no rejuzgarAlto en reintentosNada, si el hash está bien hecho
Modelo juez más chicoJuez pequeño con rúbrica muy explícitaAltoRequiere recalibrar el juez
Caros solo de nocheSuite completa + adversarial en un job programado—Ciclo de feedback más largo

La primera palanca es la que más rinde y la que menos se usa. Vale la pena decirlo sin rodeos: la mayoría de los equipos está pagando un juez LLM para verificar cosas que un if verifica gratis. ¿La respuesta cita una fuente? Eso es un regex. ¿Llamó la tool correcta con el argumento correcto? Eso es comparar strings. ¿La salida es JSON válido con los campos esperados? Eso es un parser. El juez es para lo que requiere criterio, y lo que requiere criterio es menos de lo que uno cree.

La segunda merece un detalle de implementación: si vas a juzgar una muestra en el PR, la muestra tiene que ser estable, no aleatoria por corrida. Si cada corrida elige 60 casos distintos al azar, el resultado fluctúa por muestreo y el equipo aprende que el gate es ruidoso — que es exactamente cómo empieza la muerte número 3. Muestra fija por hash del id del caso, cambiándola de forma deliberada y documentada.

"""Muestra estable + caché: las dos optimizaciones que más rinden.

La muestra estable evita que el gate fluctúe por azar. El caché evita
pagar dos veces por juzgar exactamente lo mismo (que es lo que pasa
cuando alguien reintenta un PR por un fallo de red).
"""
import hashlib
import json
import sqlite3

CACHE = sqlite3.connect("evals/.cache_juez.db")
CACHE.execute("CREATE TABLE IF NOT EXISTS juicios "
              "(clave TEXT PRIMARY KEY, resultado TEXT)")


def muestra_estable(casos: list[dict], n: int) -> list[dict]:
    """Ordena por hash del id y toma los primeros n. Determinista:
    la misma muestra en cada corrida, hasta que cambie el dataset."""
    return sorted(
        casos,
        key=lambda c: hashlib.sha256(c["id"].encode()).hexdigest(),
    )[:n]


def clave_cache(caso_id: str, prompt_version: str, modelo: str,
                salida: str) -> str:
    """La clave incluye TODO lo que puede cambiar el juicio. Si falta
    alguno, el caché devuelve resultados obsoletos — que es peor que
    no tener caché."""
    crudo = f"{caso_id}|{prompt_version}|{modelo}|{salida}"
    return hashlib.sha256(crudo.encode()).hexdigest()


def juzgar_con_cache(caso, salida, prompt_version, modelo, juez) -> dict:
    clave = clave_cache(caso["id"], prompt_version, modelo, salida)
    fila = CACHE.execute(
        "SELECT resultado FROM juicios WHERE clave = ?", (clave,)
    ).fetchone()
    if fila:
        return json.loads(fila[0])          # gratis: ya lo juzgamos

    resultado = juez(caso, salida)          # acá se gasta plata
    CACHE.execute("INSERT INTO juicios VALUES (?, ?)",
                  (clave, json.dumps(resultado, ensure_ascii=False)))
    CACHE.commit()
    return resultado

Ojo con la clave del caché. Si te falta la versión del prompt o el modelo en la clave, el caché te va a devolver el juicio de ayer sobre un sistema que cambió hoy, y tu gate va a estar verde por una razón falsa. Un caché mal invalidado es peor que no tener caché: te da confianza sin evidencia.

Muerte 3: los umbrales se relajan hasta ser decorativos

Esta es la más insidiosa porque cada paso individual parece razonable.

        LA EROSIÓN DEL UMBRAL, PASO A PASO

  mes 1  │ umbral 0.90 · el gate falla · se arregla el bug
         ▼
  mes 2  │ release urgente · falla por 0.885
         │ "lo bajamos a 0.88 por esta vez"
         ▼
  mes 3  │ el gate es ruidoso · falla 1 de cada 3 corridas
         │ "lo bajamos a 0.85 mientras estabilizamos"
         ▼
  mes 4  │ alguien agrega @skip a los dos casos
         │ que siempre fallan · "son casos raros"
         ▼
  mes 5  │ el job está siempre verde
         │ nadie recuerda qué mide
         ▼
  mes 6  │ un incidente en producción que el eval
         │ de hace 4 meses habría atrapado
         ▼
        PROGRAMA MUERTO (con la carpeta evals/ intacta)

El diagnóstico es que el umbral estaba mal desde el principio, no que la gente sea indisciplinada. Un umbral que falla una de cada tres corridas por variación normal del modelo es un umbral ruidoso, y un equipo racional aprende a ignorar una alarma que suena sin motivo. Bajarlo no fue el error; el error fue ponerlo sin medir la variación natural del sistema.

Tres reglas que detienen la erosión:

1. El umbral se fija con datos, no con aspiración. Corre la suite cinco veces sin cambiar nada y mira la dispersión. Si la métrica varía entre 0.88 y 0.93 sin que nadie toque nada, el umbral no puede ser 0.92: tiene que estar por debajo del piso observado. Es la lección del módulo 8 aplicada a la política, no al análisis.

2. Bajar un umbral requiere un PR con justificación y aprobación del dueño. No una conversación en el canal del equipo. Un PR que quede en el historial, para que en seis meses se pueda responder “¿desde cuándo esto está en 0.80 y por qué?”.

3. skip con fecha de vencimiento. Un caso saltado sin fecha es un caso borrado con pasos extra. La convención: @pytest.mark.skip(reason="bug #442, vence 2026-09-15") y un test que falla cuando la fecha pasó. Suena burocrático y es lo único que funciona.

Gates por criticidad: no todo bloquea igual

El error de diseño que hace ruidoso a un gate es tratar todos los casos como equivalentes. Una falla en “el tono fue algo seco” y una falla en “entregó datos de otro cliente” no pueden tener la misma consecuencia. Cuando la tienen, o el gate es tan estricto que bloquea por cosas cosméticas — y se relaja —, o es tan laxo que deja pasar lo grave.

La solución es clasificar cada caso por severidad, con una consecuencia distinta para cada nivel:

NivelTipo de casoEjemploSi falla
S0 — Bloqueante absolutoSeguridad crítica, datos de terceros, acciones destructivasExfiltración, PII de otro usuario, borrado no autorizadoBloquea el merge. Un solo caso basta. Sin excepción
S1 — Regla de negocio duraLo que la empresa prohíbe decir o hacerPrometer un descuento, dar consejo médico, confirmar política falsaBloquea el merge. Un solo caso basta
S2 — Calidad agregadaFidelidad, relevancia, resoluciónEl promedio del juez sobre el datasetBloquea si cae bajo el umbral o si baja más de X vs. la versión publicada
S3 — OperativoLatencia, costo, tokensp95 sube 40%, costo por conversación se duplicaBloquea si empeora más del margen acordado
S4 — CosméticoTono, formato, verbosidadRespuestas más largas de lo idealNo bloquea. Reporta y va al backlog

La diferencia clave está entre S0/S1 y S2. Los dos primeros niveles son binarios y por caso: un solo fallo cierra el gate, sin importar el promedio. El tercero es agregado y con umbral: importa la media sobre el dataset. Confundirlos es lo que produce gates inútiles en las dos direcciones — un promedio de 0.94 que esconde una exfiltración, o un merge bloqueado porque una respuesta quedó seca.

"""El gate con severidades. La lógica es corta a propósito: si el
gate es complicado de explicar, nadie va a confiar en él."""

UMBRALES_AGREGADOS = {
    "fidelidad": 0.85,
    "relevancia": 0.80,
    "tasa_resolucion": 0.75,
}
MAX_CAIDA_VS_PUBLICADA = 0.03   # tolerancia de regresión contra prod
MAX_EMPEORAMIENTO_P95 = 0.20    # 20% más lento es el techo


def evaluar_gate(resultados: list[dict], agregados: dict,
                 baseline: dict, operativo: dict) -> tuple[bool, list[str]]:
    bloqueos = []

    # S0 y S1: binarios y por caso. UN fallo cierra el gate.
    for r in resultados:
        if r["severidad"] in ("S0", "S1") and not r["paso"]:
            bloqueos.append(
                f"[{r['severidad']}] {r['id']}: {r['motivo']}"
            )

    # S2: agregado, con umbral absoluto Y con regresión relativa.
    # Las dos condiciones importan: un sistema puede estar sobre el
    # umbral y aun así haber empeorado de forma inaceptable.
    for metrica, minimo in UMBRALES_AGREGADOS.items():
        valor = agregados[metrica]
        if valor < minimo:
            bloqueos.append(f"[S2] {metrica}={valor:.3f} < {minimo}")
        caida = baseline.get(metrica, 0) - valor
        if caida > MAX_CAIDA_VS_PUBLICADA:
            bloqueos.append(
                f"[S2] {metrica} cayó {caida:.3f} vs. publicada"
            )

    # S3: operativo, relativo a la versión publicada.
    delta_p95 = (operativo["p95"] - baseline["p95"]) / baseline["p95"]
    if delta_p95 > MAX_EMPEORAMIENTO_P95:
        bloqueos.append(f"[S3] p95 empeoró {delta_p95:.0%}")

    # S4 no aparece acá a propósito: se reporta, no bloquea.
    return (not bloqueos), bloqueos

Un detalle de ese código que vale la pena marcar: S2 tiene dos condiciones, absoluta y relativa. El umbral absoluto evita publicar algo malo; la comparación contra la versión publicada evita la degradación lenta, esa donde cada release baja 0.01 y en diez releases perdiste 0.10 sin que ningún gate se haya cerrado nunca.

La cadencia: qué corre cuándo

Con severidades definidas y el costo bajo control, la cadencia se cae de madura. La regla que la organiza: lo barato corre siempre, lo caro corre cuando puede cambiar algo, lo lento corre de noche.

              CADENCIA DEL PROGRAMA

 ┌────────────────────────────────────────┬─────────┐
 │ CADA PR                                │ segundos│
 │  · deterministas completos             │   ~$0   │
 │  · suite S0 de seguridad completa      │         │
 │  · juez sobre muestra fija de 60 casos │ minutos │
 ├────────────────────────────────────────┼─────────┤
 │ CADA PR QUE TOCA PROMPT O TOOLS        │ minutos │
 │  · suite adversarial completa (mód. 9) │   $$    │
 │  · juez sobre el dataset completo      │         │
 ├────────────────────────────────────────┼─────────┤
 │ DE NOCHE (job programado)              │  lento  │
 │  · dataset completo, todas las métricas│   $$$   │
 │  · trayectorias y multiturno completos │         │
 │  · comparar vs. la versión publicada   │         │
 ├────────────────────────────────────────┼─────────┤
 │ ANTES DE RELEASE                       │   $$$   │
 │  · todo lo anterior, sin caché         │ +humano │
 │  · held-out set (el que nunca se mira) │         │
 │  · revisión humana de 20 casos         │         │
 ├────────────────────────────────────────┼─────────┤
 │ SEMANAL                                │ humano  │
 │  · triage del muestreo online (mód.11) │         │
 │  · promoción de casos al dataset       │         │
 ├────────────────────────────────────────┼─────────┤
 │ MENSUAL                                │   $$    │
 │  · re-run del golden SIN cambiar nada  │ +humano │
 │    (detecta deriva del proveedor)      │         │
 │  · recalibración del juez vs. humanos  │         │
 ├────────────────────────────────────────┼─────────┤
 │ TRIMESTRAL                             │ humano  │
 │  · revisión del dataset con el dueño   │         │
 │  · revisión de umbrales vs. el negocio │         │
 └────────────────────────────────────────┴─────────┘

Tres cosas de ese esquema que se saltan seguido y son las que más rinden:

El held-out set antes de release: un subconjunto de casos que nadie mira mientras itera. Si iteras el prompt mirando los casos del dataset, terminas optimizando para el examen. El held-out es la única forma de saber si mejoraste o si aprendiste las respuestas.

El re-run mensual sin cambiar nada es el único instrumento que detecta que el proveedor movió el modelo bajo tus pies. Cuesta una corrida al mes y responde una pregunta que de otro modo se responde con un incidente.

La recalibración del juez contra humanos: el juez se calibró una vez, y desde entonces cambió el producto, el dataset y probablemente el modelo juez. Treinta casos, dos personas, media jornada al mes.

Reportar a negocio sin métricas decorativas

Un programa de evals que no se puede explicar afuera del equipo técnico pierde el presupuesto en la primera ronda de recortes. Y la traducción no es trivial: “faithfulness 0.91” no significa nada para quien firma el gasto.

El traspaso técnico ↔ negocio — quién define qué es correcto, cómo se construye el examen antes de ver al agente, la sesión de calibración conjunta, cómo se firma una versión del dataset — está desarrollado completo en la Parte VII de la investigación de evaluación de agentes. Es el mejor lugar para el detalle del proceso y no vale la pena reescribirlo acá. Lo que sí agrega este módulo son dos criterios sobre el reporte.

Criterio 1: cada métrica del reporte tiene que responder “¿qué decisión cambia si este número se mueve?”. Si no hay respuesta, la métrica es decoración y sale del reporte. La prueba práctica: pregúntale al equipo qué harían si answer_relevancy bajara de 0.84 a 0.79. Si la respuesta es “investigaríamos”, esa métrica no está lista para el reporte ejecutivo — está lista para el dashboard técnico, que es otra cosa.

Criterio 2: se reporta en unidades de negocio, no en unidades de modelo. La traducción es mecánica una vez que la haces:

Lo que midesLo que reportas
Tasa de resolución 0.78”78 de cada 100 consultas se resuelven sin persona”
Fidelidad 0.91”9 de cada 100 respuestas contienen algo no verificable”
Resistencia adversarial S0 = 1.00”Cero casos de fuga de datos en 340 pruebas de ataque”
Tasa de reformulación 0.14”14% de los usuarios tiene que repetir su pregunta”
Costo por conversación $0.031”Cada conversación cuesta 31 pesos; una derivación cuesta X”
3 regresiones bloqueadas este mes”Tres cambios que habrían degradado el servicio no salieron”

La última fila es la que justifica el presupuesto del programa y casi nadie la lleva. Cuenta las regresiones bloqueadas. Es la única métrica que muestra el valor del programa en vez del estado del sistema: cada bloqueo es un incidente que no ocurrió, con su costo estimado de haber ocurrido. Un programa de evals que no puede decir cuántas veces salvó al producto es un programa que va a perder la discusión de presupuesto con cualquier feature.

Antipatrones finales

Los que quedan después de todo lo visto, y que no son técnicos:

AntipatrónCómo se veCorrección
El dataset del que nadie es dueñoCongelado desde hace 5 mesesUn nombre en CODEOWNERS + revisión trimestral
Arreglar el caso en vez del bugSe edita la respuesta esperadaJustificación obligatoria en el PR
Umbral que se relaja en silencio0.90 → 0.88 → 0.85 → skipPR con aprobación del dueño para cada cambio
skip eternoCasos saltados hace mesesskip con fecha de vencimiento que falla al pasar
Todo bloquea igualEl merge se cae por tonoSeveridades S0-S4 con consecuencias distintas
Juez para lo que un if resuelveFactura alta, señal igualDeterministas primero, siempre
Suite completa en cada pushCosto insostenibleMuestra fija en PR, completa de noche
Iterar mirando el datasetMétricas suben, usuarios no notan nadaHeld-out set antes de release
Dashboard de quince métricasNadie decide nada con élTres métricas con consecuencia + regresiones bloqueadas
Nunca contar lo que se salvóEl programa parece puro costoReportar regresiones bloqueadas por mes
Programa sin presupuesto explícitoSe recorta justo cuando más se necesitaLínea presupuestaria propia, con costo por corrida

Sobre el último: el patrón se repite en todas partes. Cuando el gasto aprieta, “correr menos evals” parece un ahorro inocuo porque el costo es visible y el beneficio no. Se recorta exactamente en el momento en que el equipo está apurado, tomando atajos y publicando más rápido — o sea, cuando más protección hace falta. Un presupuesto explícito, con su costo por corrida calculado y aprobado, es lo que convierte ese recorte en una decisión que alguien tiene que tomar a conciencia en vez de una que ocurre sola.

Errores comunes

  • Propiedad difusa. “Lo mantiene el equipo” es “no lo mantiene nadie”. Un nombre, en el README y en CODEOWNERS.
  • No presupuestar el costo del juez. El costo crece con la frecuencia, que sube sola cuando entra gente al equipo. Calcúlalo y ponlo en una línea de presupuesto.
  • Umbrales fijados por aspiración. Se fijan midiendo la variación natural en cinco corridas idénticas, no eligiendo un número redondo bonito.
  • Un solo nivel de severidad. Produce gates que bloquean por tono o que dejan pasar exfiltraciones. S0-S4 con consecuencias distintas.
  • Correr todo en cada push. El costo se vuelve insostenible, alguien recorta y el recorte no vuelve nunca. Cadencia por costo y por criticidad.
  • Caché sin la versión del prompt en la clave. Verde falso: estás leyendo el juicio de un sistema que ya no existe.
  • Nunca correr el dataset sin cambiar nada. Es lo único que detecta que el proveedor movió el modelo. Una vez al mes.
  • Reportar métricas que no cambian ninguna decisión. Si nadie sabe qué haría cuando el número baja, esa métrica no va en el reporte.

La frase para llevarse

“Los evals no mueren de un problema técnico: mueren de un umbral que se bajó por una buena razón, tres veces seguidas. El rigor que sostiene el programa no es estadístico, es administrativo — un dueño con nombre, un presupuesto escrito y un historial de por qué cada umbral es el que es.”

Qué viene

Nada: acá termina el Curso de Evals. Vale la pena mirar el camino completo, porque los doce módulos cuentan una sola historia.

Partimos preguntando por qué los evals — la respuesta era que sin ellos cada cambio es una corazonada — y de inmediato tuvimos que definir el éxito, que resultó ser la parte difícil y la que no es técnica. Después vino el trabajo de construcción: el primer eval determinista, gratis y sorprendentemente potente; el juez y su calibración, para lo que ningún if alcanza; y las tres dimensiones propias de los agentes, que la evaluación de texto no cubre — trayectorias, tool use y conversaciones multiturno.

Con las métricas en pie, el módulo 8 nos obligó a preguntarnos si los números significaban algo. El 9 cambió la pregunta de calidad por la de seguridad, donde el promedio deja de servir. El 10 bajó todo a herramientas que corren. El 11 salió del laboratorio a producción, donde no hay respuesta ideal y hay que descubrir lo que no sabías. Y este cierre trató del problema del que nadie habla: que todo lo anterior se erosiona si no tiene dueño, presupuesto y disciplina de umbrales.

Si te quedas con una sola idea de los doce módulos, que sea esta: el activo no es la herramienta ni la métrica, es el dataset y el hábito de mirarlo. Todo lo demás se reemplaza en una semana.

Para seguir: el glosario de Evals reúne los términos del curso. Y hay tres cursos que continúan naturalmente desde acá — Deploy de Agentes para poner el sistema evaluado a correr en serio, RAG si tu agente responde sobre documentos, y MLOps si además hay modelos propios en la ecuación.

Material relacionado

EvalsProgramaGobernanzaCostosCI

Preguntas sobre este curso

Regístrate gratis para hacer preguntas y guardar tu progreso.

✉ Entérate de lo nuevo

Recibe un aviso cuando publique nuevos cursos, workshops o investigaciones. Sin spam.