fix(alta): error 400 del servidor ya no se reporta como 'sin conexión'

- API: viviendaCredito.ubicacion ahora opcional (la app web no la exigía;
  el desfase causaba 400 que la app mostraba como fallo de red)
- App: crearAltaCliente distingue conexion/validacion; el 400 muestra
  banner rojo con el detalle de los issues en vez del banner ámbar
- Tests: api 160, alta-clientes 66
This commit is contained in:
2026-08-27 18:51:51 +00:00
parent 5d4293888b
commit 04de7d703f
5 changed files with 95 additions and 22 deletions

View File

@@ -8,6 +8,11 @@ export interface AltaClienteCreada {
id: string
}
/** Resultado del registro en el API: distingue fallo de red de rechazo por validación. */
export type ResultadoAlta =
| { ok: true; id: string }
| { ok: false; motivo: 'conexion' | 'validacion'; detalle?: string }
interface DocumentoMeta {
tipo: string
nombre: string
@@ -28,12 +33,14 @@ 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 null (con
* console.warn) y el flujo continúa solo local: los PDFs se generan igual.
* 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.
*/
export async function crearAltaCliente(payload: AltaCliente): Promise<AltaClienteCreada | null> {
export async function crearAltaCliente(payload: AltaCliente): Promise<ResultadoAlta> {
let res: Response
try {
const res = await fetch(
res = await fetch(
`${API_URL}/clientes`,
conAuth({
method: 'POST',
@@ -41,14 +48,26 @@ export async function crearAltaCliente(payload: AltaCliente): Promise<AltaClient
body: JSON.stringify(serializarAlta(payload)),
}),
)
manejar401(res)
if (!res.ok) throw new Error(`HTTP ${res.status}`)
const data = (await res.json()) as { id: string }
return { id: data.id }
} catch (err) {
console.warn('[api] crearAltaCliente: sin conexión al servidor, el expediente se genera solo localmente', err)
return null
return { ok: false, motivo: 'conexion' }
}
manejar401(res)
if (res.ok) {
const data = (await res.json()) as { id: string }
return { ok: true, id: data.id }
}
if (res.status === 400) {
// El API rechazó por validación: NO es un problema de conexión.
const cuerpo = (await res.json().catch(() => null)) as {
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)
return { ok: false, motivo: 'validacion', detalle: detalle ?? 'Datos incompletos o inválidos' }
}
console.warn(`[api] crearAltaCliente: HTTP ${res.status}`)
return { ok: false, motivo: 'conexion' }
}
/**