15. Primer proyecto generado con IA

Vamos a transformar una idea pequeña en una aplicación ejecutable mediante una solicitud en lenguaje natural, conservando los archivos y decisiones necesarios para revisar, probar y mejorar el resultado.

15.1 Introducción

Hasta ahora estudiamos qué es el Vibe Coding, cómo interpreta una IA nuestras solicitudes, qué ventajas ofrece y cuáles son sus límites. En este tema aplicaremos esos conceptos a un primer proyecto completo.

Construiremos una lista de tareas que funciona directamente en el navegador. La aplicación será lo bastante pequeña para comprenderla, pero incluirá estructura, estilos, interacción y estado.

El objetivo no es obtener una versión perfecta en el primer intento. Conservaremos la propuesta inicial para revisarla en el tema 16, probarla manualmente en el tema 17 y mejorarla mediante nuevas instrucciones en el tema 18.

El proyecto termina esta etapa cuando existe una primera versión ejecutable. Todavía no afirmaremos que está correcta ni lista para publicar.

15.2 Qué vamos a construir

La aplicación permitirá registrar tareas pendientes y administrar su estado desde una única pantalla.

La primera versión deberá permitir:

  • Escribir una tarea en un campo de texto.
  • Agregarla mediante un botón o la tecla Enter.
  • Mostrar las tareas en una lista.
  • Marcar cada tarea como completada.
  • Eliminar una tarea.
  • Informar cuántas tareas quedan pendientes.
  • Evitar que se agreguen textos vacíos.

Para mantener el alcance inicial, los datos existirán solamente mientras la página permanezca abierta. La persistencia puede considerarse más adelante como una mejora.

15.3 Tecnologías y restricciones

Definir las herramientas antes de generar evita que la IA elija dependencias innecesarias.

Elemento Decisión Motivo
Estructura HTML semántico Puede abrirse directamente en un navegador.
Presentación CSS sin bibliotecas Permite observar los estilos sin abstracciones externas.
Comportamiento JavaScript del navegador Es suficiente para la interacción elegida.
Datos Memoria durante la sesión Evita agregar almacenamiento en la primera entrega.
Dependencias Ninguna Reduce instalación, configuración y riesgos.

La aplicación estará dividida en tres archivos. Esta separación hace visible qué corresponde a contenido, presentación y comportamiento.

15.4 Criterios de aceptación

Antes de solicitar código escribimos ejemplos que permitirán comprobar el resultado:

  1. Al abrir la página, la lista está vacía y el contador indica cero tareas pendientes.
  2. Al escribir “Estudiar JavaScript” y presionar Agregar, aparece una nueva tarea.
  3. El campo queda vacío y listo para escribir otra tarea.
  4. Al marcar la tarea, su aspecto cambia y el contador disminuye.
  5. Al desmarcarla, vuelve a estar pendiente y el contador aumenta.
  6. Al eliminarla, desaparece de la lista.
  7. Un texto compuesto solo por espacios no se agrega.

Estos criterios no describen cada detalle visual, pero definen el comportamiento mínimo que observaremos.

15.5 Preparar la carpeta del proyecto

Creamos una carpeta llamada lista-tareas con la siguiente estructura:

lista-tareas/
├── index.html
├── styles.css
└── app.js

Si utilizamos una herramienta capaz de editar archivos, podemos abrir esa carpeta como proyecto. Si trabajamos mediante chat, copiaremos cada bloque en el archivo correspondiente.

No agregamos todavía imágenes, paquetes ni configuración. Cuantas menos piezas tenga la primera versión, más fácil será relacionar una solicitud con el código producido.

15.6 Redactar la solicitud inicial

La solicitud reúne objetivo, tecnologías, funciones, estructura y algunas restricciones:

Creá una aplicación de lista de tareas utilizando HTML semántico, CSS y JavaScript sin bibliotecas externas. Separá el proyecto en index.html, styles.css y app.js. Debe permitir agregar tareas con un botón o Enter, marcarlas como completadas, eliminarlas y mostrar cuántas quedan pendientes. No debe aceptar tareas vacías. Los datos pueden mantenerse solo en memoria. Usá nombres claros y no agregues funciones fuera de este alcance.

La instrucción evita pedir una aplicación “completa”, porque esa palabra podría llevar a incorporar usuarios, servidores o almacenamiento. También indica explícitamente qué queda fuera.

15.7 Revisar el plan antes del código

