C#. Validar CIF, NIF, NIE

Imagino que, al igual que todos los que alguna vez hemos empezado en esto del desarrollo de aplicaciones, nuestro ego nos ha jugado alguna que otra mala pasada. Ya desde mis inicios de la mano de Access o de Visual Basic 3.0, veía con bastante desgana cómo se iban introduciendo CIFs del tipo A00000000 o A-00000000, o A-00.000.000; matrículas con formatos dispares: GGG0000 ó 0000GGG. Comento y quizás resalto “desgana”, puesto que es uno de los puntos menos valorados tanto por desarrolladores como por clientes. Si total, sólo vale para perder el tiempo y retrasar la finalización del programa. Claro, que el problema nos lo encontramos nosotros cuando llega el momento de obtener un listado ordenado o una simple búsqueda. No recuerdo si fue la versión 5 de VB la que ya incluía la posibilidad de crear OCX que, entre otras cosas, permitía separar un conjunto de controles con el objetivo de reutilizarlos del código de la aplicación.

El caso es que he tenido miles de peleas con los datos cuando éstos no estaban normalizados, así que un día me levanté y me puse manos a la obra con la finalidad de intentar homologar todos aquellos datos que sean susceptibles de tener un “formato igual”. En el blog anterior ya quedó alguno de estos intentos. Una de las rutinas que más me costó en su día fue el intento de poder validar, en un mismo campo, un CIF de empresa, un NIF de persona física y el Número de Identificación de Extranjería (NIE), sin tener que indicarle previamente el tipo de documento.

Como suele pasar cada vez que se empieza un nuevo proyecto, el primer boceto contenía múltiples errores. Algunos de ellos se fueron subsanando; otros, quizás por la falta de tiempo habitual, no quedaron pulidos del todo. Así, aprovechando el haber tenido un poco de tiempo estas fiestas, he intentado pulir el código, de tal manera que sea útil para cualquiera que lo precise.

Uno de los errores que tenía era la eliminación, en la normalización, de aquellos números que empezaban por 0 y la asignación de algún NIE cuando realmente no lo era. Lo único que he omitido de los comentarios que me hicieron en su día es un tema sobre el que anduve buscando información y no supe encontrar una solución bien documentada: los NIE están formados por una letra, ocho dígitos numéricos y el dígito de control. En su lugar, he optado por tomar la definición y la normativa publicada en Wikipedia a tal respecto.

Dejo el código tal cuál ha quedado ya reformado:

using System;
using System.Text.RegularExpressions;

/*
 * NumeroNif.cs
 * Servicios de validación de los números NIF
 * 
 * älskare, Ene/11
 */

namespace JnSoftware.Validaciones
{

    /// <summary>
    /// Representa un número. En la clase se desglosan las distintas opciones que se puedan
    /// encontrar
    /// </summary>
    public class NumeroNif
    {
        /// <summary>
        /// Tipos de Códigos.
        /// </summary>
        /// <remarks>Aunque actualmente no se utilice el término CIF, se usa en la enumeración
        /// por comodidad</remarks>
        private enum TiposCodigosEnum { NIF, NIE, CIF }

        // Número tal cual lo introduce el usuario
        private string numero;
        private TiposCodigosEnum tipo;

        /// <summary>
        /// Parte de Nif: En caso de ser un Nif intracomunitario, permite obtener el cógido del país
        /// </summary>
        public string CodigoIntracomunitario { get; internal set; }
        internal bool EsIntraComunitario { get; set; }

        /// <summary>
        /// Parte de Nif: Letra inicial del Nif, en caso de tenerla
        /// </summary>
        public string LetraInicial { get; internal set; }

        /// <summary>
        /// Parte de Nif: Bloque numérico del NIF. En el caso de un NIF de persona física,
        /// corresponderá al DNI
        /// </summary>
        public int Numero { get; internal set; }

        /// <summary>
        /// Parte de Nif: Dígito de control. Puede ser número o letra
        /// </summary>
        public string DigitoControl { get; internal set; }

        /// <summary>
        /// Valor que representa si el Nif introducido es correcto
        /// </summary>
        public bool EsCorrecto { get; internal set; }

        /// <summary>
        /// Cadena que representa el tipo de Nif comprobado:
        ///     - NIF : Número de identificación fiscal de persona física
        ///     - NIE : Número de identificación fiscal extranjería
        ///     - CIF : Código de identificación fiscal (Entidad jurídica)
        /// </summary>
        public string TipoNif { get { return tipo.ToString(); } }

