Fix + Rediseño: Modal de confirmación de pago/cancelación (layout tipo recibo)
Date: 2026-07-25 Status: ✅ Integrado en TransaccionDetalleView.vue y verificado end-to-end (2026-07-27) Reemplaza/extiende: doc/plans/2026-07-25-fix-dialogo-confirmacion.md (ese fix ya resolvía el bug del confirm() nativo; este documento lo lleva un paso más allá con un componente propio en vez del dialog.warning() genérico de Naive UI)
Qué cambia
En vez de un diálogo de texto plano ("¿Confirmás el pago de...?"), un modal con layout tipo recibo: el monto en grande arriba de todo (es el dato que más importa confirmar antes de mover dinero real), y el resto de los datos (beneficiario, MTCN, comisión, PDV) como filas de detalle con números alineados. Ícono circular con color semántico (verde para pagar, rojo para cancelar — nunca el naranja de marca, ver el debate de paleta semántica ya cerrado).
Validado en vivo, corriendo de verdad, en claro y oscuro — no es un mockup.
Componente adjunto
reference/ConfirmActionModal.vue — componente Vue completo y ya probado. Es genérico (variant="pay" o variant="cancel"), pensado para reusarse en ambas acciones sin duplicar código.
Props:
show: boolean // v-model:show
variant: 'pay' | 'cancel'
mtcn: string
beneficiario: string
monto: string // ya formateado, ej: "4,712.00"
comision: string // ya formateado
pdv?: string // opcional — no todas las transacciones lo tienen
loading?: boolean // muestra spinner en el botón de confirmar mientras se llama al backendEvento: @confirm — se dispara al click de "Confirmar pago"/"Cancelar transacción". El onConfirm real tiene que:
- Llamar a
PATCH /api/transacciones/{id}/pay(o.../cancel). - Poner
loading = truemientras espera. - Si la respuesta es 200: cerrar el modal (
show = false), mostrar notificación de éxito (useNotification().success(...), mismo patrón que ya existe). - Si la respuesta es 409 (la transacción ya fue resuelta por otro usuario — ver
doc/plans/2026-07-25-fix-pdv-asignacion-transacciones.md): cerrar el modal, mostrar notificación de warning con el mensaje del backend, y refrescar el listado/detalle. - Si hay cualquier otro error:
loading = false, dejar el modal abierto, mostrar el error.
Dónde integrarlo
En TransaccionDetalleView.vue, reemplazando lo que haya ahí ahora (el confirm() nativo original, o el dialog.warning() si ya se había aplicado el fix anterior) por:
<ConfirmActionModal
v-model:show="showConfirmPay"
variant="pay"
:mtcn="transaction.mtcn"
:beneficiario="`${transaction.first_name} ${transaction.last_name}`"
:monto="formatMoney(transaction.amount)"
:comision="formatMoney(transaction.fee)"
:loading="confirming"
@confirm="handleConfirmPay"
/>Mismo patrón para cancelar, con variant="cancel" y su propio showConfirmCancel separado (son 2 modales distintos, no uno solo con variante dinámica en runtime, para evitar líos de estado si el usuario abre uno y cierra el otro).
Requisito previo (ya resuelto en un fix anterior, verificar que siga aplicado)
Este componente usa <NModal> — necesita que App.vue tenga n-config-provider con el tema oscuro conectado al useDarkMode() de la app (si no, el modal no respeta el dark mode). Ya se había pedido esto en doc/plans/2026-07-25-fix-dialogo-confirmacion.md — confirmar que se aplicó antes de integrar este componente.
Checklist de integración
- [x] Copiar
reference/ConfirmActionModal.vueafrontend/src/components/. - [x] Reemplazar la confirmación actual en
TransaccionDetalleView.vuepor este componente (uno para pay, uno para cancel). - [x] Conectar
@confirma las llamadas realesPATCH /pay/PATCH /cancel, con el manejo de 409 descripto arriba. - [x] Confirmar que
App.vueya tienen-config-provider+n-dialog-provider(fix anterior). No estaba — el "fix anterior" (fix-dialogo-confirmacion.md) nunca se llegó a crear ni aplicar. Se agregó ahora:n-config-provider :theme="isDark ? darkTheme : null"+n-dialog-providerenApp.vue. Sin esto, cualquier componente de Naive UI (incluido el<n-pagination>del listado) quedaba con el tema claro de Naive UI por dentro aunque el resto de la app estuviera en oscuro — Tailwind y Naive UI theming son independientes. - [x] Probar en claro y oscuro, y con el caso de 409 (dos pestañas del navegador logueadas con usuarios distintos, resolver la misma transacción desde ambas). Verificado en navegador real (Playwright + Chrome): modal recibo en ambas variantes (pay=verde, cancel=rojo), oscuro sin transparencias, pago exitoso con notificación e historial actualizado, y el caso 409 mostrando el mensaje real del backend ("...probablemente la resolvió otro usuario...") con refresco automático a PAID.
Nota: el prop error/slot de error dentro del modal no existe en el componente de referencia. Para el caso "cualquier otro error" (paso 5 de la spec de @confirm arriba) se resolvió con el mismo mecanismo de notificación que éxito/409 (notification.error(...)), dejando el modal abierto (loading=false) para reintentar — no se modificó el componente ya validado.

