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
<PackageReference Include="Cosmos.Types.IdentificacionesLegales" Version="2.0.0" />
<PackageVersion Include="Cosmos.Types.IdentificacionesLegales" Version="2.0.0" />
<PackageReference Include="Cosmos.Types.IdentificacionesLegales" />
paket add Cosmos.Types.IdentificacionesLegales --version 2.0.0
#r "nuget: Cosmos.Types.IdentificacionesLegales, 2.0.0"
#:package Cosmos.Types.IdentificacionesLegales@2.0.0
#addin nuget:?package=Cosmos.Types.IdentificacionesLegales&version=2.0.0
#tool nuget:?package=Cosmos.Types.IdentificacionesLegales&version=2.0.0
Cosmos.Types.IdentificacionesLegales
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:
TipoDocumentoes un VO de catálogo país-scoped: se obtiene conTipoDocumento.Obtener(pais, codigo), que lanzaArgumentExceptionsi el código no existe/activo para ese país. SuCrearesinternal.IdentificacionLegales un VO compuesto: su factory públicaCrear(...) : 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
V08si lleganull, vacío (tras canonizar) o no es admisible (caracteres o longitud), conV09si no normaliza anumero(sería otra identificación) y conV02si 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.NormalizarNumeroaplica 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).
NumeroOriginalesstringy nunca esnull. Cuando no se escribió nada distinto del normalizado (o lo escrito no era admisible, o el original rehidratado falta o no es coherente) valeNumero. UnNumeroOriginal is nullcompila pero ya nunca estrue, yNumeroOriginal ?? Numeroequivale aNumeroOriginal.TieneNumeroOriginalDistinto(nuevo,[JsonIgnore]):truesiNumeroOriginaldifiere deNumero(ordinal). Reemplaza aNumeroOriginal is not nullcomo forma de saber si hay una forma escrita distinta.numeroOriginalse serializa siempre, también cuando es igual anumero. Los eventos anteriores, sin la propiedad, rehidratan conNumeroOriginal == Numero. Los tests de consumidores que comparan el JSON exacto tienen que contar con la propiedad nueva.ConNumeroOriginal(string)ya no aceptanullpara borrar:nullo vacío fallan conV08. Para quitar los separadores se pasa el número sin ellos.- Evento con
"numero": null(poison): ahora rehidrata conNumeroyNumeroOriginalvacíos, yPresentacionno lanza (en 1.7.0NumeroquedabanullyPresentacionlanzaba si había DV). - Fuera de ese caso,
Presentacion,Numero, la igualdad,ClaveCanonica,NormalizarNumeroy 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 queCrear(ver "Editar el número original"). Falla conV08si no es admisible, conV09(código nuevo) si no normaliza anumero, y conV02si el tipo ya no figura en el catálogo.nullborra la foto.V05ignora todo lo que la normalización quita salvo el guion. Además de900.123.456-8y900 123 456-8, ahora rechaza900,123,456-8en un NIT (en 1.6.0 se aceptaba como número9001234568).- Signo menos U+2212 se canoniza a
-, igual que los guiones Unicode: un CIP panameño escrito8−926−1601normaliza a8-926-1601(en 1.6.0 fallabaV04), y un DIE37−2102626conserva la foto37-2102626(en 1.6.0 se descartaba conV08). - 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 campoformatoLibredel catálogo). Quien la usaba debe dejar de hacerlo:NumeroOriginalya no depende del tipo; se conserva en cualquier tipo cuando lo escrito difiere deNumero(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 aNormalizarNumero:- Un CIP panameño escrito con
–(U+2013) ahora normaliza a8-926-1601en lugar de fallarV04. - 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 otroNumeroque en 1.5.0:900123456¹era900123456y ahora es9001234561. Una búsqueda conNormalizarNumerosobre una llave guardada con 1.5.0 a partir de una entrada así no la encuentra. Solo afecta entradas raras.
- Un CIP panameño escrito con
- Saneamiento del original y advertencia nueva
V08(original descartado;Crearno 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"). V05ignora puntos y espacios de agrupación:900.123.456-8y900 123 456-8en un NIT se rechazan por traer el DV pegado (en 1.5.0 se aceptaban como número9001234568).
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 | Versions 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. |
-
net10.0
- Cosmos.Types.Abstractions (>= 1.1.1)
- Cosmos.Types.Paises (>= 1.1.1)
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.