Skip to content

Repository files navigation

NIF-DNI-NIE-CIF Validation

NuGet Version NuGet Downloads GitHub Build License MIT GitHub Stars GitHub Issues

Biblioteca .NET para validar números de identificación españoles: NIF, DNI, NIE y CIF.

Proporciona métodos síncronos, asíncronos, individuales y por lotes para verificar el formato y los dígitos de control de documentos de identificación fiscal y personal de España.

Incluye dos motores:

  • API clásica (ValidadorDocumentos): completa, con objetos de valor, excepciones, mensajes detallados.
  • API UltraFast (ValidadorDocumentosUltraFast): SIMD + zero-alloc + particionado por CPU, hasta ~50× más rápida sobre millones de documentos.

Características

  • Validación completa de DNI, NIE, NIF (incluye K/L/M) y CIF
  • Una línea para empezar: DocumentoEspanol.EsValido("12345678Z"), sin instanciar nada
  • Dos motores, un mismo veredicto:
    • ValidadorDocumentos — API completa: mensajes detallados, objetos de valor, excepciones
    • ValidadorDocumentosUltraFast — motor SIMD zero-alloc para volumen masivo
  • Cuatro estilos de API, según lo que necesites:
    • Fachada estática (DocumentoEspanol.EsValido) — lo más directo
    • Métodos booleanos (EsDniValido) — cero asignaciones, no lanzan
    • Métodos con resultado (IntentarValidarDni) — todos los errores detallados
    • Métodos con excepción (ValidarDni) — para flujos "esto no debería fallar"
  • Registro en DI de una línea: services.AddValidacionDocumentosEspanoles()
  • Multi-target net8.0 / net9.0 / net10.0, AOT-friendly y trimmable
  • API ReadOnlySpan<char> para parsers y pipelines sin asignaciones
  • Validación por lotes (síncrona y asíncrona) con paralelismo y orden de entrada garantizado
  • Streaming perezoso (IEnumerable / IAsyncEnumerable / Channel<T>) con memoria constante
  • Cancelación real: los *Async cortan a mitad de proceso, no sólo antes de empezar
  • Tolerante a null y a entradas anómalas: nunca lanza por el documento en sí
  • Serialización binaria extrema con MemoryPack (Cysharp)
  • Composición de strings zero-alloc con ZString (Cysharp)
  • Objetos de valor (Value Objects) inmutables: Dni, Nie, Nif, Cif
  • 100% documentado con XML docs en español; símbolos y SourceLink en el paquete
  • Normalización automática (elimina espacios, guiones, puntos)
  • Utilidades para calcular letras/dígitos de control
  • Resultado de lote completo con válidos, inválidos, estadísticas y porcentajes
  • 735 tests, ejecutados en net8.0 y net10.0

Compatibilidad

Target Soportado
.NET 8.0 (LTS)
.NET 9.0
.NET 10.0

Sin dependencias de plataforma. Compatible con AOT nativo y trimming (IsAotCompatible / IsTrimmable). El paquete incluye símbolos (.snupkg) y SourceLink, así que puedes depurar dentro de la librería con F11.


Instalación

Desde NuGet.org

dotnet add package NIF.DNI.NIE.CIF.Validation

Desde GitHub Packages

dotnet nuget add source https://nuget.pkg.github.com/Cristiancastt/index.json --name github --username TU_USUARIO --password TU_GITHUB_TOKEN
dotnet add package NIF.DNI.NIE.CIF.Validation

Uso Rápido

Lo más corto que puede ser: una línea, sin instanciar nada.

using NIF.DNI.NIE.CIF.Validation;

bool ok = DocumentoEspanol.EsValido("12345678Z");          // detecta el tipo solo
bool dni = DocumentoEspanol.EsDniValido("12.345.678-z");   // normaliza solo
TipoDocumento tipo = DocumentoEspanol.DetectarTipo("B12345678");  // CIF

// ¿Por qué falla?
var r = DocumentoEspanol.Validar("12345678A");
Console.WriteLine(r.Mensaje);   // "La letra de control del DNI es incorrecta."

DocumentoEspanol no lanza excepciones, no asigna memoria y acepta null como entrada inválida. Cuando necesites mensajes detallados, objetos de valor o inyección de dependencias, usa la API completa:

using NIF.DNI.NIE.CIF.Validation.Implentaciones;
using NIF.DNI.NIE.CIF.Validation.Excepciones;

var validador = new ValidadorDocumentos();

// ✅ Validación booleana simple
bool esValido = validador.EsDniValido("12345678Z");

