Prompt Engineering para Desarrolladores
El prompt engineering es la habilidad de comunicarse efectivamente con LLMs para obtener resultados precisos y consistentes. Para desarrolladores, esto significa tratar los prompts como código: versionados, parametrizados y testeados.
System Prompts
El system prompt define el comportamiento base del modelo:
const messages = [
{
role: 'system',
content: `Eres un asistente de programación especializado en TypeScript y React.
REGLAS:
- Responde SOLO con código TypeScript válido
- Usa siempre tipos explícitos (no 'any')
- Incluye comentarios explicativos
- Si no sabes algo, dilo claramente
- Formatea el código con 2 espacios de indentación`
},
{ role: 'user', content: 'Crea un hook personalizado para fetch de datos' }
];
Anatomía de un buen system prompt
1. ROL: Quién es el asistente
2. CONTEXTO: Información relevante
3. REGLAS: Restricciones y comportamiento
4. FORMATO: Cómo debe responder
5. EJEMPLOS: (opcional) Few-shot
Templates de prompts
Nunca hardcodees prompts completos. Usa templates con variables:
function buildPrompt(vars: {
language: string;
topic: string;
level: 'beginner' | 'intermediate' | 'advanced';
}): string {
return `Explica ${vars.topic} en ${vars.language}.
Nivel: ${vars.level}.
${vars.level === 'beginner'
? 'Usa analogías simples y evita jerga técnica.'
: 'Incluye detalles técnicos y edge cases.'}`;
}
// Uso
const prompt = buildPrompt({
language: 'TypeScript',
topic: 'generics',
level: 'intermediate',
});
Template con Zod para validación
import { z } from 'zod';
const PromptVars = z.object({
codeSnippet: z.string().min(1),
language: z.enum(['typescript', 'python', 'rust']),
task: z.enum(['review', 'refactor', 'explain', 'test']),
});
function buildCodePrompt(input: z.infer<typeof PromptVars>): string {
const vars = PromptVars.parse(input); // Valida en runtime
const taskInstructions = {
review: 'Revisa este código. Identifica bugs, mejoras y problemas de seguridad.',
refactor: 'Refactoriza este código aplicando clean code y SOLID principles.',
explain: 'Explica este código línea por línea para un desarrollador junior.',
test: 'Genera tests unitarios completos para este código.',
};
return `${taskInstructions[vars.task]}
Lenguaje: ${vars.language}
\`\`\`${vars.language}
${vars.codeSnippet}
\`\`\``;
}
Output estructurado (JSON Mode)
Para aplicaciones, necesitas respuestas parseables, no texto libre:
OpenAI JSON Mode
const response = await openai.chat.completions.create({
model: 'gpt-4o',
response_format: { type: 'json_object' },
messages: [
{
role: 'system',
content: 'Responde SIEMPRE en JSON válido con el schema indicado.'
},
{
role: 'user',
content: `Analiza este código y retorna JSON con este schema:
{
"bugs": [{ "line": number, "description": string, "severity": "low"|"medium"|"high" }],
"suggestions": [string],
"score": number (1-10)
}
Código: function add(a, b) { return a - b; }`
}
],
});
const analysis = JSON.parse(response.choices[0].message.content!);
// { bugs: [{ line: 1, description: "Substrae en vez de sumar", severity: "high" }], ... }
OpenAI Structured Outputs (más robusto)
import { zodResponseFormat } from 'openai/helpers/zod';
const CodeAnalysis = z.object({
bugs: z.array(z.object({
line: z.number(),
description: z.string(),
severity: z.enum(['low', 'medium', 'high']),
})),
suggestions: z.array(z.string()),
score: z.number().min(1).max(10),
});
const response = await openai.beta.chat.completions.parse({
model: 'gpt-4o',
response_format: zodResponseFormat(CodeAnalysis, 'code_analysis'),
messages: [...],
});
const analysis = response.choices[0].message.parsed;
// TypeScript sabe que analysis es tipo CodeAnalysis
Few-Shot Prompting
Proporciona ejemplos para guiar el formato y estilo de respuesta:
const systemPrompt = `Eres un clasificador de tickets de soporte.
Clasifica cada ticket en una categoría y prioridad.
EJEMPLOS:
Ticket: "No puedo iniciar sesión desde hace 2 días"
Respuesta: { "category": "auth", "priority": "high", "reason": "Usuario bloqueado" }
Ticket: "¿Cómo cambio mi foto de perfil?"
Respuesta: { "category": "account", "priority": "low", "reason": "Pregunta informativa" }
Ticket: "La app se cierra cuando subo un archivo de más de 10MB"
Respuesta: { "category": "bug", "priority": "medium", "reason": "Crash por tamaño de archivo" }`;
Chain-of-Thought (CoT)
Fuerza al modelo a razonar paso a paso antes de responder:
const mathPrompt = `Resuelve este problema paso a paso.
Muestra tu razonamiento antes de la respuesta final.
Problema: Una tienda tiene 3 estantes. Cada estante tiene 4 cajas.
Cada caja tiene entre 5 y 8 productos. Si la media de productos por caja es 6.5,
¿cuántos productos hay en total?
Piensa paso a paso:`;
// Con CoT, el modelo razona:
// 1. Total de cajas: 3 × 4 = 12
// 2. Media de productos por caja: 6.5
// 3. Total: 12 × 6.5 = 78 productos
CoT para código
const debugPrompt = `Analiza este bug paso a paso:
1. Lee el código completo
2. Identifica qué debería hacer
3. Identifica qué hace realmente
4. Explica la causa raíz
5. Proporciona la solución
\`\`\`typescript
function getUniqueItems(arr: number[]): number[] {
return arr.filter((item, index) => arr.indexOf(item) === index);
// Bug reportado: funciona pero O(n²) performance
}
\`\`\``;
Técnicas avanzadas
Prompt chaining
Divide tareas complejas en pasos secuenciales:
async function analyzeAndFix(code: string): Promise<{
analysis: string;
fixedCode: string;
tests: string;
}> {
// Paso 1: Analizar
const analysis = await llm.chat([
{ role: 'system', content: 'Analiza bugs y problemas en este código.' },
{ role: 'user', content: code },
]);
// Paso 2: Corregir (usando el análisis)
const fix = await llm.chat([
{ role: 'system', content: 'Corrige el código basándote en este análisis.' },
{ role: 'user', content: `Análisis:\n${analysis}\n\nCódigo:\n${code}` },
]);
// Paso 3: Generar tests
const tests = await llm.chat([
{ role: 'system', content: 'Genera tests unitarios para este código.' },
{ role: 'user', content: fix },
]);
return { analysis, fixedCode: fix, tests };
}
Role prompting
const expertPrompt = `Eres un ingeniero senior de seguridad en una empresa fintech.
Tienes 15 años de experiencia en OWASP, pentesting y compliance PCI-DSS.
Revisa este endpoint como si fuera una auditoría de seguridad formal.`;
Negative prompting
const strictPrompt = `Genera un componente React.
NO hagas esto:
- No uses 'any' como tipo
- No uses useEffect para data fetching (usa React Query)
- No uses estado global cuando props son suficientes
- No uses index como key en listas dinámicas
- No hagas mutación directa de estado`;
Versionamiento de prompts
Los prompts son código. Versionalos:
// prompts/code-review.ts
export const CODE_REVIEW_PROMPT = {
version: '2.1.0',
system: `Eres un revisor de código senior...`,
changelog: [
'2.1.0: Agregado check de accesibilidad',
'2.0.0: Migrado a structured output',
'1.0.0: Versión inicial',
],
} as const;
Anti-patterns
| Anti-pattern | Problema | Solución |
|---|---|---|
| Prompts vagos | Respuestas inconsistentes | Sé específico y estructurado |
| Sin system prompt | Comportamiento impredecible | Siempre define rol y reglas |
| Prompt gigante | Caro y lento | Divide en pasos (chaining) |
| Sin validación | Output inesperado rompe la app | Usa JSON mode + Zod |
| Hardcoded | Imposible de iterar | Templates con variables |