Files
devolucion_proveedores/CLAUDE.md
T
georgeandClaude Sonnet 5.5 699dd983d0 Despliegue en Windows con NSSM en lugar de WinSW
El servicio de Windows pasa a administrarse con NSSM (decisión del usuario: WinSW no se
consideró lo bastante estable).

- instalar-servicio.ps1: recibe -Nssm (ruta de nssm.exe) y configura el servicio con
  `nssm install` / `nssm set` (arranque automático, reinicio a los 10 s, logs a logs\ rotados
  a los 10 MB). Nueva opción -SoloMostrar que imprime los comandos sin ejecutarlos ni exigir
  administrador. Si un ajuste falla, quita el servicio a medio configurar.
- desinstalar-servicio.ps1: usa `nssm stop` y `nssm remove`.
- Se elimina la plantilla XML de WinSW.
- DEPLOY.md: pasos con NSSM, sección para administrar el servicio, aviso de usar la
  compilación 2.24-101 en Windows 10/11 y Server 2019/2022 (la 2.24 estable puede no arrancar),
  y limpieza de logs rotados (NSSM no limita cuántos conserva). Ya no se requiere .NET Framework.
- CLAUDE.md y README de la API actualizados.

Sin probar aún en Windows: solo se comprobó la sintaxis y los comandos generados (-SoloMostrar).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-13 08:55:29 -05:00

21 KiB

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:
    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:
    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:
    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:
    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/.