feat(alta): guardado progresivo por etapa con réplica a Monday
- Al avanzar cada paso del wizard: upsert silencioso del borrador local + sync a Monday (iniciar → Contacto en PROSPECTO; pasos siguientes → PATCH con los campos mapeables: datos generales, dirección armada, actividad económica) - Al completar: Contacto pasa a CLIENTE + Apartado + funnel (dedup) - progresoId viaja con el borrador (se restaura solo); fallos de sync no bloquean el avance (aviso ámbar no bloqueante); submit final con fallback al endpoint legado si nunca hubo sync - API: POST /clientes/progreso/iniciar, PATCH /clientes/progreso/:id, POST /clientes/progreso/:id/completar; POST /clientes legado intacto - Tests: API 338 (+16), alta-clientes 112 (+4); smoke en vivo verificado (PROSPECTO → patch → CLIENTE + Apartado) y limpiado
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import Stepper from '../components/Stepper'
|
||||
import DatosGeneralesForm from '../forms/DatosGeneralesForm'
|
||||
import EmpleoForm from '../forms/EmpleoForm'
|
||||
@@ -10,10 +10,13 @@ import DocumentosForm from '../forms/DocumentosForm'
|
||||
import FirmasForm from '../forms/FirmasForm'
|
||||
import { useAltaCliente, PASOS } from '../hooks/useAltaCliente'
|
||||
import {
|
||||
actualizarProgreso,
|
||||
completarProgreso,
|
||||
crearAltaCliente,
|
||||
eliminarBorrador,
|
||||
generarLigaRemota,
|
||||
guardarBorrador,
|
||||
iniciarProgreso,
|
||||
listarBorradores,
|
||||
obtenerBorrador,
|
||||
obtenerCotizacion,
|
||||
@@ -61,6 +64,7 @@ export default function AltaClientePage() {
|
||||
const [borradores, setBorradores] = useState<BorradorResumen[]>([])
|
||||
const [borradorId, setBorradorId] = useState<string | null>(null)
|
||||
const [avisoBorrador, setAvisoBorrador] = useState('')
|
||||
const [avisoProgreso, setAvisoProgreso] = useState('')
|
||||
const [cargoDeBorrador, setCargoDeBorrador] = useState(false)
|
||||
const [errorCarga, setErrorCarga] = useState('')
|
||||
|
||||
@@ -147,10 +151,54 @@ export default function AltaClientePage() {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Guardado progresivo (borrador local + Contacto en Monday) ----------
|
||||
// Guard in-flight: con clicks rápidos se omite el sync en curso; el siguiente
|
||||
// avance manda el estado completo (el PATCH es idempotente).
|
||||
const syncEnCurso = useRef(false)
|
||||
|
||||
const syncProgreso = async (datosSync: typeof datos) => {
|
||||
if (syncEnCurso.current) return
|
||||
if (datosSync.datosGenerales.nombreCompleto.trim() === '') return
|
||||
syncEnCurso.current = true
|
||||
try {
|
||||
if (datosSync.progresoId) {
|
||||
await actualizarProgreso(datosSync.progresoId, datosSync)
|
||||
} else {
|
||||
const r = await iniciarProgreso({
|
||||
datosGenerales: datosSync.datosGenerales,
|
||||
...(datosSync.leadMondayId ? { leadMondayId: datosSync.leadMondayId } : {}),
|
||||
})
|
||||
actualizar('progresoId', r.id)
|
||||
}
|
||||
setAvisoProgreso('')
|
||||
} catch (err) {
|
||||
console.warn('[alta] no se pudo sincronizar el avance con el servidor', err)
|
||||
setAvisoProgreso('El avance quedó guardado local; no se pudo subir a Monday')
|
||||
} finally {
|
||||
syncEnCurso.current = false
|
||||
}
|
||||
}
|
||||
|
||||
/** Al avanzar de paso: guarda el borrador en silencio y sube el avance a Monday */
|
||||
const guardarAvance = (datosNuevos: typeof datos, pasoDestino: number) => {
|
||||
guardarBorrador({ ...(borradorId ? { id: borradorId } : {}), paso: pasoDestino, datos: datosNuevos })
|
||||
.then((b) => {
|
||||
setBorradorId(b.id)
|
||||
setAvisoBorrador(
|
||||
`Borrador guardado ${new Date().toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit' })}`,
|
||||
)
|
||||
})
|
||||
.catch(() => setAvisoBorrador('No se pudo guardar el borrador'))
|
||||
void syncProgreso(datosNuevos)
|
||||
}
|
||||
|
||||
// Registra el alta en el API al llegar al paso de firmas; si el API no
|
||||
// responde, el flujo continúa solo local (los PDFs se generan igual)
|
||||
// responde, el flujo continúa solo local (los PDFs se generan igual). Con
|
||||
// guardado progresivo activo se cierra por el endpoint de progreso.
|
||||
const registrarAlta = async () => {
|
||||
const res = await crearAltaCliente(alta)
|
||||
const res = datos.progresoId
|
||||
? await completarProgreso(datos.progresoId, alta)
|
||||
: await crearAltaCliente(alta)
|
||||
if (res.ok) {
|
||||
await subirDocumentos(res.id, alta)
|
||||
setRegistro(res.id)
|
||||
@@ -337,6 +385,11 @@ export default function AltaClientePage() {
|
||||
{avisoBorrador}
|
||||
</span>
|
||||
)}
|
||||
{avisoProgreso && (
|
||||
<span role="status" className="text-xs font-light text-amber-700">
|
||||
{avisoProgreso}
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void guardar()}
|
||||
@@ -364,6 +417,7 @@ export default function AltaClientePage() {
|
||||
onSiguiente={(v) => {
|
||||
actualizar('datosGenerales', v)
|
||||
siguiente()
|
||||
guardarAvance({ ...datos, datosGenerales: v }, paso + 1)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -374,6 +428,7 @@ export default function AltaClientePage() {
|
||||
onSiguiente={(v) => {
|
||||
actualizar('empleo', v)
|
||||
siguiente()
|
||||
guardarAvance({ ...datos, empleo: v }, paso + 1)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -384,6 +439,7 @@ export default function AltaClientePage() {
|
||||
onSiguiente={(v) => {
|
||||
actualizar('economicos', v)
|
||||
siguiente()
|
||||
guardarAvance({ ...datos, economicos: v }, paso + 1)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -394,6 +450,7 @@ export default function AltaClientePage() {
|
||||
onSiguiente={(v) => {
|
||||
actualizar('coacreditado', v)
|
||||
siguiente()
|
||||
guardarAvance({ ...datos, coacreditado: v }, paso + 1)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -404,6 +461,7 @@ export default function AltaClientePage() {
|
||||
onSiguiente={(v) => {
|
||||
actualizar('referencias', v)
|
||||
siguiente()
|
||||
guardarAvance({ ...datos, referencias: v }, paso + 1)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -414,6 +472,7 @@ export default function AltaClientePage() {
|
||||
onSiguiente={(v) => {
|
||||
actualizar('viviendaCredito', v)
|
||||
siguiente()
|
||||
guardarAvance({ ...datos, viviendaCredito: v }, paso + 1)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -440,6 +499,7 @@ export default function AltaClientePage() {
|
||||
onSiguiente={() => {
|
||||
void registrarAlta()
|
||||
siguiente()
|
||||
guardarAvance(datos, paso + 1)
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user