VitrinaAPI

Caja de la clínica

El turno en una sucursal: abrirlo con su fondo, cerrarlo contra un conteo y firmar el arqueo.

Beta
Puede cambiar en cualquier momento, con una entrada en el changelog y aviso a quienes la llamaron recientemente — ver versionado.

Descarga la proyección completa de la API pública: openapi.json.

Presupuestar un plan y cerrar caja explica este recurso en prosa, con ejemplos ejecutables.

MétodoRutaQué hace
GET/clinic/cash-sessionsList caja sessions
POST/clinic/cash-sessionsOpen the drawer
GET/clinic/cash-sessions/{id}One caja session and its payments
POST/clinic/cash-sessions/{id}/closeClose the drawer against a count
POST/clinic/cash-sessions/{id}/reconcileSign off a counted shift
GET/clinic/cash-sessions/currentThe open caja session at a sucursal
GET/clinic/cash-sessions/openList every open caja session

GET /clinic/cash-sessions

List caja sessions

The shift history, newest first, each with its expected vs counted and the stored difference (SIGNED: negative is short, positive is over) plus the reason somebody wrote for it. from/to are inclusive calendar days over the day the drawer was OPENED, so a late close that crossed midnight stays with the shift it belongs to.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParámetroEnTipoObligatorioRestricciones
location_idqueryuuidno
statusqueryopen \closed \reconciled
fromquerystringnopatrón `^\d4-(0[1-9]\
toquerystringnopatrón `^\d4-(0[1-9]\
limitqueryintegerno≥ 1, ≤ 200, por defecto 50
offsetqueryinteger | nullno≥ 0, por defecto 0
curl https://api.vitrinadev.com/api/v1/clinic/cash-sessions \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "items": [
      {
        "id": "d1d832c4-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
        "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
        "opened_at": "2026-09-22T22:21:16.863Z",
        "opening_float_clp": 30000,
        "closed_by_user_id": null,
        "closed_at": null,
        "expected_clp": null,
        "counted_clp": null,
        "difference_clp": null,
        "difference_reason": null,
        "status": "open",
        "note": "Turno mañana",
        "created_at": "2026-09-22T22:21:16.863Z",
        "updated_at": "2026-09-22T22:21:16.863Z",
        "location_name": "Sucursal Maipú",
        "running_expected_clp": 30000,
        "collected_clp": 0,
        "payments": 0,
        "by_instrument": []
      }
    ],
    "count": 1,
    "difference_clp": 0
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/cash-sessions

Open the drawer

One open session per sucursal, enforced by a partial unique index — a second attempt answers 409 rather than creating a parallel shift. Requires a signed-in user: opened_by_user_id is NOT NULL because «quién abrió la caja» is the point of a shift, so an api_key principal is refused rather than stored as a null nobody can chase.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

Cuerpo

CampoTipoObligatorioRestricciones
location_iduuid
opening_float_clpinteger≥ 0, ≤ 999999999999
notestring | nullmáx. 500
curl -X POST https://api.vitrinadev.com/api/v1/clinic/cash-sessions \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "opening_float_clp": 30000,
    "note": "Turno mañana"
  }'

Ejemplo de respuesta (201)

{
  "data": {
    "id": "d1d832c4-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
    "opened_at": "2026-09-22T22:21:16.863Z",
    "opening_float_clp": 30000,
    "closed_by_user_id": null,
    "closed_at": null,
    "expected_clp": null,
    "counted_clp": null,
    "difference_clp": null,
    "difference_reason": null,
    "status": "open",
    "note": "Turno mañana",
    "created_at": "2026-09-22T22:21:16.863Z",
    "updated_at": "2026-09-22T22:21:16.863Z",
    "location_name": "Sucursal Maipú",
    "running_expected_clp": 30000,
    "collected_clp": 0,
    "payments": 0,
    "by_instrument": []
  }
}

Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/cash-sessions/{id}

One caja session and its payments

The shift plus every payment stamped with it, grouped by instrument in by_instrument and listed in items. A payment is stamped at RECORD TIME while the session is open (payments design §10.1) and never back-filled — back-filling would move money between two already-counted shifts.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

curl https://api.vitrinadev.com/api/v1/clinic/cash-sessions/<id> \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "id": "d1d832c4-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
    "opened_at": "2026-09-22T22:21:16.863Z",
    "opening_float_clp": 30000,
    "closed_by_user_id": "11111111-0000-4000-8000-000000000001",
    "closed_at": "2026-09-22T22:21:17.127Z",
    "expected_clp": 30000,
    "counted_clp": 50000,
    "difference_clp": 20000,
    "difference_reason": "Vuelto de un pago en efectivo",
    "status": "closed",
    "note": "Turno mañana",
    "created_at": "2026-09-22T22:21:16.863Z",
    "updated_at": "2026-09-22T22:21:17.127Z",
    "location_name": "Sucursal Maipú",
    "running_expected_clp": 30000,
    "collected_clp": 0,
    "payments": 0,
    "by_instrument": [],
    "items": []
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/cash-sessions/{id}/close

Close the drawer against a count

⚠ THE BODY CARRIES NO expected_clp, AND CANNOT. The server computes the expectation FROM THE LEDGER (opening float + cash stamped with the session, reversed excluded) and returns the difference; a screen that could post its own expectation could hide a shortfall. A non-zero difference REQUIRES a written reason — refused in the service and by a CHECK.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

Cuerpo

CampoTipoObligatorioRestricciones
counted_clpinteger≥ 0, ≤ 999999999999
difference_reasonstring | nullmáx. 500
curl -X POST https://api.vitrinadev.com/api/v1/clinic/cash-sessions/<id>/close \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "counted_clp": 50000,
    "difference_reason": "Vuelto de un pago en efectivo"
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "id": "d1d832c4-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
    "opened_at": "2026-09-22T22:21:16.863Z",
    "opening_float_clp": 30000,
    "closed_by_user_id": "11111111-0000-4000-8000-000000000001",
    "closed_at": "2026-09-22T22:21:17.127Z",
    "expected_clp": 30000,
    "counted_clp": 50000,
    "difference_clp": 20000,
    "difference_reason": "Vuelto de un pago en efectivo",
    "status": "closed",
    "note": "Turno mañana",
    "created_at": "2026-09-22T22:21:16.863Z",
    "updated_at": "2026-09-22T22:21:17.127Z",
    "location_name": "Sucursal Maipú",
    "running_expected_clp": 30000,
    "collected_clp": 0,
    "payments": 0,
    "by_instrument": [],
    "items": []
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

POST /clinic/cash-sessions/{id}/reconcile

Sign off a counted shift

The supervisory acceptance of a shift that has already been counted. Only a closed session can be reconciled.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

Cuerpo

CampoTipoObligatorioRestricciones
notestring | nullmáx. 500
curl -X POST https://api.vitrinadev.com/api/v1/clinic/cash-sessions/<id>/reconcile \
  -H "Authorization: Bearer $VITRINA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "note": "Revisado con el arqueo del turno"
  }'

Ejemplo de respuesta (200)

{
  "data": {
    "id": "d1d832c4-0000-4000-8000-000000000001",
    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
    "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
    "opened_at": "2026-09-22T22:21:16.863Z",
    "opening_float_clp": 30000,
    "closed_by_user_id": "11111111-0000-4000-8000-000000000001",
    "closed_at": "2026-09-22T22:21:17.127Z",
    "expected_clp": 30000,
    "counted_clp": 50000,
    "difference_clp": 20000,
    "difference_reason": "Vuelto de un pago en efectivo",
    "status": "reconciled",
    "note": "Revisado con el arqueo del turno",
    "created_at": "2026-09-22T22:21:16.863Z",
    "updated_at": "2026-09-22T22:21:17.146Z",
    "location_name": "Sucursal Maipú",
    "running_expected_clp": 30000,
    "collected_clp": 0,
    "payments": 0,
    "by_instrument": [],
    "items": []
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/cash-sessions/current

The open caja session at a sucursal

The shift at location_id, with running_expected_clp — what the LEDGER says should be in the drawer right now (opening float + cash taken, reversed payments excluded) — and the takings split by instrument. session: null is a 200 and the ordinary morning state, not a 404. Only EFECTIVO counts toward the expectation: a card or a transfer taken at the counter belongs to the shift but never reaches the till.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

ParámetroEnTipoObligatorioRestricciones
location_idqueryuuid
curl https://api.vitrinadev.com/api/v1/clinic/cash-sessions/current \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "session": {
      "id": "d1d832c4-0000-4000-8000-000000000001",
      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
      "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
      "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
      "opened_at": "2026-09-22T22:21:16.863Z",
      "opening_float_clp": 30000,
      "closed_by_user_id": null,
      "closed_at": null,
      "expected_clp": null,
      "counted_clp": null,
      "difference_clp": null,
      "difference_reason": null,
      "status": "open",
      "note": "Turno mañana",
      "created_at": "2026-09-22T22:21:16.863Z",
      "updated_at": "2026-09-22T22:21:16.863Z",
      "location_name": "Sucursal Maipú",
      "running_expected_clp": 30000,
      "collected_clp": 0,
      "payments": 0,
      "by_instrument": []
    }
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

GET /clinic/cash-sessions/open

List every open caja session

One per sucursal at most — a partial unique index enforces it. What the sidebar badge reads.

Connected apps: every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, "M.F. · #1001" — unless the clinic allowed patient names, with RUT, phone and email masked in free text and meta.patient_privacy saying so. Clinical alerts (flags) are withheld in both modes, and a non-JSON body (an export, a file) is refused with 403 CONNECTED_APP_SENSITIVE_DATA.

curl https://api.vitrinadev.com/api/v1/clinic/cash-sessions/open \
  -H "Authorization: Bearer $VITRINA_KEY"

Ejemplo de respuesta (200)

{
  "data": {
    "items": [
      {
        "id": "d1d832c4-0000-4000-8000-000000000001",
        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
        "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
        "opened_at": "2026-09-22T22:21:16.863Z",
        "opening_float_clp": 30000,
        "closed_by_user_id": null,
        "closed_at": null,
        "expected_clp": null,
        "counted_clp": null,
        "difference_clp": null,
        "difference_reason": null,
        "status": "open",
        "note": "Turno mañana",
        "created_at": "2026-09-22T22:21:16.863Z",
        "updated_at": "2026-09-22T22:21:16.863Z",
        "location_name": "Sucursal Maipú"
      }
    ]
  }
}

Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429

En esta página