{
  "info": {
    "_postman_id": "49ab2881-29ab-457d-a48b-cf5a6318c70c",
    "name": "TotalMed API v1",
    "description": "API REST y webhooks de la plataforma clínica TotalMed.\n\n## Antes de empezar\n\n1. Abra las variables de la colección.\n2. Ponga su llave en `api_key`. Las de pruebas empiezan con `tm_test_`.\n3. Deje `base_url` en el ambiente de pruebas mientras desarrolla.\n4. Empiece por la carpeta **00 · Empezar aquí**.\n\n## Trazabilidad\n\n```\ncontact_id (su CRM)\n  └─▸ patient_id      patients.id\n       └─▸ appointment_id  appointments.patient_id\n            └─▸ sale_id      treatment_sales.appointment_id\n                 └─▸ payment_id  payments.sale_id\n                      └─▸ invoice_id invoices.payment_id\n```\n\n## Tres advertencias que evitan descuadres\n\n- Un pago con `pagado: false` no es dinero, es una intención de cobro.\n- La entrega de webhooks es al menos una vez: descarte los repetidos.\n- Un 200 al emitir un comprobante no significa aceptado por SUNAT.\n\n## Ficha clínica\n\nHistoria médica, fotografías y documentos quirúrgicos no se exponen por esta API. Ninguna llave puede leerlos, por diseño.\n\nDocumentación completa: https://developers.totalmed.lat",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{api_key}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "base_url",
      "value": "https://api.sandbox.totalmed.lat/v1",
      "type": "string",
      "description": "Pruebas: https://api.sandbox.totalmed.lat/v1 · Producción: https://api.totalmed.lat/v1"
    },
    {
      "key": "api_key",
      "value": "tm_test_SU_LLAVE_AQUI",
      "type": "string",
      "description": "Su llave de API. Nunca la suba a un repositorio."
    },
    {
      "key": "webhook_url",
      "value": "https://crm.ejemplo.pe/hooks/totalmed",
      "type": "string",
      "description": "Su endpoint receptor, para probar los payloads de la carpeta 08."
    }
  ],
  "item": [
    {
      "name": "00 · Empezar aquí",
      "description": "Verifique que su llave funciona y sepa contra qué clínica está trabajando.\n\nConfigure primero las variables de la colección: `base_url` y `api_key`.",
      "item": [
        {
          "name": "Identidad de la llave",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/whoami",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "whoami"
              ]
            },
            "description": "Devuelve la clínica, el ambiente, el país y los alcances de la llave en uso. Es la primera llamada que debe hacer."
          },
          "response": [
            {
              "name": "200 · Llave de pruebas",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/whoami",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "whoami"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"clinic_id\": \"9a2b4e01-7c33-4a19-bd50-6e2f1a884c07\",\n  \"clinica\": \"Estética Lima Norte\",\n  \"ambiente\": \"test\",\n  \"pais\": \"PE\",\n  \"moneda\": \"PEN\",\n  \"zona_horaria\": \"America/Lima\",\n  \"alcances\": [\n    \"patients:read\",\n    \"patients:write\",\n    \"appointments:read\",\n    \"appointments:write\",\n    \"sales:read\",\n    \"payments:read\",\n    \"invoices:read\"\n  ]\n}"
            },
            {
              "name": "401 · Llave revocada",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/whoami",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "whoami"
                  ]
                }
              },
              "status": "Unauthorized",
              "code": 401,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"unauthorized\",\n    \"message\": \"La llave no es válida o fue revocada.\",\n    \"field\": null\n  }\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "01 · Pacientes",
      "description": "La persona. Raíz de la cadena de trazabilidad.",
      "item": [
        {
          "name": "Listar pacientes",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/patients?limit=50&order=created_at.desc",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "patients"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50"
                },
                {
                  "key": "order",
                  "value": "created_at.desc"
                }
              ]
            },
            "description": "El total disponible viaja en la cabecera Content-Range, así no necesita una llamada aparte para contar."
          },
          "response": [
            {
              "name": "200 · Listado",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/patients?limit=50&order=created_at.desc",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "patients"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "50"
                    },
                    {
                      "key": "order",
                      "value": "created_at.desc"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                },
                {
                  "key": "Content-Range",
                  "value": "0-49/3482"
                }
              ],
              "body": "[\n  {\n    \"id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n    \"nombre\": \"Ana Quispe Rojas\",\n    \"rut\": \"45872103\",\n    \"ruc\": null,\n    \"telefono\": \"+51987654321\",\n    \"email\": \"ana.quispe@ejemplo.pe\",\n    \"fecha_nacimiento\": \"1991-04-18\",\n    \"direccion\": \"Av. Larco 1240\",\n    \"distrito_id\": \"c8a1f902-3e77-4d51-9b0a-71f2e4c33a10\",\n    \"etiquetas\": [\n      \"VIP\"\n    ],\n    \"datos_extra\": {\n      \"como_nos_conocio\": \"Instagram\"\n    },\n    \"created_at\": \"2026-09-09T18:22:04-05:00\"\n  }\n]"
            }
          ]
        },
        {
          "name": "Buscar por identificador del CRM",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/patients?external_id=eq.hubspot:8842",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "patients"
              ],
              "query": [
                {
                  "key": "external_id",
                  "value": "eq.hubspot:8842"
                }
              ]
            },
            "description": "El puente con su CRM. Formato del filtro: `eq.sistema:id`."
          },
          "response": [
            {
              "name": "200 · Encontrado",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/patients?external_id=eq.hubspot:8842",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "patients"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "eq.hubspot:8842"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "[\n  {\n    \"id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n    \"nombre\": \"Ana Quispe Rojas\",\n    \"rut\": \"45872103\",\n    \"ruc\": null,\n    \"telefono\": \"+51987654321\",\n    \"email\": \"ana.quispe@ejemplo.pe\",\n    \"fecha_nacimiento\": \"1991-04-18\",\n    \"direccion\": \"Av. Larco 1240\",\n    \"distrito_id\": \"c8a1f902-3e77-4d51-9b0a-71f2e4c33a10\",\n    \"etiquetas\": [\n      \"VIP\"\n    ],\n    \"datos_extra\": {\n      \"como_nos_conocio\": \"Instagram\"\n    },\n    \"created_at\": \"2026-09-09T18:22:04-05:00\"\n  }\n]"
            }
          ]
        },
        {
          "name": "Buscar por teléfono",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/patients?telefono=eq.%2B51987654321",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "patients"
              ],
              "query": [
                {
                  "key": "telefono",
                  "value": "eq.%2B51987654321"
                }
              ]
            },
            "description": "El signo + se escapa como %2B."
          },
          "response": [
            {
              "name": "200 · Encontrado",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/patients?telefono=eq.%2B51987654321",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "patients"
                  ],
                  "query": [
                    {
                      "key": "telefono",
                      "value": "eq.%2B51987654321"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "[\n  {\n    \"id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n    \"nombre\": \"Ana Quispe Rojas\",\n    \"rut\": \"45872103\",\n    \"ruc\": null,\n    \"telefono\": \"+51987654321\",\n    \"email\": \"ana.quispe@ejemplo.pe\",\n    \"fecha_nacimiento\": \"1991-04-18\",\n    \"direccion\": \"Av. Larco 1240\",\n    \"distrito_id\": \"c8a1f902-3e77-4d51-9b0a-71f2e4c33a10\",\n    \"etiquetas\": [\n      \"VIP\"\n    ],\n    \"datos_extra\": {\n      \"como_nos_conocio\": \"Instagram\"\n    },\n    \"created_at\": \"2026-09-09T18:22:04-05:00\"\n  }\n]"
            }
          ]
        },
        {
          "name": "Crear paciente con amarre al CRM",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "hubspot-8842-alta"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/patients",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "patients"
              ]
            },
            "description": "Envíe siempre Idempotency-Key. Si repite la misma clave dentro de 24 horas recibe la respuesta original, sin duplicar al paciente.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"nombre\": \"Ana Quispe Rojas\",\n  \"telefono\": \"+51987654321\",\n  \"rut\": \"45872103\",\n  \"email\": \"ana.quispe@ejemplo.pe\",\n  \"external_ids\": [\n    {\n      \"sistema\": \"hubspot\",\n      \"external_id\": \"8842\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "201 · Creado",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "hubspot-8842-alta"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/patients",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "patients"
                  ]
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n  \"nombre\": \"Ana Quispe Rojas\",\n  \"rut\": \"45872103\",\n  \"ruc\": null,\n  \"telefono\": \"+51987654321\",\n  \"email\": \"ana.quispe@ejemplo.pe\",\n  \"fecha_nacimiento\": \"1991-04-18\",\n  \"direccion\": \"Av. Larco 1240\",\n  \"distrito_id\": \"c8a1f902-3e77-4d51-9b0a-71f2e4c33a10\",\n  \"etiquetas\": [\n    \"VIP\"\n  ],\n  \"datos_extra\": {\n    \"como_nos_conocio\": \"Instagram\"\n  },\n  \"created_at\": \"2026-09-09T18:22:04-05:00\"\n}"
            }
          ]
        },
        {
          "name": "Actualizar paciente",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/patients/3f9a1c74-2b08-4e15-9d33-5c1e77a0b412",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "patients",
                "3f9a1c74-2b08-4e15-9d33-5c1e77a0b412"
              ]
            },
            "description": "Solo los campos que envía se modifican.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"ruc\": \"20548712399\",\n  \"etiquetas\": [\n    \"VIP\",\n    \"recurrente\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 · Actualizado",
              "originalRequest": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/patients/3f9a1c74-2b08-4e15-9d33-5c1e77a0b412",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "patients",
                    "3f9a1c74-2b08-4e15-9d33-5c1e77a0b412"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n  \"nombre\": \"Ana Quispe Rojas\",\n  \"rut\": \"45872103\",\n  \"ruc\": \"20548712399\",\n  \"telefono\": \"+51987654321\",\n  \"email\": \"ana.quispe@ejemplo.pe\",\n  \"fecha_nacimiento\": \"1991-04-18\",\n  \"direccion\": \"Av. Larco 1240\",\n  \"distrito_id\": \"c8a1f902-3e77-4d51-9b0a-71f2e4c33a10\",\n  \"etiquetas\": [\n    \"VIP\",\n    \"recurrente\"\n  ],\n  \"datos_extra\": {\n    \"como_nos_conocio\": \"Instagram\"\n  },\n  \"created_at\": \"2026-09-09T18:22:04-05:00\"\n}"
            }
          ]
        },
        {
          "name": "Estado de cuenta",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/patients/3f9a1c74-2b08-4e15-9d33-5c1e77a0b412/saldo",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "patients",
                "3f9a1c74-2b08-4e15-9d33-5c1e77a0b412",
                "saldo"
              ]
            },
            "description": "El saldo a favor es dinero abonado que todavía no se imputó a ninguna venta. No es un error, y su CRM debe contemplarlo."
          },
          "response": [
            {
              "name": "200 · Saldo",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/patients/3f9a1c74-2b08-4e15-9d33-5c1e77a0b412/saldo",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "patients",
                    "3f9a1c74-2b08-4e15-9d33-5c1e77a0b412",
                    "saldo"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n  \"moneda\": \"PEN\",\n  \"total_vendido\": 2400.0,\n  \"total_pagado\": 1650.0,\n  \"saldo_pendiente\": 750.0,\n  \"saldo_a_favor\": 0.0\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "02 · Citas",
      "description": "Agenda, asistencia y ausencia.",
      "item": [
        {
          "name": "Listar ausencias del mes",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/appointments?inicio=gte.2026-09-01&inicio=lt.2026-10-01&estado=eq.no_show",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "appointments"
              ],
              "query": [
                {
                  "key": "inicio",
                  "value": "gte.2026-09-01"
                },
                {
                  "key": "inicio",
                  "value": "lt.2026-10-01"
                },
                {
                  "key": "estado",
                  "value": "eq.no_show"
                }
              ]
            },
            "description": "El caso de uso más pedido: recuperar a quien no llegó."
          },
          "response": [
            {
              "name": "200 · Ausencias",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/appointments?inicio=gte.2026-09-01&inicio=lt.2026-10-01&estado=eq.no_show",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "appointments"
                  ],
                  "query": [
                    {
                      "key": "inicio",
                      "value": "gte.2026-09-01"
                    },
                    {
                      "key": "inicio",
                      "value": "lt.2026-10-01"
                    },
                    {
                      "key": "estado",
                      "value": "eq.no_show"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                },
                {
                  "key": "Content-Range",
                  "value": "0-11/12"
                }
              ],
              "body": "[\n  {\n    \"id\": \"12ac5f38-b1d7-4e60-9a22-4f08c7d5e913\",\n    \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n    \"professional_id\": \"b40d2e18-55c1-4a70-8e29-3d0b6f7c1a44\",\n    \"service_id\": \"5e77aa03-1d92-4c88-b6f0-2a9e3c50d711\",\n    \"sede_id\": \"7c1f6b20-9ae4-4f77-8b51-0d2a4e93c118\",\n    \"inicio\": \"2026-09-15T10:30:00-05:00\",\n    \"fin\": \"2026-09-15T11:15:00-05:00\",\n    \"estado\": \"no_show\",\n    \"sena_monto\": 100.0,\n    \"sena_pagada\": true,\n    \"notas\": null,\n    \"created_at\": \"2026-09-09T19:01:10-05:00\"\n  }\n]"
            }
          ]
        },
        {
          "name": "Agenda con paciente, venta y pagos",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/appointments?select=id,inicio,estado,patients(id,nombre,telefono),treatment_sales(id,tratamiento,precio_total,abonado,payments(id,monto,metodo,pagado))&inicio=gte.2026-09-01",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "appointments"
              ],
              "query": [
                {
                  "key": "select",
                  "value": "id,inicio,estado,patients(id,nombre,telefono),treatment_sales(id,tratamiento,precio_total,abonado,payments(id,monto,metodo,pagado))"
                },
                {
                  "key": "inicio",
                  "value": "gte.2026-09-01"
                }
              ]
            },
            "description": "Una sola llamada, cuatro niveles. Así evita el problema de N más uno."
          },
          "response": [
            {
              "name": "200 · Cadena completa",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/appointments?select=id,inicio,estado,patients(id,nombre,telefono),treatment_sales(id,tratamiento,precio_total,abonado,payments(id,monto,metodo,pagado))&inicio=gte.2026-09-01",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "appointments"
                  ],
                  "query": [
                    {
                      "key": "select",
                      "value": "id,inicio,estado,patients(id,nombre,telefono),treatment_sales(id,tratamiento,precio_total,abonado,payments(id,monto,metodo,pagado))"
                    },
                    {
                      "key": "inicio",
                      "value": "gte.2026-09-01"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "[\n  {\n    \"id\": \"12ac5f38-b1d7-4e60-9a22-4f08c7d5e913\",\n    \"inicio\": \"2026-09-15T10:30:00-05:00\",\n    \"estado\": \"atendida\",\n    \"patients\": {\n      \"id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n      \"nombre\": \"Ana Quispe Rojas\",\n      \"telefono\": \"+51987654321\"\n    },\n    \"treatment_sales\": [\n      {\n        \"id\": \"77bd4c91-6a05-4f13-88de-9c2b1e740f36\",\n        \"tratamiento\": \"Rejuvenecimiento facial, 3 sesiones\",\n        \"precio_total\": 2400.0,\n        \"abonado\": 1650.0,\n        \"payments\": [\n          {\n            \"id\": \"c4e1a802-77f5-4bb9-9e31-08c6d2a45f70\",\n            \"monto\": 1200.0,\n            \"metodo\": \"tarjeta\",\n            \"pagado\": true\n          },\n          {\n            \"id\": \"d8f2b511-3c40-4a97-b2e8-15d90c7a4e22\",\n            \"monto\": 450.0,\n            \"metodo\": \"efectivo\",\n            \"pagado\": true\n          }\n        ]\n      }\n    ]\n  }\n]"
            }
          ]
        },
        {
          "name": "Cupos libres",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/disponibilidad?professional_id=b40d2e18-55c1-4a70-8e29-3d0b6f7c1a44&service_id=5e77aa03-1d92-4c88-b6f0-2a9e3c50d711&fecha=2026-09-15",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "disponibilidad"
              ],
              "query": [
                {
                  "key": "professional_id",
                  "value": "b40d2e18-55c1-4a70-8e29-3d0b6f7c1a44"
                },
                {
                  "key": "service_id",
                  "value": "5e77aa03-1d92-4c88-b6f0-2a9e3c50d711"
                },
                {
                  "key": "fecha",
                  "value": "2026-09-15"
                }
              ]
            },
            "description": "Consulte antes de agendar. Un cupo tomado devuelve 409."
          },
          "response": [
            {
              "name": "200 · Cupos",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/disponibilidad?professional_id=b40d2e18-55c1-4a70-8e29-3d0b6f7c1a44&service_id=5e77aa03-1d92-4c88-b6f0-2a9e3c50d711&fecha=2026-09-15",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "disponibilidad"
                  ],
                  "query": [
                    {
                      "key": "professional_id",
                      "value": "b40d2e18-55c1-4a70-8e29-3d0b6f7c1a44"
                    },
                    {
                      "key": "service_id",
                      "value": "5e77aa03-1d92-4c88-b6f0-2a9e3c50d711"
                    },
                    {
                      "key": "fecha",
                      "value": "2026-09-15"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"fecha\": \"2026-09-15\",\n  \"zona_horaria\": \"America/Lima\",\n  \"cupos\": [\n    \"09:00\",\n    \"09:45\",\n    \"10:30\",\n    \"15:00\"\n  ]\n}"
            }
          ]
        },
        {
          "name": "Agendar cita",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "hubspot-8842-cita-1"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/appointments",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "appointments"
              ]
            },
            "description": "Si no envía fin, se calcula con la duración del servicio.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n  \"professional_id\": \"b40d2e18-55c1-4a70-8e29-3d0b6f7c1a44\",\n  \"service_id\": \"5e77aa03-1d92-4c88-b6f0-2a9e3c50d711\",\n  \"sede_id\": \"7c1f6b20-9ae4-4f77-8b51-0d2a4e93c118\",\n  \"inicio\": \"2026-09-15T10:30:00-05:00\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "201 · Agendada",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "hubspot-8842-cita-1"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/appointments",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "appointments"
                  ]
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"12ac5f38-b1d7-4e60-9a22-4f08c7d5e913\",\n  \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n  \"professional_id\": \"b40d2e18-55c1-4a70-8e29-3d0b6f7c1a44\",\n  \"service_id\": \"5e77aa03-1d92-4c88-b6f0-2a9e3c50d711\",\n  \"sede_id\": \"7c1f6b20-9ae4-4f77-8b51-0d2a4e93c118\",\n  \"inicio\": \"2026-09-15T10:30:00-05:00\",\n  \"fin\": \"2026-09-15T11:15:00-05:00\",\n  \"estado\": \"confirmada\",\n  \"sena_monto\": 100.0,\n  \"sena_pagada\": true,\n  \"notas\": null,\n  \"created_at\": \"2026-09-09T19:01:10-05:00\"\n}"
            },
            {
              "name": "409 · Cupo tomado",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "hubspot-8842-cita-1"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/appointments",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "appointments"
                  ]
                }
              },
              "status": "Conflict",
              "code": 409,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"slot_taken\",\n    \"message\": \"El profesional ya tiene una cita en ese horario.\",\n    \"field\": \"inicio\"\n  }\n}"
            }
          ]
        },
        {
          "name": "Marcar ausencia",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/appointments/12ac5f38-b1d7-4e60-9a22-4f08c7d5e913",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "appointments",
                "12ac5f38-b1d7-4e60-9a22-4f08c7d5e913"
              ]
            },
            "description": "Cambiar el estado dispara el evento appointment.status_changed.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"estado\": \"no_show\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 · Marcada",
              "originalRequest": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/appointments/12ac5f38-b1d7-4e60-9a22-4f08c7d5e913",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "appointments",
                    "12ac5f38-b1d7-4e60-9a22-4f08c7d5e913"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"12ac5f38-b1d7-4e60-9a22-4f08c7d5e913\",\n  \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n  \"professional_id\": \"b40d2e18-55c1-4a70-8e29-3d0b6f7c1a44\",\n  \"service_id\": \"5e77aa03-1d92-4c88-b6f0-2a9e3c50d711\",\n  \"sede_id\": \"7c1f6b20-9ae4-4f77-8b51-0d2a4e93c118\",\n  \"inicio\": \"2026-09-15T10:30:00-05:00\",\n  \"fin\": \"2026-09-15T11:15:00-05:00\",\n  \"estado\": \"no_show\",\n  \"sena_monto\": 100.0,\n  \"sena_pagada\": true,\n  \"notas\": null,\n  \"created_at\": \"2026-09-09T19:01:10-05:00\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "03 · Ventas",
      "description": "Tratamientos y productos. El eslabón entre la cita y el dinero.",
      "item": [
        {
          "name": "Ventas con sus pagos",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/treatment_sales?select=id,tratamiento,precio_total,abonado,payments(id,monto,metodo,pagado,created_at)&fecha=gte.2026-09-01",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "treatment_sales"
              ],
              "query": [
                {
                  "key": "select",
                  "value": "id,tratamiento,precio_total,abonado,payments(id,monto,metodo,pagado,created_at)"
                },
                {
                  "key": "fecha",
                  "value": "gte.2026-09-01"
                }
              ]
            },
            "description": "El saldo pendiente es precio_total menos abonado. No lo guardamos calculado, para que nunca quede desincronizado."
          },
          "response": [
            {
              "name": "200 · Listado",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/treatment_sales?select=id,tratamiento,precio_total,abonado,payments(id,monto,metodo,pagado,created_at)&fecha=gte.2026-09-01",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "treatment_sales"
                  ],
                  "query": [
                    {
                      "key": "select",
                      "value": "id,tratamiento,precio_total,abonado,payments(id,monto,metodo,pagado,created_at)"
                    },
                    {
                      "key": "fecha",
                      "value": "gte.2026-09-01"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "[\n  {\n    \"id\": \"77bd4c91-6a05-4f13-88de-9c2b1e740f36\",\n    \"tratamiento\": \"Rejuvenecimiento facial, 3 sesiones\",\n    \"precio_total\": 2400.0,\n    \"abonado\": 1650.0,\n    \"payments\": [\n      {\n        \"id\": \"c4e1a802-77f5-4bb9-9e31-08c6d2a45f70\",\n        \"monto\": 1200.0,\n        \"metodo\": \"tarjeta\",\n        \"pagado\": true,\n        \"created_at\": \"2026-09-09T19:06:03-05:00\"\n      }\n    ]\n  }\n]"
            }
          ]
        },
        {
          "name": "Registrar venta de tratamiento",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "hubspot-8842-venta-1"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/treatment_sales",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "treatment_sales"
              ]
            },
            "description": "Enlace la venta a la cita con appointment_id para cerrar la cadena.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n  \"appointment_id\": \"12ac5f38-b1d7-4e60-9a22-4f08c7d5e913\",\n  \"tratamiento\": \"Rejuvenecimiento facial, 3 sesiones\",\n  \"precio_total\": 2400.0,\n  \"fecha\": \"2026-09-09\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "201 · Registrada",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "hubspot-8842-venta-1"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/treatment_sales",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "treatment_sales"
                  ]
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"77bd4c91-6a05-4f13-88de-9c2b1e740f36\",\n  \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n  \"appointment_id\": \"12ac5f38-b1d7-4e60-9a22-4f08c7d5e913\",\n  \"tratamiento\": \"Rejuvenecimiento facial, 3 sesiones\",\n  \"precio_total\": 2400.0,\n  \"abonado\": 0.0,\n  \"fecha\": \"2026-09-09\",\n  \"created_at\": \"2026-09-09T19:04:22-05:00\"\n}"
            }
          ]
        },
        {
          "name": "Catálogo de productos con existencias",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/products?activo=eq.true",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "products"
              ],
              "query": [
                {
                  "key": "activo",
                  "value": "eq.true"
                }
              ]
            },
            "description": "Consúltelo antes de vender si su flujo lo necesita."
          },
          "response": [
            {
              "name": "200 · Catálogo",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/products?activo=eq.true",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "products"
                  ],
                  "query": [
                    {
                      "key": "activo",
                      "value": "eq.true"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "[\n  {\n    \"id\": \"a19c7d40-6b22-4c08-9e13-2f7a0d5b8e61\",\n    \"nombre\": \"Protector solar SPF 50+\",\n    \"precio\": 89.0,\n    \"stock\": 34,\n    \"stock_minimo\": 10,\n    \"activo\": true\n  }\n]"
            }
          ]
        },
        {
          "name": "Registrar venta de producto",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "hubspot-8842-producto-1"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/product_sales",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "product_sales"
              ]
            },
            "description": "Descuenta existencias. Sin stock suficiente la respuesta es 422 y la venta no se crea.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n  \"product_id\": \"a19c7d40-6b22-4c08-9e13-2f7a0d5b8e61\",\n  \"cantidad\": 1,\n  \"precio_unitario\": 89.0\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "201 · Registrada",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "hubspot-8842-producto-1"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/product_sales",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "product_sales"
                  ]
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"6b0e4d92-8a17-4f35-bc60-9e213f7a5c08\",\n  \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n  \"professional_id\": null,\n  \"product_id\": \"a19c7d40-6b22-4c08-9e13-2f7a0d5b8e61\",\n  \"payment_id\": null,\n  \"cantidad\": 1,\n  \"precio_unitario\": 89.0,\n  \"fecha\": \"2026-09-10\"\n}"
            },
            {
              "name": "422 · Sin stock",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "hubspot-8842-producto-1"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/product_sales",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "product_sales"
                  ]
                }
              },
              "status": "Unprocessable Entity",
              "code": 422,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"stock_insuficiente\",\n    \"message\": \"Quedan 2 unidades y la venta pide 5.\",\n    \"field\": \"cantidad\"\n  }\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "04 · Pagos",
      "description": "El recurso más delicado de la API.\n\nUn pago con `pagado: false` **no es dinero**, es una intención de cobro. Para conciliar ingresos reales filtre siempre por `pagado=eq.true`.",
      "item": [
        {
          "name": "Ingresos confirmados del mes",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/payments?select=monto,metodo,sede_id,created_at&pagado=eq.true&created_at=gte.2026-09-01T00:00:00-05:00&created_at=lt.2026-10-01T00:00:00-05:00&order=created_at.asc",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "payments"
              ],
              "query": [
                {
                  "key": "select",
                  "value": "monto,metodo,sede_id,created_at"
                },
                {
                  "key": "pagado",
                  "value": "eq.true"
                },
                {
                  "key": "created_at",
                  "value": "gte.2026-09-01T00:00:00-05:00"
                },
                {
                  "key": "created_at",
                  "value": "lt.2026-10-01T00:00:00-05:00"
                },
                {
                  "key": "order",
                  "value": "created_at.asc"
                }
              ]
            },
            "description": "Note el filtro pagado=eq.true. Sin él estaría sumando cobros que nunca entraron."
          },
          "response": [
            {
              "name": "200 · Ingresos",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/payments?select=monto,metodo,sede_id,created_at&pagado=eq.true&created_at=gte.2026-09-01T00:00:00-05:00&created_at=lt.2026-10-01T00:00:00-05:00&order=created_at.asc",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "payments"
                  ],
                  "query": [
                    {
                      "key": "select",
                      "value": "monto,metodo,sede_id,created_at"
                    },
                    {
                      "key": "pagado",
                      "value": "eq.true"
                    },
                    {
                      "key": "created_at",
                      "value": "gte.2026-09-01T00:00:00-05:00"
                    },
                    {
                      "key": "created_at",
                      "value": "lt.2026-10-01T00:00:00-05:00"
                    },
                    {
                      "key": "order",
                      "value": "created_at.asc"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                },
                {
                  "key": "Content-Range",
                  "value": "0-49/218"
                }
              ],
              "body": "[\n  {\n    \"monto\": 1200.0,\n    \"metodo\": \"tarjeta\",\n    \"sede_id\": \"7c1f6b20-9ae4-4f77-8b51-0d2a4e93c118\",\n    \"created_at\": \"2026-09-09T19:06:03-05:00\"\n  }\n]"
            }
          ]
        },
        {
          "name": "Obtener pago",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/payments/c4e1a802-77f5-4bb9-9e31-08c6d2a45f70",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "payments",
                "c4e1a802-77f5-4bb9-9e31-08c6d2a45f70"
              ]
            },
            "description": "Los tres caminos de un pago: sale_id para un tratamiento, record_id para una cirugía, y ambos nulos para un abono a cuenta."
          },
          "response": [
            {
              "name": "200 · Pago",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/payments/c4e1a802-77f5-4bb9-9e31-08c6d2a45f70",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "payments",
                    "c4e1a802-77f5-4bb9-9e31-08c6d2a45f70"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"c4e1a802-77f5-4bb9-9e31-08c6d2a45f70\",\n  \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n  \"sale_id\": \"77bd4c91-6a05-4f13-88de-9c2b1e740f36\",\n  \"record_id\": null,\n  \"sede_id\": \"7c1f6b20-9ae4-4f77-8b51-0d2a4e93c118\",\n  \"monto\": 1200.0,\n  \"metodo\": \"tarjeta\",\n  \"pagado\": true,\n  \"descuento_codigo\": null,\n  \"descuento_monto\": null,\n  \"created_at\": \"2026-09-09T19:06:03-05:00\"\n}"
            }
          ]
        },
        {
          "name": "Registrar cobro",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Idempotency-Key",
                "value": "hubspot-8842-pago-2"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/payments",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "payments"
              ]
            },
            "description": "Impute el cobro a la venta con sale_id para que la cadena quede cerrada.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n  \"sale_id\": \"77bd4c91-6a05-4f13-88de-9c2b1e740f36\",\n  \"monto\": 450.0,\n  \"metodo\": \"efectivo\",\n  \"pagado\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "201 · Registrado",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "hubspot-8842-pago-2"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/payments",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "payments"
                  ]
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"d8f2b511-3c40-4a97-b2e8-15d90c7a4e22\",\n  \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n  \"sale_id\": \"77bd4c91-6a05-4f13-88de-9c2b1e740f36\",\n  \"record_id\": null,\n  \"sede_id\": \"7c1f6b20-9ae4-4f77-8b51-0d2a4e93c118\",\n  \"monto\": 450.0,\n  \"metodo\": \"efectivo\",\n  \"pagado\": true,\n  \"descuento_codigo\": null,\n  \"descuento_monto\": null,\n  \"created_at\": \"2026-09-09T19:06:03-05:00\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "05 · Facturación",
      "description": "Perú: emisión a SUNAT vía Nubefact o FactuSmart. IGV 18 por ciento.\n\nUn `200` **no significa aceptado**. La verdad está en el campo `aceptada`.",
      "item": [
        {
          "name": "Emitir factura",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/facturacion/emitir",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "facturacion",
                "emitir"
              ]
            },
            "description": "Para factura el paciente necesita RUC válido de once dígitos. Para boleta basta el DNI.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"payment_id\": \"c4e1a802-77f5-4bb9-9e31-08c6d2a45f70\",\n  \"tipo\": \"factura\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 · Aceptada por SUNAT",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/facturacion/emitir",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "facturacion",
                    "emitir"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"invoice_id\": \"e05b3c77-9012-4d84-a6f1-73b2c0e8d445\",\n  \"dte_tipo\": \"factura\",\n  \"folio\": \"F001-00004821\",\n  \"monto\": 1200.0,\n  \"igv\": 183.05,\n  \"aceptada\": true,\n  \"motivo\": null,\n  \"pdf_url\": \"https://cdn.totalmed.lat/fe/F001-00004821.pdf\"\n}"
            },
            {
              "name": "200 · Rechazada, comprobante interno",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/facturacion/emitir",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "facturacion",
                    "emitir"
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"invoice_id\": \"e05b3c77-9012-4d84-a6f1-73b2c0e8d445\",\n  \"dte_tipo\": \"factura\",\n  \"folio\": null,\n  \"monto\": 1200.0,\n  \"igv\": 183.05,\n  \"aceptada\": false,\n  \"motivo\": \"El RUC del receptor no existe en el padrón de SUNAT.\",\n  \"pdf_url\": null\n}"
            }
          ]
        },
        {
          "name": "Listar comprobantes",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/invoices?limit=50&order=emitida_at.desc",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "invoices"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50"
                },
                {
                  "key": "order",
                  "value": "emitida_at.desc"
                }
              ]
            },
            "description": "Un folio nulo significa que la autoridad no aceptó y quedó comprobante interno."
          },
          "response": [
            {
              "name": "200 · Listado",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/invoices?limit=50&order=emitida_at.desc",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "invoices"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "50"
                    },
                    {
                      "key": "order",
                      "value": "emitida_at.desc"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "[\n  {\n    \"id\": \"e05b3c77-9012-4d84-a6f1-73b2c0e8d445\",\n    \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n    \"payment_id\": \"c4e1a802-77f5-4bb9-9e31-08c6d2a45f70\",\n    \"dte_tipo\": \"factura\",\n    \"monto\": 1200.0,\n    \"folio\": \"F001-00004821\",\n    \"pdf_url\": \"https://cdn.totalmed.lat/fe/F001-00004821.pdf\",\n    \"emitida_at\": \"2026-09-10T15:43:02-05:00\"\n  }\n]"
            }
          ]
        }
      ]
    },
    {
      "name": "06 · Identificadores externos",
      "description": "El puente con su CRM.\n\n```\ncontact_id (su CRM)\n  └─▸ patient_id      patients.id\n       └─▸ appointment_id  appointments.patient_id\n            └─▸ sale_id      treatment_sales.appointment_id\n                 └─▸ payment_id  payments.sale_id\n                      └─▸ invoice_id invoices.payment_id\n```",
      "item": [
        {
          "name": "Amarrar una entidad existente",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/external_ids",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "external_ids"
              ]
            },
            "description": "Un mismo external_id no puede quedar amarrado a dos entidades del mismo tipo dentro de una clínica. El segundo intento devuelve 409.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"entidad\": \"patient\",\n  \"entidad_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n  \"sistema\": \"hubspot\",\n  \"external_id\": \"8842\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "201 · Amarrado",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/external_ids",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "external_ids"
                  ]
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"id\": \"b21e6f03-4d88-4a12-9c57-0e3f8b21d740\",\n  \"entidad\": \"patient\",\n  \"entidad_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n  \"sistema\": \"hubspot\",\n  \"external_id\": \"8842\",\n  \"created_at\": \"2026-09-10T09:12:00-05:00\"\n}"
            }
          ]
        },
        {
          "name": "Pacientes todavía sin amarrar",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/patients?external_id=is.null&limit=1000",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "patients"
              ],
              "query": [
                {
                  "key": "external_id",
                  "value": "is.null"
                },
                {
                  "key": "limit",
                  "value": "1000"
                }
              ]
            },
            "description": "Úselo en la reconciliación, antes de encender los webhooks."
          },
          "response": [
            {
              "name": "200 · Sin amarrar",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/patients?external_id=is.null&limit=1000",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "patients"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "is.null"
                    },
                    {
                      "key": "limit",
                      "value": "1000"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                },
                {
                  "key": "Content-Range",
                  "value": "0-40/41"
                }
              ],
              "body": "[\n  {\n    \"id\": \"0a4e91b7-77c2-4f30-8d16-5b2a9e04c318\",\n    \"nombre\": \"Luis Ccahuana Ttito\",\n    \"rut\": \"09731556\",\n    \"ruc\": null,\n    \"telefono\": \"+51956120884\",\n    \"email\": \"ana.quispe@ejemplo.pe\",\n    \"fecha_nacimiento\": \"1991-04-18\",\n    \"direccion\": \"Av. Larco 1240\",\n    \"distrito_id\": \"c8a1f902-3e77-4d51-9b0a-71f2e4c33a10\",\n    \"etiquetas\": [],\n    \"datos_extra\": {\n      \"como_nos_conocio\": \"Instagram\"\n    },\n    \"created_at\": \"2026-09-09T18:22:04-05:00\"\n  }\n]"
            }
          ]
        }
      ]
    },
    {
      "name": "07 · Webhooks",
      "description": "Doce eventos, firmados con HMAC SHA-256.\n\n**La entrega es al menos una vez.** Guarde el `X-TotalMed-Delivery` y descarte los repetidos, o su CRM va a duplicar ingresos.\n\n**El orden no está garantizado.** Ordene por `created_at`, nunca por orden de llegada.\n\nCabeceras de cada envío:\n\n```\nX-TotalMed-Event: payment.confirmed\nX-TotalMed-Delivery: dlv_01J8X3QK7ZC4\nX-TotalMed-Timestamp: 1789412531\nX-TotalMed-Signature: sha256=8f2c1b…\n```\n\nLa firma es un HMAC SHA-256 sobre `timestamp + \".\" + cuerpo crudo`, con el secreto del endpoint. Verifíquela en tiempo constante y rechace todo envío con más de cinco minutos.",
      "item": [
        {
          "name": "Envíos fallidos",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/webhook_deliveries?event_type=eq.payment.confirmed&status=eq.failed",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "webhook_deliveries"
              ],
              "query": [
                {
                  "key": "event_type",
                  "value": "eq.payment.confirmed"
                },
                {
                  "key": "status",
                  "value": "eq.failed"
                }
              ]
            },
            "description": "Treinta días de historial, con cuerpo enviado, código de respuesta e intentos."
          },
          "response": [
            {
              "name": "200 · Fallidos",
              "originalRequest": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/webhook_deliveries?event_type=eq.payment.confirmed&status=eq.failed",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "webhook_deliveries"
                  ],
                  "query": [
                    {
                      "key": "event_type",
                      "value": "eq.payment.confirmed"
                    },
                    {
                      "key": "status",
                      "value": "eq.failed"
                    }
                  ]
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "[\n  {\n    \"id\": \"dlv_01J8X3QK7ZC4\",\n    \"event_id\": \"evt_01J8X3QK7ZC4\",\n    \"event_type\": \"payment.confirmed\",\n    \"url\": \"https://crm.ejemplo.pe/hooks/totalmed\",\n    \"status\": \"failed\",\n    \"intentos\": 7,\n    \"codigo_respuesta\": 502,\n    \"proximo_intento_at\": null,\n    \"created_at\": \"2026-09-10T15:42:11-05:00\"\n  }\n]"
            }
          ]
        },
        {
          "name": "Reenviar un envío",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{base_url}}/webhook_deliveries/dlv_01J8X3QK7ZC4/reenviar",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "webhook_deliveries",
                "dlv_01J8X3QK7ZC4",
                "reenviar"
              ]
            },
            "description": "Reenvío manual de un envío que agotó sus siete intentos."
          },
          "response": [
            {
              "name": "202 · Encolado",
              "originalRequest": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{base_url}}/webhook_deliveries/dlv_01J8X3QK7ZC4/reenviar",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "webhook_deliveries",
                    "dlv_01J8X3QK7ZC4",
                    "reenviar"
                  ]
                }
              },
              "status": "Accepted",
              "code": 202,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"delivery_id\": \"dlv_01J8X3QK7ZC4\",\n  \"status\": \"pending\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "08 · Payloads de ejemplo",
      "description": "Estos son los cuerpos exactos que enviamos a su endpoint. Apunte la variable `webhook_url` a su receptor y dispárelos para probar su integración sin esperar a que ocurra el hecho real.",
      "item": [
        {
          "name": "payment.confirmed · el dinero entró",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-TotalMed-Event",
                "value": "payment.confirmed"
              },
              {
                "key": "X-TotalMed-Delivery",
                "value": "dlv_01J8X3QK7ZC4"
              },
              {
                "key": "X-TotalMed-Timestamp",
                "value": "1789412531"
              },
              {
                "key": "X-TotalMed-Signature",
                "value": "sha256=8f2c1b9d40e7…"
              }
            ],
            "url": {
              "raw": "{{webhook_url}}",
              "host": [
                "{{webhook_url}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"evt_01J8X3QK7ZC4\",\n  \"type\": \"payment.confirmed\",\n  \"created_at\": \"2026-09-10T15:42:11-05:00\",\n  \"clinic_id\": \"9a2b4e01-7c33-4a19-bd50-6e2f1a884c07\",\n  \"data\": {\n    \"payment_id\": \"c4e1a802-77f5-4bb9-9e31-08c6d2a45f70\",\n    \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n    \"sale_id\": \"77bd4c91-6a05-4f13-88de-9c2b1e740f36\",\n    \"appointment_id\": \"12ac5f38-b1d7-4e60-9a22-4f08c7d5e913\",\n    \"record_id\": null,\n    \"sede_id\": \"7c1f6b20-9ae4-4f77-8b51-0d2a4e93c118\",\n    \"monto\": 450.0,\n    \"moneda\": \"PEN\",\n    \"metodo\": \"tarjeta\",\n    \"pagado\": true\n  },\n  \"external_ids\": {\n    \"patient\": {\n      \"hubspot\": \"8842\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "El evento que su CRM debe usar para contabilizar ingresos. Trae la cadena completa más el identificador de su sistema."
          },
          "response": []
        },
        {
          "name": "sale.created · se registró una venta",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-TotalMed-Event",
                "value": "sale.created"
              },
              {
                "key": "X-TotalMed-Delivery",
                "value": "dlv_01J8X3ZP8B71"
              },
              {
                "key": "X-TotalMed-Timestamp",
                "value": "1789412531"
              },
              {
                "key": "X-TotalMed-Signature",
                "value": "sha256=8f2c1b9d40e7…"
              }
            ],
            "url": {
              "raw": "{{webhook_url}}",
              "host": [
                "{{webhook_url}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"evt_01J8X3ZP8B71\",\n  \"type\": \"sale.created\",\n  \"created_at\": \"2026-09-09T19:04:22-05:00\",\n  \"clinic_id\": \"9a2b4e01-7c33-4a19-bd50-6e2f1a884c07\",\n  \"data\": {\n    \"sale_id\": \"77bd4c91-6a05-4f13-88de-9c2b1e740f36\",\n    \"tipo\": \"treatment\",\n    \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n    \"appointment_id\": \"12ac5f38-b1d7-4e60-9a22-4f08c7d5e913\",\n    \"sede_id\": \"7c1f6b20-9ae4-4f77-8b51-0d2a4e93c118\",\n    \"tratamiento\": \"Rejuvenecimiento facial, 3 sesiones\",\n    \"precio_total\": 2400.0,\n    \"abonado\": 1200.0,\n    \"moneda\": \"PEN\"\n  },\n  \"external_ids\": {\n    \"patient\": {\n      \"hubspot\": \"8842\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Se dispara tanto para venta de tratamiento como de producto. El campo tipo lo distingue."
          },
          "response": []
        },
        {
          "name": "appointment.status_changed · ausencia",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-TotalMed-Event",
                "value": "appointment.status_changed"
              },
              {
                "key": "X-TotalMed-Delivery",
                "value": "dlv_01J8X40FR2M9"
              },
              {
                "key": "X-TotalMed-Timestamp",
                "value": "1789412531"
              },
              {
                "key": "X-TotalMed-Signature",
                "value": "sha256=8f2c1b9d40e7…"
              }
            ],
            "url": {
              "raw": "{{webhook_url}}",
              "host": [
                "{{webhook_url}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"evt_01J8X40FR2M9\",\n  \"type\": \"appointment.status_changed\",\n  \"created_at\": \"2026-09-15T11:05:00-05:00\",\n  \"clinic_id\": \"9a2b4e01-7c33-4a19-bd50-6e2f1a884c07\",\n  \"data\": {\n    \"appointment_id\": \"12ac5f38-b1d7-4e60-9a22-4f08c7d5e913\",\n    \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n    \"sede_id\": \"7c1f6b20-9ae4-4f77-8b51-0d2a4e93c118\",\n    \"estado_anterior\": \"confirmada\",\n    \"estado\": \"no_show\",\n    \"inicio\": \"2026-09-15T10:30:00-05:00\"\n  },\n  \"external_ids\": {\n    \"patient\": {\n      \"hubspot\": \"8842\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Cubre la confirmación, la asistencia y la ausencia. Trae el estado anterior."
          },
          "response": []
        },
        {
          "name": "invoice.issued · SUNAT aceptó",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-TotalMed-Event",
                "value": "invoice.issued"
              },
              {
                "key": "X-TotalMed-Delivery",
                "value": "dlv_01J8X41TC5DE"
              },
              {
                "key": "X-TotalMed-Timestamp",
                "value": "1789412531"
              },
              {
                "key": "X-TotalMed-Signature",
                "value": "sha256=8f2c1b9d40e7…"
              }
            ],
            "url": {
              "raw": "{{webhook_url}}",
              "host": [
                "{{webhook_url}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"evt_01J8X41TC5DE\",\n  \"type\": \"invoice.issued\",\n  \"created_at\": \"2026-09-10T15:43:02-05:00\",\n  \"clinic_id\": \"9a2b4e01-7c33-4a19-bd50-6e2f1a884c07\",\n  \"data\": {\n    \"invoice_id\": \"e05b3c77-9012-4d84-a6f1-73b2c0e8d445\",\n    \"payment_id\": \"c4e1a802-77f5-4bb9-9e31-08c6d2a45f70\",\n    \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n    \"sale_id\": \"77bd4c91-6a05-4f13-88de-9c2b1e740f36\",\n    \"dte_tipo\": \"factura\",\n    \"folio\": \"F001-00004821\",\n    \"monto\": 1200.0,\n    \"igv\": 183.05,\n    \"moneda\": \"PEN\",\n    \"pdf_url\": \"https://cdn.totalmed.lat/fe/F001-00004821.pdf\"\n  },\n  \"external_ids\": {\n    \"patient\": {\n      \"hubspot\": \"8842\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Solo se dispara cuando la autoridad tributaria aceptó el comprobante."
          },
          "response": []
        },
        {
          "name": "patient.created · alta de paciente",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-TotalMed-Event",
                "value": "patient.created"
              },
              {
                "key": "X-TotalMed-Delivery",
                "value": "dlv_01J8X42BQ7HF"
              },
              {
                "key": "X-TotalMed-Timestamp",
                "value": "1789412531"
              },
              {
                "key": "X-TotalMed-Signature",
                "value": "sha256=8f2c1b9d40e7…"
              }
            ],
            "url": {
              "raw": "{{webhook_url}}",
              "host": [
                "{{webhook_url}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"evt_01J8X42BQ7HF\",\n  \"type\": \"patient.created\",\n  \"created_at\": \"2026-09-09T18:22:04-05:00\",\n  \"clinic_id\": \"9a2b4e01-7c33-4a19-bd50-6e2f1a884c07\",\n  \"data\": {\n    \"patient_id\": \"3f9a1c74-2b08-4e15-9d33-5c1e77a0b412\",\n    \"nombre\": \"Ana Quispe Rojas\",\n    \"telefono\": \"+51987654321\",\n    \"email\": \"ana.quispe@ejemplo.pe\",\n    \"rut\": \"45872103\"\n  },\n  \"external_ids\": {}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Útil cuando la clínica da de alta a alguien desde TotalMed y su CRM debe enterarse."
          },
          "response": []
        }
      ]
    }
  ]
}