Del pseudocódigo a Java: el algoritmo de Luhn de las tarjetas
Traduce a Java el algoritmo de Luhn con el que se comprueba que un número de tarjeta no tiene una errata: recorrer las cifras de derecha a izquierda doblando una sí y otra no, detectar la marca por el prefijo, comprobar la longitud, tapar el número y calcular el dígito de control que faltaría.
- Del pseudocódigo al código
- Recorrer una cadena al revés
- charAt y conversión a número
- Módulo y dígito de control
- switch como expresión
- StringBuilder
Enunciado
Los números de tarjeta, el IMEI de los móviles o algunos números de la Seguridad Social llevan al final un dígito de control calculado con el algoritmo de Luhn. No es para la seguridad: sirve para detectar al momento erratas al teclear (una cifra cambiada o dos cifras vecinas intercambiadas) sin tener que preguntar al banco.
Este es el algoritmo en pseudocódigo:
suma ← 0; doblar ← falso; PARA i DESDE la última cifra HASTA la primera: d ← cifra(i); SI doblar: d ← d × 2; SI d > 9: d ← d − 9; suma ← suma + d; doblar ← NO doblar. El número es válido SI suma MOD 10 = 0.
Qué tiene que hacer el programa
- Cada línea es un número de tarjeta, que puede llevar espacios o guiones. Sin ellos debe tener de 12 a 19 cifras; si no:
«línea»: formato no válido (de 12 a 19 cifras, con espacios o guiones). - La marca, por el principio:
4Visa (13, 16 o 19 cifras);34o37American Express (15); de51a55, o de2221a2720, Mastercard (16); cualquier otra,desconocida(cualquier longitud). - Cada número válido de formato escribe el número tapado (todas las cifras menos las 4 últimas cambiadas por
*, en grupos de 4 separados por un espacio, o 4-6-5 en American Express),·y la marca, y después·y uno de estos finales:no válida (una Marca no tiene N cifras),válidaono válida (el dígito de control debería ser D). - El dígito de control que debería tener se calcula aplicando el algoritmo a las cifras sin la última, empezando a doblar por la última de ellas: D = (10 − suma % 10) % 10.
- Al final:
Válidas: V de T(T cuenta todas las líneas no vacías).
Entrada
Un número de tarjeta por línea, con o sin espacios y guiones.
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.
Tarjetas de prueba
Entrada
4111 1111 1111 1111 5500-0000-0000-0004 3782 822463 10005 4111 1111 1111 1112
Salida por consola
**** **** **** 1111 · Visa · válida **** **** **** 0004 · Mastercard · válida **** ****** *0005 · American Express · válida **** **** **** 1112 · Visa · no válida (el dígito de control debería ser 1) Válidas: 3 de 4
Formatos y longitudes
Entrada
1234 4111 1111 1111 111 6011 0009 9013 9424 2221 0000 0000 0009 abcd efgh ijkl
Salida por consola
«1234»: formato no válido (de 12 a 19 cifras, con espacios o guiones) **** **** ***1 111 · Visa · no válida (una Visa no tiene 15 cifras) **** **** **** 9424 · desconocida · válida **** **** **** 0009 · Mastercard · válida «abcd efgh ijkl»: formato no válido (de 12 a 19 cifras, con espacios o guiones) Válidas: 2 de 5
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. Limpia el número
Quita espacios y guiones con replace y comprueba el formato con matches("\\d{12,19}").
2. El pseudocódigo, línea a línea
Un for desde length() - 1 hasta 0. charAt(i) - '0' convierte el carácter en su valor numérico. La variable doblar cambia en cada vuelta.
for (int i = cifras.length() - 1; i >= 0; i--) {
int d = cifras.charAt(i) - '0';
if (doblar) {
d *= 2;
if (d > 9) d -= 9;
}
suma += d;
doblar = !doblar;
}3. El dígito de control
Sin la última cifra, la que hay que doblar primero es la que ahora es la última. Por eso el método recibe si el número incluye ya el control (empieza sin doblar) o no (empieza doblando).
4. Tapar el número
"*".repeat(n - 4) más las cuatro últimas, y después ve cortando grupos con un StringBuilder.
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.
Ejemplo
4111 1111 1111 1111 5500-0000-0000-0004 3782 822463 10005 4111 1111 1111 1112
**** **** **** 1111 · Visa · válida **** **** **** 0004 · Mastercard · válida **** ****** *0005 · American Express · válida **** **** **** 1112 · Visa · no v álida (el dígito de control debería ser 1) Válidas: 3 de 4
Solución explicada
Ver la solución completa
1import java.util.Scanner;
2
3public class Main {
4
5 /** Suma de Luhn: desde la derecha, se dobla una cifra sí y otra no (empezando por la segunda si conControl). */
6 static int sumaLuhn(String cifras, boolean conControl) {
7 int suma = 0;
8 boolean doblar = !conControl;
9 for (int i = cifras.length() - 1; i >= 0; i--) {
10 int d = cifras.charAt(i) - '0';
11 if (doblar) {
12 d *= 2;
13 if (d > 9) d -= 9;
14 }
15 suma += d;
16 doblar = !doblar;
17 }
18 return suma;
19 }
20
21 static String marca(String c) {
22 int dos = Integer.parseInt(c.substring(0, 2));
23 int cuatro = Integer.parseInt(c.substring(0, 4));
24 if (c.startsWith("4")) return "Visa";
25 if (dos == 34 || dos == 37) return "American Express";
26 if ((dos >= 51 && dos <= 55) || (cuatro >= 2221 && cuatro <= 2720)) return "Mastercard";
27 return "desconocida";
28 }
29
30 static boolean longitudValida(String marca, int n) {
31 return switch (marca) {
32 case "Visa" -> n == 13 || n == 16 || n == 19;
33 case "American Express" -> n == 15;
34 case "Mastercard" -> n == 16;
35 default -> true;
36 };
37 }
38
39 /** Todas las cifras tapadas menos las 4 últimas, en grupos de 4 (American Express: 4-6-5). */
40 static String tapar(String c, String marca) {
41 String tapado = "*".repeat(c.length() - 4) + c.substring(c.length() - 4);
42 StringBuilder sb = new StringBuilder();
43 int[] grupos = marca.equals("American Express") ? new int[] {4, 6, 5} : null;
44 int i = 0, g = 0;
45 while (i < tapado.length()) {
46 int tam = grupos != null && g < grupos.length ? grupos[g] : 4;
47 if (sb.length() > 0) sb.append(' ');
48 sb.append(tapado, i, Math.min(tapado.length(), i + tam));
49 i += tam;
50 g++;
51 }
52 return sb.toString();
53 }
54
55 public static void main(String[] args) {
56 Scanner sc = new Scanner(System.in);
57 int total = 0, validas = 0;
58 while (sc.hasNextLine()) {
59 String linea = sc.nextLine().trim();
60 if (linea.isEmpty()) continue;
61 total++;
62 String c = linea.replace(" ", "").replace("-", "");
63 if (!c.matches("\\d{12,19}")) {
64 System.out.println("«" + linea + "»: formato no válido (de 12 a 19 cifras, con espacios o guiones)");
65 continue;
66 }
67 String m = marca(c);
68 String inicio = tapar(c, m) + " · " + m;
69 if (!longitudValida(m, c.length())) {
70 System.out.println(inicio + " · no válida (una " + m + " no tiene " + c.length() + " cifras)");
71 continue;
72 }
73 if (sumaLuhn(c, true) % 10 == 0) {
74 validas++;
75 System.out.println(inicio + " · válida");
76 } else {
77 int control = (10 - sumaLuhn(c.substring(0, c.length() - 1), false) % 10) % 10;
78 System.out.println(inicio + " · no válida (el dígito de control debería ser " + control + ")");
79 }
80 }
81 System.out.println("Válidas: " + validas + " de " + total);
82 }
83}Doblar una cifra sí y otra no hace que intercambiar dos cifras vecinas cambie la suma (casi siempre), y restar 9 cuando el doble pasa de 9 es lo mismo que sumar sus dos dígitos (16 → 1 + 6 = 7 = 16 − 9).
Luhn no protege de nada: cualquiera puede calcular un número válido. Solo evita mandar al banco números con erratas.
El paso del pseudocódigo al código es casi literal: cada línea del algoritmo es una línea de Java. Lo único que añade el lenguaje es cómo se obtiene el valor de un carácter (charAt(i) - '0').
switch como expresión (con -> y yield implícito) devuelve un valor directamente: es ideal para tablas de reglas como la longitud de cada marca.
Para ir más allá
- Valida también un IMEI (15 cifras, mismo algoritmo).
- Genera números de prueba válidos para una marca, calculando el dígito de control.
- Detecta qué errata se cometió: prueba a cambiar cada cifra y di cuáles darían un número válido.