---
title: "Duplicación en lugar de extracción"
type: "ai-smell"
slug: "duplication-instead-of-extraction"
url: "http://localhost:3000/es/ai-smells/duplication-instead-of-extraction.md"
category: "Estructura"
description: "Los asistentes de IA tienden a pegar una copia nueva y ligeramente modificada de la lógica existente en lugar de reutilizar o extraer una función compartida, inflando el código duplicado mientras la refactorización desaparece en silencio."
---
# Duplicación en lugar de extracción

> Los asistentes de IA tienden a pegar una copia nueva y ligeramente modificada de la lógica existente en lugar de reutilizar o extraer una función compartida, inflando el código duplicado mientras la refactorización desaparece en silencio.

## Signs and Symptoms

Un revisor reconoce este olor cuando un diff escrito por IA _añade_ un bloque que ya existe en algún lugar del repositorio, ligeramente renombrado, en lugar de llamar a la función que ya está ahí. La forma reveladora son dos o más cuerpos casi idénticos que difieren solo en un sustantivo (`user`/`order`), un endpoint o una constante: el tipo de copia que un humano habría factorizado.

Señales comunes:

* Nuevos manejadores/servicios que repiten textualmente la misma validación, reintento, mapeo de errores o código repetitivo de fetch.
* Cadenas mágicas y literales de configuración reescritos en cada bloque nuevo en lugar de referenciarlos desde un solo lugar.
* Cada turno del agente reimplementa lógica que el turno anterior ya escribió (el agente no recuerda que existe un ayudante).
* `git log` muestra commits de IA consecutivos que añaden líneas pero casi ninguna línea "movida"/renombrada: adiciones sin consolidación.

```ts
// La IA añadió esto para la nueva ruta — pero getUser ya existe 30 líneas más arriba
async function getOrder(id: string) {
  const res = await fetch(`${API}/orders/${id}`, { headers: authHeaders() });
  if (!res.ok) throw new Error(`Request failed: ${res.status}`);
  const json = await res.json();
  if (!json?.data) throw new Error("Malformed response");
  return json.data;
}

async function getUser(id: string) {                 // <-- cuerpo 95 % idéntico
  const res = await fetch(`${API}/users/${id}`, { headers: authHeaders() });
  if (!res.ok) throw new Error(`Request failed: ${res.status}`);
  const json = await res.json();
  if (!json?.data) throw new Error("Malformed response");
  return json.data;
}

```

