import { useEffect, useRef } from 'react' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' import type { Pago } from '../types' import { fechaISOHoy, formatoMoneda } from '../lib/formatters' import { round2 } from '../lib/cobranza' function esquemaAbono(restante: number) { return z.object({ monto: z .number({ invalid_type_error: 'Captura un monto válido' }) .positive('El monto debe ser mayor a 0') .max(restante, `No puede exceder el restante (${formatoMoneda(restante)})`), fechaPago: z.string().min(1, 'La fecha es requerida'), referencia: z.string().trim().min(3, 'Referencia requerida (mín. 3 caracteres)'), }) } type AbonoForm = z.infer> interface Props { pago: Pago onCancelar: () => void onGuardar: (datos: AbonoForm) => void } const claseLabel = 'block text-xs font-bold uppercase tracking-widest text-neutral-500' /** Modal para registrar un abono (total o parcial) a un pago del plan */ export default function ModalAbono({ pago, onCancelar, onGuardar }: Props) { const restante = round2(pago.monto - pago.montoPagado) const { register, handleSubmit, formState: { errors }, } = useForm({ resolver: zodResolver(esquemaAbono(restante)), defaultValues: { monto: restante, fechaPago: fechaISOHoy(), referencia: '' }, }) // Foco al primer input al abrir const refMonto = useRef(null) useEffect(() => { refMonto.current?.focus() }, []) // Escape cierra el modal useEffect(() => { const alTeclar = (e: KeyboardEvent) => { if (e.key === 'Escape') onCancelar() } window.addEventListener('keydown', alTeclar) return () => window.removeEventListener('keydown', alTeclar) }, [onCancelar]) const regMonto = register('monto', { valueAsNumber: true }) return (

Registrar abono — Pago #{pago.numPago}

Restante: {formatoMoneda(restante)}

{ void handleSubmit(onGuardar)(e) }} >
{ regMonto.ref(el) refMonto.current = el }} /> {errors.monto &&

{errors.monto.message}

}
{errors.fechaPago &&

{errors.fechaPago.message}

}
{errors.referencia &&

{errors.referencia.message}

}
) }