Files
FRASA-CAS/apps/alta-clientes/src/forms/FirmasForm.tsx

194 lines
8.4 KiB
TypeScript

import { useState } from 'react'
import type { AltaCliente, DatosFirma } from '../types'
import { firmaSchema } from '../lib/schemas'
import { formatoMoneda, formatoFechaCorta } from '../lib/formatters'
import { calcularTotalIngresos } from '../lib/calculations'
import { estadoExpediente } from '../lib/expediente'
import SectionCard from '../components/SectionCard'
import SignaturePad from '../components/SignaturePad'
import ChecklistExpediente from '../components/ChecklistExpediente'
import * as perfilCliente from '../lib/pdf/perfilCliente'
import * as identificacionCliente from '../lib/pdf/identificacionCliente'
import * as avisoPrivacidad from '../lib/pdf/avisoPrivacidad'
import * as cartaAntilavado from '../lib/pdf/cartaAntilavado'
import * as checklistExpediente from '../lib/pdf/checklistExpediente'
interface Props {
alta: AltaCliente
onCambiarFirma: (firma: DatosFirma) => void
onAnterior: () => void
/** Registra el alta en el servidor (después de firmar y revisar el expediente) */
onRegistrar: () => void
/** true mientras el alta se está registrando */
registrando?: boolean
}
const GENERADORES: { tipo: string; descargar: (alta: AltaCliente) => void }[] = [
{ tipo: 'Perfil del cliente', descargar: perfilCliente.descargar },
{ tipo: 'Identificación de cliente (PLD)', descargar: identificacionCliente.descargar },
{ tipo: 'Aviso de privacidad', descargar: avisoPrivacidad.descargar },
{ tipo: 'Carta anti-lavado', descargar: cartaAntilavado.descargar },
{ tipo: 'Checklist de expediente', descargar: checklistExpediente.descargar },
]
/** Paso 8: firma del cliente, nombre del asesor, resumen y generación de PDFs */
export default function FirmasForm({ alta, onCambiarFirma, onAnterior, onRegistrar, registrando = false }: Props) {
const [error, setError] = useState('')
const [avisoExpediente, setAvisoExpediente] = useState('')
const { datosGenerales: g, viviendaCredito: v, economicos, firma } = alta
const proyecto = v.proyecto === 'OTRO' ? v.proyectoOtro : v.proyecto
const expediente = estadoExpediente(alta.documentos, firma.firmaDataUrl)
const validar = (): boolean => {
const r = firmaSchema.safeParse(firma)
setError(r.success ? '' : (r.error.issues[0]?.message ?? 'Revisa la firma'))
return r.success
}
// Advierte (sin bloquear) si el expediente está incompleto al generar PDFs
const avisarExpediente = () => {
setAvisoExpediente(
expediente.completoTodo
? ''
: `Expediente incompleto (${expediente.completos}/${expediente.total}): puedes generar los PDFs, pero faltan documentos o la firma.`,
)
}
const generar = (descargar: (a: AltaCliente) => void) => {
avisarExpediente()
if (validar()) descargar(alta)
}
const generarTodos = () => {
avisarExpediente()
if (!validar()) return
for (const g2 of GENERADORES) g2.descargar(alta)
}
return (
<div>
<SectionCard titulo="Resumen del alta">
<dl className="grid grid-cols-2 gap-x-6 gap-y-4 text-sm sm:col-span-2 lg:col-span-3 lg:grid-cols-3">
<div>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">Cliente</dt>
<dd className="mt-1 text-[#111827]">{g.nombreCompleto}</dd>
</div>
<div>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">CURP</dt>
<dd className="mt-1 text-[#111827]">{g.curp}</dd>
</div>
<div>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">RFC</dt>
<dd className="mt-1 text-[#111827]">{g.rfc}</dd>
</div>
<div>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">Proyecto</dt>
<dd className="mt-1 text-[#111827]">{proyecto || '—'}</dd>
</div>
<div>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">Ubicación / Modelo</dt>
<dd className="mt-1 text-[#111827]">
{v.ubicacion || '—'} {v.modelo}
</dd>
</div>
<div>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">Valor venta</dt>
<dd className="mt-1 text-[#111827]">{formatoMoneda(Number(v.valorVenta) || 0)}</dd>
</div>
<div>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">Total ingresos</dt>
<dd className="mt-1 text-[#111827]">{formatoMoneda(calcularTotalIngresos(economicos))}</dd>
</div>
<div>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">Fecha</dt>
<dd className="mt-1 text-[#111827]">{formatoFechaCorta(g.fecha)}</dd>
</div>
<div>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">Documentos</dt>
<dd className="mt-1 text-[#111827]">{alta.documentos.length} de 5</dd>
</div>
</dl>
</SectionCard>
<ChecklistExpediente documentos={alta.documentos} firmaDataUrl={firma.firmaDataUrl} />
<SectionCard titulo="Firma del cliente" descripcion="La firma se incrusta en los PDFs generados.">
<div className="sm:col-span-2">
<SignaturePad onFirma={(dataUrl) => onCambiarFirma({ ...firma, firmaDataUrl: dataUrl })} />
</div>
<label htmlFor="asesorNombre" className="block">
<span className="mb-1 block text-xs font-semibold uppercase tracking-wider text-[#64748B]">
Nombre del asesor <span className="text-red-700">*</span>
</span>
<input
id="asesorNombre"
type="text"
value={firma.asesorNombre}
onChange={(e) => onCambiarFirma({ ...firma, asesorNombre: e.target.value.toUpperCase() })}
className="w-full rounded-lg border border-[#D1D5DB] bg-white px-3 py-2.5 text-sm uppercase transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20"
/>
</label>
</SectionCard>
{error && <p className="px-6 py-2 text-xs text-red-700">{error}</p>}
<SectionCard titulo="Generar documentos PDF">
{avisoExpediente && (
<p
role="alert"
className="rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-xs text-amber-700 sm:col-span-2 lg:col-span-3"
>
{avisoExpediente}
</p>
)}
<div className="flex flex-wrap gap-3 sm:col-span-2 lg:col-span-3">
{GENERADORES.map((g3) => (
<button
key={g3.tipo}
type="button"
onClick={() => generar(g3.descargar)}
className="rounded-lg border border-[#D1D5DB] bg-white px-5 py-2.5 text-sm font-medium text-[#374151] shadow-sm transition-all duration-100 ease-out hover:bg-[#F9FAFB]"
>
{g3.tipo}
</button>
))}
<button
type="button"
onClick={generarTodos}
className="rounded-lg bg-accent px-5 py-2.5 text-sm font-medium text-white shadow-sm transition-all duration-100 ease-out hover:bg-accent-hover disabled:opacity-40"
>
Generar todos
</button>
</div>
</SectionCard>
<div className="sticky bottom-0 flex flex-wrap items-center gap-3 rounded-b-xl border-t border-[#E5E7EB] bg-white px-6 py-4">
<button
type="button"
onClick={onAnterior}
disabled={registrando}
className="rounded-lg border border-[#D1D5DB] bg-white px-5 py-2.5 text-sm font-medium text-[#374151] shadow-sm transition-all duration-100 ease-out hover:bg-[#F9FAFB] disabled:opacity-50"
>
Anterior
</button>
<button
type="button"
onClick={() => {
// La firma es obligatoria para registrar el alta
if (!validar()) return
avisarExpediente()
onRegistrar()
}}
disabled={registrando}
className="rounded-lg bg-accent px-6 py-2.5 text-sm font-medium text-white shadow-sm transition-all duration-100 ease-out hover:bg-accent-hover disabled:opacity-50"
>
{registrando ? 'Registrando…' : 'Registrar alta'}
</button>
<p className="text-xs text-[#64748B]">
El expediente se registra y los PDFs se generan después de firmar.
</p>
</div>
</div>
)
}