194 lines
8.4 KiB
TypeScript
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>
|
|
)
|
|
}
|