Apuntes DAM
Volver al inicio

Validador de DNI, NIE e IBAN diseñado con clases de equivalencia

Ejercicio de JavaMedioUnos 50 minutos

Valida DNI, NIE e IBAN españoles con sus algoritmos de control (letra módulo 23 e IBAN módulo 97) y explica el motivo de cada rechazo. Las pruebas están diseñadas con clases de equivalencia y valores límite: el enunciado incluye la tabla para que veas de dónde sale cada caso.

  • Clases de equivalencia
  • Valores límite
  • Expresiones regulares
  • Aritmética modular
  • Métodos que devuelven el motivo del error
  • switch como expresión

Enunciado

Casi todas las aplicaciones de gestión piden un DNI, un NIE o un IBAN, y casi todas los validan mal: aceptan letras que no corresponden, rechazan minúsculas o se atascan con los espacios del IBAN. Los tres llevan un control matemático que permite detectar la mayoría de los errores al teclear.

La letra del DNI es el resto de dividir el número entre 23, buscado en la tabla TRWAGMYFPDXBNJZSQVHLCKE. El NIE de los extranjeros funciona igual cambiando la X, la Y y la Z iniciales por 0, 1 y 2. El IBAN usa otro método (ISO 13616): se pasan los cuatro primeros caracteres al final, cada letra se sustituye por un número (A = 10, B = 11… Z = 35) y el número resultante, de unas 26 cifras, tiene que dar resto 1 al dividirlo entre 97.

Este ejercicio es también de Entornos de Desarrollo: las pruebas no se han elegido al azar, sino con la técnica de clases de equivalencia (un caso representativo de cada grupo de entradas que el programa trata igual) y valores límite (los bordes de cada grupo). Tienes la tabla abajo: tu programa tiene que pasar todos esos casos.

Qué tiene que hacer el programa

  1. Cada línea es TIPO valor, con TIPO DNI, NIE o IBAN en mayúsculas o minúsculas. Las líneas vacías se ignoran. Para cada línea escribe TIPO valor → válido o TIPO valor → no válido: motivo, con el tipo en mayúsculas y el valor tal como venía (sin espacios al principio ni al final).
  2. DNI: 8 cifras y una letra (mayúscula o minúscula). Si no tiene ese formato, formato incorrecto (8 cifras y una letra); si la letra no es la que toca, la letra debería ser L.
  3. NIE: X, Y o Z, 7 cifras y una letra. Si no tiene ese formato, formato incorrecto (X, Y o Z, 7 cifras y una letra); si la letra no cuadra, el mismo mensaje que el DNI.
  4. IBAN (se ignoran los espacios y las minúsculas): si no empieza por ES, solo se admiten IBAN españoles; si no tiene 24 caracteres, un IBAN español tiene 24 caracteres; si tras ES hay algo que no sea una cifra, después de ES solo puede haber cifras; si el control módulo 97 no da 1, los dígitos de control no cuadran. Se comprueba en ese orden.
  5. Un tipo desconocido escribe TIPO valor → no válido: tipo de documento desconocido.
  6. Al final, Válidos: V de T.

Entrada

Una línea por documento: DNI valor, NIE valor o IBAN valor.

Datos de referencia

Clases de equivalencia y valores límite del DNI
ClaseCaso de pruebaResultado esperado
Válida: 8 cifras y su letra12345678Zválido
Válida: letra en minúscula12345678zválido
Límite inferior del número00000000Tválido
Límite superior del número99999999Rválido
No válida: letra que no toca12345678Ala letra debería ser Z
No válida: 7 cifras1234567Zformato incorrecto
No válida: 9 cifras123456789Zformato incorrecto
No válida: sin letra12345678formato incorrecto
Clases de equivalencia del IBAN
ClaseCaso de pruebaResultado esperado
Válida, con espaciosES91 2100 0418 4502 0005 1332válido
Válida, en minúsculas y sin espacioses9121000418450200051332válido
Otro paísDE89370400440532013000solo se admiten IBAN españoles
Longitud incorrectaES91210004184502000513324 caracteres
Letras tras ESES91 2100 0418 4502 0005 13ABsolo cifras
Control incorrecto (una cifra cambiada)ES91 2100 0418 4502 0005 1333no cuadran

Ejemplos de ejecución

