Files
devolucion_proveedores/CLAUDE.md
T
georgeandClaude Sonnet 5.5 287b187c14 Aplicación web de Devolución a Proveedores para SAP Business One
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 <noreply@anthropic.com>
2026-10-13 08:09:07 -05:00

295 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/`.