Files

210 lines
11 KiB
Markdown

# 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=<ruta>/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
`<raiz>/a/b/c/d/repo/` y poniendo la DLL en
`<raiz>/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": "<usuario>",
"password": "<password>",
"schema": "<opcional>"
}
```
- `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 "<esquema>"."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:<puerto>/`.
## Generar el publish para IIS
```
dotnet publish Net.Business.Services/Net.Business.Services.csproj -c Release -r win-x64 --self-contained false -o <carpeta-salida>
```
- `-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 `<Version>`/`<AssemblyVersion>`/`<FileVersion>` 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<MAJOR.MINOR.PATCH>.zip` sin `appsettings.json` ni
`appsettings.Development.json`; `-Destino <carpeta>` 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=<ruta>/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\<NombreDelPool>` 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.)