El fin de semana que decidí no escribir código

Crónica de una aplicación full-stack desarrollada sin «picar tecla»

El viernes 20 de febrero, por la tarde, algo cambió. Llevaba semanas escuchando afirmaciones sobre la inteligencia artificial aplicada al desarrollo: que si sustituirá a los programadores, que si solo sirve para generar código trivial, que si es peligrosa, que si es mágica… mucho ruido, demasiadas opiniones y poca experiencia directa.

Así que decidí hacer algo… una pequeña locura controlada: Desarrollar una aplicación completa en poco más que un fin de semana… sin escribir ni una sola línea de código.

  • No copiar.
  • No pegar.
  • No corregir manualmente.
  • No “cambiar ni una sola coma”.

Todo debía construirse mediante una conversación con un asistente de IA integrado en VS Code (vía extensiones). Tenía sobre la mesa tres opciones muy conocidas: GitHub Copilot (con su extensión oficial en VS Code), Gemini Code Assist (también disponible como extensión) y Codex (mediante la extensión IDE de OpenAI, pensada para VS Code y compatibles). Probé Gemini Code Assist aquella tarde: acertaba en lo superficial, pero cuando empecé a apretar con arquitectura, abstracciones y coherencia entre capas, noté que la cosa ya no iba tan bién. Copilot era otra alternativa muy sólida y extendida, pero como las pruebas que hice con Codex estaban resultando especialmente consistentes para el tipo de conversación técnica que buscaba, y, además, ya había pagado la suscripción mensual, terminé decantándome por esta. Ahora faltaba decidir qué hacer.

El proyecto: algo sencillo… pero no demasiado

Podría haber elegido algo básico: una API de tareas, un blog mínimo, una aplicación de ejemplo que solo sirviera para demostrar que “funcionaba”. Pero eso no me interesaba.

La idea surgió casi de manera casual cuando un compañero del trabajo me había comentado días antes la necesidad de contar con una herramienta compartida de listín para la organización: algo sencillo en apariencia, pero estructurado, accesible y con control de permisos.

Ahí vi la oportunidad. No quería un proyecto que fuera una práctica de laboratorio. Quería algo real, con cierta complejidad organizativa, así que decidí implementar un listín telefónico corporativo, pero no uno plano y básico, sino uno que incluyera:

  • Empresas.
  • Centros.
  • Departamentos.
  • Usuarios asociados a centros, departamentos o ambos.
  • Roles diferenciados.
  • Agendas publicables.
  • Búsqueda pública y privada.
  • Control de acceso por nivel.
  • Seguridad desde el diseño.

Nada excesivamente complejo, pero lo suficientemente estructurado como para que la arquitectura importara.

Además, establecí una serie de reglas técnicas desde el principio:

  • Quería el backend en Python.
  • Persistencia en MySQL.
  • Portabilidad futura a otras bases de datos.
  • Autenticación JWT con refresh.
  • Frontend en React con TypeScript.
  • Cliente HTTP abstracto.
  • CRUD genérico reutilizable.
  • Theming centralizado.
  • Gestión automática de renovación de token.

No quería una demo, quería algo que funcionara y que tuviera coherencia.

Primera conversación: la arquitectura no se improvisa

El primer prompt que lancé no fue “hazme un endpoint”, fue algo más reflexivo:

Quiero desarrollar el backend en Python. Me gusta el lenguaje.
Necesito seguridad desde el diseño.
Usaremos MySQL, pero quiero minimizar el acoplamiento.

Lo interesante no fue que propusiera FastAPI, eso era esperable, sino cómo lo propuso:

  • FastAPI como framework ASGI moderno.
  • SQLAlchemy 2.x para ORM.
  • Pydantic v2 para validación.
  • Separación por capas.
  • Dependencias para seguridad.
  • JWT con expiración configurable.

No generó un archivo único de 800 líneas, propuso estructura, y en ese momento entendí algo que marcaría una constante durante el resto del fin de semana:

