En el vertiginoso mundo del desarrollo de software, la inteligencia artificial se ha convertido en una herramienta omnipresente. Sin embargo, la forma en que interactuamos con modelos de lenguaje avanzados como Claude y ChatGPT marca la diferencia entre obtener una solución genérica y una integración útil y precisa. ¿Alguna vez le has pedido a ChatGPT que 'arregle esto' y te ha devuelto una solución que, aunque plausible, no aborda la raíz del problema? Nos ha pasado a todos.
Este es el abismo que separa el uso de un LLM como un mero juguete de su aplicación como una herramienta de ingeniería robusta. El problema rara vez reside en el modelo en sí, sino en la interfaz entre nuestra intención y la ejecución del modelo. El 'prompting' básico, que consiste en hacer una única pregunta y aceptar la primera respuesta, tiene sus límites. Estos se hacen evidentes cuando necesitamos que el modelo razone sobre código que no escribimos, restricciones que no hemos declarado o fallos que aún no podemos prever.
El prompting avanzado: más allá de las 'frases mágicas'
El prompting avanzado no es una búsqueda de frases mágicas. Se trata de diseñar un contrato fiable entre tú y el modelo, proporcionando:
- Suficiente contexto.
- Restricciones explícitas.
- Salidas verificables.
- Bucles iterativos que detectan fallos a tiempo.
El objetivo es lograr una ganancia de información significativa que trascienda los resúmenes superficiales y se adentre en detalles exhaustivos y accionables. Esto es crucial para los desarrolladores que desean tratar a los LLM menos como un oráculo y más como un ingeniero júnior extremadamente rápido, ocasionalmente demasiado confiado, sin memoria ni contexto compartido.
De un 'prompt' vago a una especificación precisa
Muchos desarrolladores comienzan con peticiones simples como: Escribe una función que maneje la autenticación.
Este tipo de 'prompt' obliga al modelo a adivinar una multitud de detalles: ¿qué lenguaje y framework? ¿Qué mecanismo de autenticación (JWT, OAuth, cookies de sesión, claves API)? ¿Con qué código existente debe integrarse? ¿Qué manejo de errores se espera? ¿Cuáles son los requisitos de seguridad? ¿Cómo es el esquema de la base de datos existente?
El modelo producirá algo, probablemente una función genérica que, casi con certeza, no se ajustará a tu base de código.
Una versión más elaborada y efectiva sería:
Estamos trabajando en una API de TypeScript Express.
Contexto:
- Mecanismo de autenticación: tokens de acceso JWT de corta duración (15 min) + tokens de actualización almacenados en Postgres.
- El modelo de usuario existente en
src/models/user.tstiene campos:id (uuid),password_hash,role,created_at,updated_at.- Usamos
zodpara la validación yjsonwebtokenpara la firma.- El middleware reside actualmente en
src/middleware/.Tarea:
Crea un middleware de autenticación en
src/middleware/authenticate.tsque:
- Extraiga el token Bearer del encabezado Authorization.
- Verifique el JWT usando la clave pública de
env.JWT_PUBLIC_KEY.- Adjunte la carga útil de usuario decodificada (id, email, role) a
req.user.- Devuelva 401 con una forma de error consistente si el token falta, ha caducado o es inválido.
- No consulte la base de datos. La validez del token se basa únicamente en la firma y la caducidad.
Restricciones:
- Sigue la forma de respuesta de error existente utilizada en
src/utils/errors.ts (sendError).- Tipifica
req.usercorrectamente. No usesany.- Registra los fallos en nivel WARN usando nuestro logger de
src/utils/logger.ts.- No añadas nuevas dependencias.
Casos límite:
- Encabezado Authorization mal formado.
- Token firmado con un algoritmo incorrecto.
- Token válido pero caducado.
- Token para un usuario que ya no existe.
Criterios de aceptación:
- El middleware es una única función exportada como
authenticate.- TypeScript compila con el modo estricto habilitado.
- No hay importaciones de base de datos.
- Devuelve errores usando
sendErrorde forma consistente.Proporciona el código y una breve nota sobre cualquier suposición que hayas tenido que hacer.
Este 'prompt' no se basa en palabras mágicas. Funciona porque proporciona los componentes que el modelo necesita para producir una salida útil: contexto, instrucciones, restricciones, salida esperada, casos límite y criterios de aceptación. Cada componente reduce la ambigüedad, que es lo que lleva al modelo a inventar sus propias suposiciones. Cuando el modelo inventa suposiciones, terminas dedicando más tiempo a corregir la salida que el que habrías empleado en escribir el código tú mismo.
El marco de prompting para desarrolladores
Después de usar Claude y ChatGPT extensamente para tareas de software, hemos establecido un marco que produce resultados fiables. No es una fórmula rígida, sino una lista de verificación de componentes que, combinados adecuadamente, transforman una petición vaga en una especificación clara:
- Rol: Establece la perspectiva desde la que razona el modelo. Si falta, el modelo produce una salida genérica sin un marco de dominio.
- Objetivo: Define el resultado específico. Si falta, el modelo produce resultados parciales o tangenciales.
- Contexto: Suministra código, esquemas y patrones existentes relevantes. Si falta, el modelo inventa suposiciones plausibles pero incorrectas.
- Restricciones: Limita el espacio de la solución. Si falta, el modelo sobrediseña o introduce patrones incompatibles.
- Salida esperada: Define el formato del entregable. Si falta, el modelo devuelve prosa cuando quieres código, o viceversa.
- Casos límite: Fuerza la consideración de rutas de fallo. Si falta, el modelo solo maneja el 'camino feliz'.
- Criterios de aceptación: Define cómo se verificará la salida. Si falta, el modelo produce código que no se puede evaluar objetivamente.
El orden de estos componentes es menos importante que su presencia. El contexto es el elemento más crucial para el trabajo de desarrollo. El modelo no puede ver tu repositorio; solo puede razonar sobre lo que le proporcionas. Simular este contexto compartido de manera efectiva es similar a cómo integrarías a un nuevo desarrollador en tu equipo.
Aprovechando Claude para el desarrollo de software
Las fortalezas prácticas de Claude se manifiestan al trabajar con grandes volúmenes de código y al razonar sobre su estructura. El modelo está diseñado para manejar contextos largos y una lectura cuidadosa del texto proporcionado. Por ejemplo, al darle a Claude un conjunto de archivos y pedirle que razone sobre cómo interactúan, a menudo supera la generación de una función desde cero. Esto se debe a que razonar sobre código existente es una tarea de comprensión lectora, mientras que la generación es una tarea de síntesis.
Un flujo de trabajo útil implica pegar los archivos relevantes y luego pedirle a Claude que produzca un mapa de las relaciones de dependencia antes de solicitar cambios. Así, puedes pedirle:
Adjunto los siguientes archivos de un servicio Express heredado...
Tarea:
Lee estos archivos y produce un mapa de dependencias conciso. Para cada archivo, enumera:
- Lo que importa de otros archivos de este conjunto.
- Lo que exporta.
- Cualquier dependencia circular que puedas detectar.
- Cualquier efecto secundario que ocurra en el momento de la importación.
Luego, identifica los tres problemas estructurales de mayor riesgo que observes. No propongas soluciones todavía.
Claude también es excepcionalmente bueno para escribir documentación a partir de código. Pero, al igual que con cualquier otra tarea, el 'prompt' debe ser específico y detallado para obtener los mejores resultados.