Saltar al contenido
Lección 3 de 12

Prompting para Generación de Código

8 min read

El Objetivo: Código de Producción al Primer Intento

La diferencia entre un desarrollador que usa IA efectivamente y uno que no, frecuentemente se reduce a los prompts de generación de código. Un prompt efectivo produce código que compila, maneja casos extremos, sigue las convenciones de tu proyecto y está listo para revisión. Un prompt inefectivo produce código que técnicamente funciona pero requiere 20 minutos de limpieza manual antes de ser aceptable para merge.

Las técnicas en esta lección te llevarán a código de producción al primer intento para la mayoría de tareas. No siempre -- la lógica compleja aún requiere iteración. Pero para el 80% de las tareas de generación de código que siguen patrones conocidos, puedes y debes esperar salida correcta en el primer intento.

Especificar el Stack Tecnológico Explícitamente

Nunca asumas que la IA conoce tu stack. Incluso si puede leer tu package.json, declarar explícitamente el stack reduce el espacio de soluciones y previene que la IA genere código con librerías o patrones incompatibles.

Malo:

Crea un endpoint de registro de usuarios

Bueno:

Crea un endpoint de registro de usuarios en nuestra API Express.js usando
TypeScript. Stack: Express 4, TypeScript 5, Prisma ORM con PostgreSQL, Zod
para validacion de entrada, bcrypt para hash de passwords. El endpoint debe
estar en src/routes/auth.ts.

La declaración del stack elimina la ambigüedad. La IA no usará Mongoose (MongoDB), Sequelize, ni SQL directo. No generará JavaScript en lugar de TypeScript. Usará Zod en lugar de Joi o Yup para validación.

Incluir Contexto de Archivos

Una de las técnicas de generación de código más poderosas es referenciar archivos existentes. Esto le da a la IA un ejemplo concreto que igualar.

En src/components/Button.tsx, crea un nuevo componente React llamado
IconButton. Debe seguir exactamente el mismo patron que el componente Button
existente en el mismo archivo: mismo estilo de interfaz de props, mismo
enfoque de clases Tailwind, mismo patron de forwardRef. La diferencia es que
IconButton toma una prop adicional "icon" (un React.ReactNode) y la renderiza
antes de los children.

La técnica de "patrón existente" funciona porque la IA puede leer ese archivo e igualar directamente la estructura. Compara esto con describir el patrón en palabras -- decir "usa forwardRef con generics apropiados de TypeScript y clases utilitarias de Tailwind con variantes CVA" es más verboso y menos preciso que decir "sigue el patrón en Button.tsx."

El Patrón de Archivo de Referencia

Cuando generas código que debe integrarse con un codebase existente, proporciona una referencia:

Crea un nuevo handler de ruta API para GET /api/orders/:id. Usa
src/routes/products.ts como referencia para:
- Patron de manejo de errores (try/catch con AppError)
- Formato de respuesta (res.json con campos status y data)
- Uso de middleware de autenticacion
- Patrones de consulta Prisma

La orden debe incluir sus items de linea y la informacion del cliente.
Incluye manejo apropiado de 404 cuando la orden no se encuentra.

Definir Comportamiento: Entradas, Salidas, Casos Extremos

Especifica qué entra, qué sale, y qué pasa cuando las cosas van mal.

Vago:

Escribe una funcion para procesar pagos

Específico:

Escribe una funcion processPayment en src/services/payment.ts:

Entrada: { amount: number, currency: string, customerId: string, paymentMethodId: string }
Salida: { success: boolean, transactionId: string | null, error: string | null }

Comportamiento:
- Validar que amount sea positivo y currency sea uno de ['USD', 'EUR', 'GBP']
- Llamar a la API de Stripe usando nuestro cliente stripe existente de src/lib/stripe.ts
- En exito, devolver el transaction ID
- En error card_declined de Stripe, devolver { success: false, error: 'Tarjeta rechazada' }
- En error rate_limit de Stripe, reintentar una vez despues de 1 segundo
- En cualquier otro error, registrar el error con nuestro logger y devolver un mensaje de error generico
- Nunca exponer detalles de error de Stripe al llamador

Este prompt no deja nada a la interpretación. La IA conoce la firma de la función, el tipo de retorno, el camino feliz y cada escenario de error. La salida será precisa.

Anti-Patrones de Generación de Código

Ciertas frases en prompts consistentemente producen resultados pobres porque son subjetivas o no medibles:

"Escribe código limpio" -- ¿Limpio según quién? ¿Uncle Bob? ¿Tu tech lead? ¿La comunidad de Go? En su lugar, especifica qué quieres decir: "Usa retornos tempranos en lugar de ifs anidados," "Mantén las funciones bajo 20 líneas," "Usa nombres de variables descriptivos como customerEmail en lugar de ce."

"Sigue las mejores prácticas" -- ¿Las mejores prácticas de quién? El término significa cosas diferentes en diferentes comunidades. En su lugar: "Sigue el patrón de manejo de errores de nuestro codebase existente," o "Usa consultas parametrizadas para prevenir inyección SQL."