La IA no solo genera código.
Si la guías bien, genera arquitectura.

Pero la arquitectura no nace del asistente, nace de quien formula la pregunta.


El modelo de datos: donde realmente empieza todo

Antes de crear los endpoints, trabajamos en el modelo relacional. Le describí lo que quería como lo haría en una reunión técnica:

  • Empresas con centros.
  • Departamentos asociados a centros.
  • Usuarios con pertenencias múltiples.
  • Roles de aplicación.
  • Agendas que pueden publicarse por usuario, centro o departamento.
  • Contactos extensibles.

La primera propuesta fue correcta… pero incompleta. Ajusté relaciones, añadí tablas puente, refiné claves foráneas, introduje normalización donde hacía falta. Fue una conversación fluida y cruzada donde ocurrió algo importante:

La IA no discutía. No defendía su propuesta. Ajustaba estructuras con naturalidad cuando se le explicaba el porqué.

El modelo final no fue producto de una generación automática. Fue producto de diálogo arquitectónico.


Seguridad desde el primer minuto

Como no quería que la autenticación fuera un parche posterior, le pedí explícitamente:

  • El primer endpoint debe ser autenticación.
  • Debe devolver bearer token.
  • Debe existir refresh token.
  • Debe haber distintos niveles de seguridad para la invocación de servicios:
    • Público.
    • Con Token válido.
    • Con Token válido y ROL ADMIN.
    • Con Token válido y ROL ADMINCD (de centro o departamento)

Y algo más importante:

  • No quiero repetir lógica de seguridad en cada endpoint.

El resultado fue una implementación basada en dependencias de FastAPI, con validación centralizada, control por roles y refresh correctamente implementado.

Cuando vi que el refresh estaba integrado con expiraciones diferenciadas, sonreí. No era un login básico. Era un flujo serio. ¡Y yo no había escrito nada!


La estructura de carpetas: disciplina antes que velocidad

Luego le pedí algo que, si soy sincero, muchos proyectos pequeños ignoran:

Cada servicio debe tener separación clara entre DTO, router y lógica.
Añadir un nuevo servicio debe ser casi mecánico.

Y me propuso la creación de la siguiente estructura:

  • dto/
  • routers/
  • services/

Nada revolucionario, pero sí con todo el sentido:

  • La lógica no estaba en los routers.
  • Los modelos ORM no se exponían directamente.
  • Las dependencias gestionaban seguridad de forma transversal.
  • No era un hackathon de fin de semana. ¡Era arquitectura!

El momento del CRUD masivo

Una vez definida la estructura, pedí implementar todos los CRUD del modelo.

En pocos minutos tenía:

  • Endpoints tipados.
  • Validaciones básicas.
  • Operaciones CRUD completas.
  • Respuestas estructuradas.

Probé con Insomnia y ¡Funcionaba! En ese instante, el experimento dejó de ser teórico. ¡El backend estaba vivo!

Entonces… tomé la decisión que cambió el ritmo del fin de semana: Pasar al frontend. Y ahí comenzó otra parte verdaderamente interesante.


Cuando la complejidad deja de ser teoría

El sábado por la mañana comenzó con un backend que ya no era una promesa… era una API funcional. Así que llegaba el momento de comprobar si aquello era un espejismo técnico o una base sólida. ¡Vamos a meterle mano al front!

React o Angular: el primer pequeño debate

Inicié la conversación con una pregunta aparentemente trivial:

¿Qué frontend utilizamos?

Se barajaron Angular y React. Ambas opciones eran válidas. Angular aportaba estructura desde el principio; React ofrecía flexibilidad y un ecosistema inmenso.

Finalmente ganó React. No por moda, sino por equilibrio entre simplicidad y potencia para este tipo de proyecto.

