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
- Leé el capítulo entero una vez, sin hacer nada. Está escrito para entenderse leyendo, con analogías de cosas que ya hacés.
- Después volvé y hacé los ejercicios. Son cortos y usan ejemplos de Codytag, OK Nutre y el cerebro — no ejercicios de curso abstracto.
- 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.
- 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)
- Python: asyncio · Real Python async · decoradores · type hints · freeCodeCamp Python
- APIs: MDN HTTP · What is an API · json.org · requests
- CLI + Git: Missing Semester (MIT) · Learn Git Branching · Pro Git
- LLMs: Anthropic prompt engineering · DeepLearning.AI · Gemini API
- Docker: Get Started oficial · Docker Curriculum
- Arquitectura: Refactoring.Guru · Martin Fowler
- ML/fine-tuning: NLP Course de Hugging Face · PEFT · TRL DPO Trainer
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):
- ¿Sabés qué hace
async defy por qué una funciónasyncno se puede llamar como una función normal? - ¿Entendés la diferencia entre
await algo()y simplementealgo()? - ¿Sabés escribir un decorador propio con
@mi_decorador? - ¿Sabés qué significa
def foo(x: int) -> str:y por qué se usa aunque Python no lo obligue? - ¿Manejaste alguna vez
try/exceptcon 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.pyen 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→ cajaitems→ la caja número0→ el campoproducto.
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?
- 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.
- El editor te avisa antes de correr. VS Code / Cursor te subraya el error mientras escribís.
- Hay herramientas que los verifican (
mypy) — chequeo de calidad automático. - ⭐ 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
requestsy 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á elraise_for_status(): sin esa línea, eseexcept HTTPErrornunca se dispara. ② Si no le pasástimeout=,requestsno 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, **kwargssignifica "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 deexcept, 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.
asyncno 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ó unawait.
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)
async defy 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."await algo()vsalgo()→ "sin await me devuelve la orden de trabajo; con await se ejecuta, libera la espera y me devuelve el resultado."- Decorador propio → "una función que recibe una función y devuelve una versión
envuelta;
@nombrees el atajo. Ejemplo: reintento automático." 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."try/exceptespecí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):
- ¿Sabés qué es un endpoint y qué diferencia hay entre un
GETy unPOST? - ¿Entendés qué es una API key y por qué no se debe subir a un repo público?
- ¿Podés leer un objeto JSON anidado (con listas y diccionarios adentro) sin confundirte?
- ¿Usaste alguna vez una librería tipo
requests(Python) ofetch(JS) para consumir una API externa? - ¿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
GETes 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 chapitasPOST /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:
- Intención:
GETpide,POSTcrea. UnGETes seguro: podés repetirlo mil veces y no pasa nada. UnPOSTrepetido crea el pedido dos veces (por eso los botones "Enviar" se deshabilitan después del primer click — es literalmente ese problema). - Dónde viajan los datos: el
GETlos manda en la URL (?codigo=Cody0074), elPOSTen el body. Por eso elGETqueda escrito en el historial del navegador y en los logs del servidor, y elPOSTno. - ⚠️ 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:
- 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.
- ⚠️ 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}
- 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. - Comillas dobles, nunca simples.
{'codigo': 'x'}es un diccionario de Python, no JSON. - 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:
-
requests.get()te devuelve el sobre entero, no la carta. Adentro estárespuesta.status_code,respuesta.headersy el contenido. Los datos salen conrespuesta.json(). -
⚠️
requestsNO lanza excepción ante un 404 o un 500. Para el programa "salió bien": le llegó una respuesta. Sin la línearaise_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á enverificar-material.py.) -
⚠️ 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. -
⚠️ Si no pasás
timeout=,requestsno 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
POSTa 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 /chapitas ≠ POST /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
- 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."
- 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
.envignorado por git." - 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." requests/fetch→ "Sí.requests.get()devuelve un objeto respuesta con status, headers y cuerpo; los datos se sacan con.json(), hay que llamar araise_for_status()para que un 404 explote, y siempre pasartimeout."- 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):
- ¿Podés navegar entre carpetas, crear archivos y borrar archivos desde la terminal sin usar el explorador gráfico?
- ¿Sabés instalar un paquete con
pip instally verificar que se instaló? - ¿Sabés qué es una variable de entorno y cómo se define en un archivo
.env? - ¿Entendés la diferencia entre
git add,git commitygit push? - ¿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+TABy la terminal completaCodytagsola. 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.
rmborra 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.txtNO 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.txtinstala 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:
- 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".
- 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 addes elegir quién entra en la foto (los acomodás, todavía no sacaste nada).git commites sacar la foto y ponerle epígrafe.git pushes 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
commit ≠ push, 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
<<<<<<< HEADy=======→ 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)
- 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.
- Borrás las tres líneas de marcas (
<<<<<<<,=======,>>>>>>>). git add memory.md→ le avisás a Git que ya está resuelto.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 --abortdeshace 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
- Navegar y manejar archivos → "Sí:
pwdpara saber dónde estoy,lspara ver,cdpara moverme,mkdir/touchpara crear yrmpara borrar — sabiendo que borra sin papelera." pip instally verificar → "Instalo conpip install requestsy verifico conpip show requestsopip list; la prueba final es importarlo desde Python.requirements.txtNO sirve para verificar: es lo que el proyecto necesita."- Variables de entorno y
.env→ "Es configuración que vive fuera del código; en el.envse escribeNOMBRE=valor, una por línea, sin espacios ni comillas, y el archivo va siempre en el.gitignore." add/commit/push→ "addelige qué entra en la próxima foto,commitla saca y la guarda en mi historial local,pushla manda al servidor remoto. Commit y push son cosas distintas: se puede commitear sin internet."- 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 addygit 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):
- ¿Sabés qué es un "system prompt" y en qué se diferencia de lo que escribe el usuario?
- ¿Entendés qué es una ventana de contexto y por qué importa su tamaño?
- ¿Usaste alguna vez un LLM a través de una API (no solo por chat web)?
- ¿Sabés qué es un "token" en el contexto de LLMs, aunque sea de forma aproximada?
- ¿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:
- 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.
- 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
_cerebromejor 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.mdes corto a propósito (< 200 líneas) y ellog.mdno 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+ elCLAUDE.mddel 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:
- Datos de entrenamiento distintos.
- 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).
- System prompt del proveedor: el chat web de cada uno ya trae instrucciones propias que vos no ves.
- 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_pedidocon el idA-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
_cerebroes 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 veracidad — fuente 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
- 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." - 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."
- LLM por API → "Sí: se lo llama desde el código con
system,messagescon rol y parámetros comotemperature. Y lo clave: la API no tiene memoria, hay que mandarle el historial en cada llamada." - 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."
- 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):
- ¿Sabés qué es un contenedor y en qué se diferencia de una máquina virtual, aunque sea a grandes rasgos?
- ¿Corriste alguna vez
docker runodocker compose up? - ¿Entendés qué es un
docker-compose.ymly 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):
- Un comando, todo el sistema —
uppara levantar,downpara bajar todo limpio. - Es reproducible: el archivo viaja en el repo, y cualquiera del equipo (o el servidor de producción) levanta el mismo entorno idéntico.
- 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. - Es documentación viva: leés el
.ymly 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
- 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."
docker run/docker compose up→ "Conceptualmente sí:docker runbaja una imagen y levanta un contenedor;docker compose uplevanta varios servicios juntos leyendo eldocker-compose.yml." — y si querés pasar de "conceptualmente" a "sí", son 15 minutos: instalás Docker Desktop y corrésdocker run hello-worldconmigo.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):
- ¿Sabés qué significa que dos componentes estén "acoplados" y por qué eso suele ser un problema?
- ¿Entendés la idea de programar contra una interfaz/abstracción en vez de una implementación concreta?
- ¿Tenés alguna noción de qué es un patrón de diseño (aunque sea uno solo, como Factory o Strategy)?
- ¿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
- 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."
- 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."
- 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."
- ¿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):
- ¿Entendés la diferencia entre entrenar un modelo y usarlo en inferencia?
- ¿Sabés qué es un dataset etiquetado y por qué hace falta uno para "supervised fine-tuning"?
- ¿Tenés una intuición de qué significa que un modelo "aprenda una preferencia" a partir de pares de respuestas?
- ¿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
- 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."
- 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."
- 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."
- 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).