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:
| KPI | Campo | Cuándo mostrarlo |
|---|---|---|
| Total pagado | total_paid | Siempre |
| Adeudado a clínica | clinic_receivable | Siempre (0 € si saldado) |
| A cuenta (saldo) | on_account_balance | Siempre |
| Crédito del paciente | patient_credit | Sólo si > 0, sidebar |
| Total devengado | total_earned | Sidebar, 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.
Banner de estado
| Situación | Disparador | Estilo | Texto |
|---|---|---|---|
| Deuda | clinic_receivable > 0 | warning | "Adeuda {amount} a la clínica" |
| Crédito | patient_credit > 0 | info | "Tiene {amount} a su favor" |
| Saldado | ambos 0 | oculto | — |
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 porpayments.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ón | Permission | Comportamiento si falta |
|---|---|---|
| Ver pill "Pagos" | payments.record.read | Pill oculto (pestaña no aparece) |
| Abrir modal de pago | payments.record.write | CTA oculta |
| Reembolsar en menú | payments.record.refund | Item oculto del overflow |
Aislamiento entre módulos
- Host = módulo
patients. Expone un único slot nuevo:patient.detail.administracion.pagos. Nunca importa código depayments. - Provider = módulo
payments. Auto-registraPatientPaymentsPanel.vueen sufrontend/plugins/slots.client.ts. Ya declaradepends: ["patients", "budget"]— sin cambios. - Si
paymentsse desinstala o el usuario no tienepayments.record.read: el slot resuelve vacío, el toggle probe-earesolveSlot.length > 0y oculta el pill por completo. Fallback de URL: siadminMode=paymentspero no hay providers, vuelve abudgets.
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
docker-compose up, loginadmin@demo.clinic, abrir paciente con histórico de pagos.- Ficha → Administración → click "Pagos". URL →
?tab=administration&adminMode=payments. - KPIs deben coincidir con respuesta de
GET /api/v1/payments/patients/{id}/ledger. - "Registrar pago" → modal pre-rellenado con paciente. Submit → KPIs y timeline refrescan.
- Reembolso desde overflow de un pago → confirm → nueva fila refund visible, KPIs ajustan.
- Quitar
payments.record.readal usuario → pill desaparece; URL forzadaadminMode=paymentscae abudgets. - Viewport 375 px: banner + KPIs apilados, barra inferior sticky visible, overflow menu alcanzable.
- Desinstalar módulo
paymentsdesde 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>. patientsno añade dependencia apayments.
Cero deuda técnica.
Cross-links
- 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.