Eso sí, impuse condiciones claras:

  • TypeScript obligatorio.
  • Estructura limpia.
  • Cliente HTTP abstracto.
  • Token centralizado.
  • Rutas protegidas.
  • Menú dinámico según rol.
  • Nada de fetch suelto por componentes.
  • Estilo visual (tema) coherente.
  • Modulo de utilidades y Mensajes centralizados.

No quería que el frontend fuera un conjunto de componentes felices y desorganizados. Quería arquitectura.


El login: sencillo, pero no trivial

El primer componente fue una pantalla de login. Podría haber sido algo simple: usuario, contraseña y redirección, pero pedí algo más:

  • Integración con servicio de autenticación.
  • Almacenamiento controlado del token.
  • Preparación para refresh.
  • Manejo centralizado de errores.
  • Gestión de estado de carga.
  • Integración con sistema de mensajes.

El resultado fue limpio. Sin lógica HTTP incrustada en JSX. Sin estados dispersos. Sin efectos secundarios ocultos.

Era un login serio… y entonces ocurrió algo curioso. Me descubrí revisando el código línea a línea, como si estuviera esperando encontrar un error conceptual, pero… no lo encontraba.

La estructura era coherente y ahí empezó a cambiar algo en mi forma de interactuar con el asistente.


El primer CRUD… y la primera alarma

Le pedí que generase el mantenimiento fue el de usuarios. Funcionaba todo: Tabla, Formulario, Asociaciones, Restricción a rol ADMIN. Todo perfecto.

Luego le pedí que hiciera el mantenimiento de países y luego el de provincias.

Y entonces… lo vi.

Cada mantenimiento era una pequeña variación del anterior. Correcto. Limpio. Pero repetitivo. Esa sensación es familiar para cualquiera que haya desarrollado sistemas administrativos: el eterno déjà vu del CRUD.

Y ahí decidí intervenir.

“No quiero repetir esto cinco veces”

Le expliqué al asistente:

Necesito un CRUD genérico.
Parametrizable.
Declarativo.
Que permita definir campos, validaciones y endpoints.
Con el que añadir una entidad nueva sea cuestión de configuración.

Hubo un breve silencio digital… y entonces ocurrió algo que, sinceramente, no esperaba en ese nivel de coherencia.

No solo creó el componente genérico, sino que refactorizó lo que ya existía para utilizarlo.


El nacimiento del CrudMasterView

Lo que apareció no era un wrapper superficial, sino una una estructura que:

  • Definía columnas dinámicamente.
  • Orquestaba el formulario modal.
  • Gestionaba carga y estados.
  • Integraba validación desacoplada.
  • Transformaba payload antes de enviar.
  • Soportaba asociaciones.
  • Reutilizaba cliente HTTP abstracto.
  • Integraba sistema de mensajes.

¡Era un pequeño motor CRUD!

En ese momento pegue un bote de alegría sobre el asiento, casi se me saltan las lágrimas. No porque “la IA escribiera código”, sino porque estaba reorganizando toda la arquitectura con coherencia y lo mejor de todo… yo solo estaba describiendo el resultado deseado.


La limpieza invisible

A partir de ese punto, el frontend empezó a tener una forma que no es evidente al usuario final, pero sí al desarrollador:

  • CrudMasterView: el componente que lo orquesta todo.
  • CrudFormDialog: aislando el formulario.
  • CrudEntityFormFields: renderizando dinámicamente.
  • formValidation: separando reglas.
  • formPayload: gestionando transformación.
  • editResolver: resolviendo estados de edición.

Nada estaba asociado a una entidad concreta ni había nada incluido «a piñon» en el código. Era una pequeña infraestructura interna. En ese momento pensé —medio en serio, medio en broma— que si alguien veía el repositorio sin contexto, asumiría que había un equipo detrás, pero no, solo había conversación.


El refresh automático: coherencia total

