Camino de yuwen-c

Cuando las especificaciones no bastan, empieza por un prototipo

#ai #prototype #workflow notes

A estas alturas, supongo que cualquiera que trabaje con desarrollo asistido por IA ya tendrá sus propias conclusiones. Cuando me toca arrancar un proyecto nuevo, lo que había probado hasta ahora era más o menos lo de siempre: escribir primero la documentación, definir las funcionalidades, la matriz de permisos, dibujar diagramas de secuencia en Mermaid, etc. Y después, dejar que la IA empiece a construir. Pero muy a menudo el resultado se parece bastante poco a lo que tenía en la cabeza. También había probado con prototipos de pantallas estáticas, más o menos como empezar con wireframes, pero para productos donde la interacción importa de verdad no me terminaba de encajar.

Hace poco probé otra forma de trabajar: hacer primero un prototipo con el que se pueda interactuar, pero sin conectarlo al backend. Es decir, con datos, con interacción y con un user flow real, pero sin conectarlo al backend de verdad ni a la base de datos definitiva. En su lugar, todo queda guardado en el navegador con IndexedDB.

Me di cuenta de que esto resolvía dos problemas con los que me topo muy a menudo:

  • Intentar definir todas las especificaciones de golpe, desde el principio hasta el último detalle, no encaja demasiado con la forma en que pensamos. Lo más natural suele ser tener primero una estructura general y, después de ver pantallas reales y probar el flujo unas cuantas veces, ir descubriendo poco a poco detalles y casos excepcionales en los que no habías caído antes.
  • Si en frontend solo te apoyas en documentación escrita y aun así quieres que la IA saque algo que se parezca a lo que esperas, dependes muchísimo de lo bien que esté escrita la especificación.

Lo que me gusta de esta forma de trabajar es:

  • Mientras desarrollas el prototipo, el hecho de ver todo en pantalla ayuda a pensar mejor el flujo y a dejarlo más definido. Al final, somos bastante visuales: con una interfaz delante, avanzar es mucho más fácil que trabajar solo con texto.
  • También resulta mucho más cómodo para hablarlo con el equipo o con usuarios, porque ellos mismos pueden probar el flujo y ver si realmente funciona.
  • Y lo más importante: el propio prototipo acaba convirtiéndose en una especie de documento escrito en código, donde quedan definidos la forma de uso, los límites y muchos detalles. Frente a una especificación puramente textual, los requisitos pasan a estar descritos con mucha más precisión.

Esta vez probé este enfoque mientras trabajaba en una plataforma de gestión de eventos. Las funcionalidades eran, más o menos, las que cualquiera se imaginaría: creación de eventos, registro de usuarios, check-in presencial, cumplimentación de cuestionarios y estadísticas.

Este tipo de sistema tiene una característica que viene muy bien para validar este método: dentro de un mismo producto conviven distintos roles, como quien organiza el evento, quien se inscribe o el personal de apoyo in situ. Cada rol ve pantallas, datos y acciones distintas. Por eso, si el prototipo permite cambiar de rol directamente, resulta mucho más fácil comprobar si esos distintos escenarios de uso tienen sentido o no.

Entonces, ¿cómo monto este prototipo? Suelo empezar por la funcionalidad más central del producto y, a partir de ahí, definir:

  • Un layout inicial, por ejemplo el esqueleto de páginas con Nav Bar, Left Menu, Tab Bar, etc.
  • La estructura general de las rutas
  • Qué roles hay y cómo se reparten, a grandes rasgos, los permisos

En realidad, estas cosas están muy conectadas entre sí. Las rutas dependen del layout, y los roles y permisos también afectan a las rutas, así que normalmente las pienso en conjunto. Cuando eso está más o menos claro, el siguiente paso es definir el “modelo de datos (data model)”.

Después de responderle a la IA lo que parecen 800 preguntas, normalmente ya sale una primera propuesta del modelo de datos. Yo suelo pedirle que dibuje un Mermaid ER Diagram para entenderlo de forma visual y, a partir de ahí, convertirlo en un schema de IndexedDB.

Cuando la IA genera el schema, lo primero que miro es qué entidades aparecen y qué tipo tienen los campos importantes. Las entidades te enseñan cuáles son los objetos centrales que la IA cree que existen en el producto; los tipos van muy bien para detectar si ha entendido mal algo a nivel semántico dentro de los requisitos.

Por ejemplo, en este caso el requisito definía dos formas de validar el check-in: hacer check-in escaneando un QR code, o que alguien del personal busque el teléfono de la persona inscrita y le haga el check-in manualmente.

Y este fue el tipo que generó la IA para los métodos de check-in:

export type CheckInMethod =
  | "QR_CODE"
  | "STAFF_SEARCH"
  | "MANUAL";

Con solo verlo ya se entiende que “MANUAL” es una confusión provocada puramente por el texto: la búsqueda por parte del personal ya era, precisamente, la forma manual. La definición del tipo deja muy claro cómo está interpretando la IA la especificación.

Me he detenido un poco en el modelo de datos y el schema, pero si vuelvo a mirar el proceso en conjunto, lo que de verdad hay que dejar resuelto primero en esta fase son varios cimientos del proyecto: las rutas, los roles y permisos, y el modelo de datos. Cuando ya tienes una versión más o menos estable de eso, puedes empezar con las pantallas, y yo suelo arrancar por el user flow más central.

Pero en cuanto llegas a ese punto te das cuenta enseguida de que con las pantallas solas no basta. Si no tienes los datos y los estados que las acompañan, en realidad no puedes validar de verdad ni la interfaz ni el flujo. Ahí es cuando preparo un conjunto de seed data, es decir, datos de prueba definidos de antemano, para cargarlos directamente en el prototipo cuando haga falta. Por supuesto, también puedes crear datos falsos temporales interactuando con la propia interfaz, pero si necesitas validar lo mismo una y otra vez, tener que crearlos a mano cada vez acaba quitándote tiempo. Además, durante el desarrollo es bastante habitual tener que reconstruir la base de datos o tocar el schema, así que también viene bien añadir una función de “reset” para volver al estado inicial cargando ese seed data.

Otra ventaja de usar seed data es que puedes convertir directamente los “estados especiales” en parte de la propia especificación. Por ejemplo, si generas seed data para un evento con plazas agotadas, puedes validar con mucha claridad cómo deberían mostrarse todas las pantallas relacionadas en esa situación.

Además, como esta plataforma tiene usuarios con distintos roles, también suelo crear varios perfiles dentro del seed data y añadir una función de “cambio de rol”. El rol seleccionado se puede guardar en localStorage, y así puedes moverte por todo el prototipo rápidamente desde distintos puntos de vista.

Pantalla del prototipo de la plataforma de eventos con el menú para cambiar de rol

Cuando el user flow principal ya está bastante asentado, lo siguiente es ir añadiendo poco a poco las funcionalidades periféricas. Más o menos, yo voy repitiendo este proceso:

Confirmar requisitos de la funcionalidad -> ajustar el modelo de datos si hace falta -> añadir el seed data correspondiente -> implementar las rutas -> implementar la interfaz y la interacción -> probarlo de verdad

A veces, a mitad del camino, descubres que el modelo de datos original no te sirve. Otras veces, construyes algo, lo pruebas de verdad y entonces te das cuenta de que el problema está en el propio flujo que habías imaginado al principio. En esos casos, toca volver atrás y ajustarlo. Ese es precisamente el objetivo de hacer un prototipo: “definir la especificación a partir de la implementación”.

Como el objetivo del prototipo es validar decisiones que afectan al núcleo del producto, yo no intento implementarlo todo. Por ejemplo:

  • Inicio de sesión
  • Control de permisos en las rutas
  • Subida de imágenes
  • Envío de email
  • Tareas programadas en background
  • Servicios de terceros, como pasarelas de pago, WhatsApp, etc.

La idea es centrarse en validar lo que realmente afecta al flujo principal.

El desarrollo de software tiene demasiada flexibilidad y demasiadas posibilidades como para intentar dejarlo todo cerrado por escrito en una especificación textual. En cambio, con un prototipo que se pueda usar de verdad, puedes dejar mucho mejor definidos el flujo y los límites, reducir el margen de interpretación libre que tiene la IA sobre el requisito y hacer que el desarrollo se desvíe bastante menos. En fin, esta es solo una experiencia mía, pero ojalá a alguien le sirva.