Mariano Z. пре 9 месеци
комит
7551b4d824

+ 62 - 0
.dockerignore

@@ -0,0 +1,62 @@
+# Archivos y carpetas de desarrollo
+.vs/
+.vscode/
+.idea/
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Carpetas de build
+[Bb]in/
+[Oo]bj/
+[Oo]ut/
+[Dd]ebug/
+[Rr]elease/
+x64/
+x86/
+build/
+msbuild.log
+msbuild.err
+msbuild.wrn
+
+# Archivos temporales
+*.log
+*.logs
+*.tmp
+*.swp
+
+# Archivos de Git y control de versiones
+.git/
+.gitignore
+.github/
+
+# Archivos de Docker
+Dockerfile
+.dockerignore
+docker-compose*.yml
+
+# Archivos de documentación y ejemplos
+README.md
+LICENSE
+*.md
+docs/
+examples/
+
+# Archivos específicos de entorno
+.env
+*.env
+appsettings.*.json
+
+# Archivos de pruebas
+[Tt]est*/
+*.Tests/
+TestResults/
+
+# Paquetes NuGet
+*.nupkg
+packages/
+
+# Archivos del sistema operativo
+.DS_Store
+Thumbs.db

+ 161 - 0
.gitignore

@@ -0,0 +1,161 @@
+# Visual Studio files
+.vs/
+.vscode/
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+build/
+bld/
+[Bb]in/
+[Oo]bj/
+[Oo]ut/
+msbuild.log
+msbuild.err
+msbuild.wrn
+
+# ReSharper
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Visual Studio cache/options directory
+.vs/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+artifacts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# VS Code directories
+.vscode/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Mono auto generated files
+mono_crash.*
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Logs
+*.log
+*.logs


+ 46 - 0
Models/ErrorCodes.cs

@@ -0,0 +1,46 @@
+namespace SoapService.Models;
+
+public static class ErrorCodes
+{
+    // Warnings (700-1000)
+    public static class Warnings
+    {
+        public const int DatosPersonaRegularizar = 701;
+        public const string DatosPersonaRegularizarMensaje = "Datos de Persona a regularizar";
+
+        public const int DocumentoHurtadoExtraviado = 702;
+        public const string DocumentoHurtadoExtraviadoMensaje = "Documento Denunciado como Hurtado/Extraviado el ../../....";
+
+        public const int HojaInteriorHurtada = 703;
+        public const string HojaInteriorHurtadaMensaje = "La Hoja interior Serie {0}, Número {1}, figura como hurtada";
+
+        public const int DocumentoNoUltimo = 704;
+        public const string DocumentoNoUltimoMensaje = "El documento presentado no es el último gestionado";
+
+        public const int ValoresNoCoinciden = 705;
+        public const string ValoresNoCoincidenMensaje = "Los valores del material no coinciden con el último documento digitalizado ingresado. Por favor, verifique los datos y digite nuevamente si corresponde.";
+    }
+
+    // Errores Leves (> 1000)
+    public static class ErroresLeves
+    {
+        public const int PersonaInexistente = 1001;
+        public const string PersonaInexistenteMensaje = "Persona inexistente";
+
+        public const int LimiteConsultasExcedido = 1002;
+        public const string LimiteConsultasExcedidoMensaje = "Límite de consultas excedido";
+
+        public const int NumeroCedulaAnulado = 1003;
+        public const string NumeroCedulaAnuladoMensaje = "Número de cédula anulado";
+    }
+
+    // Errores Graves (> 10000)
+    public static class ErroresGraves
+    {
+        public const int ParametrosIncorrectos = 10001;
+        public const string ParametrosIncorrectosMensaje = "Parámetros incorrectos";
+
+        public const int ConsultaNoCompletada = 10002;
+        public const string ConsultaNoCompletadaMensaje = "No se pudo completar la consulta";
+    }
+}

+ 16 - 0
Models/ImagenDigital.cs

