// PaymentCapture — cobro mixto con cambio, referencia y cuenta (benchmark §7). import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { Trash2 } from 'lucide-react' import { Button, Input } from '@asteby/metacore-ui' import { useCan } from '../permissions-context' import { useFormatter } from './format' import { newTender, serializePayment, summarizePayment, validatePayment, type PaymentMethodOption, type PaymentPolicy, type PaymentTender, } from './payment' export interface PaymentCaptureProps { /** Total a cobrar. */ total: number methods: PaymentMethodOption[] value: PaymentTender[] onChange: (tenders: PaymentTender[]) => void /** Confirmar cobro: recibe el payload ya serializado. Solo se llama si es válido. */ onSubmit?: (payload: ReturnType) => void policy?: PaymentPolicy /** Errores del servidor `"<índice>."`. */ serverErrors?: Record submitLabel?: string submitting?: boolean /** Capability requerida para cobrar (p. ej. `pos_orders.pagar`). Sin ella, botón oculto. */ submitPermission?: string currency?: string disabled?: boolean } export function PaymentCapture({ total, methods, value, onChange, onSubmit, policy, serverErrors, submitLabel, submitting, submitPermission, currency, disabled, }: PaymentCaptureProps) { const { t } = useTranslation() const can = useCan() const fmt = useFormatter({ currency }) const summary = useMemo(() => summarizePayment(total, value, methods), [total, value, methods]) const validation = useMemo(() => validatePayment(total, value, methods, policy), [total, value, methods, policy]) const errors = { ...validation.errors, ...serverErrors } const byId = new Map(methods.map((m) => [m.id, m])) const mayPay = submitPermission ? can(submitPermission) : true const patch = (idx: number, p: Partial) => onChange(value.map((x, i) => (i === idx ? { ...x, ...p } : x))) return (
{methods.map((m) => ( ))}
{value.map((tender, i) => { const m = byId.get(tender.method) return (

{m?.label ?? tender.method}

{m?.requiresReference && ( patch(i, { reference: e.target.value })} /> )} {m?.requiresAccount && ( )} {(['reference', 'account_id', 'method'] as const).map( (f) => errors[`${i}.${f}`] &&

{errors[`${i}.${f}`]}

, )}
patch(i, { amount: Number(e.target.value.replace(',', '.')) || 0 })} /> {errors[`${i}.amount`] &&

{errors[`${i}.amount`]}

}
) })}
{t('payment.total', { defaultValue: 'Total' })}
{fmt.money(summary.total)}
{t('payment.paid', { defaultValue: 'Pagado' })}
{fmt.money(summary.paid)}
{summary.remaining > 0 &&
{t('payment.remaining', { defaultValue: 'Pendiente' })}
{fmt.money(summary.remaining)}
} {summary.change > 0 &&
{t('payment.change', { defaultValue: 'Cambio' })}
{fmt.money(summary.change)}
}
{validation.form && value.length > 0 &&

{validation.form}

} {onSubmit && mayPay && ( )}
) }