Tu programa debe escribir exactamente esta salida para estas entradas. Las pruebas del editor incluyen estos ejemplos y otros casos ocultos.

DNI y NIE

Entrada

DNI 12345678Z
DNI 12345678z
DNI 12345678A
DNI 1234567Z
NIE X1234567L
NIE W1234567L
PASAPORTE AB123456

Salida por consola

DNI 12345678Z → válido
DNI 12345678z → válido
DNI 12345678A → no válido: la letra debería ser Z
DNI 1234567Z → no válido: formato incorrecto (8 cifras y una letra)
NIE X1234567L → válido
NIE W1234567L → no válido: formato incorrecto (X, Y o Z, 7 cifras y una letra)
PASAPORTE AB123456 → no válido: tipo de documento desconocido
Válidos: 3 de 7

IBAN

Entrada

IBAN ES91 2100 0418 4502 0005 1332
iban es9121000418450200051332
IBAN DE89370400440532013000
IBAN ES912100041845020005133
IBAN ES91 2100 0418 4502 0005 13AB
IBAN ES91 2100 0418 4502 0005 1333

Salida por consola

IBAN ES91 2100 0418 4502 0005 1332 → válido
IBAN es9121000418450200051332 → válido
IBAN DE89370400440532013000 → no válido: solo se admiten IBAN españoles
IBAN ES912100041845020005133 → no válido: un IBAN español tiene 24 caracteres
IBAN ES91 2100 0418 4502 0005 13AB → no válido: después de ES solo puede haber cifras
IBAN ES91 2100 0418 4502 0005 1333 → no válido: los dígitos de control no cuadran
Válidos: 2 de 6

Guía paso a paso

Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.

1. Un método por documento que explique el error

Haz que cada validador devuelva null si el documento es correcto o un String con el motivo. El main solo tiene que escribir lo que le digan, y cada validador se puede probar por separado (es lo que harías con JUnit).

2. La letra del DNI

Primero el formato con matches("\\d{8}[A-Z]") (tras pasar a mayúsculas). Después, la letra esperada es LETRAS.charAt(numero % 23).

java
char esperada = LETRAS.charAt(Integer.parseInt(dni.substring(0, 8)) % 23);
3. El NIE reutiliza el DNI

Comprueba su formato y sustituye la primera letra por su posición en "XYZ": el resultado es un DNI de 8 cifras con su letra, y puedes llamar a validarDni sin repetir código.

4. El módulo 97 sin números gigantes

El número del IBAN reordenado tiene 26 cifras: no cabe en un long. Pero el resto de una división se puede calcular cifra a cifra, como se hace a mano: resto = (resto * 10 + cifra) % 97. Las letras se cambian por su número de dos cifras antes.

java
for (char cifra : valor.toCharArray())
    resto = (resto * 10 + (cifra - '0')) % 97;
5. Prueba con la tabla

Antes de ejecutar las pruebas del editor, recorre la tabla de clases de equivalencia y comprueba que tu programa da el resultado esperado en cada fila. Si añades una regla nueva, añade también su clase.

Resuélvelo aquí

El editor trae el esqueleto del programa. Pulsa «Ejecutar» para comprobarlo con los ejemplos y con 2 casos ocultos que buscan los errores típicos.

☕JavaValidador de DNI, NIE e IBAN diseñado con clases de equivalenciaMedio

Ejemplo

Entrada (lo que se escribe por teclado)
DNI 12345678Z
DNI 12345678z
DNI 12345678A
DNI 1234567Z
NIE X1234567L
NIE W1234567L
PASAPORTE AB123456
Salida esperada
DNI 12345678Z → válido
DNI 12345678z → válido
DNI 12345678A → no válido: la letra debería ser Z
DNI 1234567Z → no válido: formato incorrecto (8 cifras y una letra)
NIE X1234567L → válido
NIE W1234567L → no válido: formato incorrecto (X, Y o Z, 7 cifras y una letra)
PASAPORTE AB123456 → no válido: tipo de documento desconocido
Válidos: 3 de 7
⏳
Test oculto #3
⏳
Test oculto #4
0/4 tests pasados · pulsa un test para ver su entrada y su salida esperada

Solución explicada