// ✅ Validación con resultado detallado (sin excepción)
var resultado = validador.IntentarValidarDni("12345678Z");
if (resultado.EsValido)
    Console.WriteLine(resultado.Mensaje);

// ✅ Validación con excepción si es inválido
try
{
    var documento = validador.ValidarDni("12345678Z");
    Console.WriteLine($"Control: {documento.CaracterControl}");
}
catch (DocumentoNoValidoException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}

Tipos de Documento Soportados

Tipo Formato Ejemplo Descripción
DNI 8 dígitos + 1 letra 12345678Z Documento Nacional de Identidad
NIE X/Y/Z + 7 dígitos + 1 letra X1234567L Número de Identidad de Extranjero
NIF DNI, NIE o K/L/M + 7 dígitos + letra K1234567A Número de Identificación Fiscal
CIF Letra + 7 dígitos + control B12345678 Código de Identificación Fiscal

API Completa

Métodos Booleanos (simples)

bool EsDniValido(string dni);
bool EsNieValido(string nie);
bool EsNifValido(string nif);
bool EsCifValido(string cif);
bool EsDocumentoValido(string documento); // Auto-detecta tipo

Métodos con Excepción

Lanzan DocumentoNoValidoException si el documento no es válido. Devuelven DocumentoValidado.

DocumentoValidado ValidarDni(string dni);
DocumentoValidado ValidarNie(string nie);
DocumentoValidado ValidarNif(string nif);
DocumentoValidado ValidarCif(string cif);
DocumentoValidado ValidarDocumento(string documento); // Auto-detecta tipo

Métodos sin Excepción (resultado detallado)

Devuelven ResultadoValidacion sin lanzar excepciones.

ResultadoValidacion IntentarValidarDni(string dni);
ResultadoValidacion IntentarValidarNie(string nie);
ResultadoValidacion IntentarValidarNif(string nif);
ResultadoValidacion IntentarValidarCif(string cif);
ResultadoValidacion IntentarValidarDocumento(string documento);

Validación por Lotes (síncrona)

ResultadoValidacionLote ValidarLoteDni(IEnumerable<string> dnis);
ResultadoValidacionLote ValidarLoteNie(IEnumerable<string> nies);
ResultadoValidacionLote ValidarLoteNif(IEnumerable<string> nifs);
ResultadoValidacionLote ValidarLoteCif(IEnumerable<string> cifs);
ResultadoValidacionLote ValidarLoteDocumentos(IEnumerable<string> documentos);

Validación por Lotes (asíncrona, con paralelismo)

Task<ResultadoValidacionLote> ValidarLoteDniAsync(IEnumerable<string> dnis, CancellationToken ct = default);
Task<ResultadoValidacionLote> ValidarLoteNieAsync(IEnumerable<string> nies, CancellationToken ct = default);
Task<ResultadoValidacionLote> ValidarLoteNifAsync(IEnumerable<string> nifs, CancellationToken ct = default);
Task<ResultadoValidacionLote> ValidarLoteCifAsync(IEnumerable<string> cifs, CancellationToken ct = default);
Task<ResultadoValidacionLote> ValidarLoteDocumentosAsync(IEnumerable<string> documentos, CancellationToken ct = default);

Utilidades

TipoDocumento DetectarTipo(string? documento);
string Normalizar(string? documento);
char ObtenerLetraDni(int numero);
char ObtenerLetraNie(string nie);
(char Digito, char Letra) ObtenerControlCif(string cif);

Todos los métodos de validación aceptan string?: null es una entrada inválida, nunca una ArgumentNullException.


Objetos de Valor (Value Objects)

Los Value Objects son inmutables y solo pueden crearse con valores válidos:

using NIF.DNI.NIE.CIF.Validation.Modelos;

// Crear un DNI válido (lanza excepción si inválido)
var dni = Dni.Crear("12345678Z");
Console.WriteLine(dni.Numero);  // 12345678
Console.WriteLine(dni.Letra);   // 'Z'

// Intentar crear sin excepción (patrón TryParse)
if (Dni.Intentar("12345678Z", out var dniValido))
    Console.WriteLine(dniValido!.Valor);

// Generar un DNI a partir de un número
var dniGenerado = Dni.DesdeNumero(12345678);

// Crear un NIE
var nie = Nie.Crear("X1234567L");
Console.WriteLine(nie.LetraInicial);  // 'X'

// Crear un CIF
var cif = Cif.Crear("B12345678");
Console.WriteLine(cif.TipoEntidad);      // "Sociedad de Responsabilidad Limitada"
Console.WriteLine(cif.CodigoProvincia);   // "12"