El backend tenía refresh token, yo lo había pedido así y no iba a dejarlo como decoración, así que le pedí que para el frontend quería:

  • Detectar expiración.
  • Renovar automáticamente.
  • No interrumpir la experiencia del usuario.
  • Centralizar la lógica.

Y así nació el: authRefreshTask.

El cliente HTTP quedó integrado con la gestión de renovación. El token estaba en un store central. No había lógica duplicada. Y aquí hay algo interesante: muchos proyectos reales omiten esto por “complejidad” pero aquí apareció como una consecuencia natural del diseño inicial.


Centralización obsesiva (y saludable)

Tengo una pequeña manía personal, evitar la dispersión. No me gusta:

  • Ver colores hardcodeados.
  • Ver alertas distintas según la página.
  • Ver literales repetidos.
  • Ver endpoints incrustados.

Así que pedí:

  • Tema gráfico centralizado.
  • Extensión tipada de MUI.
  • Un archivo «uiTexts» para textos reutilizables.
  • Un MessageBox único para emitir todos los avisos y mensajes.
  • Entorno centralizado y parametrizable, configurable a través de variables de entorno.
  • Cliente HTTP único.

La IA respetó esa disciplina y lo integró todo donde debía estar.


Cuando la complejidad sube el volumen: agendas

El verdadero examen llegó con las agendas. Ya no era CRUD plano, pues había:

  • Asociaciones múltiples.
  • Publicación por centro.
  • Publicación por usuario.
  • Publicación por departamento.
  • Edición compleja de relaciones.

Aquí era fácil que la arquitectura se rompiera, pero… ¡No se rompió!

  • Se reutilizaron componentes.
  • Se integraron asociaciones.
  • Se respetó el patrón.
  • La abstracción aguantó.

Para mí, eso fue más impresionante que cualquier endpoint generado.


La búsqueda pública y privada

El módulo de búsqueda añadió otro matiz. No era mantenimiento. Era pura experiencia de usuario.

  • Resultados tipo ficha.
  • Filtrado.
  • Comportamiento distinto según autenticación.

Y aun así:

  • Cliente HTTP abstracto.
  • Gestión de token coherente.
  • Integración con mensajes.
  • Sin lógica duplicada.

La coherencia se mantuvo.


El momento de la confianza

El sábado por la tarde algo cambió. Durante todo el tiempo que había pasado, yo revisaba -por encima- el código que la IA iba generando. Luego empecé a revisar solo estructura y finalmente dejé de revisar código para revisar comportamiento.

Y ahí comprendí algo:

Estaba confiando más en las pruebas que en la inspección manual. No porque fuera infalible, sino porque estaba entendiendo que la IA comprendía las abstracciones que yo imponía.

Eso no elimina la revisión en proyectos reales, pero cambia el enfoque.


La pregunta incómoda

El domingo ya no estaba pensando en la aplicación, estaba pensando en el rol. Durante años, el desarrollo ha implicado traducir pensamiento a sintaxis, pero ahora… el asistente traduce.

Entonces, ¿qué hace el desarrollador?

La respuesta es incómodamente simple: piensa mejor.

Es importante tener claro que:

  • La IA no decidió el modelo relacional.
  • No decidió separar capas.
  • No decidió implementar refresh.
  • No decidió crear un CRUD genérico.
  • No decidió centralizar los estilos gráficos.
  • No decidió separar validación de payload.

Eso lo decidí yo y la IA ejecutó.

Y esa distinción es crucial, porque si la dirección es pobre, la ejecución será pobre.

¿Puede hacerlo cualquiera?

Sí… pero no con el mismo resultado.

Sin comprensión de:

  • Arquitectura.
  • Patrones.
  • Seguridad.
  • Modelado.
  • Separación de responsabilidades.

El asistente generará algo que aparentemente funciona, pero frágil. Y cuando llegue la primera ampliación, aparecerán los problemas técnicos.


¿Es infalible la IA?

