---
title: "Código solo del camino feliz"
type: "ai-smell"
slug: "happy-path-only"
url: "http://localhost:3000/es/ai-smells/happy-path-only.md"
category: "Corrección"
description: "Los asistentes de IA tienden a generar código que solo trata el caso correcto y bien formado —omitiendo la validación de entradas, el manejo de errores, las comprobaciones de nulos/vacíos y los casos límite— de modo que el código funciona en la demo y se rompe en producción."
---
# Código solo del camino feliz

> Los asistentes de IA tienden a generar código que solo trata el caso correcto y bien formado —omitiendo la validación de entradas, el manejo de errores, las comprobaciones de nulos/vacíos y los casos límite— de modo que el código funciona en la demo y se rompe en producción.

## Signs and Symptoms

Quien revisa detecta código solo del camino feliz cuando cada línea asume que la anterior tuvo éxito: las llamadas de red no se comprueban por estado distinto de 2xx, `JSON.parse`/`await res.json()` nunca se envuelve, los campos anulables se desreferencian directamente, los arreglos se indexan sin comprobar la longitud y la entrada externa fluye directa a la lógica sin validación. Suele haber exactamente una vía de retorno y ningún `throw`, ninguna cláusula de guarda y ningún `catch` (o un `catch` que está vacío o que solo hace `console.log`).

```ts
// Generado por IA: solo existe el escenario de éxito
async function getUserCity(userId) {
  const res = await fetch(`/api/users/${userId}`);
  const user = await res.json();
  return user.address.city.toUpperCase();
}

```

Modos de fallo silenciosamente ausentes: `res.ok` nunca se comprueba (un 404/500 devuelve un cuerpo de error que `.json()` puede rechazar), `user` podría ser `{}`, `address` podría ser `null`, `city` podría ser `undefined` → `Cannot read properties of undefined`. La función "funciona" contra un stub feliz y lanza una excepción contra la realidad.

Señales reveladoras en un diff:

* Una nueva función `async` sin `try`/`catch` y sin `.catch()` en la promesa.
* Cadenas de propiedades directas (`a.b.c.d`) sobre datos que cruzaron un límite de confianza/E-S.
* `arr[0]` sin comprobar, `find(...)!`, conversiones `as` o aserciones de no-nulo (`!`) que sustituyen al manejo real.
* `// TODO: handle errors` o un `catch (e) {}` vacío dejado como relleno.
* La descripción del PR dice "maneja X", pero solo se implementa la rama en que X tiene éxito.

## Reasons for the Problem

**Por qué los modelos lo producen**

* **La probabilidad del siguiente token favorece el flujo canónico.** La continuación más probable después de `const user = await res.json()` es `return user.something`, no una comprobación de estado. El manejo de errores es código repetitivo de alta varianza que cambia según la base de código, así que es estadísticamente "sorprendente" y se descarta.
* **Los datos de entrenamiento están sesgados hacia el camino feliz.** Los tutoriales, los fragmentos de README, las entradas de blog y las respuestas aceptadas de Stack Overflow eliminan la validación y el manejo de errores por brevedad ("se omite el manejo de errores por claridad"). El modelo aprendió de texto que eliminó deliberadamente justo el código que tú quieres.
* **Adulación / recompensa por parecer limpio.** Los asistentes ajustados con RLHF se recompensan por respuestas concisas y que responden directamente. El código defensivo parece ruido, así que el modelo optimiza para el fragmento pulcro que aparenta responder al prompt.
* **Sin contexto del repositorio.** El modelo no sabe que tienes un tipo `AppError`, un envoltorio `Result<T>`, un esquema `zod` ni una convención de logging, así que no puede reutilizarlos, y opta por "nada" en lugar de adivinar tus convenciones.
* **Tendencias de comportamiento documentadas.** El análisis de OX Security de más de 300 repositorios cita la _Evitación de refactorizaciones_ y la _Fijación al manual_ entre sus principales antipatrones de IA: el modelo emite código funcional para el prompt inmediato y nunca lo endurece. arXiv 2509.20491 cataloga olores específicos de la IA en torno a los _fallos silenciosos_; arXiv 2510.03029 halla olores de implementación elevados como el _Bloque catch vacío_ en la salida de los LLM.

**Por qué hace daño**

