Probado contra el SAP real (solo lectura): órdenes de venta y facturas de proveedor. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
api
Backend NestJS (ESM) del ERP base. Proxy hacia SAP Business One Service Layer.
cp .env.example .env
npm run start:dev # http://localhost:3000/api/health
npm test # unitarios
npm run test:e2e # e2e
npm run lint
Configuración: src/config/env.schema.ts valida el .env al arrancar (zod); si algo es inválido, el proceso no inicia.
Nota: instalar dependencias con npm 11 (npx npm@11 install); npm 10 falla al resolver peers con TypeScript 6.
Autenticación
Login web contra EmployeesInfo (U_WEB_USER / U_WEB_PASS); el backend usa la sesión SAP compartida.
Guard global: todo /api/* exige Authorization: Bearer <token> salvo rutas @Public().
| Endpoint | Descripción |
|---|---|
POST /api/auth/login {username, password} |
Devuelve {token, user, company} |
GET /api/auth/me |
Usuario de la sesión |
POST /api/auth/logout |
Cierra la sesión (204) |
- Una sesión por usuario (un nuevo login invalida el token anterior); timeout deslizante
SESSION_TIMEOUT_MINUTES(30). - Tras 5 fallos por usuario (o 20 por IP) en 15 min responde 429.
- Sesiones en memoria: ejecutar un solo proceso.
curl -s -H 'Content-Type: application/json' -d '{"username":"USUARIO","password":"CLAVE"}' localhost:3000/api/auth/login
Permisos de menú
Se guardan en SQLite (SQLITE_PATH, por defecto ./data/app.sqlite, fuera del repo). Un usuario web (U_WEB_USER) tiene una lista de patrones de ruta:
| Patrón | Permite |
|---|---|
* |
Todo (administrador) |
/ventas/* |
/ventas y todo lo que cuelga de /ventas/ (no /ventas-x) |
/ventas/ordenes |
Solo esa ruta |
| Endpoint | Quién | Descripción |
|---|---|---|
GET /api/user/menu |
Cualquier sesión | {allowedPaths} del usuario actual |
GET /api/permissions, GET /api/permissions/:webUser |
Administrador | Consulta |
PUT /api/permissions/:webUser {paths} |
Administrador | Reemplaza los permisos |
DELETE /api/permissions/:webUser |
Administrador | Elimina los permisos |
ADMIN_WEB_USERS=george,anagarantiza*a esos usuarios en cada arranque (nunca se queda el sistema sin acceso).- El sistema no deja quitar el acceso total al último administrador (409).
- El esquema se sincroniza automáticamente (
synchronize); pasar a migraciones cuando se estabilice por cliente. - Si
better-sqlite3falla al instalar en una máquina nueva:npm rebuild better-sqlite3.
Proxy del Service Layer (/api/sl)
El frontend arma los parámetros OData y el cuerpo; el backend solo valida y ejecuta (sesión SAP, cola, reintento y errores incluidos). El frontend envía el nombre del recurso, nunca la URL.
| Llamada | Descripción |
|---|---|
GET /api/sl/:recurso?$select&$filter&$orderby&$top&$skip |
Lista. Devuelve value y odata.count. $top por defecto 50, máx. 500. |
GET /api/sl/:recurso/:clave?$select |
Un registro. La clave se valida y escapa según su tipo. |
POST /api/sl/:recurso |
Crea; devuelve lo que responde SAP (ej. DocEntry, DocNum). |
PATCH /api/sl/:recurso/:clave |
Modifica (204). Acepta If-Match con el odata.etag leído (concurrencia optimista: SAP responde 412 si el documento cambió). |
Reglas:
- Solo recursos registrados en
src/sl/sl-resources.ts; el resto es 404. Nunca registrarEmployeesInfo(contieneU_WEB_PASS). - Cada recurso declara clave, métodos y la ruta de menú que gobierna lectura/escritura. Los catálogos de consulta (
Items,Warehouses...) se leen con cualquier sesión. - Parámetros OData fuera de la lista (
$apply...) → 400.$expandsolo si el recurso declara qué colecciones admite. - Cabeceras B1S-* permitidas:
B1S-ReplaceCollectionsOnPatch(PATCH),B1S-CaseInsensitiveyB1S-PageSize(GET). Se fijan solas:B1S-PageSize=$topyB1S-CaseInsensitivesi hay$filter. - No hay DELETE ni acciones (
Cancel,Close); se agregan por recurso cuando haga falta. - Las escrituras dejan una línea
AUDIT <usuario> <método> <recurso>en el log.
Agregar un recurso nuevo = una línea en SL_RESOURCES. El test en vivo (SAP_LIVE=1 npm run test:e2e) comprueba que todos existan en el Service Layer del cliente.