"Optimiza esto" -- ¿Optimizar para qué? ¿Velocidad? ¿Memoria? ¿Legibilidad? ¿Tamaño del bundle? En su lugar: "Reduce el número de consultas a la base de datos de N+1 a una sola consulta con joins," o "Memoiza este cómputo para que solo se ejecute cuando la entrada cambie."

"Hazlo production-ready" -- Esta frase significa todo y nada. En su lugar, lista las cualidades específicas: "Agrega manejo de errores, validación de entrada, logging y tipos TypeScript."

Ejemplos Reales

Generando un Endpoint de API

Crea un endpoint REST en src/routes/users.ts para PATCH /api/users/:id.

Proposito: Permitir a los usuarios actualizar su perfil (name, bio, avatarUrl).

Requisitos:
- Usar el authMiddleware existente para verificar el token JWT
- Los usuarios solo pueden actualizar su propio perfil (comparar req.user.id
  con params.id)
- Validar entrada con Zod: name (string, 1-100 chars, opcional), bio (string,
  max 500 chars, opcional), avatarUrl (URL valida, opcional)
- Usar Prisma para actualizar solo los campos proporcionados
- Devolver el objeto usuario actualizado (excluir passwordHash de la respuesta)
- Devolver 403 si el usuario intenta actualizar el perfil de otro usuario
- Devolver 404 si el ID de usuario no existe

Seguir el mismo patron que el handler GET /api/users/:id en el mismo archivo.

Generando un Componente React

Crea un componente React en src/components/UserProfile.tsx.

Props:
- user: { id: string, name: string, email: string, avatarUrl?: string, bio?: string }
- isEditable: boolean
- onSave: (updates: Partial<User>) => Promise<void>

Comportamiento:
- Modo visualizacion: Muestra info del usuario en layout de tarjeta usando
  nuestro componente Card existente
- Modo edicion (cuando isEditable y el usuario hace clic en "Editar"):
  Formulario inline con inputs controlados para name, bio y avatarUrl
- El boton de guardar llama a onSave solo con los campos que cambiaron
- Mostrar estado de carga durante el guardado (deshabilitar formulario,
  mostrar spinner)
- Mostrar toast de error en fallo de guardado usando nuestro hook existente
  useToast
- Revertir a modo visualizacion en guardado exitoso

Estilos: Usar Tailwind CSS. Igualar el estilo de nuestro componente existente
ProductCard en src/components/ProductCard.tsx.

Generando una Función Utilitaria

Crea una funcion utilitaria en src/utils/retry.ts:

export async function withRetry<T>(
  fn: () => Promise<T>,
  options?: { maxAttempts?: number, delayMs?: number, backoff?: boolean }
): Promise<T>

Opciones por defecto: maxAttempts = 3, delayMs = 1000, backoff = true

Comportamiento:
- Llamar a fn(). Si tiene exito, devolver el resultado.
- Si lanza una excepcion, esperar delayMs y reintentar.
- Si backoff es true, duplicar el delay en cada reintento (1s, 2s, 4s).
- Despues de maxAttempts fallos, lanzar el ultimo error.
- Registrar cada intento de reintento con console.warn incluyendo numero de
  intento y mensaje de error.

Incluir documentacion JSDoc y exportar el tipo de opciones.

El Flujo de Trabajo Generar-y-Refinar

Para generación de código compleja, usa un flujo de trabajo de dos pasos:

Paso 1 -- Generar el primer borrador:

Crea la clase OrderService en src/services/OrderService.ts con metodos:
createOrder, getOrderById, getOrdersByCustomer, cancelOrder. Usa Prisma para
acceso a la base de datos. Incluye manejo de errores basico.

Paso 2 -- Refinar con feedback específico:

Buena estructura. Ahora refina el OrderService:
1. Agregar validacion de entrada usando Zod a createOrder
2. El metodo cancelOrder debe verificar si el estado de la orden permite
   cancelacion (solo ordenes 'pending' y 'confirmed' pueden cancelarse)
3. Agregar paginacion a getOrdersByCustomer (basada en cursor, 20 por pagina)
4. Envolver todas las llamadas a Prisma en try/catch que convierta
   PrismaClientKnownRequestError a nuestra clase personalizada AppError

Este flujo de trabajo es más rápido que intentar especificar todo de antemano para tareas complejas. El primer paso te da el esqueleto, el segundo paso agrega los detalles. Cada paso es lo suficientemente simple para que la IA lo ejecute con precisión.

Sobre-Especificación vs Sub-Especificación

Hay un equilibrio entre dar a la IA demasiado detalle y muy poco. La sub-especificación produce código genérico. La sobre-especificación desperdicia tu tiempo escribiendo el prompt y puede confundir a la IA si tus instrucciones se contradicen.

La regla: especifica las cosas que hacen tu código único. Los patrones estándar (un endpoint CRUD básico, un componente React simple) necesitan menos especificación porque la IA ha visto miles de ejemplos. Los requisitos inusuales (lógica de negocio personalizada, manejo de errores no estándar, integración con APIs propietarias) necesitan más especificación porque la IA no puede adivinarlos.

Cuando tengas dudas, comienza con un prompt moderadamente detallado. Si la salida falta algo, agrega ese detalle específico en un seguimiento en lugar de reescribir el prompt completo.