* **Corrección:** se rompe con `null`, colecciones vacías, timeouts y respuestas distintas de 200: justo las entradas que no aparecen en una prueba manual rápida.
* **Seguridad:** el camino feliz confía implícitamente en su entrada. La validación omitida en los límites es la vía de entrada de los fallos de inyección, path traversal y contaminación de prototipos. El informe de OX enmarca esto como código "inseguro por torpeza", entregado rápido sin criterio.
* **Carga de revisión y depuración:** en la Encuesta a Desarrolladores de Stack Overflow de 2025, el 66% de los desarrolladores cita las "soluciones de IA que están casi bien, pero no del todo" como su principal frustración, y el 45% dice que depurar código generado por IA lleva _más_ tiempo. El código del camino feliz es el arquetipo de "casi bien": se lee bien y falla en tiempo de ejecución.
* **Acumulación de deuda técnica:** como el modelo no refactoriza ni reutiliza las utilidades de error existentes, cada función del camino feliz es un bloque nuevo de un solo uso. Los datos de GitClear de 2025 muestran el patrón más amplio: las líneas copiadas/pegadas subieron del 8,3% (2021) al 12,3% (2024), los bloques duplicados de más de 5 líneas crecieron \~8× en 2024, mientras que la refactorización (líneas movidas) cayó de \~25% a menos del 10%. El manejo de errores ausente se añade después a martillazos, duplicado en cada punto de llamada, nunca centralizado.

## Treatment

**Tácticas de revisión y de prompts**

* **Haz que el modelo enumere primero los modos de fallo.** Prompt: "Antes de escribir código, enumera los modos de fallo de esta función (entrada incorrecta, error de red, distinto de 200, respuesta vacía/malformada, campos ausentes, concurrencia). Luego implementa el manejo de cada uno." Forzar el paso de enumeración contrarresta la tendencia del siguiente token a saltárselos.
* **Señala las convenciones a reutilizar.** "Usa nuestro tipo `AppError`/`Result` existente de `lib/errors.ts` y el `logger` de `lib/log.ts`; valida la respuesta con el esquema `zod` de `schemas/user.ts`." Esto convierte el "sin manejo" en reutilización en lugar de un bloque hecho a medida (evita el _Código duplicado_).
* **Exige que se ejecuten las puertas de control.** "Ejecuta `eslint` y `tsc --noEmit` y arregla cada advertencia, incluida `@typescript-eslint/no-floating-promises`." La comprobación de tipos con `strictNullChecks` convierte las desreferencias silenciosas de nulos en errores de compilación que el modelo debe atender.
* **Exige las pruebas negativas.** Pide tests unitarios que cubran las entradas vacías/nulas/de error, no solo el caso feliz: la ausencia de esos tests es en sí misma el olor.

**La refactorización**

Añade validación en los límites y cláusulas de guarda, y centraliza el manejo. Movimientos con nombre: **Introducir cláusula de guarda** (`throw` temprano ante un estado inválido), **Introducir aserción / validación en el límite** (parse-don't-validate en los bordes de E-S) e **Introducir caso especial / objeto nulo** (`?? "UNKNOWN"` en lugar de romper).

```ts
// después: los modos de fallo son de primera clase
async function getUserCity(userId: string): Promise<string> {
  if (!userId) throw new InvalidArgumentError("userId required");   // cláusula de guarda

  const res = await fetch(`/api/users/${encodeURIComponent(userId)}`);
  if (!res.ok) throw new ApiError(`user fetch failed: ${res.status}`);

  const user = UserSchema.parse(await res.json());                  // validar en el límite
  return user.address?.city?.toUpperCase() ?? "UNKNOWN";           // caso especial para el hueco
}

```

Si el mismo patrón de try/validación/log empieza a repetirse en varios puntos de llamada, aplícale **Extraer función** a una función auxiliar compartida `fetchJson<T>(url, schema)` para que el manejo de errores viva en un solo lugar en vez de copiarse y pegarse (la trampa de duplicación de GitClear).

## Detected by

- **typescript-eslint** `@typescript-eslint/no-floating-promises` — Marca las promesas cuya vía de rechazo nunca se maneja (sin await/catch), un síntoma habitual de camino feliz en las llamadas asíncronas. (https://typescript-eslint.io/rules/no-floating-promises/)
- **ESLint** `no-empty` — Marca los bloques vacíos, incluidos los bloques catch vacíos: el 'manejo de errores' de relleno que el modelo deja atrás. (https://eslint.org/docs/latest/rules/no-empty)
- **SonarSource (JS/TS)** `javascript:S2486` — Las excepciones no deben ignorarse: marca los errores capturados pero tragados, el primo degenerado de no manejar nada en absoluto. (https://rules.sonarsource.com/javascript/RSPEC-2486/)
