📖 Manual de estudio pre-UTDT

Los 7 módulos del precurso de Arquitectura de Agentes IA, explicados para nosotros.
Flujo: 🎓 cuestionario → estudiar acá el módulo flojo → volver a rendirlo.

Manual de estudio pre-UTDT — Arquitectura de Agentes IA

Armado por Fito el 26/7/2026 para Die y Santi. Está calcado del precurso OFICIAL de UTDT (armado por Matías Zabaljáuregui, el director del programa): https://matiaszabal.github.io/utdt-slides/pre-curso/ Cada capítulo de acá existe para que puedas contestar "sí, con confianza" a las preguntas textuales del autodiagnóstico oficial. Ese es el único objetivo. Contexto del curso: MAPA · para Santi: cómo te enganchás


Cómo se usa esto

  1. Leé el capítulo entero una vez, sin hacer nada. Está escrito para entenderse leyendo, con analogías de cosas que ya hacés.
  2. Después volvé y hacé los ejercicios. Son cortos y usan ejemplos de Codytag, OK Nutre y el cerebro — no ejercicios de curso abstracto.
  3. Al final de cada capítulo están las preguntas OFICIALES, textuales. Contestátelas en voz alta. Si alguna te sale con un "mmm...", volvé a esa sección.
  4. Lo que no entiendas, me lo preguntás a mí (o al Claude de Santi). Este manual está hecho para leerse con un agente al lado, no solo.

Regla anti-ansiedad: el autodiagnóstico no es eliminatorio, no se entrega y no tiene nota. La vacante se asegura con el pago. Esto es un mapa, no un examen.


El tablero: dónde estás hoy (Die, al 26/7/2026)

Contra las 20 preguntas oficiales de los 4 módulos imprescindibles:

Módulo Hoy Meta Esfuerzo que pide UTDT
① Python intermedio 0 / 5 5/5 4-8 h
② APIs, HTTP y JSON 4 / 5 5/5 2-3 h
③ CLI + Git 3 / 5 🟡 5/5 3-5 h
④ LLMs y prompting 5 / 5 ✅✅ 5/5 2-4 h

Traducción honesta: tenés un solo módulo en rojo y son ~6 horas de trabajo real. Los otros tres son cerrar detalles de nomenclatura de cosas que hacés todas las semanas. Nadie llega con los cuatro en verde: vos llegás con tres.


Los capítulos

# Capítulo Qué resuelve Prioridad
01 Python intermedio async/await, decoradores, type hints, excepciones 🔴 EL grande
02 APIs, HTTP y JSON endpoints, API keys, JSON anidado, requests, status codes 🟢 cerrar detalles
03 CLI + Git terminal, pip, .env, add/commit/push, merge conflicts 🟡 medio
04 LLMs y prompting system prompt, context window, tokens, LLM por API 🟢 repaso
05 Docker básico (deseable) contenedor vs VM, imagen, docker run, compose ⚪ opcional
06 Arquitectura de software (deseable) acoplamiento, interfaces, patrones, monolito vs servicios ⚪ opcional
07 ML y fine-tuning (deseable) entrenar vs inferencia, datasets, preferencias/DPO, LoRA ⚪ opcional

Los 4 primeros son los imprescindibles (por ellos empezá). Los 3 deseables se agregaron el 30/7: dan vocabulario para módulos puntuales del curso — leerlos recién cuando los imprescindibles estén en verde.


Las 31 preguntas oficiales, todas juntas (20 imprescindibles + 11 deseables)

① Python intermedio 1. ¿Sabés qué hace async def y por qué una función async no se puede llamar como una función normal? 2. ¿Entendés la diferencia entre await algo() y simplemente algo()? 3. ¿Sabés escribir un decorador propio con @mi_decorador (no solo usar uno ya hecho)? 4. ¿Sabés qué significa def foo(x: int) -> str: y por qué se usa aunque Python no lo obligue en runtime? 5. ¿Manejaste alguna vez try/except con excepciones específicas (no un except: genérico)?

② APIs, HTTP y JSON 1. ¿Sabés qué es un endpoint y qué diferencia hay entre un GET y un POST? 2. ¿Entendés qué es una API key y por qué no se debe subir a un repo público? 3. ¿Podés leer un objeto JSON anidado (con listas y diccionarios adentro) sin confundirte? 4. ¿Usaste alguna vez una librería tipo requests (Python) o fetch (JS) para consumir una API externa? 5. ¿Sabés qué es un código de estado HTTP (200, 401, 429, 500) y qué implica cada uno a grandes rasgos?

③ CLI + Git 1. ¿Podés navegar entre carpetas, crear archivos y borrar archivos desde la terminal sin usar el explorador gráfico? 2. ¿Sabés instalar un paquete con pip install y verificar que se instaló? 3. ¿Sabés qué es una variable de entorno y cómo se define en un archivo .env? 4. ¿Entendés la diferencia entre git add, git commit y git push? 5. ¿Alguna vez resolviste (aunque sea con ayuda) un conflicto de merge?

④ LLMs y prompting 1. ¿Sabés qué es un "system prompt" y en qué se diferencia de lo que escribe el usuario? 2. ¿Entendés qué es una ventana de contexto (context window) y por qué importa su tamaño? 3. ¿Usaste alguna vez un LLM a través de una API (no solo por chat web)? 4. ¿Sabés qué es un "token" en el contexto de LLMs, aunque sea de forma aproximada? 5. ¿Tenés alguna intuición de por qué un mismo prompt puede dar resultados distintos entre modelos?

⑤ Docker básico (deseable — módulo de monitoreo del curso) 1. ¿Sabés qué es un contenedor y en qué se diferencia de una máquina virtual, aunque sea a grandes rasgos? 2. ¿Corriste alguna vez docker run o docker compose up? 3. ¿Entendés qué es un docker-compose.yml y para qué sirve tener varios servicios definidos ahí?

⑥ Arquitectura de software (deseable — módulos 2, 5 y 8 del curso) 1. ¿Sabés qué significa que dos componentes estén "acoplados" y por qué eso suele ser un problema? 2. ¿Entendés la idea de programar contra una interfaz/abstracción en vez de una implementación concreta? 3. ¿Tenés alguna noción de qué es un patrón de diseño (aunque sea uno solo, como Factory o Strategy)? 4. ¿Participaste alguna vez de una decisión de "¿lo hacemos monolítico o separado en servicios?", aunque sea como observador?

⑦ ML y fine-tuning (deseable — módulo 7 del curso) 1. ¿Entendés la diferencia entre entrenar un modelo y usarlo en inferencia? 2. ¿Sabés qué es un dataset etiquetado y por qué hace falta uno para "supervised fine-tuning"? 3. ¿Tenés una intuición de qué significa que un modelo "aprenda una preferencia" a partir de pares de respuestas? 4. ¿Escuchaste hablar de fine-tuning eficiente (LoRA / adaptadores) y por qué existe?


Material oficial gratuito que recomienda UTDT (por si querés ir a la fuente)

Capítulo 01 — Python intermedio

El único capítulo en rojo. UTDT le pone 4-8 horas; con este material son ~4. Volver al índice.

La meta de este capítulo

Al terminar tenés que poder contestar "sí, con confianza" a estas 5 (son textuales del precurso oficial):

  1. ¿Sabés qué hace async def y por qué una función async no se puede llamar como una función normal?
  2. ¿Entendés la diferencia entre await algo() y simplemente algo()?
  3. ¿Sabés escribir un decorador propio con @mi_decorador?
  4. ¿Sabés qué significa def foo(x: int) -> str: y por qué se usa aunque Python no lo obligue?
  5. ¿Manejaste alguna vez try/except con excepciones específicas?

Ojo con lo que NO pide: no pide que escribas software. Pide que leas código sin perderte y que entiendas qué está pasando. Zabaljáuregui lo dijo en la reunión: "hay que poder programar, no porque vayan a escribir código... hay que entender algoritmos, llamadas API, funciones". Los labs del curso parten de código ya armado y vos completás bloques. Leer > escribir.


Parte 0 — Cómo probar todo esto (5 minutos)

No instales nada todavía si no querés. Para practicar hoy mismo tenés dos caminos:

  • El fácil: https://www.online-python.com o Google Colab (colab.research.google.com) — escribís y le das Play. Cero instalación.
  • El de verdad (recomendado para agosto): instalar Python desde python.org y correr python archivo.py en la terminal. Cuando lo hagamos, lo hacemos juntos en 10 minutos.

Todos los ejemplos de acá abajo se copian y corren tal cual.


Parte 1 — Los cuatro ladrillos (30 min, para que lo demás se entienda)

Esto ya lo sabés de Excel y de dirigirme a mí. Es la misma lógica con otra sintaxis.

1.1 Variables y tipos

codigo = "Cody0074"        # texto (string)
precio = 26990             # entero (int)
comision = 5000.50         # decimal (float)
esta_vendida = False       # booleano (bool): True o False

f-string — armar texto con variables adentro (la vas a usar todo el tiempo):

print(f"La chapita {codigo} sale ${precio}")
# La chapita Cody0074 sale $26990

1.2 Lista [] = una columna de Excel

chapitas = ["Cody0074", "Cody0090", "Cody0102"]

chapitas[0]        # "Cody0074"  ← ¡empieza en CERO, no en 1!
len(chapitas)      # 3
chapitas.append("Cody0110")   # agregar al final

1.3 Diccionario {} = una ficha con campos (= un JSON)

Este es el más importante de los cuatro: un diccionario de Python y un objeto JSON son la misma cosa. Si entendés esto, el capítulo 02 ya está medio ganado.

chapita = {
    "codigo": "Cody0074",
    "estado": "sellable",
    "precio": 26990,
    "petshop": "PETSHOP PRUEBA",
}

chapita["estado"]              # "sellable"     ← se busca por NOMBRE, no por posición
chapita["estado"] = "vendida"  # se modifica igual
chapita.get("mascota", "sin datos")   # si el campo no existe, devuelve "sin datos"

Y se anidan (una ficha adentro de otra, listas adentro de fichas) — igual que las respuestas de tus edge functions:

pedido = {
    "cliente": {"nombre": "Carla", "mail": "oknutre@gmail.com"},
    "items": [
        {"producto": "kombucha", "cantidad": 2},
        {"producto": "budín", "cantidad": 1},
    ],
}

pedido["cliente"]["nombre"]        # "Carla"
pedido["items"][0]["producto"]     # "kombucha"
len(pedido["items"])               # 2

🎯 Truco de lectura: leelo de izquierda a derecha como si abrieras cajas. pedido → caja items → la caja número 0 → el campo producto.

1.4 if y for — la lógica que ya escribís en fórmulas

for chapita in chapitas:              # "para cada chapita de la lista..."
    if chapita.startswith("Cody00"):
        print(f"{chapita} es de la primera tanda")
    else:
        print(f"{chapita} es nueva")

La indentación (los espacios) ES la sintaxis en Python: lo que está corrido a la derecha es "lo que está adentro del for". No hay llaves. Si te sale un error raro, 8 de cada 10 veces es indentación.

1.5 Funciones — una fórmula con nombre propio

def transferencia_al_local(precio_publico, comision):
    return precio_publico - comision

transferencia_al_local(26990, 5000)     # 21990

def = definir · return = qué devuelve · lo de adentro del paréntesis = los parámetros. Es exactamente tu lógica de Codytag: público $26.990 − comisión $5.000 = $21.990 al local.


Parte 2 — Los 4 temas que pregunta UTDT

Van de más fácil a más difícil. No los saltees: el de decoradores necesita el anterior.


🅐 Type hints — la ficha técnica de la función

Pregunta oficial: ¿Sabés qué significa def foo(x: int) -> str: y por qué se usa aunque Python no lo obligue en runtime?

def transferencia_al_local(precio_publico: int, comision: int) -> int:
    return precio_publico - comision

Se lee: "esta función recibe precio_publico que debería ser un entero y comision que debería ser un entero, y devuelve un entero". La flecha -> es lo que devuelve.

La parte clave (que es lo que la pregunta busca): Python NO los controla. Si le pasás texto, no explota por el type hint — explota después, cuando intente restar. Entonces ¿para qué sirven?

  1. Son documentación que no miente, pegada al código. Le decís al que lee (o al agente) qué espera esa función sin tener que adivinar.
  2. El editor te avisa antes de correr. VS Code / Cursor te subraya el error mientras escribís.
  3. Hay herramientas que los verifican (mypy) — chequeo de calidad automático.
  4. ⭐ En agentes son casi obligatorios: cuando le das una herramienta a un LLM, el framework lee los type hints para generar el esquema que el modelo necesita para saber cómo llamarla. Sin type hints, tu herramienta le llega al modelo sin instrucciones. Por eso este tema está en el precurso: es la puerta al módulo 4 del curso (tool use).

