# ITCA_API_SAP_WMS (.NET) — build, publish y despliegue en IIS API de integración WMS ↔ SAP Business One (Service Layer + HANA directo), **ASP.NET Core 3.1**. Para contexto de por qué este proyecto sigue activo (hay una reescritura en Python en paralelo) ver [`CLAUDE.md`](./CLAUDE.md). ## Requisitos para compilar - .NET Core 3.1 SDK. - Cliente HANA .NET instalado en `C:\Program Files\sap\hdbclient\dotnetcore\v2.1\Sap.Data.Hana.Core.v2.1.dll` (ruta hardcodeada vía `HintPath` en los `.csproj` de `Net.Data` y `Net.Business.Services` — sin esto no compila ningún proyecto que dependa de HANA). - **Compilar desde macOS/Linux** (forma simple): pasar la ruta de la DLL con `-p:HanaDll=/Sap.Data.Hana.Core.v2.1.dll` a `dotnet build`/`publish` (los `.csproj` usan esa propiedad si está definida). Forma alternativa: el `HintPath` es relativo (6 niveles hacia arriba + `Program Files\sap\...`). Se puede compilar copiando el repo a `/a/b/c/d/repo/` y poniendo la DLL en `/Program Files/sap/hdbclient/dotnetcore/v2.1/` — así el `HintPath` resuelve sin tocar los `.csproj`. (Pasar `AssemblySearchPaths` por línea de comandos NO sirve: rompe la resolución del framework.) ## Configuración de HANA (`appsettings.json`, sección `Hana`) Las credenciales y el esquema de HANA ya **no** están fijos en el código; hay un único lugar, `LocalData.Configure(...)` (`Net.Data/LocalData.cs`), que se llama desde `Startup`: ```json "Hana": { "server": "192.168.0.13:30015", "user": "", "password": "", "schema": "" } ``` - `schema` es opcional: si no está, se usa `SapServiceLayer:company` (si no hay ninguno de los dos la app no arranca, no hay valor por defecto), de modo que lecturas por HANA (`CALL ""."ITCA_..."`) y escrituras por Service Layer apunten siempre a la misma compañía. - **Si falta la sección `Hana` la app no arranca** (`InvalidOperationException` clara en `LocalData.Configure`). Al desplegar sobre un sitio existente hay que agregarla a su `appsettings.json` **antes** de iniciar el pool. ## Correr localmente ``` dotnet run --project Net.Business.Services ``` Swagger queda en la raíz (`RoutePrefix = ""` en `Startup.cs`): `http://localhost:/`. ## Generar el publish para IIS ``` dotnet publish Net.Business.Services/Net.Business.Services.csproj -c Release -r win-x64 --self-contained false -o ``` - `-r win-x64 --self-contained false`: genera un `.exe` nativo (apphost) de 64 bits que no incluye el runtime — depende de que el servidor ya tenga el **ASP.NET Core 3.1 Hosting Bundle** instalado (confirmarlo con `dotnet --list-runtimes` en el servidor: debe listar `Microsoft.AspNetCore.App 3.1.x` y `Microsoft.NETCore.App 3.1.x`). - El `.xml` de comentarios (`ITCA_API_SAP_WMS.xml`, que Swagger necesita vía `IncludeXmlComments` en `Startup.cs`) ya se genera solo en Release desde que se sacó la condición `Debug|AnyCPU` del `.csproj` — si en algún momento vuelve a faltar en el output del publish, revisar que esa propiedad (`DocumentationFile`) siga sin estar condicionada a Debug. - `dotnet publish` transforma automáticamente el `processPath` del `web.config` para que apunte al `.exe` publicado — **no** copiar el `web.config` del código fuente (ese apunta a `bin\Debug\...`, es el de desarrollo) a una carpeta de publish. ## Versión y script de publish - La versión sale de ``/``/`` en `Net.Business.Services/Net.Business.Services.csproj` (actual: **2.1.15.0** en `main`); súbela en cada publish. Se usa **semver** (`MAJOR.MINOR.PATCH`, cuarto número del `csproj` siempre `0`): `MAJOR` cambia el contrato de la API, `MINOR` agrega funcionalidad compatible, `PATCH` corrige o limpia sin cambio de contrato (detalle en `CLAUDE.md`). - **Única forma de generar el zip**: `publicar.ps1` (Windows: `.\publicar.ps1`; macOS/Linux con PowerShell 7: `pwsh ./publicar.ps1`). Genera `dist/ITCA_API_SAP_WMS_v.zip` sin `appsettings.json` ni `appsettings.Development.json`; `-Destino ` además copia el zip ahí (p. ej. `-Destino ~/Desktop`). Verifica que `Version`, `AssemblyVersion` y `FileVersion` del `csproj` coincidan. En macOS/Linux define antes `export HanaDll=/Sap.Data.Hana.Core.v2.1.dll`. No hacer el `dotnet publish` a mano. - Control de versiones: rama `main` y etiquetas `vX.Y.Z.W` por versión publicada (ver `CLAUDE.md`). ## Volver a la versión 2.1.7.0 (punto de retorno antes de la limpieza) La etiqueta `v2.1.7.0` (commit `d0118d1`) es el último estado antes de la limpieza de código (rama `limpieza-2.1.8.0`, versión 2.1.8.0). Está en el remoto `origin` y en el repositorio local. ``` git fetch --tags # si la etiqueta no está en tu copia local git tag -l "v2.1.*" # ver las etiquetas disponibles git diff v2.1.7.0 --stat # qué cambió respecto a 2.1.7.0 git checkout v2.1.7.0 # ver/compilar exactamente esa versión (solo lectura) git switch -c recuperar-2.1.7.0 v2.1.7.0 # trabajar desde ahí en una rama nueva ``` - Para **volver a publicar 2.1.7.0**: `git checkout v2.1.7.0` y generar el publish como en "Versión y script de publish" (`AssemblyVersion` ya vale 2.1.7.0 en ese commit). Luego `git switch main` para regresar. - Para **deshacer** la limpieza ya unida a `main`: como fue avance directo (no hay commit de merge), usar `git revert --no-commit v2.1.7.0..HEAD` y confirmar (no reescribe el historial). Evitar `git reset --hard` y `git push --force` sobre `main`. - La limpieza se unió a `main` el 2026-10-20 (avance directo, sin commit de merge), por lo que ya no hay una rama aparte; la etiqueta `v2.1.7.0` sigue siendo el punto de retorno. ## Logs de integración - **`B_Log`** (controllers): en cada llamada guarda en `ITC_WMS_SAP.ITCA_LOG_INTEGRACION` el request del WMS (`TRAMA_ORIGEN`) y la respuesta de la API (`TRAMA_RESPUESTA`), con `PROCESO` (Picking, Recibo...), `NRO_ORDEN`, `DOCENTRY`, `DOCNUMSAP` y `DOCENTRYSAP`. - **Log de errores del Service Layer** (desde 2.1.5.0): cuando SAP rechaza un envío con cuerpo JSON (`PostAsync`, `PatchNoContentAsync`) se guarda en esa misma tabla el JSON enviado y el mensaje de SAP, con `PROCESO = 'ServiceLayer Error'`, `NRO_ORDEN` = ruta del recurso y `DOCENTRY` = `BaseEntry` del documento base. Solo en error; no cambia la respuesta de la API. - Ambos usan el esquema fijo `ITC_WMS_SAP` y la tabla de `Hana:logTable` (por defecto `ITCA_LOG_INTEGRACION` para QA/Desarrollo; en **producción** poner `ITCA_LOG_INTEGRACION_PROD`) (columnas `PROCESO`, `NRO_ORDEN` y `DOCENTRY` son `VARCHAR(100)`). - Consulta de ejemplo: `SELECT * FROM "ITC_WMS_SAP"."ITCA_LOG_INTEGRACION" WHERE "PROCESO" = 'ServiceLayer Error' ORDER BY "FECHA" DESC, "HORA" DESC` ## Desplegar/actualizar un sitio en IIS ### Si el sitio YA corre desde una carpeta de publish (actualización de versión) 1. Detener el Application Pool del sitio (libera los locks de los `.dll`/`.exe`). 2. Copiar el contenido del nuevo publish **sin tocar** `appsettings.json` ni `web.config` existentes — esos quedan tal cual están configurados en ese servidor/ambiente (pueden tener valores distintos a los del repo, ej. `SapServiceLayer:company` apuntando a una base SAP distinta de QA/prod). 3. Iniciar el Application Pool. ### Si el sitio corre directo desde el código fuente (`bin\Debug\...`) Así corre al menos un ambiente real (`CLONE-SRV-WEB`, ruta física `...\BackEnd\Desarrollo\BackEnd\Net.Business.Services`, `web.config` con `processPath="bin\Debug\netcoreapp3.1\ITCA_API_SAP_WMS.exe"`) — ahí **no** se usa `dotnet publish`, se compila in situ: 1. Detener el Application Pool primero (si no, falla la compilación por archivos en uso). 2. Copiar los `.cs`/`.csproj` que cambiaron a las mismas rutas relativas dentro de la solución en el servidor (no hace falta tocar ningún `.csproj` para agregar archivos nuevos — son proyectos SDK-style con glob automático de `**/*.cs`). 3. Compilar la solución ahí (Visual Studio → Build Solution en Debug, o `dotnet build` desde la carpeta de la solución). 4. Iniciar el Application Pool. ### Checklist de configuración del Application Pool (IIS Manager, una sola vez por pool nuevo) - **.NET CLR Version**: Sin código administrado (No Managed Code). - **Configuración avanzada → Habilitar aplicaciones de 32 bits**: **False** (el `.exe`/DLLs son de 64 bits — con esto en True da `500.32 Failed to load .NET Core host`, bitness mismatch). - **Configuración avanzada → Cargar perfil de usuario (Load User Profile)**: **True** — sin esto, arranque falla con `ErrorCode '0x8007023e'` (excepción no manejada al iniciar el proceso in-process). - **Permisos de carpeta**: `IIS AppPool\` con **Lectura y ejecución** sobre toda la carpeta del sitio, y **Modificar** sobre la subcarpeta `logs\` (para que `stdoutLogEnabled` pueda escribir si hace falta diagnosticar). ### Diagnóstico de errores de arranque 1. Visor de eventos de Windows → Registros de Windows → Aplicación → buscar el evento de `IIS AspNetCoreModule V2` más reciente — casi siempre trae el error real. 2. Si no alcanza, poner `stdoutLogEnabled="true"` en el `web.config` de esa carpeta y revisar `.\logs\stdout_*.log` después de reciclar el pool. ### Editar `appsettings.json` de un sitio ya desplegado No hace falta detener el Application Pool para editarlo (no queda bloqueado como los binarios) — pero conviene **reciclar el pool** después de guardar, para garantizar que el valor nuevo se relea (el host usa `Host.CreateDefaultBuilder`, que ya trae `reloadOnChange: true` para `appsettings.json`, pero no vale la pena depender de eso para un cambio puntual). El nombre de la base SAP (`CompanyDB`) se configura en `SapServiceLayer:company` dentro de `appsettings.json` — lo lee `ConnectionServiceLayer.cs` (`_company = configuration["SapServiceLayer:company"]`) para armar el body del `Login` contra el Service Layer. ## Riesgos de seguridad heredados (documentados, no corregidos sin coordinar antes) - `appsettings.json` tiene credenciales reales en texto plano (SAP Service Layer, **HANA `B1ADMIN`**, semilla JWT). Está en `.gitignore` y no se versiona (solo `appsettings.example.json`); no subirlo a ningún repositorio. - `B1ADMIN` es un usuario administrador de SAP B1; la API solo necesita ejecutar los `ITCA_*` y leer algunas tablas. Conviene un usuario HANA de permisos mínimos y rotar la contraseña (estuvo en texto plano en el código y en el historial git del repo antiguo archivado). - El cliente HTTP `bypass-ssl-validation` (usado por `ConnectionServiceLayer`) acepta cualquier certificado TLS — bypass de validación de certificado hacia el Service Layer, igual criterio que `SERVICE_LAYER_VERIFY_SSL=false` en la reescritura Python. (Se quitó el método `ValidarCertificado`, que no se usaba.)