No… y no debería serlo.

  • Puede cometer errores sutiles.
  • Puede asumir cosas no especificadas.
  • Puede añadir más complejidad de la necesaria.
  • Puede proponer soluciones demasiado básicas para el problema real.

Pero también puede analizar su propio trabajo.

Por ejemplo, solicitarle los siguiente:

“Revisa el proyecto en busca de vulnerabilidades o inconsistencias.”

Produce respuestas sorprendentemente útiles, aunque no sustituye la auditoría profesional, pero la complementa.


El valor del desarrollador senior

Pasar por esta experiencia durante el fin de semana, reforzo en mi algo que sospechaba:

El conocimiento profundo no pierde valor, lo multiplica.

Un desarrollador con experiencia:

  • Detecta decisiones subóptimas.
  • Identifica riesgos de seguridad.
  • Piensa en escalabilidad.
  • Anticipa crecimiento.
  • Entiende impacto organizativo.

Un asistente de IA amplifica eso, pero no lo sustituye.

En cambio, un desarrollador sin base arquitectónica puede producir sistemas que funcionan… hasta que dejan de hacerlo.


El lunes por la tarde

Ya no estaba construyendo, estaba observando.

La aplicación funcionaba perfectamente:

  • Listín corporativo.
  • Agendas complejas.
  • Búsqueda pública y privada.
  • Roles diferenciados.
  • Refresh automático.
  • CRUD genérico.
  • Abstracciones coherentes.

Cuatro días, escasos, sin escribir código. Y la sensación no era de velocidad, sino de desplazamiento.

Sin pretenderlo, algo había cambiado en mi forma de desarrollar… ¡Y no me desagradaba!


¿Es el fin del programador?

Yo diría que no, solo el fin del programador que solo programa.

  • La inteligencia artificial no elimina la necesidad de conocimiento, la hace más visible.
  • No elimina la arquitectura, la convierte en el centro.
  • No elimina la responsabilidad, la incrementa.

Porque ahora podemos:

  • Construir más rápido.
  • Escalar más rápido.
  • Equivocarte más rápido.
  • O diseñar mejor y ejecutar con mayor eficacia.
  • El cuello de botella ya no es escribir código.
  • Es pensar con claridad.

Reflexiones

Si tuviera que condensar todo el fin de semana en una serie de reflexiones honestas, serían estas:

  • Me parece sorprendente el nivel de comprensión estructural que tienen estas herramientas cuando la conversación se mantiene en términos de arquitectura.
  • He podido desarrollar una aplicación completa sin pasar por la curva de aprendizaje de entornos nuevos, simplemente explicando lo que quería y aplicando la abstracción que mi experiencia me aporta.
  • Durante ese fin de semana no he tenido el convencimiento de que la IA programe sola.
  • Si me he demostrado que cuando la experiencia guía, la IA ejecuta con una eficacia que obliga a replantear el oficio.
  • ¿Cualquiera puede hacerlo? Sí. Pero el resultado no será óptimo sin criterio.
  • No es el fin del desarrollador. Es el fin del desarrollador que solo programa.
  • ¿Me fiaría del código generado? En un entorno profesional siempre revisaría, pero el tiempo que se gana es indiscutible.
  • Usar asistentes de IA va a reducir drásticamente los tiempos de desarrollo. Pero también exigirá más claridad mental.
  • El futuro pertenece a quien diseña, decide, cuestiona y valida.
  • Pedirle a la IA que revise y analice su propio trabajo es casi tan importante como pedirle que lo genere.
  • ¿Es infalible? No. Pero evoluciona rápido.
  • Este nuevo paradigma da más valor —no menos— a quienes entienden profundamente lo que están construyendo.
  • El teclado sigue ahí, pero ya no es el protagonista.

Resultado

Si alguno de ustedes tiene curiosidad por disponer del código fuente de este «listin», puede obtenerlo del siguiente enlace.

Miguel Ángel Jiménez Santana

Comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *