Logotipo de ConstructiCat
CodeBust.
Explorar la sección ▾

Configuración codificada de forma rígida.

Los asistentes de IA incrustan valores específicos del entorno —URL, puertos, rutas, tiempos de espera, claves y números mágicos— directamente en la lógica en lugar de leerlos de la configuración o del entorno, porque un literal es el siguiente token más probable y el modelo desconoce la capa de configuración existente.

##Signos y síntomas

Un asistente de IA tiende a emitir un literal plausible justo donde corresponde una referencia. El código funciona en la demo, así que el smell sobrevive a la revisión a menos que lo busques.

Señales reveladoras:

  • Endpoints, puertos, rutas y tiempos de espera incrustados esparcidos por la lógica de negocio en lugar de en un módulo de configuración: http://localhost:3000, /tmp/cache, 5432, setTimeout(..., 30000).
  • Números/cadenas mágicos sin constante con nombre: recuentos de reintentos, tamaños de página, límites de tasa, umbrales de funcionalidades.
  • El mismo literal repetido entre archivos porque el modelo lo regeneró en lugar de importar una constante existente (el patrón de GitClear «copiar/pegar arriba, refactorizar abajo» en miniatura).
  • Credenciales / claves codificadas de forma rígida: tokens de API, claves de cifrado o cadenas de autenticación Basic pegadas desde los datos de entrenamiento.
  • Un config.ts / .env / objeto de ajustes existente que el nuevo código ignora por completo.
  • Valores incrustados del entorno: una URL de desarrollo/staging o un ID de cuenta de pruebas cableado en una ruta que llega a producción.
// 🚩 Generado por IA: cada parámetro es un literal, con valores de desarrollo incrustados
export async function syncOrders() {
  const res = await fetch("https://api.staging.acme.dev/v1/orders", {
    headers: { Authorization: "Bearer sk_test_4eC39HqLyjWDarjtT1zdp7dc" },
    signal: AbortSignal.timeout(30000),
  });
  const orders = (await res.json()).slice(0, 50); // ¿por qué 50?
  for (let i = 0; i < 3; i++) { /* reintentar 3 veces... ¿por qué 3? */ }
}

La línea de Authorization es una coincidencia para gitleaks/Semgrep; la URL, 30000, 50 y 3 son smells de valores mágicos; y api.staging.acme.dev es un valor de desarrollo a punto de llegar a producción.

##Causas del problema

