POST
/appointmentsCitas
Crear una cita
Crea una nueva cita médica (programada o directa).
Crea una cita. La cita puede ser programada (`type: "p"`) o directa (`type: "d"`). El backend reenvía el payload a MedicalCore RD, que es extremadamente estricto con la estructura: un campo extra en `paciente` o un `area._id` mal mapeado devuelve 510 Not Extended.
Headers obligatorios: `Content-Type: application/json`, `x-api-key: <MEDEX_API_KEY>`, `enterpriseid: 150575`, `ownerid: 150575`.
Opción A (recomendada): envía `pacienteCedula` en la raíz y el backend busca el `raw` automáticamente.
Opción B (más rápida, validada): envía `citaPaciente.paciente` con el objeto `raw` saneado (sólo 11 campos). Evita llamadas extra al backend.
Mapeo crítico: `cita.area._id` debe ser el área de CONSULTA (660, 1816, 622), NO la specialty (1704, 1690, 1723). El wrapper `/areas` expone especialidades; las áreas de consulta son un subconjunto interno.
Timezone: `cita.start` en formato `-04:00` (AST) y `cita.end` en formato `Z` (UTC). Si se calcula mal → 510 "la fecha fin debe ser mayor a la fecha de inicio".
Saneamiento de `paciente`: el `raw` de `/patients/search` tiene ~40 campos. Sólo enviar los 11 básicos (`_id`, `nombre`, `apellido`, `sexo`, `email`, `fechaNacimiento`, `newPatient`, `numeroDeRecord`, `numeroIdentificacion`, `telefono`, `afiliaciones`).
Operations: leer de `paciente.raw.properties` — NO hardcodear todos a `false`, MedicalCore los usa para resolver el estado de admisión.
Headers extra: además de `x-api-key`, este endpoint requiere `enterpriseid: 150575` y `ownerid: 150575`.
Bug 510: casi todos los 510 de este endpoint vienen de: (a) `paciente` con campos extra, (b) `area._id` de specialty en vez de consulta, (c) `start/end` con timezone mal calculado, (d) falta `cita.pacienteId` a nivel raíz.
Flujo recomendado
Orden en que deberías llamar a los endpoints.
- 1GET
/doctors?branchId=392→ guardaridyspecialtyIddel doctor. - 2GET
/patients/search?cedula={cedula}→ guardaridyrawdel paciente. - 3GET
/appointments/availability?doctorId={id}&date={YYYY-MM-DD}→ guardarstartdel slot (en UTC). - 4GET
/services?specialtyId={specialtyId}→ guardar el primer servicio (su_id). - 5GET
/services/{serviceId}/coverages→ guardar el array completo decoverages. - 6Mapear
specialtyId→consultationAreaId(ver Mapeo de áreas más abajo). - 7Calcular
starten formato-04:00(AST) yendenZ(UTC) — ver regla de timezone. - 8POST
/appointmentscon el payload armado a partir de los datos anteriores.
Mapeo de campos
De dónde sale cada valor que tienes que enviar.
| Campo | Origen |
|---|---|
cita.citaPaciente.paciente | Campo `raw` de `/patients/search` (limpio, sólo 11 campos) |
cita.doctor | Backend lo resuelve automáticamente por `doctorId` |
cita.area._id | Mapeo specialty → consultation (660, 1816, 622). NO usar la specialty directamente. |
cita.subArea._id | ID del doctor (mismo valor que `doctorId`) |
cita.citaPaciente.service | Primer resultado de `/services?specialtyId=` |
cita.citaPaciente.coverages | Array completo de `/services/{id}/coverages` |
cita.operations | Leído de `paciente.raw.properties` (NO hardcodear `false`) |
cita.start | Slot UTC convertido a `-04:00` (AST) |
cita.end | `start + 20 minutos` en formato `Z` (UTC) |
Cuerpo de la petición
Objeto `cita` con todos los datos necesarios. Estructura validada contra MedicalCore RD.
Esquema
| Campo | Tipo | In | Requerido | Descripción |
|---|---|---|---|---|
cita.citaId | integer | query | opcional | Siempre `null` al crear. Enviar como `null` en el JSON. nullable |
cita.doctorIdej. 153625 | integer | query | requerido | ID numérico del doctor. |
cita.pacienteIdej. 4958123 | integer | query | requerido | ID del paciente. Debe ir a nivel raíz, no solo dentro de `citaPaciente`. |
cita.pacienteCedulaej. 223-0073480-7 | string | query | opcional | Cédula del paciente. Requerida si no envías `citaPaciente.paciente`. |
cita.attentionType | string | query | opcional | Tipo de atención. default: h |
cita.type | string | query | opcional | `p` = programada, `d` = directa. default: p |
cita.recurrente | boolean | query | opcional | — default: false |
cita.doctor._idej. 153625 | integer | query | opcional | ID del doctor (mismo que `doctorId`). |
cita.doctor.nombreej. Pedro | string | query | opcional | Nombre del doctor. |
cita.doctor.apellidoej. Rodriguez Gonzalez | string | query | opcional | Apellido del doctor. |
cita.doctor.fullNameej. Pedro Rodriguez Gonzalez | string | query | opcional | Nombre completo del doctor. |
cita.doctor.universalId | string | query | opcional | Identificador universal (puede ir vacío). default: |
cita.doctor.especialidadej. Medicina Interna | string | query | opcional | Nombre de la especialidad (con espacio inicial en el payload real). |
cita.area._idej. 660 | integer | query | requerido | Área de CONSULTA (mapeada desde specialty). 660, 1816, 622… NO usar la specialty directamente. |
cita.area.descriptionej. Consulta de Medicina Interna | string | query | opcional | Descripción legible del área. |
cita.area.type._id | integer | query | opcional | Tipo de área. Siempre `1` (Doctores) en este flujo. default: 1 |
cita.subArea._idej. 153625 | integer | query | opcional | ID del doctor (mismo que `doctorId`). |
cita.subArea.fullNameej. Pedro Rodriguez Gonzalez | string | query | opcional | Nombre completo del doctor. |
cita.startej. 2026-08-05T11:40:00-04:00 | string · date-time | query | requerido | Inicio de la cita en `-04:00` (AST), no UTC. |
cita.endej. 2026-08-05T16:00:00.000Z | string · date-time | query | requerido | Fin de la cita en `Z` (UTC), `start + 20 minutos`. |
cita.status | string | query | opcional | Estado inicial de la cita. default: Pendiente por confirmar |
cita.citaPaciente.place._id | integer | query | opcional | ID del lugar (Médico Express RD). default: 150575 |
cita.citaPaciente.service._idej. 2221 | integer | query | opcional | ID del servicio desde `/services?specialtyId=`. |
cita.citaPaciente.coverages[]ej. 75229 | integer | query | opcional | Array COMPLETO de coberturas devuelto por `/services/{id}/coverages`. |
cita.citaPaciente.paciente | string | query | opcional | Saneado a 11 campos. Tomado de `raw` de `/patients/search`. |
cita.branch._id | integer | query | opcional | ID de la sede (392 = San Isidro). default: 392 |
cita.servicioAseguradoraej. {"code":"1","description":"Ambulatorio"} | string | query | opcional | Objeto `{ code, description }`. Default `Ambulatorio`. |
cita.doneSaved | boolean | query | opcional | — default: false |
cita.operationsej. {"priority":false,"fallRisk":false,"wheelChair":false,"vip":false,"companion":false,"overWeight":false,"pregnant":false,"needSupport":false,"disabled":false,"special":false} | string | query | opcional | Objeto con 10 flags leídos de `paciente.raw.properties`. Ver sección Operations en las notas. |
cita.ubicacion | string | query | opcional | `null` por defecto. nullable |
cita.pagadorReferidor | string | query | opcional | `null` por defecto. nullable |
cita.pagador | string | query | opcional | Objeto pagador (vacío por defecto). default: {} |
cita.referallDoctors | string | query | opcional | Array de doctores referidores (vacío por defecto). default: [] |
cita.ordenMedicaej. {"numero":null,"doctor":{"_id":0,"fullName":null,"especialidad":null}} | string | query | opcional | Estructura fija con valores null. |
Ejemplos
Ejemplo 1
Opción A — mínima con cédulaEl backend busca el paciente por la cédula. Sólo lo mínimo indispensable.
opción-a-—-mínima-con-cédula.json·json
{
"cita": {
"citaId": null,
"doctorId": 153625,
"pacienteId": 4958123,
"pacienteCedula": "223-0073480-7",
"start": "2026-08-05T11:40:00-04:00",
"end": "2026-08-05T16:00:00.000Z",
"area": {
"_id": 660,
"description": "Consulta de Medicina Interna",
"type": {
"_id": 1,
"description": "Doctores"
}
},
"branch": {
"_id": 392,
"description": "San Isidro"
}
}
}Ejemplo 2
Opción B — payload completo validadoEstructura probada y validada con MedicalCore RD. Incluye todos los `raw` saneados.
opción-b-—-payload-completo-validado.json·json
{
"cita": {
"citaId": null,
"doctorId": 153625,
"pacienteId": 4958123,
"pacienteCedula": "223-0073480-7",
"attentionType": "h",
"type": "p",
"recurrente": false,
"doctor": {
"_id": 153625,
"nombre": "Pedro",
"apellido": "Rodriguez Gonzalez",
"fullName": "Pedro Rodriguez Gonzalez",
"universalId": "",
"especialidad": " Medicina Interna"
},
"area": {
"_id": 660,
"description": "Consulta de Medicina Interna",
"type": {
"_id": 1,
"description": "Doctores"
}
},
"subArea": {
"_id": 153625,
"fullName": "Pedro Rodriguez Gonzalez"
},
"start": "2026-08-05T11:40:00-04:00",
"end": "2026-08-05T16:00:00.000Z",
"status": "Pendiente por confirmar",
"citaPaciente": {
"place": {
"_id": 150575,
"name": "Médico Express RD"
},
"service": {
"_id": 2221,
"description": "CONSULTA DE SEGUNDA OPINIÓN LOCAL",
"duration": {
"_id": 1,
"label": "240 Minutos",
"value": "m",
"time": 240
},
"baseApiPath": "api/especialidad"
},
"pacienteId": 4958123,
"paciente": {
"_id": 4958123,
"nombre": "DANIEL GIOVANNI",
"apellido": "SANTILLAN PEREZ",
"sexo": "M",
"email": "SANTILLANDSP@GMAIL.COM",
"fechaNacimiento": "1989-05-26T08:00:00.000Z",
"newPatient": false,
"numeroDeRecord": "2024-3831",
"numeroIdentificacion": "223-0073480-7",
"telefono": [
{
"tipo": "Móvil",
"numero": "829-605-8450"
}
],
"afiliaciones": []
},
"comment": null,
"internment": false,
"coverages": [
{
"_id": 75229,
"codCobertura": "00423315A",
"description": "CONSULTA DE SEGUNDA OPINIÓN LOCAL",
"duration": {
"_id": 1,
"label": "20 Minutos",
"value": "m",
"time": 20
},
"tipo": "Otros",
"subTipo": " ",
"dispositivo": false,
"usePaymentLink": false,
"servicioAseguradora": {
"code": "1",
"description": "Ambulatorio"
}
}
]
},
"branch": {
"_id": 392,
"description": "San Isidro"
},
"servicioAseguradora": {
"code": "1",
"description": "Ambulatorio"
},
"doneSaved": false,
"operations": {
"priority": false,
"fallRisk": false,
"wheelChair": false,
"vip": false,
"companion": false,
"overWeight": false,
"pregnant": true,
"needSupport": false,
"disabled": false,
"special": false
},
"ubicacion": null,
"pagadorReferidor": null,
"pagador": {},
"referallDoctors": [],
"ordenMedica": {
"numero": null,
"doctor": {
"_id": 0,
"fullName": null,
"especialidad": null
}
}
}
}cURL
request.sh·bash
curl -X POST "https://apimedex.dploy.lol/api/v1/appointments" \
-H "Content-Type: application/json" \
-H "x-api-key: $MEDEX_API_KEY" \
-H "enterpriseid: 150575" \
-H "ownerid: 150575" \
-d '{
"cita": {
"citaId": null,
"doctorId": 153625,
"pacienteId": 4958123,
"pacienteCedula": "223-0073480-7",
"attentionType": "h",
"type": "p",
"recurrente": false,
"doctor": { "_id": 153625, "nombre": "Pedro", "apellido": "Rodriguez Gonzalez", "fullName": "Pedro Rodriguez Gonzalez", "universalId": "", "especialidad": " Medicina Interna" },
"area": { "_id": 660, "description": "Consulta de Medicina Interna", "type": { "_id": 1, "description": "Doctores" } },
"subArea": { "_id": 153625, "fullName": "Pedro Rodriguez Gonzalez" },
"start": "2026-08-05T11:40:00-04:00",
"end": "2026-08-05T16:00:00.000Z",
"status": "Pendiente por confirmar",
"citaPaciente": {
"place": { "_id": 150575, "name": "Médico Express RD" },
"service": { "_id": 2221, "description": "CONSULTA DE SEGUNDA OPINIÓN LOCAL", "duration": { "_id": 1, "label": "240 Minutos", "value": "m", "time": 240 }, "baseApiPath": "api/especialidad" },
"pacienteId": 4958123,
"paciente": { "_id": 4958123, "nombre": "DANIEL GIOVANNI", "apellido": "SANTILLAN PEREZ", "sexo": "M", "email": "SANTILLANDSP@GMAIL.COM", "fechaNacimiento": "1989-05-26T08:00:00.000Z", "newPatient": false, "numeroDeRecord": "2024-3831", "numeroIdentificacion": "223-0073480-7", "telefono": [{"tipo": "Móvil", "numero": "829-605-8450"}], "afiliaciones": [] },
"comment": null, "internment": false,
"coverages": [{ "_id": 75229, "codCobertura": "00423315A", "description": "CONSULTA DE SEGUNDA OPINIÓN LOCAL", "duration": { "_id": 1, "label": "20 Minutos", "value": "m", "time": 20 }, "tipo": "Otros", "subTipo": " ", "dispositivo": false, "usePaymentLink": false, "servicioAseguradora": { "code": "1", "description": "Ambulatorio" } }]
},
"branch": { "_id": 392, "description": "San Isidro" },
"servicioAseguradora": { "code": "1", "description": "Ambulatorio" },
"doneSaved": false,
"operations": { "priority": false, "fallRisk": false, "wheelChair": false, "vip": false, "companion": false, "overWeight": false, "pregnant": true, "needSupport": false, "disabled": false, "special": false },
"ubicacion": null, "pagadorReferidor": null, "pagador": {}, "referallDoctors": [],
"ordenMedica": { "numero": null, "doctor": { "_id": 0, "fullName": null, "especialidad": null } }
}
}'Respuestas4
| Status | Descripción |
|---|---|
| 200 | Cita creada exitosamente. |
| 400 | Estructura inválida o paciente no encontrado. Verificar `citaId`, `cita.pacienteId`, `cita.doctorId`, `cita.area._id`. |
| 510 | MedicalCore no pudo procesar. Casi siempre es: (a) `paciente` con campos extra del `raw`, (b) `area._id` de specialty en vez de consulta, (c) `start/end` con timezone mal calculado, (d) falta `cita.pacienteId` a nivel raíz. Ver Troubleshooting 510 abajo. |
| 510 | Mensaje alternativo: fechas inconsistentes. |
200
Cita creada exitosamente.response-200.json·json
{
"data": {
"citaId": "2026-XXXX",
"_id": 9000000,
"status": "Pendiente por confirmar"
}
}400
Estructura inválida o paciente no encontrado. Verificar `citaId`, `cita.pacienteId`, `cita.doctorId`, `cita.area._id`.response-400.json·json
{
"error": "Invalid appointment structure. Must contain cita object with doctorId and pacienteId."
}510
MedicalCore no pudo procesar. Casi siempre es: (a) `paciente` con campos extra del `raw`, (b) `area._id` de specialty en vez de consulta, (c) `start/end` con timezone mal calculado, (d) falta `cita.pacienteId` a nivel raíz. Ver Troubleshooting 510 abajo.response-510.json·json
{
"error": "Cannot read properties of null (reading 'admitionStatus')"
}510
Mensaje alternativo: fechas inconsistentes.response-510.json·json
{
"error": "la fecha fin debe ser mayor a la fecha de inicio"
}