🏭 Tu analogía: es la ficha técnica del artículo. La máquina va a intentar tejer igual si le mandás el hilado equivocado — la ficha no lo impide. Pero sin ficha, el que viene atrás no sabe qué corresponde, y con ficha el sistema puede validar antes de arrancar.

Los tipos que vas a ver: int, float, str, bool, list[str] (lista de textos), dict (ficha), None (nada). Ejemplo completo:

def buscar_chapita(codigo: str) -> dict:
    ...

🅑 try / except con excepciones específicas — el plan B de la línea

Pregunta oficial: ¿Manejaste alguna vez try/except con excepciones específicas (no un except: genérico)?

Cuando algo puede fallar (una API que no responde, un archivo que no existe, un JSON roto), Python "lanza una excepción" y el programa se corta. try/except es decir: "probá esto; si falla de tal manera, hacé esto otro".

try:
    respuesta = requests.get("https://api.codytag.com/chapita/Cody0074", timeout=10)
    respuesta.raise_for_status()      # convierte un 404 / 500 en excepción
    datos = respuesta.json()

except requests.Timeout:
    print("El servidor tardó demasiado. Reintentar en un rato.")

except requests.ConnectionError:
    print("No hay internet o el servidor está caído.")

except requests.HTTPError:
    print("Respondió, pero con un código de error (404, 500...).")

except requests.exceptions.JSONDecodeError:
    print("Respondió, pero lo que mandó no es JSON válido.")

⚠️ Dos detalles que verifiqué en la documentación oficial de requests y corriendo el código, porque son contraintuitivos: requests.get() NO lanza excepción ante un 404 o un 500 — para el programa "salió bien". Por eso está el raise_for_status(): sin esa línea, ese except HTTPError nunca se dispara. Si no le pasás timeout=, requests no tiene límite de espera y tu programa puede quedarse colgado para siempre.

Por qué "específicas" y no except: pelado: un except: a secas atrapa todo — incluso el Ctrl+C con el que querés cortar el programa, y sobre todo los errores de tu propio código. El síntoma clásico: el agente "anda" pero devuelve cualquier cosa, porque hace media hora se está tragando un error de tipeo tuyo sin decir nada.

🏭 Tu analogía: no es lo mismo el cartel "si algo falla, apagá la planta" que tener un procedimiento por falla: si se corta el hilado hacés A, si se traba el carro hacés B, si se va la luz hacés C. El except: genérico es apagar la planta cada vez que suena cualquier pitido — y nunca enterarte de qué se rompió.

Tres piezas más que vas a ver en clase:

try:
    ...
except ValueError as e:      # "as e" = guardame el error en la variable e
    print(f"Falló: {e}")     # para poder loguearlo (clave en monitoreo, módulo 10)
else:
    print("Salió todo bien") # se ejecuta solo si NO hubo error
finally:
    print("Esto corre siempre")  # pase lo que pase (cerrar conexiones, etc.)

🅒 Decoradores — el control de calidad que envuelve al proceso

Pregunta oficial: ¿Sabés escribir un decorador propio con @mi_decorador?

Paso previo imprescindible: en Python, una función es un valor como cualquier otro. La podés guardar en una variable y pasársela a otra función:

def saludar():
    print("Hola")

f = saludar        # SIN paréntesis: guardo la función, no la ejecuto
f()                # ahora sí la ejecuto → "Hola"

Un decorador es una función que recibe una función y devuelve otra función mejorada, sin tocar el código original. La sintaxis @algo arriba de un def es solo un atajo.

Ejemplo real de tu mundo: reintentar automáticamente cuando la API falla (esto es literalmente lo que hace un agente robusto cuando una herramienta se cae).

def con_reintento(func):                    # ① recibe la función original
    def envoltorio(*args, **kwargs):        # ② arma la versión "envuelta"
        for intento in range(1, 4):         # hasta 3 intentos
            try:
                return func(*args, **kwargs)     # ③ ejecuta la original
            except ConnectionError:
                print(f"Intento {intento} falló, reintento...")
        raise ConnectionError("No se pudo después de 3 intentos")
    return envoltorio                       # ④ devuelve la envuelta


@con_reintento
def consultar_chapita(codigo: str) -> dict:
    ...

A partir de ahí, cada vez que alguien llame consultar_chapita("Cody0074"), en realidad está llamando al envoltorio: intenta, y si se cae la conexión, reintenta solo. El código de consultar_chapita quedó intacto — no sabe que lo están cuidando.

  • *args, **kwargs significa "cualquier cosa que le hayan pasado, pasásela igual a la original". Se escribe siempre así; no te trabes con eso.
  • raise = lanzar una excepción a propósito (lo contrario de except, que la atrapa).

🏭 Tu analogía: el operario teje igual que siempre. Vos le agregaste un control de calidad antes y después sin cambiarle el trabajo: si la pieza sale mal, se rehace. El proceso central no se enteró.

Dónde te lo vas a cruzar en el curso (por eso lo piden): los frameworks de agentes declaran las herramientas con decoradores. Vas a ver cosas así todo el tiempo:

@tool
def buscar_pedido(id_pedido: str) -> dict:
    """Busca un pedido por su ID."""
    ...

Ese @tool es un decorador que registra la función como herramienta disponible para el LLM, le lee los type hints y el texto de abajo del def (el docstring) para armar la descripción que ve el modelo. Type hints + decoradores + docstring = así se le da una herramienta a un agente. Ahí se junta todo lo de este capítulo.


🅓 async / await — el horno y la espera

Preguntas oficiales: ¿Qué hace async def y por qué no se puede llamar como una función normal? · ¿Diferencia entre await algo() y algo()?

Este es el que más cuesta y el que más aparece en agentes. Vamos con la analogía primero.

🏭 Tu analogía: tenés que teñir 3 partidas. Cada una está 40 minutos en la máquina. - Modo sincrónico (el default): ponés la partida 1, te quedás parado mirando 40 minutos, la sacás, ponés la 2, mirás 40 minutos... → 2 horas. - Modo asincrónico: ponés las 3 partidas, y mientras las máquinas trabajan hacés otra cosa. Volvés cuando avisan → 40 minutos.

async no hace que la máquina tiña más rápido. Hace que vos no te quedes parado mirando mientras espera.

Esto importa muchísimo en agentes porque un agente se pasa la vida esperando: espera al LLM, espera a una API, espera a una base de datos. Esperar parado es tirar tiempo (y en producción, plata).

import asyncio

async def consultar_chapita(codigo: str) -> str:      # ① async def
    print(f"Consultando {codigo}...")
    await asyncio.sleep(2)                            # ② acá espera "la respuesta"
    return f"{codigo}: OK"


async def main():
    resultados = await asyncio.gather(                # ③ las 3 a la vez
        consultar_chapita("Cody0074"),
        consultar_chapita("Cody0090"),
        consultar_chapita("Cody0102"),
    )
    print(resultados)


asyncio.run(main())          # ④ el arranque desde el mundo normal

Esto tarda 2 segundos, no 6. Las tres esperas ocurren al mismo tiempo.

Las 3 cosas que tenés que poder decir:

① Por qué una función async no se llama como una normal. Una función normal, cuando la llamás, hace el trabajo y te devuelve el resultado. Una async def, cuando la llamás, no ejecuta nada: te devuelve una corrutina, que es como una orden de trabajo firmada pero sin entregar a producción. Alguien tiene que llevarla al taller. Ese alguien es await (o asyncio.gather, o asyncio.run).

consultar_chapita("Cody0074")
# <coroutine object consultar_chapita at 0x...>   ← una ORDEN, no el resultado
# (y Python te tira un warning: "coroutine was never awaited")

await algo() vs algo().

Qué pasa
algo() Te devuelve la orden de trabajo (corrutina). No se hizo nada.
await algo() Manda la orden a producción, libera el tiempo de espera para otra cosa, y cuando termina te devuelve el resultado real.

③ Dónde se puede usar await. Solo adentro de una función async. Si lo ponés en código normal, error de sintaxis. Por eso siempre hay un asyncio.run(main()) en el borde: es el puente entre el mundo normal y el mundo async.

⚠️ El error clásico (te lo vas a hacer en el lab, y ahora lo vas a reconocer): te olvidás un await, el programa no falla, pero el resultado es un objeto raro <coroutine object ...> en vez del dato. Cuando veas eso: te faltó un await.


Parte 3 — Ejercicio final: leer código real

No lo escribas: leelo y contate en voz alta qué hace. Después chequeás con el desglose.

import asyncio
from typing import Any


def con_log(func):
    async def envoltorio(*args, **kwargs):
        print(f"→ Llamando a {func.__name__}")
        resultado = await func(*args, **kwargs)
        print(f"← {func.__name__} terminó")
        return resultado
    return envoltorio


@con_log
async def traer_pedido(id_pedido: str) -> dict[str, Any]:
    """Trae un pedido de la Sheet por su ID."""
    try:
        await asyncio.sleep(1)
        return {"id": id_pedido, "cliente": "Carla", "items": [{"producto": "budín"}]}
    except ConnectionError:
        return {}


async def main():
    pedidos = await asyncio.gather(traer_pedido("A-101"), traer_pedido("A-102"))
    for p in pedidos:
        if p:
            print(f'{p["id"]} — {p["cliente"]} pidió {p["items"][0]["producto"]}')


asyncio.run(main())

El desglose (leelo recién después de intentarlo): 1. con_log es un decorador que imprime antes y después. Como la función que envuelve es async, el envoltorio también tiene que serlo y hace await func(...). 2. traer_pedido tiene type hints (id_pedido: str → devuelve un dict), un docstring, un try/except específico y una espera simulada de 1 segundo. 3. main lanza los dos pedidos en paralelo con gather → tarda 1 segundo, no 2. 4. El for recorre los resultados, saltea los vacíos (if p:) y navega el JSON anidado hasta el nombre del producto.

Si entendiste esos 4 puntos, el módulo de Python está aprobado. Eso es exactamente el nivel que pide el curso.


Parte 4 — Machete de una carilla

Concepto En una línea Se ve así
f-string texto con variables adentro f"Chapita {codigo}"
lista columna ["a", "b"] · lista[0]
diccionario ficha / JSON {"codigo": "Cody0074"} · d["codigo"]
función fórmula con nombre def f(x): return x
type hint ficha técnica, no obliga def f(x: int) -> str:
docstring qué hace, en criollo """Busca un pedido."""
try/except plan B por tipo de falla except requests.Timeout:
raise lanzar un error a propósito raise ValueError("mal")
decorador envolver sin tocar @con_reintento
async def función que puede esperar sin bloquear async def f(): ...
corrutina orden de trabajo sin ejecutar lo que devuelve f() sin await
await mandar a producción y traer el resultado r = await f()
asyncio.gather varias esperas a la vez await asyncio.gather(a(), b())
asyncio.run puente del mundo normal al async asyncio.run(main())
pip install instalar una librería pip install requests

Parte 5 — Ahora contestá las 5 (en voz alta)

  1. async def y por qué no se llama normal"define una función que puede esperar sin bloquear; llamarla no la ejecuta, devuelve una corrutina que alguien tiene que awaitear."
  2. await algo() vs algo()"sin await me devuelve la orden de trabajo; con await se ejecuta, libera la espera y me devuelve el resultado."
  3. Decorador propio"una función que recibe una función y devuelve una versión envuelta; @nombre es el atajo. Ejemplo: reintento automático."
  4. def foo(x: int) -> str:"recibe un int y devuelve un str; Python no lo obliga, pero documenta, lo chequea el editor, y los frameworks de agentes lo usan para generarle al LLM el esquema de la herramienta."
  5. try/except específico"un except por tipo de falla en vez de uno genérico, para no tragarme errores propios y poder actuar distinto según qué se rompió."

Si las cinco te salieron sin trabarte: módulo ① en verde. Seguí con el capítulo 02, que ya lo tenés casi todo.

Capítulo 02 — APIs, HTTP y JSON

UTDT le pone 2-3 horas. Este lo hacés todos los días sin llamarlo así: lo único que falta es ponerle los nombres formales. Volver al índice.

La meta de este capítulo

Poder contestar "sí, con confianza" a estas 5 (textuales del precurso oficial):

  1. ¿Sabés qué es un endpoint y qué diferencia hay entre un GET y un POST?
  2. ¿Entendés qué es una API key y por qué no se debe subir a un repo público?
  3. ¿Podés leer un objeto JSON anidado (con listas y diccionarios adentro) sin confundirte?
  4. ¿Usaste alguna vez una librería tipo requests (Python) o fetch (JS) para consumir una API externa?
  5. ¿Sabés qué es un código de estado HTTP (200, 401, 429, 500) y qué implica cada uno?

Arranquemos por lo que ya está hecho: vos no solo consumís APIs — construiste una. El MCP de Codytag que uso para consultar tu stock es un servidor de API. Cuando pido get_stock, viaja un pedido HTTP, un servidor lo atiende y me devuelve JSON. Eso es todo este capítulo, contado al revés.


