import { Fragment, useMemo, useState } from 'react' import { Link, useNavigate, useParams } from 'react-router-dom' import type { Pago, PlanPago } from '../types' import { agregarNota, cancelarPlan, obtenerPlan, registrarAbonoEnPlan } from '../services/repository' import { avancePlan, diasAtraso, moraDePago, pagadoTotal, round2, saldoPlan, statusPago, statusPlan, totalMora, } from '../lib/cobranza' import { formatoFechaCorta, formatoMoneda, formatoPorcentaje } from '../lib/formatters' import { descargar } from '../lib/pdf/estadoCuenta' import { descargarRecibo } from '../lib/pdf/reciboAbono' import BadgeStatus from '../components/BadgeStatus' import KpiCard from '../components/KpiCard' import ModalAbono from '../components/ModalAbono' export default function PlanDetallePage() { const { id } = useParams<{ id: string }>() const navigate = useNavigate() const [plan, setPlan] = useState(() => (id ? obtenerPlan(id) : undefined)) const [pagoAbono, setPagoAbono] = useState(null) const [expandidos, setExpandidos] = useState>(new Set()) const [error, setError] = useState(null) const [notaTexto, setNotaTexto] = useState('') const [errorNota, setErrorNota] = useState(null) const hoy = useMemo(() => new Date(), []) if (!plan) { return (

Plan no encontrado.

Volver al dashboard
) } const status = statusPlan(plan) const saldo = saldoPlan(plan) const atraso = diasAtraso(plan, hoy) const tasaMora = plan.tasaMoraMensual ?? 0 const mora = totalMora(plan, hoy) const toggleExpandido = (pagoId: string) => { setExpandidos((prev) => { const next = new Set(prev) if (next.has(pagoId)) next.delete(pagoId) else next.add(pagoId) return next }) } const guardarAbono = (datos: { monto: number; fechaPago: string; referencia: string }) => { if (!pagoAbono) return try { const actualizado = registrarAbonoEnPlan(plan.id, datos, pagoAbono.id) setPlan(actualizado) setPagoAbono(null) setError(null) } catch (e) { setError(e instanceof Error ? e.message : 'Error al registrar el abono') } } const cancelar = () => { if (!window.confirm('¿Cancelar este plan? El saldo pendiente dejará de cobrarse.')) return setPlan(cancelarPlan(plan.id)) } const guardarNota = () => { try { setPlan(agregarNota(plan.id, notaTexto)) setNotaTexto('') setErrorNota(null) } catch (e) { setErrorNota(e instanceof Error ? e.message : 'Error al guardar la nota') } } return (
← Volver al dashboard

{plan.clienteNombre}

{plan.unidadCodigo} · {plan.proyecto} · Venta {plan.ventaRef || '—'} ·{' '} {status} {atraso > 0 && · {atraso} días de atraso}

Tel. {plan.clienteTelefono || '—'} {tasaMora > 0 && · Mora {tasaMora}% mensual}

{status === 'ACTIVO' && saldo > 0 && ( )}
{error && (
{error}
)} {/* KPIs del plan */}
{mora > 0 && ( {formatoMoneda(mora)}} detalle={`Tasa ${tasaMora}% mensual`} /> )}
{/* Tabla de pagos */}
{tasaMora > 0 && } {plan.pagos.map((pago) => { const st = statusPago(pago, hoy) const restante = round2(pago.monto - pago.montoPagado) const moraPago = tasaMora > 0 ? moraDePago(pago, tasaMora, hoy) : 0 const expandido = expandidos.has(pago.id) return ( {tasaMora > 0 && ( )} {expandido && ( )} ) })}
# Vencimiento Monto Pagado RestanteMoraStatus Abonos
{pago.numPago} {formatoFechaCorta(pago.fechaVencimiento)} {formatoMoneda(pago.monto)} {formatoMoneda(pago.montoPagado)} {formatoMoneda(restante)} {moraPago > 0 ? ( {formatoMoneda(moraPago)} ) : ( — )} {pago.aplicaciones.length > 0 ? ( ) : ( — )} {st !== 'PAGADO' && status === 'ACTIVO' && ( )}
0 ? 9 : 8} className="px-4 py-4"> {pago.aplicaciones.map((a) => ( ))}
Fecha Monto Referencia Recibo
{formatoFechaCorta(a.fechaPago)} {formatoMoneda(a.monto)} {a.referencia}
{/* Bitácora del plan */}

Bitácora

{(plan.notas ?? []).length === 0 ? (

Sin notas registradas.

) : (
    {[...(plan.notas ?? [])] .sort((a, b) => b.fecha.localeCompare(a.fecha)) .map((nota) => (
  • {new Date(nota.fecha).toLocaleString('es-MX', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', })}

    {nota.texto}

  • ))}
)}
{ e.preventDefault() guardarNota() }} >