From 287b187c144f411acb0c1f546d00d21b7b0cecf9 Mon Sep 17 00:00:00 2001 From: george Date: Tue, 13 Oct 2026 08:09:07 -0500 Subject: [PATCH] =?UTF-8?q?Aplicaci=C3=B3n=20web=20de=20Devoluci=C3=B3n=20?= =?UTF-8?q?a=20Proveedores=20para=20SAP=20Business=20One?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reemplaza al addon .NET Framework (SolucionLibros). Genera Devoluciones de mercancías enlazadas a sus Entradas por compra a partir de un listado pegado desde Excel. - libs/shared (@dp/shared): modelos y lógica de dominio pura (FIFO, consolidación por proveedor, parseo del portapapeles), portada del addon. - apps/api (NestJS): cliente del Service Layer con cola por sesión, login con la sesión de SAP, metadatos (UDT/UDF/UDO) y botón Reconstruir, catálogos, planificaciones (pegar, procesar, reprocesar, marcar líneas, crear Devoluciones, cerrar), listado paginado con filtros y descarga de PDF de la Guía de despacho desde BES. Sirve también la web compilada, con HTTPS opcional. - apps/web (Angular): login, Configuración → Metadatos y Planificaciones (listado, nueva, detalle). - deploy/windows y scripts/publicar.mjs: despliegue como servicio de Windows con WinSW. Verificado contra el Service Layer de SQL Server. Sin probar aún: SAP HANA y la instalación del servicio en Windows (ver CLAUDE.md, sección "Sin verificar"). Co-Authored-By: Claude Sonnet 5.5 --- .gitignore | 9 + CLAUDE.md | 294 + apps/api/.env.example | 37 + apps/api/.oxlintrc.json | 10 + apps/api/.prettierrc | 4 + apps/api/README.md | 138 + apps/api/nest-cli.json | 8 + apps/api/package.json | 56 + apps/api/src/app.module.ts | 26 + apps/api/src/auth/auth.controller.ts | 64 + apps/api/src/auth/auth.module.ts | 10 + apps/api/src/auth/dto/login.dto.ts | 12 + apps/api/src/auth/session.guard.ts | 42 + .../api/src/catalogos/catalogos.controller.ts | 22 + apps/api/src/catalogos/catalogos.module.ts | 6 + apps/api/src/catalogos/catalogos.service.ts | 64 + apps/api/src/config/configuration.ts | 82 + apps/api/src/health/health.controller.ts | 11 + apps/api/src/http-exception.logger.ts | 17 + apps/api/src/https.spec.ts | 120 + apps/api/src/https.ts | 32 + apps/api/src/main.ts | 40 + .../definitions/external-dependencies.json | 25 + .../src/metadata/definitions/user-fields.json | 325 + .../metadata/definitions/user-objects.json | 27 + .../src/metadata/definitions/user-tables.json | 14 + .../src/metadata/metadata.analyzer.spec.ts | 104 + apps/api/src/metadata/metadata.analyzer.ts | 196 + apps/api/src/metadata/metadata.controller.ts | 29 + apps/api/src/metadata/metadata.definitions.ts | 25 + apps/api/src/metadata/metadata.module.ts | 9 + apps/api/src/metadata/metadata.service.ts | 124 + apps/api/src/pdf/bes-soap.spec.ts | 76 + apps/api/src/pdf/bes-soap.ts | 73 + apps/api/src/pdf/bes.client.ts | 55 + apps/api/src/pdf/pdf-dte.service.spec.ts | 154 + apps/api/src/pdf/pdf-dte.service.ts | 122 + apps/api/src/pdf/pdf.controller.ts | 27 + apps/api/src/pdf/pdf.module.ts | 8 + .../dto/planificaciones.dto.ts | 49 + .../planificaciones.controller.ts | 96 + .../planificaciones/planificaciones.module.ts | 7 + .../planificaciones.service.spec.ts | 168 + .../planificaciones.service.ts | 416 + .../src/planificaciones/udo.mapper.spec.ts | 89 + apps/api/src/planificaciones/udo.mapper.ts | 160 + .../service-layer.client.spec.ts | 70 + .../src/service-layer/service-layer.client.ts | 182 + .../src/service-layer/service-layer.module.ts | 10 + apps/api/src/service-layer/session.store.ts | 62 + .../sql-queries/sql-queries.definitions.ts | 48 + .../api/src/sql-queries/sql-queries.module.ts | 6 + .../src/sql-queries/sql-queries.service.ts | 58 + apps/api/src/web-estatico.spec.ts | 98 + apps/api/src/web-estatico.ts | 41 + apps/api/test/app.e2e-spec.ts | 43 + apps/api/test/cookie.e2e-spec.ts | 76 + apps/api/tsconfig.build.json | 8 + apps/api/tsconfig.json | 27 + apps/api/vitest.config.e2e.ts | 10 + apps/api/vitest.config.ts | 10 + apps/web/.editorconfig | 17 + apps/web/.gitignore | 44 + apps/web/.prettierrc | 12 + apps/web/.vscode/extensions.json | 4 + apps/web/.vscode/launch.json | 20 + apps/web/.vscode/tasks.json | 42 + apps/web/README.md | 92 + apps/web/angular.json | 81 + apps/web/package.json | 33 + apps/web/public/favicon.ico | Bin 0 -> 15086 bytes apps/web/src/app/app.config.ts | 13 + apps/web/src/app/app.routes.ts | 31 + apps/web/src/app/app.spec.ts | 76 + apps/web/src/app/app.ts | 9 + apps/web/src/app/core/auth.interceptor.ts | 25 + apps/web/src/app/core/auth.service.ts | 48 + apps/web/src/app/core/guards.ts | 36 + apps/web/src/app/core/http-error.ts | 13 + apps/web/src/app/core/metadata.service.ts | 41 + .../src/app/core/planificaciones.service.ts | 84 + apps/web/src/app/layout/shell.ts | 82 + apps/web/src/app/layout/stepper.ts | 67 + apps/web/src/app/pages/login/login.css | 49 + apps/web/src/app/pages/login/login.html | 42 + apps/web/src/app/pages/login/login.ts | 57 + .../web/src/app/pages/metadatos/metadatos.css | 39 + .../src/app/pages/metadatos/metadatos.html | 101 + apps/web/src/app/pages/metadatos/metadatos.ts | 87 + .../pages/planificaciones/detalle/detalle.css | 108 + .../planificaciones/detalle/detalle.html | 289 + .../planificaciones/detalle/detalle.spec.ts | 234 + .../pages/planificaciones/detalle/detalle.ts | 224 + .../app/pages/planificaciones/nueva/nueva.css | 87 + .../pages/planificaciones/nueva/nueva.html | 123 + .../pages/planificaciones/nueva/nueva.spec.ts | 222 + .../app/pages/planificaciones/nueva/nueva.ts | 178 + .../pages/planificaciones/planificaciones.css | 86 + .../planificaciones/planificaciones.html | 120 + .../planificaciones/planificaciones.spec.ts | 174 + .../pages/planificaciones/planificaciones.ts | 144 + apps/web/src/index.html | 13 + apps/web/src/main.ts | 6 + apps/web/src/proxy.conf.json | 6 + apps/web/src/styles.css | 224 + apps/web/tsconfig.app.json | 14 + apps/web/tsconfig.json | 31 + apps/web/tsconfig.spec.json | 14 + deploy/windows/DEPLOY.md | 221 + deploy/windows/desinstalar-servicio.ps1 | 25 + deploy/windows/devolucion-api.xml.plantilla | 32 + deploy/windows/instalar-servicio.ps1 | 86 + libs/shared/package.json | 22 + libs/shared/src/domain/fifo.spec.ts | 82 + libs/shared/src/domain/fifo.ts | 176 + libs/shared/src/domain/portapapeles.spec.ts | 43 + libs/shared/src/domain/portapapeles.ts | 80 + libs/shared/src/index.ts | 6 + libs/shared/src/models/auth.ts | 16 + libs/shared/src/models/catalogos.ts | 141 + libs/shared/src/models/metadata.ts | 98 + libs/shared/src/models/planificacion.ts | 97 + libs/shared/tsconfig.json | 17 + package-lock.json | 11021 ++++++++++++++++ package.json | 16 + scripts/publicar.mjs | 81 + 126 files changed, 20033 insertions(+) create mode 100644 .gitignore create mode 100644 CLAUDE.md create mode 100644 apps/api/.env.example create mode 100644 apps/api/.oxlintrc.json create mode 100644 apps/api/.prettierrc create mode 100644 apps/api/README.md create mode 100644 apps/api/nest-cli.json create mode 100644 apps/api/package.json create mode 100644 apps/api/src/app.module.ts create mode 100644 apps/api/src/auth/auth.controller.ts create mode 100644 apps/api/src/auth/auth.module.ts create mode 100644 apps/api/src/auth/dto/login.dto.ts create mode 100644 apps/api/src/auth/session.guard.ts create mode 100644 apps/api/src/catalogos/catalogos.controller.ts create mode 100644 apps/api/src/catalogos/catalogos.module.ts create mode 100644 apps/api/src/catalogos/catalogos.service.ts create mode 100644 apps/api/src/config/configuration.ts create mode 100644 apps/api/src/health/health.controller.ts create mode 100644 apps/api/src/http-exception.logger.ts create mode 100644 apps/api/src/https.spec.ts create mode 100644 apps/api/src/https.ts create mode 100644 apps/api/src/main.ts create mode 100644 apps/api/src/metadata/definitions/external-dependencies.json create mode 100644 apps/api/src/metadata/definitions/user-fields.json create mode 100644 apps/api/src/metadata/definitions/user-objects.json create mode 100644 apps/api/src/metadata/definitions/user-tables.json create mode 100644 apps/api/src/metadata/metadata.analyzer.spec.ts create mode 100644 apps/api/src/metadata/metadata.analyzer.ts create mode 100644 apps/api/src/metadata/metadata.controller.ts create mode 100644 apps/api/src/metadata/metadata.definitions.ts create mode 100644 apps/api/src/metadata/metadata.module.ts create mode 100644 apps/api/src/metadata/metadata.service.ts create mode 100644 apps/api/src/pdf/bes-soap.spec.ts create mode 100644 apps/api/src/pdf/bes-soap.ts create mode 100644 apps/api/src/pdf/bes.client.ts create mode 100644 apps/api/src/pdf/pdf-dte.service.spec.ts create mode 100644 apps/api/src/pdf/pdf-dte.service.ts create mode 100644 apps/api/src/pdf/pdf.controller.ts create mode 100644 apps/api/src/pdf/pdf.module.ts create mode 100644 apps/api/src/planificaciones/dto/planificaciones.dto.ts create mode 100644 apps/api/src/planificaciones/planificaciones.controller.ts create mode 100644 apps/api/src/planificaciones/planificaciones.module.ts create mode 100644 apps/api/src/planificaciones/planificaciones.service.spec.ts create mode 100644 apps/api/src/planificaciones/planificaciones.service.ts create mode 100644 apps/api/src/planificaciones/udo.mapper.spec.ts create mode 100644 apps/api/src/planificaciones/udo.mapper.ts create mode 100644 apps/api/src/service-layer/service-layer.client.spec.ts create mode 100644 apps/api/src/service-layer/service-layer.client.ts create mode 100644 apps/api/src/service-layer/service-layer.module.ts create mode 100644 apps/api/src/service-layer/session.store.ts create mode 100644 apps/api/src/sql-queries/sql-queries.definitions.ts create mode 100644 apps/api/src/sql-queries/sql-queries.module.ts create mode 100644 apps/api/src/sql-queries/sql-queries.service.ts create mode 100644 apps/api/src/web-estatico.spec.ts create mode 100644 apps/api/src/web-estatico.ts create mode 100644 apps/api/test/app.e2e-spec.ts create mode 100644 apps/api/test/cookie.e2e-spec.ts create mode 100644 apps/api/tsconfig.build.json create mode 100644 apps/api/tsconfig.json create mode 100644 apps/api/vitest.config.e2e.ts create mode 100644 apps/api/vitest.config.ts create mode 100644 apps/web/.editorconfig create mode 100644 apps/web/.gitignore create mode 100644 apps/web/.prettierrc create mode 100644 apps/web/.vscode/extensions.json create mode 100644 apps/web/.vscode/launch.json create mode 100644 apps/web/.vscode/tasks.json create mode 100644 apps/web/README.md create mode 100644 apps/web/angular.json create mode 100644 apps/web/package.json create mode 100644 apps/web/public/favicon.ico create mode 100644 apps/web/src/app/app.config.ts create mode 100644 apps/web/src/app/app.routes.ts create mode 100644 apps/web/src/app/app.spec.ts create mode 100644 apps/web/src/app/app.ts create mode 100644 apps/web/src/app/core/auth.interceptor.ts create mode 100644 apps/web/src/app/core/auth.service.ts create mode 100644 apps/web/src/app/core/guards.ts create mode 100644 apps/web/src/app/core/http-error.ts create mode 100644 apps/web/src/app/core/metadata.service.ts create mode 100644 apps/web/src/app/core/planificaciones.service.ts create mode 100644 apps/web/src/app/layout/shell.ts create mode 100644 apps/web/src/app/layout/stepper.ts create mode 100644 apps/web/src/app/pages/login/login.css create mode 100644 apps/web/src/app/pages/login/login.html create mode 100644 apps/web/src/app/pages/login/login.ts create mode 100644 apps/web/src/app/pages/metadatos/metadatos.css create mode 100644 apps/web/src/app/pages/metadatos/metadatos.html create mode 100644 apps/web/src/app/pages/metadatos/metadatos.ts create mode 100644 apps/web/src/app/pages/planificaciones/detalle/detalle.css create mode 100644 apps/web/src/app/pages/planificaciones/detalle/detalle.html create mode 100644 apps/web/src/app/pages/planificaciones/detalle/detalle.spec.ts create mode 100644 apps/web/src/app/pages/planificaciones/detalle/detalle.ts create mode 100644 apps/web/src/app/pages/planificaciones/nueva/nueva.css create mode 100644 apps/web/src/app/pages/planificaciones/nueva/nueva.html create mode 100644 apps/web/src/app/pages/planificaciones/nueva/nueva.spec.ts create mode 100644 apps/web/src/app/pages/planificaciones/nueva/nueva.ts create mode 100644 apps/web/src/app/pages/planificaciones/planificaciones.css create mode 100644 apps/web/src/app/pages/planificaciones/planificaciones.html create mode 100644 apps/web/src/app/pages/planificaciones/planificaciones.spec.ts create mode 100644 apps/web/src/app/pages/planificaciones/planificaciones.ts create mode 100644 apps/web/src/index.html create mode 100644 apps/web/src/main.ts create mode 100644 apps/web/src/proxy.conf.json create mode 100644 apps/web/src/styles.css create mode 100644 apps/web/tsconfig.app.json create mode 100644 apps/web/tsconfig.json create mode 100644 apps/web/tsconfig.spec.json create mode 100644 deploy/windows/DEPLOY.md create mode 100644 deploy/windows/desinstalar-servicio.ps1 create mode 100644 deploy/windows/devolucion-api.xml.plantilla create mode 100644 deploy/windows/instalar-servicio.ps1 create mode 100644 libs/shared/package.json create mode 100644 libs/shared/src/domain/fifo.spec.ts create mode 100644 libs/shared/src/domain/fifo.ts create mode 100644 libs/shared/src/domain/portapapeles.spec.ts create mode 100644 libs/shared/src/domain/portapapeles.ts create mode 100644 libs/shared/src/index.ts create mode 100644 libs/shared/src/models/auth.ts create mode 100644 libs/shared/src/models/catalogos.ts create mode 100644 libs/shared/src/models/metadata.ts create mode 100644 libs/shared/src/models/planificacion.ts create mode 100644 libs/shared/tsconfig.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/publicar.mjs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b9b973 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +node_modules/ +dist/ +.angular/ +coverage/ +*.tsbuildinfo +.env +.DS_Store + +release/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..bcd5dda --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,294 @@ +# CLAUDE.md + +Aplicación web **Devolución a Proveedores** para SAP Business One. Reemplaza al addon +.NET Framework (`../SolucionLibros/`), que **se abandona**: todo pasa a Angular + NestJS. +`../SolucionLibros/` queda solo como referencia de la lógica original — **no modificarlo**. + +## Qué hace el proceso + +Genera **Devoluciones de mercancías** (ORPD, ObjType 21) enlazadas a sus **Entradas de +mercancías por compra** (OPDN, ObjType 20), a partir de un listado de artículos y +cantidades pegado desde Excel. Cada ejecución se guarda en el UDO `LIB_ODEV` +(cabecera `@LIB_ODEV`, líneas `@LIB_DEV1`). + +Pasos de la UI (decididos): + +1. **Pegar**: ItemCode + cantidad desde Excel (con el botón *Pegar desde el portapapeles* o + `Ctrl/Cmd+V` en el cuadro). Se valida el stock (`OITW.OnHand`) del almacén elegido. No guarda. +2. **Procesar**: reemplaza a "Selección" del addon. Asignación FIFO contra líneas abiertas + de Entradas de mercancías **del almacén elegido**, ordenadas por `DocDate`. Genera filas + Padre (`F`, artículo) y Hijo (`S`, una por Entrada consumida). El usuario marca o desmarca + qué líneas Hijo devolver. +3. **Crear devoluciones**: agrupa las líneas marcadas sin `TargetEntry` por proveedor y + las parte en lotes de `U_PrintingLines` líneas (11 por defecto). Crea una Devolución por + lote y graba `U_TargetType`/`U_TargetEntry` en las líneas. +4. **Descargar PDF**: por cada Devolución, se pide el PDF de la Guía de despacho (DTE tipo 52) + al web service SOAP de BES, usando las credenciales de `@DTE_CONFC`. Opción de unir todos + los PDF en uno. + +## Decisiones tomadas (no volver a discutir) + +- Solo se crean **Devoluciones** (ObjType 21), no Solicitudes de devolución. Solo se cargan + las series de Devoluciones. +- `U_Status` de cabecera **eliminado**. El estado usa el `Status` estándar del UDO (`O`/`C`). +- Se usan `U_TipoDespacho` y `U_IndicadorTraslado` en `@LIB_ODEV`. Se envían a la Devolución + como `U_BFE_TipoDespacho` / `U_BFE_IndTraslado`. +- Cantidades y referencias son **enteros** (`db_Numeric`): `Quantity`, `OnHand`, `OpenQty`, + `AllocatedQty`, `BaseEntry`, `BaseLine`, `BaseRef`, `TargetEntry` y `U_Series`. +- Producción en **HANA**; ambiente de pruebas en **MSSQL**. Toda consulta SQL debe tener las + dos variantes, según `DB_TYPE`. +- Sin control de concurrencia: **solo puede haber una planificación abierta**. Para crear + una nueva, todas las anteriores tienen que estar cerradas. +- Después de Procesar, el usuario elige qué líneas devolver. **Una línea con `TargetEntry` + ya no se puede desmarcar** (ver `puedeCambiarSeleccion` en `libs/shared`). +- La planificación se **cierra a mano** (`POST /planificaciones/:id/cerrar`). +- **Reprocesar** (botón *Rehacer FIFO* del Detalle) solo vale para una planificación **abierta y sin + Devoluciones**. Las filas de un UDO no se pueden quitar por Service Layer, así que se crea una nueva + (heredando la cabecera) y, **después** de crearla, se cierra la anterior (si el cierre falla se avisa + para cerrarla a mano). +- **Crear devoluciones**: resultado **parcial por documento**. Cada Devolución se crea aparte; las + buenas graban `U_TargetType`/`U_TargetEntry`, las fallidas `U_ErrCode`/`U_ErrMsg` y se reintentan. +- **Consultas SQL** por `SQLQueries` del Service Layer (no hay conexión directa a la BD). La API + crea/actualiza `DP_Stock` y `DP_EntradasAbiertas` sola (variante según `DB_TYPE`). El analizador + SQL del Service Layer **no admite `FORMAT`** y SAP reescribe el `SqlText`: al cambiar el SQL hay + que subir `version` en `sql-queries.definitions.ts`. Los parámetros van entre comillas simples. +- **Listado** de planificaciones **paginado en SAP** (`$top/$skip/$inlinecount`, OData v3 del SL v1), + más nueva primero, con filtros por estado, almacén y rango de fechas. Por defecto 20 por página, + máximo 100. En la pantalla: botones **Atrás** / **Siguiente**; filtros y página quedan en la URL. +- **PDF**: no se usa el paquete `soap`; `bes-soap.ts` arma el sobre y lee la respuesta a mano y `BesClient` + lo envía con axios (SOAPAction `http://tempuri.org/Consulta_TXTDTE`). Credenciales y URL salen de + `U_DTE_CONFC` y **nunca** se registran ni se devuelven al navegador. Solo se consulta con el + `FolioNumber` de la Devolución: **si todavía no tiene folio no se llama a BES** y esa Devolución informa + "todavía no tiene folio" (el addon usaba el `DocNum` como respaldo; se descartó porque podría traer el DTE + de otro documento). Unir con `pdf-lib`; un fallo en una Devolución no impide las demás. +- Transporte en la Devolución (ORPD): `RutTransporte→U_BFE_RutTrasporte` (sic), `Patente→U_BFE_Patente`, + `RutChofer→U_BFE_RutChofer`, `NombreChofer→U_BFE_NombreChofer`. **ORPD no tiene UDF para fechas + ni hora de salida/llegada**: solo se guardan en `@LIB_ODEV`. +- **Despliegue en Windows**: un solo servicio. La API sirve también la web compilada (mismo origen: sin + CORS y con la cookie `SameSite=strict`), con **HTTPS directo en Node** (`HTTPS_PFX_PATH` o + `HTTPS_KEY_PATH`+`HTTPS_CERT_PATH`) y redirección opcional de HTTP (`HTTP_REDIRECT_PORT`). El servicio + lo administra **WinSW** (elegido sobre NSSM por su configuración en XML versionable); nginx no es + necesario. `npm run publicar` arma `release/devolucion-proveedores/` (API + web + solo dependencias de + producción, ~55 MB). Guía completa: `deploy/windows/DEPLOY.md`. +- **Logs**: la aplicación **no escribe logs a archivo**; usa el `Logger` de Nest por consola. En + producción WinSW captura la salida y la rota (10 MB × 8). Con `NO_COLOR=1` el log sale sin códigos de + color. Solo se registran errores 4xx/5xx, fallos o lentitud del Service Layer y eventos de negocio. +- Aplicación web **independiente**. Login con el usuario de SAP B1 vía **Login del Service Layer**. +- Metadatos (UDT/UDF/UDO) definidos en **JSON** con formato del Service Layer. La web tiene la + pantalla **Configuración → Metadatos** con el botón **Reconstruir**, y NestJS los crea. + Los cambios que borran datos (campo con tipo distinto o campo sobrante) solo se hacen con + autorización explícita. +- **Después de cada login** se verifica que existan las tablas, los campos y el UDO. Si + falta algo, se redirige a Configuración → Metadatos. El login tiene una casilla **"Verificar + los metadatos de SAP al ingresar"** (activa por defecto; se recuerda en `localStorage`). + Desactivada, el guard omite la verificación; Configuración → Metadatos siempre verifica. + +## Estructura + +``` +devolucion-proveedores-web/ npm workspaces +├── libs/shared/ @dp/shared tipos + lógica de dominio pura (ESM, se compila a dist/) +│ └── src/ +│ ├── models/ auth, metadata, planificacion, catalogos (catálogos, páginas, PDF) +│ └── domain/ portapapeles.ts (parseo Excel), fifo.ts (FIFO, consolidación) +├── apps/api/ NestJS 12 ESM ("type": "module"), imports con extensión .js +│ ├── .env(.example) +│ └── src/ +│ ├── config/ configuration.ts (lee .env) +│ ├── service-layer/ ServiceLayerClient (axios, cola por sesión), SessionStore (memoria) +│ ├── auth/ login/logout/me/info, SessionGuard global + @Public() +│ ├── sql-queries/ SqlQueriesService + definiciones (DP_Stock, DP_EntradasAbiertas) +│ ├── catalogos/ almacenes, series, valores UDF +│ ├── planificaciones/ service, controller, dto, udo.mapper (puro, con tests) +│ ├── pdf/ bes-soap (puro), BesClient, PdfDteService (descarga y unión) +│ ├── metadata/ analyzer (puro), service, controller +│ │ └── definitions/ user-tables.json, user-fields.json, user-objects.json, +│ │ external-dependencies.json ← fuente de verdad +│ ├── health/ +│ ├── http-exception.logger.ts filtro global: deja en el log todo error 4xx/5xx +│ ├── web-estatico.ts sirve el build de Angular, fallback a index.html, cabeceras de seguridad +│ └── https.ts lectura del certificado y redirección HTTP → HTTPS +├── scripts/publicar.mjs arma la carpeta de despliegue (release/, ignorada por git) +├── deploy/windows/ WinSW (plantilla XML), instalar/desinstalar-servicio.ps1, DEPLOY.md +└── apps/web/ Angular 22 standalone, signals, zoneless, CSS (no SCSS) + └── src/app/ + ├── core/ auth.service, metadata.service, planificaciones.service, + │ guards, auth.interceptor, http-error + ├── layout/ shell.ts (barra y navegación), stepper.ts (4 pasos) + └── pages/ + ├── login/ con la casilla de verificar metadatos + ├── metadatos/ Configuración → Metadatos + └── planificaciones/ listado (filtros + paginación) · nueva/ (pasos 1-2) · detalle/ (pasos 3-4) +``` + +### API (prefijo `/api`) + +| Método | Ruta | Descripción | +|---|---|---| +| GET | `/health` | público | +| GET | `/auth/info` | público: CompanyDB y DB_TYPE configurados | +| POST | `/auth/login` | `{userName, password}`: Login en el Service Layer y cookie httpOnly `dp_sid` | +| POST | `/auth/logout` | Logout en el Service Layer | +| GET | `/auth/me` | usuario de la sesión | +| GET | `/metadata/status` | compara las definiciones contra la base SAP | +| POST | `/metadata/rebuild` | `{permitirDestructivos: boolean}`: crea y corrige | +| GET | `/catalogos` | almacenes, series de Devoluciones (con la por defecto), valores de `BFE_TipoDespacho`/`BFE_IndTraslado` | +| GET | `/catalogos/almacenes` | solo almacenes (filtros del listado) | +| GET | `/planificaciones?status=&whsCode=&desde=&hasta=&pagina=&tamano=` | **una página** (`{items,total,pagina,tamano,totalPaginas}`), más nueva primero; filtros y salto en SAP. `tamano` máx. 100, por defecto 20 | +| GET | `/planificaciones/abierta` | la abierta (`{abierta: null}` si no hay; siempre JSON) | +| GET | `/planificaciones/:docEntry` | detalle: cabecera y líneas | +| POST | `/planificaciones/pegar` | `{whsCode, texto}`: valida Excel contra stock. No guarda | +| POST | `/planificaciones` | procesa: FIFO + guarda en `LIB_ODEV`. 409 si ya hay una abierta | +| PATCH | `/planificaciones/:id/cabecera` | serie, fecha, despacho, traslado, transporte | +| PATCH | `/planificaciones/:id/lineas/:lineId` | `{applied}`. 409 si la línea ya tiene `TargetEntry` | +| POST | `/planificaciones/:id/devoluciones` | crea las Devoluciones de las líneas marcadas | +| POST | `/planificaciones/:id/reprocesar` | rehace el FIFO: crea una planificación nueva y cierra la anterior. Solo si está **abierta y sin Devoluciones** (409 si no) | +| POST | `/planificaciones/:id/pdf` | `{unir?: boolean}`: pide a BES el PDF de la Guía de despacho (DTE 52) de cada Devolución; devuelve los archivos en base64 y el resultado por Devolución | +| POST | `/planificaciones/:id/cerrar` | cierra la planificación | + +La sesión del Service Layer (`B1SESSION`/`ROUTEID`) vive en el servidor; el navegador solo +tiene `dp_sid`. `SessionStore` es en memoria: alcanza para una sola instancia de la API y **se +pierde al reiniciar la API** (hay que volver a iniciar sesión). + +### Metadatos (definitions/*.json) + +- **Tablas:** `LIB_ODEV` (bott_Document) y `LIB_DEV1` (bott_DocumentLines). +- **UDO:** `LIB_ODEV` (boud_Document), hija `LIB_DEV1`. CanFind/CanClose/CanCancel = tYES, + CanDelete = tNO, CanLog = tYES (`ALIB_ODEV`), sin formulario por defecto. Columnas de búsqueda: + `DocEntry`, `U_WhsCode`, `U_DocDate`. +- **Campos de `@LIB_ODEV`:** WhsCode, DocDate, Series (num), ObjType, PrintingLines (num), + TipoDespacho, IndicadorTraslado, RutTransporte, RutChofer, NombreChofer, FechaSalida, + HoraSalida (st_Time), FechaLlegada, Patente. +- **Campos de `@LIB_DEV1`:** LineType (F/S), ItemCode, ItemDescription, Quantity, OnHand, Status, + BaseType, BaseEntry, BaseLine, BaseRef, BaseDocDate, OpenQty, AllocatedQty, Applied (Y/N), + TargetType, TargetEntry, CardCode, CardName, ErrCode, ErrMsg. +- **Dependencias externas** (solo se verifican, no se crean): `OPDN.U_DOT_TipoRecepcion`, + `OINV.U_BFE_TipoDespacho`, `OINV.U_BFE_IndTraslado` y la tabla `@DTE_CONFC`. + +## Preparar el entorno (otro computador) + +1. **Node 22 LTS** o superior (`node -v`). +2. **Red con Fortinet** (inspección SSL): npm falla con `SELF_SIGNED_CERT_IN_CHAIN`. + Exportar antes de usar npm u ng: + ```sh + export NODE_EXTRA_CA_CERTS="/ruta/a/fortinet-root.crt" # está en ../fortinet-root.crt + # alternativa, si el certificado está en el llavero de macOS: + export NODE_OPTIONS=--use-system-ca + export NG_CLI_ANALYTICS=false # si no, `ng` puede quedar esperando el prompt + ``` +3. Instalar y configurar: + ```sh + npm install + cp apps/api/.env.example apps/api/.env # completar SL_URL, SL_COMPANY_DB, DB_TYPE + ``` + Variables del `.env` de la API: `PORT` (3000), `WEB_ORIGIN` (CORS), `SL_URL` (con `/b1s/v1`), + `SL_COMPANY_DB`, `SL_REJECT_UNAUTHORIZED` (`false` si el certificado es autofirmado), `DB_TYPE` + (`HANA`/`MSSQL`) y `COOKIE_SECURE` (`true` solo con HTTPS). **El usuario y la contraseña de SAP no van + en el `.env`**: se escriben en el login. Solo para producción: `HTTPS_PFX_PATH`/`HTTPS_PFX_PASSPHRASE` o + `HTTPS_KEY_PATH`/`HTTPS_CERT_PATH`, `HTTP_REDIRECT_PORT` y `WEB_DIST_DIR` (opcional). +4. Desarrollo, en dos terminales: + ```sh + npm run dev:api # compila @dp/shared y levanta NestJS en :3000 + npm run dev:web # compila @dp/shared y levanta Angular en :4200 (proxy /api → :3000) + ``` +5. Verificación: + ```sh + npm test # shared + api + web + npm run test:e2e -w api + npm run lint -w api + npm run build + npm run publicar # carpeta de despliegue en release/ (ver deploy/windows/DEPLOY.md) + ``` + +Reglas: +- `@dp/shared` se consume **compilado** (`dist/`). Si se cambia, ejecutar `npm run build:shared` + **y reiniciar `ng serve`**: Vite sirve una copia vieja y la página falla con "does not provide an + export named ..." (los tests no lo detectan). +- En la API, los tipos que aparecen en firmas con decoradores se importan con `import type` + (por `isolatedModules` + `emitDecoratorMetadata`). Los DTO (clases) sí se importan como valor. +- Al cambiar código del backend con `start:dev` la API se reinicia y **se pierden las sesiones**. +- Los tests del frontend no usan zone.js: tras un `flush` hay que esperar con + `await new Promise((r) => setTimeout(r))` y luego `fixture.detectChanges()` (`whenStable` no basta). +- Para ver la UI sin la extensión de Chrome se puede abrir Chrome sin cabeza con + `--remote-debugging-port` y manejarlo por DevTools Protocol (`WebSocket` de Node 22). + +## Estado al 2026-10-12 + +**Hecho y verificado contra el Service Layer real (SBOLibros, MSSQL):** +- Login, Metadatos y **Reconstruir** (creación de tablas, campos y UDO desde cero). +- `libs/shared`: modelos y dominio portado del .NET. 14 tests. +- `apps/api`: Service Layer, autenticación, metadatos, catálogos, planificaciones y PDF. 81 tests + unitarios + 6 e2e (incluye la cookie de sesión: `HttpOnly`, `Secure`, `SameSite=Strict`), lint limpio. Prueba de extremo a extremo manual OK: pegar → procesar (FIFO) → + cabecera (fecha/hora) → marcar/desmarcar → crear Devolución (ORPD con base en OPDN) → `TargetEntry` + grabado → cerrar. **Varios proveedores** OK contra SAP (una planificación con 4 líneas generó 3 + Devoluciones, con un proveedor reuniendo dos líneas). Las reglas 409 (una sola abierta, línea con + destino, cerrada) OK. **Listado + paginado y filtrado** (total, saltos, estado/almacén/fechas) OK. **PDF** contra BES (certificación) + con 4 Devoluciones reales: individuales y unificado (3 páginas) OK. +- `apps/web`: login, guards, Metadatos y Planificaciones completas (listado con filtros y paginación, + Nueva, Detalle con crear devoluciones, Rehacer FIFO, PDF y cerrar). 38 tests. Verificado en Chrome + real (sin cabeza): login, listado con filtros, selector de almacenes, validación de pegado y la + descarga de PDF del Detalle. + +**Aprendido del Service Layer real:** +- **Una sesión no admite peticiones simultáneas**: las procesa en serie y cada una espera ~7 s tras + la anterior (seis en paralelo = ~40 s; una a una = ~40 ms). `ServiceLayerClient.request` las + **encola por sesión**; no lo saltes con otro cliente HTTP. Avisa en el log si una tarda > 2 s. +- `CanFind = tYES` exige `UserObjectMD_FindColumns`; sin ellas SAP lo deja en `tNO` en silencio. +- `GET UserFieldsMD` informa los campos de hora como `db_Numeric/st_Time` (el analizador lo normaliza). +- `PATCH` de `LIB_DEV1Collection` actualiza las filas por `LineId` sin tocar las demás; **no se pueden + quitar filas** de un UDO. +- Los campos numéricos sin llenar del UDO vuelven como `0`: el mapeo los trata como `null`. +- `POST /LIB_ODEV(n)/Close` cierra el UDO. El UDO no se puede borrar (`CanDelete = tNO`). +- Si no hay resultado, el Service Layer responde cuerpo vacío: por eso `/planificaciones/abierta` + devuelve siempre un JSON (`{abierta: null}`). + +**Sin verificar:** +- **HANA**: el addon .NET ya funcionaba en HANA, y las consultas son un port de las suyas, así que + su lógica está probada. Lo que no se ha probado es **cómo las recibe el Service Layer de HANA** + (`SQLQueries`: su analizador es más estricto que el `DoQuery` del addon; en MSSQL rechazó `FORMAT`) y los + dos cambios respecto al addon: el filtro por almacén (`PDN1.WhsCode`) y los parámetros con nombre + (`:item`, `:whs`). Tampoco el paginado (`$inlinecount`/`odata.count` es de `/b1s/v1`; en `/b1s/v2` sería + `$count`) ni los tipos que HANA informa en los metadatos. +- **Reprocesar** solo con tests (no contra SAP). +- Fallo parcial al crear Devoluciones (solo se probó el caso feliz). El corte en lotes de + `U_PrintingLines` líneas está probado con tests del algoritmo portado del addon, no contra SAP. +- El `PATCH` de Reconstruir sobre un UDO ya existente con columnas de búsqueda. +- Paginación con varias páginas en la interfaz (hoy hay pocas planificaciones; sí está probada con la + API real y con tests de la pantalla). +- Recorrido con clics de **Procesar** en un navegador real. +- **PDF en la base de pruebas**: las Devoluciones de `SBOLibros` no tienen `FolioNum`, así que ahora su + descarga informa el error de folio. El PDF contra BES se probó antes del cambio, usando el `DocNum`. + Para repetirlo hace falta una Devolución con folio (la que emite el addon de facturación electrónica). +- **Despliegue en Windows**: la carpeta publicada se probó en un Mac (API con HTTPS autofirmado, web + servida por la API, rutas de Angular, 404, cabeceras, redirección 301, log sin colores). **No se ha + ejecutado en Windows**: los `.ps1` solo pasaron la comprobación de sintaxis y la generación del XML de + WinSW; falta instalar el servicio de verdad. Las banderas de la cookie de sesión están comprobadas en un + test e2e; solo falta verlo en un navegador real sobre HTTPS (iniciar sesión en el sitio desplegado). +- **Estilos:** son CSS porque, en el Mac con macOS 12, el compilador nativo de Sass exige + macOS 14 o superior. En Sequoia se puede volver a SCSS si se prefiere. + +## Siguientes pasos + +1. **Validar en HANA** (ver "Sin verificar"): `DB_TYPE=HANA`, Reconstruir, repetir la prueba de extremo a extremo. +2. Probar los casos sin verificar (fallo parcial y reprocesar contra SAP). +3. **Probar el despliegue en un servidor Windows** (de pruebas) siguiendo `deploy/windows/DEPLOY.md`. El + botón de pegar desde el portapapeles solo funciona en `localhost` o HTTPS. +4. Archivar `../SolucionLibros/` cuando HANA esté validado (ninguna carpeta está bajo `git`). + +## Preguntas abiertas para el usuario + +1. ¿Se agregan UDF de fecha/hora de salida y llegada a la Devolución (ORPD)? Hoy no existen. +2. Tamaño de `U_TipoDespacho`/`U_IndicadorTraslado` en `@LIB_ODEV` (10) frente a ORPD (1 y 2). + +## Referencias en el addon .NET (`../SolucionLibros/DevolucionProveedores/`) + +Ya está todo portado; sirve para comparar resultados (sobre todo al validar en HANA). + +- `FormularioDevolucionProveedores.Negocio.cs`: parseo, consultas OnHand/FIFO, consolidación. +- `FormularioDevolucionProveedores.Botones.cs`: Crear devoluciones (BOM y `Add`), descarga de PDF. +- `FormularioDevolucionProveedores.Bom.cs`: guardado del UDO (`GeneralService`) y BOM de la Devolución. +- `WebServices/ConsultaDTE.cs`, `WebServices/PdfUtils.cs`: SOAP BES y unión de PDF. +- `ESPECIFICACION.md`: especificación original del proceso. +- `bin/x64/Debug/UserFields.xml` (UTF-16): versión XML anterior de los metadatos, ya + reemplazada por los JSON de `apps/api/src/metadata/definitions/`. diff --git a/apps/api/.env.example b/apps/api/.env.example new file mode 100644 index 0000000..621f287 --- /dev/null +++ b/apps/api/.env.example @@ -0,0 +1,37 @@ +# Puerto de la API NestJS +PORT=3000 + +# Origen del frontend Angular (CORS). En desarrollo ng serve usa proxy, no hace falta. +WEB_ORIGIN=http://localhost:4200 + +# Service Layer de SAP Business One (con /b1s/v1 al final) +SL_URL=https://servidor-sap:50000/b1s/v1 +# Base de datos de la sociedad (CompanyDB del Login) +SL_COMPANY_DB=SBODEMOCL +# false si el Service Layer usa un certificado autofirmado +SL_REJECT_UNAUTHORIZED=false + +# Motor de la base de datos: HANA (producción) o MSSQL (pruebas) +DB_TYPE=HANA + +# true cuando la API se publica por HTTPS (cookie de sesión con Secure) +COOKIE_SECURE=false + +# --------------------------------------------------------------------------------------------- +# Despliegue (producción en Windows). En desarrollo se dejan vacías. +# --------------------------------------------------------------------------------------------- + +# La API sirve también la web compilada. Si se omite, usa apps/web/dist/web/browser cuando existe. +# En la carpeta publicada (release) ya queda en su sitio y no hace falta configurarla. +# WEB_DIST_DIR= + +# HTTPS directo en la API. Use UNA de estas dos formas (las rutas son absolutas). +# Con un .pfx: +# HTTPS_PFX_PATH=C:\apps\devolucion-proveedores\certificados\sitio.pfx +# HTTPS_PFX_PASSPHRASE= +# O con clave privada y certificado en PEM: +# HTTPS_KEY_PATH=C:\apps\devolucion-proveedores\certificados\sitio.key +# HTTPS_CERT_PATH=C:\apps\devolucion-proveedores\certificados\sitio.crt + +# Con HTTPS: puerto HTTP que redirige al HTTPS (normalmente 80). Vacío = no se abre. +# HTTP_REDIRECT_PORT=80 diff --git a/apps/api/.oxlintrc.json b/apps/api/.oxlintrc.json new file mode 100644 index 0000000..554f9be --- /dev/null +++ b/apps/api/.oxlintrc.json @@ -0,0 +1,10 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "rules": { + "typescript/no-explicit-any": "off", + "typescript/no-floating-promises": "error" + }, + "env": { + "node": true + } +} diff --git a/apps/api/.prettierrc b/apps/api/.prettierrc new file mode 100644 index 0000000..a20502b --- /dev/null +++ b/apps/api/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} diff --git a/apps/api/README.md b/apps/api/README.md new file mode 100644 index 0000000..b0ed194 --- /dev/null +++ b/apps/api/README.md @@ -0,0 +1,138 @@ +# API — Devolución a Proveedores + +Backend en **NestJS 12** (ESM) de la aplicación Devolución a Proveedores para SAP Business One. +No tiene base de datos propia: habla con el **Service Layer** de SAP B1 en nombre del usuario que +inició sesión, y guarda cada ejecución en el UDO `LIB_ODEV`. + +La documentación general del proyecto (decisiones, flujo, estado) está en +[`../../CLAUDE.md`](../../CLAUDE.md). Este archivo cubre solo cómo ejecutar y mantener la API. + +## Requisitos + +- Node 22 LTS o superior. +- Acceso de red al Service Layer de SAP B1 (por defecto `https://servidor:50000/b1s/v1`). +- Red con Fortinet (inspección SSL): exportar `NODE_OPTIONS=--use-system-ca` (o `NODE_EXTRA_CA_CERTS`) + antes de usar `npm`. + +## Puesta en marcha + +Desde la raíz del monorepo (`devolucion-proveedores-web/`): + +```bash +npm install +cp apps/api/.env.example apps/api/.env # y completar los valores +npm run dev:api # compila @dp/shared y levanta la API en :3000 +``` + +### Variables de `.env` + +| Variable | Descripción | +|---|---| +| `PORT` | Puerto de la API (3000). | +| `WEB_ORIGIN` | Origen del frontend para CORS. En desarrollo `ng serve` usa proxy y no hace falta. | +| `SL_URL` | URL del Service Layer, **con `/b1s/v1` al final**. | +| `SL_COMPANY_DB` | Base de la sociedad (CompanyDB del Login). | +| `SL_REJECT_UNAUTHORIZED` | `false` si el Service Layer usa un certificado autofirmado. | +| `DB_TYPE` | `HANA` (producción) o `MSSQL` (pruebas). Elige la variante de cada consulta SQL. | +| `COOKIE_SECURE` | `true` solo cuando la API se publica por HTTPS. | +| `HTTPS_PFX_PATH` / `HTTPS_PFX_PASSPHRASE` | Solo producción: certificado `.pfx` y su clave. La API escucha por HTTPS. | +| `HTTPS_KEY_PATH` / `HTTPS_CERT_PATH` | Alternativa al `.pfx`: clave privada y certificado en PEM (no mezclar con el `.pfx`). | +| `HTTP_REDIRECT_PORT` | Con HTTPS: puerto HTTP que redirige (301) al HTTPS, normalmente 80. | +| `WEB_DIST_DIR` | Carpeta con la web compilada. Por defecto `apps/web/dist/web/browser` si existe. | + +El **usuario y la contraseña de SAP no se configuran aquí**: se escriben en la pantalla de login y se +usan para el `Login` del Service Layer. La API reinicia sin conservar sesiones. + +## Scripts + +Se ejecutan con `npm run