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:
@@ -32,16 +32,16 @@ const serializarAlta = (payload: AltaCliente) => ({
|
||||
})
|
||||
|
||||
/**
|
||||
* Registra el alta en el API Gateway (POST /clientes). El backend persiste en
|
||||
* SQLite y replica en Monday. Si el API no responde devuelve motivo 'conexion'
|
||||
* y el flujo continúa solo local: los PDFs se generan igual. Si el API rechaza
|
||||
* por validación (400) devuelve motivo 'validacion' con el detalle.
|
||||
* POST JSON del alta contra `ruta` (`/clientes` o `/clientes/progreso/:id/completar`).
|
||||
* Si el API no responde devuelve motivo 'conexion' y el flujo continúa solo
|
||||
* local: los PDFs se generan igual. Si el API rechaza por validación (400)
|
||||
* devuelve motivo 'validacion' con el detalle.
|
||||
*/
|
||||
export async function crearAltaCliente(payload: AltaCliente): Promise<ResultadoAlta> {
|
||||
async function enviarAlta(ruta: string, payload: AltaCliente): Promise<ResultadoAlta> {
|
||||
let res: Response
|
||||
try {
|
||||
res = await fetch(
|
||||
`${API_URL}/clientes`,
|
||||
`${API_URL}${ruta}`,
|
||||
conAuth({
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -49,7 +49,7 @@ export async function crearAltaCliente(payload: AltaCliente): Promise<ResultadoA
|
||||
}),
|
||||
)
|
||||
} catch (err) {
|
||||
console.warn('[api] crearAltaCliente: sin conexión al servidor, el expediente se genera solo localmente', err)
|
||||
console.warn('[api] alta: sin conexión al servidor, el expediente se genera solo localmente', err)
|
||||
return { ok: false, motivo: 'conexion' }
|
||||
}
|
||||
manejar401(res)
|
||||
@@ -63,13 +63,65 @@ export async function crearAltaCliente(payload: AltaCliente): Promise<ResultadoA
|
||||
issues?: { path: (string | number)[]; message: string }[]
|
||||
} | null
|
||||
const detalle = cuerpo?.issues?.map((i) => `${i.path.join('.')}: ${i.message}`).join(' · ')
|
||||
console.warn('[api] crearAltaCliente: validación rechazada', detalle)
|
||||
console.warn('[api] alta: validación rechazada', detalle)
|
||||
return { ok: false, motivo: 'validacion', detalle: detalle ?? 'Datos incompletos o inválidos' }
|
||||
}
|
||||
console.warn(`[api] crearAltaCliente: HTTP ${res.status}`)
|
||||
console.warn(`[api] alta: HTTP ${res.status}`)
|
||||
return { ok: false, motivo: 'conexion' }
|
||||
}
|
||||
|
||||
/**
|
||||
* Registra el alta en el API Gateway (POST /clientes). El backend persiste en
|
||||
* SQLite y replica en Monday.
|
||||
*/
|
||||
export async function crearAltaCliente(payload: AltaCliente): Promise<ResultadoAlta> {
|
||||
return enviarAlta('/clientes', payload)
|
||||
}
|
||||
|
||||
// ---------- Progreso del alta etapa por etapa (auth) ----------
|
||||
|
||||
export interface ProgresoIniciado {
|
||||
id: string
|
||||
mondayContactoId: string | null
|
||||
}
|
||||
|
||||
/** POST /clientes/progreso/iniciar — crea el alta en progreso (Contacto PROSPECTO en Monday) */
|
||||
export async function iniciarProgreso(payload: {
|
||||
datosGenerales: unknown
|
||||
leadMondayId?: string
|
||||
}): Promise<ProgresoIniciado> {
|
||||
const res = await fetch(
|
||||
`${API_URL}/clientes/progreso/iniciar`,
|
||||
conAuth({
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
}),
|
||||
)
|
||||
manejar401(res)
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
return (await res.json()) as ProgresoIniciado
|
||||
}
|
||||
|
||||
/** PATCH /clientes/progreso/:id — guarda el avance del wizard y lo replica en el Contacto */
|
||||
export async function actualizarProgreso(id: string, datos: unknown): Promise<void> {
|
||||
const res = await fetch(
|
||||
`${API_URL}/clientes/progreso/${id}`,
|
||||
conAuth({
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(datos),
|
||||
}),
|
||||
)
|
||||
manejar401(res)
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
}
|
||||
|
||||
/** POST /clientes/progreso/:id/completar — cierra el alta (mismo contrato que POST /clientes) */
|
||||
export async function completarProgreso(id: string, payload: AltaCliente): Promise<ResultadoAlta> {
|
||||
return enviarAlta(`/clientes/progreso/${id}/completar`, payload)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sube los documentos reales del expediente (POST multipart
|
||||
* /clientes/:id/documentos, campo 'archivos'). El backend acepta un solo
|
||||
|
||||
Reference in New Issue
Block a user