Skip to content

Subtab "Pagos" en la ficha del paciente (Administración)

Status: draft — design approved 2026-05-13, pending technical plan.

Problema actual

La información económica del paciente está fragmentada en la ficha:

  • Presupuestos → muestra cobrado/pendiente por presupuesto, no agregado.
  • Facturación → muestra facturas emitidas, no flujo de dinero real.

No existe una vista centrada en el paciente que responda a: ¿cuánto ha pagado este paciente en total? ¿qué me debe? ¿tiene saldo a favor? ¿cuál es su histórico de movimientos?

El módulo payments ya expone PatientLedger vía GET /api/v1/payments/patients/{id}/ledger con todos los datos necesarios, pero ninguna UI los consume desde la ficha.

Propuesta

Nuevo sub-modo "Pagos" dentro del tab Administración existente. Una sola pantalla con balance, banner de estado y timeline completo. CTA "Registrar pago" siempre a un clic.

Toggle resultante: [ presupuestos ] [ facturación ] [ pagos ] [ documentos ].

Por qué aquí (y no en facturación)

Pagos ≠ facturas. En la realidad operativa de la clínica:

  • Un pago puede no estar facturado todavía.
  • Una factura puede no estar cobrada.
  • El paciente puede tener saldo a cuenta sin presupuesto asignado.

Mostrar pagos dentro de facturación mezcla dos ejes contables y rompe la realidad off-books que muchas clínicas llevan. Reglas de memoria del proyecto: nunca exponer diferencias entre eje "pagado" y eje "facturado".

KPIs que se muestran

Datos del endpoint PatientLedger:

KPICampoCuándo mostrarlo
Total pagadototal_paidSiempre
Adeudado a clínicaclinic_receivableSiempre (0 € si saldado)
A cuenta (saldo)on_account_balanceSiempre
Crédito del pacientepatient_creditSólo si > 0, sidebar
Total devengadototal_earnedSidebar, informativo

Lo que NO se muestra (por la regla off-books): diferencias entre total_paid y monto facturado, conciliación factura↔pago, ratio cobro/facturación.

SituaciónDisparadorEstiloTexto
Deudaclinic_receivable > 0warning"Adeuda {amount} a la clínica"
Créditopatient_credit > 0info"Tiene {amount} a su favor"
Saldadoambos 0oculto

Timeline

Cronológico inverso. Tres tipos de entrada vienen del endpoint:

  • payment → icono positivo verde, monto + método de pago, menú overflow (...) con Ver detalle + Reembolsar (gated por payments.record.refund).
  • refund → icono negativo rojo, monto + reason code, sólo Ver detalle.
  • earned → icono neutro "Tratamiento realizado" + concepto. Sin acciones (los tratamientos viven en el tab clínico).

CTA "Registrar pago"

Abre PaymentCreateModal existente con patient_id pre-cargado y allocation por defecto = on_account. Dentro del modal el usuario puede opcionalmente elegir un presupuesto. Refleja la realidad: cuando el paciente paga, la clínica primero recibe el dinero — la asignación es un segundo paso opcional.

Responsive

Desktop (≥1024 px)

┌──────────────────────────────────────────────────────┐
│ [Banner: deuda / crédito]                            │
├──────────────────────────────┬───────────────────────┤
│ KPIs row (3 totales)         │ Sidebar               │
│ • Total pagado               │ • A cuenta            │
│ • Adeudado clínica           │ • Crédito             │
│ • A cuenta                   │ • Último pago         │
│                              │ [+ Registrar pago]    │
├──────────────────────────────┴───────────────────────┤
│ Timeline (pagos · reembolsos · devengados)            │
└──────────────────────────────────────────────────────┘

Tablet (768–1023 px) KPIs grid 3-col compacto, sidebar baja debajo, resto igual.

Móvil (<768 px)

  • Banner full-width arriba.
  • KPIs en stack vertical (1 col).
  • Sidebar se disuelve; CTA "Registrar pago" pasa a barra inferior sticky con tap target ≥48 px.
  • Timeline en 2 líneas (tipo+monto / fecha+método muted).
  • Overflow menu vía UDropdownMenu, accesible con tap.

Permisos

AcciónPermissionComportamiento si falta
Ver pill "Pagos"payments.record.readPill oculto (pestaña no aparece)
Abrir modal de pagopayments.record.writeCTA oculta
Reembolsar en menúpayments.record.refundItem oculto del overflow

Aislamiento entre módulos

  • Host = módulo patients. Expone un único slot nuevo: patient.detail.administracion.pagos. Nunca importa código de payments.
  • Provider = módulo payments. Auto-registra PatientPaymentsPanel.vue en su frontend/plugins/slots.client.ts. Ya declara depends: ["patients", "budget"] — sin cambios.
  • Si payments se desinstala o el usuario no tiene payments.record.read: el slot resuelve vacío, el toggle probe-ea resolveSlot.length > 0 y oculta el pill por completo. Fallback de URL: si adminMode=payments pero no hay providers, vuelve a budgets.

patients no añade a manifest.depends. La inversión de dependencia (provider self-registers) es lo que hace que esto sea limpio.

Cómo se valida

  1. docker-compose up, login admin@demo.clinic, abrir paciente con histórico de pagos.
  2. Ficha → Administración → click "Pagos". URL → ?tab=administration&adminMode=payments.
  3. KPIs deben coincidir con respuesta de GET /api/v1/payments/patients/{id}/ledger.
  4. "Registrar pago" → modal pre-rellenado con paciente. Submit → KPIs y timeline refrescan.
  5. Reembolso desde overflow de un pago → confirm → nueva fila refund visible, KPIs ajustan.
  6. Quitar payments.record.read al usuario → pill desaparece; URL forzada adminMode=payments cae a budgets.
  7. Viewport 375 px: banner + KPIs apilados, barra inferior sticky visible, overflow menu alcanzable.
  8. Desinstalar módulo payments desde admin (si está soportado) → pill desaparece, sin errores en consola.

Lo que esta feature NO añade

  • No hay endpoints nuevos.
  • No hay permisos nuevos.
  • No hay migraciones.
  • No hay infra de slots nueva — todo reutiliza useModuleSlots + <ModuleSlot>.
  • patients no añade dependencia a payments.

Cero deuda técnica.

  • Plan técnico: docs/technical/payments/patient-ledger-subtab.md.
  • Módulo origen: backend/app/modules/payments/.
  • Sistema de slots: frontend/app/composables/useModuleSlots.ts, frontend/app/components/ModuleSlot.vue.
  • Componentes detail-page compartidos: docs/technical/detail-page-shared-components.md.