@@ -0,0 +1,16 @@
+using System.Runtime.Serialization;
+
+namespace SoapService.Models;
+
+[DataContract(Namespace = "http://dnic.gub.uy/")]
+public class ImagenDigital
+{
+    [DataMember(Order = 0)]
+    public string Foto { get; set; }
+
+    [DataMember(Order = 1)]
+    public int LargoBytes { get; set; }
+
+    [DataMember(Order = 2)]
+    public int TipoImagen { get; set; }
+}

+ 16 - 0
Models/Mensaje.cs

@@ -0,0 +1,16 @@
+using System.Runtime.Serialization;
+
+namespace SoapService.Models;
+
+[DataContract(Namespace = "http://dnic.gub.uy/")]
+public class Mensaje
+{
+    [DataMember(Order = 0)]
+    public int CodMensaje { get; set; }
+
+    [DataMember(Order = 1)]
+    public string Descripcion { get; set; }
+
+    [DataMember(Order = 2)]
+    public string DatoExtra { get; set; }
+}

+ 49 - 0
Models/ObjPersona.cs

@@ -0,0 +1,49 @@
+using System.Runtime.Serialization;
+
+namespace SoapService.Models;
+
+[DataContract(Namespace = "http://dnic.gub.uy/")]
+public class ObjPersona
+{
+    [DataMember(Order = 0)]
+    public string CodTipoDocumento { get; set; }
+
+    [DataMember(Order = 1)]
+    public string NroDocumento { get; set; }
+
+    [DataMember(Order = 2)]
+    public string Nombre1 { get; set; }
+
+    [DataMember(Order = 3)]
+    public string Nombre2 { get; set; }
+
+    [DataMember(Order = 4)]
+    public string PrimerApellido { get; set; }
+
+    [DataMember(Order = 5)]
+    public string SegundoApellido { get; set; }
+
+    [DataMember(Order = 6)]
+    public string ApellidoAdoptivo1 { get; set; }
+
+    [DataMember(Order = 7)]
+    public string ApellidoAdoptivo2 { get; set; }
+
+    [DataMember(Order = 8)]
+    public int Sexo { get; set; }
+
+    [DataMember(Order = 9)]
+    public string FechaNacimiento { get; set; }
+
+    [DataMember(Order = 10)]
+    public int CodNacionalidad { get; set; }
+
+    [DataMember(Order = 11)]
+    public string NombreEnCedula { get; set; }
+
+    [DataMember(Order = 12)]
+    public int IdSolicitud { get; set; }
+
+    [DataMember(Order = 13)]
+    public int IdRespuesta { get; set; }
+}

+ 28 - 0
Models/ParamObtDocDigitalizado.cs

@@ -0,0 +1,28 @@
+using System.Runtime.Serialization;
+
+namespace SoapService.Models;
+
+[DataContract(Namespace = "http://dnic.gub.uy/")]
+public class ParamObtDocDigitalizado
+{
+    [DataMember(Name = "ClaveAcceso1", Order = 0)]
+    public string ClaveAcceso1 { get; set; }
+
+    [DataMember(Name = "ClaveAcceso2", Order = 1)]
+    public string ClaveAcceso2 { get; set; }
+
+    [DataMember(Name = "NroDocumento", Order = 2)]
+    public string NroDocumento { get; set; }
+
+    [DataMember(Name = "NroIdentificacion", Order = 3)]
+    public int NroIdentificacion { get; set; }
+
+    [DataMember(Name = "NroSerie", Order = 4)]
+    public string NroSerie { get; set; }
+
+    [DataMember(Name = "Organismo", Order = 5)]
+    public string Organismo { get; set; }
+
+    [DataMember(Name = "TipoDocumento", Order = 6)]
+    public string TipoDocumento { get; set; } = "DO";
+}

+ 19 - 0
Models/ResultObtDocDigitalizado.cs

