feat: API Gateway (Express 5 + Prisma + Monday) y conexion de apps al API
- api-gateway: servicio Monday (retry 429/complejidad, paginacion cursor, column IDs por titulo), endpoints unidades/cotizaciones/clientes/planes, webhook con handshake challenge, Prisma SQLite dev, 55 tests - cotizador: inventario desde /api/unidades con fallback a seed - alta-clientes: POST /api/clientes + upload multipart de documentos - cobranza: persistencia via API con respaldo offline en localStorage - .env.example con board IDs del workspace Frasa-CAS
This commit is contained in:
@@ -1,11 +1,44 @@
|
||||
import type { PlanPago } from '../types'
|
||||
import { cargarPlanes } from './repository'
|
||||
// Cliente HTTP del API Gateway de FRASA (Express + Prisma)
|
||||
const API_URL = import.meta.env.VITE_API_URL ?? 'http://192.168.10.123:3000/api'
|
||||
// TODO produccion: '/api' same-origin detras de NGINX
|
||||
|
||||
/**
|
||||
* Devuelve los planes de pago registrados.
|
||||
* TODO: reemplazar por fetch('/api/planes') cuando el API Gateway esté disponible
|
||||
* TODO: cron de vencidos y envío WhatsApp van en el API Gateway (fase posterior)
|
||||
* Error HTTP del API (respuesta con status >= 400, p. ej. validación o 404).
|
||||
* NO amerita fallback offline: el servidor respondió y su respuesta manda.
|
||||
*/
|
||||
export async function getPlanes(): Promise<PlanPago[]> {
|
||||
return cargarPlanes()
|
||||
export class ErrorApi extends Error {
|
||||
constructor(
|
||||
public readonly status: number,
|
||||
message: string,
|
||||
) {
|
||||
super(message)
|
||||
this.name = 'ErrorApi'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch solo rechaza (TypeError) cuando falla la red; una respuesta HTTP de
|
||||
* error resuelve normalmente. El fallback offline solo aplica al primer caso.
|
||||
*/
|
||||
export function esErrorDeRed(err: unknown): boolean {
|
||||
return err instanceof TypeError
|
||||
}
|
||||
|
||||
/** Llama al API Gateway; lanza ErrorApi con el mensaje del servidor si el status no es 2xx */
|
||||
export async function request<T>(ruta: string, init?: RequestInit): Promise<T> {
|
||||
const res = await fetch(`${API_URL}${ruta}`, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
...init,
|
||||
})
|
||||
if (!res.ok) {
|
||||
let mensaje = `Error ${res.status} al llamar ${ruta}`
|
||||
try {
|
||||
const cuerpo = (await res.json()) as { error?: string }
|
||||
if (typeof cuerpo.error === 'string') mensaje = cuerpo.error
|
||||
} catch {
|
||||
// cuerpo no JSON: se queda el mensaje genérico
|
||||
}
|
||||
throw new ErrorApi(res.status, mensaje)
|
||||
}
|
||||
return (await res.json()) as T
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user