Por qué lo producen los modelos

  • Un literal es el siguiente token de mayor probabilidad. Dado fetch(, la terminación más barata que satisface el prompt inmediato es una cadena de URL concreta, no config.apiBaseUrl. Resolver una referencia requiere saber que un símbolo existe en otro lugar; emitir un valor no requiere nada. Los modelos optimizan para obtener un fragmento localmente plausible y ejecutable.
  • El corpus de entrenamiento son fragmentos autocontenidos. Los tutoriales, los ejemplos de README y las respuestas de Stack Overflow incrustan sus valores para que se ejecuten de forma independiente. El modelo aprendió que «el buen código de ejemplo» codifica los valores de forma rígida, lo contrario de la higiene de producción.
  • Sin contexto del repositorio / ventana limitada. Normalmente el modelo no ha leído tu config/, el esquema de variables de entorno ni el archivo de constantes, así que no puede reutilizarlos. El resumen de DevClass sobre la investigación de GitClear lo relaciona con el aumento de la duplicación: los asistentes proponen con menos frecuencia reutilizar una función similar, en parte por el tamaño limitado del contexto. Los propios datos de GitClear muestran que los bloques con cinco o más líneas duplicadas se multiplicaron aproximadamente por ocho durante 2024, mientras que las líneas asociadas a refactorización ("movidas") cayeron del 25% de las líneas cambiadas en 2021 a menos del 10% en 2024.
  • Servilismo / sesgo de responder al prompt. Si se le pide «añade una sincronización de pedidos», el modelo entrega algo que funciona ahora; externalizar la configuración es andamiaje adicional que no ofrecerá voluntariamente a menos que se le indique.
  • Obsolescencia por la fecha de corte del entrenamiento. Los literales incrustados suelen ser literales obsoletos: endpoints antiguos, versiones de API en desuso, puertos por defecto o constantes criptográficas débiles. El artículo de OX Security sobre la seguridad del vibe coding atribuye prácticas anticuadas como el hash MD5 a "patrones aprendidos de bases de código heredadas en las que esas prácticas fueron en su día el estándar del sector" y señala que "el modelo no tiene ninguna conciencia de que los estándares de seguridad evolucionaron".

Por qué es perjudicial

  • Mantenibilidad: Cirugía a tiros. Cambiar un tiempo de espera o una URL base implica buscar cada literal duplicado por toda la base de código; si se te escapa uno, el comportamiento diverge en silencio según el punto de llamada.
  • Corrección entre entornos. Una URL de staging o un ID de cuenta de desarrollo cableado en la lógica acaba en producción; los umbrales mágicos sin nombre se desincronizan de los valores que deberían reflejar.
  • Seguridad. Los tokens y claves incrustados son fugas de credenciales: una vez confirmados, viven para siempre en el historial de git. Un estudio empírico del código de compilación generado por IA (arXiv 2601.16839) encontró 364 olores en código de compilación de pull requests creados por agentes, entre ellos 25 rutas/URL incrustadas y 5 credenciales incrustadas de gravedad alta; más del 61% de esos pull requests de agentes se fusionaron.
  • Carga de revisión y deuda técnica. Los revisores deben verificar la procedencia de cada literal, y los valores que gobiernan el comportamiento a través de funciones auxiliares y umbrales son precisamente los más difíciles de auditar a mano. En la entrevista de Stack Overflow con Eno Reyes, de Factory, Reyes observa que "cuanto mayor era la calidad de la base de código, más aceleraba la IA a la organización", y la proliferación de valores incrustados erosiona exactamente esa base.

##Tratamiento

Tácticas de prompting / revisión

  • Apunta el modelo hacia tu superficie de configuración: «Lee src/config.ts y .env.example; usa config.* / process.env para cada URL, puerto, tiempo de espera y credencial. No introduzcas literales.» Los modelos incrustan valores porque no saben que el símbolo existe: nómbralo.
  • Prohíbe los valores mágicos de forma explícita: «Nada de números ni cadenas mágicas: extrae constantes con nombre.» Después haz que ejecute eslint --rule no-magic-numbers y un escáner de secretos (gitleaks/Semgrep) y que corrija lo que marquen. Según la guía de Factory, conecta los linters/escáneres al bucle para que el agente se autocorrija en lugar de apoyarse en la revisión humana.
  • Proporciona el esquema del entorno (zod/envalid/.env.example) para que el modelo tenga dónde colocar los valores en lugar de adivinarlos.
  • Inspecciona los nuevos diffs en busca de http, localhost, literales de IP, Bearer y dígitos sueltos en los argumentos de las llamadas antes de hacer merge.

La refactorización: las técnicas del catálogo y una práctica de configuración:

  • Reemplazar número mágico por constante simbólica para umbrales, contadores y tamaños.
  • Extraer clase para reunir los ajustes en un único módulo de configuración tipado y validado.
  • Lleva los valores que dependen del entorno y los secretos a variables de entorno (configuración 12-factor) y léelos solo a través de ese módulo; nunca confirmes secretos.
// config.ts — fuente de verdad única y validada
import { z } from "zod";
const env = z.object({
  ORDERS_API_BASE_URL: z.string().url(),
  ORDERS_API_TOKEN: z.string().min(1),
  ORDERS_TIMEOUT_MS: z.coerce.number().default(30_000),
  ORDERS_PAGE_SIZE: z.coerce.number().default(50),
  ORDERS_MAX_RETRIES: z.coerce.number().default(3),
}).parse(process.env);
export const config = env;
// ✅ después: referencias, no literales — un único lugar que cambiar, el secreto fuera del código fuente
import { config } from "./config";
export async function syncOrders() {
  const res = await fetch(`${config.ORDERS_API_BASE_URL}/v1/orders`, {
    headers: { Authorization: `Bearer ${config.ORDERS_API_TOKEN}` },
    signal: AbortSignal.timeout(config.ORDERS_TIMEOUT_MS),
  });
  const orders = (await res.json()).slice(0, config.ORDERS_PAGE_SIZE);
  for (let i = 0; i < config.ORDERS_MAX_RETRIES; i++) { /* ... */ }
}

Si un secreto ya llegó a un commit, rótalo: eliminar la línea no purga el historial de git.

##Detectado por