using System.Net.Http.Json; using Domain.Dtos.Stock; using Domain.Generics; using Transversal.Services; public class StockScanService : IStockScanService { private readonly HttpClient _http; public StockScanService(HttpClient http) { _http = http; } public async Task ParseAndMatchAsync(string rawInput, int locationId) { if (string.IsNullOrWhiteSpace(rawInput)) return null; try { var parsed = Gs1CodeParser.Parse(rawInput); var raw = rawInput.Trim(); bool hasParsedAis = !string.IsNullOrWhiteSpace(parsed.Lot) || parsed.ExpirationDate.HasValue || !string.IsNullOrWhiteSpace(parsed.Serial) || !string.IsNullOrWhiteSpace(parsed.Variant); // incluir (22) string? gtinToSend = parsed.Gtin ?? parsed.Variant; // (22) como fallback if (gtinToSend is null && !hasParsedAis && IsPlainCode(raw)) gtinToSend = raw; // código plano tipeado (factory/regulatory) // 3. Armar parámetros de búsqueda var sp = new StockItemParsedSearchParams { Gtin = gtinToSend, Batch = string.IsNullOrWhiteSpace(parsed.Lot) ? null : parsed.Lot, Expiration = parsed.ExpirationDate.HasValue ? DateOnly.FromDateTime(parsed.ExpirationDate.Value) : null, Serial = string.IsNullOrWhiteSpace(parsed.Serial) ? null : parsed.Serial, LocationId = locationId, Page = 1, PageSize = 10 }; // 4. Log para depuración (quitar en producción) Console.WriteLine($"[ParseAndMatchAsync] Gtin={sp.Gtin}, Batch={sp.Batch}, Exp={sp.Expiration}, Serial={sp.Serial}, Loc={sp.LocationId}"); // 5. Llamar a la API var resp = await _http.PostAsJsonAsync("/api/lsstockscan/search-parsed", sp); if (!resp.IsSuccessStatusCode) { var err = await resp.Content.ReadAsStringAsync(); Console.WriteLine($"[ParseAndMatchAsync] API devolvió error {resp.StatusCode}: {err}"); return null; } // 6. Leer resultado var pr = await resp.Content.ReadFromJsonAsync>(); var first = pr?.Items?.FirstOrDefault(); if (first == null) { Console.WriteLine("[ParseAndMatchAsync] No se encontró ningún ítem que coincida."); return null; } // 7. Mapear a DTO de selección return new StockItemSelectionDto { StockItemId = first.StockItemId, ProductId = first.ProductId, ProductName = first.ProductName, Batch = first.Batch ?? string.Empty, Expiration = first.Expiration?.ToDateTime(TimeOnly.MinValue), Quantity = first.AvailableQty, LocationId = first.LocationId ?? 0 }; } catch (Exception ex) { Console.WriteLine($"[ParseAndMatchAsync] Error inesperado: {ex}"); throw; } } private static bool IsPlainCode(string s) { // sin FNC1 ($), sin espacios y sin prefijos AI típicos if (s.Contains('$') || s.Contains((char)29) || s.Contains(' ')) return false; // evita raws que empiezan como AIs "01","10","17","21","22" var prefix = s.Length >= 2 ? s[..2] : s; if (prefix is "01" or "10" or "11" or "17" or "21" or "22") return false; // permite letras/dígitos y algunos separadores comunes return s.All(c => char.IsLetterOrDigit(c) || c is '-' or '/' or '_'); } }