18. Mejora del proyecto mediante nuevas instrucciones

Mejorar con IA significa convertir evidencia en cambios pequeños, indicar qué debe conservarse y comprobar cada nueva versión antes de continuar con la siguiente función.

18.1 Introducción

La primera versión de la lista de tareas permitió recorrer el ciclo completo del Vibe Coding: describimos una idea, generamos archivos, revisamos el código y ejecutamos pruebas manuales.

La revisión y las pruebas confirmaron dos problemas: el contador utiliza plural cuando queda una sola tarea y la entrada inválida se rechaza sin explicación. También registramos una limitación conocida: las tareas desaparecen al recargar.

Ahora convertiremos esos hallazgos en nuevas instrucciones. No pediremos “mejorá toda la aplicación”, porque una solicitud abierta mezclaría decisiones y dificultaría comprobar qué produjo cada modificación.

Iterar no es repetir el pedido original. Es utilizar lo aprendido para formular el siguiente cambio con más precisión.

18.2 Construir una lista priorizada

Antes de volver a conversar con la IA, ordenamos los hallazgos según su naturaleza e impacto.

Elemento Tipo Prioridad Decisión
“1 tareas pendientes” Defecto confirmado Alta Corregir primero.
Entrada inválida sin mensaje Experiencia y accesibilidad Alta Corregir junto con su prueba.
Pérdida al recargar Mejora funcional Media Agregar en una segunda iteración.
Ausencia de estado vacío Mejora de experiencia Baja Agregar después de persistencia.
Tareas duplicadas Decisión pendiente Sin definir No modificar sin requisito.
Edición y filtros Fuera del alcance actual Futura No incorporar ahora.

La prioridad no depende de cuán interesante sea una función. Primero corregimos comportamientos confirmados y luego agregamos valor nuevo.

18.3 Anatomía de una instrucción de mejora

Una instrucción para modificar código existente debería incluir:

  • Estado actual: qué archivos y comportamiento existen.
  • Evidencia: qué caso produjo un resultado incorrecto.
  • Resultado esperado: cómo debe comportarse después.
  • Alcance: qué puede cambiar y qué debe conservarse.
  • Restricciones: tecnologías, dependencias y convenciones.
  • Verificación: qué casos deben ejecutarse.

Una solicitud precisa permite revisar la respuesta contra una intención explícita. También ayuda a detectar cuando la IA agrega refactorizaciones o funciones no pedidas.

18.4 Primera instrucción: corregir defectos confirmados

Modificá la lista de tareas existente sin cambiar su estructura general ni agregar dependencias. Corregí el contador para mostrar “1 tarea pendiente” cuando el valor sea uno y “0 tareas pendientes” o “N tareas pendientes” en los demás casos. Cuando se intente enviar un texto vacío o compuesto solo por espacios, mostrale a la persona el mensaje “Escribí una tarea antes de agregarla”, asocialo al campo y hacé que pueda ser anunciado por tecnologías de asistencia. Ocultá el mensaje después de una entrada válida. Conservá agregar con Enter, completar, eliminar y el foco visible. Indicá los archivos y fragmentos modificados.

La instrucción combina dos correcciones pequeñas relacionadas con mensajes de interfaz. Define textos exactos y comportamientos que pueden probarse.

No solicita persistencia todavía. Separar esa mejora evita atribuir un posible error de almacenamiento a la corrección del contador.

18.5 Cambio propuesto en index.html

La IA puede asociar el campo con una región de error mediante aria-describedby:

<input
  id="texto-tarea"
  name="tarea"
  type="text"
  placeholder="Ejemplo: estudiar JavaScript"
  autocomplete="off"
  aria-describedby="mensaje-error"
>

<p id="mensaje-error" class="mensaje-error" aria-live="polite" hidden>
  Escribí una tarea antes de agregarla
</p>

El atributo hidden evita mostrar el mensaje en el estado inicial. aria-live="polite" permite anunciar el cambio sin interrumpir de forma brusca.

Durante la revisión confirmamos que el identificador sea único y que aria-describedby apunte exactamente al elemento agregado.

18.6 Cambios propuestos en styles.css y app.js

Agregamos una presentación sencilla para el mensaje:

.mensaje-error {
  margin: 8px 0 0;
  color: #b91c1c;
  font-weight: 700;
}

