Definir prestaciones y precios
Qué ofrece la clínica, quién lo hace y cuánto cuesta para cada paciente.
ClínicasSolo en los workspaces de clínicas.
GET /clinic/services lista lo que la clínica ofrece y GET /clinic/professionals quién lo hace. La agenda se apoya en ese catálogo: una cita es una prestación con un profesional a una hora. Los presupuestos salen del mismo lugar.
Cuatro recursos, en el orden en que se arman:
- Profesionales: quién atiende, con qué especialidad.
- Prestaciones: qué se hace, cuánto dura, cuánto vale de lista.
- Precios: aranceles, reglas y convenios. El precio de una prestación para un paciente sale de la cotización.
- Packs: sesiones vendidas por adelantado y consumidas después.
Todo esto vive en Vitrina cuando la clínica usa su propio catálogo. Si la clínica trabaja con Medilink, Dentalink o Reservo, las filas llegan reflejadas desde ese sistema y las escrituras responden 409 not_native_source. Se edita en el sistema que manda. Cada fila dice de dónde viene en source.
Profesionales
curl https://api.vitrinadev.com/api/v1/clinic/professionals \
-H "Authorization: Bearer $VITRINA_KEY"{
"data": {
"data": [
{
"id": "18342d1b-0000-4000-8000-000000000001",
"source": "native",
"nombre": "Ana",
"apellidos": "Rojas Vidal",
"rut": "12.345.678-5",
"email": "[email protected]",
"especialidad": "Ortodoncia",
"registro": "SIS 123456",
"agenda_online": true,
"intervalo_minutes": 30,
"active": true
}
],
"total": 1,
"page": 1,
"limit": 50
}
}El id es el que usa la agenda: professional_id en el feed, en las horas libres y al reservar. agenda_online dice si aparece en la reserva online. intervalo_minutes es el largo por defecto de su bloque.
Datos personales del profesional
La ficha del profesional guarda datos suyos: RUT, fecha de nacimiento,
dirección. Una credencial del workspace los recibe; una aplicación
conectada no. rut, birthdate y direccion no viajan a un tercero,
porque agendar no los necesita. El nombre, la especialidad, el registro y el
contacto de trabajo sí, que es para lo que existe el listado.
Crear, editar y eliminar piden clinic:write. Eliminar un profesional es definitivo y sus citas pasadas lo siguen nombrando por id.
Prestaciones
curl -X POST https://api.vitrinadev.com/api/v1/clinic/services \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: svc-ort-ctrl-2026" \
-d '{
"nombre": "Control de ortodoncia",
"codigo": "ORT-CTRL",
"categoria": "Ortodoncia",
"precio": 25000,
"duration_minutes": 30,
"online_bookable": true
}'precio es el precio de lista de la prestación, el punto de partida. duration_minutes es lo que bloquea en la agenda. online_bookable la ofrece en la reserva online. requires_consent_template_id exige un consentimiento firmado antes de ejecutarla, y eligibility_* limita a quién se le puede agendar por edad, sexo o previsión.
Las categorías (/clinic/service-categories) y las especialidades (/clinic/specialties) son listas cortas que ordenan el catálogo. Ambas se crean con un nombre, y si ya existe una con ese nombre devuelven la que hay en vez de fallar.
PUT /clinic/services/{id}/professionals fija quién puede ejecutar una prestación, y con qué duración y precio propios. Una lista vacía significa «cualquiera», no «nadie».
Precios: el arancel, las reglas y el convenio
Un arancel (/clinic/pricing/lists) es una página de precios: una lista con un precio por prestación. Las entradas se guardan de una vez:
curl -X PUT https://api.vitrinadev.com/api/v1/clinic/pricing/lists/$LIST/entries \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"entries": [
{ "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001", "precio_clp": 28000 },
{ "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002", "precio_clp": 35000 }
]
}'precio_clp: null quita la entrada. «Este arancel no le pone precio» y «vale cero» son cosas distintas.
Una regla (/clinic/pricing/rules) modifica ese precio para un caso: un convenio, una previsión, una campaña, un profesional, la antigüedad del paciente. Lleva modifier (percent_off, amount_off, override), value y priority. La prioridad decide cuál gana cuando dos aplican.
Un convenio (/clinic/pricing/agreements) es el acuerdo con una organización, y sus miembros son los pacientes que lo llevan. Agregar un miembro nombra a un paciente, así que una aplicación conectada lee esa lista con seudónimos.
La pregunta que importa es cuánto le cuesta a esta persona, y la responde la cotización. Además explica de dónde salió el número:
curl -X POST https://api.vitrinadev.com/api/v1/clinic/pricing/quote \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"service_id": "4cf5bc59-0000-4000-8000-000000000001",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"professional_id": "18342d1b-0000-4000-8000-000000000001"
}'La respuesta trae el precio final, el arancel que se usó y las reglas que se aplicaron, en orden. Es una lectura (clinic:read): no reserva ni cobra nada. Úsala antes de mostrar un precio en tu sistema, en vez de multiplicar el precio de lista por tu cuenta.
Packs
Un pack es la definición: cuántas sesiones, de qué prestaciones, a qué precio y por cuánto tiempo. Una compra (/clinic/packs/purchases) es ese pack vendido a un paciente, y es la que lleva el saldo:
{
"data": {
"purchase": {
"id": "d376ca29-0000-4000-8000-000000000001",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"name_snapshot": "Pack 4 controles de ortodoncia",
"session_count": 4,
"precio_clp": 96000,
"expires_at": "2027-03-21",
"status": "active"
},
"balance": { "session_count": 4, "consumed": 0, "remaining": 4 }
}
}name_snapshot y precio_clp quedan congelados en la compra. Si mañana el pack sube de precio o cambia de nombre, lo comprado no se mueve.
El saldo es derivado de las sesiones consumidas menos las revertidas, nunca un contador guardado. Por eso POST .../consume y POST .../sessions/{sessionId}/reverse son las dos únicas formas de moverlo, y una reversión es una fila nueva que nunca borra la anterior.
Congelar (/freeze) detiene el reloj de vencimiento mientras el paciente no puede venir. Anular (/cancel) termina la compra con su motivo, con o sin devolución, y se lleva las sesiones que quedaban.
Vender una compra pide clinic_money:write, porque es dinero. Consumir una sesión pide clinic:write, porque es la agenda del día.
Eventos
Cada cambio del catálogo tiene su evento, para no tener que revisar el catálogo entero cada noche:
| Evento | Cuándo |
|---|---|
clinic_professional.created · .updated · .deleted | Cambió la nómina |
clinic_service.created · .updated · .deleted | Cambió una prestación (el .updated trae el precio anterior) |
clinic_price_list.updated | Se editó un arancel, o las prestaciones que tiene dentro |
clinic_pack_purchase.created · .session_consumed · .cancelled | Un paciente compró un pack, usó una sesión o la compra se anuló |
Los eventos de pack nombran al paciente por id y nunca por nombre. Para saber quién es hay que leer el recurso con una credencial que pueda, y cómo suscribirse está en Webhooks.