// Conversión implícita a string
string texto = dni; // "12345678Z"

Validación por Lotes - Ejemplo Completo

var validador = new ValidadorDocumentos();

var documentos = new[]
{
    "12345678Z",  // DNI
    "X1234567L",  // NIE
    "B12345678",  // CIF
    "INVALIDO",   // Inválido
    "00000000T",  // DNI
    "999",        // Inválido
};

// Síncrono
var lote = validador.ValidarLoteDocumentos(documentos);

Console.WriteLine(lote.Resumen);
// "Procesados: 6 | Válidos: 4 (66.67%) | Inválidos: 2 (33.33%)"

Console.WriteLine($"Todos válidos: {lote.TodosValidos}");     // false
Console.WriteLine($"Tiene errores: {lote.TieneInvalidos}");    // true

// Iterar válidos
foreach (var valido in lote.Validos)
    Console.WriteLine($"  {valido.TipoDocumento}: {valido.ValorNormalizado}");

// Iterar inválidos
foreach (var error in lote.Invalidos)
    Console.WriteLine($"  Error: {error.Valor} - {error.Mensaje}");

// Filtrar por tipo
var dnis = lote.ObtenerValidosPorTipo(TipoDocumento.DNI);

// Asíncrono con cancelación
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var loteAsync = await validador.ValidarLoteDocumentosAsync(documentos, cts.Token);

Modelo de Resultados

ResultadoValidacion

Propiedad Tipo Descripción
EsValido bool Si el documento es válido
ValorOriginal string Valor tal como fue proporcionado
ValorNormalizado string Valor sin espacios/guiones, en mayúsculas
TipoDocumento TipoDocumento Tipo detectado
Mensaje string Mensaje descriptivo
CaracterControl string? Letra o dígito de control
CaracterControlEsperado string? Control esperado (para depuración)

ResultadoValidacionLote

Propiedad Tipo Descripción
Validos List<DocumentoValidado> Documentos válidos
Invalidos List<ErrorValidacion> Errores de validación
ResultadosIndividuales List<ResultadoValidacion> Todos los resultados
TotalProcesados int Total procesado
TotalValidos int Cantidad de válidos
TotalInvalidos int Cantidad de inválidos
TodosValidos bool Si todos son válidos
TodosInvalidos bool Si todos son inválidos
TieneValidos bool Si hay algún válido
TieneInvalidos bool Si hay algún inválido
PorcentajeValidos double % válidos (0-100)
PorcentajeInvalidos double % inválidos (0-100)
Resumen string Resumen textual

DocumentoValidado

Propiedad Tipo Descripción
ValorOriginal string Valor original
ValorNormalizado string Valor normalizado
TipoDocumento TipoDocumento Tipo de documento
CaracterControl string? Carácter de control
ParteNumerica string Parte numérica

ErrorValidacion

Propiedad Tipo Descripción
Valor string Valor original
ValorNormalizado string Valor normalizado
Mensaje string Descripción del error
TipoDocumentoEsperado TipoDocumento Tipo esperado
CodigoError string Código programático

Inyección de Dependencias

using NIF.DNI.NIE.CIF.Validation.Extensiones;

// En Program.cs: registra IValidadorDocumentos, ValidadorDocumentosUltraFast
// y ProcesadorLotesUltraFast, todos como singleton (no tienen estado mutable).
builder.Services.AddValidacionDocumentosEspanoles();

// Opcional: limitar el paralelismo de los lotes (p.ej. en un contenedor con cuota)
builder.Services.AddValidacionDocumentosEspanoles(gradoParalelismoLotes: 2);

// En tu servicio/controlador
public class MiServicio
{
    private readonly IValidadorDocumentos _validador;

    public MiServicio(IValidadorDocumentos validador)
    {
        _validador = validador;
    }

    public bool VerificarCliente(string documentoIdentidad)
    {
        return _validador.EsDocumentoValido(documentoIdentidad);
    }
}

API UltraFast (alto rendimiento)

Pensada para procesar millones de DNIs/NIEs/CIFs en streaming o batch. No reemplaza la API clásica: ambas coexisten y son 100% compatibles en resultados.

using NIF.DNI.NIE.CIF.Validation.UltraFast;

var ultra = ValidadorDocumentosUltraFast.Instancia;

// Atajo booleano (normaliza internamente)
bool esValido = ultra.EsDniValido("12345678Z");

