Qué decisiones de arquitectura deja abiertas una spec
El comportamiento puede estar bien definido y admitir varias implementaciones con costes, fallos y requisitos operativos distintos.

Una especificación puede definir sin ambigüedad que dos alumnos no deben reservar el mismo hueco y, aun así, dejar abierta la decisión más importante de la implementación. La regla describe el resultado que debe observarse, pero no decide si el sistema lo garantiza mediante una restricción de la base de datos, un bloqueo o una transacción con aislamiento serializable.
Los tres mecanismos pueden proteger el mismo invariante funcional e incluso combinarse, aunque introducen costes y modos de fallo diferentes. Elegir el mecanismo dominante y las defensas complementarias exige conocer el modelo de datos, el motor de persistencia, la concurrencia prevista y la experiencia que debe recibir quien pierde la reserva. Ese trabajo sigue perteneciendo a la arquitectura.
El invariante puede ser común; la garantía se elige con el modelo de datos, el motor y la carga reales.
La spec puede detectar el caso y dejar abierta la decisión
Tomemos el caso hipotético de la guía principal. Un profesor ofrece un hueco de tutoría a las 16:00. Dos alumnos lo ven libre y lo solicitan casi al mismo tiempo. La especificación puede fijar un comportamiento completo:
- solo una solicitud queda activa y retiene el hueco;
- el otro alumno recibe una respuesta que indica que el hueco ya no está disponible;
- no se notifica al profesor ni se crea una videollamada para la petición perdedora;
- el sistema conserva evidencia suficiente para explicar qué ocurrió.
Esto ya es bastante mejor que pedir «evita reservas duplicadas». Define el invariante —la condición que siempre debe cumplirse— y el resultado visible. También fija los efectos que deben quedar fuera de la operación perdedora. Un agente puede convertirlo en pruebas y detectar una implementación que mantenga dos ocupaciones activas.
La spec todavía no contiene una arquitectura, ya que la regla admite varias formas de garantía y cada una necesita condiciones que el documento de comportamiento no puede inventar.
El primer diseño que parece correcto falla con concurrencia
La implementación más inmediata suele comprobar primero y escribir después:
si no existe una ocupación activa para el hueco:
crear la solicitud y la ocupación
Leído de arriba abajo parece cumplir la regla. Con dos peticiones concurrentes aparece el fallo:
Alumno A Alumno B
--------- ---------
comprueba: libre
comprueba: libre
inserta ocupación
inserta ocupación
intenta confirmar intenta confirmar
Cada petición tomó una decisión coherente con la información que había leído. El error está en considerar la comprobación y la escritura como una operación indivisible cuando la base de datos las ejecuta por separado. Añadir otra comprobación en el código no cambia esa separación.
La prueba funcional que lanza una petición cada vez tampoco descubre el fallo: hay que ejecutar ambas dentro de la misma ventana de concurrencia y comprobar el estado final. El invariante permite derivar esa prueba; la arquitectura decide qué mecanismo debe hacerla pasar.
Para reproducirla, usaría dos conexiones reales y una barrera después de leer «hueco disponible» y antes de escribir. Al liberarlas a la vez, la prueba exige una sola ocupación activa y una respuesta ganadora. También exige un conflicto de dominio para la otra petición, ningún efecto externo de la perdedora y un rastro auditable. Se repite con los reintentos habilitados para comprobar que no duplican efectos.
Tres mecanismos cumplen la misma regla de formas distintas
En lugar de elegir una solución universal, compararía las alternativas con el modelo real:
| Mecanismo | Condición que necesita | Qué ocurre bajo contención | Coste que introduce | Error visible |
|---|---|---|---|---|
| Restricción de unicidad | Una clave estable que represente el hueco ocupado | Una escritura gana y la otra es rechazada | Hay que traducir el error de persistencia y modelar cancelaciones | «El hueco acaba de ocuparse» |
| Bloqueo de la fila del hueco | El hueco existe como entidad bloqueable y todos los caminos de reserva bloquean esa misma fila antes de decidir | La segunda transacción espera y vuelve a evaluar | Esperas, bloqueos largos si la transacción hace demasiado trabajo | Confirmación o rechazo después de la espera |
| Aislamiento serializable | Toda la decisión está en una transacción repetible y todos los caminos que compiten usan el mismo nivel o una defensa de integridad complementaria | El motor aborta una ejecución no serializable | Reintentos explícitos y disciplina transaccional | Confirmación tras reintento o rechazo controlado |
Las opciones pueden combinarse. Una restricción de integridad puede ser la última defensa aunque la aplicación use además bloqueo o aislamiento para coordinar una operación más amplia. El comportamiento exacto de los abortos y reintentos depende del motor. Por eso debe verificarse en su documentación y en una prueba contra la misma tecnología que se usará en producción.

Una restricción de unicidad es una defensa excelente cuando el modelo permite expresar el invariante con una clave. En vez de pedir al código que recuerde la regla en todos los caminos, la base de datos rechaza el estado imposible. La aplicación sigue teniendo trabajo: convertir ese rechazo en una respuesta de dominio y evitar que los efectos externos empiecen antes de confirmar la transacción.
El bloqueo encaja cuando los huecos ya existen como filas y la operación necesita coordinar más cambios antes de decidir. Mantendría dentro de la transacción solo la reserva y su registro local. Crear una videollamada o enviar un correo mientras se conserva el bloqueo alarga la contención y deja la duración de la transacción en manos de otro sistema.
Sacar esos efectos de la transacción no basta: la intención debe quedar registrada de forma duradera antes de confirmar, por ejemplo en una bandeja de salida transaccional. El proceso que la ejecuta reutiliza la misma clave lógica y reconcilia antes de reintentar. Así, una caída entre la confirmación y la llamada externa no convierte una solicitud válida en trabajo perdido.
El aislamiento serializable permite que el motor detecte ejecuciones que no pueden ordenarse sin romper la regla, pero no convierte el conflicto en éxito. Una de las transacciones puede abortar, y la aplicación debe saber cuándo repetir, cuándo informar y cómo evitar duplicar efectos. Si el equipo no trata ese aborto como parte normal del contrato, el mecanismo será correcto en la base de datos y parecerá una avería en la interfaz.
La tabla no elige por nosotros, pero hace visibles los datos que faltan para elegir y las consecuencias que deben aceptarse de forma consciente.
La elección depende de información que la spec no debe inventar
El primer dato es el modelo de huecos. Si cada disponibilidad existe como una fila con identidad estable, bloquearla o referenciarla desde una reserva resulta natural. Si los huecos se calculan al vuelo a partir de reglas recurrentes, esa fila quizá no existe y habrá que materializarla o expresar el solape de otra forma.
También importa el ciclo de vida. Conservar reservas canceladas en la misma tabla puede impedir una unicidad simple sobre profesor y hora. En cambio, borrar el histórico para facilitar el índice sacrifica la auditoría. Otra estructura puede separar la ocupación vigente del historial de cambios. La spec debería exigir trazabilidad; no debería decidir una tabla concreta sin conocer estas restricciones.
El volumen condiciona la elección. En un sistema con poca concurrencia, esperar brevemente por un bloqueo puede ser aceptable. En una apertura simultánea de miles de plazas, la contención, los reintentos y la distribución de claves forman parte del diseño. El mecanismo correcto depende de dónde se concentra la carga, no de cuál parece más elegante en un ejemplo.
Por último está la respuesta al alumno. Aunque «conflicto de serialización» y «clave duplicada» son detalles internos, el contrato visible debe decir si el hueco se ocupó, si la petición se repetirá o si hace falta elegir otro. Esa traducción pertenece al diseño de la operación y se prueba junto al invariante.
Qué puede delegarse al agente y qué responsabilidad conserva el equipo
Un agente puede ayudar a enumerar alternativas, localizar todos los caminos de escritura, generar pruebas concurrentes y comprobar que la migración contiene la restricción decidida. También puede señalar que una transacción incluye una llamada externa o que un error técnico llega sin traducir a la API.
No debería elegir el mecanismo sin recibir las condiciones anteriores. Si lo hace, supondrá que existe una fila bloqueable y que el motor admite el mecanismo elegido. También puede dar por hecho que los conflictos son raros o que el cliente sabe reintentar. El código puede ser consistente con esos supuestos y seguir siendo inadecuado para el sistema real.
| Responsabilidad | Producto | Arquitectura | Agente | Revisión |
|---|---|---|---|---|
| Definir el resultado visible | Decide | Participa | Documenta | Comprueba |
| Elegir la garantía técnica | Informa restricciones | Decide | Propone e implementa | Discute y valida |
| Construir pruebas concurrentes | Aclara casos | Fija invariantes | Genera y ejecuta | Examina cobertura |
| Aceptar el coste y el riesgo operativo | Decide prioridad | Explica consecuencias | Aporta señales | El responsable del servicio y los equipos de Operaciones y Seguridad aceptan o escalan |
La revisión tampoco puede limitarse a preguntar si el código coincide con la spec. Debe comprobar si la decisión técnica protege el invariante bajo las condiciones reales y si el sistema puede explicar el conflicto cuando ocurra.
Qué exigiría la arquitectura en este caso
Para este caso fijaría las condiciones que faltaban: un único PostgreSQL, huecos de treinta minutos con identidad estable y concurrencia moderada. Mantendría un histórico obligatorio y una ocupación activa separada de la reserva. Elegiría una restricción única sobre la ocupación activa como última defensa. La solicitud y la intención de publicar los efectos externos se confirman en la misma transacción; un proceso crea después la videollamada y envía el correo a partir de una bandeja duradera y deduplicable.
Antes de desplegar, buscaría ocupaciones duplicadas, corregiría los datos, crearía la garantía sin dejar una ventana para escrituras antiguas y mantendría compatibilidad durante la transición. El plan de vuelta atrás puede retirar el código nuevo, pero no debe retirar la protección hasta comprobar que ninguna versión anterior vuelve a crear estados inválidos. La contención, los conflictos, los reintentos agotados y la edad de la bandeja necesitan métricas y alertas.
La especificación hace explícita la reserva simultánea y fija que solo puede existir una ocupación activa. También define el comportamiento de la petición perdedora y permite construir una prueba que falla con el diseño ingenuo. Así reduce varias decisiones implícitas antes de escribir código.
Las decisiones restantes siguen dependiendo del equipo. Sus responsables deben elegir el mecanismo, limitar la transacción y diseñar los reintentos. También deben separar los efectos externos y decidir qué señales necesita la operación. El agente puede ejecutar y comprobar esa decisión cuando está escrita, pero no asumir la responsabilidad de una que nadie ha tomado.
Esta es la frontera que completa la guía sobre qué debe contener una spec para guiar a un agente de IA. La spec conserva el problema y las condiciones de éxito, mientras que la arquitectura decide qué garantía puede sostenerlas.
Última revisión: 27 de julio de 2026.
Alberto Lara Hernández trabaja en dirección técnica, arquitectura de software, inteligencia artificial aplicada y plataformas de aprendizaje.
Más de 22 años construyendo y evolucionando plataformas de aprendizaje que tienen que operar de verdad.
Seguir leyendo
- Deuda técnica en IA generativa: RAG, agentes y código generado
La deuda técnica de la IA generativa se acumula en fuentes, índices, agentes y código. El análisis propone controles para RAG, agentes, código generado y desarrollo dirigido por especificaciones.
- Spec-Driven Development con IA: qué debe contener una spec
Una spec útil conserva las decisiones que deben llegar al diseño, el código y las pruebas. Esta guía explica qué incluye, cuánta autoridad recibe y dónde deja de bastar.
- ¿Está tu LMS preparado para agentes? Ocho pruebas de arquitectura
Ocho pruebas para saber si un LMS permite que agentes de IA consulten y actúen con delegación, límites, trazabilidad y responsabilidad educativa.