Si la herramienta puede planificar, conviene pedir que describa primero la estructura esperada. Una respuesta razonable sería:

  1. Crear un formulario con campo de texto y botón.
  2. Agregar una lista para representar las tareas.
  3. Mantener un arreglo de objetos en JavaScript.
  4. Renderizar la lista después de cada modificación.
  5. Escuchar el envío del formulario, los cambios de estado y los botones de eliminación.
  6. Calcular el contador a partir de tareas no completadas.

El plan coincide con el alcance y no incorpora dependencias. Por eso autorizamos la generación de los tres archivos.

15.8 Código generado para index.html

La propuesta inicial utiliza un formulario, una lista y una región para el contador:

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Lista de tareas</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <main class="aplicacion">
    <h1>Lista de tareas</h1>

    <form id="formulario-tarea" class="formulario">
      <label for="texto-tarea">Nueva tarea</label>
      <div class="formulario__fila">
        <input
          id="texto-tarea"
          name="tarea"
          type="text"
          placeholder="Ejemplo: estudiar JavaScript"
          autocomplete="off"
        >
        <button type="submit">Agregar</button>
      </div>
    </form>

    <p id="contador" class="contador" aria-live="polite">
      0 tareas pendientes
    </p>

    <ul id="lista-tareas" class="lista-tareas"></ul>
  </main>

  <script src="app.js"></script>
</body>
</html>

El formulario permite que Enter y el botón utilicen el mismo evento. La lista comienza vacía porque los elementos serán creados desde JavaScript.

15.9 Código generado para styles.css

Los estilos crean una tarjeta central y diferencian visualmente una tarea completada:

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
  font-family: Arial, sans-serif;
  color: #1f2937;
  background: #eef2ff;
}

.aplicacion {
  width: min(100%, 640px);
  padding: 32px;
  border-radius: 18px;
  background: #ffffff;
  box-shadow: 0 18px 45px rgba(30, 41, 59, 0.14);
}

h1 {
  margin-top: 0;
  color: #312e81;
}

.formulario label {
  display: block;
  margin-bottom: 8px;
  font-weight: 700;
}

.formulario__fila {
  display: flex;
  gap: 10px;
}

.formulario input {
  flex: 1;
  min-width: 0;
  padding: 12px;
  border: 1px solid #a5b4fc;
  border-radius: 8px;
  font: inherit;
}