Este es el clásico olor de [Código duplicado](https://refactoring.guru/smells/duplicate-code), pero producido sistemáticamente en lugar de por accidente.

## Reasons for the Problem

**Por qué los modelos lo producen**

* **Localidad del siguiente token.** Un LLM completa el fragmento _actual_ a partir del texto más similar en su ventana de contexto. Reproducir un bloque cercano y conocido como bueno es la continuación de mayor probabilidad; "detente, ve a definir un ayudante y luego llámalo" es un desvío más largo y de menor probabilidad que abarca varios archivos.
* **Sin modelo de todo el repositorio.** El asistente rara vez sabe que ya existe un ayudante adecuado fuera de su contexto. Sin recuperación no puede reutilizar lo que no ve, así que lo regenera. Los profesionales describen agentes que "recrean lógica similar desde cero para cada nueva tarea", y Eno Reyes (Factory) plantea la propia calidad del código como el principal predictor de si la IA ayuda o perjudica a una base de código.
* **Amnesia por turno + alcance del prompt.** Cada petición se trata como una unidad autónoma ("haz que _esto_ funcione"), así que el agente sobreajusta una solución estrecha y de un solo uso en lugar de generalizar. OX Security llama a esto **sobreespecificación** (vista en el 80–90 % del código de IA) y **evitación de refactorizaciones** (80–90 %): el modelo emite código funcional para el prompt inmediato pero nunca consolida.
* **Adulación / mínima fricción.** Los modelos optimizan por una salida que satisfaga visiblemente la petición sin tocar archivos no relacionados. Duplicar es "seguro" y local; refactorizar arriesga romper algo que el modelo no puede ver, así que lo evita.
* **Las herramientas lo abaratan.** Como señala GitClear, el aceptar-con-tabulador hace que insertar un bloque nuevo sea casi gratis, eliminando la fricción que antes empujaba a los desarrolladores hacia la reutilización.

**Por qué resulta perjudicial**

* **Erosión de calidad medida.** El análisis de 2025 de GitClear (211 millones de líneas modificadas, 2020–2024) encontró que las líneas copiadas/pegadas subieron del 8,3 % al 12,3 % mientras que las líneas "movidas" (refactorizadas) cayeron de \~24 % al 9,5 %; 2024 fue el primer año en que las líneas copiadas/pegadas _superaron_ a las movidas, y los bloques de 5+ líneas duplicadas se multiplicaron \~8×.
* **Deuda de corrección: "déjà-vu de errores".** OX Security lo encontró en el 70–80 % del código de IA: un error corregido en un clon sobrevive en silencio en todos los demás, así que defectos idénticos reaparecen y cada uno necesita una corrección redundante. La duplicación convierte un error en N errores.
* **Mantenibilidad y carga de revisión.** Cada cambio de comportamiento debe encontrarse y editarse en muchos lugares; los diffs se hinchan con bloques casi idénticos que los revisores deben comparar línea a línea para confirmar que son _realmente_ idénticos (y no sutil y peligrosamente distintos).
* **Superficie de seguridad.** Un patrón vulnerable (falta de comprobación de autenticación, entrada sin escapar) se propaga a cada copia, y una corrección de endurecimiento posterior puede pasar por alto algún clon: la duplicación multiplica la superficie del parche.

## Treatment

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

* Dile al modelo dónde mirar primero: _"Antes de escribir código nuevo, busca en el repositorio un ayudante existente que haga esto y reutilízalo; no dupliques."_ Pega el módulo relevante para que esté en el contexto.
* Exige la consolidación de forma explícita: _"Si dos bloques difieren solo en los parámetros, extrae una función parametrizada (Extraer función) y llámala desde ambos."_
* Hazlo ejecutar las herramientas: _"Ejecuta el linter / jscpd / la comprobación de duplicación y resuelve cualquier clon que informe antes de devolver el diff."_ Los agentes se autocorrigen bien cuando se les da una señal determinista.
* En la revisión, trata un bloque con aspecto de pegado como una invitación a hacer grep: busca una línea distintiva; si ya existe, devuélvelo para su extracción.

**La refactorización — Extraer función + parametrizar**

```ts
// después: una única fuente de verdad, llamada desde ambos
async function getResource<T>(path: string): Promise<T> {
  const res = await fetch(`${API}/${path}`, { headers: authHeaders() });
  if (!res.ok) throw new Error(`Request failed: ${res.status}`);
  const json = await res.json();
  if (!json?.data) throw new Error("Malformed response");
  return json.data as T;
}

const getOrder = (id: string) => getResource<Order>(`orders/${id}`);
const getUser  = (id: string) => getResource<User>(`users/${id}`);

```

Ahora una corrección al manejo de errores o al parseo de respuestas se aplica una sola vez. Donde los duplicados difieren en un paso en lugar de en un valor, prefiere [Formar método plantilla](https://refactoring.guru/design-patterns/template-method) o pasa el paso variable como callback. Los movimientos canónicos aquí son **Extraer función**, **Extraer variable/constante** (para los literales reescritos) y **Subir método** cuando los clones viven en clases hermanas: todas curas estándar para el [Código duplicado](https://refactoring.guru/smells/duplicate-code).

**Barrera de protección, no solo limpieza:** conecta un detector de clones a CI con un umbral bajo para que el _siguiente_ duplicado generado por IA rompa la compilación en lugar de fusionarse. Combínalo con una regla de la casa de una línea en `CLAUDE.md`/`AGENTS.md` ("reutiliza los ayudantes existentes; nunca pegues un casi-duplicado") para que la restricción viaje en el contexto del modelo en cada turno.

## Detected by

- **jscpd** `copy/paste duplication (configurable min-tokens / min-lines threshold; supports --threshold gate in CI)` (https://github.com/kucherenko/jscpd)
- **PMD** `CPD (Copy/Paste Detector) — token-based clone detection across 30+ languages` (https://pmd.github.io/pmd/pmd_userdocs_cpd.html)
- **SonarQube / SonarSource** `S4144 — Methods should not have identical implementations` (https://rules.sonarsource.com/javascript/RSPEC-4144/)
- **SonarQube / SonarSource** `S1192 — String literals should not be duplicated` (https://rules.sonarsource.com/javascript/RSPEC-1192/)
- **SonarQube** `Duplicated blocks / duplicated_lines_density metric (built-in clone engine, fails quality gate over threshold)` (https://docs.sonarsource.com/sonarqube-server/latest/user-guide/code-metrics/metrics-definition/)
- **eslint-plugin-sonarjs** `sonarjs/no-identical-functions` (https://github.com/SonarSource/eslint-plugin-sonarjs/blob/master/docs/rules/no-identical-functions.md)
- **eslint-plugin-sonarjs** `sonarjs/no-duplicate-string` (https://github.com/SonarSource/eslint-plugin-sonarjs/blob/master/docs/rules/no-duplicate-string.md)