// Resultado completo: tipo, carácter de control, código y mensaje de error
ResultadoValidacionUltra r = ultra.ValidarDni("12345678Z");
Console.WriteLine($"{r.EsValido} {r.Tipo} {r.CaracterControl}");

// Sobre Span<char>: cero asignaciones, para parsers y pipelines.
// OJO: el span debe venir YA normalizado (mayúsculas, sin puntos ni guiones).
ResultadoValidacionUltra s = ultra.ValidarDniSpan("12345678Z".AsSpan());

// Auto-detección de tipo sobre Span
var t = ultra.ValidarDocumentoSpan("X1234567L".AsSpan()); // TipoDocumentoUltra.NIE

Procesamiento por lotes con partition-based

var proc = new ProcesadorLotesUltraFast(); // 1 partición por CPU

// 1 millón de DNIs en ~15 ms. resultados[i] corresponde siempre a dnis[i].
ResultadoValidacionUltra[] resultados = proc.ValidarLote(dnis);

// Versión async: descarga el trabajo del hilo llamante.
// El token se comprueba DURANTE el proceso, no sólo al planificarlo: cancelar
// a mitad de un lote de 50 millones lo detiene de verdad.
var resultadosAsync = await proc.ValidarLoteAsync(dnis, ct);

// Versión compatible con la API clásica (devuelve ResultadoValidacionLote)
var lote = proc.ValidarLoteCompat(dnis, ct);
var loteAsync = await proc.ValidarLoteCompatAsync(dnis, ct);

Streaming perezoso (para datos que no caben en memoria)

Los métodos ValidarLote* materializan el origen entero en un array. Para un fichero de millones de líneas eso es un OutOfMemoryException. Usa el stream:

// Memoria constante, sea cual sea el tamaño del fichero
foreach (var r in proc.ValidarStream(File.ReadLines("padron.csv"), ct))
    if (!r.EsValido) errores++;

// Origen asíncrono
await foreach (var r in proc.ValidarStreamAsync(File.ReadLinesAsync("padron.csv"), ct))
    if (!r.EsValido) errores++;

// La API clásica también lo ofrece, con mensajes de error detallados
foreach (var r in validador.ValidarStream(File.ReadLines("padron.csv"), ct))
    if (!r.EsValido) log.Warn(r.Mensaje);

Streaming con Channel<T>

Para pipelines con productor y consumidor desacoplados:

var entrada = Channel.CreateUnbounded<string?>();
var salida  = Channel.CreateBounded<ResultadoValidacionUltra>(1024); // backpressure

var productor = Task.Run(async () =>
{
    foreach (var dni in colaDeDnis)
        await entrada.Writer.WriteAsync(dni, ct);
    entrada.Writer.Complete();   // imprescindible: marca el fin de la entrada
});

// Arranca el pipeline y CONSUME EN PARALELO. Si esperas a que termine
// `pipeline` antes de leer y el canal de salida es acotado, se llena y los
// workers se quedan bloqueados: interbloqueo.
var pipeline = proc.ProcesarStreamAsync(entrada.Reader, salida.Writer, ct);

await foreach (var r in salida.Reader.ReadAllAsync(ct))
    Console.WriteLine($"{r.EsValido} {r.Tipo}");

await productor;
await pipeline;   // propaga cualquier fallo del pipeline

ProcesarStreamAsync siempre completa el canal de salida al terminar — también si un worker falla o si se cancela —, de modo que el await foreach del consumidor nunca se queda esperando indefinidamente.

Si no necesitas dos canales, ValidarStream / ValidarStreamAsync hacen lo mismo con mucha menos ceremonia.

Exportar resultados con MemoryPack (Cysharp)

byte[] bytes = ExportadorResultados.SerializarLote(lote);
var dto = ExportadorResultados.DeserializarLote(bytes);

Resumen zero-alloc con ZString (Cysharp)

string resumen = ProcesadorLotesUltraFast.ResumenLoteZString(
    total: 10_000_000, validos: 9_800_000, invalidos: 200_000);
// "Procesados: 10000000 | Válidos: 9800000 (98%) | Inválidos: 200000 (2%)"

Benchmarks (BenchmarkDotNet, .NET 10, x64 RyuJIT)

Comparativa entre la API clásica y la API UltraFast. Máquina: 13th Gen Intel Core i5-1335U, 12 lógicos / 10 físicos. Job: ShortRun (3 iteraciones, warmup 3). Mezcla realista: 70 % DNIs, 15 % NIEs, 10 % CIFs, 5 % inválidos.

Validación individual (1 documento)

Método Tiempo Memoria
Clásica.EsDniValido ~35 ns 0 B
Ultra.ValidarDni (string) ~16 ns 0 B
Ultra.ValidarDniSpan (Span) ~7 ns 0 B

La API clásica venía de 379 ns y 856 B por llamada: construía cinco objetos regla, una lista y una consulta LINQ en cada validación, y los documentos inválidos además lanzaban y capturaban una excepción (1.296 B). Hoy las tres vías no asignan nada y un documento inválido cuesta lo mismo que uno válido.

Detección de tipo

Método Tiempo Memoria
Clásica.DetectarTipo ~17 ns 0 B
Ultra.DetectarPrefijo ~1 ns 0 B

Lotes (síncrono)

Tamaño Clásica Ultra (struct) Ultra (compat)
100 k 82 ms / 39 MB 2,3 ms / 1,1 MB 62 ms / 31 MB
1 M 772 ms / 388 MB 15 ms / 11 MB 570 ms / 311 MB
10 M 8,9 s / 3,8 GB 90 ms / 114 MB 5,1 s / 3,0 GB

Lotes (asíncrono)

Tamaño Clásica Ultra Speedup
100 k 74 ms / 39 MB 2,0 ms / 1,1 MB 36×
1 M 534 ms / 388 MB 14 ms / 11 MB 38×
10 M 5,4 s / 3,8 GB 114 ms / 114 MB 47×

Cómo elegir. Si validas documentos sueltos, cualquiera de las dos APIs vale: la diferencia son nanosegundos. La API ultra gana cuando procesas millones, porque devuelve structs en lugar de construir un objeto de resultado, un DocumentoValidado y un mensaje por documento. Ese coste es justo lo que mide Ultra (compat): mismo motor, pero materializando los DTOs de la API clásica.

Si el origen no cabe en memoria, ninguna de las dos: usa ValidarStream, que consume memoria constante.

Para correr los benchmarks en local:

dotnet run --project Benchmarks -c Release -- --filter "*BenchmarksLote*"
# O solo una categoría:  --anyCategories Individual Prefijo -j short
# O smoke test rápido:   -- --smoke

Algoritmos de Validación

DNI

  1. Formato: 8 dígitos + 1 letra
  2. Letra = "TRWAGMYFPDXBNJZSQVHLCKE"[número % 23]

NIE

  1. Formato: X/Y/Z + 7 dígitos + 1 letra
  2. Reemplazar X→0, Y→1, Z→2
  3. Calcular letra como DNI

NIF Especial (K/L/M)

  1. Formato: K/L/M + 7 dígitos + 1 letra
  2. Calcular letra como DNI sobre los 7 dígitos

CIF

  1. Formato: Letra tipo + 7 dígitos + control
  2. Posiciones impares (1ª, 3ª, 5ª, 7ª): multiplicar por 2, sumar dígitos del resultado
  3. Posiciones pares (2ª, 4ª, 6ª): sumar directamente
  4. Control = (10 - (suma total % 10)) % 10
  5. Según tipo: A/B/E/H→dígito, K/P/Q/S→letra, resto→ambos

Garantías de comportamiento

Cosas con las que puedes contar al integrar la librería:

Garantía Detalle
Ambos motores coinciden ValidadorDocumentos y ValidadorDocumentosUltraFast dan siempre el mismo veredicto sobre el mismo documento.
Orden estable en lotes En todos los ValidarLote*, síncronos y asíncronos, ResultadosIndividuales[i] corresponde a la entrada i.
null nunca lanza Cualquier entrada (null, vacía, con basura, enorme) devuelve "inválido"; no hay ArgumentNullException por el documento.
Cancelación real Los *Async comprueban el token durante el proceso. Cancelar un lote de millones lo detiene; no sólo evita que empiece.
Sin interbloqueos ProcesarStreamAsync completa el canal de salida pase lo que pase (fin, fallo o cancelación): el consumidor nunca se queda colgado.
Memoria acotada Ni cachés estáticas que crezcan sin tope ni buffers proporcionales a entradas anómalas. Los lotes son lineales; el streaming, constante.
Independiente de la cultura Resumen y los porcentajes usan InvariantCulture: el mismo texto en cualquier máquina.
Thread-safe Ningún tipo público tiene estado mutable. Regístralos como singleton y compártelos.
Sin excepciones en el camino feliz EsXValido, Intentar y la API ultra no lanzan ni capturan internamente; validar un documento inválido no cuesta más que uno válido.

Licencia

MIT

Autor

Cristian Arana Castiñeiras

About

Libreria eficiente para la validacion de documentos identificativos españoles en C#

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages