Algoritmia/Algoritmo para obtener la letra del NIF

El número de identificación fiscal (NIF) español es un código único que identifica a todos los ciudadanos españoles a efectos fiscales. Partiendo del tradicional documento nacional de identidad, DNI, añade a éste una letra que actúa como elemento verificador.

Algoritmo

editar

La letra del NIF se obtiene a partir de un algoritmo conocido como módulo 23. El algoritmo consiste en aplicar la operación aritmética de módulo 23 al número del DNI. El módulo 23 es el número entero obtenido como resto de la división entera del número del DNI entre 23. El resultado es un número comprendido entre el 0 y el 22. En base a una tabla conocida se asigna una letra. La combinación del DNI con esa letra es el NIF.

Este mismo algoritmo también puede utilizarse para el cálculo 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.

El algoritmo no se aplica para obtener el Código de Identificación Fiscal (CIF), que es el "NIF" propio de las personas jurídicas, pues la letra que tiene no se basa en una fórmula, sino que identifica el tipo de entidad (p.e. B para Sociedades Limitadas; G para Asociaciones sin ánimo de lucro y otros tipos no definidos, etc.).

Tabla de asignación

editar
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
T R W A G M Y F P D X B N J Z S Q V H L C K E

No se utilizan las letras: I, Ñ, O, U

La I y la O se descartan para evitar confusiones con otros caracteres, como 1, l o 0.

Se usan veintitrés letras por ser éste un número primo.

Ejemplos

editar

Algunos ejemplos de la implementación de este algoritmo para distintos lenguajes de programación son:

Éste es el código fuente en Ada para calcular la letra del NIF.

function Letra_Nif (Dni : Positive) return Character is
   Letras : constant String := "TRWAGMYFPDXBNJZSQVHLCKE";
begin
   return Letras ((Dni mod Letras'Length) + 1);
end Letra_Nif;

Éste es el código fuente en Matlab para calcular la letra del NIF.

dni=12345678
res=dni-floor(dni/23)*23;
A=['T','R','W','A','G','M','Y','F','P','D','X','B','N','J','Z','S','Q','V','H','L','C','K','E'];
letra=A(res+1)


Código fuente del cálculo de la Letra del NIF en AppleScript

on LetraDNI(dni)
    return character (1 + (dni as integer) mod 23) of "TRWAGMYFPDXBNJZSQVHLCKE"
end LetraDNI
Function sacaLetra(ByVal dni)
    Dim tabla,pos
    tabla = Array("T","R","W","A","G","M","Y","F","P","D","X","B","N","J","Z","S","Q","V","H","L","C","K","E")
    pos = dni mod 23
    sacaLetra = tabla(pos)
End Function

Y para Validar el NIF:

Function validaNIF(ByVal nif)
    Dim dni
    If Len(nif)<7 Then
        validaNIF = false
    Else
        dni = Left(nif,Len(nif)-1)
        validaNIF = UCase(nif)=dni&sacaLetra(dni)
    End If
End Function
echo TRWAGMYFPDXBNJZSQVHLCKE | cut -c $(( $(( $1 % 23 )) + 1 ))

otra forma sin utilizar las órdenes externas (echo y cut)

LETRANIF="TRWAGMYFPDXBNJZSQVHLCKE"
DNI=12345678
echo ${LETRANIF:$DNI % 23:1}
    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    
    /* numero de cifras para el DNI */
    #define kTAM 8
    
    int main(int argc, char *argv[])
    {
        int  dni;
        char letra[] = "TRWAGMYFPDXBNJZSQVHLCKE";
    
        if (argc != 2) {
                printf("Uso: %s <DNI>\n", argv[0]);
        } else {
            if (strlen(argv[1]) != kTAM) {
                puts("DNI no valido.");
            } else {
                dni = atoi (argv[1]);
                dni %= 23;
                printf("%s-%c\n", argv[1],letra[dni]);
             }
        }
    
        return 0;
    }
#include <iostream>
using namespace std;

char letraDNI(unsigned int dni);

char letraDNI(unsigned int dni)
{
    return "TRWAGMYFPDXBNJZSQVHLCKE"[dni % 23];
}

int main()
{
    int dni;
    cout << "Introduce el DNI: ";
    cin >> dni;
    char letra = letraDNI(dni);
    return 0;
}
AnsiString Letra(AnsiString Nif)
{
     int nSuma, nSumaPar, nSumaNon;

     if (Nif.IsEmpty())
          return ("");

     if (isdigit(Nif.c_str()[0]) || AnsiString("KLMXYZ").Pos(Nif.SubString(1, 1)) != 0)  // Persona física
     {
          if (AnsiString("XYZ").Pos(Nif.SubString(1, 1)) != 0)
               Nif = iif(Nif.SubString(1, 1) == "Z", "2", iif(Nif.SubString(1, 1) == "Y", "1", "0")) + Nif.SubString(2, Nif.Length());
          else if (AnsiString("KLM").Pos(Nif.SubString(1, 1)) != 0)
               Nif = Nif.SubString(2, Nif.Length());
               
          return ("TRWAGMYFPDXBNJZSQVHLCKE"[StrToInt(Trim(Nif)) % 23]);
     }            
     else      // Persona jurídica
     {
          if (AnsiString("ABCDEFGHJNPQRSUVW").Pos(Nif.SubString(1, 1)) == 0)
               return ("");

          nSumaPar = 0;
          nSumaNon = 0;
          for (int nLetra = 2; nLetra < 9; nLetra += 2)
          {
               if (nLetra < 8)
                    nSumaPar += StrToInt(Nif.SubString(nLetra + 1, 1));

               nSumaNon += ((2 * StrToInt(Nif.SubString(nLetra, 1))) % 10) + ((2 * StrToInt(Nif.SubString(nLetra, 1))) / 10);
          }

          nSuma = nSumaPar + nSumaNon;
          nSuma = 10 - (nSuma % 10);
          if (nSuma == 10)
               nSuma = 0;

          if (AnsiString("ABDEFGHJUV").Pos(Nif.SubString(1, 1)) == 0)
               return ("ABCDEFGHIJ"[nSuma]);
          else
               return (FormatFloat("0", nSuma));
     }
}
/// <summary> Genera la letra correspondiente a un DNI. </summary>
public: System::String^ LetraNIF(System::String^ dni)
{
	System::String^ Correspondencia = "TRWAGMYFPDXBNJZSQVHLCKE";
	return Correspondencia[ Convert::ToInt32(dni) % 23 ].ToString() ;
}

Corrección comprobada con datos reales:

  • El DNI esta compuesto de 9 caracteres 8 dígitos+Letra.
  • El NIE esta compuesto de 9 caracteres Letra+7digitos+Letra, debe tenerse esto encuenta al realizar las validaciones de tamaño.

En las comparaciones longitud y conversión a entero, debe poner (nie.Length != 9) y (!int.TryParse(nie.Substring(1,7), out n) o jamas aceptara un NIE real como valido.

/// <summary> Tabla de asignación. </summary>
public const string CORRESPONDENCIA = "TRWAGMYFPDXBNJZSQVHLCKE";

/// <summary> Genera la letra correspondiente a un DNI. </summary>
/// <param name="dni"> DNI a procesar. </param>
/// <returns> Letra correspondiente al DNI. </returns>
public char LetraNIF(string dni)
{
	int n;
	
	//if ((dni == null) || (dni.Length != 8) || (!int.TryParse(dni, out n)))
        if ((dni == null) || (dni.Length != 9) || (!int.TryParse(dni.Substring(0,8), out n)))
	{
		throw new ArgumentException("El DNI debe contener 8 dígitos.");
	}
	
	return CORRESPONDENCIA[n % 23];
}

/// <summary> Genera la letra correspondiente a un NIE. </summary>
/// <param name="nie"> NIE a procesar. </param>
/// <returns> Letra correspondiente al NIE. </returns>
public char LetraNIE(string nie)
{
	int n;
//Linea original, editado en caso practico 
//if ((nie == null) || (nie.Length != 8) || ((char.ToUpper(nie[0]) != 'X') && (char.ToUpper(nie[0]) != 'Y') && (char.ToUpper(nie[0]) != 'Z')) || (!int.TryParse(nie.Substring(1), out n)))
	if ((nie == null) || (nie.Length != 9) || ((char.ToUpper(nie[0]) != 'X') && (char.ToUpper(nie[0]) != 'Y') && (char.ToUpper(nie[0]) != 'Z')) || (!int.TryParse(nie.Substring(1,7), out n)))
	{
		throw new ArgumentException("El NIE debe comenzar con la letra X, Y o Z seguida de 7 dígitos.");
	}

	switch (char.ToUpper(nie[0]))
	{
		case 'X':
			return CORRESPONDENCIA[n % 23];
		case 'Y':
			return CORRESPONDENCIA[(10000000 + n) % 23];
                case 'Z':
			return CORRESPONDENCIA[(20000000 + n) % 23];
		default:
			return '\0';
	}
}

C# (versión mejorada con expresiones regulares)

editar

Versión mejorada que usa expresiones regulares para comprobar que el DNI está compuesto por 8 dígitos y que el nie está compuesto por el caracter 'X', 'Y' o 'Z' (independientemente de que sea en mayúsculas o minúsculas) seguido de 7 dígitos.

/// <summary> Tabla de asignación. </summary>
public const string CORRESPONDENCIA = "TRWAGMYFPDXBNJZSQVHLCKE";

/// <summary> Genera la letra correspondiente a un DNI. </summary>
/// <param name="dni"> DNI a procesar. </param>
/// <returns> Letra correspondiente al DNI. </returns>
static public char LetraNIF(string dni)
{
	Match match = new Regex(@"\b(\d{8})\b").Match(dni);
	if (match.Success)
		return CORRESPONDENCIA[int.Parse(dni) % 23];
	else
		throw new ArgumentException("El DNI debe contener 8 dígitos.");
}

NOTA: Si la expresión anterior no cuenta bien la cantidad de dígitos probar con: 
Match match = new Regex(@"\d{8}").Match(dni);

/// <summary> Genera la letra correspondiente a un NIE. </summary>
/// <param name="nie"> NIE a procesar. </param>
/// <returns> Letra correspondiente al NIE. </returns>
static public char LetraNIE(string nie)
{
	Match match = new Regex(@"\b([X|Y|Z|x|y|z])(\d{7})\b").Match(nie);
	if (match.Success)
	{
		int n = int.Parse(match.Groups[2].Value); // también se podría haber usado como parámetro nie.Substring(1, 7)
		switch (char.ToUpper(nie[0])) // también se podría haber usado como parámetro Char.ToUpper(((String)match.Groups[1].Value)[0])
		{
			case 'X':
				return CORRESPONDENCIA[n % 23];
			case 'Y':
				return CORRESPONDENCIA[(10000000 + n) % 23];
			case 'Z':
				return CORRESPONDENCIA[(20000000 + n) % 23];
			default:
				return '\0';
		}
	}
	else
		throw new ArgumentException("El NIE debe comenzar con la letra X, Y o Z seguida de 7 dígitos.");
}
  *****************************************************
  * devuelve la letra correspondiente a un número DNI *
  *****************************************************
   WORKING-STORAGE SECTION.                                    
 01 WS-WORK-AREA.                                            
                                                             
    05 COM-AREA.                                             
       10 COM-DNI        PIC 9(8).                           
       10 COM-LETRA      PIC X.                              
       10 COM-OK         PIC XX.                             
                                                             
    05 AUX-COCIENTE      PIC 9(9)       VALUE ZEROS.         
    05 AUX-RESTO         PIC 99         VALUE ZEROS.         
    05 CTE-23            PIC 99         VALUE 23.            
                                                             
    05 LETRAS     PIC X(23) VALUE 'TRWAGMYFPDXBNJZSQVHLCKE'. 
    05 TBL-LETRA  REDEFINES LETRAS                           
                  OCCURS 23 TIMES                            
                  PIC X.                                     
  LINKAGE SECTION.                           
 01 LS-DATOS             PIC X(11).         
                                            
 PROCEDURE DIVISION USING LS-DATOS.         
 1000-PRINCIPAL.                            
     PERFORM 2000-INICIO                    
        THRU 2000-INICIO-EXIT.              
                                            
     PERFORM 3000-PROCESO                   
        THRU 3000-PROCESO-EXIT.             
                                            
     PERFORM 8000-FIN                       
        THRU 8000-FIN-EXIT.                 
                                            
     GOBACK.                                
                                            
 2000-INICIO.                               
     DISPLAY 'COMIENZA EL PROGRAMA LETRADNI'.
     MOVE LS-DATOS TO COM-AREA.             
                                            
 2000-INICIO-EXIT.                          
     EXIT.                                  
 3000-PROCESO.                                    
     COMPUTE AUX-RESTO = FUNCTION MOD (COM-DNI, CTE-23) + 1.
     MOVE TBL-LETRA(AUX-I) TO COM-LETRA.  
                                                  
 3000-PROCESO-EXIT.                               
     EXIT.                                        
                                                  
 8000-FIN.                                        
     MOVE COM-AREA TO LS-DATOS.                   
                                                  
     DISPLAY 'FIN DEL PROGRAMA LETRADNI'.          
 8000-FIN-EXIT.
 program TREVISONE
 implicit none
 integer :: numero
 character(len=23) :: string='TRWAGMYFPDXBNJZSQVHLCKE'
 integer :: nif
     
     write(*,'(a)') 'Este programa calcula su letra del NIF a partir de su numero de DNI'
     write(*,'(a)') 'Introduzca su TREVISONE'
     read(*,*) numero
     
     nif=mod(numero,23)+1
     
     write(*,'(a,2x,a)') 'Su letra del NIF es:', string(nif:nif) 
     
 end program TREVISONE
  public static final String NIF_STRING_ASOCIATION = "TRWAGMYFPDXBNJZSQVHLCKE";
  
  /**
   * Devuelve un NIF completo a partir de un DNI. Es decir, añade la letra del NIF
   * @param dni dni al que se quiere añadir la letra del NIF
   * @return NIF completo.
   */
  public static String letraDNI(int dni) {
    return String.valueOf(dni) + NIF_STRING_ASOCIATION.charAt(dni % 23);
  }
  // devuelve la letra correspondiente a un número DNI
  function letraDni(dni) {
    return "TRWAGMYFPDXBNJZSQVHLCKE".charAt(dni % 23);
  }

Ejecutable en navegador:

 Javascript:letraDni(numeroDni);
Function IsValidNIF(ANIF: String): Boolean;
Var
  sChar, sDNI: String;
begin
  Result := FALSE;

  ANIF := UpperCase(ANIF);

  If Length(Trim(ANIF)) = 9 Then
  Begin
   If ANIF[1] In ['0'..'9'] THen
     sDNI := Copy(ANIF, 1, 8)
   Else
     sDNI := Copy(ANIF, 2, 7);

   sChar  := Copy('TRWAGMYFPDXBNJZSQVHLCKE', StrToInt(sDNI) Mod 23 + 1, 1);
   Result := sChar = ANIF[9];
  End;
end;
    sub LetraDNI {
        my $dni = shift;
        return substr( 'TRWAGMYFPDXBNJZSQVHLCKE', $dni % 23, 1);
    }
 
    function letra_nif($dni) {
        return substr("TRWAGMYFPDXBNJZSQVHLCKE",strtr($dni,"XYZ","012")%23,1);
    }
 
    $numero = "12345678";
    echo 'El NIF del DNI "'.$numero.'" es "'.$numero.letra_nif($numero).'"';
    def letra_nif(numeros):
        return "TRWAGMYFPDXBNJZSQVHLCKE"[numeros%23]
     puts "TRWAGMYFPDXBNJZSQVHLCKE"[gets.to_i % 23].chr
     Private Function NIF(DNI As Long)
          NIF = DNI & "-" & Mid$("TRWAGMYFPDXBNJZSQVHLCKE", (DNI Mod 23) + 1, 1)
     End Function
 
   =CONCATENAR(A1;MED("TRWAGMYFPDXBNJZSQVHLCKE";RESTO(A1;23)+1;1))
   =CONCATENAR(A1;EXTRAE("TRWAGMYFPDXBNJZSQVHLCKE";RESIDUO(A1;23)+1;1))
   =CONCATENATE(A1,MID("TRWAGMYFPDXBNJZSQVHLCKE",MOD(A1,23)+1,1))
En calc (OpenOffice)
=CONCATENAR(A1,MID("TRWAGMYFPDXBNJZSQVHLCKE",RESIDUO(A1,23)+1,1))

Siendo A1 la celda de origen del DNI sin letra.

  declare @nif varchar(9)
  declare @dni int 
  
  set @dni = 12345678
  
  set @nif = CONVERT(varchar(8),@dni) + SUBSTRING('TRWAGMYFPDXBNJZSQVHLCKE', @dni % 23 + 1, 1)

  print @nif

en sentencias SELECT:

 
  SELECT CONVERT(varchar(8),tablaPersonas.dni) + SUBSTRING('TRWAGMYFPDXBNJZSQVHLCKE', tablaPersonas.dni % 23 + 1, 1)
  FROM tablaPersonas

o más simple para ORACLE:

 
 SELECT dni||SUBSTR('TRWAGMYFPDXBNJZSQVHLCKE',MOD(dni,23)+1,1)
 FROM tablaPersonas

siendo tablaPersonas la tabla con el dato de usuario y el campo dni el origen del DNI y en formato entero (int).

  FORM resultado USING VALUE(GV_NUMBER)
    DATA GV_result(9) TYPE c.
    DATA lt_letters(24) TYPE c VALUE 'TRWAGMYFPDXBNJZSQVHLCKE'.
    DATA lv_num TYPE i.
    DATA lv_number(8) type n.
    lv_number = gv_number.
    lv_num = gv_number MOD 23.
    gv_character = lt_letters+lv_num(1).
    CONCATENATE lv_number gv_character INTO gv_result.
  ENDFORM.                    " resultado

Siendo gv_result el nº de nif con la letra.

CALCULAR_LETRA: PROC(DNI) RETURNS(CHAR);
    DCL DNI        PIC '99999999';
    DCL LETRA      CHAR           INIT (' ');
    DCL LETRAS     CHAR(23)       INIT ('TRWAGMYFPDXBNJZSQVHLCKE');
    DCL VECTOR(23) DEFINED LETRAS CHAR(1);
    DCL RESTO      DEC FIXED(2)   INIT(0);
    RESTO = MOD(DNI,23)+1;
    LETRA = VECTOR(RESTO);
    RETURN(LETRA);
END CALCULAR_LETRA;

Previamente hay que declarar en el programa que se importa la función del sistema para hallar restos:

DCL (MOD) BUILTIN;

Enlaces externos

editar