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.
Líneas
Una Línea es una entrada de la lista corta que la clínica usa para leer lo que buscan sus pacientes: Implantes, Estética, Endodoncia, TTM. Suelen ser menos que las especialidades del equipo, que a veces vienen repetidas o desordenadas desde el sistema de la clínica.
GET /clinic/lineas devuelve la lista, que Vitrina arma sola la primera vez a partir de las especialidades del equipo y que la clínica edita (PATCH /clinic/lineas/{id} renombra o archiva, POST /clinic/lineas/{id}/merge une dos). Cada especialidad y cada anuncio pertenece a una sola Línea:
- Una especialidad se coloca con
PUT /clinic/lineas/specialties/{id}. Las que ninguna Línea tiene aún vienen enunmapped_specialties. - Un anuncio se coloca por un fragmento de su nombre (
POST /clinic/lineas/{id}/rules): si el nombre de la campaña, del conjunto de anuncios o del anuncio lo contiene, es de esa Línea. Los anuncios sin Línea vienen enadsconlinea_id: null.
Cada lead lleva exactamente una Línea (linea en el lead, con source). Nace del anuncio que abrió la conversación (ad), la primera visita agendada la afina según su prestación (prestacion), es General si nada dice más (default), y el equipo la corrige con PUT /leads/{id}/linea (staff). Lo que una persona elige es definitivo: nada automático lo cambia después. GET /leads y el tablero filtran con linea_id.
curl https://api.vitrinadev.com/api/v1/clinic/lineas \
-H "Authorization: Bearer $VITRINA_KEY"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.