@@ -0,0 +1,19 @@
+using System.Runtime.Serialization;
+
+namespace SoapService.Models;
+
+[DataContract(Namespace = "http://dnic.gub.uy/")]
+public class ResultObtDocDigitalizado
+{
+    [DataMember(Order = 0)]
+    public ObjPersona Persona { get; set; }
+
+    [DataMember(Order = 1)]
+    public ImagenDigital[] Imagenes { get; set; }
+
+    [DataMember(Order = 2)]
+    public Mensaje[]? Warnings { get; set; }
+
+    [DataMember(Order = 3)]
+    public Mensaje[] Errores { get; set; }
+}

+ 43 - 0
Program.cs

@@ -0,0 +1,43 @@
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using SoapCore;
+using SoapService.Services;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Agregar servicios al contenedor
+builder.Services.AddSoapCore();
+builder.Services.TryAddSingleton<IWsServicioDeInformacion, WsServicioDeInformacion>();
+builder.Services.AddControllers();
+builder.Services.AddEndpointsApiExplorer();
+
+// Configurar Logging
+builder.Logging.AddConsole();
+builder.Logging.AddDebug();
+
+// Configurar URL y Kestrel
+builder.WebHost.UseKestrel(options =>
+{
+    options.ListenAnyIP(5050);
+});
+
+var app = builder.Build();
+
+// Configurar el pipeline HTTP
+app.UseHttpsRedirection();
+app.UseRouting();
+
+// Configurar endpoints SOAP
+app.UseEndpoints(endpoints =>
+{
+    endpoints.UseSoapEndpoint<IWsServicioDeInformacion>(
+        "/WsServicioDeInformacion.svc",
+        new SoapEncoderOptions(),
+        SoapSerializer.DataContractSerializer);
+
+    endpoints.UseSoapEndpoint<IWsServicioDeInformacion>(
+        "/WsServicioDeInformacion.asmx",
+        new SoapEncoderOptions(),
+        SoapSerializer.XmlSerializer);
+});
+
+app.Run();

+ 41 - 0
Properties/launchSettings.json

@@ -0,0 +1,41 @@
+{
+  "$schema": "http://json.schemastore.org/launchsettings.json",
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://localhost:28336",
+      "sslPort": 44394
+    }
+  },
+  "profiles": {
+    "http": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "applicationUrl": "http://localhost:5032",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "https": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "applicationUrl": "https://localhost:7249;http://localhost:5032",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}

+ 90 - 0
README.md

