export type FrontRpcCode = "VALIDATION" | "NETWORK" | "INTERNAL"; export type FrontResponse = { ok: boolean; code: FrontRpcCode | string; layer: "front"; message: string; context?: Record; data?: T | null; errors?: Record | string[] | null; }; export function buildFrontError( message: string, code: FrontRpcCode = "VALIDATION", context?: Record, errors?: Record | string[] | null, ): FrontResponse { return { ok: false, code, layer: "front", message, context, data: null, errors: errors ?? null, }; } export function buildFrontNetworkError(method: string, path: string): FrontResponse { return buildFrontError( `No se pudo conectar con el servidor en ${method} ${path} — verifique su conexión`, "NETWORK", { route: `${method} ${path}` }, ); } export function isApiEnvelope(value: unknown): value is { ok: boolean; code: string; status?: number; layer: string; message: string; data?: unknown; } { return !!value && typeof value === "object" && "ok" in value && "message" in value && "layer" in value; } /** Normaliza respuestas RPC `{ ok, data, message }` al shape plano que esperan las pantallas. */ export function unwrapApiPayload(body: unknown): T { if (!isApiEnvelope(body)) return body as T; const env = body; const data = env.data; const spread = data && typeof data === "object" && !Array.isArray(data) ? data as Record : {}; return { ...spread, data, ok: env.ok, code: env.code, message: env.message, status: env.status, layer: env.layer, } as T; } function firstListedError(value: object): string | null { if (!("errors" in value) || value.errors == null) return null; const errors = (value as { errors: unknown }).errors; if (typeof errors === "string" && errors.trim()) return errors; if (!Array.isArray(errors)) return null; for (const item of errors) { if (typeof item === "string" && item.trim()) return item; if (item && typeof item === "object" && "messages" in item) { const msgs = (item as { messages: unknown }).messages; if (Array.isArray(msgs)) { const first = msgs.find((msg) => typeof msg === "string" && msg.trim()); if (typeof first === "string") return first; } } } return null; } /** Muestra el mensaje detallado de la API o front sin reescribirlo. */ export function displayErrorMessage(value: unknown, fallback = "Ocurrió un error"): string { if (isApiEnvelope(value)) { const listed = firstListedError(value); const message = value.message?.trim(); if (message) return message; if (listed) return listed; } if (value && typeof value === "object") { if ("message" in value) { const message = String((value as { message: unknown }).message || "").trim(); if (message) return message; } if ("error" in value) { const error = String((value as { error: unknown }).error || "").trim(); if (error) return error; } const listed = firstListedError(value); if (listed) return listed; } return fallback || "Ocurrió un error"; }