MÓDULO 2 · PROMPT ENGINEERING · 45 MIN

Prompting para código: generación, refactoring, testing y debugging con LLMs

45 min Intermedio Claude ChatGPT Copilot cualquier LLM
Cada tipo de tarea de código pide información distinta. Generación necesita requisitos completos, refactoring necesita restricciones de qué NO cambiar, testing necesita cobertura explícita, debugging necesita contexto del error, y documentación necesita audiencia definida.
El problema

Los desarrolladores usan LLMs para código pero obtienen resultados inconsistentes.

Situación típica 1 — Código incompleto: pedís "crea una función para validar emails" y recibís una función básica sin manejo de errores, sin type hints, con regex obsoleta, sin considerar casos edge como emails con "+".

Situación típica 2 — Refactoring destructivo: pedís "mejora este código" y el LLM cambia la API pública, elimina funcionalidad que "parecía innecesaria", e introduce dependencias nuevas no solicitadas.

Situación típica 3 — Tests superficiales: pedís "genera tests para esta función" y recibís 2-3 tests del happy path, cero tests de casos edge, cero tests de errores.

Situación típica 4 — Debug por adivinanza: pegás 200 líneas de código con "esto no funciona, arreglalo" y el LLM hace cambios aleatorios sin entender el problema real.

Por qué es importante: sin prompts específicos para código pasás más tiempo corrigiendo que si lo hubieras escrito vos, introducís bugs sutiles que el LLM no menciona, el código generado no sigue los estándares de tu proyecto, y no aprendés del proceso porque no entendés los cambios.

La solución: prompts especializados por tipo de tarea
GENERACIÓN → Contexto + Requisitos + Restricciones + Formato REFACTORING → Código + Objetivo específico + Lo que NO cambiar TESTING → Código + Casos a cubrir + Framework + Estilo DEBUGGING → Error + Código + Contexto + Hipótesis DOCUMENTACIÓN → Código + Audiencia + Formato + Ejemplos
Cómo funciona
1. Generación de código

Elementos de un prompt de generación efectivo: contexto técnico (lenguaje/versión, framework, dependencias permitidas, estándares del proyecto), requisito funcional (qué debe hacer, inputs, outputs), requisitos no funcionales (manejo de errores, seguridad, rendimiento), y formato de respuesta.

plantillaGenera una función/clase en [LENGUAJE] [VERSIÓN]. # Contexto - Framework: [framework o "ninguno"] - Dependencias permitidas: [lista o "solo stdlib"] - Estilo de código: [PEP 8, Airbnb, etc.] # Requisito [Descripción clara de qué debe hacer] # Inputs - [parámetro1]: [tipo] - [descripción] # Output - Retorna: [tipo] - [descripción] # Manejo de errores - Si [condición de error]: [qué hacer] - Validar: [qué validaciones hacer en inputs] # Restricciones - [Restricción 1] # Formato de respuesta Solo el código con type hints y docstring breve. Sin explicaciones adicionales.

// Ejemplo real: función que valide y normalice números de teléfono españoles a formato E.164, con manejo de errores explícito (ValueError si no tiene 9 dígitos, si tiene caracteres no numéricos), restricción de no usar librerías como `phonenumbers`, y formato de respuesta con 3 casos de test inline.

2. Refactoring

El refactoring requiere ser MUY específico sobre qué mejorar y qué NO tocar.

ObjetivoCómo pedirlo
Legibilidad"Mejora la legibilidad: nombres descriptivos, extrae funciones auxiliares, elimina código duplicado"
Rendimiento"Optimiza rendimiento: identifica el cuello de botella y proponé mejora. Explica el trade-off."
Patrones"Aplica el patrón [Strategy/Factory/etc.] para [objetivo específico]"
Modernización"Actualiza a sintaxis moderna de [lenguaje versión]: usa [feature específica]"
Simplificación"Reduce complejidad ciclomática: divide funciones >20 líneas, elimina anidación >3 niveles"
plantillaRefactoriza el siguiente código. # Objetivo específico [Exactamente qué mejorar y por qué] # NO modificar - [ ] API pública (nombres de funciones/parámetros) - [ ] Comportamiento funcional - [ ] [Otras restricciones] # SÍ modificar - [ ] [Aspecto 1 a mejorar] # Formato de respuesta - Código refactorizado completo - Lista de cambios en bullets - Si hay trade-offs, mencionalos # Código actual ```[lenguaje] [código a refactorizar] ```

