Ingeniería de prompts para la productividad del desarrollador
Técnicas prácticas de ingeniería de prompts que convierten los LLM en asistentes de código fiables: contexto, cadena de pensamiento y salidas estructuradas.

Los modelos de lenguaje de gran tamaño (LLM) son útiles para tareas de programación, pero la calidad de sus respuestas varía enormemente según cómo se les pregunte. Los prompts vagos producen respuestas vagas. Los prompts precisos, con contexto, restricciones y ejemplos, producen código que realmente se puede usar. La diferencia no está en el modelo, sino en el prompt.
Esto no tiene que ver con trucos ni con jailbreaks. Se trata de estructurar tus solicitudes para que el modelo tenga suficiente información para dar respuestas útiles al primer intento.
La anatomía de un buen prompt
Todo prompt de programación eficaz tiene cuatro componentes: contexto, tarea, restricciones y formato.
# ❌ Vague prompt — produces generic, possibly wrong code
"Write a function to validate emails"
# ✅ Structured prompt — produces usable, specific code
Context: TypeScript project using Zod for validation.
The function is part of a user registration form handler.
Task: Write a function that validates an email address.
Constraints:
- Must use Zod schema validation
- Must check for disposable email domains (mailinator, tempmail, guerrillamail)
- Must return a typed result object, not throw errors
- Must handle null/undefined input
Format: TypeScript function with JSDoc, followed by unit test examples.La versión estructurada le indica al modelo exactamente qué stack usar, qué casos límite manejar y cómo debe verse la salida. La versión vaga obliga al modelo a adivinar cada decisión.
Gestión de la ventana de contexto
Los modelos tienen un contexto limitado. Volcar toda tu base de código en el prompt desperdicia tokens en código irrelevante. En su lugar, proporciona contexto específico: los tipos, interfaces y patrones concretos con los que el código generado debe integrarse.
// Include only the interfaces the generated code needs to implement
// Instead of pasting 500 lines of code, paste 30 lines of types
/*
Given these existing types and the database schema:
interface User {
id: string;
email: string;
role: 'admin' | 'user' | 'moderator';
createdAt: Date;
}
interface PaginatedResponse<T> {
data: T[];
total: number;
page: number;
pageSize: number;
hasMore: boolean;
}
Database: PostgreSQL with Prisma ORM
Table: users (id UUID PK, email VARCHAR UNIQUE, role VARCHAR, created_at TIMESTAMP)
Write a function that queries users with pagination, filtering by role,
and returns a PaginatedResponse<User>.
*/// The model can now produce code that matches your exact types
async function getUsers(
params: { role?: User['role']; page: number; pageSize: number }
): Promise<PaginatedResponse<User>> {
const { role, page, pageSize } = params;
const skip = (page - 1) * pageSize;
const where = role ? { role } : {};
const [data, total] = await Promise.all([
prisma.user.findMany({ where, skip, take: pageSize, orderBy: { createdAt: 'desc' } }),
prisma.user.count({ where }),
]);
return {
data,
total,
page,
pageSize,
hasMore: skip + data.length < total,
};
}Cadena de pensamiento para lógica compleja
Para algoritmos de varios pasos o decisiones de arquitectura, pídele al modelo que razone sobre el problema antes de escribir código.
# ❌ Direct request — model jumps to code, may miss edge cases
"Implement a rate limiter using the sliding window algorithm"
# ✅ Chain-of-thought prompt
Task: Implement a sliding window rate limiter.
Before writing code, explain:
1. How the sliding window algorithm differs from fixed window
2. What data structure you'll use to track request timestamps
3. How you'll handle the window boundary calculation
4. What happens when the limit is exceeded
Then implement in TypeScript with:
- Redis for storage (using ioredis)
- Configurable window size and request limit
- Return type that includes remaining requests and reset time// The model's reasoning leads to more robust implementations
// because it explicitly considers edge cases before coding
interface RateLimitResult {
allowed: boolean;
remaining: number;
resetAt: Date;
retryAfter?: number; // seconds until next allowed request
}
async function checkRateLimit(
redis: Redis,
key: string,
limit: number,
windowMs: number
): Promise<RateLimitResult> {
const now = Date.now();
const windowStart = now - windowMs;
// Remove entries outside the current window
// Count entries within the window
// Add current request if under limit
const pipeline = redis.pipeline();
pipeline.zremrangebyscore(key, 0, windowStart);
pipeline.zcard(key);
pipeline.zadd(key, now.toString(), `${now}-${Math.random()}`);
pipeline.pexpire(key, windowMs);
const results = await pipeline.exec();
const currentCount = results?.[1]?.[1] as number;
if (currentCount >= limit) {
// Over limit — remove the entry we just added
await redis.zremrangebyscore(key, now, now);
const oldestInWindow = await redis.zrange(key, 0, 0, 'WITHSCORES');
const resetAt = oldestInWindow.length > 1
? parseInt(oldestInWindow[1]) + windowMs
: now + windowMs;
return {
allowed: false,
remaining: 0,
resetAt: new Date(resetAt),
retryAfter: Math.ceil((resetAt - now) / 1000),
};
}
return {
allowed: true,
remaining: limit - currentCount - 1,
resetAt: new Date(now + windowMs),
};
}Ejemplos few-shot
Cuando el modelo necesita seguir un patrón específico, proporciona ejemplos de la relación entre entrada y salida. Dos o tres ejemplos establecen el patrón de forma más fiable que descripciones extensas.
Task: Generate TypeScript API route handlers following this project's pattern.
Example 1:
Input: GET /api/users — list all users with pagination
Output:// app/api/users/route.ts
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1');
const limit = parseInt(searchParams.get('limit') || '20');
const users = await db.user.findMany({
skip: (page - 1) * limit,
take: limit,
});
return NextResponse.json({ data: users, page, limit });
}Example 2:
Input: POST /api/users — create a new user
Output:// app/api/users/route.ts
export async function POST(request: NextRequest) {
const body = await request.json();
const parsed = createUserSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json(
{ error: parsed.error.flatten() },
{ status: 400 }
);
}
const user = await db.user.create({ data: parsed.data });
return NextResponse.json({ data: user }, { status: 201 });
}Now generate:
Input: DELETE /api/users/[id] — delete a user by IDEl modelo ahora entiende las convenciones de tu proyecto: los route handlers de Next.js, la validación con Zod, el ORM Prisma, un formato de respuesta consistente. Sigue el patrón sin que tengas que especificar cada decisión.
Solicitudes de salida estructurada
Cuando necesites que el modelo genere datos en un formato específico, y no solo código, sé explícito respecto a la estructura.
# ❌ Ambiguous output format
"What are the security issues in this code?"
# ✅ Structured output format
Analyze this code for security vulnerabilities.
Output format (JSON):
{
"vulnerabilities": [
{
"severity": "critical" | "high" | "medium" | "low",
"line": number,
"issue": "brief description",
"fix": "code snippet showing the fix",
"cwe": "CWE-XXX reference"
}
],
"summary": "one paragraph overall assessment"
}// Model output follows the exact schema
{
"vulnerabilities": [
{
"severity": "critical",
"line": 15,
"issue": "SQL query built with string concatenation allows injection",
"fix": "db.query('SELECT * FROM users WHERE id = $1', [userId])",
"cwe": "CWE-89"
},
{
"severity": "high",
"line": 28,
"issue": "User input rendered without sanitization enables XSS",
"fix": "import DOMPurify from 'dompurify'; element.innerHTML = DOMPurify.sanitize(input);",
"cwe": "CWE-79"
}
],
"summary": "Two vulnerabilities found: a critical SQL injection and a high-severity XSS. Both stem from unsanitized user input being used directly in queries and DOM manipulation."
}Refinamiento iterativo
Los prompts de un solo intento rara vez producen código listo para producción. Trata al modelo como a un compañero de programación en pareja: dale retroalimentación, pide modificaciones y ve acotando la solución correcta.
# Round 1: Get the basic structure
"Implement a retry mechanism with exponential backoff for HTTP requests"
# Round 2: Add constraints based on the output
"Good. Now modify it to:
- Use a jitter factor to prevent thundering herd
- Accept a custom shouldRetry predicate
- Log each retry attempt with the attempt number and delay
- Respect a maximum total timeout, not just max retries"
# Round 3: Integration
"Now wrap this in a class that can be used as middleware
for our existing HttpClient. Show usage examples."Cada ronda se construye sobre la salida anterior. El modelo tiene el contexto completo de la conversación, así que refina en lugar de empezar de cero. Esto es más rápido que intentar especificarlo todo perfectamente de una sola vez.
Plantillas de prompts para tareas recurrentes
Para las tareas que realizas repetidamente, guarda plantillas de prompts que incluyan las convenciones de tu proyecto.
## Bug Fix Template
Context: [paste the error message and stack trace]
Relevant code: [paste the function/file where the error occurs]
Expected behavior: [what should happen]
Actual behavior: [what actually happens]
Constraints:
- Fix must not change the public API
- Must include a test case that would have caught this bug
- Explain why the bug occurred (root cause)
## Code Review Template
Review this code for:
1. Logic errors or edge cases
2. Performance issues (N+1 queries, unnecessary re-renders)
3. Security vulnerabilities
4. Deviation from project conventions
For each issue found, provide:
- Line number
- Issue description
- Suggested fix with codeEstas plantillas convierten a los LLM de herramientas de chat inconsistentes en asistentes de desarrollo fiables. La consistencia proviene de la estructura del prompt, no del entrenamiento del modelo.
Conclusiones clave
- Estructura los prompts con contexto, tarea, restricciones y formato — los prompts vagos producen código vago
- Proporciona contexto específico, no archivos completos — los tipos y las interfaces le dan al modelo lo que necesita para integrarse con tu base de código
- Usa cadena de pensamiento para lógica compleja — razonar antes de programar detecta casos límite
- Los ejemplos few-shot establecen patrones — dos ejemplos valen más que un párrafo de descripción
- Itera por rondas — trata al modelo como a un compañero de programación en pareja, no como a un oráculo
- Guarda plantillas de prompts — las tareas recurrentes obtienen resultados consistentes gracias a estructuras reutilizables


