# 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 **NSSM** (decisión del usuario; se evaluó WinSW y no se lo consideró lo bastante estable); nginx no es necesario. NSSM 2.24 estable puede no arrancar en Windows recientes: usar la 2.24-101. `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 NSSM captura la salida y la rota a los 10 MB (no limita cuántos archivos conserva: hay que limpiar los viejos). 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/ NSSM: 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 lista de comandos de NSSM que generan (`-SoloMostrar`); 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/`.