VitrinaAPI

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:

  1. Profesionales: quién atiende, con qué especialidad.
  2. Prestaciones: qué se hace, cuánto dura, cuánto vale de lista.
  3. Precios: aranceles, reglas y convenios. El precio de una prestación para un paciente sale de la cotización.
  4. 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:

EventoCuándo
clinic_professional.created · .updated · .deletedCambió la nómina
clinic_service.created · .updated · .deletedCambió una prestación (el .updated trae el precio anterior)
clinic_price_list.updatedSe editó un arancel, o las prestaciones que tiene dentro
clinic_pack_purchase.created · .session_consumed · .cancelledUn 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.

En esta página