Contracts — API de agenda (Route Handlers Next.js)
Base: /api. Todos los cuerpos y errores en español de España. Errores: { "error": "mensaje claro", "codigo": "HUECO_OCUPADO | PASADO | ... " }.
GET /api/agenda?profesionalId=<uuid>&fecha=YYYY-MM-DD → 200 (US1, FR-013/FR-014)
Respuesta:
{
"profesional": { "id": "uuid", "nombre": "María", "especialidad": "fisioterapia" },
"fecha": "2026-10-05",
"jornada": { "inicio": "09:00", "fin": "20:00" },
"citas": [
{
"id": "uuid",
"inicio": "2026-10-05T10:00:00+02:00",
"fin": "2026-10-05T10:45:00+02:00",
"servicio": "sesión fisio",
"paciente": "Ana García",
"estado": "reservada",
"inicioTexto": "05/10/2026, 10:00",
"tramoTexto": "05/10/2026, 10:00–10:45",
"precioTexto": "40,00 €"
}
],
"huecosLibres": [{ "inicio": "2026-10-05T10:45:00+02:00", "fin": "2026-10-05T11:30:00+02:00" }]
}
Contrato: ordenadas por inicio; incluye vigentes y finales del día; resto de jornada como libre. 400 si fecha inválida.
POST /api/citas → 201 | 409 | 422 (US2, RN1+RN2)
Petición:
{
"profesionalId": "uuid",
"servicioId": "uuid",
"pacienteId": "uuid",
"inicio": "2026-10-05T10:00:00+02:00"
}
201:{ "id": "uuid", "inicio": "...", "fin": "...+30min", "estado": "reservada", "tramoTexto": "05/10/2026, 10:00–10:30" }.409 HUECO_OCUPADO:Ese tramo ya está ocupado(solape de rangos[inicio, fin)conreservada/completadamismo profesional, incl. carrera concurrente; entradas truncadas a minuto — FR-007).422 PASADO / FUERA_JORNADA / DIA_PARTIDO:No se pueden crear citas en el pasado/Fuera de la jornada 09:00–20:00/La cita debe pertenecer a un único día(FR-009 / FR-013 jornada-bounds).404: profesional/servicio/paciente inexistente.finlo calcula el servidor (FR-006).
PATCH /api/citas/[id]/estado → 200 | 409 | 422 (US3)
Petición: { "estado": "completada" | "cancelada" | "no_asistida" }.
200: cita en estado final.canceladalibera tramo.409 TRANSICION_INVALIDA: cualquier cambio noreservada → final, entre finales, ono_asistidasin incomparecencia desdereservada, con avisoes-ES.
Fichas y acceso (US4 + gestión básica)
POST /api/auth/panel { "clave": "…" }→200con cookie de sesión de clínica o401Clave incorrecta(genérico, sin bloqueo — Q4/A).GET/POST /api/profesionales|/servicios|/pacientesyPATCHcorrespondiente → CRUD básico;DELETEde ficha con citas ⇒409 FICHA_CON_CITAS(No se puede eliminar: tiene citas asociadas); crear servicio duplicado ⇒409 SERVICIO_DUPLICADO; paciente sin/inválido ⇒422(Q1/C, Q3/A, Q5/B).GET /api/semilla { "version": 1, "resumen": { "profesionales": 3, "servicios": 4, "pacientes": "38–42" } }para SC-005. Endpoint auxiliar de verificabilidad justificado por Constitución V (no es funcionalidad de clínica); alternativaseed:verificartambién válida.
OpenAPI simplificado y esquemas zod viven en lib/validacion.ts; estos .md son la referencia para contracts/ y los tests de contrato Vitest.