# Porte de funcionalidad nueva desde la reescritura Python Este documento (y los dos que lo acompañan, [`SOLICITUD_TRANSFERENCIA.md`](./SOLICITUD_TRANSFERENCIA.md) y [`PICKING.md`](./PICKING.md)) son la guía para agregar a **este proyecto `.NET Core 3.1`** la funcionalidad de negocio nueva que se diseñó e implementó primero en la reescritura Python (`/Users/george/Documents/Desarrollo/ITCG/itca_api_sap_wms_python`), porque este `.NET` va a seguir corriendo en producción (IIS, Windows) más tiempo del previsto originalmente. **La lógica de negocio ya está validada en Python — acá es 100% traducción de lenguaje, no rediseño.** El contrato HTTP (rutas, shapes de request/response, status codes) debe quedar idéntico al de la API Python, porque ambas corren sobre el mismo SAP Business One y el mismo WMS las va a consumir. > Alcance confirmado: **solo la funcionalidad nueva** (sin equivalente en > este `.NET` hoy). No se portan acá las correcciones de bugs de contrato que > sí se hicieron en la reescritura Python (ej. `201` real en los `POST` de > creación, `400` explícito en Inventario/Recibo en vez de éxito falso) — > quedan fuera de este trabajo salvo que se decida lo contrario más adelante. ## Qué hay que agregar 1. **Módulo SolicitudTransferencia completo** (no existe ningún controller ni repositorio hoy) — ver [`SOLICITUD_TRANSFERENCIA.md`](./SOLICITUD_TRANSFERENCIA.md). 2. **Picking diurno/nocturno** — campo `Notificacion` en el `POST /api/Picking` existente + nuevo `POST /api/Picking/HojaRuta/{docEntry}/Cerrar` — ver [`PICKING.md`](./PICKING.md). ## Dónde vive cada pieza hoy (mapa de archivos .NET) | Concepto | Archivo | |---|---| | Controllers existentes | `Net.Business.Services/Controllers/*.cs` | | Repositorios de negocio (WMS) | `Net.Data/WMS/*/` | | Repositorios de Service Layer (SAP) | `Net.Data/ServicesLayer/*/` | | Cliente HTTP de Service Layer | `Net.Connection.ServiceLayer/ConnectionServiceLayer.cs` | | DTOs (shape del request HTTP) | `Net.Business.DTO/**/Dto_*.cs` | | Entidades de negocio | `Net.Business.Entities/**/BE_*.cs` | | Wrapper de inyección de dependencias | `Net.Data/DependencyInjection/{IRepositoryWrapper,RepositoryWrapper}.cs` | | Wrapper de resultado (`BE_ResultadoTransaccion`) | `Net.Business.Entities/ResultadoTransaccion/BE_ResultadoTransaccion.cs` + `Net.CrossCotting/ResultadoTransaccion.cs` | | Logging de auditoría | `Net.Business.Services/Log/B_Log.cs` (se llama desde cada controller, después del repositorio) | | Handler global de excepciones | `Net.Business.Services/Extensions/ExceptionMiddlewareExtensions.cs` | Patrón a seguir para cada pieza nueva (igual que `TransferenciaController`/ `TransferenciaRepository`/`StockTransferRepository`): DTO → `RetornaModelo()` → Entidad → Repositorio de negocio (usa `ConnectionServiceLayer` + helpers de HANA) → Controller (valida, llama al repo, llama a `B_Log.Add`, decide el status code). ## Gaps de infraestructura compartida a tener en cuenta Estos dos módulos nuevos necesitan cosas que `ConnectionServiceLayer.cs` hoy **no soporta**, y hay que agregarlas: 1. **Headers extra en PATCH.** `SolicitudTransferencia` necesita mandar `B1S-ReplaceCollectionsOnPatch: true` en el PATCH a SAP. Ningún método de `ConnectionServiceLayer` (`PostAsync`, `PatchAsyncSBA`, etc.) acepta headers adicionales hoy — todos hardcodean `Accept`/`Content-Type`/`Cookie` únicamente. Hay que agregar un parámetro opcional (ej. `Dictionary extraHeaders = null`) a `PatchAsyncSBA`. 2. **GET de un solo recurso (no colección).** `SolicitudTransferencia` necesita `GET InventoryTransferRequests({docEntry})` (un objeto, no una lista `.value`). `ConnectionServiceLayer.GetAsync` ya tiene un parámetro `dinamico` que deserializa `T` directo sin esperar el wrapper `{ "value": [...] }` (`ConnectionServiceLayer.cs:363-373`) — **reusar esa ruta** (`dinamico: true`) en vez de escribir un método nuevo. 3. **Acción `/Close` sin body.** Ni `SolicitudTransferencia` ni `Picking/HojaRuta` tienen hoy un método para `POST {Recurso}({docEntry})/Close` con body vacío — el único análogo es `StockTransferRepository.StockTransfer` (`Net.Data/ServicesLayer/StockTransfer/StockTransferRepository.cs`), que sí manda body. Hay que agregar un método nuevo (ej. `CloseAsyncSBA`) o un overload de `PostAsync` que acepte `obj: null`/`{}`. ## Bugs e inconsistencias encontrados en el código `.NET` existente Auditado puntualmente el código que estos dos módulos nuevos van a tocar o reusar (`ConnectionServiceLayer.cs`, `HelperProcesoPiciking.cs`, `BE_ResultadoTransaccion.cs`). No es una auditoría completa del proyecto — solo lo relevante para este trabajo. ### 1. `BE_ResultadoTransaccion.NombreEstacion` — setter con recursión infinita (bug serio, no relacionado al porte pero lo tocan ambos módulos nuevos) **Archivo**: `Net.Business.Entities/ResultadoTransaccion/BE_ResultadoTransaccion.cs`, línea 36 (propiedad `NombreEstacion`). ```csharp public string NombreEstacion { get => System.Environment.MachineName; set => NombreEstacion = System.Environment.MachineName; } ``` El `set` asigna `NombreEstacion = ...`, lo que vuelve a invocar al propio setter — recursión infinita. Si alguna vez se invoca ese setter (por ejemplo, si algo deserializa un `BE_ResultadoTransaccion` con `JsonConvert.DeserializeObject` y el JSON trae la propiedad `NombreEstacion`/`nombreEstacion`, o si cualquier código hace `objeto.NombreEstacion = "algo"` directamente), el proceso revienta con `StackOverflowException` — una excepción que **.NET no puede atrapar con try/catch**, tumba el proceso entero (el worker de IIS). Hoy no se dispara porque nada construye `BE_ResultadoTransaccion` por deserialización ni asigna esa propiedad directamente (todo pasa por `ResultadoTransaccion.GetInformacionResultado(...)`, que no la toca). Pero al escribir el código nuevo para SolicitudTransferencia/HojaRuta, **nunca hacer `.NombreEstacion = ...` a mano ni deserializar un `BE_ResultadoTransaccion` desde JSON** — usar siempre `GetInformacionResultado(...)`. **Solución propuesta**: la propiedad solo necesita ser de lectura. ```csharp public string NombreEstacion => System.Environment.MachineName; ``` (o, si hace falta que el setter exista por compatibilidad con algo que no vimos en esta auditoría puntual, que el setter no haga nada: `set { }`). ### 2. `ConnectionServiceLayer.PatchAsyncSBA` nunca marca éxito en un `204` **Archivo**: `Net.Connection.ServiceLayer/ConnectionServiceLayer.cs`, método `PatchAsyncSBA`, líneas 739-856 (el caso puntual, líneas 840-848). Con un `204 No Content` real de SAP, el código no entra en la rama de éxito (la que solo cubre `200`/`201`, líneas 788-796) ni en ninguna rama de error — el bloque `if (response_.StatusCode == NoContent) { ... }` (línea 840) está con el cuerpo comentado, así que cae directo a `return default(T);` (línea 848), o sea `null`. Cualquier caller que acceda a una propiedad del resultado (`.Exito`, `.DocEntry`, etc.) revienta con `NullReferenceException`. Ya es un bug conocido y documentado en el proyecto Python (`CLAUDE.md`, regla 4) — ahí se corrigió tratando un `204` real como éxito explícito. **Importante**: confirmado que **nadie llama a este método hoy** (`grep` de `PatchAsyncSBA` en todo el proyecto no da otro resultado que su propia definición) — es seguro modificarlo directamente, no hace falta duplicarlo para no romper nada en producción. **Solución propuesta**: al construir el repositorio de SolicitudTransferencia, modificar `PatchAsyncSBA` para que un `204` (o `200`) se trate como éxito real, sin intentar deserializar un body que puede venir vacío — por ejemplo, devolviendo un `bool`/un objeto liviano de éxito en vez de `T` deserializado a la fuerza, o agregando un chequeo explícito antes del `return default(T)` final. ### 3. `ConnectionServiceLayer.PatchAsyncSBA` — un `401` con `error.code` distinto de `"301"` también devuelve `null` silenciosamente **Archivo**: `Net.Connection.ServiceLayer/ConnectionServiceLayer.cs`, método `PatchAsyncSBA`, líneas 797-822. Dentro de la rama `401`, si `NumError < 2` y el código de error de SAP no es `"301"` (el que dispara relogin + reintento), no hay ningún `goto band` ni `return` — el flujo simplemente sale del `if/else if` (porque ya entró a la rama `401`, la siguiente condición es `else if` y no se evalúa) y cae en el `return default(T);` del final del método. Mismo síntoma que el bug anterior (`null` silencioso, no un error explícito) pero por una causa distinta. **Solución propuesta**: agregar un `else` final dentro de la rama `401` que construya y devuelva un resultado de error explícito, en vez de dejar que el flujo caiga al `return default(T)` genérico. ### 4. `ConnectionServiceLayer` — `throw` muerto después de `return` en 3 métodos **Archivo**: `Net.Connection.ServiceLayer/ConnectionServiceLayer.cs`, métodos `CancelAsyncSBA` (líneas 621-625), `DeleteAsyncSBA` (líneas 733-737) y `PatchAsyncSBA` (líneas 851-855): ```csharp catch (Exception ex) { return default(T); throw new Exception(ex.Message.ToString()); } ``` El `throw` es código inalcanzable (el compilador de C# debería marcarlo con el warning `CS0162`) — la excepción original queda silenciada sin loguear ni propagar, y el caller recibe `null` sin ninguna pista de qué pasó. **Solución propuesta**: sacar el `throw` muerto y decidir explícitamente el comportamiento — si se quiere tragar el error y devolver `null`/un resultado de error (dejar solo el `return`), o repropagar la excepción real (`throw;`, sin el `return` antes). Para los métodos nuevos de SolicitudTransferencia, no repetir este patrón: loguear o envolver el error en el `BE_ResultadoTransaccion` de salida, igual que hace el resto del proyecto (ver `TransferenciaRepository.Create`, que atrapa la excepción y la mete en `GetInformacionResultado(ex.Message, entidad)`). ### 5. `ConnectionServiceLayer.Login()` y `.LoginPost()` — código duplicado **Archivo**: `Net.Connection.ServiceLayer/ConnectionServiceLayer.cs`, `Login()` (líneas 44-130) y `LoginPost()` (líneas 131-218). Son copias casi idénticas línea por línea — la única diferencia real es un parámetro `Language` que está comentado en `LoginPost`. No es un bug funcional, es una inconsistencia de mantenimiento (dos lugares que hay que tocar a mano si cambia el login). No bloquea el porte de la funcionalidad nueva — se deja solo documentado por si se decide refactorizar en el futuro. **Solución propuesta**: unificar en un solo método (`LoginPost` ya tiene el parámetro `Language` listo para habilitarse si hiciera falta). ### 6. `HelperProcesoPiciking.Set` — SQL injection real por interpolación de string **Archivo**: `Net.Data/HelperData/ProcesoPiciking/HelperProcesoPiciking.cs`, ambas sobrecargas de `Set` (líneas 14 y 28): ```csharp cmd.CommandText = $"CALL\"B1_ARDI_PROD\".\"ITCA_WMS_SAP_SetProcesoPicking\"('{Proceso}', {IdEstado}, {DocEntry}, '', '', '{CodMotivo}', '{Code}')"; ``` `Proceso` y `CodMotivo` vienen directo del body del request HTTP (`entidad.Proceso`, `entidad.CodMotivo` en `PickingRepository.cs:96` y `:374`) e, interpolados así, son un vector de SQL injection real — mismo patrón de vulnerabilidad ya documentado y confirmado en el módulo Chatbot del `.NET` (`CLAUDE.md` del proyecto Python, sección "Pendiente"). Este método es justo el que hay que tocar para aislar la actualización de estado (ver [`PICKING.md`](./PICKING.md)), así que es un buen momento para corregirlo de una vez en vez de solo copiarlo. **Solución propuesta**: parametrizar con `HanaParameter`/placeholders en vez de interpolar el string, por ejemplo: ```csharp cmd.CommandText = "CALL \"B1_ARDI_PROD\".\"ITCA_WMS_SAP_SetProcesoPicking\"(?, ?, ?, ?, ?, ?, ?)"; cmd.Parameters.Add(new HanaParameter("1", Proceso)); cmd.Parameters.Add(new HanaParameter("2", IdEstado)); cmd.Parameters.Add(new HanaParameter("3", DocEntry)); cmd.Parameters.Add(new HanaParameter("4", HoraInicio?.ToString() ?? "")); cmd.Parameters.Add(new HanaParameter("5", HoraFin?.ToString() ?? "")); cmd.Parameters.Add(new HanaParameter("6", CodMotivo)); cmd.Parameters.Add(new HanaParameter("7", Code)); ``` (Ajustar la sintaxis exacta de binding de parámetros al driver `Sap.Data.Hana` realmente instalado — confirmar la forma correcta de parámetros posicionales de ese driver ADO.NET antes de aplicar, no asumida en esta auditoría.)