En muchos equipos, el código empieza demasiado pronto. Hay una reunión, alguien comparte una idea, se redactan cuatro notas rápidas y en pocas horas ya hay una rama abierta, endpoints en marcha y componentes de interfaz construyéndose a toda velocidad. El problema llega después: requisitos ambiguos, reglas mal entendidas, validaciones inconsistentes y retrabajo continuo.
Spec-Driven Development propone justo lo contrario: antes de implementar, se define con precisión qué debe hacer el sistema, qué reglas lo gobiernan, cómo se valida y cómo sabremos que está bien hecho. El código deja de ser el primer paso y pasa a ser la consecuencia natural de una especificación sólida.
La teoría es útil, pero donde realmente se entiende este enfoque es en un caso real. Por eso en este artículo vamos a desarrollar un ejemplo completo: un sistema de reservas para una clínica que necesita dejar atrás un proceso manual y propenso a errores.
El problema inicial
Una clínica pequeña gestiona las citas de pacientes por teléfono y WhatsApp. Las recepcionistas anotan reservas en una hoja compartida y, en ocasiones, también en papel. El sistema funciona mientras el volumen es bajo, pero empieza a fallar cuando crece la demanda.
Los principales problemas son estos:
- se producen solapamientos entre citas;
- algunos pacientes creen tener reserva, pero la clínica no la registró correctamente;
- no hay confirmación automática;
- cancelar o modificar citas consume mucho tiempo;
- no existe una trazabilidad clara del estado de cada reserva.
La dirección de la clínica quiere una aplicación web simple donde el paciente pueda reservar una cita, recibir confirmación y cancelar dentro de un plazo determinado. El equipo de desarrollo podría lanzarse a construir pantallas y endpoints, pero eso solo trasladaría la confusión actual al software. Aquí es donde entra el enfoque spec-driven.
Paso 1: especificación del problema de negocio
El primer paso no es técnico. Es definir con claridad qué necesidad existe y cuál es el resultado esperado.
Objetivo del sistema
Permitir que un paciente reserve una cita médica online eligiendo servicio, profesional y franja horaria disponible, evitando solapamientos y automatizando la confirmación.
Actores
- Paciente
- Recepción
- Profesional sanitario
- Sistema de reservas
Flujo principal esperado
- El paciente accede al formulario de reserva.
- Selecciona un servicio.
- Elige un profesional disponible.
- Escoge una fecha y una franja horaria.
- Introduce sus datos de contacto.
- Confirma la reserva.
- El sistema valida disponibilidad.
- Si todo es correcto, crea la reserva y envía confirmación.
Restricciones de negocio
- No pueden existir dos reservas confirmadas para el mismo profesional en la misma franja.
- Una reserva puede cancelarse solo hasta 24 horas antes de la cita.
- El sistema debe enviar un email de confirmación al crear la reserva.
- Cada reserva debe tener un estado claramente definido.
Hasta aquí no hemos escrito una sola línea de código, pero ya hemos reducido una gran parte de la ambigüedad.
Paso 2: especificación funcional
Ahora convertimos la necesidad de negocio en comportamiento funcional observable.
Historias funcionales
Como paciente, quiero elegir un servicio, un profesional y una franja horaria para reservar una cita sin llamar por teléfono.
Como clínica, quiero impedir reservas solapadas para evitar errores de agenda.
Como paciente, quiero recibir un email de confirmación para tener constancia de la cita.
Como clínica, quiero que la cancelación fuera de plazo no esté permitida para proteger la operativa del centro.
Reglas funcionales
- El sistema mostrará solo franjas disponibles.
- El paciente deberá proporcionar nombre, email y teléfono.
-
La reserva se creará con estado
confirmadasi la franja sigue libre en el momento de confirmar. - Si la franja ya no está disponible, el sistema devolverá un error claro.
- El paciente podrá cancelar desde un enlace seguro recibido por email, siempre que falten más de 24 horas para la cita.
Casos límite
- Dos pacientes intentan reservar la misma franja a la vez.
- El usuario intenta cancelar una cita cuando faltan menos de 24 horas.
- El paciente introduce un email inválido.
- El profesional no trabaja ese día.
- La franja existe, pero el servicio no está habilitado para ese profesional.
Aquí ya tenemos material suficiente para alinear negocio, producto y desarrollo.
Paso 3: especificación técnica
Ahora sí, la especificación empieza a tomar forma de sistema.
Entidades principales
Reserva
-
id -
patient_name -
patient_email -
patient_phone -
service_id -
professional_id -
appointment_date -
start_time -
end_time -
status -
created_at -
updated_at
Servicio
-
id -
name -
duration_minutes -
active
Profesional
-
id -
name -
active
Disponibilidad
-
professional_id -
weekday -
start_time -
end_time
Estados de reserva
-
pending -
confirmed -
cancelled
En este caso, para simplificar, podemos decidir que una reserva se confirme directamente tras la validación. Aun así, mantener los estados definidos desde el principio evita problemas futuros.
Contrato de API
Crear reserva
POST /api/reservasContent-Type: application/json
{ "patient_name": "Ana López", "patient_email": "ana@example.com", "patient_phone": "600123123", "service_id": 2, "professional_id": 8, "appointment_date": "2026-05-10", "start_time": "10:00"}
Respuesta exitosa
{ "id": 1542, "status": "confirmed", "message": "Reserva creada correctamente"}
Respuesta por franja ocupada
{ "error": "slot_unavailable", "message": "La franja seleccionada ya no está disponible"}
Cancelar reserva
POST /api/reservas/{id}/cancel
Respuesta exitosa de cancelación
{ "id": 1542, "status": "cancelled", "message": "Reserva cancelada correctamente"}
Respuesta por cancelación fuera de plazo
{ "error": "cancellation_not_allowed", "message": "No se puede cancelar con menos de 24 horas de antelación"}
Paso 4: criterios de aceptación
Aquí es donde el enfoque spec-driven empieza a aportar una diferencia enorme. El equipo deja de discutir opiniones y empieza a trabajar contra condiciones verificables.
Criterios de aceptación para crear una reserva
-
Dado un profesional disponible y una franja libre, cuando el paciente completa el formulario correctamente, entonces la reserva se crea con estado
confirmed. - Dada una franja ya ocupada, cuando otro paciente intenta reservarla, entonces el sistema rechaza la operación.
- Dado un email válido, cuando la reserva se crea correctamente, entonces el paciente recibe una confirmación.
- Dado un formulario incompleto, cuando el usuario intenta enviarlo, entonces el sistema devuelve errores de validación.
Criterios de aceptación para cancelar una reserva
-
Dada una reserva confirmada con más de 24 horas de antelación, cuando el paciente solicita cancelarla, entonces el sistema la marca como
cancelled. - Dada una reserva con menos de 24 horas de margen, cuando el paciente intenta cancelarla, entonces el sistema rechaza la operación.
Infografía del caso práctico
Paso 5: diseñar la implementación a partir de la spec
Una vez definida la especificación, implementar deja de ser una exploración caótica y pasa a ser una traducción controlada.
Podemos imaginar una arquitectura sencilla:
- backend en Laravel o Node;
- base de datos PostgreSQL o MySQL;
- frontend web con formulario de reserva;
- servicio de email transaccional;
- tests automáticos sobre reglas críticas.
Ejemplo de lógica de dominio
Pensemos en el caso central: crear una reserva. Antes de persistir nada, el sistema debe verificar disponibilidad real.
public function createReservation(array $data): Reservation{ $service = $this->serviceRepository->findOrFail($data['service_id']); $professional = $this->professionalRepository->findOrFail($data['professional_id']); $endTime = $this->timeCalculator->addMinutes( $data['start_time'], $service->duration_minutes ); $isAvailable = $this->availabilityService->isSlotAvailable( professionalId: $professional->id, date: $data['appointment_date'], startTime: $data['start_time'], endTime: $endTime ); if (!$isAvailable) { throw new SlotUnavailableException('La franja seleccionada ya no está disponible'); } $reservation = $this->reservationRepository->create([ 'patient_name' => $data['patient_name'], 'patient_email' => $data['patient_email'], 'patient_phone' => $data['patient_phone'], 'service_id' => $service->id, 'professional_id' => $professional->id, 'appointment_date' => $data['appointment_date'], 'start_time' => $data['start_time'], 'end_time' => $endTime, 'status' => 'confirmed', ]); $this->notificationService->sendConfirmationEmail($reservation); return $reservation;}
Lo importante no es el lenguaje. Lo importante es que la implementación nace directamente de la spec:
- la duración del servicio define la hora de fin;
- la disponibilidad se valida antes de crear;
- el estado queda definido;
- el email forma parte del flujo obligatorio.
Paso 6: tests derivados de la especificación
En un enfoque spec-driven, los tests no se improvisan. Se extraen de los criterios de aceptación.
Test 1: crea una reserva cuando la franja está libre
public function test_it_creates_a_reservation_when_slot_is_available(): void{ $payload = [ 'patient_name' => 'Ana López', 'patient_email' => 'ana@example.com', 'patient_phone' => '600123123', 'service_id' => 2, 'professional_id' => 8, 'appointment_date' => '2026-05-10', 'start_time' => '10:00', ]; $response = $this->postJson('/api/reservas', $payload); $response->assertStatus(201) ->assertJson([ 'status' => 'confirmed' ]); $this->assertDatabaseHas('reservations', [ 'patient_email' => 'ana@example.com', 'status' => 'confirmed' ]);}
Test 2: impide una reserva en franja ocupada
public function test_it_rejects_reservation_when_slot_is_taken(): void{ Reservation::factory()->create([ 'professional_id' => 8, 'appointment_date' => '2026-05-10', 'start_time' => '10:00', 'end_time' => '10:30', 'status' => 'confirmed', ]); $payload = [ 'patient_name' => 'Carlos Ruiz', 'patient_email' => 'carlos@example.com', 'patient_phone' => '611111111', 'service_id' => 2, 'professional_id' => 8, 'appointment_date' => '2026-05-10', 'start_time' => '10:00', ]; $response = $this->postJson('/api/reservas', $payload); $response->assertStatus(409) ->assertJson([ 'error' => 'slot_unavailable' ]);}
Test 3: no permite cancelar fuera de plazo
public function test_it_does_not_allow_late_cancellation(): void{ $reservation = Reservation::factory()->create([ 'appointment_date' => now()->addHours(10)->toDateString(), 'start_time' => now()->addHours(10)->format('H:i'), 'status' => 'confirmed', ]); $response = $this->postJson("/api/reservas/{$reservation->id}/cancel"); $response->assertStatus(422) ->assertJson([ 'error' => 'cancellation_not_allowed' ]);}
Aquí aparece una ventaja clave: cuando la spec está bien hecha, los tests salen casi solos.
Qué cambia realmente al trabajar así
La diferencia entre un desarrollo tradicional y uno guiado por especificación no está solo en el orden de trabajo. Cambia la calidad de la conversación dentro del equipo.
Con una spec clara:
- producto sabe exactamente qué se va a entregar;
- desarrollo entiende las reglas antes de implementarlas;
- QA dispone de criterios verificables;
- negocio puede validar el comportamiento sin discutir detalles técnicos;
- el retrabajo disminuye.
En este ejemplo, el equipo no discute después si el email era obligatorio, si se permitían solapamientos o si la cancelación fuera de plazo debía bloquearse. Todo eso estaba resuelto antes de escribir código.
Errores comunes cuando se intenta aplicar Spec-Driven Development
Aunque el enfoque es potente, también puede fracasar si se aplica mal.
El primer error es confundir especificación con documentación burocrática. Una spec útil no es un documento inflado; es una definición clara, accionable y verificable.
El segundo error es quedarse solo en lo funcional y no aterrizar lo técnico. Si no se define contrato, estados, validaciones y comportamiento esperado, la ambigüedad vuelve a aparecer durante la implementación.
El tercer error es no conectar la spec con los tests. Si la especificación no genera casos de validación, se convierte en texto bonito pero no en una herramienta de entrega.
Resultado del caso práctico
Una vez implementado este sistema con enfoque spec-driven, la clínica obtiene mejoras inmediatas:
- desaparecen los solapamientos de agenda;
- el paciente recibe confirmación automática;
- las reglas de cancelación quedan claras;
- el equipo puede ampliar el sistema sin romper el comportamiento base;
- la comunicación entre negocio y desarrollo mejora mucho.
Y lo más importante: el software deja de depender de interpretaciones personales. Lo que manda no es la memoria de una reunión ni la intuición del desarrollador de turno, sino una especificación compartida y validable.
Conclusión
Spec-Driven Development no consiste en escribir más documentos, sino en reducir incertidumbre antes de construir. Obliga a pensar mejor, alinear mejor y validar mejor. El resultado suele ser menos improvisación, menos retrabajo y un software mucho más predecible.
El caso de la clínica lo demuestra bien. Un problema que parecía simplemente “hacer un formulario de reservas” en realidad escondía reglas de negocio, estados, validaciones y criterios de aceptación que, sin una especificación clara, habrían terminado repartidos entre conversaciones, tickets y suposiciones.
Cuando la spec está bien definida, el desarrollo se vuelve más rápido no porque se piense menos, sino porque se piensa en el momento correcto.
Toni Domenech
