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.
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
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.
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:
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:
- Definimos una necesidad pequeña.
- Escribimos criterios antes de pedir código.
- Generamos una primera versión limitada.
- Relacionamos requisitos con implementación.
- Revisamos estructura, lógica, accesibilidad y seguridad.
- Ejecutamos casos manuales y registramos evidencia.
- Priorizamos defectos y mejoras por separado.
- Formulamos instrucciones específicas.
- 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.
18.16 Actividad final
Aplicá un ciclo completo de mejora sobre la versión de tu proyecto.
- Elegí un defecto confirmado en tus pruebas y una mejora fuera del alcance inicial.
- Priorizá el defecto y escribí su resultado esperado.
- Redactá una instrucción que incluya contexto, alcance, restricciones y verificación.
- Revisá todos los cambios propuestos antes de ejecutarlos.
- Probá la corrección y repetí los casos relacionados.
- Guardá esa versión antes de solicitar la mejora funcional.
- Repetí generación, revisión y prueba para la segunda iteración.
- 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.