Parte 1 — Qué es una API (y por qué existe)

Una API es el mostrador de atención de un sistema: la lista de cosas que le podés pedir y el formato acordado para pedirlas. No entrás a la base de datos a manotear — le pedís al mostrador.

🏭 Tu analogía: es el pañol de la fábrica. No entrás a agarrar herramientas de la estantería: pedís por ventanilla, con un vale, y el pañolero te la da. Ese protocolo existe para que nadie rompa nada, para saber quién pidió qué, y para poder cambiar el orden de la estantería sin que a vos te cambie nada.

Ese último punto es la razón técnica de fondo: la API es un contrato. Mientras el mostrador siga atendiendo igual, el de adentro puede reformar todo. Por eso tu panel de Codytag no se rompe cuando cambiamos algo en Supabase.

Los cuatro casos tuyos, para anclar:

Lo que ya tenés Qué es en jerga
El panel del petshop lee el stock Un cliente consumiendo una API
El formulario de Carla manda un pedido al Apps Script Un cliente enviando datos a una API
Las edge functions de Supabase Endpoints de una API que escribimos nosotros
El MCP de Codytag Un servidor de API que exponés hacia mí

Parte 2 — Anatomía de un pedido HTTP

Todo pedido HTTP tiene siempre las mismas cuatro partes. Si te acordás de estas cuatro, podés leer cualquier código que llame a cualquier API del mundo.

POST  https://api.codytag.com/chapitas          ← ① método  ② endpoint (URL)
Authorization: Bearer eyJhbGciOi...             ← ③ headers (metadatos del sobre)
Content-Type: application/json

{"codigo": "Cody0074", "tamano": "28mm"}        ← ④ body (la carga, solo si hace falta)

Y la respuesta viene con la misma lógica:

201 Created                                     ← ① status code
Content-Type: application/json                  ← ② headers

{"id": 74, "estado": "draft"}                   ← ③ body

📮 La analogía del sobre: el método es qué querés hacer, el endpoint es la dirección, los headers son lo que va escrito en el sobre (quién manda, en qué idioma, con qué credencial) y el body es la carta adentro. Un GET es un sobre vacío: solo vas a buscar.

El endpoint

Es la URL específica donde la API atiende una operación. La API es el edificio; los endpoints son las ventanillas:

https://api.codytag.com/chapitas          ← ventanilla de chapitas
https://api.codytag.com/chapitas/Cody0074 ← ventanilla de UNA chapita puntual
https://api.codytag.com/ventas            ← ventanilla de ventas

⚠️ Ojo con esto, que es media pregunta del autodiagnóstico: una ventanilla sola no define una operación. Operación = endpoint + método. La misma URL /chapitas hace dos cosas distintas según con qué verbo llegues:

  • GET /chapitas → dame la lista de chapitas
  • POST /chapitas → creá una chapita nueva

Parte 3 — Los verbos: tu ABM de toda la vida

Verbo Qué hace Tu ejemplo ABM
GET Leer, sin modificar nada el panel muestra el stock Baja... perdón: consulta
POST Crear algo nuevo Carla manda un pedido Alta
PUT / PATCH Modificar algo que ya existe marcar una chapita como vendida Modificación
DELETE Borrar eliminar una venta cargada mal Baja

PUT vs PATCH (por si aparece): PUT reemplaza el registro entero, PATCH cambia solo los campos que le mandás. En la práctica se usa mucho más PATCH.

GET vs POST — la diferencia REAL

Esta es la pregunta oficial, y tiene tres respuestas que hay que tener separadas:

  1. Intención: GET pide, POST crea. Un GET es seguro: podés repetirlo mil veces y no pasa nada. Un POST repetido crea el pedido dos veces (por eso los botones "Enviar" se deshabilitan después del primer click — es literalmente ese problema).
  2. Dónde viajan los datos: el GET los manda en la URL (?codigo=Cody0074), el POST en el body. Por eso el GET queda escrito en el historial del navegador y en los logs del servidor, y el POST no.
  3. ⚠️ Lo que NO los diferencia: el cifrado. Los dos viajan igual de expuestos por HTTP y los dos van cifrados por HTTPS. Lo que protege es la S, no el verbo. Es un mito muy extendido y una opción incorrecta clásica en los cuestionarios.

Parte 4 — Códigos de estado: la respuesta en clave

El servidor siempre contesta con un número de tres cifras. Lo que importa es la familia:

Familia Significa El truco para acordarte
2xx Salió bien 200 OK · 201 Created
3xx Está en otro lado 301 se mudó · 304 no cambió
4xx El problema es del que pide 4 = culpa tuya
5xx El problema es del servidor 5 = culpa de ellos

Los seis que hay que saber sí o sí:

Código Nombre Qué pasó En tu mundo
200 OK Todo bien el panel cargó el stock
201 Created Se creó el recurso entró un pedido de OK Nutre
400 Bad Request Le mandaste algo mal armado falta un campo obligatorio
401 Unauthorized No te identificaste entrar al panel sin login
403 Forbidden Te identificaste, pero no podés un petshop entrando al admin
404 Not Found Eso no existe acá escanear un QR de una chapita inexistente
429 Too Many Requests Pediste demasiado, muy rápido un for que dispara 500 llamadas
500 Internal Server Error Explotó del otro lado un bug en la edge function

🚪 401 vs 403, la analogía del boliche: 401 = viniste sin documento, no sé quién sos. 403 = sé perfectamente quién sos, pero no estás en la lista. Cuando debuguees un agente, esta diferencia te ahorra media hora: 401 → revisá el token; 403 → revisá los permisos de ese token.

⭐ El 429, el que más vas a ver construyendo agentes

Casi todas las APIs tienen rate limit: un máximo de pedidos por minuto. Un agente entusiasta que dispara llamadas en un bucle lo revienta en segundos, y ahí empiezan a llover los 429.

La solución tiene nombre y la vas a escuchar en el curso: backoff exponencial — esperar 1 segundo, después 2, después 4, después 8, en vez de martillar. ¿Y dónde se implementa? En un decorador de reintento, exactamente el del capítulo 01. Los temas del precurso cierran entre sí; no son cuatro listas sueltas.


Parte 5 — API keys: el tema de seguridad

Una API key (o token) es la credencial que demuestra quién sos ante la API. Viaja en un header:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

💳 Tratala como una tarjeta de crédito con tu nombre. El que la tenga: gasta tus tokens, lee tus datos y borra tus cosas. No hay contraseña que la proteja — la clave ES la credencial.

Por qué nunca va escrita en el código que subís a un repo:

  1. Cualquiera que vea el repo la puede usar. Hay bots escaneando GitHub las 24 horas buscando claves; el tiempo hasta el primer uso indebido se mide en minutos, no en días.
  2. ⚠️ Y borrarla después NO alcanza. Git guarda el historial completo: la clave sigue viva en los commits viejos aunque la saques del archivo actual. Si se te escapó una, la única solución real es rotarla (darla de baja en el proveedor y sacar una nueva).

Dónde va entonces: en un archivo .env que el .gitignore deja afuera del repo, y el código la lee al arrancar con os.getenv("API_KEY"). En producción se usan los "secrets" del servicio (Supabase, Cloudflare, Google Cloud) — misma idea, con candado. El detalle práctico está en el capítulo 03.


Parte 6 — JSON: el idioma común

JSON es el formato en que se hablan todos tus sistemas. Y ya lo sabés, aunque no lo sepas: un objeto JSON y un diccionario de Python son la misma cosa.

Las tres reglas (y los tres errores que rompen todo)

{"codigo": "Cody0074", "precio": 26990, "reclamada": false}
  1. Las claves van SIEMPRE entre comillas dobles. {codigo: "x"} no es JSON: es un objeto de JavaScript. Se parecen muchísimo y no son lo mismo.
  2. Comillas dobles, nunca simples. {'codigo': 'x'} es un diccionario de Python, no JSON.
  3. Sin coma después del último campo. {"codigo": "x",} — la famosa trailing comma rompe parsers de verdad.

Y dos detalles finos: los booleanos en JSON van en minúscula (true/false) mientras que en Python van con mayúscula (True/False), y JSON no admite comentarios. import json traduce entre los dos mundos: json.loads() (texto → diccionario) y json.dumps() (diccionario → texto).

Anidamiento: la pregunta oficial #3

Los dos símbolos, y todo sale de acá:

  • { } = una ficha con campos (un objeto)
  • [ ] = una lista de cosas

Y se anidan sin límite. Este es el JSON típico que devuelve un panel:

{
  "total": 2,
  "pedidos": [
    {"id": "A-101", "cliente": {"nombre": "Carla"},
     "items": [{"producto": "budín", "cantidad": 2}]},
    {"id": "A-102", "cliente": {"nombre": "Nayla"},
     "items": [{"producto": "kombucha", "cantidad": 1}]}
  ]
}

Cómo se navega, sin confundirse nunca: leelo de izquierda a derecha, como si abrieras cajas, y aplicá esta regla:

🎯 Si el paso siguiente es un {, usás el NOMBRE del campo. Si es un [, usás un NÚMERO de posición.

datos["total"]                                    # 2
datos["pedidos"][0]["id"]                         # "A-101"
datos["pedidos"][0]["cliente"]["nombre"]          # "Carla"
datos["pedidos"][0]["items"][0]["producto"]       # "budín"
datos["pedidos"][1]["items"][0]["cantidad"]       # 1
len(datos["pedidos"])                             # 2

El error clásico (y la opción incorrecta más elegida): saltearse el [0]. Escribir datos["pedidos"]["items"] no funciona porque pedidos es una lista — Python no sabe de cuál de los pedidos le estás hablando.

Para recorrerlos todos:

for pedido in datos["pedidos"]:
    print(pedido["cliente"]["nombre"], "pidió", pedido["items"][0]["producto"])

Parte 7 — Consumir una API de verdad (requests)

Esta es la pregunta oficial #4. El código mínimo, bien hecho:

import requests

url = "https://api.codytag.com/chapitas/Cody0074"
headers = {"Authorization": "Bearer " + API_KEY}

try:
    respuesta = requests.get(url, headers=headers, timeout=10)
    respuesta.raise_for_status()          # convierte un 404/500 en excepción
    datos = respuesta.json()              # acá recién tenés el diccionario
    print(datos["estado"])

except requests.Timeout:
    print("El servidor no contestó a tiempo.")
except requests.HTTPError as e:
    print(f"El servidor respondió con error: {e}")
except requests.ConnectionError:
    print("No hay red o el servidor está caído.")

Las cuatro cosas que tenés que entender de ese bloque:

  1. requests.get() te devuelve el sobre entero, no la carta. Adentro está respuesta.status_code, respuesta.headers y el contenido. Los datos salen con respuesta.json().

  2. ⚠️ requests NO lanza excepción ante un 404 o un 500. Para el programa "salió bien": le llegó una respuesta. Sin la línea raise_for_status(), seguís trabajando con un error como si fuera un dato. (Verificado en la documentación oficial y corriendo el código — está en verificar-material.py.)

  3. ⚠️ Y hay una vuelta más: que .json() funcione tampoco garantiza nada. Muchos servidores devuelven un JSON impecable con el detalle del error, junto con el 500. La documentación oficial lo dice con todas las letras.

  4. ⚠️ Si no pasás timeout=, requests no tiene límite de espera y tu programa puede quedarse colgado para siempre. En un agente que corre solo, eso es fatal.

En JavaScript el equivalente es fetch() — mismo concepto, otra sintaxis. Es lo que usa la app del taller de Nayla para hablar con Supabase.


Parte 8 — Webhooks: la API al revés

  • API normal: vos preguntás. "¿Hay pedidos nuevos?"
  • Webhook: te avisan. "¡Entró un pedido!" — el otro sistema le pega un POST a tu endpoint cuando pasa algo.

Preguntar cada X minutos tiene nombre propio y es el rival del webhook: se llama polling. Funciona, pero gasta llamadas al pedo y siempre llega tarde.

🔔 El webhook es el timbre de tu casa. El polling es salir a mirar la puerta cada 5 minutos.

Así funcionaría WhatsApp Business en Codytag: cada mensaje de un cliente dispara un POST a tu endpoint, y tu sistema reacciona en el momento.


Machete de una carilla

Concepto En una línea
API El mostrador: qué le podés pedir a un sistema y cómo
Endpoint La URL de una ventanilla concreta
Operación endpoint + método (GET /chapitasPOST /chapitas)
GET Leer. Repetible sin consecuencias. Datos en la URL
POST Crear. Datos en el body. Repetirlo duplica
PUT/PATCH Modificar (entero / por campos) · DELETE borrar
Header Metadato del sobre (Authorization, Content-Type)
Body La carga de datos del pedido o de la respuesta
2xx / 4xx / 5xx Bien / culpa tuya / culpa del servidor
401 / 403 No te identificaste / no tenés permiso
429 Demasiados pedidos → backoff exponencial
API key Credencial = tarjeta de crédito. Va en .env, nunca al repo
JSON {} ficha · [] lista · claves con comillas dobles · sin coma final
Navegar JSON Si sigue { → nombre. Si sigue [ → número
requests.get() Devuelve el sobre. .json() saca los datos
raise_for_status() Sin esto, un 404 pasa como si nada
timeout= Sin esto, tu programa puede colgarse para siempre
Webhook Te avisan (timbre) · Polling = preguntar cada rato

Ahora contestá las 5

  1. Endpoint y GET vs POST"El endpoint es la URL donde la API atiende una operación; la operación real es endpoint + método. GET pide sin modificar y manda los datos en la URL; POST crea y los manda en el body."
  2. API key y por qué no va al repo"Es la credencial que me identifica; quien la tenga puede gastar y borrar en mi nombre. Hay bots escaneando repos, y borrarla del archivo no alcanza porque queda en el historial de git: hay que rotarla. Va en un .env ignorado por git."
  3. JSON anidado"Sí: {} es ficha y [] es lista; se navega de izquierda a derecha, con nombre si sigue una ficha y con número de posición si sigue una lista."
  4. requests / fetch"Sí. requests.get() devuelve un objeto respuesta con status, headers y cuerpo; los datos se sacan con .json(), hay que llamar a raise_for_status() para que un 404 explote, y siempre pasar timeout."
  5. Status codes"2xx bien, 4xx problema del que pide (401 sin identificar, 403 sin permiso, 404 no existe, 429 demasiados pedidos), 5xx problema del servidor."

Si las cinco salieron: módulo ② en verde. Seguí con el capítulo 03.

Capítulo 03 — Línea de comandos y git

UTDT le pone 3-5 horas. Vivís en la terminal conmigo y tu cerebro ES un repositorio git — pero hay tres temas que nunca tocaste y están en el autodiagnóstico. Volver al índice.

La meta de este capítulo

Poder contestar "sí, con confianza" a estas 5 (textuales del precurso oficial):

  1. ¿Podés navegar entre carpetas, crear archivos y borrar archivos desde la terminal sin usar el explorador gráfico?
  2. ¿Sabés instalar un paquete con pip install y verificar que se instaló?
  3. ¿Sabés qué es una variable de entorno y cómo se define en un archivo .env?
  4. ¿Entendés la diferencia entre git add, git commit y git push?
  5. ¿Alguna vez resolviste (aunque sea con ayuda) un conflicto de merge?

Tu foto honesta: la 1 y la 4 las tenés. La 2, la 3 y la 5 no las hiciste nunca — son el grueso de este capítulo.

⚠️ Nota para Windows: Die trabaja en Windows 11 con Claude Code en la terminal de Cursor. Algunos comandos cambian según si estás en PowerShell o en Git Bash. Donde hay diferencia, la marco. Los comandos de git son idénticos en todos lados.


Parte 1 — La terminal: qué es y por qué no se muere nunca

La terminal es el mismo sistema operativo de siempre, manejado escribiendo en vez de haciendo clicks. No es una versión "para expertos": es una versión automatizable.

⚙️ La razón que te va a cerrar: un click no se puede repetir solo. Un comando sí. Todo lo que se escribe se puede guardar en un archivo, programar, versionar y correr mil veces igual. Es la diferencia entre hacer una pieza a mano y dejar la máquina programada. Por eso todo lo serio (deploys, servidores, agentes) vive en la terminal.

El prompt es la línea donde escribís, y siempre te dice dónde estás parado:

C:\Users\diego\OneDrive\Agentes rev00>

Eso es fundamental: en la terminal siempre estás "adentro" de una carpeta, y los comandos se ejecutan ahí. El 80% de los errores de principiante son estar parado en el lugar equivocado.

Rutas absolutas y relativas:

Tipo Ejemplo Cuándo
Absoluta C:\Users\diego\OneDrive\Agentes rev00\Codytag desde cualquier lado
Relativa Codytag desde la carpeta que la contiene
El padre .. subir un nivel
Acá . la carpeta actual

Parte 2 — Los comandos que necesitás (son ocho)

Moverse y mirar

Qué Git Bash / Mac / Linux PowerShell
¿Dónde estoy? pwd pwd
¿Qué hay acá? ls ls o dir
Entrar a una carpeta cd Codytag cd Codytag
Subir un nivel cd .. cd ..
Ir a una ruta con espacios cd "Agentes rev00" cd "Agentes rev00"

💡 El truco que más tiempo ahorra: la tecla TAB. Escribís cd Cod + TAB y la terminal completa Codytag sola. Además te confirma que existe: si no completa, esa carpeta no está ahí. Usala siempre; evita el 90% de los errores de tipeo.

Crear y borrar

Qué Git Bash PowerShell
Crear carpeta mkdir informes mkdir informes
Crear archivo vacío touch nota.md New-Item nota.md
Ver el contenido cat nota.md cat nota.md
Copiar cp a.md b.md Copy-Item a.md b.md
Mover / renombrar mv a.md b.md Move-Item a.md b.md
Borrar un archivo rm nota.md Remove-Item nota.md
Borrar carpeta + contenido rm -r informes Remove-Item -Recurse informes

☠️ La advertencia que hay que tener tatuada: la terminal NO tiene papelera de reciclaje. rm borra y no vuelve. No hay "deshacer".

Por eso en tus reglas está escrito que yo no borro nada sin verificar primero, y por eso versionamos el cerebro con git: el historial de git es la única red de seguridad real cuando esto pasa.

Flags

Los flags son las opciones del comando. Dos formatos, la misma cosa:

  • Corto: una raya y una letra → -r, -m
  • Largo: dos rayas y la palabra → --recursive, --message
git commit -m "arreglo el scanner"      # -m es la versión corta de --message

El flag que hay que conocer siempre: --help. git commit --help te lista todos los demás. No hay que memorizar nada.


Parte 3 — pip y los entornos virtuales (pregunta oficial #2)

Qué es pip

pip es el instalador de librerías de Python. Baja paquetes de PyPI, el repositorio público donde la comunidad publica sus librerías (cientos de miles).

pip install requests

Cuidado con confundir tres cosas que se usan juntas:

Herramienta Qué hace
python Ejecuta tus archivos .py
pip Instala librerías
venv Aísla las librerías de cada proyecto

Cómo verificar que quedó instalado

Esta es literalmente la pregunta del autodiagnóstico. Tres formas, de menor a mayor certeza:

pip show requests      # versión, ubicación y dependencias
pip list               # todo lo instalado en este entorno
python -c "import requests; print(requests.__version__)"   # la prueba definitiva

⚠️ La trampa del cuestionario: mirar requirements.txt NO verifica nada. Ese archivo es la lista de lo que el proyecto NECESITA, no de lo que tenés instalado. Se usa al revés: pip install -r requirements.txt instala toda la lista de una.

Entornos virtuales (el concepto, no hace falta dominarlo)

Un entorno virtual es una cajita con las librerías de un proyecto. Existe porque el proyecto A puede necesitar la versión 1 de algo y el B la versión 2 — sin cajitas, se pelean.

python -m venv .venv          # crear la cajita
.venv\Scripts\activate        # entrar (Windows)
source .venv/bin/activate     # entrar (Mac/Linux/Git Bash)

Cuando estás adentro, el prompt te lo muestra: (.venv) C:\...>. Alcanza con reconocerlo: si ves eso al principio de la línea, estás en un entorno virtual y lo que instales queda ahí adentro. (Dato tuyo: el proyecto Perfumes ya usa uno — .\.venv\Scripts\python.exe.)


Parte 4 — Variables de entorno y .env (pregunta oficial #3)

Una variable de entorno es configuración que vive FUERA del código: claves, URLs de base de datos, si estás en producción o en pruebas.

Por qué afuera, dos razones concretas:

  1. El mismo código funciona en tu compu y en el servidor, cambiando solo la configuración. Nada de "acordate de cambiar esta línea antes de subir".
  2. Los secretos no quedan escritos en el código — que es la respuesta técnica a la pregunta de la API key del capítulo 02.

El archivo .env

Es lo más simple que existe: NOMBRE=valor, una por línea.

# .env  ← el archivo se llama así, con el punto adelante y sin extensión
SUPABASE_URL=https://xxxx.supabase.co
SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
MODO=produccion

Las cuatro reglas: 1. Sin espacios alrededor del =. CLAVE = valor puede romper. 2. Sin comillas, salvo que el valor tenga espacios. 3. Comentarios con #. 4. El nombre del archivo es .env a secas. ⚠️ Windows esconde los archivos que empiezan con punto: activá "elementos ocultos" en el explorador si no lo ves.

Cómo lo lee el código:

import os
from dotenv import load_dotenv     # pip install python-dotenv

load_dotenv()                      # lee el .env y lo carga
clave = os.getenv("SUPABASE_KEY")  # y acá lo usás

Y la regla que cierra el círculo: el .env SIEMPRE va listado en el .gitignore. Sin eso, todo el capítulo de seguridad no sirve de nada.


Parte 5 — Git: el modelo mental de las tres zonas

Git es una máquina de fotos con historial. Todo lo demás sale de entender que hay tres zonas y que un archivo se mueve entre ellas:

   Tu carpeta          Zona de preparación         El historial
  (working dir)            (staging)                 (repo)
       │                       │                        │
       │──── git add ─────────>│                        │
       │                       │──── git commit ───────>│
       │                       │                        │──── git push ───> servidor

📸 La analogía de la foto de egresados: git add es elegir quién entra en la foto (los acomodás, todavía no sacaste nada). git commit es sacar la foto y ponerle epígrafe. git push es mandarla al fotógrafo para que la publique.

Por qué existe el paso del medio (que es lo que más se pregunta): porque te deja guardar solo una parte de lo que cambiaste. Si tocaste cinco archivos y solo tres son de un mismo arreglo, commiteás esos tres con su mensaje y los otros dos después. El historial queda contando una historia clara en vez de un revoltijo.

Los comandos del día a día

git status              # ¿qué cambió desde la última foto? ← el más usado de todos
git diff                # ¿qué cambió, línea por línea?
git add archivo.md      # preparar un archivo
git add .               # preparar TODO lo que cambió
git commit -m "cierre 26/7: manual de estudio"
git log --oneline       # el historial de commits
git push                # mandar al servidor remoto
git pull                # traer lo que subieron otros

commitpush, y es pregunta segura: después de commitear, tus cambios están en el historial de tu máquina. El servidor no se enteró de nada. Git funciona local primero: podés trabajar, commitear y ver todo el historial sin internet.

De hecho es exactamente lo que hacemos con tu cerebro: git local, jamás push. Tenés historial completo y podés volver a cualquier versión anterior, pero nada sale de tu máquina, porque adentro hay material privado. La nube la hace OneDrive, no GitHub.


Parte 6 — Ramas, merge y conflictos (pregunta oficial #5)

Ramas

Una rama (branch) es una línea de trabajo paralela: probás algo sin tocar la versión buena. La principal se suele llamar main.

git branch                     # ¿en qué rama estoy?
git checkout -b nueva-pantalla # crear una rama y saltar a ella
git checkout main              # volver a la principal
git merge nueva-pantalla       # traer a main lo que hiciste en la rama

🧪 Es el "Guardar como → versión_prueba_2_FINAL_ok.docx", pero bien hecho: sin duplicar archivos y con la posibilidad de unirlo después.

El conflicto de merge: qué es (y qué NO es)

Cuando hacés merge, Git compara y une solo. Si las dos ramas tocaron partes distintas del mismo archivo, lo resuelve sin molestarte. El conflicto aparece únicamente cuando las dos tocaron las mismas líneas:

CONFLICT (content): Merge conflict in memory.md
Automatic merge failed; fix conflicts and then commit the result.

🚨 Lo primero: esto NO es un error ni algo roto. Es Git siendo prudente: no adivina cuál versión vale, te pasa la decisión a vos. Que te asuste la palabra "conflicto" es normal; que te frene, no debería.

Cómo se ve por dentro

Abrís el archivo y Git te dejó un formulario para completar:

Las reglas de cursada se firman el lunes.
<<<<<<< HEAD
El tope semanal es de 10 horas.
=======
El tope semanal es de 8 horas.
>>>>>>> nueva-pantalla
El resto queda igual.
  • Entre <<<<<<< HEAD y =======tu versión (la de la rama en la que estás).
  • Entre ======= y >>>>>>>la versión que estás trayendo.

Cómo se resuelve (los 4 pasos)

  1. Editás el archivo a mano y dejás lo que tiene que quedar. Puede ser una, la otra, o un mix de las dos — vos decidís.
  2. Borrás las tres líneas de marcas (<<<<<<<, =======, >>>>>>>).
  3. git add memory.md → le avisás a Git que ya está resuelto.
  4. git commit → cierra el merge.

⚠️ El error más común no es elegir mal: es olvidarse de borrar las marcas y commitear los <<<<<<< adentro del archivo. Antes de commitear, buscá <<<< en el archivo.

Y el botón de pánico, por si te asustás en el momento: git merge --abort deshace todo el merge y te devuelve a como estabas antes. No perdés nada.


Parte 7 — .gitignore y los secretos

El .gitignore es la lista de "esto no entra al historial":

.env                 # las claves
node_modules/        # librerías (se reinstalan solas)
.venv/               # el entorno virtual
*.mp4                # videos pesados

Tu propio cerebro lo usa: el .gitignore de Agentes rev00 hace que se versionen solo los .md.

La regla de oro, que el curso va a repetir en el módulo de seguridad: los secretos jamás se versionan. Y si ya se versionó uno, no alcanza con borrarlo del archivo: sigue en el historial. Hay que rotar la clave.


Machete de una carilla

Comando Qué hace
pwd · ls · cd X · cd .. dónde estoy · qué hay · entrar · subir
TAB autocompleta (y te confirma que existe)
mkdir · cat · cp · mv · rm crear carpeta · ver · copiar · mover · borrar sin papelera
rm -r carpeta borra carpeta y contenido. No vuelve
-m / --message flag corto / largo · --help los lista todos
pip install X instalar librería · pip show X / pip list para verificar
requirements.txt lo que el proyecto NECESITA (no lo instalado)
python -m venv .venv crear entorno virtual · el prompt muestra (.venv)
variable de entorno configuración fuera del código
.env NOMBRE=valor, sin espacios, sin comillas, siempre en .gitignore
git status qué cambió desde la última foto
git add elegir quién entra en la foto (staging)
git commit -m sacar la foto con epígrafe → historial LOCAL
git push / pull mandar al servidor / traer del servidor
git log --oneline historial · git diff diferencias línea por línea
rama (branch) línea de trabajo paralela · merge las une
conflicto de merge las dos ramas tocaron las mismas líneas
<<<<<<< ======= >>>>>>> tu versión / la otra → editar, borrar marcas, add, commit
git merge --abort botón de pánico: deshace el merge
.gitignore qué NO versionar. Los secretos, JAMÁS

Ahora contestá las 5

  1. Navegar y manejar archivos"Sí: pwd para saber dónde estoy, ls para ver, cd para moverme, mkdir/touch para crear y rm para borrar — sabiendo que borra sin papelera."
  2. pip install y verificar"Instalo con pip install requests y verifico con pip show requests o pip list; la prueba final es importarlo desde Python. requirements.txt NO sirve para verificar: es lo que el proyecto necesita."
  3. Variables de entorno y .env"Es configuración que vive fuera del código; en el .env se escribe NOMBRE=valor, una por línea, sin espacios ni comillas, y el archivo va siempre en el .gitignore."
  4. add / commit / push"add elige qué entra en la próxima foto, commit la saca y la guarda en mi historial local, push la manda al servidor remoto. Commit y push son cosas distintas: se puede commitear sin internet."
  5. Conflicto de merge"Pasa cuando dos ramas tocaron las mismas líneas. Git marca las dos versiones con <<<<<<<, ======= y >>>>>>>; se edita a mano, se borran las marcas, git add y git commit. Y si me asusto, git merge --abort."

Si las cinco salieron: módulo ③ en verde. Seguí con el capítulo 04, que es el que ya tenés más ganado.

Capítulo 04 — LLMs y prompting

UTDT le pone 2-4 horas. Es el módulo que Die ya tiene (10/10 sin estudiar) — pero es también el puente al curso entero: casi todo lo que van a enseñar arranca acá. Si venís de cero, este es el capítulo que más te conviene leer completo. Volver al índice.

La meta de este capítulo

Poder contestar "sí, con confianza" a estas 5 (textuales del precurso oficial):

  1. ¿Sabés qué es un "system prompt" y en qué se diferencia de lo que escribe el usuario?
  2. ¿Entendés qué es una ventana de contexto y por qué importa su tamaño?
  3. ¿Usaste alguna vez un LLM a través de una API (no solo por chat web)?
  4. ¿Sabés qué es un "token" en el contexto de LLMs, aunque sea de forma aproximada?
  5. ¿Tenés alguna intuición de por qué un mismo prompt puede dar resultados distintos entre modelos?

Parte 1 — Qué es un LLM, en serio

Un LLM (Large Language Model) hace una sola cosa: dado un texto, predice qué pedacito viene después. Y después otro. Y otro. Todo lo demás —que razone, que programe, que te escriba un post— emerge de hacer eso muy bien, a una escala enorme.

Suena a poco y es la clave de todo. De esa sola frase se desprenden las cuatro cosas que más te van a importar en el curso:

Porque solo predice texto... ...pasa esto
No consulta una base de datos de hechos puede inventar (alucinaciones)
No ejecuta nada por su cuenta necesita herramientas (tool use)
No recuerda nada entre llamadas necesita que le vuelvas a contar todo (contexto)
Elige la palabra siguiente con algo de azar no es determinista → hacen falta evals

🏭 La analogía que mejor le queda: es un empleado nuevo brillantísimo, con una memoria de 5 minutos. Entiende todo lo que le expliques, redacta mejor que vos, razona rápido... y mañana no se acuerda de nada, ni de vos. Todo el trabajo de "arquitectura de agentes" es, en el fondo, diseñarle el puesto de trabajo a ese empleado: qué le contás al empezar cada día, qué herramientas le das, qué decide solo y qué te consulta.


Parte 2 — Tokens (pregunta oficial #4)

El modelo no lee palabras: pica el texto en tokens, pedacitos de más o menos 4 caracteres — aproximadamente ¾ de una palabra.

"Chapita Cody0074 vendida"  →  ["Chap", "ita", " Cody", "007", "4", " vend", "ida"]

Palabras comunes ("de", "the") suelen ser un token entero. Palabras raras, nombres propios y códigos se parten en varios. Y el español rinde peor que el inglés: el mismo texto gasta más tokens, así que un prompt en castellano sale un poco más caro.

Por qué importa, dos motivos concretos:

  1. Se cobra por token, y por separado los de entrada (lo que le mandás) y los de salida (lo que responde) — la salida suele ser bastante más cara.
  2. El contexto se mide en tokens. Es la unidad de todo el sistema.

⚠️ Ojo con una trampa de vocabulario que aparece en los cuestionarios: "token" significa dos cosas distintas según el contexto. En HTTP, el Bearer token es tu credencial de acceso (capítulo 02). En LLMs, es el pedacito de texto. Misma palabra, mundos distintos.


Parte 3 — La ventana de contexto (pregunta oficial #2)

La ventana de contexto es todo lo que el modelo puede "tener en la cabeza" de una sola vez, medido en tokens. Es su memoria de trabajo, y es fija para cada modelo.

Cuando se llena, algo tiene que salir: se resume o se descarta lo más viejo. Eso es lo que sentís cuando una conversación larguísima empieza a "olvidarse" de cosas del principio.

Y acá viene lo que casi nadie sabe: más ventana NO es siempre mejor

Un contexto lleno de basura da peores respuestas que uno chico y bien elegido. Tiene nombre: le dicen context rot. Meter todo "por las dudas" degrada la calidad, además de costar más.

De ahí sale la disciplina que es un tema entero del curso: context engineering — decidir, para cada llamada, qué entra y qué no.

📁 Y esto explica tu _cerebro mejor que cualquier definición. No es una manía de orden: es la solución correcta al problema. La memoria de verdad vive afuera del modelo, en archivos, y se recarga cuando hace falta. memory.md es corto a propósito (< 200 líneas) y el log.md no se lee entero: se consulta por fecha. Eso es context engineering, y lo venís haciendo hace meses sin llamarlo así.

⚠️ Dos malentendidos caros que conviene tener claros: - Ningún archivo "amplía" la ventana de contexto. Es fija por modelo. Lo que hacés es elegir mejor qué le metés adentro. - Que el modelo lea un archivo NO lo entrena. Leer y aprender son cosas distintas (ver Parte 8).


Parte 4 — System prompt vs. usuario (pregunta oficial #1)

Una llamada a un LLM no es "un texto": son mensajes con rol. Los tres roles:

Rol Qué es
system Las instrucciones permanentes: quién es, cómo habla, qué puede y qué no
user Lo que escribe la persona
assistant Lo que respondió el modelo (así se arma el historial)

El system prompt es el contrato de trabajo: va antes de todo y se mantiene durante toda la conversación. El modelo le da más peso que a un mensaje suelto del usuario.

🤝 El ejemplo lo tenés adelante: mi soul.md + el CLAUDE.md del proyecto son mi system prompt. Por eso te digo "Die", escribo en rioplatense, no borro archivos sin preguntarte y arranco las sesiones saludándote. Cuando hoy escribimos ahí "primero la persona, después la tarea", editaste mi system prompt. Literalmente.

Dos cosas que NO son ciertas (y son opciones incorrectas clásicas): - Sí se cobra en tokens. Va en el contexto de cada llamada — y en un agente que hace cientos de llamadas, se paga cientos de veces. Por eso conviene corto y filoso. - No es secreto. Se puede filtrar con las preguntas correctas. En el curso lo vas a ver como vector de ataque en el módulo de seguridad. Nunca metas ahí datos que no querés que salgan.


Parte 5 — Usarlo por API (pregunta oficial #3)

Es el mismo modelo que en el chat web. Lo que cambia es que lo llamás desde tu código:

respuesta = cliente.messages.create(
    model="claude-opus-5",
    max_tokens=1000,
    temperature=0,
    system="Sos un asistente de operaciones industriales. Respondé en español rioplatense.",
    messages=[
        {"role": "user", "content": "¿Cuánto stock queda de chapitas de 28mm?"}
    ],
)

Fijate que ahí están todas las piezas de este capítulo: el system, los messages con rol, el límite de max_tokens y la temperature.

Los parámetros que vas a tocar:

Parámetro Qué hace
model cuál modelo (más grande = mejor y más caro)
max_tokens tope de largo de la respuesta
temperature la perilla del azar: 0 = determinista, alto = creativo
tools qué herramientas puede pedir usar (Parte 7)

temperature, la regla práctica: para extraer datos, clasificar o cualquier cosa que tenga que ser confiable → cerca de 0. Para brainstorming de captions → alto. Para un sistema de pedidos: siempre bajo.

⭐ El detalle que cambia cómo se construye todo: la API no tiene memoria

Cada llamada arranca de cero. El modelo solo ve lo que le mandás en ese momento.

Entonces, ¿cómo hace un chat para "recordar"? Te vuelve a mandar toda la conversación en cada mensaje. Por eso una charla larga se pone cara: en el mensaje 50 estás pagando otra vez los 49 anteriores.

Este solo hecho explica tres temas del curso de una sola vez: - por qué un agente necesita un sistema de memoria explícito, - por qué el context engineering es una disciplina y no una manía, - y por qué tu _cerebro en archivos es la respuesta correcta.


Parte 6 — Por qué los modelos dan respuestas distintas (pregunta oficial #5)

Mismo prompt, tres modelos, tres respuestas. Las razones:

  1. Datos de entrenamiento distintos.
  2. Criterios de ajuste distintos: cada empresa entrenó al modelo con una idea propia de qué es "una buena respuesta" (más cauto, más directo, más largo).
  3. System prompt del proveedor: el chat web de cada uno ya trae instrucciones propias que vos no ves.
  4. Forma de elegir el token siguiente (el muestreo) y valores por defecto distintos.

Y algo más profundo: un LLM no es determinista. Elige cada token con algo de azar, así que el mismo modelo con el mismo prompt puede darte dos respuestas distintas.

🎯 Consecuencia directa, y es un módulo entero del curso: no podés testear un agente con if respuesta == "lo esperado". Hace falta otra cosa: evals — evaluaciones que miden si la respuesta es correcta, no si es idéntica.


Parte 7 — Prompting: las 4 técnicas que hay que saber nombrar

Técnica Qué es Cuándo
Zero-shot Pedir sin ejemplos tareas simples y claras
Few-shot Poner 2-3 ejemplos adentro del prompt cuando querés un formato exacto
Chain-of-thought Pedirle que razone paso a paso antes de responder cálculos, decisiones, análisis
Structured output Exigir la respuesta en un formato fijo (JSON) cuando la respuesta la consume otro programa

Few-shot es la más barata y la más efectiva: lo hacés intuitivamente cuando me mostrás un post viejo y me decís "así".

Chain-of-thought: en vez del resultado seco, le pedís que muestre las cuentas. Igual que en la fábrica: si el cálculo de un lote viene sin el desarrollo, no sabés dónde está el error. Con el razonamiento a la vista, los errores se ven — y el modelo mismo se equivoca menos.


Parte 8 — Tool use: el concepto que convierte un chat en un agente

Un LLM solo es un cerebro en un frasco: piensa, habla, y nada más. No puede leer un archivo ni mandar un mail.

Function calling (o tool use) es cómo se le dan manos. Y acá está la sutileza que más se confunde, así que leela despacio:

El modelo NO ejecuta nada. Lo único que hace es devolver un pedido estructurado: "quiero llamar a buscar_pedido con el id A-101". Tu código decide si lo ejecuta, lo ejecuta, y le devuelve el resultado. El modelo sigue desde ahí.

El circuito completo:

① Vos le declarás las herramientas disponibles (nombre, para qué sirve, qué parámetros)
② El modelo, en vez de responder, pide: "usá buscar_pedido con id=A-101"
③ TU CÓDIGO ejecuta la función de verdad
④ Le devolvés el resultado al modelo
⑤ El modelo responde usando ese dato real

Que el paso ③ sea tuyo y no del modelo es lo que hace posible la seguridad: ahí ponés permisos, límites y aprobaciones. Todo el módulo de protección del curso vive en ese detalle.

Y así se declara una herramienta — mirá cómo se juntan los tres temas del capítulo 01:

@tool                                              # ← decorador
def buscar_pedido(id_pedido: str) -> dict:         # ← type hints
    """Busca un pedido por su ID y devuelve sus datos."""   # ← docstring
    ...

El framework lee el decorador para registrarla, los type hints para armar el esquema de parámetros, y el docstring para explicarle al modelo cuándo usarla. Eso es una herramienta de agente. Por eso el precurso pide Python: no para que programes, sino para que puedas leer esto.

(Es literalmente lo que hago yo todo el día: pido leer un archivo, abrir el browser, buscar en Gmail. Y tu MCP de Codytag me da tres de esas manos.)


Parte 9 — RAG, fine-tuning y memoria: tres cosas distintas

Se confunden todo el tiempo y en el cuestionario aparecen como opciones cruzadas:

Qué hace Cuándo se usa
RAG Busca en tus documentos y le pega lo relevante al prompt antes de responder información propia, que cambia
Fine-tuning Reentrena el modelo con tus datos cambiar el estilo o el comportamiento
Memoria Guardar y recuperar lo que pasó en conversaciones anteriores continuidad entre sesiones

RAG son dos pasos: retrieval (buscar los fragmentos que sirven para ESTA pregunta) y generation (responder con esos fragmentos adelante). No entrena nada.

📚 Tu app de especificaciones de El Espartano es un caso RAG de manual: el modelo no sabe nada de tus alfombras, pero si le acercás las specs correctas, responde como si supiera. Y tu _cerebro es el caso de memoria.


Parte 10 — Alucinaciones

El modelo completa texto probable, no texto verdadero. Cuando no sabe algo, muchas veces no lo dice: rellena — con fechas, cifras, citas y links que suenan perfectos y no existen.

⚠️ Lo peligroso no es que se equivoque: es que NO hay señal de aviso. El tono es exactamente el mismo cuando acierta y cuando inventa.

Los antídotos, que son módulos del curso: RAG con fuentes, pedirle que cite de dónde sacó cada dato, evals que midan la tasa de error, y verificación humana de lo crítico.

Y el tuyo, que ya existe: el protocolo de veracidadfuente primaria o no se afirma; la memoria del modelo no es fuente. Hoy mismo, escribiendo este manual, me hizo sacar dos afirmaciones que había puesto de memoria.


Parte 11 — De LLM a agente: el bucle

Esta es la definición que es el corazón del curso:

  • Un LLM responde: entra texto, sale texto, se terminó.
  • Un agente persigue un objetivo: mira el estado → decide → ejecuta una herramienta → verifica el resultado → vuelve a empezar, hasta terminar o hasta escalar a un humano.
        ┌──────────────────────────────────────┐
        │                                      │
   MIRAR el estado → DECIDIR → ACTUAR → VERIFICAR
        │                                      │
        └──────── ¿ya está? ──── no ───────────┘
                      │
                     sí → entregar / escalar a un humano

La palabra clave es BUCLE. Sin bucle no hay agente: hay una llamada a un modelo.

🏭 Y acá está tu ventaja, que no es menor: diseñar un agente se parece muchísimo a diseñar un puesto de trabajo. Qué rol tiene · qué herramientas le doy · qué decide solo y qué me consulta · cómo sé que lo hizo bien · qué pasa cuando algo falla. Eso es operaciones, y lo venís haciendo hace 10 años. La mitad del curso (módulos 3, 9, 10, 11 y 13) es exactamente eso, con otro vocabulario.


Machete de una carilla

Concepto En una línea
LLM Predice el pedacito de texto siguiente. Todo lo demás emerge de ahí
Token ~¾ de palabra. Se cobra por token (entrada y salida por separado)
Ventana de contexto La memoria de trabajo, en tokens. Fija por modelo
Context rot Contexto lleno de basura = peores respuestas
Context engineering Decidir qué entra al contexto y qué no
System prompt Instrucciones permanentes de rol. Sí se cobra, no es secreto
Roles system · user · assistant
La API no tiene memoria Cada llamada va de cero: hay que remandar el historial
temperature Perilla del azar. 0 = confiable, alto = creativo
No determinismo Mismo prompt puede dar distinto → por eso hacen falta evals
Zero / few-shot Sin ejemplos / con ejemplos adentro del prompt
Chain-of-thought "Mostrame las cuentas" antes de la respuesta
Tool use El modelo pide; tu código ejecuta. Ahí vive la seguridad
Herramienta decorador + type hints + docstring
RAG Busca en TUS docs y los pega al prompt. No entrena
Fine-tuning Sí reentrena el modelo. Otra cosa, cara
Alucinación Inventa con total seguridad y sin avisar
Agente LLM + herramientas + contexto + bucle mirar→decidir→actuar→verificar

Ahora contestá las 5

  1. System prompt"Son las instrucciones permanentes de rol y reglas, con el rol system, que van antes de todo y pesan más que un mensaje suelto del usuario. Se cobran en cada llamada y no son secretas."
  2. Ventana de contexto"Es cuánto puede tener presente el modelo de una vez, medido en tokens, y es fija por modelo. Importa porque cuando se llena se pierde lo viejo — y porque llenarla de basura empeora las respuestas."
  3. LLM por API"Sí: se lo llama desde el código con system, messages con rol y parámetros como temperature. Y lo clave: la API no tiene memoria, hay que mandarle el historial en cada llamada."
  4. Token"El pedacito de texto que procesa el modelo, más o menos ¾ de una palabra. Es la unidad de cobro y la unidad en que se mide el contexto."
  5. Por qué difieren los modelos"Distintos datos y criterios de entrenamiento, distinto system prompt del proveedor y distinta forma de elegir el token siguiente. Y además ninguno es determinista: hay azar, así que ni el mismo modelo repite exacto."

Si las cinco salieron: módulo ④ en verde — y con eso, los cuatro imprescindibles cerrados. Lo que sigue no es estudiar más: es probarte en la plataforma y ver cuáles vuelven a fallar.

Capítulo 05 — Docker básico (deseable)

UTDT le pone 2-3 horas y lo marca DESEABLE (opcional): se usa en el módulo de monitoreo en producción del curso (el stack Loki + Tempo + Grafana se levanta con Docker). Si llegás al 22/9 sin esto, el curso no se cae — este capítulo existe para que ese día, en vez de ver magia negra, veas una herramienta conocida. Volver al índice.

La meta de este capítulo

Poder contestar "sí, con confianza" a estas 3 (textuales del precurso oficial):

  1. ¿Sabés qué es un contenedor y en qué se diferencia de una máquina virtual, aunque sea a grandes rasgos?
  2. ¿Corriste alguna vez docker run o docker compose up?
  3. ¿Entendés qué es un docker-compose.yml y para qué sirve tener varios servicios definidos ahí?

Arranquemos por una verdad tuya: nunca necesitaste Docker — y eso NO es un atraso. Tus sistemas corren en Cloudflare Pages, Supabase y Apps Script, que son plataformas serverless: ellas resuelven por debajo el problema que Docker resuelve a mano. Este capítulo te cuenta qué problema es ese, porque en el curso te lo vas a cruzar de frente.


Parte 1 — El problema: "en mi máquina anda"

La frase más famosa del software: el programa funciona perfecto en la compu del que lo hizo, y explota en el servidor. ¿Por qué? Porque el programa no viaja solo: depende de la versión de Python, de las librerías instaladas, de variables de entorno, de carpetas que existen en una máquina y en la otra no.

Vos ya viviste una versión chiquita de esto: el venv de faster-whisper que armamos en D: existe porque las librerías de un proyecto no deben mezclarse con las de otro. Un venv aísla las librerías de Python. Docker lleva la misma idea al extremo: aísla TODO — el sistema operativo, los programas instalados, las librerías, la configuración.

🏭 Tu analogía es la del rubro logístico: el contenedor marítimo. Antes de 1956, cada barco se cargaba a mano: bolsas, cajones, barriles — cada puerto un mundo. El contenedor estandarizó LA CAJA: no importa qué haya adentro, todos los barcos, grúas y camiones del mundo saben moverla. Docker hace eso con software: no importa qué haya adentro (Python, una base de datos, Grafana), la caja se corre igual en cualquier máquina que tenga Docker.


Parte 2 — Contenedor vs máquina virtual (la pregunta oficial #1)

Las dos tecnologías resuelven "quiero un entorno aislado", pero con un costo muy distinto:

Máquina virtual (VM) Contenedor
Qué simula Una computadora entera, con su propio sistema operativo Solo el entorno del programa: comparte el sistema operativo de la máquina real
Peso Gigabytes (trae un Windows/Linux completo adentro) Megabytes (trae solo lo que el programa necesita)
Arranque Minutos (bootea un sistema operativo) Segundos (arranca un proceso)
Cuántas entran Pocas por máquina Decenas por máquina

🏢 Analogía edificio: la VM es construir un edificio nuevo al lado, con cimientos, plomería y medidor de luz propios, para poner una sola oficina. El contenedor es una oficina amueblada dentro del edificio que ya existe: paredes propias, llave propia, muebles propios — pero comparte cimientos, ascensor y luz. Por eso levantar la oficina es rápido y barato, y el edificio aguanta muchas.

La respuesta de examen, en una frase: el contenedor comparte el kernel (núcleo) del sistema operativo de la máquina donde corre y solo empaqueta el programa con sus dependencias; la VM virtualiza una computadora completa con su propio sistema operativo — por eso el contenedor es mucho más liviano y arranca en segundos.


Parte 3 — Imagen vs contenedor (el par de conceptos que ordena todo)

Docker tiene dos palabras que la gente mezcla, y separarlas te acomoda el resto:

  • Imagen = el paquete inmutable: el programa + sus dependencias + su configuración, congelados. Se construye una vez (la "receta" es un archivo llamado Dockerfile) y se publica en un catálogo (Docker Hub) para que cualquiera la baje.
  • Contenedor = una instancia corriendo de esa imagen.

🏭 En tus términos de planta: la imagen es la MATRIZ; el contenedor es la PIEZA producida. De una matriz sacás todas las piezas idénticas que quieras; de una imagen levantás todos los contenedores idénticos que quieras. Y si la pieza sale mal, no arreglás la pieza: corregís la matriz y producís de nuevo — en Docker igual: el contenedor no se "parcha", se reconstruye la imagen y se levanta uno nuevo.


Parte 4 — Los dos comandos (la pregunta oficial #2)

docker run — levantar UN contenedor

docker run -p 3000:3000 grafana/grafana

Eso hace, en orden: ① busca la imagen grafana/grafana (si no la tenés, la baja sola de Docker Hub) → ② crea un contenedor a partir de ella → ③ lo arranca → ④ con -p 3000:3000 conecta el puerto 3000 de tu máquina al del contenedor, así abrís http://localhost:3000 y ves Grafana andando.

Fijate lo que NO hiciste: no instalaste Grafana, no configuraste nada, no tocaste tu sistema. Y cuando lo frenás, tu máquina queda exactamente como estaba. Eso es lo que enamora de Docker: probar cosas sin ensuciar.

docker compose up — levantar un EQUIPO de contenedores

Los sistemas reales casi nunca son un solo programa. El stack de observabilidad que usa el curso son tres servicios que trabajan juntos: Loki (guarda logs), Tempo (guarda trazas) y Grafana (los muestra en tableros). Levantarlos de a uno con tres docker run coordinados sería un dolor. Para eso existe Docker Compose.


Parte 5 — El docker-compose.yml (la pregunta oficial #3)

Es un archivo de texto que declara el equipo completo: qué servicios lo forman, de qué imagen sale cada uno, qué puertos exponen y cómo se conectan entre sí.

services:
  loki:
    image: grafana/loki
  tempo:
    image: grafana/tempo
  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"

Con ese archivo en la carpeta, un solo comando levanta todo:

docker compose up

📋 Tu analogía: es la ORDEN DE PRODUCCIÓN de un sistema. En una sola hoja está declarado qué máquinas participan, en qué orden y cómo se pasan el material. Cualquier operario (cualquier máquina con Docker) que reciba la hoja produce EXACTAMENTE lo mismo. Nadie va de memoria: la hoja ES la configuración.

Para qué sirve tener varios servicios definidos ahí (la parte fina de la pregunta):

  1. Un comando, todo el sistemaup para levantar, down para bajar todo limpio.
  2. Es reproducible: el archivo viaja en el repo, y cualquiera del equipo (o el servidor de producción) levanta el mismo entorno idéntico.
  3. Los servicios se ven entre sí por nombre: dentro del compose, Grafana llega a Loki escribiendo http://loki:3100 — Docker arma la red interna solo.
  4. Es documentación viva: leés el .yml y sabés de qué está hecho el sistema, sin preguntarle a nadie.

Por qué está en el precurso: en el módulo de monitoreo, el profe va a decir "levanten el stack con docker compose up" y en un minuto vas a tener Loki + Tempo + Grafana corriendo para ver los logs y trazas de TU agente. Con este capítulo leído, ese momento es trámite, no misterio.


Parte 6 — Dónde encaja en TU mundo (y dónde no)

Por qué nunca lo necesitaste: Cloudflare Pages y Supabase son serverless — vos subís el código y ellos resuelven dónde y cómo corre (usando contenedores por debajo, que no ves ni te importa). Para tus sistemas de clientes, esa sigue siendo la elección correcta: menos fierros que mantener.

Cuándo lo vas a querer: el día que un sistema tuyo necesite correr algo que las plataformas serverless no ofrecen (una base de datos puntual, un motor de dashboards como Grafana, un modelo local) — ahí Docker es la forma de tenerlo andando en minutos, en tu máquina o en un servidor alquilado, sin instalar nada a mano.


Machete de una carilla

Concepto En una línea
El problema "En mi máquina anda": el programa depende de un entorno que no viaja con él
Contenedor Caja estándar con el programa + TODO su entorno; corre igual en cualquier lado
VM vs contenedor VM = computadora entera con su SO (pesada, lenta) · contenedor = comparte el SO (liviano, arranca en segundos)
Imagen La matriz: paquete congelado del programa + dependencias
Contenedor (vs imagen) La pieza producida: una instancia corriendo de la imagen
Dockerfile La receta con la que se construye una imagen
Docker Hub El catálogo público de imágenes listas para usar
docker run imagen Baja la imagen (si hace falta), crea el contenedor y lo arranca
-p 3000:3000 Conecta un puerto de tu máquina con uno del contenedor
docker-compose.yml La orden de producción: declara varios servicios y cómo se conectan
docker compose up Levanta el equipo completo con un comando (down lo baja)
En el curso Módulo de monitoreo: Loki + Tempo + Grafana se levantan con compose

Ahora contestá las 3

  1. Contenedor vs VM"El contenedor empaqueta el programa con todas sus dependencias y comparte el sistema operativo de la máquina; la VM virtualiza una computadora entera con su propio SO. Por eso el contenedor pesa megas y arranca en segundos, y la VM pesa gigas y tarda minutos."
  2. docker run / docker compose up"Conceptualmente sí: docker run baja una imagen y levanta un contenedor; docker compose up levanta varios servicios juntos leyendo el docker-compose.yml." — y si querés pasar de "conceptualmente" a "sí", son 15 minutos: instalás Docker Desktop y corrés docker run hello-world conmigo.
  3. docker-compose.yml"Es el archivo que declara un sistema de varios servicios (qué imagen usa cada uno, puertos, conexiones); sirve para levantar y bajar todo el equipo con un comando, reproducirlo idéntico en cualquier máquina y que los servicios se encuentren entre sí por nombre."

Si las tres salieron: módulo ⑤ en verde. Seguí con el capítulo 06.


Material oficial que recomienda UTDT para este módulo: Docker Get Started (tutorial oficial, hasta la sección de compose alcanza) · Docker Curriculum (tutorial práctico).

Capítulo 06 — Nociones de arquitectura de software (deseable)

UTDT le pone 2-4 horas y lo marca DESEABLE (opcional): da el vocabulario para las discusiones de diseño de los módulos 2 (diseño de sistemas de agentes), 5 (orquestación) y 8 (multiagente) del curso. Spoiler alentador: de los 7 módulos del precurso, este es en el que MÁS ventaja tenés sin saberlo — diseñar sistemas que no se rompan entre sí es lo que hiciste 10 años en planta. Volver al índice.

La meta de este capítulo

Poder contestar "sí, con confianza" a estas 4 (textuales del precurso oficial):

  1. ¿Sabés qué significa que dos componentes estén "acoplados" y por qué eso suele ser un problema?
  2. ¿Entendés la idea de programar contra una interfaz/abstracción en vez de una implementación concreta?
  3. ¿Tenés alguna noción de qué es un patrón de diseño (aunque sea uno solo, como Factory o Strategy)?
  4. ¿Participaste alguna vez de una decisión de "¿lo hacemos monolítico o separado en servicios?", aunque sea como observador?

Arranquemos por lo que ya está hecho: la pregunta 4 ya la tenés en "sí" — cuando decidimos que OK Nutre viva en UNA Apps Script y que Codytag se reparta en edge functions separadas, participaste de esa decisión exacta. Este capítulo le pone los nombres.


Parte 1 — Acoplamiento: el concepto que ordena todo lo demás

Dos componentes están acoplados cuando uno no puede cambiar sin que el otro se entere (o se rompa). El acoplamiento no es pecado — algo de conexión tiene que haber o no hay sistema — pero el acoplamiento innecesario es deuda: cada cambio se vuelve caro, lento y riesgoso.

🏭 Tu analogía de planta: una línea donde la máquina 2 agarra la pieza DIRECTAMENTE de la boca de la máquina 1 está acoplada: si la 1 para, la 2 para; si la 1 cambia el ritmo, la 2 sufre. Por eso existen los pulmones (buffers) entre puestos: desacoplan. Cada máquina habla con el pulmón, no con la otra máquina — y entonces podés frenar, cambiar o reemplazar una sin tocar la otra.

Y lo viviste en código, con dolor incluido: en OK Nutre, agregar UN producto obliga a tocar cuatro lugares (catálogo del frontend, tabla, CSV y el estado) — tenemos una regla de oro escrita para no olvidarnos. Eso ES acoplamiento: cuatro piezas que deben moverse juntas a mano. Funciona porque el sistema es chico; a escala, esa regla de oro se vuelve una fábrica de bugs.

Por qué el acoplamiento fuerte es un problema (la respuesta de examen): los cambios se propagan en cadena (tocás A y se rompe B), no podés probar ni desplegar las piezas por separado, y el sistema entero queda tan frágil como su pieza más frágil.


Parte 2 — Programar contra interfaces (la vacuna contra el acoplamiento)

Una interfaz (o abstracción) es el contrato de un componente: QUÉ ofrece, sin decir CÓMO lo hace por dentro. Programar "contra la interfaz" significa que tu código depende del contrato, nunca de la implementación concreta que hay detrás.

Ya conocés el caso estrella: una API es exactamente eso (capítulo 02, el mostrador). Tu panel de Codytag habla con el contrato de Supabase; por eso pudimos cambiar cosas por dentro sin que el panel se entere.

🔌 La analogía del enchufe: tus máquinas se enchufan a la PARED (el contrato: 220V, 50Hz, esa ficha), no a "la caldera 3 de Edenor". Edenor puede cambiar generadores, cablear distinto, comprar turbinas nuevas — tu máquina ni se entera. Ahora imaginate lo contrario: cada máquina cableada a un generador específico. Cambiar un generador = recablear la planta. Eso es programar contra la implementación.

En un sistema de agentes (módulos 2 y 8 del curso): cada agente y cada herramienta expone su contrato — nombre, qué recibe, qué devuelve. El orquestador programa contra esos contratos. Así podés cambiar un agente por otro mejor (o un modelo por otro) sin tocar el resto. Mi relación con el MCP de Codytag es literal: yo conozco get_stock y qué me devuelve; cómo lo resuelve la edge function por dentro, ni idea — y así debe ser.


Parte 3 — Patrones de diseño: soluciones con nombre propio

Un patrón de diseño es una solución probada y CON NOMBRE para un problema que se repite. No es código para copiar: es vocabulario compartido — decís "acá va un Adapter" y cualquier persona del equipo entiende la jugada sin ver una línea.

🏭 En manufactura los tenés hace décadas sin llamarlos así: "poka-yoke", "kanban", "célula en U" son patrones — problemas repetidos, soluciones probadas, nombre corto.

Los cuatro que nombra el precurso, cada uno anclado a algo tuyo:

Patrón El problema que resuelve Ya lo viste en...
Adapter Dos sistemas que no se entienden necesitan hablar: una pieza intermedia traduce entre los dos Tu integración HubSpot ↔ EasyBroker: ninguno conoce al otro; tu código del medio traduce
Strategy Una misma operación con varias formas de resolverse: elegís cuál en el momento, sin ifs regados por todo el código OK Nutre calcula el pedido distinto si es mayorista o minorista — misma operación, dos estrategias
Observer Componentes que quieren enterarse cuando pasa algo, sin que el emisor los conozca El trigger de la Sheet de Carla y todo webhook (cap 02): "avisame cuando entre un pedido"
Factory Crear el objeto correcto según el caso, en un solo lugar, en vez de repetir la lógica de creación por todos lados El alta de Codytag: según el punto de venta, la chapita nace con su estado y datos correctos desde un único flujo

Para la pregunta oficial alcanza con UNO bien contado. Recomendación: contá el Adapter con HubSpot↔EasyBroker — es tuyo, es real y es de manual.

Por qué está en el precurso: en el módulo 8 (multiagente) estas ideas reaparecen con agentes en los roles: un agente-traductor entre dos sistemas es un Adapter; un orquestador que elige a qué agente derivar es un Strategy; agentes que reaccionan a eventos son Observers. Mismos patrones, piezas nuevas.


Parte 4 — ¿Monolítico o separado en servicios? (la decisión eterna)

  • Monolito: TODO el sistema en una sola pieza desplegable. Una app, un deploy.
  • Servicios: el sistema repartido en piezas independientes que se hablan por API (contratos, Parte 2). Cada una se despliega, escala y falla por separado.

Vos tenés uno de cada uno en producción:

OK Nutre Codytag
Forma Monolito chico: una Apps Script atiende pedidos, mails y planilla Servicios: frontend + edge functions separadas (venta, salud, MCP...) + base
Deploy Un solo deploy, todo junto Cada edge function se deploya sola
Si algo falla Falla todo junto (y se encuentra rápido) Falla ESA pieza; el resto sigue
Costo de coordinar Bajísimo: no hay piezas que sincronizar Real: versiones, contratos, permisos entre piezas

La respuesta madura (la que espera el curso): ninguno "es mejor". El monolito gana en simpleza — arrancar, entender, deployar, debuggear — y para un sistema chico o un equipo chico suele ser LA respuesta correcta. Los servicios ganan cuando distintas partes necesitan escalar, cambiar o fallar por separado — al precio de mucha más coordinación. El error clásico es empezar por servicios "porque es lo profesional" y pagar la coordinación sin necesitarla. Empezá monolito; separá cuando duela.

🧩 Modularidad, la palabra que falta: que el monolito sea UNA pieza desplegable no significa que sea un plato de spaghetti por dentro. Un buen monolito es modular: componentes con responsabilidades claras y contratos internos limpios (como _cerebro/: un archivo por trabajo — memoria, reglas, herramientas). La modularidad es sobre el ORDEN interno; monolito/servicios es sobre el DESPLIEGUE. Un monolito modular se puede partir en servicios el día que haga falta; uno spaghetti, no.

En el curso (módulos 2, 5 y 8): la MISMA decisión aparece con agentes: ¿un solo agente generalista con muchas herramientas (monolito) o varios especializados coordinados por un orquestador (servicios)? Los criterios son idénticos — y vos ya tenés el criterio, porque es la decisión "¿una máquina multipropósito o una línea de puestos especializados?" que evaluaste toda tu carrera.


Machete de una carilla

Concepto En una línea
Acoplamiento Uno no puede cambiar sin que el otro se rompa; fuerte = cambios en cadena, frágil
Desacoplar Poner un contrato/pulmón en el medio: cada pieza habla con el contrato
Interfaz / abstracción El contrato: QUÉ ofrece un componente, sin el CÓMO
Programar contra interfaz Depender del contrato (el enchufe), no de la implementación (el generador)
Patrón de diseño Solución probada y con nombre a un problema repetido; vocabulario compartido
Adapter Traductor entre dos sistemas que no se entienden (HubSpot↔EasyBroker)
Strategy Varias formas de la misma operación; se elige en el momento (mayorista/minorista)
Observer "Avisame cuando pase X" — triggers y webhooks
Factory Un único lugar que fabrica el objeto correcto según el caso
Monolito Todo en una pieza: simple de arrancar y entender; falla y escala junto
Servicios Piezas independientes habladas por API: flexibles; coordinación cara
El criterio Empezá monolito; separá cuando duela — no "porque es lo profesional"
Modularidad Orden interno con responsabilidades claras; otra cosa que el despliegue

Ahora contestá las 4

  1. Acoplamiento"Dos componentes están acoplados cuando uno no puede cambiar sin romper o arrastrar al otro. Es un problema porque los cambios se propagan en cadena y no podés probar ni desplegar por separado — como una línea sin pulmones entre máquinas."
  2. Programar contra interfaces"Sí: que mi código dependa del contrato (qué ofrece el componente) y no de la implementación concreta. Como enchufar a la pared y no cablear al generador: podés cambiar lo de atrás sin tocar lo de adelante."
  3. Patrón de diseño"Es una solución con nombre a un problema que se repite. Usé un Adapter de verdad: mi integración HubSpot–EasyBroker es una pieza intermedia que traduce entre dos sistemas que no se conocen."
  4. ¿Monolítico o servicios?"Participé y decidí: mi plataforma de pedidos es un monolito chico (una Apps Script) porque la simpleza ganaba; mi sistema QR corre en servicios (edge functions separadas) porque cada pieza cambia y falla por separado. Empezar simple y separar cuando duele."

Si las cuatro salieron: módulo ⑥ en verde. Seguí con el capítulo 07.


Material oficial que recomienda UTDT para este módulo: Refactoring.Guru (patrones explicados con dibujos — entrá a Adapter y Strategy y listo) · Martin Fowler (arquitectura a nivel práctico).

Capítulo 07 — Fundamentos de ML supervisado y fine-tuning (deseable)

UTDT le pone 3-6 horas y lo marca DESEABLE (opcional): existe para que el módulo 7 del curso ("Aprendizaje en sistemas agénticos": SFT, DPO, RLVR) no te agarre con un salto conceptual grande. Acá NO hay que programar nada ni saber matemática: son 4 ideas conceptuales. Volver al índice.

La meta de este capítulo

Poder contestar "sí, con confianza" a estas 4 (textuales del precurso oficial):

  1. ¿Entendés la diferencia entre entrenar un modelo y usarlo en inferencia?
  2. ¿Sabés qué es un dataset etiquetado y por qué hace falta uno para "supervised fine-tuning"?
  3. ¿Tenés una intuición de qué significa que un modelo "aprenda una preferencia" a partir de pares de respuestas?
  4. ¿Escuchaste hablar de fine-tuning eficiente (LoRA / adaptadores) y por qué existe?

Arranquemos por lo que ya está hecho: la pregunta 3 la practicás sin saberlo. Cada vez que te muestro 2-3 borradores y elegís uno, estás generando exactamente el dato con el que se entrena una preferencia. Este capítulo te muestra esa mecánica desde adentro.


Parte 1 — Entrenar vs usar en inferencia (la pregunta oficial #1)

Un modelo tiene dos vidas completamente separadas:

  • Entrenamiento: se le muestran millones de ejemplos y un algoritmo va ajustando sus parámetros (miles de millones de "perillas" internas) para que sus respuestas se acerquen a las esperadas. Es carísimo, tarda semanas y pasa en un datacenter.
  • Inferencia: el modelo ya está congelado y solo se USA: entra un prompt, salen tokens. Las perillas no se mueven ni un milímetro. Es lo que pasa cada vez que hablás conmigo.

🏭 Tu analogía de planta: entrenar es la puesta a punto de la máquina — corridas de prueba, medir contra la muestra aprobada, ajustar perillas, repetir hasta que la pieza sale bien. Inferencia es producción: la máquina calibrada, produciendo. Y en producción NADIE toca las perillas.

⚠️ El malentendido que este concepto mata: "el modelo aprende de nuestras conversaciones". No. En inferencia el modelo no aprende NADA — lo que parece memoria es contexto que se le vuelve a pasar en cada llamada (capítulo 04). Tu _cerebro existe precisamente porque yo no aprendo entre sesiones: mi "aprendizaje" son archivos que releo. Cuando en el curso digan "aprendizaje en sistemas agénticos", la novedad es justamente esa: técnicas para que el sistema SÍ mejore con la experiencia.


Parte 2 — Dataset etiquetado y SFT (la pregunta oficial #2)

ML supervisado = enseñar con ejemplos resueltos. Un dataset etiquetado es la colección de esos ejemplos: cada entrada viene con su respuesta correcta (la "etiqueta") puesta por alguien que sabe.

📋 Es una planilla de dos columnas: entrada → salida correcta. En control de calidad lo hiciste años: fotos/muestras de piezas con su veredicto "aprobada" o "rechazada". Con suficientes muestras etiquetadas, un inspector nuevo (o un modelo) aprende el criterio.

Supervised Fine-Tuning (SFT) es aplicar eso a un modelo YA entrenado: se agarra el modelo general y se lo re-ajusta (fino, de ahí "fine") con un dataset chico y específico de pares prompt → respuesta ideal, para que aprenda TU tarea, TU tono, TU formato.

Por qué hace falta el dataset etiquetado (la parte fina de la pregunta): porque en lo supervisado la etiqueta ES el maestro. El algoritmo ajusta el modelo midiendo la distancia entre lo que respondió y la respuesta correcta que le mostraste; sin etiquetas no hay contra qué medir. Y vale la regla de tu planta: basura que entra, basura que sale — etiquetas malas entrenan un modelo malo. Armar el dataset limpio es el 80% del laburo real.

Un SFT tuyo, hipotético y muy posible: 500 mensajes reales de clientes de Codytag, cada uno con la respuesta que VOS considerás perfecta → un modelo chico afinado que responde WhatsApp con tu tono exacto. Eso es un dataset etiquetado y un SFT completo.


Parte 3 — Aprender una preferencia (la pregunta oficial #3)

Para tareas abiertas (escribir un mail, explicar un análisis) no existe UNA respuesta correcta que etiquetar — pero sí es fácil decir cuál de dos es mejor. De ahí sale otra forma de dataset: el par de preferencia.

prompt: "Escribí el aviso de demora del pedido"
respuesta A  ←  elegida ✅
respuesta B  ←  descartada

Con miles de pares así, un algoritmo ajusta el modelo para que suba la probabilidad de las respuestas tipo A y baje la de las tipo B. El modelo no memoriza esos textos: absorbe el CRITERIO que hay detrás de tus elecciones. Eso es "aprender una preferencia".

  • La forma clásica se llama RLHF (aprendizaje por refuerzo con feedback humano) y es el proceso que convirtió a los LLM crudos en asistentes que conversan bien.
  • La forma moderna y simple es DPO (Direct Preference Optimization): optimizar directo sobre los pares elegido/descartado, sin el aparato de refuerzo en el medio. Es la sigla del módulo 7 del curso.
  • Vas a escuchar también RLVR: refuerzo con recompensas verificables — en vez de gustos humanos, se premia lo que se puede CHEQUEAR (¿el código pasa los tests? ¿la cuenta da?). Ideal para agentes, donde el resultado es verificable.

El ancla tuya es literal: cuando elegís entre mis 2-3 borradores estás generando pares de preferencia — por eso los botones 👍/👎 y los "¿versión A o B?" de todas las plataformas: son fábricas de datasets de preferencias. Tus elecciones de hoy entrenan los modelos de mañana.


Parte 4 — LoRA y adaptadores: fine-tuning eficiente (la pregunta oficial #4)

El problema: un LLM tiene miles de millones de parámetros. Reentrenarlos TODOS para tu caso pide un datacenter, cuesta una fortuna y te deja una copia gigante del modelo por cada cliente o tarea. Para una PyME (o para vos), inviable.

La solución — LoRA (Low-Rank Adaptation): el modelo grande queda congelado, y se entrena solamente una pieza chiquita al costado — el adaptador — que ajusta el comportamiento. El adaptador suele ser menos del 1% del tamaño del modelo: se entrena en horas en una GPU normal, y el resultado pesa megas, no gigas.

🏭 Tu analogía exacta: la máquina y la matricería. Nadie fabrica una máquina nueva por cada producto — la máquina (carísima, universal) queda fija, y cada producto tiene su matriz/molde (barata, chiquita, intercambiable). LoRA es eso: el modelo es la máquina; el adaptador es la matriz de TU producto. ¿Cliente nuevo? Matriz nueva, misma máquina. Hasta podés tener varios adaptadores y cambiarlos según la tarea.

Por qué existe (la respuesta de examen): porque ajusta el modelo a una tarea específica a una fracción del costo, tiempo y hardware del reentrenamiento completo, sin duplicar el modelo, y con adaptadores intercambiables por tarea o cliente.


Parte 5 — El mapa de decisión (para que nada se te mezcle)

Tres herramientas que la gente confunde, y cuándo va cada una:

Necesito que el modelo... Herramienta Costo
Siga instrucciones, tono, rol Prompt / system prompt (cap 04) Gratis, al instante
Conozca MIS datos, que cambian seguido RAG (cap 04): buscar y pegar al contexto Bajo
Cambie su COMPORTAMIENTO de fondo (formato, estilo, tarea repetitiva) Fine-tuning (SFT/DPO, con LoRA) Medio: dataset + entrenamiento

La regla que te va a servir en el curso y con clientes: se sube por esa escalera EN ORDEN. El 90% de los problemas se resuelven en los dos primeros escalones — el fine-tuning es el último recurso, no el primero. ⚠️ Y el clásico de examen: para conocimiento fresco NO se hace fine-tuning (caro, lento, y el modelo igual alucina); para eso está RAG. El fine-tuning enseña cómo comportarse, no qué pasó ayer.


Machete de una carilla

Concepto En una línea
Entrenamiento Ajustar las perillas del modelo con ejemplos (puesta a punto)
Inferencia Usar el modelo congelado: entra prompt, salen tokens (producción)
"Aprende de la charla" NO — en inferencia nada cambia; lo que parece memoria es contexto
ML supervisado Enseñar con ejemplos resueltos (entrada + respuesta correcta)
Dataset etiquetado Planilla entrada → etiqueta puesta por quien sabe; sin ella no hay contra qué medir
SFT Re-ajustar un modelo ya entrenado con TUS pares prompt → respuesta ideal
Par de preferencia Mismo prompt, respuesta elegida vs descartada — tu "elijo la A"
RLHF / DPO Aprender el criterio detrás de esas elecciones (DPO: directo de los pares)
RLVR Refuerzo premiando lo verificable (tests que pasan, cuentas que dan)
LoRA / adaptador Modelo congelado + pieza chica entrenable (<1%): la matriz de la máquina
Por qué LoRA existe Fracción del costo/hardware, sin duplicar el modelo, adaptadores intercambiables
Prompt → RAG → fine-tuning La escalera: subí solo cuando el escalón anterior no alcanza

Ahora contestá las 4

  1. Entrenar vs inferencia"Entrenar es ajustar los parámetros del modelo con ejemplos — caro, lento, en datacenter. Inferencia es usarlo congelado: entra un prompt, salen tokens y nada del modelo cambia. La puesta a punto vs producción."
  2. Dataset etiquetado y SFT"Es una colección de ejemplos con su respuesta correcta puesta por alguien que sabe. Hace falta porque lo supervisado aprende midiendo su salida contra esa etiqueta: sin etiquetas no hay contra qué ajustar."
  3. Aprender una preferencia"Sí: le mostrás pares del mismo prompt con una respuesta elegida y una descartada, y el ajuste sube la probabilidad de las del tipo elegido. Absorbe el criterio, no los textos. Yo genero esos pares cada vez que elijo entre borradores."
  4. LoRA / adaptadores"Sí: en vez de reentrenar el modelo entero, se congela y se entrena un adaptador chiquito al costado. Existe porque da el 90% del resultado a una fracción del costo y el hardware — como cambiar la matriz en vez de fabricar otra máquina."

Si las cuatro salieron: precurso COMPLETO, los 7 módulos. 🏁


Material oficial que recomienda UTDT para este módulo: NLP Course de Hugging Face (las primeras secciones alcanzan) · Docs de PEFT (LoRA/adaptadores) · TRL DPO Trainer (por si querés espiar cómo se ve un DPO real).

← 06 · Arquitectura · opcional🏁 Fin del manual — ¡a evaluarse!