Ver la solución completa
java
1import java.util.Scanner;
2
3public class Main {
4    static final String LETRAS = "TRWAGMYFPDXBNJZSQVHLCKE";
5
6    /** null si el DNI es correcto, o el motivo por el que no lo es. */
7    static String validarDni(String dni) {
8        dni = dni.toUpperCase();
9        if (!dni.matches("\\d{8}[A-Z]")) return "formato incorrecto (8 cifras y una letra)";
10        char esperada = LETRAS.charAt(Integer.parseInt(dni.substring(0, 8)) % 23);
11        return dni.charAt(8) == esperada ? null : "la letra debería ser " + esperada;
12    }
13
14    /** El NIE se valida como un DNI cambiando X, Y y Z por 0, 1 y 2. */
15    static String validarNie(String nie) {
16        nie = nie.toUpperCase();
17        if (!nie.matches("[XYZ]\\d{7}[A-Z]")) return "formato incorrecto (X, Y o Z, 7 cifras y una letra)";
18        return validarDni("XYZ".indexOf(nie.charAt(0)) + nie.substring(1));
19    }
20
21    /** IBAN español: 24 caracteres y control módulo 97 (ISO 13616). */
22    static String validarIban(String iban) {
23        iban = iban.replace(" ", "").toUpperCase();
24        if (!iban.startsWith("ES")) return "solo se admiten IBAN españoles";
25        if (iban.length() != 24) return "un IBAN español tiene 24 caracteres";
26        if (!iban.substring(2).matches("\\d{22}")) return "después de ES solo puede haber cifras";
27        // Se pasan los 4 primeros caracteres al final, las letras a números (A = 10 … Z = 35)
28        // y se calcula el resto entre 97 cifra a cifra, sin construir un número enorme
29        String reordenado = iban.substring(4) + iban.substring(0, 4);
30        int resto = 0;
31        for (char c : reordenado.toCharArray()) {
32            String valor = Character.isLetter(c) ? String.valueOf(c - 'A' + 10) : String.valueOf(c);
33            for (char cifra : valor.toCharArray()) resto = (resto * 10 + (cifra - '0')) % 97;
34        }
35        return resto == 1 ? null : "los dígitos de control no cuadran";
36    }
37
38    public static void main(String[] args) {
39        Scanner sc = new Scanner(System.in);
40        int total = 0, validos = 0;
41        while (sc.hasNextLine()) {
42            String linea = sc.nextLine().trim();
43            if (linea.isEmpty()) continue;
44            int espacio = linea.indexOf(' ');
45            String tipo = (espacio < 0 ? linea : linea.substring(0, espacio)).toUpperCase();
46            String valor = espacio < 0 ? "" : linea.substring(espacio + 1).trim();
47            String error = switch (tipo) {
48                case "DNI" -> validarDni(valor);
49                case "NIE" -> validarNie(valor);
50                case "IBAN" -> validarIban(valor);
51                default -> "tipo de documento desconocido";
52            };
53            total++;
54            if (error == null) validos++;
55            System.out.println(tipo + (valor.isEmpty() ? "" : " " + valor) + " → " + (error == null ? "válido" : "no válido: " + error));
56        }
57        System.out.println("Válidos: " + validos + " de " + total);
58    }
59}

Cada validador devuelve el motivo del error en lugar de un simple boolean. Eso hace el programa más útil para el usuario y las pruebas más precisas: no basta con saber que algo es inválido, hay que saber que lo es por la razón correcta.

El NIE se reduce a un DNI y se valida con el mismo método: reutilizar en lugar de copiar significa que un error corregido en el DNI queda corregido también en el NIE.

El cálculo del módulo 97 cifra a cifra se basa en que (a × 10 + b) mod 97 = ((a mod 97) × 10 + b) mod 97: nunca hay un número mayor que 970. Es lo que permite comprobar IBAN en cualquier lenguaje sin BigInteger.

Las pruebas salen de la tabla de clases de equivalencia y valores límite: un caso por cada grupo que el programa trata igual y los extremos (00000000 y 99999999). Con ocho casos bien elegidos se cubre lo que no cubrirían cien números al azar.

Para ir más allá

  • Valida también el CIF de las empresas, que tiene un dígito o letra de control calculado de otra forma.
  • Escribe las pruebas de los tres validadores con JUnit 5 y @ParameterizedTest a partir de las tablas.
  • Calcula los dígitos de control de un IBAN a partir del código de cuenta (CCC) de 20 cifras.

Dónde se explica