En JavaScript seleccionamos la región y centralizamos su estado:

const mensajeError = document.querySelector("#mensaje-error");

function mostrarError(mensaje) {
  mensajeError.textContent = mensaje;
  mensajeError.hidden = false;
  campoTexto.setAttribute("aria-invalid", "true");
}

function ocultarError() {
  mensajeError.hidden = true;
  campoTexto.removeAttribute("aria-invalid");
}

La función del contador queda vinculada con la cantidad:

function actualizarContador() {
  const pendientes = tareas.filter(tarea => !tarea.completada).length;
  contador.textContent = pendientes === 1
    ? "1 tarea pendiente"
    : `${pendientes} tareas pendientes`;
}

18.7 Integrar la validación en el envío

Modificamos solamente la parte del evento relacionada con la validación:

const texto = campoTexto.value.trim();

if (!texto) {
  mostrarError("Escribí una tarea antes de agregarla");
  campoTexto.focus();
  return;
}

ocultarError();

tareas.push({
  id: proximoId,
  texto,
  completada: false
});

La posición de ocultarError() es importante: se ejecuta después de confirmar que la nueva entrada es válida y antes de continuar con el alta.

No eliminamos trim(), el foco ni la validación anterior. La mejora agrega comunicación sin debilitar el control existente.

18.8 Verificar la primera iteración

Repetimos pruebas relacionadas y también casos que ya funcionaban.

Caso Resultado esperado
Lista vacía “0 tareas pendientes” y mensaje de error oculto.
Agregar una tarea “1 tarea pendiente”.
Agregar dos tareas “2 tareas pendientes”.
Enviar vacío Mensaje visible, campo inválido y ninguna tarea nueva.
Enviar espacios Mismo resultado que vacío.
Corregir la entrada Se agrega y el mensaje desaparece.
Completar y eliminar Los flujos anteriores continúan funcionando.

Solo después de aprobar esta iteración avanzamos hacia persistencia.

18.9 Segunda instrucción: conservar las tareas

La nueva función requiere varias decisiones. Para este proyecto elegimos localStorage, almacenamiento disponible en el navegador para pares de clave y texto.

Sobre la versión corregida, agregá persistencia en localStorage sin dependencias. Guardá el arreglo completo después de agregar, completar, descompletar o eliminar. Al iniciar, recuperá únicamente una lista válida; si no existe o el contenido está dañado, comenzá con una lista vacía sin interrumpir la aplicación. Calculá proximoId a partir del identificador mayor recuperado. Conservá todas las funciones y mensajes actuales. Mostrá primero un plan y luego los cambios mínimos en app.js.

La instrucción define cuándo guardar, qué hacer ante datos dañados y cómo continuar la secuencia de identificadores. Sin esas condiciones, la IA tendría que decidirlas.

18.10 Diseñar el almacenamiento

localStorage conserva texto. Para almacenar el arreglo utilizamos JSON:

Operación Transformación
Guardar Objeto JavaScript → JSON.stringify → texto.
Cargar Texto → JSON.parse → valor JavaScript.
Validar Comprobar que el valor recuperado sea una lista.
Recuperarse Ante ausencia o error, utilizar una lista vacía.

El almacenamiento es local al navegador y al origen de la página. No sincroniza dispositivos, no reemplaza una base de datos y puede ser eliminado por la persona.

18.11 Funciones para cargar y guardar

Una propuesta posible concentra el acceso al almacenamiento:

const CLAVE_TAREAS = "lista-tareas";

function cargarTareas() {
  try {
    const datos = localStorage.getItem(CLAVE_TAREAS);

    if (!datos) {
      return [];
    }

    const resultado = JSON.parse(datos);
    return Array.isArray(resultado) ? resultado : [];
  } catch (error) {
    console.warn("No se pudieron recuperar las tareas", error);
    return [];
  }
}

function guardarTareas() {
  localStorage.setItem(CLAVE_TAREAS, JSON.stringify(tareas));
}

El bloque try...catch evita que un JSON inválido impida iniciar la aplicación. La validación todavía es básica: confirma la colección, pero no cada propiedad interna. Para el alcance didáctico la registramos como una limitación conocida.

18.12 Inicializar y guardar después de cada cambio

Reemplazamos el estado inicial vacío:

