Código autodocumentado: el principio que le salva la vida al que viene después

El conocimiento más caro es el que vive en la cabeza de alguien
Al principio de mi carrera me incorporé a un proyecto donde el desarrollador original se había ido seis meses antes. El código funcionaba. Los tests pasaban. Pero nadie podía explicar por qué una lógica de reintentos usaba exactamente tres intentos con un backoff de 2 segundos, ni por qué el handler del webhook de billing tragaba ciertos códigos de error silenciosamente en vez de propagarlos.
Pasamos semanas haciendo ingeniería inversa de decisiones que podrían haberse capturado en una sola línea de comentario. Esa experiencia formó un principio que ahora aplico a cada codebase que construyo: el código debe explicarse solo para que el próximo desarrollador no tenga que adivinar.
No se trata de escribir más comentarios. Se trata de escribir los comentarios correctos, elegir nombres que se lean como oraciones, y tratar las definiciones de tipos como documentación viva.
Cómo se ve el código autodocumentado en la práctica
Voy a mostrar ejemplos reales de una plataforma SaaS en producción con Node.js y Next.js.
Tipos que cuentan una historia
Una definición de tipo debería comunicar intención, restricciones y contexto sin necesidad de abrir otro archivo:
/**
* Fecha de expiración del plan.
*
* **Uso:**
* - Planes TRIAL: Fecha fin del período de prueba de 7 días (local, sin suscripción Stripe)
* - Planes BEAUTY/STORE: NO se usa (las suscripciones Stripe son abiertas con auto-renovación)
*
* **Control de acceso:**
* - TRIAL: Se verifica contra esta fecha
* - BEAUTY/STORE: Controlado por el estado de suscripción en Stripe, NO por este campo
*/
endDate: Date;Un desarrollador que mira endDate podría asumir razonablemente que controla el acceso para todos los tipos de plan. Sin este comentario, alguien podría introducir un bug checkeando endDate para una suscripción paga, sin darse cuenta de que Stripe maneja eso por otro lado. El comentario explica el por qué y la trampa en cuatro líneas.
Headers de módulo que mapean la arquitectura
Cuando un archivo implementa un patrón no trivial, el encabezado debe explicar el contexto arquitectónico:
/**
* Supervisor Agent - Coordinador de sub-agents
*
* Pattern: Tool Calling (True Supervisor Pattern)
* El supervisor coordina sub-agents especializados invocados como tools.
*
* Arquitectura:
* - Sub-agents (GameConfig, Analytics, Billing, General) -> Tools del supervisor
* - Supervisor decide qué tools llamar según el request del usuario
* - Supervisor puede llamar múltiples tools en una conversación
* - Supervisor sintetiza resultados en respuesta conversacional final
*
* Escalabilidad:
* - Agregar nuevo dominio = crear sub-agent en su carpeta + agregarlo aquí
* - NO modificar graph structure
* - NO modificar routing logic
*/Un nuevo miembro del equipo que lea esto entiende inmediatamente: qué patrón se usa, cómo se conectan las piezas, y cómo extenderlo. No hace falta un mensaje de Slack ni una call de 30 minutos. El código mismo lo capacita.
Comentarios que explican POR QUÉ, no QUÉ
El peor tipo de comentario es // incrementar contador arriba de counter++. El mejor tipo explica una decisión que de otra forma parecería arbitraria:
/**
* Paths que deben estar al INICIO de la URL para ser bloqueados.
* Usa startsWith para evitar falsos positivos con slugs de negocios.
* Ej: /public/joomla-bakery NO debe bloquearse, pero /joomla sí.
*/
const BLOCKED_PATH_PREFIXES = [
"/wp-admin",
"/wp-login",
"/wordpress",
"/administrator",
"/phpmyadmin",
];Sin ese comentario, un desarrollador futuro podría refactorizar esto a includes() por "simplificar" y romper URLs legítimas. El comentario previene una regresión sutil explicando el razonamiento detrás de la implementación.
Flujos numerados paso a paso
Para operaciones complejas de múltiples pasos, los comentarios numerados crean una narrativa que hace el código revisable de un vistazo:
/**
* Finaliza un job de generación de imagen: convierte, almacena y notifica.
*
* @param pngBuffer - Buffer con la imagen PNG generada por Gemini
* @param optimizedPrompt - Prompt usado para generar la imagen (se guarda en MongoDB)
* @param contentName - Nombre del challenge/game (para email)
* @param businessInfo - Datos del business (ID, nombre, owner)
*/
async finalize(pngBuffer: Buffer, optimizedPrompt: string, ...): Promise<JobResult> {
/**
* PASO 1: Guardar optimizedPrompt en MongoDB.
* Esto se hace antes de la conversión para tener el prompt guardado
* incluso si falla el resto.
*/
await this.imageGenerationRepository.updateImage(imageId, { optimizedPrompt });
/**
* PASO 2: Convertir PNG a JPG para optimizar para Instagram.
* Calidad 85% es el sweet spot entre tamaño y calidad visual.
*/
const jpgBuffer = await sharp(pngBuffer).jpeg({ quality: 85 }).toBuffer();
/**
* PASO 3: Subir JPG a Cloudflare R2.
* Path format: stories/{businessId}/{jobId}.jpg
*/
const r2Url = await R2Client.getInstance().uploadStoryImage(jpgBuffer, r2Path);
// ... PASO 4, 5, 6
}Cada paso explica el por qué de su orden. El paso 1 guarda el prompt primero porque la recuperación es posible incluso si la conversión de imagen falla. El paso 2 explica la elección del parámetro de calidad. Esto no es ruido; es conocimiento institucional incrustado en el código.
Interfaces como documentación visual
A veces la mejor documentación es un tipo bien estructurado que funciona como referencia visual:
/**
* IBrandTheme - Interfaz de colores específicos por elemento HTML
*
* Define TODOS los colores del juego de ruleta de premios para móviles.
*
* ESTRUCTURA VISUAL (de arriba hacia abajo):
* +-------------------------------------------+
* | HEADER (logo + título del negocio) |
* +-------------------------------------------+
* | |
* | RULETA DE PREMIOS |
* | (segmentos + puntero + botón GIRA) |
* | |
* | +-----------------------------------+ |
* | | CARD: Formulario o información | |
* | | (inputs, botones, textos) | |
* | +-----------------------------------+ |
* | |
* +-------------------------------------------+
*/
export interface IBrandTheme {
/**
* Color de fondo del header - barra superior de ~80px de alto.
*
* UBICACIÓN: Parte superior de la pantalla, ocupa todo el ancho.
* CONTIENE: Logo del negocio (izquierda) y título del negocio (centro/derecha).
* INTERACCIÓN: El texto del título (headerTitleColor) va encima de este fondo.
*
* RESTRICCIONES:
* - Debe ser el color más representativo de la marca/logo del negocio
* - headerTitleColor debe tener contraste WCAG AA (4.5:1) sobre este color
*
* @example "#1A1F3B" (azul oscuro profesional)
* @example "#D4508F" (rosa vibrante para spa/belleza)
*/
headerBackgroundColor: string;
// ... 60+ campos, cada uno con este nivel de detalle
}Esta interfaz tiene más de 60 propiedades, y cada una incluye: dónde aparece en pantalla, con qué interactúa, restricciones de accesibilidad, y ejemplos concretos. Un modelo de IA o un desarrollador nuevo puede generar un tema completo y válido sin hacer una sola pregunta.
Documentación inline que enseña patrones
Para sistemas complejos, incluyo archivos markdown junto al código que enseñan el patrón que se está usando:
sub-agents/tools/
HowToCreateTools.md <-- Explica ambos patrones de tools
sendLoginEmail.ts <-- Implementa el patrón con JSDoc completo
escalateToHuman.ts <-- Otra implementación de referencia
El HowToCreateTools.md incluye ejemplos correctos e incorrectos, anti-patrones a evitar, diagramas de arquitectura en ASCII, y un checklist para crear nuevas tools. Vive al lado del código que documenta, no en una página de Confluence que va a quedar desactualizada en tres meses.
Por qué esto le importa al negocio
El código autodocumentado no es un capricho de desarrollador. Impacta directamente en métricas del negocio:
Onboarding más rápido. Una persona nueva que lee un codebase bien documentado puede hacer su primer PR en días, no en semanas. El código le enseña los patrones, las restricciones y las decisiones.
Bus factor reducido. Cuando el conocimiento vive en el código en lugar de en la cabeza de alguien, perder un miembro del equipo no significa perder contexto. El comentario de endDate de arriba podría prevenir un bug de billing que cuesta miles.
Desarrollo asistido por IA más barato. Los agentes de IA producen código dramáticamente mejor cuando el codebase existente está bien documentado. Los tipos, el JSDoc y los comentarios arquitectónicos se convierten en contexto que guía la generación.
Menos conversaciones de "por qué hicimos esto?" Cada comentario que explica una decisión es un hilo futuro de Slack que nunca sucede.
La conclusión práctica
No hace falta documentar todo. Hace falta documentar las cosas que harían que alguien se detenga y se pregunte. Esta es mi regla:
- Cada función pública lleva JSDoc con propósito, parámetros y valor de retorno.
- Cada decisión no obvia lleva un comentario explicando por qué, no qué.
- Cada definición de tipo incluye suficiente contexto para que el lector nunca necesite abrir otro archivo para entenderla.
- Cada flujo complejo lleva pasos numerados con la razón del orden.
- Cada patrón lleva un documento de referencia vivo junto al código.
El objetivo no es la perfección. El objetivo es que dentro de seis meses, cuando abras un archivo que no tocaste en un tiempo, lo entiendas en segundos en vez de minutos. Ese es el interés compuesto del código autodocumentado.