Inicio / Inteligencia Artificial / AI-First Full Stack: Construye Apps con IA / Prompt Engineering para Desarrolladores

Prompt Engineering para Desarrolladores

System prompts, templates, variables, output estructurado (JSON mode), few-shot y chain-of-thought.

Principiante Funciones

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

Ejercicio de práctica

Motor de templates de prompts

Implementa un sistema de templates de prompts reutilizables.

function renderPrompt(template: string, vars: Record<string, string>): string
  // Reemplaza {{variable}} con su valor. Si no existe, deja cadena vacía.

function buildFewShot(examples: {input: string, output: string}[], query: string): string
  // Genera prompt few-shot: cada ejemplo como "Input: ...\nOutput: ..." separados por \n\n, y al final "Input: {query}\nOutput:"

function validateJsonOutput(text: string, requiredKeys: string[]): { valid: boolean; data?: any; error?: string }
  // Intenta JSON.parse. Si falla → {valid:false, error:'Invalid JSON'}
  // Si faltan keys requeridas → {valid:false, error:'Missing keys: x, y'}
  // Si todo ok → {valid:true, data: parsed}