let tareas = cargarTareas();
let proximoId = tareas.reduce(
  (mayor, tarea) => Math.max(mayor, Number(tarea.id) || 0),
  0
) + 1;

Después de cada modificación del arreglo o de una propiedad agregamos guardarTareas() antes de volver a representar:

casilla.addEventListener("change", () => {
  tarea.completada = casilla.checked;
  guardarTareas();
  renderizarTareas();
});

botonEliminar.addEventListener("click", () => {
  tareas = tareas.filter(item => item.id !== tarea.id);
  guardarTareas();
  renderizarTareas();
});

El alta requiere la misma llamada después de tareas.push(...). Si olvidamos uno de los caminos, la interfaz parecerá correcta hasta recargar.

18.13 Tercera instrucción: estado vacío

Una vez estable la persistencia, agregamos una mejora visual pequeña:

Agregá el texto “Todavía no hay tareas” cuando la lista esté vacía. Debe desaparecer al agregar la primera tarea y volver después de eliminar la última. Utilizá un elemento HTML independiente de la lista y actualizalo desde renderizarTareas. No cambies almacenamiento, validación ni controles existentes. Incluí tres casos de prueba.

En HTML incorporamos:

<p id="estado-vacio" class="estado-vacio">
  Todavía no hay tareas
</p>

Y actualizamos su visibilidad durante el renderizado:

const estadoVacio = document.querySelector("#estado-vacio");

function renderizarTareas() {
  lista.replaceChildren();

  tareas.forEach(tarea => {
    lista.append(crearElementoTarea(tarea));
  });

  estadoVacio.hidden = tareas.length > 0;
  actualizarContador();
}

18.14 Pruebas de regresión de la versión mejorada

Las funciones nuevas amplían el conjunto de casos. También repetimos los anteriores.

Grupo Casos principales
Flujo original Agregar con botón y Enter, completar, desmarcar y eliminar.
Contador Cero, uno, varios y transiciones después de completar.
Validación Vacío, espacios, mensaje, foco y corrección posterior.
Persistencia Recargar después de alta, cambio de estado y eliminación.
Recuperación Clave ausente y contenido JSON inválido.
Identificadores Agregar después de recuperar tareas existentes.
Estado vacío Inicio vacío, primera alta y eliminación de la última.
Accesibilidad Teclado, foco, nombres de controles y anuncios.

Si una prueba falla, detenemos la incorporación de nuevas funciones. Primero reducimos el problema, corregimos y repetimos el grupo relacionado.

18.15 Lecciones del ciclo completo

El proyecto muestra que el Vibe Coding es un proceso iterativo y no una única generación:

  1. Definimos una necesidad pequeña.
  2. Escribimos criterios antes de pedir código.
  3. Generamos una primera versión limitada.
  4. Relacionamos requisitos con implementación.
  5. Revisamos estructura, lógica, accesibilidad y seguridad.
  6. Ejecutamos casos manuales y registramos evidencia.
  7. Priorizamos defectos y mejoras por separado.
  8. Formulamos instrucciones específicas.
  9. Revisamos diferencias y repetimos pruebas.

La IA aceleró la producción y propuso implementaciones. La calidad surgió de combinar esa capacidad con decisiones humanas, límites, lectura y verificación.

El mejor siguiente pedido no nace de imaginar más funciones, sino de observar la versión actual y elegir el cambio de mayor valor que podamos comprobar.

18.16 Actividad final

Aplicá un ciclo completo de mejora sobre la versión de tu proyecto.

  1. Elegí un defecto confirmado en tus pruebas y una mejora fuera del alcance inicial.
  2. Priorizá el defecto y escribí su resultado esperado.
  3. Redactá una instrucción que incluya contexto, alcance, restricciones y verificación.
  4. Revisá todos los cambios propuestos antes de ejecutarlos.
  5. Probá la corrección y repetí los casos relacionados.
  6. Guardá esa versión antes de solicitar la mejora funcional.
  7. Repetí generación, revisión y prueba para la segunda iteración.
  8. Prepará un informe final con funciones, limitaciones conocidas y posibles pasos futuros.

Para cerrar, explicá con tus palabras qué decisiones tomó la IA, cuáles tomaste vos y qué evidencia permite afirmar que la versión mejorada cumple su objetivo.