12 ene 2026·7 min de lectura

Elimina código copiado de forma segura con utilidades compartidas

Elimina código copiado de forma segura con un plan de refactor por pasos pequeños que desduplica funciones generadas por IA, mantiene el comportamiento idéntico y verifica cada cambio.

Elimina código copiado de forma segura con utilidades compartidas

Lo que el “código copiado” rompe con el tiempo

El código copiado es cuando la misma lógica aparece en varios sitios con pequeñas ediciones: una variable renombrada, un valor por defecto distinto, un mensaje de error ligeramente diferente. Al principio parece más rápido que crear una función reutilizable. Meses después, se convierte en una trampa.

En proyectos reales suele verse como el mismo helper de petición repetido en tres archivos, cinco versiones de “formatear una fecha” o dos comprobaciones de autenticación casi idénticas que no coinciden sobre lo que significa “logueado”. El código funciona hasta que una de esas copias se arregla y las otras no.

Las apps generadas por IA tienden a duplicar lógica porque el modelo resuelve el problema que tiene delante cada vez. Pide una nueva página y puede recrear helpers en lugar de reutilizar los existentes. Las herramientas que generan código por ráfagas (página a página, función por función) hacen la repetición aún más probable. Terminas con una base de código llena de casi-clones que parecen consistentes, pero no lo son.

La duplicación aumenta los bugs y ralentiza los cambios de forma simple: cada cambio se convierte en una búsqueda en múltiples archivos. Fallas en modificar una copia y entregas comportamiento inconsistente. Las diferencias también son fáciles de pasar por alto en revisión porque suelen ser pequeñas.

Cuando la gente dice “comportamiento idéntico”, normalmente quiere decir “los usuarios no deberían notar nada cambiado”. Eso incluye la salida obvia, pero también detalles que se olvidan:

  • Las mismas entradas producen las mismas salidas, incluidos los casos límite.
  • Los mismos errores ocurren en las mismas situaciones, con los mismos mensajes o códigos.
  • Los mismos efectos secundarios ocurren (logging, caché, reintentos, escrituras en BD).
  • El comportamiento sensible al tiempo permanece dentro de los límites esperados.

Si eliminas código copiado de forma segura, el objetivo real no es “código más limpio”. Es “sin sorpresas en producción”.

Elige un buen primer objetivo de desduplicación

Empieza por un área pequeña que sea fácil de observar y difícil de malinterpretar. Buenos objetivos iniciales son patrones de helper como validación de entradas, formateo de fechas o dinero, comprobaciones de auth repetidas o el mismo envoltorio de llamada a la API usado en varias pantallas.

Elige algo en lo que el código haga sobre todo una tarea y puedas describirlo en una frase. Si necesitas tres párrafos para explicar qué hace, no es un objetivo inicial.

Antes de tocar nada, define los límites. Anota qué entra (inputs), qué sale (outputs) y qué más afecta (efectos secundarios como logging, caching, lectura de variables de entorno o mutación de un objeto compartido). Las funciones generadas por IA suelen esconder efectos secundarios en lugares pequeños, como valores por defecto “útiles” o silenciosamente ignorar errores.

Un buen candidato normalmente:

  • Aparece en 3+ sitios con líneas mayormente iguales
  • Tiene entradas y salidas claras (pocas lecturas globales)
  • Falla de forma visible (error, código de estado, mensaje)
  • No está en tu ruta más crítica o frágil (pagos, núcleo de auth, migraciones)
  • Tiene casos límite que puedes enumerar sin adivinar

A continuación, inventaría un inventario de duplicados. No te fíes solo de resultados de búsqueda. Abre cada archivo y confirma que realmente hace la misma tarea, no solo que parece similar.

Finalmente, decide qué no puede cambiar. Sé específico: tipos de error exactos, mensajes exactos (si usuarios o tests dependen de ellos), cómo se tratan valores vacíos y cualquier comportamiento “raro” en casos límite. Aquí se esconden las sorpresas: una copia recorta espacios, otra no, y de repente el inicio de sesión falla para un pequeño grupo de usuarios.

Asegura el comportamiento actual antes de cambiar nada

Necesitas pruebas de lo que el código hace hoy, incluidos los puntos raros que desearías no tener. Sin esa línea base, una “limpieza simple” puede cambiar silenciosamente salidas, mensajes de error o casos límite de los que dependen llamadores.

Empieza por capturar ejemplos reales de entradas y salidas. Extrae unos pocos de logs, tickets de soporte o tus propias ejecuciones manuales. Si no tienes logs, ejecuta la app y registra unas cuantas peticiones reales o flujos de usuario (qué ingresaste, qué viste y qué devolvió el sistema).

Anota también fallos esperados. Muchos helpers generados por IA difieren principalmente en cómo fallan: uno devuelve un objeto vacío, otro lanza, un tercero devuelve 200 con una cadena de error. Esas diferencias importan cuando otras partes de la app se construyeron alrededor de ellas.

Crea un pequeño conjunto de escenarios “dorados” que puedas volver a ejecutar después de cada cambio pequeño. Mantenlo corto y realista:

  • Una entrada común que debería tener éxito
  • Una entrada límite (vacía, cadena larga, cero, campo faltante)
  • Una entrada mala conocida que debería producir un error específico
  • Un escenario donde flags u cabeceras opcionales cambian el comportamiento
  • Un caso sensible a rendimiento (payload grande o bucle repetido)

También anota dependencias ocultas que pueden hacer el comportamiento inconsistente entre ejecuciones: variables de entorno, feature flags, hora actual y zonas horarias, IDs aleatorios, caches globales y llamadas de red.

Ejemplo: si tres helpers duplicados de petición añaden un header de auth, comprueba si difieren cuando falta el token (lanza vs devuelve null) y si leen el token de una variable global, del almacenamiento local o de una variable de entorno. Ese es el comportamiento que necesitas preservar mientras desduplicas.

Mapea duplicados y sus pequeñas diferencias

El código copiado raramente coincide perfectamente. Antes de fusionar nada, haz un mapa claro de lo que se comparte y de lo que cambió entre versiones.

Pon las funciones duplicadas una junto a otra y compáralas línea por línea. No te la saltes. El código generado por IA suele cambiar un pequeño detalle (un valor por defecto, el nombre de una cabecera, una comprobación nula ausente) que solo aparece en producción.

La mayoría de las diferencias caen en unas pocas categorías:

  • Nombres y forma (nombres de parámetros, campos devueltos, mensajes de error)
  • Valores por defecto (timeouts, reintentos, cadena vacía vs null, valores de fallback)
  • Manejo de casos límite (campos faltantes, respuestas 204, vars de entorno indefinidas)
  • Efectos secundarios (logging, métricas, caching, escrituras)
  • Formato de entrada/salida (recorte, codificación, parseo de fechas)

Una vez listadas las diferencias, elige una versión como línea base. “Línea base” no significa “mejor escrita”. Significa “la que más usa el resto de la app”, a menudo la que se usa en más sitios o la que coincide con lo que los usuarios ven en producción.

Luego decide qué diferencias son intencionales vs accidentales. Si una diferencia está documentada, testeada o claramente requerida por un llamador específico, trátala como intencional. Si parece una variación aleatoria (timeout por defecto distinto sin motivo, mapeo de errores inconsistente, casing de cabecera ligeramente distinto), asume que es accidental hasta demostrar lo contrario.

Ejemplo concreto: dos helpers de petición añaden Authorization, pero solo uno también envía cookies por defecto. Esa diferencia de una línea puede cambiar quién está “logueado” en producción.

Diseña una utilidad compartida que se mantenga simple

Desduplicar sin sorpresas en producción
Convertimos helpers copiados en utilidades compartidas sin cambiar lo que ya usan tus usuarios.

La utilidad compartida debería sentirse casi aburrida. La versión uno no busca “mejor diseño”. Es el mismo comportamiento en un solo lugar.

Mantén la superficie pequeña

Empieza por la unidad más pequeña que se repite. Si cinco funciones normalizan las mismas entradas o construyen el mismo payload, extrae solo esa parte. Deja reglas de validación, reintentos y casos especiales donde están hasta que hayas probado que son realmente compartidos.

Una utilidad pequeña es más fácil de revisar porque hay menos formas de cambiar el comportamiento por accidente. Señales de que la mantuviste pequeña:

  • El nombre describe una acción.
  • Toma entradas explícitas y devuelve un valor.
  • No sabe de rutas, pantallas o tablas de BD.
  • Evita valores por defecto ocultos que adivinan lo que quisiste decir.

Prefiere parámetros explícitos sobre globals

El código generado por IA a menudo accede a globals, variables de entorno o singletons compartidos. Eso hace que la desduplicación sea arriesgada porque cada llamador puede depender de un estado oculto ligeramente distinto.

En su lugar, pasa lo que la utilidad necesita como parámetros. Por ejemplo, pasa baseUrl, headers o timezone. Puede parecer repetitivo al principio, pero hace visibles las diferencias y mantiene la utilidad compartida honesta.

Mantén los efectos secundarios en el llamador

Si el código duplicado hace logging, escribe en BD o envía analíticas, intenta mantener esos efectos fuera de la utilidad compartida. Apunta a “dadas entradas, devuelve salida”. El llamador decide si loguea, guarda o ignora un error.

Ejemplo: si tres endpoints construyen un mensaje de error y además lo registran, extrae solo buildErrorMessage(details). Cada endpoint puede mantener su propio logging para que no cambies por accidente el volumen o el momento de los logs.

Paso a paso: movimientos de refactor pequeños y verificados

Trátalo como una serie de intercambios pequeños, no como una reescritura grande. Quieres el comportamiento de hoy, solo movido a un lugar.

  1. Elige un duplicado “fuente de verdad”.

  2. Copia esa implementación exacta en un nuevo archivo de utilidad compartida. No la limpies todavía. Nada de renombrar, formatear ni “mientras estoy aquí” cambios.

  3. Migra en saltos pequeños:

  • Crea la utilidad compartida copiando una función existente tal cual y mantén los duplicados antiguos en su lugar.
  • Actualiza un único sitio de llamada para usar la nueva utilidad.
  • Ejecuta tus escenarios dorados y compara salidas, logs y efectos secundarios.
  • Repite: mueve otro sitio de llamada, vuelve a ejecutar las mismas comprobaciones.
  • Después de que todos los sitios usen la utilidad, borra los duplicados antiguos y elimina imports sin usar.

Entre cada salto, mantén un commit limpio y revisable. Si algo se rompe, puedes revertir un cambio pequeño en lugar de buscar en un diff gigantesco.

Si los sitios de llamada tienen formas de argumentos ligeramente distintas, usa un wrapper de compatibilidad temporal. Mantén la conversión sucia en el borde (cerca del sitio de llamada), no dentro de la utilidad compartida.

Cómo verificar que el comportamiento sea idéntico

La forma más segura de probar que no cambiaste comportamiento es mantener ambas rutas en paralelo durante un corto tiempo y compararlas con las mismas entradas.

Guarda de 10 a 20 entradas reales que puedas reproducir (fixtures de logs son ideales). Pásalas por la función antigua y por la nueva utilidad en el mismo orden y compara los resultados incluyendo forma y tipos, no solo valores.

No te quedes en el camino feliz. Muchos fallos aparecen solo cuando algo falla. Compara:

  • Mensajes de error y tipos de error (incluyendo redacción si la UI lo muestra)
  • Códigos de estado en respuestas API (200 vs 204 vs 404 importa)
  • Manejo de vacío, null y campos faltantes ("" vs null vs undefined)
  • Orden de ítems (especialmente si ordenas o mapeas claves)
  • Efectos secundarios como logging, reintentos o caching

Si las diferencias son difíciles de detectar, añade un toggle de debug temporal que ejecute ambas rutas y muestre un diff compacto cuando discrepen. Cuando termines la migración, quita el toggle.

Finalmente, vigila el rendimiento en rutas comunes. Cronometra unas pocas llamadas típicas antes y después y confirma que no añadiste consultas DB extra, parseos JSON repetidos o llamadas de red innecesarias.

Trampas comunes que cambian el comportamiento sin darte cuenta

Prepárate para desplegar
Limpiamos problemas de arquitectura y bloqueos de despliegue para que tu app salga con confianza.

La mayoría de los refactors fallan por una razón aburrida: cambiaste dos cosas a la vez. Mantén como objetivo “misma salida para la misma entrada”, no “código más bonito”.

Estas trampas aparecen mucho:

  • Cambiar valores por defecto sin notar (timeout = 0 vs timeout = 30, o undefined tratado distinto que null)
  • Perder coerción de tipos (cadena "0" convirtiéndose en número 0, campo faltante volviéndose cadena vacía)
  • “Limpiar” el manejo de errores de forma que oculte fallos (reemplazar un error lanzado por una advertencia logueada)
  • Construir una utilidad compartida que lo haga todo (crece con flags, casos especiales y estado oculto)
  • Migrar 9 sitios y olvidar el décimo (a menudo un job en background, una pantalla admin o un endpoint raro)

Ejemplo: dos helpers de petición pueden reintentar en errores 500, pero solo uno reintenta en 429 por límite de tasa. Si los fusionas sin preservar esa excepción, un flujo de caja podría volverse más lento o empezar a fallar en picos de tráfico.

Lista rápida de comprobaciones antes de mergear el refactor

Antes de mergear, quieres eliminar la duplicación sin cambiar lo que hace la app.

Comprobaciones de diseño de la utilidad

  • El nuevo helper hace una tarea clara. Si hace dos, sepáralas antes de que crezca.
  • Entradas y salidas previsibles. Evita defaults mágicos que dependan de estado global.
  • El nombre y parámetros coinciden con los términos que la gente ya usa.
  • No lee variables de entorno, archivos, contexto de request o sesión de usuario internamente. Pasa valores.
  • Los errores se manejan igual que antes (tipo, forma del mensaje, código de estado si aplica).

Si dudas sobre un cambio de comportamiento, compara con lo que producía el código antiguo, no con lo que quisieras que produjese.

Comprobaciones de preparación para mergear

  • Cada sitio de llamada está migrado. No quedan archivos a medias usando un duplicado antiguo.
  • Las copias antiguas se eliminan (o están claramente marcadas para eliminación inmediata) para que no puedan desviarse.
  • Los escenarios dorados pasan, incluyendo al menos un camino infeliz (input malo, campo faltante, timeout).
  • Logs, métricas y mensajes de error no se volvieron más ruidosos ni más silenciosos de forma que dificulte depurar.
  • Un diff rápido no muestra secretos o tokens movidos accidentalmente a la utilidad compartida.

Escenario de ejemplo: desduplicando helpers de petición generados por IA

Desenreda un prototipo desordenado
Si tu base de código está enredada, la estabilizamos primero para que los refactors pequeños sean seguros otra vez.

Heridas una app donde una herramienta de IA produjo tres helpers similares: getJson(), postJson() y requestWithRetry(). Cada uno “funciona”, pero cada endpoint llama a uno ligeramente distinto y los bugs aparecen solo en producción.

Al alinearlos notas diferencias importantes: cabeceras (Bearer vs API key, casing), timeouts (5 segundos vs 5000 ms), reglas de reintento (solo red de red vs también 503) y mapeo de errores (devolver { ok: false } vs lanzar vs envolver en message).

En vez de imponer una versión “mejor”, crea un constructor de petición compartido con entradas explícitas, como makeRequest({ method, url, headers, timeoutMs, retryPolicy, mapError }). La clave es que los valores por defecto deben coincidir con el comportamiento actual del primer endpoint que migras, no con lo que desearías que hiciera.

Migra un endpoint primero, preferiblemente uno simple que aún pase por auth y manejo de errores. Mantén el helper antiguo en todo lo demás.

Tus escenarios dorados detectan cambios sutiles rápido. Ejemplo: un endpoint que devolvía 204 No Content solía retornar null, pero el nuevo helper compartido intenta llamar a json() y lanza. El escenario falla inmediatamente y lo arreglas manejando 204 explícitamente.

Próximos pasos si la base de código está demasiado enredada

A veces no puedes desduplicar de forma segura porque los duplicados esconden problemas más profundos. Si cada “función similar” tiene diferentes efectos secundarios, manejo de errores distinto o correcciones de datos silenciosas, una utilidad compartida puede romper flujos reales de usuario.

Una pausa ayuda, pero no necesita ser una reescritura. El objetivo es un reinicio corto que haga posibles los refactors por pasos pequeños otra vez.

Señales para endurecer seguridad mientras refactorizas

Si tocas código compartido, trata estas comprobaciones como no negociables:

  • Autenticación inconsistente (algunas rutas la comprueban y otras la olvidan)
  • Secretos expuestos (API keys en código, logs o bundles del cliente)
  • Entrada de usuario toca SQL o queries sin validación estricta (riesgo de inyección)
  • Lógica “admin” depende de una flag del front-end o de una comprobación de rol débil
  • Mensajes de error filtran detalles internos (stack traces, nombres de tablas)

Arreglar duplicados sin abordar esto puede propagar un patrón riesgoso en tu nueva utilidad.

Mantén el impulso sin reescribir toda la app

Apunta primero a una capa de estabilización delgada: un pequeño conjunto de helpers compartidos con reglas estrictas, además de tests o snapshots alrededor de los flujos más transitados. Luego elimina duplicados por clústeres (todos los helpers de petición, luego todas las comprobaciones de auth). Si un módulo está demasiado hecho un lío, aíslalo detrás de una interfaz simple y pospón la limpieza interna hasta que el resto de la app esté estable.

Si heredaste un prototipo generado por IA defectuoso de herramientas como Lovable, Bolt, v0, Cursor o Replit, una auditoría externa puede ahorrar días de suposiciones. FixMyMess (fixmymess.ai) empieza con una auditoría de código gratuita para descubrir duplicados, diferencias de comportamiento ocultas y problemas de seguridad, y luego ayuda a convertir el prototipo en software listo para producción sin cambiar lo que los usuarios esperan.

Preguntas Frecuentes

¿Qué cuenta exactamente como “copy-paste code"?

Copy-paste code es la misma lógica duplicada en varios archivos con pequeñas diferencias fáciles de pasar por alto. Suele funcionar al principio, pero con el tiempo las correcciones se aplican a una copia y no a las demás, de modo que el comportamiento deriva y aparecen errores.

¿Por qué las apps generadas por IA acaban con tantos duplicados?

Porque el modelo tiende a resolver lo que le pides "ahora mismo", a menudo recrea helpers en lugar de reutilizar lo que ya existe. Si se generan características página a página, terminas con casi-clones que parecen consistentes pero manejan casos límite, valores por defecto o errores de forma distinta.

¿Qué es lo mejor para desduplicar primero?

Empieza por algo pequeño, visible y fácil de describir en una sola frase, como validación de entrada, formateo, un helper de petición a la API o una comprobación de autenticación repetida. Evita tus rutas más críticas al principio para aprender el patrón de refactor con bajo riesgo.

¿Qué debo documentar antes de tocar código duplicado?

Anota las entradas, salidas y efectos secundarios de cada duplicado antes de tocar nada. Incluye detalles que se olvidan: mensajes de error exactos, códigos de estado, logging, caching y cómo se tratan los valores vacíos.

¿Qué son los "escenarios dorados" y cuántos necesito?

Son un conjunto corto de escenarios reales que puedas volver a ejecutar después de cada cambio pequeño para demostrar que el comportamiento se mantuvo. Manténlos prácticos: un éxito común, un caso límite, una falla conocida con un error específico y un caso que cambia comportamiento por flags, cabeceras o entorno.

¿Cómo elijo el duplicado “base” para fusionar en una utilidad compartida?

Elige la versión en la que el resto de la app confía más, no la que se vea más bonita. Si un helper se usa en más sitios o coincide con lo que los usuarios ven en producción, trata eso como el comportamiento que preservas primero.

¿Cuál es la forma más segura de migrar sitios de llamada a la nueva utilidad?

Avanza en pasos pequeños: copia la función base en un archivo compartido sin limpiar nada, migra un único sitio de llamada y vuelve a ejecutar tus escenarios dorados. Commits pequeños y reversibles son la mejor defensa contra cambios accidentales del comportamiento.

¿Por qué los refactors suelen romper el manejo de errores aunque la salida “parezca igual"?

Porque los refactors a menudo cambian cómo fallan las cosas sin que nadie lo note, especialmente entre errores lanzados y objetos devueltos, o frente a respuestas 204/vacías. Tu nuevo helper compartido debe preservar los tipos de error exactos, los mensajes y el manejo de vacíos que los llamadores ya esperan.

¿Dónde deberían vivir el logging, los reintentos y otros efectos secundarios tras la desduplicación?

Mantén efectos secundarios como logging, métricas, escrituras en almacenamiento y analíticas en el llamador siempre que puedas, para que la utilidad compartida sea predecible. Así reduces sorpresas como duplicar el volumen de logs o cambiar cuándo ocurren reintentos.

¿Cuándo debo dejar de refactorizar y pedir una auditoría o ayuda externa?

Si los duplicados esconden problemas de seguridad como comprobaciones de auth inconsistentes, secretos expuestos o manejo inseguro de entradas, unificar puede propagar esos riesgos a una única función compartida. En ese caso, para y busca ayuda externa. FixMyMess puede ejecutar una auditoría gratuita para mapear duplicados, diferencias de comportamiento y riesgos de seguridad, y luego arreglar o reconstruir el código generado por IA rápidamente.