@@ -0,0 +1,90 @@
+# Servicio de Simulación de Documentos Digitalizados
+
+Este documento explica cómo utilizar el servicio de simulación de documentos digitalizados para fines educativos. El servicio genera información ficticia de personas basada en números de documentos de identidad (cédulas).
+
+## Características principales
+
+- Generación de datos personales ficticios deterministas (siempre los mismos para la misma cédula)
+- Simulación de errores y advertencias controladas mediante patrones en los números de cédula
+- Generación de edades específicas basadas en los últimos dígitos de la cédula
+
+## Cómo generar errores y advertencias específicos
+
+El servicio utiliza los **dos primeros dígitos** de la cédula para determinar si ocurre un error o una advertencia:
+
+| Dos primeros dígitos | Resultado generado                           |
+| -------------------- | -------------------------------------------- |
+| 11                   | Error - Persona inexistente                  |
+| 12                   | Error - Límite de consultas excedido         |
+| 13                   | Error - Número de cédula anulado             |
+| 14                   | Advertencia - Datos de persona a regularizar |
+| 15                   | Advertencia - Documento hurtado o extraviado |
+| Otros                | Sin errores ni advertencias                  |
+
+Por ejemplo, si consulta la cédula `11.234.567-8`, el servicio devolverá un error indicando que la persona no existe.
+
+## Cómo obtener cédulas con edades específicas
+
+La edad de la persona generada se determina por los **últimos dos dígitos** de la cédula:
+
+- Para una persona de 25 años: use una cédula que termine en "25"
+- Para una persona de 18 años: use una cédula que termine en "18"
+- Para una persona de 65 años: use una cédula que termine en "65"
+
+Por ejemplo, la cédula `4.321.025-9` generará una persona que tiene 25 años.
+
+## Ejemplos de uso
+
+### Ejemplos de cédulas que generan errores:
+
+- `11.234.567-8`: Error - Persona inexistente
+- `12.345.678-9`: Error - Límite de consultas excedido
+- `13.456.789-0`: Error - Número de cédula anulado
+
+### Ejemplos de cédulas que generan advertencias:
+
+- `14.567.890-1`: Sin error, con advertencia - Datos a regularizar
+- `15.456.789-0`: Sin error, con advertencia - Documento hurtado/extraviado
+- `56.789.123-4`: Sin error, sin advertencia (no comienza con 11-15)
+
+### Ejemplos de cédulas con edades específicas:
+
+- `4.321.018-9`: Persona de 18 años
+- `5.678.930-3`: Persona de 30 años
+- `6.789.045-1`: Persona de 45 años
+- `7.890.175-8`: Persona de 75 años
+
+### Combinando características:
+
+- `15.467.825-7`: Persona de 25 años con advertencia de documento hurtado
+- `14.418.965-3`: Persona de 65 años con advertencia de datos a regularizar
+
+## Otros datos generados
+
+Además de la edad, el servicio genera determinísticamente para cada cédula:
+
+- Nombres y apellidos
+- Sexo (basado en el número de cédula)
+- Fecha de nacimiento (basada en la edad)
+- Nacionalidad
+- Nombres completos para la cédula
+- Identificadores de solicitud y respuesta
+
+## Notas importantes
+
+1. Los datos generados son ficticios y no corresponden a personas reales.
+2. El servicio es determinista: la misma cédula siempre generará los mismos datos.
+3. Para edades de 0-9, asegúrese de incluir un cero delante (por ejemplo, "05" para 5 años).
+4. Para cédulas sin dígitos suficientes, el sistema puede comportarse de manera inesperada.
+
+## Formato de parámetros requeridos
+
+Para realizar una consulta válida, debe proporcionar los siguientes parámetros:
+
+- `NroDocumento`: Número de cédula (requerido)
+- `NroSerie`: Número de serie del documento (requerido)
+- `Organismo`: Código del organismo solicitante (requerido)
+- `ClaveAcceso1`: Clave de acceso (requerida)
+- `TipoDocumento`: Tipo de documento (opcional)
+
+La ausencia de cualquiera de los parámetros requeridos resultará en un error de "Parámetros incorrectos".

+ 11 - 0
Services/IWsServicioDeInformacion.cs

@@ -0,0 +1,11 @@
+using SoapService.Models;
+using System.ServiceModel;
+
+namespace SoapService.Services;
+
+[ServiceContract(Namespace = "http://dnic.gub.uy/")]
+public interface IWsServicioDeInformacion
+{
+    [OperationContract]
+    ResultObtDocDigitalizado ObtDocDigitalizado(ParamObtDocDigitalizado param);
+}

+ 250 - 0
Services/WsServicioDeInformacion.cs

@@ -0,0 +1,250 @@
+using SoapService.Models;
+
+namespace SoapService.Services;
+
+public class WsServicioDeInformacion : IWsServicioDeInformacion
+{
+    private static readonly Dictionary<string, ObjPersona> _personasGeneradas = new();
+    private static readonly Random _random = new();
+
+    // Expanded lists of Uruguayan names and surnames
+    private static readonly string[] _nombresHombre = {
+        "Juan", "Carlos", "Martín", "Luis", "Diego", "Marcelo", "Alejandro", "Fernando",
+        "Matías", "Pablo", "Eduardo", "Federico", "Gonzalo", "Sergio", "Daniel",
+        "Raúl", "Gabriel", "Andrés", "Nicolás", "Roberto", "Sebastián", "Héctor",
+        "Gustavo", "Leonardo", "Rodrigo", "Gerardo", "Ricardo", "Santiago", "Ignacio",
+        "Emiliano", "Dante", "Agustín", "Julio", "Mario", "Alfredo", "Bruno", "Mauricio"
+    };
+    private static readonly string[] _nombresMujer = {
+        "María", "Ana", "Laura", "Sofía", "Lucía", "Valentina", "Natalia", "Andrea",
+        "Gabriela", "Carolina", "Verónica", "Claudia", "Patricia", "Alejandra", "Victoria",
+        "Soledad", "Carmen", "Isabel", "Cecilia", "Paula", "Florencia", "Silvia", "Leticia",
+        "Jimena", "Romina", "Agustina", "Lorena", "Camila", "Valeria", "Daniela", "Mariana",
+        "Eugenia", "Rosario", "Pilar", "Adriana", "Carla", "Micaela", "Magdalena", "Inés"
+    };
+    private static readonly string[] _segundosNombres = {
+        "José", "Pablo", "Elena", "Inés", "Alberto", "Miguel", "Beatriz", "Javier",
+        "Alejandro", "Antonio", "Teresa", "Francisco", "Manuel", "Mercedes", "Rosario",
+        "Ernesto", "Juana", "Felipe", "Jaime", "Clara", "Ignacio", "Ángel", "Joaquín",
+        "Rafael", "Susana", "Héctor", "Alicia", "Marta", "Luciana", "Mateo", "Margarita"
+    };
+    private static readonly string[] _apellidos = {
+        "Rodríguez", "Fernández", "García", "Martínez", "López", "González", "Pérez",
+        "Gómez", "Sánchez", "Romero", "Silva", "Castro", "Torres", "Álvarez", "Benítez",
+        "Ramírez", "Flores", "Herrera", "Gutiérrez", "Suárez", "Rojas", "Vargas", "Acosta",
+        "Morales", "Giménez", "Cardozo", "Méndez", "Delgado", "Díaz", "Pereyra", "Olivera",
+        "Ferreira", "Núñez", "Castillo", "Aguirre", "Duarte", "Sosa", "Vázquez", "Techera",
+        "Medina", "Hernández", "Cabrera", "Machado", "Bentancor", "Moreira", "Fagúndez", "Pintos"
+    };
+
+    public ResultObtDocDigitalizado ObtDocDigitalizado(ParamObtDocDigitalizado param)
+    {
+        // Validate input parameters
+        if (string.IsNullOrEmpty(param?.NroDocumento) ||
+            string.IsNullOrEmpty(param?.NroSerie) ||
+            string.IsNullOrEmpty(param?.Organismo) ||
+            string.IsNullOrEmpty(param?.ClaveAcceso1))
+        {
+            return CreateErrorResult(ErrorCodes.ErroresGraves.ParametrosIncorrectos,
+                                    ErrorCodes.ErroresGraves.ParametrosIncorrectosMensaje);
+        }
+
+        try
+        {
+            int seed = GetSeedFromCI(param.NroDocumento);
+            Random randomCI = new(seed);
+
+            var (errors, warnings) = CheckErrorsAndWarnings(param.NroDocumento);
+            if (errors.Any())
+            {
+                return new ResultObtDocDigitalizado { Errores = errors.ToArray() };
+            }
+
+            ObjPersona persona = GeneratePerson(param, randomCI);
+            var images = GenerateImages(seed);
+
+            return new ResultObtDocDigitalizado
+            {
+                Persona = persona,
+                Imagenes = images,
+                Warnings = warnings.Any() ? warnings.ToArray() : null
+            };
+        }
+        catch (Exception ex)
+        {
+            return CreateErrorResult(ErrorCodes.ErroresGraves.ConsultaNoCompletada,
+                                    $"Error inesperado: {ex.Message}", ex.StackTrace!);
+        }
+    }
+
+    private (List<Mensaje> Errors, List<Mensaje> Warnings) CheckErrorsAndWarnings(string nroDocumento)
+    {
+        var errors = new List<Mensaje>();
+        var warnings = new List<Mensaje>();
+
+        // Extract digits for error/warning checking
+        string digits = new string(nroDocumento.Where(char.IsDigit).ToArray());
+        if (string.IsNullOrEmpty(digits))
+        {
+            errors.Add(CreateMensaje(ErrorCodes.ErroresGraves.ParametrosIncorrectos,
+                                    ErrorCodes.ErroresGraves.ParametrosIncorrectosMensaje));
+            return (errors, warnings);
+        }
+
+        string firstTwoDigits = digits.Length >= 2 ? digits.Substring(0, 2) : digits.PadRight(2, '0');
+
+        switch (firstTwoDigits)
+        {
+            case "11": // Persona inexistente
+                errors.Add(CreateMensaje(ErrorCodes.ErroresLeves.PersonaInexistente,
+                                        ErrorCodes.ErroresLeves.PersonaInexistenteMensaje));
+                return (errors, warnings);
+
+            case "12": // Límite excedido
+                errors.Add(CreateMensaje(ErrorCodes.ErroresLeves.LimiteConsultasExcedido,
+                                        ErrorCodes.ErroresLeves.LimiteConsultasExcedidoMensaje));
+                return (errors, warnings);
+
+            case "13": // Cédula anulada
+                errors.Add(CreateMensaje(ErrorCodes.ErroresLeves.NumeroCedulaAnulado,
+                                        ErrorCodes.ErroresLeves.NumeroCedulaAnuladoMensaje));
+                return (errors, warnings);
+
+            case "14": // Regularización needed
+                warnings.Add(CreateMensaje(ErrorCodes.Warnings.DatosPersonaRegularizar,
+                                        ErrorCodes.Warnings.DatosPersonaRegularizarMensaje));
+                break;
+
+            case "15": // Documento hurtado
+                warnings.Add(CreateMensaje(ErrorCodes.Warnings.DocumentoHurtadoExtraviado,
+                                        ErrorCodes.Warnings.DocumentoHurtadoExtraviadoMensaje));
+                break;
+        }
+
+        return (errors, warnings);
+    }
+
+    private ObjPersona GeneratePerson(ParamObtDocDigitalizado param, Random randomCI)
+    {
+        // Return cached person if exists
+        if (_personasGeneradas.TryGetValue(param.NroDocumento, out var existingPerson))
+        {
+            return existingPerson;
+        }
+
+        // Generate deterministic person data
+        int sexo = randomCI.Next(1, 3); // 1 = Masculino, 2 = Femenino
+        string nombre1 = sexo == 1
+            ? _nombresHombre[randomCI.Next(_nombresHombre.Length)]
+            : _nombresMujer[randomCI.Next(_nombresMujer.Length)];
+
+        string nombre2 = _segundosNombres[randomCI.Next(_segundosNombres.Length)];
+        string apellido1 = _apellidos[randomCI.Next(_apellidos.Length)];
+        string apellido2 = _apellidos[randomCI.Next(_apellidos.Length)];
+
+        // Optional adoptive surnames
+        string? apellidoAdoptivo1 = randomCI.NextDouble() < 0.1 ? _apellidos[randomCI.Next(_apellidos.Length)] : null;
+        string? apellidoAdoptivo2 = apellidoAdoptivo1 != null && randomCI.NextDouble() < 0.5 ? _apellidos[randomCI.Next(_apellidos.Length)] : null;
+
+        // Extract last two digits of document number for age
+        string digits = new string(param.NroDocumento.Where(char.IsDigit).ToArray());
+        string lastTwoDigits = digits.Length >= 2 ? digits[^2..] : digits.PadLeft(2, '0');
+        int edad = int.Parse(lastTwoDigits);
+
+        // Ensure age is reasonable (e.g., 0-99); adjust if needed
+        edad = Math.Clamp(edad, 0, 99);
+
+        // Generate birth date based on age
+        DateTime fechaNacimiento = DateTime.Now.AddYears(-edad).AddDays(-randomCI.Next(0, 365));
+        string fechaNacimientoStr = fechaNacimiento.ToString("yyyy-MM-dd");
+
+        // Build full name
+        string nombreCompleto = $"{nombre1} {nombre2} {apellido1} {apellido2}";
+        if (apellidoAdoptivo1 != null)
+        {
+            nombreCompleto += $" {apellidoAdoptivo1}";
+            if (apellidoAdoptivo2 != null)
+                nombreCompleto += $" {apellidoAdoptivo2}";
+        }
+
+        // Create person
+        var persona = new ObjPersona
+        {
+            CodTipoDocumento = param.TipoDocumento,
+            NroDocumento = param.NroDocumento,
+            Nombre1 = nombre1,
+            Nombre2 = nombre2,
+            PrimerApellido = apellido1,
+            SegundoApellido = apellido2,
+            ApellidoAdoptivo1 = apellidoAdoptivo1!,
+            ApellidoAdoptivo2 = apellidoAdoptivo2!,
+            Sexo = sexo,
+            FechaNacimiento = fechaNacimientoStr,
+            CodNacionalidad = randomCI.Next(1, 3), // 1 = Oriental, 2 = Extranjero
+            NombreEnCedula = nombreCompleto,
+            IdSolicitud = 10000 + (randomCI.Next(0, 90000)),
+            IdRespuesta = 10000 + (randomCI.Next(0, 90000))
+        };
+
+        // Cache the person
+        _personasGeneradas[param.NroDocumento] = persona;
+        return persona;
+    }
+
+    private ImagenDigital[] GenerateImages(int seed)
+    {
+        Random randomImage = new(seed);
+        int length1 = 100 + (randomImage.Next(0, 300)); // 100-399 bytes
+        int length2 = 100 + (randomImage.Next(0, 300)); // 100-399 bytes
+
+        return new[]
+        {
+            new ImagenDigital
+            {
+                Foto = "No Implementado...",
+                LargoBytes = length1,
+                TipoImagen = 1
+            },
+            new ImagenDigital
+            {
+                Foto = "No Implementado...",
+                LargoBytes = length2,
+                TipoImagen = 2
+            }
+        };
+    }
+
+    private int GetSeedFromCI(string nroDocumento)
+    {
+        string digits = new string(nroDocumento.Where(char.IsDigit).ToArray());
+        if (string.IsNullOrEmpty(digits))
+        {
+            return 12345;
+        }
+
+        if (digits.Length > 8)
+        {
+            digits = digits[^8..];
+        }
+
+        return int.TryParse(digits, out int seed) ? seed : 12345;
+    }
+
+    private ResultObtDocDigitalizado CreateErrorResult(int code, string message, string extraData = "Trace")
+    {
+        return new ResultObtDocDigitalizado
+        {
+            Errores = new[] { CreateMensaje(code, message, extraData) }
+        };
+    }
+
+    private Mensaje CreateMensaje(int code, string description, string extraData = "Trace")
+    {
+        return new Mensaje
+        {
+            CodMensaje = code,
+            Descripcion = description,
+            DatoExtra = extraData
+        };
+    }
+}

+ 15 - 0
SoapService.csproj

@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />
+    <PackageReference Include="SoapCore" Version="1.2.1.8" />
+    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
+  </ItemGroup>
+
+</Project>

+ 6 - 0
SoapService.http

@@ -0,0 +1,6 @@
+@SoapService_HostAddress = http://localhost:5032
+
+GET {{SoapService_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###

+ 8 - 0
appsettings.Development.json

@@ -0,0 +1,8 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  }
+}

+ 9 - 0
appsettings.json

@@ -0,0 +1,9 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  },
+  "AllowedHosts": "*"
+}