121 lines
9.3 KiB
Markdown
121 lines
9.3 KiB
Markdown
# api
|
|
|
|
Backend NestJS (ESM) del ERP base. Proxy hacia SAP Business One Service Layer.
|
|
|
|
```bash
|
|
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.
|
|
|
|
```bash
|
|
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,ana` garantiza `*` 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-sqlite3` falla 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ó). |
|
|
| `POST /api/sl/:recurso/:clave/:accion` | Acción de un registro (hoy `Cancel` de pagos y asientos). Solo las que el recurso declara en `actions`; requiere el permiso de escritura. |
|
|
| `POST /api/sl/:accion` | Acción de solo consulta sin registro (`SeriesService_GetDocumentSeries`, series de numeración): cualquier sesión. |
|
|
|
|
Reglas:
|
|
- **Solo recursos registrados** en `src/sl/sl-resources.ts`; el resto es 404. Nunca registrar `EmployeesInfo` (contiene `U_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. `$expand` solo si el recurso declara qué colecciones admite.
|
|
- Cabeceras B1S-* permitidas: `B1S-ReplaceCollectionsOnPatch` (PATCH), `B1S-CaseInsensitive` y `B1S-PageSize` (GET). Se fijan solas: `B1S-PageSize` = `$top` y `B1S-CaseInsensitive` si hay `$filter`.
|
|
- No hay DELETE. Las acciones (`Cancel`) existen solo donde el recurso las declara en `actions` (pagos recibidos y efectuados, asientos); `Close` y las demás se agregan por recurso cuando haga falta.
|
|
- Las escrituras y las acciones se registran en la auditoría (ver abajo) y dejan una línea `AUDIT` 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.
|
|
|
|
## Auditoría
|
|
|
|
Tabla `audit_log` en la misma SQLite. Registra inicios de sesión (correctos, fallidos con su motivo y cierres), cada creación, modificación y acción (`sl_action`) que pasa por el proxy (recurso, clave, resultado y **solo los nombres de los campos**, nunca valores ni contraseñas), las operaciones denegadas por permisos y los cambios de permisos de menú. Las lecturas no se auditan. `AuditService.record` nunca lanza: un fallo del registro no cambia la operación del usuario.
|
|
|
|
| Endpoint | Quién | Descripción |
|
|
|---|---|---|
|
|
| `GET /api/audit?usuario&accion&recurso&resultado&desde&hasta&page` | Administrador | Consulta; 50 por página (máx. 200) |
|
|
|
|
Pendiente de afinar: retención programada, IP real detrás del proxy de desarrollo, exportar a CSV.
|
|
|
|
## Reportes del API Gateway (Crystal Reports)
|
|
|
|
Servicio aparte del Service Layer (`APIGATEWAY_*` en el `.env`): su propio login (`/login` con base, usuario, clave e instancia), su propia sesión (se renueva sola ante un 401) y su propia cola (`APIGATEWAY_MAX_CONCURRENCY` reportes a la vez; generar uno pesa). Sin `APIGATEWAY_URL` el módulo responde 503 y el resto de la API no se ve afectado.
|
|
|
|
Llama a `rs/v1/LoadAuthorizedCRList` (los reportes autorizados), `rs/v1/LoadCR` (sus parámetros) y `rs/v1/ExportPDFData` (el PDF, en base64; responde 201 y, si falla, un texto de error con 200 o `(---)` si el reporte no produjo nada).
|
|
|
|
| Endpoint | Quién | Descripción |
|
|
|---|---|---|
|
|
| `GET /api/gateway/authorized` | Administrador | Reportes que el gateway autoriza |
|
|
| `GET /api/gateway/authorized/:code/parameters` | Administrador | Parámetros de un reporte, como definiciones de matrícula |
|
|
| `GET /api/gateway/registry` | Administrador | Reportes matriculados |
|
|
| `PUT /api/gateway/registry/:code` | Administrador | Matricula o actualiza un reporte (título, categoría, habilitado y cómo se pide cada parámetro) |
|
|
| `DELETE /api/gateway/registry/:code` | Administrador | Quita un reporte de la matrícula |
|
|
| `GET /api/gateway/reports` | Permiso de menú `/reportes/ejecutar` | Reportes habilitados |
|
|
| `POST /api/gateway/reports/:code/export` `{values}` | Permiso de menú `/reportes/ejecutar` | Genera el PDF |
|
|
| `GET /api/gateway/documents?screen=/ventas/entrega` | Permiso de menú de esa pantalla | Formatos PDF asociados a un documento (el por defecto primero) |
|
|
| `POST /api/gateway/documents/export` `{screen, code, docEntry}` | Permiso de menú de esa pantalla | PDF del documento: el `DocEntry` va en el parámetro asociado en la matrícula |
|
|
|
|
La matrícula (tabla `gateway_reports` de la SQLite) es lo que arma el cuerpo de `ExportPDFData`: el usuario solo manda valores y el servidor los ordena según los parámetros matriculados (nombre y tipo de Crystal), exige los obligatorios y descarta lo que no esté matriculado. La auditoría registra `report_register`, `report_unregister` y `report_export` (solo los nombres de los parámetros, nunca los valores).
|
|
|
|
## Consultas a la base de datos de SAP (solo lectura)
|
|
|
|
Algunas pantallas necesitan datos que el Service Layer no entrega bien (por ejemplo, el stock de un lote por almacén). El módulo `src/sapdb/` ejecuta **consultas registradas, de solo lectura**, directamente en la base de SAP. Hoy el motor es **HANA** (`hdb`, JavaScript puro, sin módulos nativos); la interfaz (`SapDbAdapter`) deja listo sumar MSSQL con su adaptador.
|
|
|
|
- **Configuración (`SAPDB_*` en el `.env`):** `SAPDB_ENGINE=hana`, servidor, puerto (30015), usuario, clave y `SAPDB_SCHEMA` (la base de la empresa). Sin motor el módulo responde 503 y el resto de la API no se ve afectado. Usar un usuario de la base que **solo pueda leer**.
|
|
- **El frontend nunca manda SQL.** Pide una consulta por su nombre con sus parámetros: `POST /api/sapdb/queries/:name` `{params}` → `{rows, truncated}`; `GET /api/sapdb/queries` lista las que el usuario puede ejecutar.
|
|
- **Registro de consultas** (`sapdb-queries.ts` + `sapdb.extensions.ts`, vacío en `main`; la rama de cada cliente agrega las suyas): cada una declara su SQL por motor, sus parámetros con nombre (`:lote`, tipos `string | number | date`), el permiso de menú que basta para ejecutarla (`menu`, o `null` = cualquier sesión) y un tope de filas. Al arrancar se rechaza una consulta que no sea un único `SELECT`/`WITH`, que use verbos que escriben, que repita nombre o cuyos parámetros no coincidan con el SQL.
|
|
- **Parámetros:** el SQL lleva `:nombre` y el servidor los cambia por `?` con los valores como argumentos; un valor nunca se concatena al SQL.
|
|
- **Operación:** una conexión por consulta (se abre, fija el esquema, ejecuta y cierra; nunca confirma nada), cola propia (`SAPDB_MAX_CONCURRENCY`), tiempo máximo (`SAPDB_TIMEOUT`) y tope de filas (`SAPDB_MAX_ROWS`). La auditoría registra `db_query` con el nombre, los nombres de los parámetros y las filas devueltas, nunca los valores.
|
|
|