Presupuestos de la clínica
Presupuestos con folio, sus líneas con el precio congelado y los planes de tratamiento que ordenan lo que se va a hacer.
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étodo | Ruta | Qué hace |
|---|---|---|
DELETE | /clinic/budget-items/{id} | Remove a line from a DRAFT |
PATCH | /clinic/budget-items/{id} | Edit a line of a DRAFT |
GET | /clinic/budgets | List presupuestos (the conversion funnel) |
POST | /clinic/budgets | Create a presupuesto (draft) |
DELETE | /clinic/budgets/{id} | Throw away a DRAFT |
GET | /clinic/budgets/{id} | One presupuesto, by UUID or folio |
PATCH | /clinic/budgets/{id} | Edit a DRAFT presupuesto |
POST | /clinic/budgets/{id}/accept | Accept it (STAFF path) |
POST | /clinic/budgets/{id}/accept-and-collect | Collect a payment and accept the lines it pays (the counter) |
POST | /clinic/budgets/{id}/items | Add a line, freezing its price |
GET | /clinic/budgets/{id}/lineage | Every version of one proposal |
POST | /clinic/budgets/{id}/reject | Record the patient declining |
GET | /clinic/budgets/{id}/render | The presupuesto as a PDF |
POST | /clinic/budgets/{id}/send | Send it to the patient |
POST | /clinic/budgets/{id}/supersede | Write a new version (Nueva versión) |
POST | /clinic/budgets/{id}/void | Void it — never DELETE |
POST | /clinic/budgets/from-mirror-plan | Create a presupuesto (draft) from a mirrored Dentalink plan |
GET | /clinic/budgets/mirror-plans | A patient's mirrored Dentalink plans, for «Crear desde Dentalink» |
GET | /clinic/budgets/summary | The COMERCIAL badge and funnel, counted by lineage |
GET | /clinic/imported-balances | «Saldos pendientes de Dentalink»: what patients still owe on imported presupuestos |
GET | /clinic/imported-balances/{budget_id} | One imported presupuesto’s Dentalink balance and default payer |
POST | /clinic/imported-balances/{budget_id}/collect | Collect a payment (or apply credit) against a Dentalink balance |
GET | /clinic/patients/{id}/treatment-progress | A patient's treatment progress |
POST | /clinic/treatment-plan-items/{id}/status | Move one phase item along |
GET | /clinic/treatment-plans | List treatment plans, with phase progress |
POST | /clinic/treatment-plans | Create a treatment plan by hand |
DELETE | /clinic/treatment-plans/{id} | Delete a plan |
GET | /clinic/treatment-plans/{id} | One plan with its phases and progress |
PATCH | /clinic/treatment-plans/{id} | Edit a plan |
POST | /clinic/treatment-plans/{id}/items | Add prestaciones to an open plan |
DELETE /clinic/budget-items/{id}
Remove a line from a DRAFT
Remove a line from a DRAFT
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl -X DELETE https://api.vitrinadev.com/api/v1/clinic/budget-items/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Responde: 204 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /clinic/budget-items/{id}
Edit a line of a DRAFT
Edit a line of a DRAFT
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
label | string | — | mín. 1, máx. 200 |
quantity | integer | — | ≥ 1, ≤ 999 |
unit_price_clp | integer | — | ≥ 0, ≤ 9999999999 |
discount_clp | integer | — | ≥ 0, ≤ 9999999999 |
tax_treatment | afecto \ | exento \ | no_gravado |
phase | integer | — | ≥ 1, ≤ 99 |
note | string | null | — | máx. 500 |
sort_order | integer | — | ≥ 0, ≤ 999 |
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/budget-items/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"quantity": 2
}'Ejemplo de respuesta (200)
{
"data": {
"id": "3a7d9767-0000-4000-8000-000000000006",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"pack_id": null,
"label": "Limpieza dental",
"quantity": 2,
"unit_price_clp": 35000,
"discount_clp": 0,
"total_clp": 70000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 35000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 3,
"note": "Control final",
"sort_order": 2,
"created_at": "2026-09-22T22:21:15.470Z",
"updated_at": "2026-09-22T22:21:15.481Z"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/budgets
List presupuestos (the conversion funnel)
Quotes newest first, with their lines, the patient and the professional. ?live=true is the working set (borrador + enviado); ?status= picks one column. A voided presupuesto stays listable — a correlative series with a hole in it cannot be audited. Every row carries its LINEAGE (lineage_id, version, lineage_size, reissued_from_id, reissued_by_id) and the patient's «visto» (first_viewed_at, last_viewed_at). ?group_by=lineage returns ONE row per lineage — its latest version that is neither a draft nor voided (a draft only when the lineage has nothing else) — and the other filters then read that version; ?lineage_id= lists one lineage's versions.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
status | query | draft \ | sent \ | accepted \ |
live | query | true \ | false | no |
patient_id | query | uuid | no | |
contact_id | query | uuid | no | |
professional_id | query | uuid | no | |
location_id | query | uuid | no | |
treatment_plan_id | query | uuid | no | |
lineage_id | query | uuid | no | |
group_by | query | lineage | no | |
page | query | integer | no | ≥ 1, por defecto 1 |
limit | query | integer | no | ≥ 1, ≤ 200, por defecto 50 |
curl https://api.vitrinadev.com/api/v1/clinic/budgets \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"items": [
{
"id": "81e4a552-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"display_seq": 1,
"display_id": "E-1",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"lead_id": null,
"lineage_id": "81e4a552-0000-4000-8000-000000000001",
"reissued_from_id": null,
"conversation_id": null,
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"price_list_id": null,
"agreement_id": null,
"legal_entity_id": null,
"status": "draft",
"subtotal_clp": 147000,
"discount_clp": 5000,
"total_clp": 142000,
"tax_treatment": "afecto",
"valid_until": "2026-12-31",
"installments": 3,
"notes": "Incluye los cuatro controles del primer semestre",
"obligation_group_id": null,
"rendered_document_id": null,
"created_by_user_id": null,
"sent_at": null,
"accepted_at": null,
"accepted_via": null,
"rejected_at": null,
"rejected_reason": null,
"expired_at": null,
"voided_at": null,
"voided_by": null,
"voided_reason": null,
"created_at": "2026-09-22T22:21:15.369Z",
"updated_at": "2026-09-22T22:21:15.412Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000004",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"pack_id": null,
"label": "Control de ortodoncia",
"quantity": 4,
"unit_price_clp": 28000,
"discount_clp": 0,
"total_clp": 112000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 28000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 1,
"note": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:15.392Z",
"updated_at": "2026-09-22T22:21:15.392Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-000000000005",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"pack_id": null,
"label": "Limpieza dental",
"quantity": 1,
"unit_price_clp": 35000,
"discount_clp": 5000,
"total_clp": 30000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 35000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 2,
"note": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:15.410Z",
"updated_at": "2026-09-22T22:21:15.410Z",
"service_name": "Limpieza dental"
}
],
"is_live": true,
"days_left": 100
}
],
"total": 1,
"page": 1,
"limit": 50
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/budgets
Create a presupuesto (draft)
Allocates the E- folio and freezes each line's price through the quote resolver — the arancel, the convenio and the professional surcharge are read ONCE and stored with the explanation that produced them. unit_price_clp on a line is the human override; omit it and the resolver decides. Always lands in draft. With reissued_from_id it is a NEW VERSION of that presupuesto and follows exactly the rules of POST /clinic/budgets/\{id\}/supersede: the draft copies the predecessor's header and lines (accepted lines carried), the header fields given override the copied ones, and items are added after the copied lines.
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
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
clinic_patient_id | uuid | sí | |
contact_id | string | null | — | |
professional_id | string | null | — | |
location_id | string | null | — | |
price_list_id | string | null | — | |
agreement_id | string | null | — | |
conversation_id | string | null | — | |
lead_id | string | null | — | |
treatment_plan_id | string | null | — | |
reissued_from_id | string | null | — | |
tax_treatment | afecto \ | exento \ | no_gravado |
valid_until | string | null | — | patrón ^\d{4}-\d{2}-\d{2}$ |
installments | integer | null | — | ≥ 1, ≤ 48 |
installments_first_due | string | null | — | patrón ^\d{4}-\d{2}-\d{2}$ |
notes | string | null | — | máx. 2000 |
items | object[] | — |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"valid_until": "2026-12-31",
"installments": 3,
"notes": "Incluye los cuatro controles del primer semestre",
"items": [
{
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"quantity": 4,
"phase": 1
},
{
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"quantity": 1,
"phase": 2,
"discount_clp": 5000
}
]
}'Ejemplo de respuesta (201)
{
"data": {
"id": "81e4a552-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"display_seq": 1,
"display_id": "E-1",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"lead_id": null,
"lineage_id": "81e4a552-0000-4000-8000-000000000001",
"reissued_from_id": null,
"conversation_id": null,
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"price_list_id": null,
"agreement_id": null,
"legal_entity_id": null,
"status": "draft",
"subtotal_clp": 147000,
"discount_clp": 5000,
"total_clp": 142000,
"tax_treatment": "afecto",
"valid_until": "2026-12-31",
"installments": 3,
"notes": "Incluye los cuatro controles del primer semestre",
"obligation_group_id": null,
"rendered_document_id": null,
"created_by_user_id": null,
"sent_at": null,
"accepted_at": null,
"accepted_via": null,
"rejected_at": null,
"rejected_reason": null,
"expired_at": null,
"voided_at": null,
"voided_by": null,
"voided_reason": null,
"created_at": "2026-09-22T22:21:15.369Z",
"updated_at": "2026-09-22T22:21:15.412Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000004",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"pack_id": null,
"label": "Control de ortodoncia",
"quantity": 4,
"unit_price_clp": 28000,
"discount_clp": 0,
"total_clp": 112000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 28000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 1,
"note": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:15.392Z",
"updated_at": "2026-09-22T22:21:15.392Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-000000000005",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"pack_id": null,
"label": "Limpieza dental",
"quantity": 1,
"unit_price_clp": 35000,
"discount_clp": 5000,
"total_clp": 30000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 35000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 2,
"note": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:15.410Z",
"updated_at": "2026-09-22T22:21:15.410Z",
"service_name": "Limpieza dental"
}
],
"is_live": true,
"days_left": 100
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
DELETE /clinic/budgets/{id}
Throw away a DRAFT
Only a draft. Anything that has been sent is voided (POST /clinic/budgets/\{id\}/void) and keeps its folio — ADR 0058 §10.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl -X DELETE https://api.vitrinadev.com/api/v1/clinic/budgets/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Responde: 204 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/budgets/{id}
One presupuesto, by UUID or folio
\{id\} accepts a UUID or the folio (E-47).
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | any | sí |
curl https://api.vitrinadev.com/api/v1/clinic/budgets/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "81e4a552-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"display_seq": 1,
"display_id": "E-1",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"lead_id": null,
"lineage_id": "81e4a552-0000-4000-8000-000000000001",
"reissued_from_id": null,
"conversation_id": null,
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"price_list_id": null,
"agreement_id": null,
"legal_entity_id": null,
"status": "draft",
"subtotal_clp": 147000,
"discount_clp": 5000,
"total_clp": 142000,
"tax_treatment": "afecto",
"valid_until": "2026-12-31",
"installments": 3,
"notes": "Incluye los cuatro controles del primer semestre",
"obligation_group_id": null,
"rendered_document_id": null,
"created_by_user_id": null,
"sent_at": null,
"accepted_at": null,
"accepted_via": null,
"rejected_at": null,
"rejected_reason": null,
"expired_at": null,
"voided_at": null,
"voided_by": null,
"voided_reason": null,
"created_at": "2026-09-22T22:21:15.369Z",
"updated_at": "2026-09-22T22:21:15.412Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000004",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"pack_id": null,
"label": "Control de ortodoncia",
"quantity": 4,
"unit_price_clp": 28000,
"discount_clp": 0,
"total_clp": 112000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 28000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 1,
"note": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:15.392Z",
"updated_at": "2026-09-22T22:21:15.392Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-000000000005",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"pack_id": null,
"label": "Limpieza dental",
"quantity": 1,
"unit_price_clp": 35000,
"discount_clp": 5000,
"total_clp": 30000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 35000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 2,
"note": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:15.410Z",
"updated_at": "2026-09-22T22:21:15.410Z",
"service_name": "Limpieza dental"
}
],
"is_live": true,
"days_left": 100
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /clinic/budgets/{id}
Edit a DRAFT presupuesto
Header fields only, and only while the quote is a draft — a sent one answers 409 rather than silently ignoring the patch. Moving the sucursal moves the razón social with it.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
contact_id | string | null | — | |
professional_id | string | null | — | |
location_id | string | null | — | |
price_list_id | string | null | — | |
agreement_id | string | null | — | |
conversation_id | string | null | — | |
lead_id | string | null | — | |
treatment_plan_id | string | null | — | |
tax_treatment | afecto \ | exento \ | no_gravado |
valid_until | string | null | — | patrón ^\d{4}-\d{2}-\d{2}$ |
installments | integer | null | — | ≥ 1, ≤ 48 |
installments_first_due | string | null | — | patrón ^\d{4}-\d{2}-\d{2}$ |
notes | string | null | — | máx. 2000 |
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/budgets/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"notes": "Incluye los cuatro controles y la limpieza inicial"
}'Ejemplo de respuesta (200)
{
"data": {
"id": "81e4a552-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"display_seq": 1,
"display_id": "E-1",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"lead_id": null,
"lineage_id": "81e4a552-0000-4000-8000-000000000001",
"reissued_from_id": null,
"conversation_id": null,
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"price_list_id": null,
"agreement_id": null,
"legal_entity_id": null,
"status": "draft",
"subtotal_clp": 147000,
"discount_clp": 5000,
"total_clp": 142000,
"tax_treatment": "afecto",
"valid_until": "2026-12-31",
"installments": 3,
"notes": "Incluye los cuatro controles y la limpieza inicial",
"obligation_group_id": null,
"rendered_document_id": null,
"created_by_user_id": null,
"sent_at": null,
"accepted_at": null,
"accepted_via": null,
"rejected_at": null,
"rejected_reason": null,
"expired_at": null,
"voided_at": null,
"voided_by": null,
"voided_reason": null,
"created_at": "2026-09-22T22:21:15.369Z",
"updated_at": "2026-09-22T22:21:15.445Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000004",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"pack_id": null,
"label": "Control de ortodoncia",
"quantity": 4,
"unit_price_clp": 28000,
"discount_clp": 0,
"total_clp": 112000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 28000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 1,
"note": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:15.392Z",
"updated_at": "2026-09-22T22:21:15.392Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-000000000005",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"pack_id": null,
"label": "Limpieza dental",
"quantity": 1,
"unit_price_clp": 35000,
"discount_clp": 5000,
"total_clp": 30000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 35000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 2,
"note": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:15.410Z",
"updated_at": "2026-09-22T22:21:15.410Z",
"service_name": "Limpieza dental"
}
],
"is_live": true,
"days_left": 100
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/budgets/{id}/accept
Accept it (STAFF path)
Stamps accepted_via: "staff" — the route decides, never the body. Accepts every line still presented, or (#2448) only item_ids: status: "accepted" means AT LEAST ONE line accepted, and the lines not yet accepted stay presented — the patient link keeps working for them, staff can accept more later, and valid_until still applies to them. Opens an obligation_group of kind presupuesto (one per lineage) plus one obligation per NEWLY accepted line in the payments ledger, creates or grows the treatment plan, and moves the lead to «Aceptado». Idempotent: a call with nothing left to accept returns the same objects with accepted_now: false; accepted_item_ids names the lines this call accepted. A presupuesto imported from Dentalink is never accepted: 409 imported_balance_pending while it carries a balance (collected via /clinic/imported-balances), else imported_budget_readonly (write a new native presupuesto to continue the treatment). An AGENT can reach neither this route nor the public one.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
item_ids | uuid[] | — |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/accept \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{}'Ejemplo de respuesta (200)
{
"data": {
"budget": {
"id": "81e4a552-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"display_seq": 2,
"display_id": "E-2",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"lead_id": "8cb33b27-0000-4000-8000-000000000001",
"lineage_id": "81e4a552-0000-4000-8000-000000000002",
"reissued_from_id": null,
"conversation_id": null,
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"price_list_id": null,
"agreement_id": null,
"legal_entity_id": null,
"status": "accepted",
"subtotal_clp": 147000,
"discount_clp": 5000,
"total_clp": 142000,
"tax_treatment": "afecto",
"valid_until": "2026-12-31",
"installments": 3,
"notes": "Incluye los cuatro controles del primer semestre",
"obligation_group_id": "a067dce6-0000-4000-8000-000000000002",
"rendered_document_id": null,
"created_by_user_id": null,
"sent_at": "2026-09-22T22:21:15.544Z",
"accepted_at": "2026-09-22T22:21:16.040Z",
"accepted_via": "staff",
"rejected_at": null,
"rejected_reason": null,
"expired_at": null,
"voided_at": null,
"voided_by": null,
"voided_reason": null,
"created_at": "2026-09-22T22:21:15.502Z",
"updated_at": "2026-09-22T22:21:16.048Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000007",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000002",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"pack_id": null,
"label": "Control de ortodoncia",
"quantity": 4,
"unit_price_clp": 28000,
"discount_clp": 0,
"total_clp": 112000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 28000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 1,
"note": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:15.515Z",
"updated_at": "2026-09-22T22:21:15.515Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-000000000008",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000002",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"pack_id": null,
"label": "Limpieza dental",
"quantity": 1,
"unit_price_clp": 35000,
"discount_clp": 5000,
"total_clp": 30000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 35000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 2,
"note": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:15.530Z",
"updated_at": "2026-09-22T22:21:15.530Z",
"service_name": "Limpieza dental"
}
],
"is_live": false,
"days_left": 100
},
"plan": {
"id": "1ccffee2-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"name": "Ortodoncia fija superior e inferior",
"diagnosis": "Apiñamiento anterosuperior moderado",
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"status": "proposed",
"accepted_at": null,
"completed_at": null,
"abandoned_reason": null,
"created_at": "2026-09-22T22:21:15.213Z",
"updated_at": "2026-09-22T22:21:15.285Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"phase": 1,
"quantity": 4,
"tooth_or_zone": null,
"status": "scheduled",
"appointment_id": "d93e8eec-0000-4000-8000-000000000001",
"budget_item_id": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:15.215Z",
"updated_at": "2026-09-22T22:21:15.337Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-000000000003",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"phase": 2,
"quantity": 1,
"tooth_or_zone": null,
"status": "pending",
"appointment_id": null,
"budget_item_id": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:15.215Z",
"updated_at": "2026-09-22T22:21:15.215Z",
"service_name": "Limpieza dental"
}
],
"progress": {
"pending": 1,
"scheduled": 4,
"performed": 0,
"cancelled": 0,
"active": 5,
"percent": 0,
"phases": [
{
"phase": 1,
"pending": 0,
"scheduled": 4,
"performed": 0,
"cancelled": 0,
"active": 4,
"percent": 0
},
{
"phase": 2,
"pending": 1,
"scheduled": 0,
"performed": 0,
"cancelled": 0,
"active": 1,
"percent": 0
}
],
"current_phase": 1
}
},
"obligation_group_id": "a067dce6-0000-4000-8000-000000000002",
"obligation_ids": [
"b44936d0-0000-4000-8000-000000000002",
"b44936d0-0000-4000-8000-000000000003"
],
"accepted_now": true,
"accepted_item_ids": [
"3a7d9767-0000-4000-8000-000000000007",
"3a7d9767-0000-4000-8000-000000000008"
]
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/budgets/{id}/accept-and-collect
Collect a payment and accept the lines it pays (the counter)
Reception's one gesture (#2448): the lines in item_ids become accepted (accepted_via: "staff", stamped with the cashier), their obligations open in the presupuesto group, and the payment is recorded and allocated to exactly those obligations — in ONE transaction, so a refused payment or allocation leaves the lines presented. The payer is the budget's contact_id (409 payer_unknown without one) and the caja its location_id. Money beyond the lines' total stays as the patient's credit; less leaves the last line partly paid. A POS swipe is a tarjeta* instrument with source: "card_terminal". Requires BOTH clinic_money:write and payments:write. 409 nothing_to_accept when the lines are already accepted, expired / not_sent like accept, and imported_balance_pending / imported_budget_readonly for a presupuesto imported from Dentalink (never accepted here). Idempotency-Key is stored on the payment. An AGENT cannot reach it.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
item_ids | uuid[] | sí | |
payment | object | sí |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/accept-and-collect \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"item_ids": [
"3a7d9767-0000-4000-8000-000000000007"
],
"payment": {
"instrument": "tarjeta_debito",
"source": "card_terminal",
"amount_clp": 112000
}
}'Ejemplo de respuesta (201)
{
"data": {
"budget": {
"id": "81e4a552-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"display_seq": 2,
"display_id": "E-2",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"lead_id": "8cb33b27-0000-4000-8000-000000000001",
"lineage_id": "81e4a552-0000-4000-8000-000000000002",
"reissued_from_id": null,
"conversation_id": null,
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"price_list_id": null,
"agreement_id": null,
"legal_entity_id": null,
"status": "accepted",
"subtotal_clp": 147000,
"discount_clp": 5000,
"total_clp": 142000,
"tax_treatment": "afecto",
"valid_until": "2026-12-31",
"installments": 3,
"notes": "Incluye los cuatro controles del primer semestre",
"obligation_group_id": "a067dce6-0000-4000-8000-000000000002",
"rendered_document_id": null,
"created_by_user_id": null,
"sent_at": "2026-09-22T22:21:15.544Z",
"accepted_at": "2026-09-22T22:21:16.040Z",
"accepted_via": "staff",
"rejected_at": null,
"rejected_reason": null,
"expired_at": null,
"voided_at": null,
"voided_by": null,
"voided_reason": null,
"created_at": "2026-09-22T22:21:15.502Z",
"updated_at": "2026-09-22T22:21:16.048Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000007",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000002",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"pack_id": null,
"label": "Control de ortodoncia",
"quantity": 4,
"unit_price_clp": 28000,
"discount_clp": 0,
"total_clp": 112000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 28000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 1,
"note": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:15.515Z",
"updated_at": "2026-09-22T22:21:15.515Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-000000000008",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000002",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"pack_id": null,
"label": "Limpieza dental",
"quantity": 1,
"unit_price_clp": 35000,
"discount_clp": 5000,
"total_clp": 30000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 35000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 2,
"note": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:15.530Z",
"updated_at": "2026-09-22T22:21:15.530Z",
"service_name": "Limpieza dental"
}
],
"is_live": false,
"days_left": 100
},
"plan": {
"id": "1ccffee2-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"name": "Ortodoncia fija superior e inferior",
"diagnosis": "Apiñamiento anterosuperior moderado",
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"status": "proposed",
"accepted_at": null,
"completed_at": null,
"abandoned_reason": null,
"created_at": "2026-09-22T22:21:15.213Z",
"updated_at": "2026-09-22T22:21:15.285Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"phase": 1,
"quantity": 4,
"tooth_or_zone": null,
"status": "scheduled",
"appointment_id": "d93e8eec-0000-4000-8000-000000000001",
"budget_item_id": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:15.215Z",
"updated_at": "2026-09-22T22:21:15.337Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-000000000003",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"phase": 2,
"quantity": 1,
"tooth_or_zone": null,
"status": "pending",
"appointment_id": null,
"budget_item_id": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:15.215Z",
"updated_at": "2026-09-22T22:21:15.215Z",
"service_name": "Limpieza dental"
}
],
"progress": {
"pending": 1,
"scheduled": 4,
"performed": 0,
"cancelled": 0,
"active": 5,
"percent": 0,
"phases": [
{
"phase": 1,
"pending": 0,
"scheduled": 4,
"performed": 0,
"cancelled": 0,
"active": 4,
"percent": 0
},
{
"phase": 2,
"pending": 1,
"scheduled": 0,
"performed": 0,
"cancelled": 0,
"active": 1,
"percent": 0
}
],
"current_phase": 1
}
},
"obligation_group_id": "a067dce6-0000-4000-8000-000000000002",
"obligation_ids": [
"b44936d0-0000-4000-8000-000000000002"
],
"accepted_now": true,
"accepted_item_ids": [
"3a7d9767-0000-4000-8000-000000000007"
],
"payment": {
"payment": {
"id": "9a7e0000-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"source": "card_terminal",
"provenance": "manual",
"instrument": "tarjeta_debito",
"amount_clp": 112000,
"paid_on": "2026-09-22",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"allocated_clp": 112000,
"unallocated_clp": 0
},
"applied": [
{
"obligationId": "b44936d0-0000-4000-8000-000000000002",
"amountClp": 112000,
"allocatedClp": 112000,
"outstandingClp": 0,
"expectedClp": 112000,
"state": "paid",
"label": "Control de ortodoncia"
}
],
"surplus_clp": 0,
"reasons": [
"payer_recorded_by_staff",
"allocation_staff_manual"
],
"replayed": false
}
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/budgets/{id}/items
Add a line, freezing its price
Resolves the price through the quote explainer and stores the ordered steps beside the number. An explicit unit_price_clp is recorded AS an override step, so "why does it say $38.000" stays answerable.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
clinic_service_id | string | null | — | |
pack_id | string | null | — | |
unit_price_clp | integer | null | — | ≥ 0, ≤ 9999999999 |
quantity | integer | — | ≥ 1, ≤ 999, por defecto 1 |
discount_clp | integer | — | ≥ 0, ≤ 9999999999, por defecto 0 |
tax_treatment | afecto \ | exento \ | no_gravado |
phase | integer | — | ≥ 1, ≤ 99, por defecto 1 |
note | string | null | — | máx. 500 |
label | string | — | mín. 1, máx. 200 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/items \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"quantity": 1,
"phase": 3,
"note": "Control final"
}'Ejemplo de respuesta (201)
{
"data": {
"id": "3a7d9767-0000-4000-8000-000000000006",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"pack_id": null,
"label": "Limpieza dental",
"quantity": 1,
"unit_price_clp": 35000,
"discount_clp": 0,
"total_clp": 35000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 35000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 3,
"note": "Control final",
"sort_order": 2,
"created_at": "2026-09-22T22:21:15.470Z",
"updated_at": "2026-09-22T22:21:15.470Z"
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/budgets/{id}/lineage
Every version of one proposal
The budget's whole lineage (#2446), version ascending — the first presupuesto handed over plus each one that superseded it. \{id\} may name any member, by UUID or folio.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | any | sí |
curl https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/lineage \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"items": [
{
"id": "81e4a552-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"display_seq": 2,
"display_id": "E-2",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"lead_id": "8cb33b27-0000-4000-8000-000000000001",
"lineage_id": "81e4a552-0000-4000-8000-000000000002",
"reissued_from_id": null,
"conversation_id": null,
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"price_list_id": null,
"agreement_id": null,
"legal_entity_id": null,
"status": "accepted",
"subtotal_clp": 147000,
"discount_clp": 5000,
"total_clp": 142000,
"tax_treatment": "afecto",
"valid_until": "2026-12-31",
"installments": 3,
"notes": "Incluye los cuatro controles del primer semestre",
"obligation_group_id": "a067dce6-0000-4000-8000-000000000002",
"rendered_document_id": null,
"created_by_user_id": null,
"sent_at": "2026-09-23T01:07:07.750Z",
"accepted_at": "2026-09-23T01:07:07.912Z",
"accepted_via": "staff",
"rejected_at": null,
"rejected_reason": null,
"expired_at": null,
"voided_at": null,
"voided_by": null,
"voided_reason": null,
"first_viewed_at": null,
"last_viewed_at": null,
"created_at": "2026-09-23T01:07:07.683Z",
"updated_at": "2026-09-23T01:07:07.941Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000007",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000002",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"pack_id": null,
"label": "Control de ortodoncia",
"quantity": 4,
"unit_price_clp": 28000,
"discount_clp": 0,
"total_clp": 112000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 28000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 1,
"note": null,
"sort_order": 0,
"accepted_at": "2026-09-23T01:07:07.912Z",
"carried_from_item_id": null,
"created_at": "2026-09-23T01:07:07.701Z",
"updated_at": "2026-09-23T01:07:07.911Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-000000000008",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000002",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"pack_id": null,
"label": "Limpieza dental",
"quantity": 1,
"unit_price_clp": 35000,
"discount_clp": 5000,
"total_clp": 30000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 35000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 2,
"note": null,
"sort_order": 1,
"accepted_at": "2026-09-23T01:07:07.912Z",
"carried_from_item_id": null,
"created_at": "2026-09-23T01:07:07.718Z",
"updated_at": "2026-09-23T01:07:07.911Z",
"service_name": "Limpieza dental"
}
],
"is_live": false,
"days_left": 100,
"version": 1,
"lineage_size": 2,
"reissued_by_id": "458363ad-0000-4000-8000-000000000001"
},
{
"id": "458363ad-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"display_seq": 3,
"display_id": "E-3",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"lead_id": "8cb33b27-0000-4000-8000-000000000001",
"lineage_id": "81e4a552-0000-4000-8000-000000000002",
"reissued_from_id": "81e4a552-0000-4000-8000-000000000002",
"conversation_id": null,
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"price_list_id": null,
"agreement_id": null,
"legal_entity_id": null,
"status": "draft",
"subtotal_clp": 147000,
"discount_clp": 5000,
"total_clp": 142000,
"tax_treatment": "afecto",
"valid_until": "2026-12-31",
"installments": 3,
"notes": "Incluye los cuatro controles del primer semestre",
"obligation_group_id": null,
"rendered_document_id": null,
"created_by_user_id": null,
"sent_at": null,
"accepted_at": null,
"accepted_via": null,
"rejected_at": null,
"rejected_reason": null,
"expired_at": null,
"voided_at": null,
"voided_by": null,
"voided_reason": null,
"first_viewed_at": null,
"last_viewed_at": null,
"created_at": "2026-09-23T01:07:07.990Z",
"updated_at": "2026-09-23T01:07:07.990Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000009",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "458363ad-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"pack_id": null,
"label": "Control de ortodoncia",
"quantity": 4,
"unit_price_clp": 28000,
"discount_clp": 0,
"total_clp": 112000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 28000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 1,
"note": null,
"sort_order": 0,
"accepted_at": "2026-09-23T01:07:07.912Z",
"carried_from_item_id": "3a7d9767-0000-4000-8000-000000000007",
"created_at": "2026-09-23T01:07:07.990Z",
"updated_at": "2026-09-23T01:07:07.990Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-00000000000a",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "458363ad-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"pack_id": null,
"label": "Limpieza dental",
"quantity": 1,
"unit_price_clp": 35000,
"discount_clp": 5000,
"total_clp": 30000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 35000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 2,
"note": null,
"sort_order": 1,
"accepted_at": "2026-09-23T01:07:07.912Z",
"carried_from_item_id": "3a7d9767-0000-4000-8000-000000000008",
"created_at": "2026-09-23T01:07:07.990Z",
"updated_at": "2026-09-23T01:07:07.990Z",
"service_name": "Limpieza dental"
}
],
"is_live": true,
"days_left": 100,
"version": 2,
"lineage_size": 2,
"reissued_by_id": null
}
],
"total": 2
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/budgets/{id}/reject
Record the patient declining
Record the patient declining
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
reason | string | null | — | máx. 500 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/reject \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "El paciente prefiere esperar al próximo semestre"
}'Ejemplo de respuesta (200)
{
"data": {
"id": "90afb8c1-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"display_seq": 3,
"display_id": "E-3",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"lead_id": "8cb33b27-0000-4000-8000-000000000001",
"lineage_id": "90afb8c1-0000-4000-8000-000000000001",
"reissued_from_id": null,
"conversation_id": null,
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"price_list_id": null,
"agreement_id": null,
"legal_entity_id": null,
"status": "rejected",
"subtotal_clp": 147000,
"discount_clp": 5000,
"total_clp": 142000,
"tax_treatment": "afecto",
"valid_until": "2026-12-31",
"installments": 3,
"notes": "Incluye los cuatro controles del primer semestre",
"obligation_group_id": null,
"rendered_document_id": null,
"created_by_user_id": null,
"sent_at": "2026-09-22T22:21:16.137Z",
"accepted_at": null,
"accepted_via": null,
"rejected_at": "2026-09-22T22:21:16.460Z",
"rejected_reason": "El paciente prefiere esperar al próximo semestre",
"expired_at": null,
"voided_at": null,
"voided_by": null,
"voided_reason": null,
"created_at": "2026-09-22T22:21:16.091Z",
"updated_at": "2026-09-22T22:21:16.459Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000009",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "90afb8c1-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"pack_id": null,
"label": "Control de ortodoncia",
"quantity": 4,
"unit_price_clp": 28000,
"discount_clp": 0,
"total_clp": 112000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 28000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 1,
"note": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:16.106Z",
"updated_at": "2026-09-22T22:21:16.106Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-00000000000a",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "90afb8c1-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"pack_id": null,
"label": "Limpieza dental",
"quantity": 1,
"unit_price_clp": 35000,
"discount_clp": 5000,
"total_clp": 30000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 35000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 2,
"note": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:16.121Z",
"updated_at": "2026-09-22T22:21:16.121Z",
"service_name": "Limpieza dental"
}
],
"is_live": false,
"days_left": 100
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/budgets/{id}/render
The presupuesto as a PDF
Renders on demand and returns application/pdf. It is immutable BECAUSE every figure on it is frozen in the budget and its lines (ADR 0057), not because the bytes were stored.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | any | sí |
curl https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/render \
-H "Authorization: Bearer $VITRINA_KEY"Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/budgets/{id}/send
Send it to the patient
Moves draft → sent FIRST (the freeze must be in force before the patient can read the figures), then delivers through the conversation with a tokenised accept link: WhatsApp while the window is open, email otherwise. A patient with neither is reported honestly (delivered: "none") and the quote still counts as sent — the clinic handed it over. Also puts the lead on «Presupuesto enviado» with the total as its value.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
conversation_id | string | null | — | |
resend | boolean | — | por defecto false |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/send \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{}'Ejemplo de respuesta (200)
{
"data": {
"budget": {
"id": "81e4a552-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"display_seq": 2,
"display_id": "E-2",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"lead_id": "8cb33b27-0000-4000-8000-000000000001",
"lineage_id": "81e4a552-0000-4000-8000-000000000002",
"reissued_from_id": null,
"conversation_id": null,
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"price_list_id": null,
"agreement_id": null,
"legal_entity_id": null,
"status": "sent",
"subtotal_clp": 147000,
"discount_clp": 5000,
"total_clp": 142000,
"tax_treatment": "afecto",
"valid_until": "2026-12-31",
"installments": 3,
"notes": "Incluye los cuatro controles del primer semestre",
"obligation_group_id": null,
"rendered_document_id": null,
"created_by_user_id": null,
"sent_at": "2026-09-22T22:21:15.544Z",
"accepted_at": null,
"accepted_via": null,
"rejected_at": null,
"rejected_reason": null,
"expired_at": null,
"voided_at": null,
"voided_by": null,
"voided_reason": null,
"created_at": "2026-09-22T22:21:15.502Z",
"updated_at": "2026-09-22T22:21:16.026Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000007",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000002",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"pack_id": null,
"label": "Control de ortodoncia",
"quantity": 4,
"unit_price_clp": 28000,
"discount_clp": 0,
"total_clp": 112000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 28000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 1,
"note": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:15.515Z",
"updated_at": "2026-09-22T22:21:15.515Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-000000000008",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "81e4a552-0000-4000-8000-000000000002",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"pack_id": null,
"label": "Limpieza dental",
"quantity": 1,
"unit_price_clp": 35000,
"discount_clp": 5000,
"total_clp": 30000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 35000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 2,
"note": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:15.530Z",
"updated_at": "2026-09-22T22:21:15.530Z",
"service_name": "Limpieza dental"
}
],
"is_live": true,
"days_left": 100
},
"delivered": "email",
"accept_url": "https://api.example.com/api/v1/public/clinic/budget/<token>",
"reason": null
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/budgets/{id}/supersede
Write a new version (Nueva versión)
From sent, accepted, rejected or expired (409 otherwise, and 409 when a version already supersedes it). Creates a DRAFT copying the header and every line verbatim, in the same lineage. Lines already ACCEPTED are carried forward (carried_from_item_id, their original accepted_at), are immutable on the draft (409 carried_item), and open no second obligation when the new version is accepted — which reuses the predecessor's obligation group. Nothing is withdrawn until the draft is SENT: that voids a sent, rejected or expired predecessor with reason superseded in the same transaction. An ACCEPTED predecessor stays accepted until the new version is itself accepted, so its debt always has an accepted document. Discarding the draft leaves the predecessor untouched. Same rules as POST /clinic/budgets with reissued_from_id. A presupuesto photographed from a mirrored Dentalink plan (mirror set) is RE-PHOTOGRAPHED instead: accepted lines are still carried, every other line comes from the plan's live state, and the new version records that state — born without mirror.changed_at (409 MIRROR_LINES_UNMAPPED if the live plan has a prestación missing from the catalogue).
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/supersede \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{}'Ejemplo de respuesta (201)
{
"data": {
"id": "458363ad-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"display_seq": 3,
"display_id": "E-3",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"lead_id": "8cb33b27-0000-4000-8000-000000000001",
"lineage_id": "81e4a552-0000-4000-8000-000000000002",
"reissued_from_id": "81e4a552-0000-4000-8000-000000000002",
"conversation_id": null,
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"price_list_id": null,
"agreement_id": null,
"legal_entity_id": null,
"status": "draft",
"subtotal_clp": 147000,
"discount_clp": 5000,
"total_clp": 142000,
"tax_treatment": "afecto",
"valid_until": "2026-12-31",
"installments": 3,
"notes": "Incluye los cuatro controles del primer semestre",
"obligation_group_id": null,
"rendered_document_id": null,
"created_by_user_id": null,
"sent_at": null,
"accepted_at": null,
"accepted_via": null,
"rejected_at": null,
"rejected_reason": null,
"expired_at": null,
"voided_at": null,
"voided_by": null,
"voided_reason": null,
"first_viewed_at": null,
"last_viewed_at": null,
"created_at": "2026-09-23T01:07:07.990Z",
"updated_at": "2026-09-23T01:07:07.990Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000009",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "458363ad-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"pack_id": null,
"label": "Control de ortodoncia",
"quantity": 4,
"unit_price_clp": 28000,
"discount_clp": 0,
"total_clp": 112000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 28000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 1,
"note": null,
"sort_order": 0,
"accepted_at": "2026-09-23T01:07:07.912Z",
"carried_from_item_id": "3a7d9767-0000-4000-8000-000000000007",
"created_at": "2026-09-23T01:07:07.990Z",
"updated_at": "2026-09-23T01:07:07.990Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-00000000000a",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "458363ad-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"pack_id": null,
"label": "Limpieza dental",
"quantity": 1,
"unit_price_clp": 35000,
"discount_clp": 5000,
"total_clp": 30000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 35000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 2,
"note": null,
"sort_order": 1,
"accepted_at": "2026-09-23T01:07:07.912Z",
"carried_from_item_id": "3a7d9767-0000-4000-8000-000000000008",
"created_at": "2026-09-23T01:07:07.990Z",
"updated_at": "2026-09-23T01:07:07.990Z",
"service_name": "Limpieza dental"
}
],
"is_live": true,
"days_left": 100,
"version": 2,
"lineage_size": 2,
"reissued_by_id": null
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/budgets/{id}/void
Void it — never DELETE
Keeps the folio and stays listable (ADR 0058 §10). reason is required by the service AND by a database CHECK: "why did we withdraw 40 quotes this month" is only answerable if it was written down. An ACCEPTED presupuesto cannot be voided here, and the reason superseded is reserved — write a new version instead (POST /clinic/budgets/\{id\}/supersede).
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
reason | string | sí | mín. 1, máx. 500 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/<id>/void \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"reason": "Se emitió con el arancel equivocado"
}'Ejemplo de respuesta (200)
{
"data": {
"id": "e9cede9b-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"display_seq": 4,
"display_id": "E-4",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"lead_id": "8cb33b27-0000-4000-8000-000000000001",
"lineage_id": "e9cede9b-0000-4000-8000-000000000001",
"reissued_from_id": null,
"conversation_id": null,
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"price_list_id": null,
"agreement_id": null,
"legal_entity_id": null,
"status": "voided",
"subtotal_clp": 147000,
"discount_clp": 5000,
"total_clp": 142000,
"tax_treatment": "afecto",
"valid_until": "2026-12-31",
"installments": 3,
"notes": "Incluye los cuatro controles del primer semestre",
"obligation_group_id": null,
"rendered_document_id": null,
"created_by_user_id": null,
"sent_at": "2026-09-22T22:21:16.514Z",
"accepted_at": null,
"accepted_via": null,
"rejected_at": null,
"rejected_reason": null,
"expired_at": null,
"voided_at": "2026-09-22T22:21:16.806Z",
"voided_by": null,
"voided_reason": "Se emitió con el arancel equivocado",
"created_at": "2026-09-22T22:21:16.473Z",
"updated_at": "2026-09-22T22:21:16.805Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-00000000000b",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "e9cede9b-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"pack_id": null,
"label": "Control de ortodoncia",
"quantity": 4,
"unit_price_clp": 28000,
"discount_clp": 0,
"total_clp": 112000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 28000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 1,
"note": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:16.485Z",
"updated_at": "2026-09-22T22:21:16.485Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-00000000000c",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"budget_id": "e9cede9b-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"pack_id": null,
"label": "Limpieza dental",
"quantity": 1,
"unit_price_clp": 35000,
"discount_clp": 5000,
"total_clp": 30000,
"price_explain": [
{
"kind": null,
"label": "Arancel particular 2026",
"order": 1,
"value": null,
"source": "price_list",
"rule_id": null,
"modifier": null,
"delta_clp": 0,
"amount_after_clp": 35000,
"amount_before_clp": null
}
],
"tax_treatment": "afecto",
"phase": 2,
"note": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:16.500Z",
"updated_at": "2026-09-22T22:21:16.500Z",
"service_name": "Limpieza dental"
}
],
"is_live": false,
"days_left": 100
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/budgets/from-mirror-plan
Create a presupuesto (draft) from a mirrored Dentalink plan
On a Dentalink clinic the plan is born in Dentalink; this composes the presupuesto FROM its mirrored copy in one action. Every line of the plan becomes a line at Dentalink's net price (after its descuentos), frozen with an explanation that names the source; the plan's professional becomes the presupuesto's (per-line performers are not kept). The response carries mirror: \{plan_id, snapshot_at, changed_at\}. When the plan later changes in Dentalink — a line added, removed or repriced, even at the same total — mirror.changed_at is set and clinic_budget.mirror_changed fires; POST /clinic/budgets/\{id\}/supersede then writes a new version from the live plan. 409 MIRROR_LINES_UNMAPPED lists the prestaciones missing from the local catalogue (details.fix names the importer); 409 with details.reason for a plan whose patient is not linked, a plan with no lines yet, or one another presupuesto already photographs. Vitrina never writes to Dentalink.
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
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
mirror_plan_id | uuid | sí | |
clinic_patient_id | uuid | — | |
contact_id | string | null | — | |
location_id | string | null | — | |
conversation_id | string | null | — | |
lead_id | string | null | — | |
tax_treatment | afecto \ | exento \ | no_gravado |
valid_until | string | null | — | patrón ^\d{4}-\d{2}-\d{2}$ |
installments | integer | null | — | ≥ 1, ≤ 48 |
notes | string | null | — | máx. 2000 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/budgets/from-mirror-plan \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"mirror_plan_id": "6d2c1f0a-0000-4000-8000-000000000001"
}'Ejemplo de respuesta (201)
{
"data": {
"id": "458363ad-0000-4000-8000-000000000009",
"display_id": "E-52",
"status": "draft",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"lineage_id": "458363ad-0000-4000-8000-000000000009",
"reissued_from_id": null,
"subtotal_clp": 150000,
"discount_clp": 0,
"total_clp": 150000,
"tax_treatment": "afecto",
"valid_until": "2026-10-23",
"mirror": {
"plan_id": "6d2c1f0a-0000-4000-8000-000000000001",
"snapshot_at": "2026-09-23T14:02:11.000Z",
"changed_at": null
},
"items": [
{
"id": "a1c0ffee-0000-4000-8000-000000000001",
"clinic_service_id": "5e5e5e5e-0000-4000-8000-000000000001",
"label": "Resina compuesta",
"quantity": 1,
"unit_price_clp": 100000,
"discount_clp": 0,
"total_clp": 100000,
"price_explain": [
{
"order": 1,
"source": "mirror_snapshot",
"label": "Precio del tratamiento en Dentalink",
"amount_before_clp": null,
"amount_after_clp": 120000,
"delta_clp": 0,
"external_id": 1,
"external_plan": 90412
},
{
"order": 2,
"source": "mirror_snapshot",
"label": "Descuento registrado en Dentalink",
"amount_before_clp": 120000,
"amount_after_clp": 100000,
"delta_clp": -20000,
"external_id": 1,
"external_plan": 90412
}
]
},
{
"id": "a1c0ffee-0000-4000-8000-000000000002",
"clinic_service_id": "5e5e5e5e-0000-4000-8000-000000000002",
"label": "Sellante",
"quantity": 1,
"unit_price_clp": 50000,
"discount_clp": 0,
"total_clp": 50000,
"price_explain": [
{
"order": 1,
"source": "mirror_snapshot",
"label": "Precio del tratamiento en Dentalink",
"amount_before_clp": null,
"amount_after_clp": 50000,
"delta_clp": 0,
"external_id": 2,
"external_plan": 90412
}
]
}
],
"version": 1,
"lineage_size": 1,
"reissued_by_id": null
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/budgets/mirror-plans
A patient's mirrored Dentalink plans, for «Crear desde Dentalink»
The patient's plans in the espejo de ingresos (a Dentalink clinic's tratamientos, copied read-only), newest first, each with lines_count, unmapped_count (lines whose prestación is not in the local catalogue yet — the plan cannot be photographed until the catalog importer runs) and already_budgeted / budget_id (a non-voided presupuesto already photographs it). available: false means the workspace has no enabled Dentalink mirror, and items is empty.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
patient_id | query | uuid | sí |
curl https://api.vitrinadev.com/api/v1/clinic/budgets/mirror-plans \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"available": true,
"items": [
{
"id": "6d2c1f0a-0000-4000-8000-000000000001",
"external_id": 90412,
"fecha": "2026-09-10",
"finalizado": false,
"bloqueado": false,
"total": 150000,
"deuda": 150000,
"clinic_professional_id": "18342d1b-0000-4000-8000-000000000001",
"professional_name": "Camila Rojas",
"lines_count": 2,
"unmapped_count": 0,
"already_budgeted": false,
"budget_id": null
}
],
"total": 1
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/budgets/summary
The COMERCIAL badge and funnel, counted by lineage
Counts LINEAGES, not documents: a proposal re-issued three times is one proposal, read at its latest version that is neither a draft nor voided. sent_and_live is the badge and counts on valid_until rather than on status, so a quote that lapsed twelve minutes ago is already out — the expiry sweep runs on a tick and the badge must not tell the clinic to chase a dead number. lineages / by_status are the funnel.
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/budgets/summary \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"sent_and_live": 0
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/imported-balances
«Saldos pendientes de Dentalink»: what patients still owe on imported presupuestos
Read-only. One row per patient whose presupuestos imported from Dentalink by the instructed migration run (#2467) still carry a balance: the balance at cutover (data.imported.deuda of each imported, non-voided presupuesto), what was collected in Vitrina since, the pending balance_clp, the presupuestos it sits on (folio + the Dentalink plan id and date), and the last payment — last_vendor_payment_on from the revenue mirror (voided payments excluded), last_collected_on from collections in Vitrina, last_payment_on the later of the two (YYYY-MM-DD). A balance leaves the list when it is collected in full (POST /clinic/imported-balances/\{budget_id\}/collect), or when its presupuesto is voided; a partial collection lowers it. A collected payment that is later reversed stays owed in the native ledger, not back on this list. No charge, obligation, reminder, follow-up, commission or boleta is ever produced by a listed balance on its own. q matches a name (accent-folded), a RUT prefix (any format) or a phone fragment; sort is balance (default, largest first), name (by apellido) or last_payment. summary is the whole list, whatever q says. Requires clinic_money:read (and clinic_patients:read for an API key).
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
q | query | string | no | máx. 120 |
sort | query | balance \ | name \ | last_payment |
offset | query | integer | null | no | ≥ 0, ≤ 100000 |
limit | query | integer | no | ≥ 1, ≤ 200 |
curl https://api.vitrinadev.com/api/v1/clinic/imported-balances \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"rows": [
{
"patient": {
"id": "b1b0b8de-0000-4000-8000-000000000001",
"nombre": "María José",
"apellidos": "Fuentes Lagos",
"rut": "11.111.111-1",
"numero_ficha": "4412"
},
"balance_clp": 110000,
"cutover_balance_clp": 150000,
"collected_clp": 40000,
"last_vendor_payment_on": "2026-10-01",
"last_collected_on": "2026-11-03",
"last_payment_on": "2026-11-03",
"budgets": [
{
"budget_id": "81e4a552-0000-4000-8000-000000000011",
"display_id": "E-412",
"status": "accepted",
"external_id": "18233",
"plan_date": "2026-06-01",
"cutover_balance_clp": 100000,
"collected_clp": 40000,
"balance_clp": 60000
},
{
"budget_id": "81e4a552-0000-4000-8000-000000000012",
"display_id": "E-398",
"status": "expired",
"external_id": "17120",
"plan_date": "2026-03-01",
"cutover_balance_clp": 50000,
"collected_clp": 0,
"balance_clp": 50000
}
]
}
],
"total": 1,
"offset": 0,
"limit": 50,
"summary": {
"patients": 1,
"balance_clp": 110000,
"cutover_balance_clp": 150000,
"collected_clp": 40000
}
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/imported-balances/{budget_id}
One imported presupuesto’s Dentalink balance and default payer
The pending balance of one presupuesto imported from Dentalink (0 when voided) and the payer a collection is recorded for when the request names none: the presupuesto’s own contact_id, else the patient’s reading bond or verified link, else a payer bond a collection left. payer: null means a collection must name one. credits lists the payer’s money not yet applied to anything (a transfer the bank-email matcher recorded, the surplus of an earlier payment) — the payer named by contact_id, else the default one; any of them can settle the balance with credit_payment_id. will_bond_payer (with contact_id) says whether collecting with that payer leaves a new payer bond. 409 not_imported for a presupuesto created in Vitrina. Requires clinic_money:read (and clinic_patients:read for an API key).
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
budget_id | path | uuid | sí | |
contact_id | query | uuid | no |
curl https://api.vitrinadev.com/api/v1/clinic/imported-balances/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"budget_id": "81e4a552-0000-4000-8000-000000000011",
"display_id": "E-412",
"status": "accepted",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"cutover_balance_clp": 100000,
"balance_clp": 60000,
"payer": {
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"name": "María José Fuentes Lagos"
},
"will_bond_payer": false,
"credits": [
{
"payment_id": "c0ffee00-0000-4000-8000-000000000021",
"paid_on": "2026-11-02",
"instrument": "transferencia",
"amount_clp": 30000,
"unallocated_clp": 30000
}
]
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/imported-balances/{budget_id}/collect
Collect a payment (or apply credit) against a Dentalink balance
Send exactly one of payment or credit_payment_id. With payment, records it through the payments ledger exactly like any counter payment (caja shift of the presupuesto’s sucursal, card recargo, clinic.payment.registered), and — in the same transaction — opens ONE obligation in the presupuesto’s balance group for the amount applied: min(amount_clp, pending balance), no due date, allocated at once, so it is born settled. Money beyond the balance stays as the payer’s credit. With credit_payment_id, applies what that payment still has unallocated (its payer pays; contact_id is ignored) the same way, as one write; 409 no_credit when nothing is left on it. Two collections of one balance are serialised on the presupuesto; neither can take it below zero. contact_id names the payer; omitted, the default payer of GET /clinic/imported-balances/\{budget_id\} is used (409 payer_unknown when there is none). When the patient had no payer at all, the payer this collection names becomes a bond (can_book, never can_read_record, verified_method: "manual", role otro; payer_bond_id) and is the default payer next time. 409 not_imported, budget_voided, nothing_to_collect. Idempotency-Key is stored on the payment: a repeat records and applies nothing and echoes the first payment (payment.replayed: true once the request cache has expired); a key already used for another gesture is 409 idempotency_key_reused. Requires BOTH clinic_money:write and payments:write. An AGENT cannot reach it.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
budget_id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
contact_id | string | null | — | |
payment | object | — | |
credit_payment_id | uuid | — |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/imported-balances/<id>/collect \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"payment": {
"instrument": "tarjeta_debito",
"source": "card_terminal",
"amount_clp": 40000
}
}'Ejemplo de respuesta (201)
{
"data": {
"budget_id": "81e4a552-0000-4000-8000-000000000011",
"display_id": "E-412",
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"applied_clp": 40000,
"balance_before_clp": 100000,
"balance_after_clp": 60000,
"obligation_group_id": "a067dce6-0000-4000-8000-000000000011",
"obligation_id": "b44936d0-0000-4000-8000-000000000011",
"payer_bond_id": null,
"payment": {
"payment": {
"id": "c0ffee00-0000-4000-8000-000000000011",
"contact_id": "1a73af9e-0000-4000-8000-000000000001",
"suggested_contact_id": null,
"payer_reasons": [
"payer_recorded_by_staff"
],
"source": "card_terminal",
"provenance": "manual",
"instrument": "tarjeta_debito",
"amount_clp": 40000,
"paid_on": "2026-11-03",
"allocated_clp": 40000,
"unallocated_clp": 0,
"location_id": "b1b1b1b1-0000-4000-8000-000000000002",
"recorded_at": "2026-11-03T13:11:26.575Z",
"reversed": false,
"allocations": [
{
"id": "a11ca7e0-0000-4000-8000-000000000011",
"customer_payment_id": "c0ffee00-0000-4000-8000-000000000011",
"customer_obligation_id": "b44936d0-0000-4000-8000-000000000011",
"amount_clp": 40000,
"method": "staff",
"reasons": [
"allocation_staff_manual"
],
"allocated_at": "2026-11-03T13:11:26.575Z",
"released_at": null,
"live": true
}
]
},
"applied": [
{
"obligationId": "b44936d0-0000-4000-8000-000000000011",
"amountClp": 40000,
"allocatedClp": 40000,
"outstandingClp": 0,
"expectedClp": 40000,
"state": "paid",
"label": "Saldo de tratamiento · E-412"
}
],
"surplus_clp": 0,
"reasons": [
"payer_recorded_by_staff",
"allocation_staff_manual"
],
"replayed": false
}
}
}Responde: 200 · 201 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/patients/{id}/treatment-progress
A patient's treatment progress
Two sources, both reported: pack-backed progress where the clinic sells packs, and performed / unclosed / upcoming citas per service off the mirror for a clinic that does not (Medilink). Reports what was PERFORMED; it never invents the session count quoted at a consultation.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl https://api.vitrinadev.com/api/v1/clinic/patients/<id>/treatment-progress \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"patient": {
"id": "b1b0b8de-0000-4000-8000-000000000001",
"source": "native",
"nombre": "María José",
"apellidos": "Fuentes Lagos"
},
"today": "2026-09-22",
"timezone": "America/Santiago",
"packs": [
{
"purchase_id": "d376ca29-0000-4000-8000-000000000001",
"name": "Pack 4 controles de ortodoncia",
"pack_id": "6acd6212-0000-4000-8000-000000000001",
"source": "native",
"purchased_at": "2026-09-22T22:21:14.782Z",
"expires_at": "2027-04-20",
"frozen_from": null,
"frozen_to": null,
"freeze_count": 1,
"stored_status": "active",
"precio_clp": 96000,
"obligation_group_id": "a067dce6-0000-4000-8000-000000000001",
"balance": {
"session_count": 4,
"consumed": 0,
"reversed": 1,
"remaining": 4,
"over_consumed": false,
"frozen": false,
"date_passed": false,
"days_to_expiry": 210,
"effective_status": "active"
},
"last_session_at": "2026-09-22T22:21:14.813Z",
"next_session_due_after": null
}
],
"services": [],
"truncated": false
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/treatment-plan-items/{id}/status
Move one phase item along
pending → scheduled → performed (or cancelled). scheduled requires the cita it lands on. The PLAN's own status follows from its items, so nobody has to remember to close a plan whose last phase was performed.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
status | pending \ | scheduled \ | performed \ |
appointment_id | string | null | — |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/treatment-plan-items/<id>/status \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "scheduled",
"appointment_id": "d93e8eec-0000-4000-8000-000000000001"
}'Ejemplo de respuesta (200)
{
"data": {
"id": "1ccffee2-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"name": "Ortodoncia fija superior e inferior",
"diagnosis": "Apiñamiento anterosuperior moderado",
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"status": "proposed",
"accepted_at": null,
"completed_at": null,
"abandoned_reason": null,
"created_at": "2026-09-22T22:21:15.213Z",
"updated_at": "2026-09-22T22:21:15.285Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"phase": 1,
"quantity": 4,
"tooth_or_zone": null,
"status": "scheduled",
"appointment_id": "d93e8eec-0000-4000-8000-000000000001",
"budget_item_id": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:15.215Z",
"updated_at": "2026-09-22T22:21:15.337Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-000000000003",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"phase": 2,
"quantity": 1,
"tooth_or_zone": null,
"status": "pending",
"appointment_id": null,
"budget_item_id": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:15.215Z",
"updated_at": "2026-09-22T22:21:15.215Z",
"service_name": "Limpieza dental"
}
],
"progress": {
"pending": 1,
"scheduled": 4,
"performed": 0,
"cancelled": 0,
"active": 5,
"percent": 0,
"phases": [
{
"phase": 1,
"pending": 0,
"scheduled": 4,
"performed": 0,
"cancelled": 0,
"active": 4,
"percent": 0
},
{
"phase": 2,
"pending": 1,
"scheduled": 0,
"performed": 0,
"cancelled": 0,
"active": 1,
"percent": 0
}
],
"current_phase": 1
}
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/treatment-plans
List treatment plans, with phase progress
Progress is DERIVED from the items (pending / scheduled / performed), never a stored counter — a counter drifts the first time a cita is cancelled. ?live=true is accepted + in_progress, the work still to do.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
status | query | draft \ | proposed \ | accepted \ |
live | query | true \ | false | no |
patient_id | query | uuid | no | |
professional_id | query | uuid | no | |
page | query | integer | no | ≥ 1, por defecto 1 |
limit | query | integer | no | ≥ 1, ≤ 200, por defecto 50 |
curl https://api.vitrinadev.com/api/v1/clinic/treatment-plans \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"items": [],
"total": 0,
"page": 1,
"limit": 50
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/treatment-plans
Create a treatment plan by hand
Accepting a presupuesto creates one automatically; this is the path for a plan that never had a quote. Clinical authoring: accepts clinic_money:write or clinic_record:write (items carry no price); a clinical-only caller must be able to open the patient's ficha.
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
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
clinic_patient_id | uuid | sí | |
name | string | sí | mín. 1, máx. 200 |
diagnosis | string | null | — | máx. 2000 |
professional_id | string | null | — | |
items | object[] | — |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/treatment-plans \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"name": "Ortodoncia fija superior e inferior",
"diagnosis": "Apiñamiento anterosuperior moderado",
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"items": [
{
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"phase": 1,
"quantity": 4
},
{
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"phase": 2,
"quantity": 1
}
]
}'Ejemplo de respuesta (201)
{
"data": {
"id": "1ccffee2-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"name": "Ortodoncia fija superior e inferior",
"diagnosis": "Apiñamiento anterosuperior moderado",
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"status": "draft",
"accepted_at": null,
"completed_at": null,
"abandoned_reason": null,
"created_at": "2026-09-22T22:21:15.213Z",
"updated_at": "2026-09-22T22:21:15.213Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"phase": 1,
"quantity": 4,
"tooth_or_zone": null,
"status": "pending",
"appointment_id": null,
"budget_item_id": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:15.215Z",
"updated_at": "2026-09-22T22:21:15.215Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-000000000003",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"phase": 2,
"quantity": 1,
"tooth_or_zone": null,
"status": "pending",
"appointment_id": null,
"budget_item_id": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:15.215Z",
"updated_at": "2026-09-22T22:21:15.215Z",
"service_name": "Limpieza dental"
}
],
"progress": {
"pending": 5,
"scheduled": 0,
"performed": 0,
"cancelled": 0,
"active": 5,
"percent": 0,
"phases": [
{
"phase": 1,
"pending": 4,
"scheduled": 0,
"performed": 0,
"cancelled": 0,
"active": 4,
"percent": 0
},
{
"phase": 2,
"pending": 1,
"scheduled": 0,
"performed": 0,
"cancelled": 0,
"active": 1,
"percent": 0
}
],
"current_phase": 1
}
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
DELETE /clinic/treatment-plans/{id}
Delete a plan
Delete a plan
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl -X DELETE https://api.vitrinadev.com/api/v1/clinic/treatment-plans/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Responde: 204 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/treatment-plans/{id}
One plan with its phases and progress
One plan with its phases and progress
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
curl https://api.vitrinadev.com/api/v1/clinic/treatment-plans/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "1ccffee2-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"name": "Ortodoncia fija superior e inferior",
"diagnosis": "Apiñamiento anterosuperior moderado",
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"status": "draft",
"accepted_at": null,
"completed_at": null,
"abandoned_reason": null,
"created_at": "2026-09-22T22:21:15.213Z",
"updated_at": "2026-09-22T22:21:15.213Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"phase": 1,
"quantity": 4,
"tooth_or_zone": null,
"status": "pending",
"appointment_id": null,
"budget_item_id": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:15.215Z",
"updated_at": "2026-09-22T22:21:15.215Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-000000000003",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"phase": 2,
"quantity": 1,
"tooth_or_zone": null,
"status": "pending",
"appointment_id": null,
"budget_item_id": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:15.215Z",
"updated_at": "2026-09-22T22:21:15.215Z",
"service_name": "Limpieza dental"
}
],
"progress": {
"pending": 5,
"scheduled": 0,
"performed": 0,
"cancelled": 0,
"active": 5,
"percent": 0,
"phases": [
{
"phase": 1,
"pending": 4,
"scheduled": 0,
"performed": 0,
"cancelled": 0,
"active": 4,
"percent": 0
},
{
"phase": 2,
"pending": 1,
"scheduled": 0,
"performed": 0,
"cancelled": 0,
"active": 1,
"percent": 0
}
],
"current_phase": 1
}
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /clinic/treatment-plans/{id}
Edit a plan
status: "abandoned" requires abandoned_reason — "why do plans die at phase 2" is only answerable if somebody wrote it.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
name | string | — | mín. 1, máx. 200 |
diagnosis | string | null | — | máx. 2000 |
professional_id | string | null | — | |
status | draft \ | proposed \ | accepted \ |
abandoned_reason | string | null | — | máx. 500 |
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/treatment-plans/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "proposed"
}'Ejemplo de respuesta (200)
{
"data": {
"id": "1ccffee2-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"name": "Ortodoncia fija superior e inferior",
"diagnosis": "Apiñamiento anterosuperior moderado",
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"status": "proposed",
"accepted_at": null,
"completed_at": null,
"abandoned_reason": null,
"created_at": "2026-09-22T22:21:15.213Z",
"updated_at": "2026-09-22T22:21:15.285Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"phase": 1,
"quantity": 4,
"tooth_or_zone": null,
"status": "pending",
"appointment_id": null,
"budget_item_id": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:15.215Z",
"updated_at": "2026-09-22T22:21:15.215Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-000000000003",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"phase": 2,
"quantity": 1,
"tooth_or_zone": null,
"status": "pending",
"appointment_id": null,
"budget_item_id": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:15.215Z",
"updated_at": "2026-09-22T22:21:15.215Z",
"service_name": "Limpieza dental"
}
],
"progress": {
"pending": 5,
"scheduled": 0,
"performed": 0,
"cancelled": 0,
"active": 5,
"percent": 0,
"phases": [
{
"phase": 1,
"pending": 4,
"scheduled": 0,
"performed": 0,
"cancelled": 0,
"active": 4,
"percent": 0
},
{
"phase": 2,
"pending": 1,
"scheduled": 0,
"performed": 0,
"cancelled": 0,
"active": 1,
"percent": 0
}
],
"current_phase": 1
}
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/treatment-plans/{id}/items
Add prestaciones to an open plan
Clinical authoring (an odontograma finding, a new phase): accepts clinic_money:write or clinic_record:write. Items carry no price — a presupuesto prices them. A clinical-only caller must be able to open the patient's ficha (own patients). A completed or abandoned plan answers 409 clinic_plan_closed.
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ámetro | En | Tipo | Obligatorio | Restricciones |
|---|---|---|---|---|
id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
items | object[] | sí |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/treatment-plans/<id>/items \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"quantity": 1,
"tooth_or_zone": {
"fdi": [
16
]
}
}
]
}'Ejemplo de respuesta (201)
{
"data": {
"id": "1ccffee2-0000-4000-8000-000000000001",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"source": "native",
"external_id": null,
"clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
"name": "Ortodoncia fija superior e inferior",
"diagnosis": "Apiñamiento anterosuperior moderado",
"professional_id": "18342d1b-0000-4000-8000-000000000001",
"status": "proposed",
"accepted_at": null,
"completed_at": null,
"abandoned_reason": null,
"created_at": "2026-09-22T22:21:15.213Z",
"updated_at": "2026-09-22T22:21:15.285Z",
"patient_name": "María José Fuentes Lagos",
"professional_name": "Ana Rojas Vidal",
"items": [
{
"id": "3a7d9767-0000-4000-8000-000000000002",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
"phase": 1,
"quantity": 4,
"tooth_or_zone": null,
"status": "scheduled",
"appointment_id": "d93e8eec-0000-4000-8000-000000000001",
"budget_item_id": null,
"sort_order": 0,
"created_at": "2026-09-22T22:21:15.215Z",
"updated_at": "2026-09-22T22:21:15.337Z",
"service_name": "Control de ortodoncia"
},
{
"id": "3a7d9767-0000-4000-8000-000000000003",
"tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
"treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
"clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
"phase": 2,
"quantity": 1,
"tooth_or_zone": null,
"status": "pending",
"appointment_id": null,
"budget_item_id": null,
"sort_order": 1,
"created_at": "2026-09-22T22:21:15.215Z",
"updated_at": "2026-09-22T22:21:15.215Z",
"service_name": "Limpieza dental"
}
],
"progress": {
"pending": 1,
"scheduled": 4,
"performed": 0,
"cancelled": 0,
"active": 5,
"percent": 0,
"phases": [
{
"phase": 1,
"pending": 0,
"scheduled": 4,
"performed": 0,
"cancelled": 0,
"active": 4,
"percent": 0
},
{
"phase": 2,
"pending": 1,
"scheduled": 0,
"performed": 0,
"cancelled": 0,
"active": 1,
"percent": 0
}
],
"current_phase": 1
}
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
Agenda de la clínica
El día de una clínica: leer la agenda, buscar horas libres, reservar y mover una cita, con los mismos ids sea cual sea el sistema que la lleva. Una aplicación conectada la lee con seudónimos de paciente.
Caja de la clínica
El turno en una sucursal: abrirlo con su fondo, cerrarlo contra un conteo y firmar el arqueo.