Inventario de insumos
Los insumos de la clínica con su stock, mínimo y último precio; cada entrada, salida y conteo queda registrado con quién, cuándo y por qué, y los que están bajo su mínimo salen como «Por reponer».
Descarga la proyección completa de la API pública: openapi.json.
| Método | Ruta | Qué hace |
|---|---|---|
GET | /clinic/supplies | List insumos |
POST | /clinic/supplies | Add an insumo |
GET | /clinic/supplies/{id} | Get an insumo |
PATCH | /clinic/supplies/{id} | Edit an insumo |
POST | /clinic/supplies/{id}/archive | Archive an insumo |
GET | /clinic/supplies/{id}/movements | An insumo’s movement history |
POST | /clinic/supplies/{id}/movements | Record a manual entry or exit |
POST | /clinic/supplies/{id}/restore | Restore an archived insumo |
GET | /clinic/supplies/{id}/service-quantities | Quantity this insumo consumes per prestación |
DELETE | /clinic/supplies/{id}/service-quantities/{service_id} | Stop consuming this insumo for a prestación |
PUT | /clinic/supplies/{id}/service-quantities/{service_id} | Set the quantity a prestación consumes of this insumo |
POST | /clinic/supplies/counts | Record a periodic count |
GET | /clinic/supplies/unmapped-items | Purchase-invoice items still asking «¿qué insumo es?» |
POST | /clinic/supplies/unmapped-items/resolve | Say which insumo a supplier item is |
GET | /clinic/supply-orders | List supplier orders |
POST | /clinic/supply-orders | Write a supplier order by hand |
DELETE | /clinic/supply-orders/{id} | Delete a draft order |
GET | /clinic/supply-orders/{id} | One supplier order |
PATCH | /clinic/supply-orders/{id} | Edit a draft order |
POST | /clinic/supply-orders/{id}/cancel | Cancel an order |
POST | /clinic/supply-orders/{id}/close | Close an order with what arrived |
DELETE | /clinic/supply-orders/{id}/invoice | Unlink the purchase invoice from an open order |
PUT | /clinic/supply-orders/{id}/invoice | Receive an order against a purchase invoice |
GET | /clinic/supply-orders/{id}/invoice-candidates | Purchase invoices an order could be received against |
GET | /clinic/supply-orders/{id}/pdf | The order as a PDF for the supplier |
POST | /clinic/supply-orders/{id}/receipts | Receive an order by hand |
POST | /clinic/supply-orders/{id}/send | Mark an order as sent |
POST | /clinic/supply-orders/draft | Draft the orders from «Por reponer» |
GET | /clinic/supply-orders/suggestions | Suggested supplier orders from «Por reponer» |
GET /clinic/supplies
List insumos
The clinic’s insumos with stock on hand, unit, minimum, last purchase price and supplier, plus totals (active, archived, below minimum). A clinic’s first open materialises the starter catalogue (general consumables) — never a migration, so an archived insumo is not handed back. below_minimum=true is the «Por reponer» filter.
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 | active \ | archived \ | all |
below_minimum | query | true \ | false | no |
q | query | string | no | máx. 120 |
curl https://api.vitrinadev.com/api/v1/clinic/supplies \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"items": [
{
"id": "5a5a5a5a-0000-4000-8000-000000000001",
"name": "Guantes de examen",
"unit": "par",
"category": "Desechables",
"min_stock": 200,
"stock_on_hand": 150,
"below_minimum": true,
"last_unit_cost_clp": 198,
"last_purchase_on": "2026-10-05",
"supplier": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"archived_at": null,
"created_at": "2026-10-01T14:00:00.000Z",
"updated_at": "2026-10-05T15:20:00.000Z"
}
],
"totals": {
"active": 11,
"archived": 0,
"below_minimum": 1
}
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/supplies
Add an insumo
initial_stock is recorded as a count so the ledger explains it. 409 when a live insumo already has that name.
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 |
|---|---|---|---|
name | string | sí | mín. 1, máx. 120 |
unit | string | — | mín. 1, máx. 30 |
category | string | null | — | máx. 60 |
min_stock | number | — | ≥ 0, ≤ 999999999 |
supplier | string | null | — | máx. 200 |
supplier_tax_id | string | null | — | máx. 20 |
initial_stock | number | — | ≥ 0, ≤ 999999999 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/supplies \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Hilo dental",
"unit": "caja",
"category": "Insumos de box",
"min_stock": 5,
"initial_stock": 12
}'Ejemplo de respuesta (201)
{
"data": {
"id": "5a5a5a5a-0000-4000-8000-000000000002",
"name": "Hilo dental",
"unit": "caja",
"category": "Insumos de box",
"min_stock": 5,
"stock_on_hand": 12,
"below_minimum": false,
"last_unit_cost_clp": null,
"last_purchase_on": null,
"supplier": null,
"supplier_tax_id": null,
"archived_at": null,
"created_at": "2026-10-01T14:00:00.000Z",
"updated_at": "2026-10-05T15:20:00.000Z"
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/supplies/{id}
Get an insumo
Get an insumo
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/supplies/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "5a5a5a5a-0000-4000-8000-000000000001",
"name": "Guantes de examen",
"unit": "par",
"category": "Desechables",
"min_stock": 200,
"stock_on_hand": 150,
"below_minimum": true,
"last_unit_cost_clp": 198,
"last_purchase_on": "2026-10-05",
"supplier": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"archived_at": null,
"created_at": "2026-10-01T14:00:00.000Z",
"updated_at": "2026-10-05T15:20:00.000Z"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /clinic/supplies/{id}
Edit an insumo
Name, unit, category, minimum and supplier. Stock is never edited here: it moves by movements and counts.
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. 120 |
unit | string | — | mín. 1, máx. 30 |
category | string | null | — | máx. 60 |
min_stock | number | — | ≥ 0, ≤ 999999999 |
supplier | string | null | — | máx. 200 |
supplier_tax_id | string | null | — | máx. 20 |
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/supplies/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"min_stock": 300
}'Ejemplo de respuesta (200)
{
"data": {
"id": "5a5a5a5a-0000-4000-8000-000000000001",
"name": "Guantes de examen",
"unit": "par",
"category": "Desechables",
"min_stock": 300,
"stock_on_hand": 150,
"below_minimum": true,
"last_unit_cost_clp": 198,
"last_purchase_on": "2026-10-05",
"supplier": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"archived_at": null,
"created_at": "2026-10-01T14:00:00.000Z",
"updated_at": "2026-10-05T15:20:00.000Z"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/supplies/{id}/archive
Archive an insumo
Soft: its movements keep pointing at it and it leaves the list and the alert. Reversible with restore.
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 POST https://api.vitrinadev.com/api/v1/clinic/supplies/<id>/archive \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "5a5a5a5a-0000-4000-8000-000000000001",
"name": "Guantes de examen",
"unit": "par",
"category": "Desechables",
"min_stock": 200,
"stock_on_hand": 150,
"below_minimum": true,
"last_unit_cost_clp": 198,
"last_purchase_on": "2026-10-05",
"supplier": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"archived_at": "2026-10-09T12:00:00.000Z",
"created_at": "2026-10-01T14:00:00.000Z",
"updated_at": "2026-10-05T15:20:00.000Z"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/supplies/{id}/movements
An insumo’s movement history
Newest first: who (actor_user_id), when (occurred_at), why (kind + reason), the delta and the stock after. Cursor-paged. A consumption movement carries consumption: the prestación and the atención (cita, or the mirrored presupuesto) it came from, and reversal when it put the quantity back.
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í | |
limit | query | integer | no | ≥ 1, ≤ 200, por defecto 50 |
cursor | query | string | no | máx. 200 |
curl https://api.vitrinadev.com/api/v1/clinic/supplies/<id>/movements \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"items": [
{
"id": "5b5b5b5b-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"kind": "purchase",
"delta": 150,
"stock_after": 150,
"unit_cost_clp": 198,
"reason": "GUANTES NITRILO CAJA 100",
"actor_user_id": null,
"expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
"occurred_at": "2026-10-05T15:20:00.000Z",
"consumption": null
}
],
"next_cursor": null
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/supplies/{id}/movements
Record a manual entry or exit
An exit needs a reason (merma, vencimiento, préstamo…) and cannot take more than is on hand (409); a count corrects the difference.
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 |
|---|---|---|---|
direction | entry \ | exit | sí |
quantity | number | sí | > 0, ≤ 999999999 |
reason | string | null | — | máx. 300 |
unit_cost_clp | number | null | — | ≥ 0, ≤ 999999999 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/supplies/<id>/movements \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"direction": "exit",
"quantity": 10,
"reason": "Vencimiento"
}'Ejemplo de respuesta (201)
{
"data": {
"movement": {
"id": "5b5b5b5b-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"kind": "manual_exit",
"delta": -10,
"stock_after": 140,
"unit_cost_clp": null,
"reason": "Vencimiento",
"actor_user_id": "20000000-0000-4000-8000-000000000001",
"expense_id": null,
"occurred_at": "2026-10-05T15:20:00.000Z",
"consumption": null
},
"supply": {
"id": "5a5a5a5a-0000-4000-8000-000000000001",
"name": "Guantes de examen",
"unit": "par",
"category": "Desechables",
"min_stock": 200,
"stock_on_hand": 140,
"below_minimum": true,
"last_unit_cost_clp": 198,
"last_purchase_on": "2026-10-05",
"supplier": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"archived_at": null,
"created_at": "2026-10-01T14:00:00.000Z",
"updated_at": "2026-10-05T15:20:00.000Z"
}
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/supplies/{id}/restore
Restore an archived insumo
Restore an archived insumo
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 POST https://api.vitrinadev.com/api/v1/clinic/supplies/<id>/restore \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "5a5a5a5a-0000-4000-8000-000000000001",
"name": "Guantes de examen",
"unit": "par",
"category": "Desechables",
"min_stock": 200,
"stock_on_hand": 150,
"below_minimum": true,
"last_unit_cost_clp": 198,
"last_purchase_on": "2026-10-05",
"supplier": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"archived_at": null,
"created_at": "2026-10-01T14:00:00.000Z",
"updated_at": "2026-10-05T15:20:00.000Z"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/supplies/{id}/service-quantities
Quantity this insumo consumes per prestación
The optional quantities that make a performed prestación take this insumo out of stock. An insumo with none is never touched by a prestación and keeps the periodic count.
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/supplies/<id>/service-quantities \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"items": [
{
"id": "5d5d5d5d-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"clinic_service_id": "5e5e5e5e-0000-4000-8000-000000000001",
"service_name": "Limpieza dental",
"quantity": 2,
"effective_from": "2026-10-10T09:00:00.000Z",
"updated_at": "2026-10-10T09:00:00.000Z"
}
]
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
DELETE /clinic/supplies/{id}/service-quantities/{service_id}
Stop consuming this insumo for a prestación
Removes the quantity. What was already consumed stays in the history.
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í | |
service_id | path | uuid | sí |
curl -X DELETE https://api.vitrinadev.com/api/v1/clinic/supplies/<id>/service-quantities/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"removed": true
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PUT /clinic/supplies/{id}/service-quantities/{service_id}
Set the quantity a prestación consumes of this insumo
Per ONE performance of the prestación, in the insumo’s unit (a plan item of quantity 3 consumes 3×). Performing the prestación records a consumption movement linked to the atención; un-performing it puts the quantity back; a line is never counted twice, including mirrored (Dentalink) lines on every re-sync. A NEW quantity counts from now on: work performed before it is never consumed. Stock cannot go negative: a taking larger than the stock on hand is clamped and says so in the movement’s reason. 404 for an unknown insumo or prestación, 409 when the insumo is archived.
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í | |
service_id | path | uuid | sí |
Cuerpo
| Campo | Tipo | Obligatorio | Restricciones |
|---|---|---|---|
quantity | number | sí | > 0, ≤ 999999999 |
curl -X PUT https://api.vitrinadev.com/api/v1/clinic/supplies/<id>/service-quantities/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"quantity": 2
}'Ejemplo de respuesta (200)
{
"data": {
"id": "5d5d5d5d-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"clinic_service_id": "5e5e5e5e-0000-4000-8000-000000000001",
"service_name": "Limpieza dental",
"quantity": 2,
"effective_from": "2026-10-10T09:00:00.000Z",
"updated_at": "2026-10-10T09:00:00.000Z"
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/supplies/counts
Record a periodic count
Sets the stock of each counted insumo and records the difference as a count movement. All or nothing.
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 |
|---|---|---|---|
counts | object[] | sí | |
reason | string | null | — | máx. 300 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/supplies/counts \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"counts": [
{
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"counted_quantity": 320
}
],
"reason": "Conteo de octubre"
}'Ejemplo de respuesta (200)
{
"data": {
"results": [
{
"movement": {
"id": "5b5b5b5b-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"kind": "count",
"delta": 170,
"stock_after": 320,
"unit_cost_clp": null,
"reason": "Conteo de octubre",
"actor_user_id": null,
"expense_id": null,
"occurred_at": "2026-10-05T15:20:00.000Z",
"consumption": null
},
"supply": {
"id": "5a5a5a5a-0000-4000-8000-000000000001",
"name": "Guantes de examen",
"unit": "par",
"category": "Desechables",
"min_stock": 200,
"stock_on_hand": 320,
"below_minimum": false,
"last_unit_cost_clp": 198,
"last_purchase_on": "2026-10-05",
"supplier": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"archived_at": null,
"created_at": "2026-10-01T14:00:00.000Z",
"updated_at": "2026-10-05T15:20:00.000Z"
}
}
]
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/supplies/unmapped-items
Purchase-invoice items still asking «¿qué insumo es?»
Pending lines of confirmed purchase invoices, one row per (supplier, item code). The question is asked ONCE; after it is answered the same supplier item applies automatically. If a purchase feed carries totals only (no lines), nothing appears here and manual entry covers 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.
curl https://api.vitrinadev.com/api/v1/clinic/supplies/unmapped-items \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"items": [
{
"supplier_key": "76543210-K",
"supplier_name": "Dental Proveedor SpA",
"item_key": "code:gn-100",
"item_code": "GN-100",
"description": "GUANTES NITRILO CAJA 100",
"lines": 1,
"total_quantity": 3,
"last_unit_price_clp": 9900,
"last_seen_on": "2026-10-05"
}
]
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/supplies/unmapped-items/resolve
Say which insumo a supplier item is
Send exactly one of supply_id (an existing insumo), new_supply (create it now) or ignore (not an insumo: a service, freight). Pending lines of that supplier item are applied (stock goes up by quantity × units_per_item) and future ones apply automatically.
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 |
|---|---|---|---|
supplier_key | string | sí | mín. 1, máx. 200 |
item_key | string | sí | mín. 1, máx. 300 |
units_per_item | number | — | > 0, ≤ 1000000 |
supply_id | uuid | — | |
new_supply | object | — | |
ignore | true | — |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/supplies/unmapped-items/resolve \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"supplier_key": "76543210-K",
"item_key": "code:gn-100",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"units_per_item": 50
}'Ejemplo de respuesta (200)
{
"data": {
"supply": {
"id": "5a5a5a5a-0000-4000-8000-000000000001",
"name": "Guantes de examen",
"unit": "par",
"category": "Desechables",
"min_stock": 200,
"stock_on_hand": 150,
"below_minimum": true,
"last_unit_cost_clp": 198,
"last_purchase_on": "2026-10-05",
"supplier": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"archived_at": null,
"created_at": "2026-10-01T14:00:00.000Z",
"updated_at": "2026-10-05T15:20:00.000Z"
},
"applied_lines": 1,
"ignored_lines": 0
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/supply-orders
List supplier orders
Newest first. status is draft, sent, partially_received, received, closed or cancelled; the two received states are derived from the ledger. Each order carries its lines with ordered, received and the difference.
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 \ | partially_received \ |
limit | query | integer | no | ≥ 1, ≤ 200, por defecto 100 |
curl https://api.vitrinadev.com/api/v1/clinic/supply-orders \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"items": [
{
"id": "5d5d5d5d-0000-4000-8000-000000000001",
"number": 7,
"status": "partially_received",
"supplier_name": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"notes": null,
"expected_on": "2026-10-14",
"sent_at": "2026-10-09T15:00:00.000Z",
"closed_at": null,
"cancelled_at": null,
"created_at": "2026-10-09T14:30:00.000Z",
"updated_at": "2026-10-12T12:00:00.000Z",
"lines": [
{
"id": "5e5e5e5e-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"supply_name": "Guantes de examen",
"unit": "par",
"quantity_ordered": 300,
"quantity_received": 150,
"difference": -150,
"unit_cost_clp": 198
}
],
"totals": {
"lines": 1,
"ordered": 300,
"received": 150,
"estimated_total_clp": 59400
},
"invoice": {
"expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
"document_number": "F-2210",
"supplier": "Dental Proveedor SpA",
"spent_on": "2026-10-12",
"amount_clp": 29700,
"pending_lines": 0
},
"received_by_hand": false
}
]
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/supply-orders
Write a supplier order by hand
A draft for one supplier. 400 when an insumo does not exist or is archived. An order never moves stock by itself: goods enter through the purchase invoice or a manual receipt, and the order reads what arrived from the ledger.
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 |
|---|---|---|---|
supplier_name | string | null | — | máx. 200 |
supplier_tax_id | string | null | — | máx. 20 |
notes | string | null | — | máx. 1000 |
expected_on | string | null | — | patrón ^\d{4}-\d{2}-\d{2}$ |
lines | object[] | sí |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/supply-orders \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"supplier_name": "Dental Proveedor SpA",
"supplier_tax_id": "76.543.210-K",
"expected_on": "2026-10-14",
"lines": [
{
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"quantity": 300
}
]
}'Ejemplo de respuesta (201)
{
"data": {
"id": "5d5d5d5d-0000-4000-8000-000000000001",
"number": 7,
"status": "draft",
"supplier_name": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"notes": null,
"expected_on": "2026-10-14",
"sent_at": null,
"closed_at": null,
"cancelled_at": null,
"created_at": "2026-10-09T14:30:00.000Z",
"updated_at": "2026-10-12T12:00:00.000Z",
"lines": [
{
"id": "5e5e5e5e-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"supply_name": "Guantes de examen",
"unit": "par",
"quantity_ordered": 300,
"quantity_received": 150,
"difference": -150,
"unit_cost_clp": 198
}
],
"totals": {
"lines": 1,
"ordered": 300,
"received": 150,
"estimated_total_clp": 59400
},
"invoice": null,
"received_by_hand": false
}
}Responde: 201 · 400 · 401 · 403 · 404 · 409 · 429
DELETE /clinic/supply-orders/{id}
Delete a draft order
Drafts only; a sent order is cancelled instead.
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/supply-orders/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"removed": true
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/supply-orders/{id}
One supplier order
With its lines (ordered, received, difference), the purchase invoice it was received against and whether it was received by hand.
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/supply-orders/<id> \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "5d5d5d5d-0000-4000-8000-000000000001",
"number": 7,
"status": "partially_received",
"supplier_name": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"notes": null,
"expected_on": "2026-10-14",
"sent_at": "2026-10-09T15:00:00.000Z",
"closed_at": null,
"cancelled_at": null,
"created_at": "2026-10-09T14:30:00.000Z",
"updated_at": "2026-10-12T12:00:00.000Z",
"lines": [
{
"id": "5e5e5e5e-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"supply_name": "Guantes de examen",
"unit": "par",
"quantity_ordered": 300,
"quantity_received": 150,
"difference": -150,
"unit_cost_clp": 198
}
],
"totals": {
"lines": 1,
"ordered": 300,
"received": 150,
"estimated_total_clp": 59400
},
"invoice": {
"expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
"document_number": "F-2210",
"supplier": "Dental Proveedor SpA",
"spent_on": "2026-10-12",
"amount_clp": 29700,
"pending_lines": 0
},
"received_by_hand": false
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PATCH /clinic/supply-orders/{id}
Edit a draft order
Supplier, notes, expected date and (replacing the whole list) lines. 409 once the order was sent.
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 |
|---|---|---|---|
supplier_name | string | null | — | máx. 200 |
supplier_tax_id | string | null | — | máx. 20 |
notes | string | null | — | máx. 1000 |
expected_on | string | null | — | patrón ^\d{4}-\d{2}-\d{2}$ |
lines | object[] | — |
curl -X PATCH https://api.vitrinadev.com/api/v1/clinic/supply-orders/<id> \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"notes": "Entregar en recepción",
"lines": [
{
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"quantity": 300
}
]
}'Ejemplo de respuesta (200)
{
"data": {
"id": "5d5d5d5d-0000-4000-8000-000000000001",
"number": 7,
"status": "draft",
"supplier_name": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"notes": "Entregar en recepción",
"expected_on": "2026-10-14",
"sent_at": null,
"closed_at": null,
"cancelled_at": null,
"created_at": "2026-10-09T14:30:00.000Z",
"updated_at": "2026-10-12T12:00:00.000Z",
"lines": [
{
"id": "5e5e5e5e-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"supply_name": "Guantes de examen",
"unit": "par",
"quantity_ordered": 300,
"quantity_received": 150,
"difference": -150,
"unit_cost_clp": 198
}
],
"totals": {
"lines": 1,
"ordered": 300,
"received": 150,
"estimated_total_clp": 59400
},
"invoice": null,
"received_by_hand": false
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/supply-orders/{id}/cancel
Cancel an order
A draft or a sent order that has received nothing. One that already received goods is closed instead (409).
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 POST https://api.vitrinadev.com/api/v1/clinic/supply-orders/<id>/cancel \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "5d5d5d5d-0000-4000-8000-000000000001",
"number": 7,
"status": "cancelled",
"supplier_name": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"notes": null,
"expected_on": "2026-10-14",
"sent_at": "2026-10-09T15:00:00.000Z",
"closed_at": null,
"cancelled_at": "2026-10-10T12:00:00.000Z",
"created_at": "2026-10-09T14:30:00.000Z",
"updated_at": "2026-10-12T12:00:00.000Z",
"lines": [
{
"id": "5e5e5e5e-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"supply_name": "Guantes de examen",
"unit": "par",
"quantity_ordered": 300,
"quantity_received": 150,
"difference": -150,
"unit_cost_clp": 198
}
],
"totals": {
"lines": 1,
"ordered": 300,
"received": 150,
"estimated_total_clp": 59400
},
"invoice": null,
"received_by_hand": false
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/supply-orders/{id}/close
Close an order with what arrived
Stop waiting for the rest: a short delivery is accepted and the difference stays visible on the order. Stock is untouched.
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 POST https://api.vitrinadev.com/api/v1/clinic/supply-orders/<id>/close \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "5d5d5d5d-0000-4000-8000-000000000001",
"number": 7,
"status": "closed",
"supplier_name": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"notes": null,
"expected_on": "2026-10-14",
"sent_at": "2026-10-09T15:00:00.000Z",
"closed_at": "2026-10-15T12:00:00.000Z",
"cancelled_at": null,
"created_at": "2026-10-09T14:30:00.000Z",
"updated_at": "2026-10-12T12:00:00.000Z",
"lines": [
{
"id": "5e5e5e5e-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"supply_name": "Guantes de examen",
"unit": "par",
"quantity_ordered": 300,
"quantity_received": 150,
"difference": -150,
"unit_cost_clp": 198
}
],
"totals": {
"lines": 1,
"ordered": 300,
"received": 150,
"estimated_total_clp": 59400
},
"invoice": {
"expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
"document_number": "F-2210",
"supplier": "Dental Proveedor SpA",
"spent_on": "2026-10-12",
"amount_clp": 29700,
"pending_lines": 0
},
"received_by_hand": false
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
DELETE /clinic/supply-orders/{id}/invoice
Unlink the purchase invoice from an open order
The invoice’s stock stays; the order simply stops reading 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í |
curl -X DELETE https://api.vitrinadev.com/api/v1/clinic/supply-orders/<id>/invoice \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "5d5d5d5d-0000-4000-8000-000000000001",
"number": 7,
"status": "sent",
"supplier_name": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"notes": null,
"expected_on": "2026-10-14",
"sent_at": "2026-10-09T15:00:00.000Z",
"closed_at": null,
"cancelled_at": null,
"created_at": "2026-10-09T14:30:00.000Z",
"updated_at": "2026-10-12T12:00:00.000Z",
"lines": [
{
"id": "5e5e5e5e-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"supply_name": "Guantes de examen",
"unit": "par",
"quantity_ordered": 300,
"quantity_received": 150,
"difference": -150,
"unit_cost_clp": 198
}
],
"totals": {
"lines": 1,
"ordered": 300,
"received": 150,
"estimated_total_clp": 59400
},
"invoice": null,
"received_by_hand": false
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
PUT /clinic/supply-orders/{id}/invoice
Receive an order against a purchase invoice
Links the invoice to the order. Writes NO movement: the invoice’s own lines already moved (or will move, once mapped) the stock, and the order reads those movements as what it received, so nothing is counted twice. 409 when the order was already received by hand, the invoice has no insumo lines or it is already another order’s receipt.
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 |
|---|---|---|---|
expense_id | uuid | sí |
curl -X PUT https://api.vitrinadev.com/api/v1/clinic/supply-orders/<id>/invoice \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"expense_id": "5c5c5c5c-0000-4000-8000-000000000001"
}'Ejemplo de respuesta (200)
{
"data": {
"id": "5d5d5d5d-0000-4000-8000-000000000001",
"number": 7,
"status": "partially_received",
"supplier_name": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"notes": null,
"expected_on": "2026-10-14",
"sent_at": "2026-10-09T15:00:00.000Z",
"closed_at": null,
"cancelled_at": null,
"created_at": "2026-10-09T14:30:00.000Z",
"updated_at": "2026-10-12T12:00:00.000Z",
"lines": [
{
"id": "5e5e5e5e-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"supply_name": "Guantes de examen",
"unit": "par",
"quantity_ordered": 300,
"quantity_received": 150,
"difference": -150,
"unit_cost_clp": 198
}
],
"totals": {
"lines": 1,
"ordered": 300,
"received": 150,
"estimated_total_clp": 59400
},
"invoice": {
"expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
"document_number": "F-2210",
"supplier": "Dental Proveedor SpA",
"spent_on": "2026-10-12",
"amount_clp": 29700,
"pending_lines": 0
},
"received_by_hand": false
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/supply-orders/{id}/invoice-candidates
Purchase invoices an order could be received against
Recent invoices (180 days) with insumo lines from the same supplier or for insumos on the order, not already the receipt of another order. pending_lines are lines still asking «¿qué insumo es?».
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/supply-orders/<id>/invoice-candidates \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"items": [
{
"expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
"document_number": "F-2210",
"supplier": "Dental Proveedor SpA",
"spent_on": "2026-10-12",
"amount_clp": 29700,
"pending_lines": 0,
"matching_lines": 1
}
]
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/supply-orders/{id}/pdf
The order as a PDF for the supplier
Insumo, quantity and unit, the clinic, the date and the notes; none of the clinic’s stock levels or prices.
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/supply-orders/<id>/pdf \
-H "Authorization: Bearer $VITRINA_KEY"Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/supply-orders/{id}/receipts
Receive an order by hand
The goods are here and the invoice is not: each line raises the stock as an order_receipt movement tied to the order. 409 when the order is received against an invoice (that would count the goods twice) or is not open.
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 |
|---|---|---|---|
lines | object[] | sí | |
note | string | null | — | máx. 300 |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/supply-orders/<id>/receipts \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"lines": [
{
"line_id": "5e5e5e5e-0000-4000-8000-000000000001",
"quantity": 150
}
],
"note": "Guía de despacho 8841"
}'Ejemplo de respuesta (200)
{
"data": {
"id": "5d5d5d5d-0000-4000-8000-000000000001",
"number": 7,
"status": "partially_received",
"supplier_name": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"notes": null,
"expected_on": "2026-10-14",
"sent_at": "2026-10-09T15:00:00.000Z",
"closed_at": null,
"cancelled_at": null,
"created_at": "2026-10-09T14:30:00.000Z",
"updated_at": "2026-10-12T12:00:00.000Z",
"lines": [
{
"id": "5e5e5e5e-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"supply_name": "Guantes de examen",
"unit": "par",
"quantity_ordered": 300,
"quantity_received": 150,
"difference": -150,
"unit_cost_clp": 198
}
],
"totals": {
"lines": 1,
"ordered": 300,
"received": 150,
"estimated_total_clp": 59400
},
"invoice": null,
"received_by_hand": true
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/supply-orders/{id}/send
Mark an order as sent
Freezes the lines. Needs a supplier name and at least one line. The PDF (GET …/pdf) is the sheet to send. An order never moves stock by itself: goods enter through the purchase invoice or a manual receipt, and the order reads what arrived from the ledger.
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 POST https://api.vitrinadev.com/api/v1/clinic/supply-orders/<id>/send \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"id": "5d5d5d5d-0000-4000-8000-000000000001",
"number": 7,
"status": "sent",
"supplier_name": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"notes": null,
"expected_on": "2026-10-14",
"sent_at": "2026-10-09T15:00:00.000Z",
"closed_at": null,
"cancelled_at": null,
"created_at": "2026-10-09T14:30:00.000Z",
"updated_at": "2026-10-12T12:00:00.000Z",
"lines": [
{
"id": "5e5e5e5e-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"supply_name": "Guantes de examen",
"unit": "par",
"quantity_ordered": 300,
"quantity_received": 150,
"difference": -150,
"unit_cost_clp": 198
}
],
"totals": {
"lines": 1,
"ordered": 300,
"received": 150,
"estimated_total_clp": 59400
},
"invoice": null,
"received_by_hand": false
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
POST /clinic/supply-orders/draft
Draft the orders from «Por reponer»
One draft per supplier with the suggested quantities (supply_ids narrows it). A supplier that already has an open draft gets the missing insumos added to it; quantities already on it are never overwritten. Insumos without a supplier go on a draft with no supplier name, to be named before sending. An order never moves stock by itself: goods enter through the purchase invoice or a manual receipt, and the order reads what arrived from the ledger.
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 |
|---|---|---|---|
coverage_days | integer | — | ≥ 0, ≤ 365, por defecto 30 |
supply_ids | uuid[] | — |
curl -X POST https://api.vitrinadev.com/api/v1/clinic/supply-orders/draft \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"coverage_days": 30
}'Ejemplo de respuesta (200)
{
"data": {
"orders": [
{
"id": "5d5d5d5d-0000-4000-8000-000000000001",
"number": 7,
"status": "draft",
"supplier_name": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"notes": null,
"expected_on": "2026-10-14",
"sent_at": null,
"closed_at": null,
"cancelled_at": null,
"created_at": "2026-10-09T14:30:00.000Z",
"updated_at": "2026-10-12T12:00:00.000Z",
"lines": [
{
"id": "5e5e5e5e-0000-4000-8000-000000000001",
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"supply_name": "Guantes de examen",
"unit": "par",
"quantity_ordered": 245,
"quantity_received": 0,
"difference": -245,
"unit_cost_clp": 198
}
],
"totals": {
"lines": 1,
"ordered": 245,
"received": 0,
"estimated_total_clp": 48510
},
"invoice": null,
"received_by_hand": false
}
],
"nothing_to_order": false
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
GET /clinic/supply-orders/suggestions
Suggested supplier orders from «Por reponer»
The insumos below their minimum, grouped by supplier, each with a suggested quantity: minimum + daily consumption (last 60 days, from the prestaciones performed) × coverage_days, or twice the minimum when there is no consumption, minus what is already on sent orders not yet received. open_draft_id is the draft «Crear pedido» would extend.
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 |
|---|---|---|---|---|
coverage_days | query | integer | null | no | ≥ 0, ≤ 365, por defecto 30 |
curl https://api.vitrinadev.com/api/v1/clinic/supply-orders/suggestions \
-H "Authorization: Bearer $VITRINA_KEY"Ejemplo de respuesta (200)
{
"data": {
"items": [
{
"supplier_key": "76543210-K",
"supplier_name": "Dental Proveedor SpA",
"supplier_tax_id": "76543210-K",
"open_draft_id": null,
"lines": [
{
"supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
"name": "Guantes de examen",
"unit": "par",
"min_stock": 200,
"stock_on_hand": 150,
"daily_consumption": 6.5,
"on_order": 0,
"last_unit_cost_clp": 198,
"suggested_quantity": 245
}
]
}
]
}
}Responde: 200 · 400 · 401 · 403 · 404 · 409 · 429
Sugerencias del panel de inicio
Hasta tres tarjetas con la cifra de la propia clínica que justifica abrir un módulo (pagos sin boleta, comisiones sin liquidar, pagos sin conciliar, presupuestos aceptados sin cita, gasto en publicidad), y cómo cerrar una para quien la ve.
Empresas
Las organizaciones a las que pertenecen los contactos. Agrupan personas; la identidad legal vive en el contacto que representa a la empresa.