// Ejemplo real: refactorizar una función `process_order` con anidación profunda de if/else y variables de una letra (o, t, i, d), manteniendo el nombre público, los tipos de parámetro/retorno y el comportamiento exacto — solo extrayendo lógica repetida, renombrando variables, y reduciendo anidación a 2 niveles.

3. Testing
TipoQué especificar
Unit testsFunción específica, casos edge, mocks necesarios
Integration testsComponentes a integrar, estado inicial/final, cleanup
Property-basedPropiedades invariantes, rangos de inputs
Regression testsBug específico que debe quedar cubierto
plantillaGenera tests para el siguiente código. # Framework y estilo - Framework: [pytest/unittest/jest/etc.] - Estilo: [AAA (Arrange-Act-Assert) / BDD (Given-When-Then)] # Cobertura requerida - [ ] Happy path: [caso normal] - [ ] Edge cases: [lista de casos límite] - [ ] Errores: [casos que deben fallar y cómo] - [ ] Tipos inválidos: [qué pasa con inputs incorrectos] # Mocks necesarios - [Componente externo a mockear]: [comportamiento simulado] # Formato - Un test por caso (no combinar múltiples assertions) - Nombres descriptivos: test_[función]_[escenario]_[resultado_esperado] # Código a testear ```[lenguaje] [código] ```

// Ejemplo real: tests pytest para `validate_email`, con edge cases explícitos (subdominio, "+", números, dominios largos como .museum), errores explícitos (sin @, sin dominio, espacios, múltiples @), y tipos inválidos (None, número, lista como input) — usando pytest.mark.parametrize.

4. Debugging

El debugging efectivo requiere contexto estructurado: el error (mensaje exacto, cuándo ocurre, stack trace), el código (solo lo relevante, no 500 líneas), el contexto (qué debería hacer vs qué hace en realidad, qué cambió), y tus hipótesis (qué creés que puede estar mal, qué ya probaste).

plantillaAyúdame a debuggear este error. # Error [Mensaje de error exacto / comportamiento inesperado] # Comportamiento esperado [Qué debería pasar] # Comportamiento actual [Qué pasa en realidad] # Contexto - Lenguaje/Framework: [versión] - Cuándo falla: [siempre / a veces / bajo condición X] - Cambios recientes: [qué modificaste antes de que fallara] # Lo que ya probé - [Intento 1]: [resultado] # Código relevante ```[lenguaje] [código donde ocurre el error] ``` # Datos de entrada que causan el error (si los conocés) [ejemplo de input que reproduce el bug] --- Analiza paso a paso: 1. ¿Cuál es la causa probable del error? 2. ¿Cómo verificamos esa hipótesis? 3. ¿Cuál es la solución? 4. ¿Cómo prevenimos errores similares en el futuro?

// Ejemplo real: TypeError al hacer unpacking de `get_user_data(user_id)` cuando el usuario no existe — la función tiene un `return` implícito de `None` cuando el `if user:` es falso (falta el `else` con un manejo explícito). El contexto incluye qué ya se probó (verificar que el user_id es un UUID válido, print confirmando que retorna None).

5. Documentación
TipoEspecificar
DocstringsEstilo (Google, NumPy, Sphinx), incluir ejemplos o no
READMESecciones requeridas, audiencia, nivel de detalle
API docsFormato (OpenAPI, markdown), ejemplos de request/response
GuíasAudiencia (junior/senior), paso a paso vs conceptual
Comentarios inlineCuándo añadir (solo lógica compleja vs todo)
plantillaGenera docstrings para el siguiente código. # Estilo [Google / NumPy / Sphinx / reStructuredText] # Incluir - [ ] Descripción breve (1 línea) - [ ] Descripción extendida (si la función es compleja) - [ ] Args/Parameters con tipos y descripción - [ ] Returns con tipo y descripción - [ ] Raises con excepciones posibles - [ ] Examples con casos de uso # Audiencia [Desarrolladores del equipo / Usuarios de API pública / Juniors] # Código ```[lenguaje] [código sin documentar] ```

La plantilla de generación pide "3 casos de test inline con assert al final" como parte del formato de respuesta. Eso es un smoke test rápido para confirmar que el código anda mientras lo generás — no reemplaza la suite de tests real de la técnica 3 (con edge cases, errores y tipos inválidos explícitos). Si tratás esos asserts inline como "ya está testeado", te vas a llevar sorpresas: son una primera señal, no cobertura.