        /// <summary>
        /// Constructor. Al instanciar la clase se realizan todos los cálculos
        /// </summary>
        /// <param name="numero">Cadena de 9 u 11 caracteres que contiene el DNI/NIF
        /// tal cual lo ha introducido el usuario para su verificación</param>
        private NumeroNif(string numero)
        {
            // Se eliminan los carácteres sobrantes
            numero = EliminaCaracteres(numero);

            // Todo en maýusculas
            numero = numero.ToUpper();

            // Comprobación básica de la cadena introducida por el usuario
            if (numero.Length != 9 && numero.Length != 11)
                throw new ArgumentException("El NIF no tiene un número de caracteres válidos");
           
            this.numero = numero;
            Desglosa();

            switch (tipo)
            {
                case TiposCodigosEnum.NIF:
                case TiposCodigosEnum.NIE:
                    this.EsCorrecto = CompruebaNif();
                    break;
                case TiposCodigosEnum.CIF:
                    this.EsCorrecto = CompruebaCif();
                    break;
            }
        }

        #region Preparación del número (desglose)

        /// <summary>
        /// Realiza un desglose del número introducido por el usuario en las propiedades
        /// de la clase
        /// </summary>
        private void Desglosa()
        {
            Int32 n;
            if (numero.Length == 11)
            {
                // Nif Intracomunitario
                EsIntraComunitario = true;
                CodigoIntracomunitario = numero.Substring(0, 2);
                LetraInicial = numero.Substring(2, 1);
                Int32.TryParse(numero.Substring(3, 7), out n);
                DigitoControl = numero.Substring(10, 1);
                tipo = GetTipoDocumento(LetraInicial[0]);
            }
            else
            {
                // Nif español
                tipo = GetTipoDocumento(numero[0]);
                EsIntraComunitario = false;
                if (tipo == TiposCodigosEnum.NIF)
                {
                    LetraInicial = string.Empty;
                    Int32.TryParse(numero.Substring(0, 8), out n);
                }
                else
                {
                    LetraInicial = numero.Substring(0, 1);
                    Int32.TryParse(numero.Substring(1, 7), out  n);
                }
                DigitoControl = numero.Substring(8, 1);
            }
            Numero = n;
        }

        /// <summary>
        /// En base al primer carácter del código, se obtiene el tipo de documento que se intenta
        /// comprobar
        /// </summary>
        /// <param name="letra">Primer carácter del número pasado</param>
        /// <returns>Tipo de documento</returns>
        private TiposCodigosEnum GetTipoDocumento(char letra)
        {
            Regex regexNumeros = new Regex("[0-9]");
            if (regexNumeros.IsMatch(letra.ToString()))
                return TiposCodigosEnum.NIF;

            Regex regexLetrasNIE = new Regex("[XYZ]");
            if (regexLetrasNIE.IsMatch(letra.ToString()))
                return TiposCodigosEnum.NIE;

            Regex regexLetrasCIF = new Regex("[ABCDEFGHJPQRSUVNW]");
            if (regexLetrasCIF.IsMatch(letra.ToString()))
                return TiposCodigosEnum.CIF;

            throw new ApplicationException("El código no es reconocible");
        }



        /// <summary>
        /// Eliminación de todos los carácteres no numéricos o de texto de la cadena
        /// </summary>
        /// <param name="numero">Número tal cual lo escribe el usuario</param>
        /// <returns>Cadena de 9 u 11 carácteres sin signos</returns>
        private string EliminaCaracteres(string numero)
        {
            // Todos los carácteres que no sean números o letras
            string caracteres = @"[^w]";
            Regex regex = new Regex(caracteres);
            return regex.Replace(numero, "");
        }

        #endregion

        #region Cálculos

        private bool CompruebaNif()
        {
            return DigitoControl == GetLetraNif();
        }

        /// <summary>
        /// Cálculos para la comprobación del Cif (Entidad jurídica)
        /// </summary>
        private bool CompruebaCif()
        {
            string[] letrasCodigo = { "J", "A", "B", "C", "D", "E", "F", "G", "H", "I" };

            string n = Numero.ToString("0000000");
            Int32 sumaPares = 0;
            Int32 sumaImpares = 0;
            Int32 sumaTotal = 0;
            Int32 i = 0;
            bool retVal = false;

            // Recorrido por todos los dígitos del número
            for (i = 0; i < n.Length; i++)
            {
                Int32 aux;
                Int32.TryParse(n[i].ToString(), out aux);

                if ((i + 1) % 2 == 0)
                {
                    // Si es una posición par, se suman los dígitos
                    sumaPares += aux;
                }
                else
                {
                    // Si es una posición impar, se multiplican los dígitos por 2 
                    aux = aux * 2;

                    // se suman los dígitos de la suma
                    sumaImpares += SumaDigitos(aux);
                }
            }
            // Se suman los resultados de los números pares e impares
            sumaTotal += sumaPares + sumaImpares;

            // Se obtiene el dígito de las unidades
            Int32 unidades = sumaTotal % 10;

            // Si las unidades son distintas de 0, se restan de 10
            if (unidades != 0)
                unidades = 10 - unidades;

            switch (LetraInicial)
            {
                // Sólo números
                case "A":
                case "B":
                case "E":
                case "H":
                    retVal = DigitoControl == unidades.ToString();
                    break;

                // Sólo letras
                case "K":
                case "P":
                case "Q":
                case "S":
                    retVal = DigitoControl == letrasCodigo[unidades];
                    break;

                default:
                    retVal = (DigitoControl == unidades.ToString())
                            || (DigitoControl == letrasCodigo[unidades]);
                    break;
            }

            return retVal;

        }

        /// <summary>
        /// Obtiene la suma de todos los dígitos
        /// </summary>
        /// <returns>de 23, devuelve la suma de 2 + 3</returns>
        private Int32 SumaDigitos(Int32 digitos)
        {
            string sNumero = digitos.ToString();
            Int32 suma = 0;

            for (Int32 i = 0; i < sNumero.Length; i++)
            {
                Int32 aux;
                Int32.TryParse(sNumero[i].ToString(), out aux);
                suma += aux;
            }
            return suma;
        }

        /// <summary>
        /// Obtiene la letra correspondiente al Dni
        /// </summary>
        private string GetLetraNif()
        {
            int indice = Numero % 23;
            return "TRWAGMYFPDXBNJZSQVHLCKET"[indice].ToString();
        }

        /// <summary>
        /// Obtiene una cadena con el número de identificación completo
        /// </summary>
        public override string ToString()
        {
            string nif;
            string formato = "{0:0000000}";

            if (tipo == TiposCodigosEnum.CIF && LetraInicial == "")
                formato = "{0:00000000}";
            if (tipo == TiposCodigosEnum.NIF)
                formato = "{0:00000000}";
            
            nif = EsIntraComunitario ? CodigoIntracomunitario :
                string.Empty + LetraInicial + string.Format(formato,Numero ) + DigitoControl;
            return nif;
        }

        #endregion

        /// <summary>
        /// Comprobación de un número de identificación fiscal español
        /// </summary>
        /// <param name="numero">Numero a analizar</param>
        /// <returns>Instancia de <see cref="NumeroNif"/> con los datos del número.
        /// Destacable la propiedad <seealso cref="NumeroNif.EsCorrecto"/>, que contiene la verificación
        /// </returns>
        public static NumeroNif CompruebaNif(string numero)
        {
            return new NumeroNif(numero);
        }

    }

}
 

En el siguiente post intentaré comentar cómo podemos crear un control de usuario que nos valide la entrada de un CIF/NIF/NIE correcto en ASP Net. Digo intentaré porque últimamente me estoy oxidando mucho con el tema de los controles de ASP. ¿Cuál es la razón?, muy sencillo, que cada día soy más vago y últimamente me estoy habituando demasiado a trabajar con los controles de DevExpress que, tal como lo veo, ofrecen una gran cantidad de opciones que, de trabajar con controles estándares me costarían bastante de hacer.

7 thoughts on “C#. Validar CIF, NIF, NIE

  1. Una corrección pequeña para la validación del NIE. El método GetLetraNif funciona bien solo si el NIE empieza por X. La validación cambia si empieza por Y o Z. En la página http://es.wikibooks.org/wiki/Algoritmo_para_obtener_la_letra_del_NIF dice :

    “Este mismo algoritmo también puede utilizarse para el calculo del NIE. En el caso que el NIE empiece por X, se calcula despreciando la X y utilizando los 7 dígitos, si el NIE empieza por Y, se sustituye la letra Y por el número 1, si el NIE empieza por Z, se sustituye la letra Z por el número 2 y se realiza el mismo cálculo.”

    Saludos

  2. Con la modificación que comenta Kostras, bastaría añadir a la función CompruebaNif estas dos líneas:

    if (numero.Substring(0,1).ToString() == “Y”) numero = 1 + numero.Substring(1, 8);
    if (numero.Substring(0,1).ToString() == “Z”) numero = 2 + numero.Substring(1, 8);

  3. He tenido que cambiar la expresión regular en EliminaCaracteres de la forma:

    private string EliminaCaracteres(string numero)
    {
    // Todos los carácteres que no sean números o letras
    string caracteres = @”[^\\w]”;
    Regex regex = new Regex(caracteres);
    return regex.Replace(numero, “”);
    }

    Gracias. Un saludo.

  4. Para que validen correctamente numeros como Z-9373122Y , X-8978038B , Y-5493492W hay que añadir en el constructor, después de la llamada a EliminaCaracteres el siguiente codigo

    //correccion para los NIE, substitucion de la primera letra X,Y,Z por 0,1,2
    if (numero.Substring(0,1).ToString() == “X”)
    numero = 0 + numero.Substring(1, 8);
    if (numero.Substring(0,1).ToString() == “Y”)
    numero = 1 + numero.Substring(1, 8);
    if (numero.Substring(0,1).ToString() == “Z”)
    numero = 2 + numero.Substring(1, 8);

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *