feat: ecosistema FRASA — apps Cotizador, Alta Clientes, Cobranza y Landing

- Cotizador (/quote): 4 propuestas con formulas validadas del Excel, comparador,
  combobox de unidades, vigencia, PDF ANEXO A
- Alta Clientes (/client): wizard 8 pasos, validacion CURP RENAPO, OCR de INE,
  checklist de expediente, firma digital, 5 PDFs legales
- Cobranza (/pay): planes de enganche, mora, vista cobranza del dia, recibos PDF,
  bitacora, dashboard con filtros
- Landing (/): entrada al ecosistema
- Design system StudioBlank (monocromo, acento #1B4332) en las 4 apps
- 123 tests vitest
This commit is contained in:
2026-08-21 22:27:36 +00:00
commit ecd4edac02
147 changed files with 27345 additions and 0 deletions

View File

@@ -0,0 +1,134 @@
import { useState } from 'react'
import Stepper from '../components/Stepper'
import DatosGeneralesForm from '../forms/DatosGeneralesForm'
import EmpleoForm from '../forms/EmpleoForm'
import EconomicosForm from '../forms/EconomicosForm'
import CoacreditadoForm from '../forms/CoacreditadoForm'
import ReferenciasForm from '../forms/ReferenciasForm'
import ViviendaCreditoForm from '../forms/ViviendaCreditoForm'
import DocumentosForm from '../forms/DocumentosForm'
import FirmasForm from '../forms/FirmasForm'
import { useAltaCliente, PASOS } from '../hooks/useAltaCliente'
import { crearAltaCliente, subirDocumentos } from '../services/api'
import { enviarAMonday } from '../services/monday'
export default function AltaClientePage() {
const { paso, siguiente, anterior, irAPaso, datos, alta, actualizar, agregarDocumento, quitarDocumento } =
useAltaCliente()
const [registro, setRegistro] = useState<string>('')
// Registra el alta (stub API + Monday) al llegar al paso de firmas
const registrarAlta = async () => {
const { id } = await crearAltaCliente(alta)
await subirDocumentos(id, alta)
await enviarAMonday(alta)
setRegistro(id)
}
return (
<div className="min-h-screen bg-white">
<header className="border-b border-neutral-200 px-4 py-6">
<div className="mx-auto flex max-w-6xl items-baseline gap-3">
<span className="text-sm font-bold uppercase tracking-widest">FRASA</span>
<h1 className="text-2xl font-light tracking-wide">Alta de Clientes</h1>
</div>
</header>
<main className="mx-auto max-w-6xl px-4 py-16">
<Stepper pasos={[...PASOS]} pasoActual={paso} onIrAPaso={irAPaso} />
<div className="mt-16">
{paso === 0 && (
<DatosGeneralesForm
valores={datos.datosGenerales}
onSiguiente={(v) => {
actualizar('datosGenerales', v)
siguiente()
}}
/>
)}
{paso === 1 && (
<EmpleoForm
valores={datos.empleo}
onAnterior={anterior}
onSiguiente={(v) => {
actualizar('empleo', v)
siguiente()
}}
/>
)}
{paso === 2 && (
<EconomicosForm
valores={datos.economicos}
onAnterior={anterior}
onSiguiente={(v) => {
actualizar('economicos', v)
siguiente()
}}
/>
)}
{paso === 3 && (
<CoacreditadoForm
valores={datos.coacreditado}
onAnterior={anterior}
onSiguiente={(v) => {
actualizar('coacreditado', v)
siguiente()
}}
/>
)}
{paso === 4 && (
<ReferenciasForm
valores={datos.referencias}
onAnterior={anterior}
onSiguiente={(v) => {
actualizar('referencias', v)
siguiente()
}}
/>
)}
{paso === 5 && (
<ViviendaCreditoForm
valores={datos.viviendaCredito}
onAnterior={anterior}
onSiguiente={(v) => {
actualizar('viviendaCredito', v)
siguiente()
}}
/>
)}
{paso === 6 && (
<DocumentosForm
documentos={alta.documentos}
firmaDataUrl={alta.firma.firmaDataUrl}
onAgregar={agregarDocumento}
onQuitar={quitarDocumento}
onAplicarDatosGenerales={(campos) =>
actualizar('datosGenerales', { ...datos.datosGenerales, ...campos })
}
onAnterior={anterior}
onSiguiente={() => {
void registrarAlta()
siguiente()
}}
/>
)}
{paso === 7 && (
<>
{registro && (
<p className="mb-8 border border-accent bg-white px-4 py-3 text-xs font-bold uppercase tracking-widest text-accent">
Alta registrada (stub API): {registro}
</p>
)}
<FirmasForm
alta={alta}
onCambiarFirma={(f) => actualizar('firma', f)}
onAnterior={anterior}
/>
</>
)}
</div>
</main>
</div>
)
}