Cuándo aplicarlo
TareaTécnica clave
Función nueva desde ceroGeneración: contexto + requisitos + restricciones
Mejorar código existente sin cambiar comportamientoRefactoring: objetivo específico + lista de NO cambiar
Código funciona pero sin testsTesting: cobertura explícita + casos edge + errores
Error que no entiendoDebugging: error + contexto + hipótesis + qué ya probaste
Código funciona pero sin docsDocumentación: estilo + audiencia + qué incluir
Código legacy que debo entenderCombinación: primero pedí explicación, luego docs
Ejercicio práctico

Objetivo: aplicar las 5 técnicas en un mini-proyecto coherente — vas a construir la misma función a lo largo de todo el ejercicio.

Contexto inicial: usá Claude.ai, ChatGPT, o cualquier LLM.

Paso 1 — Generación: creá la función base

Pedí una función format_bytes(bytes_size: int) -> str en Python 3.11+, sin dependencias externas, que convierta cantidades de bytes a formato legible humano (ej: 1024 → "1.0 KB", 1048576 → "1.0 MB"), con manejo de errores (ValueError si es negativo, TypeError si no es int), usando divisiones de 1024 y máximo 1 decimal. Guardá el código generado — lo vas a reutilizar en los pasos siguientes.

Paso 2 — Testing: tests completos

Con el código del paso 1, pedí tests pytest cubriendo: happy path (bytes pequeños, KB, MB, GB, TB), edge cases (0 bytes, exactamente 1024, números muy grandes, límites entre unidades como 1023 vs 1024), y errores (negativo, string, None, float como input). Verificá que los tests cubren todos los casos que pediste.

Paso 3 — Refactoring: mejorá el código

Pedí refactorizar la función para que sea más extensible (fácil agregar nuevas unidades) y legible, sin modificar el nombre, los parámetros, el tipo de retorno, ni el comportamiento — extrayendo las unidades a una constante y usando patrones más pythónicos.

Paso 4 — Debugging: encontrá un bug

Copiá esta versión con un bug intencional (a la lista de unidades le falta 'PB'):

python — con bugdef format_bytes(bytes_size: int) -> str: if not isinstance(bytes_size, int): raise TypeError("bytes_size must be an integer") if bytes_size < 0: raise ValueError("bytes_size must be non-negative") units = ['B', 'KB', 'MB', 'GB', 'TB'] # Falta 'PB' size = float(bytes_size) unit_index = 0 while size >= 1024 and unit_index < len(units) - 1: size /= 1024 unit_index += 1 return f"{size:.1f} {units[unit_index]}"

Armá el prompt de debugging con el síntoma real: para el input 1125899906842624 (1 PB), la función retorna "1024.0 TB" en lugar de "1.0 PB". Preguntá: ¿por qué ocurre?, ¿cuál es la solución?, ¿qué test debería agregarse para evitar regresiones?

Paso 5 — Documentación: documentá completamente

Con el código corregido (unidades incluyendo 'PB'), pedí docstring estilo Google (descripción, args, returns, raises, 3 examples variados), comentarios inline solo donde la lógica no sea obvia, y un bloque final explicando cuándo usar la función, limitaciones conocidas, y alternativas — audiencia: desarrolladores junior recién incorporados al equipo.

Paso 6 — Validación final
PasoVerificación
Generación¿El código funciona con los ejemplos dados?
Testing¿Hay al menos 10 tests cubriendo happy path, edge y errores?
Refactoring¿El código es más limpio sin cambiar el comportamiento?
Debugging¿Identificó correctamente que falta 'PB' en la lista?
Documentación¿Un junior podría usar la función solo leyendo el docstring?
Troubleshooting rápido
Si ves esto...CausaSolución
Código generado sin manejo de erroresNo especificaste excepcionesAñadí sección "# Manejo de errores" con casos específicos
Refactoring cambia la API públicaNo restringiste qué NO cambiarLista explícita: "NO modificar: nombre, parámetros, retorno"
Tests solo del happy pathNo listaste casos edgeIncluí lista exhaustiva: "Edge cases: [caso 1], [caso 2]..."
Debug no encuentra el problemaPoco contexto del errorIncluí: mensaje exacto, inputs que fallan, qué ya probaste
Documentación demasiado genéricaNo especificaste audienciaDefiní: "Audiencia: [tipo], asumir que [saben/no saben] [concepto]"
Código mezcla español e inglésNo definiste idiomaEspecificá: "Variables y comentarios en [inglés/español]"
Puntos clave

Concepto central: cada tipo de tarea de código requiere información diferente. Generación necesita requisitos completos, refactoring necesita restricciones de qué NO cambiar, testing necesita cobertura explícita, debugging necesita contexto del error, y documentación necesita audiencia definida.

Para recordar: