Cosmos.Types.IdentificacionesLegales 2.0.0

dotnet add package Cosmos.Types.IdentificacionesLegales --version 2.0.0
                    
NuGet\Install-Package Cosmos.Types.IdentificacionesLegales -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Cosmos.Types.IdentificacionesLegales" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Cosmos.Types.IdentificacionesLegales" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Cosmos.Types.IdentificacionesLegales" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Cosmos.Types.IdentificacionesLegales --version 2.0.0
                    
#r "nuget: Cosmos.Types.IdentificacionesLegales, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Cosmos.Types.IdentificacionesLegales@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Cosmos.Types.IdentificacionesLegales&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Cosmos.Types.IdentificacionesLegales&version=2.0.0
                    
Install as a Cake Tool

Cosmos.Types.IdentificacionesLegales

NuGet

IdentificacionLegal — Value Object de identificación legal para el ERP Cosmos: tipo de documento (país-scoped, catálogo embebido de 47 tipos), número y dígito de verificación. Implementa el módulo-11 DIAN para el NIT colombiano. Viaja dentro de eventos event-sourced (JSONB) aguas abajo, por lo que su forma serializada es un contrato estable.

Instalación

dotnet add package Cosmos.Types.IdentificacionesLegales

Construcción

Dos tipos, dos puertas distintas:

  • TipoDocumento es un VO de catálogo país-scoped: se obtiene con TipoDocumento.Obtener(pais, codigo), que lanza ArgumentException si el código no existe/activo para ese país. Su Crear es internal.
  • IdentificacionLegal es un VO compuesto: su factory pública Crear(...) : Result<IdentificacionLegal> valida sin lanzar (EsExitoso / .Valor / .Errores).
using Cosmos.Types.IdentificacionesLegales;
using Cosmos.Types.Paises;

var colombia = Pais.Obtener("CO");
var nit = TipoDocumento.Obtener(colombia, "NIT");   // VO de catálogo: Obtener (lanza)

var resultado = IdentificacionLegal.Crear(           // VO compuesto: Crear (Result)
    tipo: nit,
    numero: "900123456");                            // el DV se calcula (módulo-11 DIAN)

if (resultado.EsExitoso)
    UsarIdentificacion(resultado.Valor);             // resultado.Valor.DigitoVerificacion == "8"
else
    foreach (var error in resultado.Errores)
        Console.WriteLine(error.Codigo);   // V02 (tipo), V03 (número), V04 (formato), V05 (DV pegado), V06 (DV)

Crear recibe el TipoDocumento tipado y deriva el país de él (un tipo de documento existe "para un país"): no se pasa el país por separado. Para tipos con DV (NIT) el dígito se calcula (módulo-11 DIAN); si lo proveés (digitoVerificacion: "8") la politica controla el desajuste: Rechazo falla con V06, Advertencia lo degrada. Ver el algoritmo en docs/algoritmos-dv.md.

Forma serializada

{
  "tipoDocumento": "NIT",
  "numero": "900123456",
  "pais": { "codigo": "CO" },
  "digitoVerificacion": "8",
  "numeroOriginal": "900123456"
}

Las claves son camelCase ancladas con [JsonPropertyName]. Notá la asimetría deliberada: el tipo de documento se aplana a un string ("tipoDocumento": "NIT", no un objeto), mientras que el pais se anida como objeto ({ "codigo": "CO" }). Un consumidor que mapee a mano necesita saberlo. La ClaveCanonica es un accessor derivado [JsonIgnore]: no viaja.

Número original (cualquier tipo)

En cualquier tipo el VO guarda, además del numero normalizado (la llave), el número tal como se escribió. Se guarda lo escrito canonizado (ver "Normalización") y recortado, con mayúsculas/minúsculas y separadores tal cual. Siempre tiene valor: si no se escribió nada distinto del normalizado, es igual a numero.

{
  "tipoDocumento": "DIE",
  "numero": "372102626",
  "pais": { "codigo": "CO" },
  "digitoVerificacion": null,
  "numeroOriginal": "37-2102626"
}
Se escribe Tipo numero numeroOriginal TieneNumeroOriginalDistinto
37-2102626 DIE (CO) 372102626 37-2102626 true
900.123.456 NIT (CO) 900123456 900.123.456 true
900123456 NIT (CO) 900123456 900123456 false
ab12345 DIE (CO) AB12345 ab12345 true
20-12345678-9 CUIT (AR) 20123456789 20-12345678-9 true
37‑2102626 (U+2011) DIE (CO) 372102626 37-2102626 true

Dos puertas. Numero es para todo lo que compara: igualdad, hash, ClaveCanonica, búsquedas, índices y duplicados. NumeroOriginal es solo para mostrar y para entregar a un sistema externo que conoce el documento escrito así. 37-2102626 y 3721026-26 son la misma identificación. Para mostrarla se usa Presentacion ([JsonIgnore]): NumeroOriginal con el DV como sufijo cuando existe (900.123.456-8; si el número lleva guiones, 155720753-2-2022 DV 39).

¿Se escribió distinto? TieneNumeroOriginalDistinto ([JsonIgnore]) es true cuando NumeroOriginal difiere de Numero en comparación ordinal (una diferencia solo de mayúsculas cuenta). No hace falta comparar a mano.

Serialización. numeroOriginal se escribe siempre, también cuando es igual a numero. numero se sigue escribiendo (es la llave que indexan los consumidores).

Saneamiento. El original solo admite letras y dígitos ASCII, -, ., / y espacio, con a lo sumo el doble de la longitud máxima del tipo (26 caracteres para el NIT). Si lo escrito no cumple (p. ej. NIT: 900123456 o 900,123,456), Crear no falla: crea el VO con numeroOriginal igual a numero y devuelve la advertencia V08.

Rehidratación. numero se lee tal como viene del evento: no se recalcula a partir de numeroOriginal, así que un evento histórico nunca cambia de llave. Un numeroOriginal que llega en un evento solo se expone si es coherente con el resto del VO: NormalizarNumero(tipo, numeroOriginal) == numero y cumple el saneamiento. Si no viene (eventos anteriores a 2.0.0 que no lo traían), o no es coherente (editado a mano, de otra llave, o de un tipo que ya no figura en el catálogo), la propiedad devuelve numero; nunca lanza. Un "numero": null se lee como "". También aplica tras un with: si se cambia Numero con with, el original que ya no normaliza a la nueva llave se lee como la nueva llave.

Regla de versión. La coherencia se evalúa en cada lectura contra el catálogo y la canonización de la versión vigente del paquete, no contra los de la versión que escribió el evento. Por eso una versión futura que baje la longitudMax de un tipo, cambie su separadorSignificativo, lo retire del catálogo o cambie la canonización puede ocultar originales históricos (se leen como numero; numero no cambia). Esos cambios del catálogo se tratan como cambios de comportamiento y se anuncian en "Cambios desde …".

Editar el número original. Un comando que edita el número original de una identificación existente usa ConNumeroOriginal(numeroOriginal) : Result<IdentificacionLegal>, no with { NumeroOriginal = … } (con with un original inválido se lee y se serializa como numero: la edición se pierde sin error):

var editada = identificacion.ConNumeroOriginal("37-2102626");  // canoniza y valida
var sinGuion = identificacion.ConNumeroOriginal("372102626");  // quita la forma escrita distinta
  • Es obligatorio: para quitar los separadores se escribe el número sin ellos.
  • Falla con V08 si llega null, vacío (tras canonizar) o no es admisible (caracteres o longitud), con V09 si no normaliza a numero (sería otra identificación) y con V02 si el tipo ya no figura en el catálogo. No cambia la llave ni el DV.

Normalización del número sin construir el VO

IdentificacionLegal.NormalizarNumero(tipo, numero) publica la regla V03 que aplica Crear, para que un consumidor que busca por numero (p. ej. una consulta por número de documento) no la reimplemente:

IdentificacionLegal.NormalizarNumero(die, "37-2102626");  // "372102626"
IdentificacionLegal.NormalizarNumero(cip, " pe-1-196 ");  // "PE-1-196"

Primero canoniza lo escrito: forma Unicode NFKC (los dígitos de ancho completo 9 pasan a 9), todo guion Unicode (‑ U+2011, – U+2013, …) pasa a -, todo espacio Unicode (U+00A0, …) pasa a espacio y se quitan los caracteres invisibles de formato (U+200B, …). Luego recorta, pasa a mayúsculas y, si tipo.SeparadorSignificativo es false, quita todo lo que no sea letra o dígito. Si es true (Panamá) o null (tipo rehidratado que ya no figura en el catálogo) conserva los separadores. No valida (tipo, formato, longitud ni DV) y no lanza; null da "". Crear usa este mismo método: es la única fuente de la regla.

Rehidratación (deserialización)

No requiere setup: ni JsonConverter ni JsonSerializerContext registrados; basta el STJ por defecto. Al deserializar un evento histórico el VO se rehidrata crudo ([JsonConstructor]), sin validar — un tipo que ya no figura en el catálogo igual se rehidrata.

Listado

Para poblar un selector de tipos de documento de un país (frontends) usá TipoDocumento.ListarPorPais(pais) — devuelve los mismos TipoDocumento que consume la escritura. El backend proyecta su propio DTO de API:

var tiposDto = TipoDocumento.ListarPorPais(colombia)
    .Select(tipo => new { tipo.Codigo, tipo.Nombre, tipo.AplicaA, tipo.CapturaDv });

Superficie derivada de TipoDocumento (para armar el formulario)

Además de Nombre, TipoDocumento expone accessors derivados del catálogo para que un consumidor arme el formulario de captura sin conocer vocabularios del catálogo (nombres de algoritmo, strings de naturaleza):

  • AplicaA (Naturaleza?) — enum tipado { PersonaNatural, PersonaJuridica, Ambos }. Sirve para parear tipo ↔ naturaleza (ofrecer solo los tipos que aplican al sujeto).
  • CapturaDv (CapturaDigitoVerificacion?) — record (bool Requiere, int? Longitud): la decisión de mostrar/ocultar el campo de DV y su longitud, no el nombre del algoritmo. NIT (CO) → { true, 1 }; RUC-PA/NT-PA → { true, 2 }; DV embebido (cédula/RNC de DO) o sin algoritmo → { false, null }.
  • PatronValidacion (string?) + LongitudMinima/LongitudMaxima (int?) — regex ASCII anclada ^{charset}{min,max}$ que describe el número ya normalizado. Aplicarla sobre el input crudo con separadores da falso rechazo.
  • SeparadorSignificativo (bool?) — la regla de normalización que el consumidor necesita para llevar el input a la forma canónica antes del patrón: false ⇒ trim + mayúsculas + quitar todo lo que no sea letra o dígito (37-2102626 → 372102626); true ⇒ solo trim + mayúsculas (los guiones son parte del número, tipos de Panamá: 8-926-1601). En .NET, IdentificacionLegal.NormalizarNumero aplica la regla completa (incluida la canonización Unicode).

Todos son [JsonIgnore]: no viajan al wire (la forma serializada de TipoDocumento sigue siendo { pais, codigo }). Como son datos de display/decisión, es responsabilidad del backend consumidor proyectar su propio DTO (ver el ejemplo de ListarPorPais arriba) antes de exponerlos a un frontend JS. En un TipoDocumento rehidratado cuyo código ya no figura en el catálogo (poison), todos devuelven null sin lanzar (igual que Nombre).

La forma del DV que expone CapturaDv sale de la misma fuente única que gobierna qué algoritmo de cálculo aplica IdentificacionLegal.Crear: no hay un segundo mapeo que pueda diverger.

Cambios desde 1.7.0 (2.0.0)

Cambio de contrato: el número original pasa a ser un dato que siempre tiene valor y siempre se guarda, junto al normalizado, que sigue siendo la llave (se calcula en Crear y se lee tal cual del evento al rehidratar). Quien usaba la 1.7.0 debe revisar todo uso de NumeroOriginal (el compilador no avisa en todos los casos).

  • NumeroOriginal es string y nunca es null. Cuando no se escribió nada distinto del normalizado (o lo escrito no era admisible, o el original rehidratado falta o no es coherente) vale Numero. Un NumeroOriginal is null compila pero ya nunca es true, y NumeroOriginal ?? Numero equivale a NumeroOriginal.
  • TieneNumeroOriginalDistinto (nuevo, [JsonIgnore]): true si NumeroOriginal difiere de Numero (ordinal). Reemplaza a NumeroOriginal is not null como forma de saber si hay una forma escrita distinta.
  • numeroOriginal se serializa siempre, también cuando es igual a numero. Los eventos anteriores, sin la propiedad, rehidratan con NumeroOriginal == Numero. Los tests de consumidores que comparan el JSON exacto tienen que contar con la propiedad nueva.
  • ConNumeroOriginal(string) ya no acepta null para borrar: null o vacío fallan con V08. Para quitar los separadores se pasa el número sin ellos.
  • Evento con "numero": null (poison): ahora rehidrata con Numero y NumeroOriginal vacíos, y Presentacion no lanza (en 1.7.0 Numero quedaba null y Presentacion lanzaba si había DV).
  • Fuera de ese caso, Presentacion, Numero, la igualdad, ClaveCanonica, NormalizarNumero y el catálogo no cambian.

Cambios desde 1.6.0 (1.7.0)

  • ConNumeroOriginal(string?) : Result<IdentificacionLegal> (nuevo): fija o edita la foto de una identificación existente con las mismas reglas que Crear (ver "Editar el número original"). Falla con V08 si no es admisible, con V09 (código nuevo) si no normaliza a numero, y con V02 si el tipo ya no figura en el catálogo. null borra la foto.
  • V05 ignora todo lo que la normalización quita salvo el guion. Además de 900.123.456-8 y 900 123 456-8, ahora rechaza 900,123,456-8 en un NIT (en 1.6.0 se aceptaba como número 9001234568).
  • Signo menos U+2212 se canoniza a -, igual que los guiones Unicode: un CIP panameño escrito 8−926−1601 normaliza a 8-926-1601 (en 1.6.0 fallaba V04), y un DIE 37−2102626 conserva la foto 37-2102626 (en 1.6.0 se descartaba con V08).
  • Documentación: "Regla de versión" de la coherencia de la foto rehidratada, y el alcance de NFKC (abajo).

Cambios desde 1.5.0 (1.6.0)

  • Se quita la propiedad pública TipoDocumento.FormatoLibre (y el campo formatoLibre del catálogo). Quien la usaba debe dejar de hacerlo: NumeroOriginal ya no depende del tipo; se conserva en cualquier tipo cuando lo escrito difiere de Numero (ver "Número original").
  • Canonización Unicode antes de normalizar y de guardar el original: NFKC, guiones Unicode a -, espacios Unicode a espacio, sin caracteres invisibles de formato. Afecta también a NormalizarNumero:
    • Un CIP panameño escrito con – (U+2013) ahora normaliza a 8-926-1601 en lugar de fallar V04.
    • NFKC es amplio a propósito: además de los dígitos de ancho completo (9 → 9), convierte superíndices y dígitos encerrados (¹, ①, ⒈) y № → No. La misma entrada cruda puede dar otro Numero que en 1.5.0: 900123456¹ era 900123456 y ahora es 9001234561. Una búsqueda con NormalizarNumero sobre una llave guardada con 1.5.0 a partir de una entrada así no la encuentra. Solo afecta entradas raras.
  • Saneamiento del original y advertencia nueva V08 (original descartado; Crear no falla).
  • Original rehidratado incoherente se ignora (la propiedad devuelve null), sin lanzar. La coherencia se evalúa contra el catálogo vigente (ver "Regla de versión").
  • V05 ignora puntos y espacios de agrupación: 900.123.456-8 y 900 123 456-8 en un NIT se rechazan por traer el DV pegado (en 1.5.0 se aceptaban como número 9001234568).

Dependencias

  • Cosmos.Types.Abstractions (kernel: Result<T>)
  • Cosmos.Types.Paises (validación del país)

Requiere net10.0. Solo está implementado el módulo-11 DIAN (NIT colombiano); otros algoritmos de DV quedan diferidos.

Documentación

Licencia

Uso interno del ERP Cosmos.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on Cosmos.Types.IdentificacionesLegales:

Package Downloads
Cosmos.Impuestos.Contratos

Eventos públicos de integración de Cosmos Impuestos (IPublicEvent) para consumidores en otros bounded contexts.

ObligacionesPorPagar.Entradas.Contratos

Contratos de reconocimiento expuestos a terceros.

Cosmos.Contabilidad.Contratos

Package Description

ObligacionesPorPagar.Reconocimiento.Contratos

Contratos de reconocimiento expuestos a terceros.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 0 10/5/2026
1.7.0 74 10/3/2026
1.6.0 43 10/3/2026
1.5.0 56 10/2/2026
1.4.0 85 10/2/2026
1.3.0 1,624 9/16/2026
1.2.0 993 7/23/2026
1.1.4 166 7/22/2026
1.1.3 206 7/16/2026
1.1.2 169 7/16/2026
1.1.1 1,551 6/30/2026
1.1.0 157 6/24/2026