IPS Horizonte Salud agenda citas desde su call center y su portal, y concilia los giros de las EPS desde su sistema de cartera. En esta receta conectas ese recorrido con Compense: buscas un cupo, agendas a nombre del paciente, recuerdas la cita, manejas cancelaciones y reprogramaciones, registras la asistencia, ubicas la factura de la atención y registras el pago de la EPS sin duplicarlo. Los detalles de cada operación están en la guía de Salud.

sequenceDiagram
    autonumber
    participant S as Sistema de IPS Horizonte Salud
    participant G as Gateway Compense
    participant P as Panel de la IPS
    S->>G: availability (especialidad, fecha)
    S->>G: book {document, slot_id} + Idempotency-Key
    G-)S: appointment.booked
    S->>G: appointments.list(when=upcoming) → recordatorio 24 h antes
    opt el paciente no puede
        S->>G: reschedule / cancel
        G-)S: appointment.rescheduled / appointment.cancelled
    end
    S->>G: status completed / no_show
    G-)S: appointment.status_changed
    P->>P: facturación emite la factura (DIAN) y la radica ante la EPS
    S->>G: billing.list_invoices(document, status=CUV_OK,RADICADA) + XML + RIPS
    S->>G: billing.list_glosas(invoice)
    S->>G: register_payment (método + referencia del giro)
    G-)S: health_billing.payment.registered

Requisitos #

  • Una llave con appointments:read, appointments:write, health_billing:read y health_billing:collect.
  • Un endpoint de webhooks suscrito a appointment.booked, appointment.cancelled, appointment.rescheduled, appointment.status_changed y health_billing.payment.registered.

Los ejemplos crean el cliente en el paso 1 y lo reutilizan; en Go asumen ctx y client creados como en el quickstart.

1. Busca un cupo y agenda #

Consulta la disponibilidad del día (o asap=true para los primeros cupos libres) y agenda el que elija el paciente. Entre tu consulta y el agendamiento otro canal puede tomar el cupo: en ese caso la respuesta es 409 con SLOT_NOT_AVAILABLE (type …/upstream-error) y pasas al siguiente. Usa una Idempotency-Key por solicitud y cupo, para que un reintento por red del mismo intento no agende dos veces.

Si el paciente no existe en la IPS, la respuesta es 404 HEALTH_PATIENT_NOT_FOUND: reenvía con new_patient (ver Agendar en nombre del paciente).

import os
from compense_gateway import CompenseClient, IdempotencyConflictError

client = CompenseClient(api_key=os.environ["COMPENSE_API_KEY"], environment="sandbox")
PACIENTE = "1000000001"
SOLICITUD = "HORIZONTE-SAL-2026-00311"      # id de la solicitud en tu sistema

cupos = client.health.availability(date="2026-11-03")
cita = None
for cupo in cupos.results:
    try:
        tx = client.health.appointments.book(
            document=PACIENTE, slot_id=cupo["id"], appointment_type="first_time",
            consultation_type="presencial", channel="telefono", reason="Dolor lumbar",
            idempotency_key=f"{SOLICITUD}-{cupo['id']}",
        )
        cita = tx.data
        break
    except IdempotencyConflictError as e:
        if e.problem.type.endswith("/idempotency-conflict"):
            raise                                   # tu intento anterior sigue en curso
        continue                                    # SLOT_NOT_AVAILABLE: siguiente cupo

print(cita["id"], cita["appointment_code"], cita["date"], cita["start_time"])

Guarda en tu sistema el id de la cita, su appointment_code (CITA-AAAA-NNNNNN, el que le dictas al paciente) y el patient.id (identificador estable del paciente; el documento y el nombre vuelven enmascarados).

2. Recuerda, cancela o reprograma #

Compense no envía recordatorios por API: tu sistema los programa. Una vez al día, lista las citas próximas de tus pacientes con appointments.list(document, when="upcoming") (o mantén tu agenda al día con los webhooks appointment.*) y envía el recordatorio por tu canal.

Si el paciente no puede asistir:

  • Reprogramar exige al menos 2 horas de anticipación sobre la cita actual y un cupo nuevo libre; el cupo original se libera si faltaba más de una hora. La cita reprogramada puede cambiar de id: actualiza el que guardaste.
  • Cancelar libera el cupo y exige un motivo de al menos 5 caracteres. Si la cancelación nace del paciente (por ejemplo en tu portal), envía on_behalf_of_patient: true para aplicar sus reglas (incluida la de 2 horas). Repetir una cancelación responde 200 con already_cancelled: true.
proximas = client.health.appointments.list(PACIENTE, when="upcoming")
for c in proximas.results:
    enviar_recordatorio(PACIENTE, c["appointment_code"], c["date"], c["start_time"])  # tu canal

tx = client.health.appointments.reschedule(
    cita["id"], new_slot_id="slt-D2-20261104-1000",
    idempotency_key=f"{SOLICITUD}-reprograma-1",
)
cita = tx.data                                     # el id puede cambiar

# O cancelar a pedido del paciente desde el portal:
# client.health.appointments.cancel(cita["id"], reason="Viaje del paciente",
#                                   on_behalf_of_patient=True, idempotency_key=f"{SOLICITUD}-cancela")

3. Registra la asistencia #

Desde la hora de la cita, tu sistema de admisiones marca si el paciente asistió (completed) o no (no_show). Antes de esa hora la respuesta es 422. La asistencia es la base para facturar la atención (y, si no asistió, para tus indicadores de inasistencia).

client.health.appointments.set_attendance(
    cita["id"], status="completed", idempotency_key=f"{SOLICITUD}-asistencia",
)

4. Ubica la factura y sus soportes #

Emitir la factura electrónica, anularla y radicarla ante la EPS se hace en el panel de la IPS (envían documentos a la DIAN y exigen revisión humana). Por API ubicas la factura del paciente, su estado y descargas el XML y los RIPS para tu archivo o para la radicación. Antes de cobrar, revisa las glosas: lo glosado no se paga hasta que la EPS levante la glosa.

facturas = client.health.billing.list_invoices(document=PACIENTE, status="CUV_OK,RADICADA")
factura = facturas.results[0]
print(factura["number"], factura["status"], factura["net_amount"], factura["balance"],
      factura.get("radication_number"))

xml = client.health.billing.get_invoice_xml(factura["id"])
rips = client.health.billing.get_invoice_rips(factura["id"])
glosas = client.health.billing.list_glosas(invoice=factura["id"])
glosado = sum(float(g["amount"]) for g in glosas.results)
cobrable = float(factura["balance"]) - glosado

5. Registra el pago de la EPS #

Cuando llega el giro de la EPS (lo ves en tu extracto o en la relación de pagos del pagador), regístralo sobre la factura. El registro está protegido dos veces contra duplicados:

  • por la Idempotency-Key en el gateway, y
  • por la llave natural del motor: método + referencia. El mismo giro registrado otra vez (aunque llegue con otra llave, desde otro proceso o desde el panel) responde 200 con created: false y no suma dos veces.

Por eso conviene que la Idempotency-Key también salga de la llave natural (<método>-<referencia>), y que reference sea el número de giro tal como lo reporta la EPS. method es TRANSFERENCIA, CONSIGNACION, GIRO_DIRECTO o RETENCION; el monto nunca puede superar el saldo no glosado (422).

giro = {"metodo": "TRANSFERENCIA", "referencia": "GIRO-778899", "valor": "53500", "fecha": "2026-10-27"}

tx = client.health.billing.register_payment(
    factura["id"], amount=giro["valor"], method=giro["metodo"],
    reference=giro["referencia"], paid_at=giro["fecha"],
    idempotency_key=f"horizonte-eps-{giro['metodo']}-{giro['referencia']}",
)
r = tx.data
if not r["created"]:
    print("Ese giro ya estaba registrado")
print(r["invoice"]["status"], r["invoice"]["paid_amount"], r["invoice"]["balance"])

Un giro de la EPS suele cubrir varias facturas. Regístralo factura por factura con la misma reference y el valor aplicado a cada una; la llave natural evita que un reproceso del archivo de pagos duplique alguna.

6. Escucha los webhooks #

Con los eventos mantienes tu agenda y tu cartera sincronizadas aunque los cambios vengan del panel o de otro canal. Ninguno trae el documento completo ni el nombre del paciente: cruza por ids.

EventoQué hacer en tu sistema
appointment.bookedRegistrar la cita (también las agendadas desde otros canales)
appointment.rescheduledMover la cita; previous trae la fecha y hora anteriores
appointment.cancelledLiberar la cita y cancelar el recordatorio
appointment.status_changedMarcar asistencia (completed) o inasistencia (no_show)
health_billing.payment.registeredAplicar el recaudo en tu cartera
Receptor del webhook
from compense_gateway.webhooks import verify_webhook, WebhookVerificationError

def recibir(raw_body: bytes, headers) -> int:
    try:
        ev = verify_webhook(raw_body, headers, secret=os.environ["COMPENSE_WEBHOOK_SECRET"])
    except WebhookVerificationError:
        return 401
    if ev.is_test or ya_procesado(ev.event_id):
        return 200
    if ev.event.startswith("appointment."):
        sincronizar_cita(ev.event, ev.resource)            # tu lógica
    elif ev.event == "health_billing.payment.registered":
        aplicar_recaudo(ev.resource)                        # tu lógica
    registrar_procesado(ev.event_id)
    return 200

Pruébalo en el sandbox #

Para probarHaz esto
AgendarCupos slt-D1-<AAAAMMDD>-0800, -0820, -1000… de la disponibilidad
Cupo tomadoUn cupo con minuto 40 (por ejemplo slt-D1-20261103-0840) → 409 SLOT_NOT_AVAILABLE
Paciente nuevoDocumento 9999999999 → 404; reenvía con new_patient
AsistenciaSobre una cita futura → 422; sobre una pasada → 200
FacturasUna RADICADA de $120.000 (con una glosa de $12.000) y una CUV_OK de $53.500
Pago que excedeMás que el neto de la factura → 422 PAYMENT_EXCEEDS_BALANCE
Medio inválidomethod que no sea de pagador (por ejemplo PSE) → 422

El sandbox no guarda estado (una cita reprogramada vuelve a su estado inicial al consultarla) y no emite los webhooks reales: valida tu receptor con eventos de prueba (Pruebas).

Lista de verificación #

  • Ante 409 SLOT_NOT_AVAILABLE ofreces otro cupo; ante 404 HEALTH_PATIENT_NOT_FOUND registras con new_patient.
  • Guardas el id, el appointment_code y el patient.id, y actualizas el id tras reprogramar.
  • Los recordatorios salen de tu sistema con appointments.list(when="upcoming") o los webhooks.
  • Registras la asistencia solo desde la hora de la cita.
  • Revisas glosas antes de cobrar y nunca registras más que el saldo no glosado.
  • El pago de la EPS usa reference = número de giro y una llave derivada de método + referencia.