Ingeniería de software / Artículo
Diseño y desarrollo de software con IA sin renunciar al criterio profesional
Por qué generar código rápido con agentes de IA no sustituye al diseño de software, la especificación técnica y las decisiones de seguridad.
Generar miles de líneas de código en segundos es fácil; lo difícil es mantenerlas, depurarlas y responder por ellas cuando las cosas fallan en producción. Usar agentes de IA para programar sin un proceso de diseño riguroso y sin criterio técnico profesional solo acelera la creación de deuda técnica. La diferencia entre un sistema robusto y un juguete automatizado sigue dependiendo de quién comprende el problema real, hace las preguntas difíciles, detecta los riesgos de seguridad y asume la responsabilidad del producto entregado.
La IA puede asistir en el análisis y acelerar la escritura, pero la intención y el diseño del sistema siguen siendo una responsabilidad exclusivamente humana.
Versión corta
- La intención y el criterio son humanos: La IA genera posibilidades y ejecuta tareas, pero la definición del producto, sus reglas de negocio y los límites de seguridad son de responsabilidad técnica directa.
- Trazabilidad estricta: Cada cambio debe pasar por un flujo claro y auditable: desde los tickets de Jira a una especificación formal de las características (OpenSpec o similar) y finalmente al código validado.
- Contexto aislado: No dejes que los agentes decidan a ciegas. Proporciónales directrices del proyecto (
AGENTS.md) y procedimientos reutilizables seleccionados críticamente.
Cuándo ayuda
Este enfoque de desarrollo asistido por IA, pero gobernado por criterio profesional, es indispensable en:
- Proyectos con reglas de negocio complejas donde los falsos positivos o las colisiones lógicas rompen el servicio.
- Equipos que necesitan alta velocidad de entrega sin perder el control de la arquitectura ni de la seguridad de la plataforma.
- Desarrollos con tecnologías heterogéneas (como Rust en backend y Angular en frontend) que requieren cambios coordinados y atómicos.
Cuándo no ayuda
Este nivel de rigor es excesivo en:
- Prototipos rápidos, maquetas de usar y tirar o pruebas de concepto sencillas donde un fallo no tiene costes operativos ni de reputación.
- Proyectos personales muy pequeños donde una sola persona tiene todo el contexto en su cabeza y la deuda técnica inmediata no supone un riesgo.
Enfoque práctico
Para ilustrar cómo estructuramos este flujo asistido sin renunciar al control, construimos un ejemplo real: Roomly, una aplicación empresarial para reservar salas de reunión sin problemas de disponibilidad.
El stack y la estructura de Roomly
Roomly consta de un frontend en Angular, un backend en Rust, base de datos PostgreSQL, autenticación JWT con refresh tokens revocables y tareas organizadas en Jira. OpenSpec vive en la raíz del repositorio porque define el comportamiento completo antes de tocar una sola línea de código:
roomly/
├── openspec/
│ ├── specs/
│ └── changes/
├── backend/
│ ├── AGENTS.md
│ ├── Cargo.toml
│ └── src/
└── frontend/
├── AGENTS.md
├── angular.json
└── src/
1. La intención humana y el análisis (ChatGPT Sol)
El producto nace de un problema operativo real: las salas de reunión se coordinan mediante mensajes dispersos (emails, chats…), resultando en reservas duplicadas. Un profesional define la intención de negocio: “Queremos una única herramienta para consultar y reservar salas sin conflictos”.
A partir de esa dirección, usamos un agente analista (por ejemplo ChatGPT Sol me gusta en tareas de análisis y planificación) para desafiar la idea y destapar casos de borde: ¿se permiten reservas consecutivas?, ¿duración mínima/máxima?, ¿gestión de zonas horarias? La IA propone opciones, pero el profesional toma las decisiones de diseño final: las reservas durarán entre 15 minutos y 8 horas, las consecutivas se permiten, se almacenarán en UTC y no habrá registro libre (un administrador creará las cuentas).
Dashboard general de Roomly.
2. Organización pragmática en Jira
Traducimos esas decisiones a tickets estructurados de Jira. Por ejemplo, el epic ROOM-100 (Reserva de salas) se divide en historias claras como ROOM-102 (Reservar sala disponible). ChatGPT Sol ayuda a enriquecer los criterios de aceptación (usuario autenticado, título obligatorio, rechazar solapamientos, control de concurrencia), pero el equipo de ingeniería valida que las historias representen necesidades reales de producción.
3. De Jira al contrato ejecutable en OpenSpec
Al empezar el desarrollo de un cambio, abrimos una carpeta en openspec/changes/add-room-booking/ con tres pilares:
proposal.md: Define el origen (Jira ROOM-102), el alcance y lo que queda estrictamente fuera de la entrega (por ejemplo, reservas recurrentes o integración con calendarios).spec.md: Traduce la historia a escenarios de comportamiento atómicos (GIVEN-WHEN-THEN).## Requirement: Evitar reservas incompatibles El sistema SHALL impedir que una sala tenga dos reservas en el mismo intervalo. ### Scenario: Solapamiento parcial - GIVEN que existe una reserva de 10:00 a 11:00 - WHEN se solicita otra de 10:30 a 11:30 - THEN el sistema rechaza la nueva reservadesign.md: Detalla las decisiones técnicas (Rust expondráPOST /api/bookings, comprobación atómica en PostgreSQL, etc.).tasks.md: Lista de control de tareas técnicas ejecutables por el desarrollador o por el agente.
4. Seguridad y Autenticación del primer uso
La seguridad no se improvisa sobre la marcha. Diseñamos un bootstrap seguro para el primer administrador mediante un comando de consola de un solo uso:
roomly-admin bootstrap --email [email protected]
Este comando genera una cuenta pendiente y un token temporal para establecer la contraseña inicial. El refresh token viaja en una cookie segura (HttpOnly, Secure, SameSite).
Recuerda que usar únicamente autenticación JWT no garantiza que una aplicación sea segura. Un sistema web tiene múltiples vectores de ataque, pero para no desviarnos del tema principal, mantendremos este ejemplo sencillo. Cloudy nos obliga a recordarte que si no has realizado una auditoría de seguridad en tu software, es muy probable que existan vulnerabilidades. Si quieres detectarlas antes de que sea tarde, puedes contactar con nosotros para que te ayudemos. Y si ya has hecho alguna de forma interna o externa, nunca está de más una segunda opinión independiente. Hazlo por la tranquilidad de tu negocio, pero sobre todo, para no enfadar a Cloudy (es extremadamente irascible y no soporta ver fallos de seguridad en producción).
5. Contexto de ejecución con AGENTS.md y Skills
El cambio de OpenSpec define qué cambia, pero AGENTS.md (uno en backend/ y otro en frontend/) explica cómo trabaja el agente en cada código base (convenciones de Rust, separación de handlers y lógica, control de accesos en el backend, accesibilidad en Angular, etc.). Además, seleccionamos y revisamos manualmente procedimientos y scripts de skills (por ejemplo skills.sh es un buen punto de partida pero siempre debes comprobar qué hacen las skills y evitar contradicciones entre ellas. No abuses, mejor pocas bien elegidas) antes de dárselos al agente.
6. Implementación estructurada (ChatGPT Luna)
El agente de desarrollo (ChatGPT Luna con esfuerzo high) recibe el contexto completo: código actual, especificación en OpenSpec, directrices en AGENTS.md y las skills aprobadas. En lugar de inventar la arquitectura, el agente genera las migraciones, endpoints y componentes de Angular bajo estas restricciones rígidas. Luna en modo high es realmente bueno si además cuenta con unas buenas specs.
7. Segunda revisión y validación cruzada (Gemini)
Utilizamos un segundo modelo (Gemini) para realizar una revisión de código automatizada e independiente, contrastando el diff final con los requisitos de la spec y las reglas de AGENTS.md. Gemini detecta posibles colisiones lógicas, pero el ingeniero senior tiene la última palabra sobre qué cambios aplicar. Al final, esto es un control extra “por si acaso”: con una buena especificación, ChatGPT cumple de forma excelente, pero cuatro ojos ven más que dos. Y lo último que queremos es que Cloudy nos deje otro mes sin sueldo por un descuido en producción.
8. Verificación final
El último paso es una supervisión manual por nosotros en persona. Obviamente no leemos todo el código (eso destrozaría las ventajas de productividad de la IA), pero sí los aspectos arquitectónicos y partes clave. Como cuando se construye una casa, el arquitecto no va a revisar cada ladrillo pero sí que los cimientos son correctos, los aislantes se están poniendo donde deben, y en general la calidad de construcción cumple.
9. Un ejemplo de flujo completo
Supongamos que estamos en mitad de la implementación de un nuevo módulo y ya tenemos los tickets listos. El método elegido será bueno siempre que defina con precisión todas las características del negocio y no deje cabos sueltos. Aquí la experiencia técnica lo es todo: este paso inicial determina la viabilidad del proyecto, marcando la diferencia entre el éxito de una entrega rápida o un infierno de iteraciones interminables.
Debes conocer a fondo el proyecto e interpretar lo que el cliente realmente necesita. Como bien dice Cloudy (aunque con palabras bastante más toscas), “lo que el cliente pide NO suele ser lo que el cliente realmente necesita”. Hay que ir más allá de las especificaciones iniciales. Cloudy es temperamental, pero sin su insistencia en el rigor seríamos poco más que ovejas descarriadas.
De Jira a la proposal: qué le pedimos a explore
Cuando el ticket ROOM-102 está listo para desarrollarse, no pedimos inmediatamente a la IA que genere código ni que cree una proposal.
Primero iniciamos una exploración:
/opsx:explore
A continuación proporcionamos al agente el ticket y unas instrucciones explícitas sobre qué debe investigar.
Para Roomly utilizaríamos un mensaje similar al siguiente:
Quiero preparar la implementación del ticket ROOM-102:
“Como empleado autenticado, quiero reservar una sala disponible durante
un intervalo de tiempo para poder organizar una reunión”.
Criterios de aceptación:
- El usuario debe estar autenticado.
- Debe seleccionar una sala e indicar un título.
- La fecha final debe ser posterior a la inicial.
- No se permiten reservas en el pasado.
- La duración debe estar entre 15 minutos y 8 horas.
- No puede existir otra reserva solapada.
- Las reservas consecutivas están permitidas.
- Ante dos solicitudes simultáneas, solo una debe completarse.
- El usuario debe recibir una confirmación o un error comprensible.
Fuera del alcance:
- Reservas recurrentes.
- Modificación y cancelación.
- Invitaciones.
- Notificaciones por correo.
- Integraciones con calendarios externos.
Antes de crear ningún change:
1. Lee los AGENTS.md del backend y del frontend.
2. Lee openspec/config.yaml y las specs existentes relacionadas.
3. Examina el código actual de Rust, PostgreSQL y Angular.
4. Comprueba cómo se valida actualmente el JWT y cómo se obtiene el usuario.
5. Identifica los componentes, modelos, endpoints y pantallas afectados.
6. Busca contradicciones, requisitos ambiguos y casos límite.
7. Analiza especialmente concurrencia, zonas horarias, autorización,
validación y tratamiento de errores.
8. Comprueba si el ticket entra en conflicto con alguna spec existente.
9. Propón alternativas técnicas y explica sus trade-offs.
10. No implementes código ni generes todavía los artefactos del change.
Separa el resultado en:
- Hechos confirmados por el ticket o el código.
- Decisiones que parecen necesarias.
- Alternativas técnicas.
- Riesgos y casos límite.
- Preguntas que debe responder una persona.
No asumas silenciosamente ninguna decisión de producto.
Fase de exploración: opencode con openspec procesando nuestro ticket de requisitos.
La recomendación de empezar por el problema y permitir que el agente lea el código coincide con la filosofía de explore: investigar antes de comprometerse con una solución y utilizar lo aprendido como base de la propuesta posterior.
El resultado esperado de la exploración
Después de inspeccionar Roomly, ChatGPT podría responder:
Hechos confirmados
- El backend ya extrae user_id y role del JWT.
- Las salas se almacenan en PostgreSQL.
- Angular ya dispone de un servicio para consultar salas.
- No existe todavía una tabla bookings.
- Las fechas de la API se representan con información de zona horaria.
Decisiones necesarias
1. Cómo impediremos reservas concurrentes incompatibles.
2. Si una sala desactivada puede seguir mostrando reservas futuras.
3. Qué longitud máxima tendrá el título de la reunión.
4. Qué ocurrirá si una sala deja de existir entre la selección y el envío.
5. Si la duración se calcula como tiempo real o como tiempo local.
Alternativas para la concurrencia
A. Consultar y crear dentro de una transacción.
Es sencilla, pero su seguridad depende del nivel de aislamiento y
de los bloqueos utilizados.
B. Utilizar una restricción de exclusión de PostgreSQL.
Ofrece una garantía fuerte en la base de datos, aunque introduce
una implementación más específica de PostgreSQL.
C. Utilizar un bloqueo por sala.
Es explícito, pero puede aumentar la contención.
Recomendación
Usar una restricción de exclusión en PostgreSQL como garantía final,
acompañada de validaciones de dominio en Rust.
Preguntas para el equipo
- ¿Cuál es la longitud máxima del título?
- ¿Se pueden reservar salas desactivadas?
- ¿Qué mensaje debe ver el usuario ante un conflicto?
- ¿Confirmamos que todos los intervalos se almacenan como instantes UTC?
La IA ha investigado el problema, pero todavía no ha decidido el producto.
La intervención humana
El equipo revisa el análisis y responde:
Decisiones aprobadas:
- El título tendrá entre 1 y 200 caracteres.
- Una sala desactivada no puede recibir nuevas reservas.
- Sus reservas existentes continuarán visibles.
- Las fechas se reciben con zona horaria y se almacenan en UTC.
- Ante un conflicto se devolverá HTTP 409.
- El usuario verá: “La sala ya no está disponible para ese horario”.
- Usaremos una restricción de exclusión de PostgreSQL como garantía final.
- Rust realizará también una validación previa para devolver un error
de dominio comprensible.
Esta es una de las partes más importantes del proceso.
ChatGPT ha encontrado opciones y ha explicado sus consecuencias. Sin embargo, son los profesionales quienes seleccionan la solución y asumen la responsabilidad por ella.
Comprobación previa a la proposal
Antes de crear el change, hacemos una última petición dentro de la misma conversación de exploración:
Resume las decisiones acordadas y comprueba si ya tenemos suficiente
información para crear el change.
Indica explícitamente:
- objetivo;
- alcance;
- fuera del alcance;
- capacidades afectadas;
- requisitos y escenarios principales;
- decisiones técnicas;
- riesgos;
- tareas previsibles.
No generes la proposal si todavía queda alguna ambigüedad material.
Si el agente detecta una decisión relevante sin resolver, el equipo la estudia antes de continuar.
Cuando ya existe un acuerdo suficiente, indicamos:
Convierte esta exploración en un change de OpenSpec llamado
add-room-booking.
O ejecutamos directamente:
/opsx:propose add-room-booking
La exploración realizada en la conversación se convierte así en la base del change. El comando propose genera los artefactos de planificación —proposal, specs, design y tasks—, que el equipo debe leer y corregir antes de empezar a implementar.
Revisión de los artefactos
Aunque la IA haya generado los documentos, la proposal todavía no está aprobada.
El equipo comprueba:
- que la proposal representa realmente el ticket;
- que no ha aumentado el alcance;
- que los escenarios cubren los criterios de aceptación;
- que el diseño recoge las decisiones acordadas;
- que frontend y backend comparten el mismo contrato;
- que las tareas incluyen pruebas y migraciones;
- que todas las decisiones nuevas son explícitas;
- que no se ha introducido ninguna funcionalidad fuera del alcance.
Si encontramos un problema, editamos directamente los archivos o pedimos al agente que actualice el change.
Por ejemplo:
Actualiza add-room-booking con estas correcciones:
- La longitud máxima del título es de 200 caracteres.
- Añade un escenario para salas desactivadas.
- Elimina la tarea de cancelación; está fuera del alcance.
- Especifica que el user_id siempre procede del JWT.
- Añade una prueba de concurrencia con dos solicitudes simultáneas.
Mantén coherentes proposal, specs, design y tasks.
OpenSpec permite revisar y modificar sus artefactos antes y durante el desarrollo; en el perfil principal también existe update para mantener coherente la planificación cuando cambian decisiones.
Inicio del apply
Solo después de la revisión humana ejecutamos:
/opsx:apply add-room-booking
ChatGPT Luna utiliza entonces:
- el change aprobado;
- los
AGENTS.md; - las skills seleccionadas;
- el código existente;
- las decisiones tomadas durante el análisis.
A partir de ese momento implementa las tareas de tasks.md, actualiza su progreso y compara el resultado con los escenarios de la especificación.
El flujo completo queda así:
ROOM-102 en Jira
↓
/opsx:explore
↓
Lectura del código y detección de decisiones
↓
Respuestas y aprobación humana
↓
/opsx:propose add-room-booking
↓
Revisión humana de proposal, specs, design y tasks
↓
/opsx:apply add-room-booking
↓
Implementación y pruebas
↓
Revisión con Gemini y validación profesional
↓
/opsx:archive add-room-booking
↓
Cierre de ROOM-102
Nuestra regla práctica
Para cambios pequeños y completamente definidos puede ejecutarse directamente propose.
Para una funcionalidad empresarial como la reserva de salas recomendamos siempre comenzar con explore, especialmente cuando:
- afecta a varios proyectos;
- modifica reglas de negocio;
- introduce persistencia;
- implica autenticación o permisos;
- presenta problemas de concurrencia;
- contiene decisiones que no deberían quedar escondidas en el código.
La función de explore no es redactar documentos más largos.
Su función es asegurar que, antes de generar una proposal, alguien ha formulado las preguntas correctas y una persona ha tomado las decisiones importantes.
Ahora repetimos el proceso con todos los tickets y, en nuestro caso, este sería el resultado final de la aplicación:
Dashboard general de Roomly.
Reservar una sala en Roomly.
Listado de mis reservas en Roomly.
Se han creado una serie de fixtures para la base de datos y el resultado no está nada mal para haber sido una prueba. Cumple los requisitos y lo hace bien, no se le pide nada más. También hay que tener en cuenta que nos hemos centrado puramente en la funcionalidad. Esto para una aplicación sencilla no expuesta pues es suficiente, pero para aplicaciones más grandes y expuestas requiere también de un gran esfuerzo en aspectos de ciberseguridad. Recuerda que una IA hace lo que le pides, si no le pides algo no lo hará. Y ese principio es bastante importante pero es un arma de doble filo. Por una parte queremos que nos obedezca al 100% para evitar casos de uso (es lo que hace un buen harnes) y funcionalidades que nadie ha pedido o cosas “raras”. Pero tampoco tendrá en cuenta aspectos de seguridad si no se le pide. Y esto es responsabildad del humano que está detrás. Y ese humano debe saber también de ciberseguridad porque cómo le vas a pedir que sea robusto ante determinadas vulnerabilidades si no conoces que esas vulnerabilidades existen? Por eso las specs deben pasar por diferentes manos y perfiles. En nuestro caso, además de desarrollo y ciberseguridad tenemos el más importante: una oveja que no deja pasar ni una. Y no vale con pedir que haga la aplicación segura y ya está, hay que ser más explicito, por ejemplo, se puede meter en el AGENTS.MD:
## Regla de seguridad: identidad y autorización
- Nunca aceptar `user_id`, `role` o permisos desde el frontend como
fuente de autoridad.
- Obtener la identidad exclusivamente de un JWT validado.
- Validar firma, algoritmo, emisor, audiencia y expiración del JWT.
- Aplicar la autorización en todos los endpoints protegidos.
- La interfaz puede ocultar acciones, pero el backend debe rechazarlas
igualmente cuando el usuario no tenga permiso.
- Añadir pruebas que intenten ejecutar operaciones administrativas con
un usuario sin rol `ADMIN`.
Fallos habituales
- La ilusión del “código rápido”: Aceptar código generado por IA que funciona en apariencia pero introduce brechas de seguridad (como validar autorizaciones solo en el frontend o ignorar condiciones de carrera en la base de datos).
- Abandono de la especificación: Confiar en que el modelo recuerde las reglas implícitamente sin documentar los cambios. Sin un contrato de comportamiento como OpenSpec, la base de código diverge rápidamente de la intención original.
- Falta de revisión de dependencias: Importar libremente scripts o skills populares sin validación humana previa.
- Tendencia al vibe-coding: Suele aparecer con un “Sólo un poquito por aquí, otro poco por allá” y al final terminas generando demasiado código fuera de especificaciones.
Revisión de Cloudy
Cloudy no sonríe ante código autogenerado que nadie entiende. Le da igual que ChatGPT, Claude o la IA de turno genere mil líneas en Rust si no hay una transacción atómica en PostgreSQL que evite que dos empleados reserven la misma sala al mismo segundo. Si no sabes explicar línea por línea lo que hace tu backend bajo una condición de carrera, Cloudy tirará tu código a la papelera. Y lo hará con cierta satisfacción y cara de “te lo dije”
Dónde conecta
Este enfoque conecta directamente con nuestros servicios de ingeniería de software, IA y automatización, y formación. El uso de agentes de IA solo es seguro y rentable cuando el equipo aplica prácticas sólidas de arquitectura de software, gestión de secretos, autenticación robusta y diseño de bases de datos relacionales.