La explicación dice que el array y cada elemento son nuevos, así que modificar el resultado no debería afectar a la entrada. Sin embargo, cuando quien llama convierte un label a mayúsculas, el original también cambia. La copia superficial conservó la referencia al objeto anidado.

Descarga el laboratorio de vista previa de paquetes. Su código, sus datos y su explicación deliberadamente incorrecta se crearon desde cero para este tutorial. Usa Node.js 22 o posterior. No se necesita instalación, credenciales ni acceso a la red.

Mantén la afirmación junto al código

Comprueba si modificar el label de un parcel copiado también cambia la entrada. Lee juntas la función de copia y la que la llama.

Abre la carpeta extraída parcel-preview-lab en Yenpo. Usa Command-P (Go to File) para abrir claims.md, src/copy-parcels.mjs y src/preview.mjs en pestañas. Contrasta la afirmación con la copia y con su llamador. Search All Files permite localizar row.label.text; inspecciona la condición uppercase que lo rodea, en lugar de tomar una línea coincidente como prueba de que todas las llamadas modifican la entrada.

Este paso de lectura no necesita conexión a un modelo ni servidor de lenguaje. Descarga Yenpo para Mac para abrir el mismo ejemplo, o consulta la ayuda de búsqueda de archivos y texto si pierdes la ubicación. Ejecuta tú mismo el observador y las comprobaciones siguientes en Terminal y registra las pruebas en tu nota.

Divide la explicación en afirmaciones

Esta es la explicación didáctica que comprobaremos:

copyParcels crea un array nuevo y un objeto nuevo para cada paquete. Sus etiquetas son copias independientes, así que preview puede convertirlas a mayúsculas sin modificar la entrada.

La primera frase contiene dos afirmaciones. La segunda añade una afirmación sobre la propiedad de los datos y una conclusión sobre el comportamiento. Tratar el párrafo como una única respuesta «correcta o incorrecta» haría perder las partes útiles junto con el error.

ID Afirmación ¿Qué la demostraría o refutaría?
C1 El array devuelto es nuevo. Comparar las identidades de los arrays de entrada y salida.
C2 Cada objeto exterior de paquete es nuevo. Comparar las identidades de los paquetes correspondientes.
C3 Cada etiqueta anidada es independiente. Comparar las identidades de las etiquetas, no solo su texto.
C4 Convertir la vista previa a mayúsculas conserva la entrada. Alcanzar esa rama e inspeccionar después la entrada del llamador.
C5 Cualquier mutación de la entrada es un error de negocio. Encontrar un requisito o contrato de propiedad; el código por sí solo no proporciona ninguno.

C5 es una posible conclusión añadida por el lector, no parte de la explicación citada. Mantenla separada para que refutar C4 no determine silenciosamente el requisito del producto.

Nuestro alcance es un array denso de objetos de paquete ordinarios y mutables, cada uno con un objeto simple label y una cadena text. El laboratorio no cubre getters, proxies, instancias de clases ni E/S externa. Conserva ese alcance junto a la explicación, sin generalizar la ejecución a todos los valores de JavaScript.

Sigue la referencia más allá de la función

Abre src/copy-parcels.mjs:

export function copyParcels(parcels) {
  return parcels.map((parcel) => ({ ...parcel }));
}

El literal de objeto crea un objeto exterior nuevo en cada invocación del callback. Copia el valor de la propiedad label, que es una referencia a un objeto. No crea una etiqueta anidada nueva. MDN documenta esta distinción en sintaxis de propagación y copia superficial.

Ahora abre el llamador real, src/preview.mjs:

import { copyParcels } from './copy-parcels.mjs';

export function preview(parcels, { uppercase = false } = {}) {
  const rows = copyParcels(parcels);
  if (uppercase) {
    for (const row of rows) {
      row.label.text = row.label.text.toUpperCase();
    }
  }
  return rows;
}

La escritura relevante es en row.label.text. Reasignar row.id afectaría al objeto exterior nuevo; asignar mediante row.label alcanza el objeto anidado compartido. Para los datos de prueba, la estructura de referencias inmediatamente después de copiar es:

input ──> original array ──> original parcel ──┐
                                             ├──> one label object
rows  ──> new array ──────> new parcel ────────┘

Encontrar esta asignación no basta para explicar una ejecución concreta. La rama predeterminada la omite. Para probar C4, proporciona uppercase: true y un texto cuya forma en mayúsculas difiera del valor actual.

Ejecuta la rama que puede contradecir la explicación

Descomprime la descarga, entra en parcel-preview-lab y ejecuta:

node observe.mjs

El observador crea datos de prueba nuevos para cada rama. Guarda el texto original como cadena antes de llamar a preview y después imprime identidades y valores. Estos son los resultados:

Observación uppercase: false uppercase: true
¿Mismo array? false false
¿Mismo objeto exterior de paquete? false false
¿Misma etiqueta anidada? true true
Texto guardado antes de la llamada fragile fragile
Texto de entrada después de la llamada fragile FRAGILE
Texto de vista previa después de la llamada fragile FRAGILE

Este es un contraejemplo alcanzable de C4: el llamador pasa la entrada, selecciona la rama de mayúsculas y puede observar cómo cambia su propio texto anidado. C1 y C2 siguen siendo ciertas. C3 ya queda refutada por la identidad compartida de la etiqueta, incluso en la rama que no hace ninguna asignación.

Una etiqueta que ya está en mayúsculas es una prueba menos concluyente: la escritura sigue ocurriendo, pero el valor anterior y posterior es el mismo. Un array vacío nunca entra en el bucle. Esas entradas pueden superar una comprobación de «el texto no cambió» sin demostrar una propiedad independiente. Las pruebas de evidencia incluyen ambos controles.

No confundas valores iguales con objetos independientes

Antes de la escritura, esta aserción pasa:

assert.deepEqual(rows, input);

Eso indica que la estructura y los valores comparados coinciden. No indica que los objetos anidados correspondientes tengan identidades distintas. Estas tres aserciones también pasan al mismo tiempo:

assert.notStrictEqual(rows, input);
assert.notStrictEqual(rows[0], input[0]);
assert.strictEqual(rows[0].label, input[0].label);

El laboratorio importa assert de node:assert/strict. Consulta la referencia de aserciones de Node para conocer la semántica de las comparaciones. Conserva ambos tipos de evidencia: las identidades describen qué se comparte, mientras que los valores anteriores y posteriores muestran la consecuencia de una escritura alcanzable.

No uses const before = input como estado «anterior» guardado. Esa variable seguiría apuntando al mismo array y también podría mostrar el valor modificado. El observador guarda la cadena primitiva beforeText antes de la llamada. Un valor capturado y una referencia a un objeto mutable responden a preguntas distintas.

Convierte el resultado en una explicación corregida

Ejecuta estos comandos por separado; se espera deliberadamente que el primero falle:

node --test checks/original-claims.test.mjs
node --test checks/evidence.test.mjs

La ejecución de las afirmaciones originales tiene dos pruebas que pasan y dos que fallan, y termina con un estado distinto de cero. C1 y C2 pasan; las aserciones de etiquetas independientes y entrada conservada fallan. La ejecución de evidencia tiene seis pruebas que pasan y termina con cero. La documentación del ejecutor de pruebas de Node describe cómo ejecutar estos archivos. El código de la aplicación no cambia entre ejecuciones: las aserciones expresan afirmaciones distintas sobre el mismo programa.

Registra la conclusión y su límite, no solo un resultado verde o rojo:

ID Conclusión Evidencia y límite
C1, C2 Respaldadas dentro del alcance Array y paquetes exteriores nuevos: coinciden map, el literal de objeto y las comprobaciones de identidad.
C3 Refutada Las etiquetas anidadas se comparten: entrada y salida contienen la misma referencia.
C4 Refutada Las mayúsculas modifican la entrada: fragile pasa a ser FRAGILE en la rama verdadera.
C5 Sin determinar No se demuestra un incumplimiento de requisitos: no se proporcionó un contrato de propiedad.

Una explicación corregida sería:

copyParcels crea un array nuevo y objetos exteriores de paquete nuevos, conservando la referencia de cada etiqueta anidada. Con uppercase: false, preview no asigna texto. Con uppercase: true, escribe a través de la etiqueta compartida. Para los datos de prueba fragile, la entrada del llamador pasa a ser FRAGILE. Que esa mutación sea aceptable depende del contrato de propiedad.

El ZIP incluye este registro en claims.md, el observador, ambos archivos de pruebas y un manifiesto de hashes de archivos.

Devuelve la evidencia a la tarea de lectura

Guarda la versión del código, la explicación revisada y la entrada y salida de la prueba. Después pregunta: «Los dos label comparten un objeto y la conversión a mayúsculas cambia el original. Reescribe la explicación teniendo esto en cuenta». Contrasta la nueva respuesta con el mismo código.

AI Code Reader de Yenpo permite explorar código local, plantear preguntas específicas y abrir referencias mientras sigues a los llamadores. Ejecuta el laboratorio y tus comprobaciones en Terminal o en tus herramientas habituales; Yenpo no ejecuta comandos de shell ni pruebas por ti.

Si la siguiente decisión es cambiar este comportamiento, establece primero el requisito. Para un cambio propuesto real, continúa con la revisión de código generado por IA mediante un contraejemplo. Si necesitas registrar un comportamiento desconocido antes de refactorizar, usa pruebas de caracterización.