button {
  padding: 10px 16px;
  border: 0;
  border-radius: 8px;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

.formulario button {
  color: #ffffff;
  background: #4f46e5;
}

.contador {
  margin: 24px 0 12px;
  color: #475569;
}

.lista-tareas {
  display: grid;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.tarea {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
}

.tarea__texto {
  flex: 1;
  overflow-wrap: anywhere;
}

.tarea--completada .tarea__texto {
  color: #64748b;
  text-decoration: line-through;
}

.tarea__eliminar {
  color: #991b1b;
  background: #fee2e2;
}

button:focus-visible,
input:focus-visible {
  outline: 3px solid #f59e0b;
  outline-offset: 2px;
}

@media (max-width: 520px) {
  .aplicacion {
    padding: 22px;
  }

  .formulario__fila {
    flex-direction: column;
  }
}

Los estilos incluyen una adaptación básica para pantallas pequeñas y un foco visible para controles utilizados mediante teclado.

15.10 Código generado para app.js

JavaScript conserva un arreglo de tareas y vuelve a representar la lista después de cada cambio:

const formulario = document.querySelector("#formulario-tarea");
const campoTexto = document.querySelector("#texto-tarea");
const lista = document.querySelector("#lista-tareas");
const contador = document.querySelector("#contador");

let tareas = [];
let proximoId = 1;

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

function crearElementoTarea(tarea) {
  const elemento = document.createElement("li");
  elemento.className = "tarea";
  elemento.dataset.id = tarea.id;

  if (tarea.completada) {
    elemento.classList.add("tarea--completada");
  }

  const casilla = document.createElement("input");
  casilla.type = "checkbox";
  casilla.checked = tarea.completada;
  casilla.setAttribute("aria-label", `Completar ${tarea.texto}`);

  const texto = document.createElement("span");
  texto.className = "tarea__texto";
  texto.textContent = tarea.texto;

  const botonEliminar = document.createElement("button");
  botonEliminar.type = "button";
  botonEliminar.className = "tarea__eliminar";
  botonEliminar.textContent = "Eliminar";
  botonEliminar.setAttribute("aria-label", `Eliminar ${tarea.texto}`);

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

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

  elemento.append(casilla, texto, botonEliminar);
  return elemento;
}

function renderizarTareas() {
  lista.replaceChildren();

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

  actualizarContador();
}

formulario.addEventListener("submit", evento => {
  evento.preventDefault();
  const texto = campoTexto.value.trim();

  if (!texto) {
    campoTexto.focus();
    return;
  }

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

  proximoId += 1;
  campoTexto.value = "";
  campoTexto.focus();
  renderizarTareas();
});

renderizarTareas();

La propiedad textContent incorpora el texto como contenido y no como HTML. El identificador permite eliminar una tarea específica aunque existan dos con el mismo texto.

15.11 Relacionar requisitos y código

Antes de ejecutar podemos ubicar dónde aparece cada función solicitada:

Requisito Implementación
Agregar con botón o Enter Evento submit del formulario.
Evitar texto vacío trim() y condición if (!texto).
Marcar como completada Casilla, propiedad completada y clase CSS.
Eliminar Botón y filtrado por identificador.
Contar pendientes Filtro de tareas no completadas.
Mantener datos en memoria Arreglo tareas sin almacenamiento externo.

Esta trazabilidad inicial no demuestra que la implementación sea perfecta. Confirma que la propuesta contiene una respuesta visible para cada requisito.

15.12 Ejecutar la primera versión

Como el proyecto no utiliza servidor ni dependencias, podemos abrir index.html en un navegador. También es posible utilizar un servidor local simple si nuestro editor lo proporciona.

En esta etapa realizamos una comprobación mínima:

  1. Abrir la página y comprobar que no aparece un error visible.
  2. Escribir una tarea y presionar Agregar.
  3. Marcarla como completada.
  4. Desmarcarla.
  5. Eliminarla.
  6. Observar el contador después de cada acción.

También abrimos la consola del navegador para detectar errores de JavaScript. Una pantalla visible no garantiza que el programa se haya ejecutado sin problemas.

15.13 Qué información registrar

Para revisar y mejorar necesitamos conservar el punto de partida. Registramos:

  • La solicitud exacta utilizada.
  • Los tres archivos generados.
  • Las decisiones que ya estaban definidas.
  • Los criterios de aceptación.
  • La herramienta y el entorno de ejecución.
  • Las primeras observaciones, sin corregir todavía.

Si utilizamos control de versiones, este es un buen momento para guardar el estado inicial. Así podremos comparar cambios posteriores y recuperar la primera propuesta.

15.14 Límites visibles de la primera versión

Sin realizar aún una revisión completa, podemos reconocer decisiones que quedaron fuera de alcance:

  • Las tareas desaparecen al recargar la página.
  • No existe edición del texto.
  • No hay filtros para pendientes y completadas.
  • No se establece una longitud máxima.
  • El contador utiliza siempre la palabra “tareas”.
  • No se muestra un estado vacío específico.

No todos estos puntos son errores: varios fueron excluidos de la solicitud inicial. Diferenciar defecto de función no solicitada evita ampliar el proyecto sin una decisión consciente.

Una primera versión puede ser correcta para su alcance y, al mismo tiempo, incompleta para necesidades futuras.

15.15 Resultado de esta etapa

Partimos de una idea y obtuvimos una aplicación compuesta por tres archivos. La solicitud definió tecnologías, alcance y comportamiento; la IA propuso una estructura y generó una implementación ejecutable.

Hasta este punto podemos afirmar:

  • Los archivos fueron creados con la estructura solicitada.
  • La aplicación puede abrirse en un navegador.
  • Existe una implementación para cada requisito inicial.
  • El flujo principal puede recorrerse manualmente.

Todavía no evaluamos cada línea, todos los límites ni la experiencia completa. Esa separación es importante: generar, revisar, probar y mejorar son actividades relacionadas, pero no idénticas.

15.16 Actividad de comprensión

Reproducí el proyecto en una carpeta propia utilizando la solicitud de la sección 15.6. La respuesta de tu herramienta puede ser diferente de la mostrada en este tema.

  1. Guardá la solicitud y los archivos obtenidos.
  2. Comprobá que la aplicación abre sin errores visibles.
  3. Relacioná cada criterio de aceptación con una parte del código.
  4. Anotá las diferencias respecto de la versión presentada aquí.
  5. Identificá tres decisiones que la IA tomó sin que fueran especificadas.
  6. Registrá problemas o mejoras posibles, pero no los corrijas todavía.

El material producido será la entrada para el próximo tema, donde revisaremos sistemáticamente el código antes de confiar en su funcionamiento.