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:
2026-08-21 23:39:17 +00:00
parent ecd4edac02
commit 0546f35d2a
48 changed files with 7129 additions and 66 deletions

View File

@@ -0,0 +1,36 @@
import type { Request, Response } from 'express'
import { z } from 'zod'
const eventoSchema = z
.object({
type: z.string().optional(),
boardId: z.union([z.string(), z.number()]).optional(),
itemId: z.union([z.string(), z.number()]).optional(),
})
.passthrough()
/**
* POST /api/webhooks/monday
* Handshake: Monday envía { challenge } al registrar el webhook y exige que
* se responda tal cual. Para eventos reales solo se loggea y se responde 200.
*
* OJO: Monday NO firma sus webhooks → el payload no es confiable. Antes de
* actuar sobre un evento hay que re-consultar el estado vía API (por ahora
* solo se registra el evento).
*/
export function webhookMonday(req: Request, res: Response): void {
const body = req.body as Record<string, unknown>
if (typeof body?.['challenge'] === 'string') {
res.json({ challenge: body['challenge'] })
return
}
const parsed = eventoSchema.safeParse(body)
if (parsed.success) {
const { type, boardId, itemId } = parsed.data
// eslint-disable-next-line no-console
console.log(`[webhook monday] tipo=${type ?? 'desconocido'} board=${boardId ?? '?'} item=${itemId ?? '?'}`)
}
res.sendStatus(200)
}