El Algoritmo de Luhn: El Guardián de tus Números de Tarjeta 💳

El Algoritmo de Luhn: El Guardián de tus Números de Tarjeta 💳

¿Alguna vez te has preguntado cómo un sitio web sabe instantáneamente si el número de tu tarjeta de crédito es falso o tiene un error antes de procesar el pago? La respuesta es el Algoritmo de Luhn.

También conocido como el algoritmo de módulo 10 mod 10, fue creado por el científico de IBM Hans Peter Luhn en 1954. Es una fórmula de suma de verificación (checksum) simple pero poderosa, utilizada para validar una variedad de números de identificación.

¿Dónde se utiliza?

  • Tarjetas de Crédito: Visa, MasterCard, American Express. 💳
  • IMEI: Números de identidad de teléfonos móviles.
  • Seguridad Social: En algunos países para validar documentos nacionales.
  • Números de cuenta bancaria
  • Códigos de barras de algunos productos

Cómo funciona el algoritmo (Paso a paso)

El algoritmo valida un número basándose en cálculos matemáticos sobre sus dígitos de derecha a izquierda.

  1. Empezar por la derecha: Desde el último dígito (el dígito de control), muévete hacia la izquierda.
  2. Duplicar cada segundo dígito: Empezando por el segundo dígito desde la derecha, duplica el valor de cada segundo dígito.
  3. Ajustar resultados: Si al duplicar un número el resultado es mayor a 9 (por ejemplo, $8 \times 2 = 16$), suma los dígitos del resultado ($1 + 6 = 7$) o simplemente réstale 9 ($16 - 9 = 7$).
  4. Sumar todo: Suma todos los dígitos finales (tanto los que duplicaste como los que dejaste igual).
  5. Verificación final: Si el total de la suma termina en 0 (es decir, total % 10 == 0), entonces el número es válido.

Inforgrafia algortmo de Luhn

Ejemplo Práctico:

  1. Tomemos como ejemplo el número: 4539 1488 0343 6467

    Paso 1 — Separar el dígito de control

    El último dígito (7 en nuestro ejemplo) es el dígito de control (check digit). Lo apartamos temporalmente.

    4 5 3 9  1 4 8 8  0 3 4 3  6 4 6  [7]
                                        ↑
                                  dígito de control
    

    Paso 2 — Duplicar los dígitos en posición par (de derecha a izquierda)

    Empezando desde el dígito más a la derecha del número restante y moviéndonos a la izquierda, duplicamos los dígitos en posición par (2ª, 4ª, 6ª…):

    Posición:  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15
    Dígito:    4  5  3  9  1  4  8  8  0  3  4  3  6  4  6
    Acción:       ×2    ×2    ×2    ×2    ×2    ×2    ×2    ×2
    Resultado: 4  10 3  18 1  8  8  16 0  6  4  6  6  8  6
    

    Paso 3 — Restar 9 si el resultado es mayor que 9

    10 → 10 - 9 = 1
    18 → 18 - 9 = 9
    16 → 16 - 9 = 7
     6 →  6      = 6
    

    Equivalente a sumar los dígitos del resultado: 10 → 1+0 = 1.

    4  1  3  9  1  8  8  7  0  6  4  6  6  8  6
    

    Paso 4 — Sumar todos los dígitos

    4+1+3+9+1+8+8+7+0+6+4+6+6+8+6 = 77
    

    Paso 5 — Añadir el dígito de control y verificar

    77 + 7 = 84
    

    Si el resultado es divisible entre 10 → número válido

    84 % 10 = 4  ← ¡No es 0!
    

    Eso significa que 4539 1488 0343 6467 no pasaría la validación. Usemos un número real válido: 4532015112830366


    Ejemplo completo con número real válido

    Número: 4532 0151 1283 0366

    Posición (der→izq) Dígito Acción Resultado Si >9, -9
    1 (control) 6
    2 6 ×2 12 3
    3 3 3
    4 0 ×2 0 0
    5 3 3
    6 8 ×2 16 7
    7 2 2
    8 1 ×2 2 2
    9 1 1
    10 5 ×2 10 1
    11 1 1
    12 0 ×2 0 0
    13 2 2
    14 3 ×2 6 6
    15 5 5
    16 4 ×2 8 8

    Suma = 3+3+0+3+7+2+2+1+1+0+2+6+5+8 = 44

    44 + 6 (dígito de control) = 50

    50 % 10 = 0 → ✅ Válido

    ¿Cuántos números válidos existen? Dado un número de N dígitos, aproximadamente 1 de cada 10 números aleatorios pasará la validación de Luhn. Esto significa que un atacante que intente adivinar números de tarjeta a ciegas tendría una tasa de éxito del ~10% solo en la validación básica, antes de cualquier verificación adicional del banco.

    ¿Puedo usar Luhn para validar tarjetas de crédito reales en mi app? Sí, como primera capa de validación en el cliente. Sin embargo, la validación definitiva siempre debe hacerse contra la red de pago (Visa, Mastercard, etc.) a través de un procesador de pagos certificado (Stripe, Redsys, Adyen…). Luhn solo descarta números imposibles, no confirma que la tarjeta exista o tenga fondos. ¿Por qué empezar a contar desde la derecha y no desde la izquierda? Porque el número de dígitos puede variar entre emisores (14, 15 o 16 dígitos). Anclando el conteo desde la derecha, el dígito de control siempre es la posición 1 (impar, sin duplicar), y el patrón de duplicación se mantiene constante independientemente de la longitud total. ¿El algoritmo cambia si el número tiene un número impar de dígitos? No. La lógica es idéntica: se empieza desde el dígito de control hacia la izquierda, duplicando los dígitos en posición par. La paridad total del número no afecta el resultado. ¿Cómo se genera un número de tarjeta válido de prueba? Se elige un prefijo según el emisor (p. ej. 4 para Visa), se rellena con dígitos aleatorios hasta N-1 posiciones y se calcula el último dígito con luhnGenerate. Los números de prueba oficiales como 4111 1111 1111 1111 se generaron exactamente así. ¿Existe algún checksum más potente para tarjetas? No se necesita. La validación criptográfica real ocurre en la capa de red de pago, no en el número de tarjeta en sí. Luhn existe solo para filtrar errores de escritura, y para ese propósito específico es perfectamente adecuado.

    Prefijos de tarjetas más comunes

    Emisor Prefijo Longitud
    Visa 4 16 dígitos
    Mastercard 51-55, 2221-2720 16 dígitos
    American Express 34, 37 15 dígitos
    Discover 6011, 622126-622925 16 dígitos
    Diners Club 300-305, 36, 38 14 dígitos

    Todos estos números, sin excepción, pasan la validación de Luhn.


{% include_relative luhn_interactive_validator.html %}

Enlaces relacionados

  • Ver en GitHub (interfaz):
    https://github.com/enfaseterminal/cacharreria/blob/main/validador-numero-tarjeta-credito/luhn_interactive_validator.html

  • Abrir raw (para guardar):
    https://raw.githubusercontent.com/enfaseterminal/cacharreria/main/validador-numero-tarjeta-credito/luhn_interactive_validator.html


Ejemplos en Lenguajes de Programación

1. Python

Python es excelente para esto por su facilidad para manejar listas y comprensiones.

Python

def luhn_validator(card_number):
    digits = [int(d) for d in str(card_number)]
    # Tomamos todos menos el último para procesar, luego sumamos el último
    checksum = digits[-1]
    payload = digits[:-1][::-1]
    
    for i, d in enumerate(payload):
        if i % 2 == 0:
            d *= 2
            if d > 9: d -= 9
        checksum += d
        
    return checksum % 10 == 0

# Prueba
print(luhn_validator(79927398713)) # True

2. JavaScript

Ideal para validaciones en el frontend antes de enviar formularios.

JavaScript

function validateLuhn(number) {
    let sum = 0;
    let shouldDouble = false;
    
    // Recorrer de derecha a izquierda
    for (let i = number.length - 1; i >= 0; i--) {
        let digit = parseInt(number.charAt(i));

        if (shouldDouble) {
            digit *= 2;
            if (digit > 9) digit -= 9;
        }

        sum += digit;
        shouldDouble = !shouldDouble;
    }

    return (sum % 10) === 0;
}

console.log(validateLuhn("79927398713")); // true

3. Java

Una versión robusta utilizando tipos de datos estándar.

Java

public class Luhn {
    public static boolean check(String number) {
        int sum = 0;
        boolean alternate = false;
        for (int i = number.length() - 1; i >= 0; i--) {
            int n = Integer.parseInt(number.substring(i, i + 1));
            if (alternate) {
                n *= 2;
                if (n > 9) n -= 9;
            }
            sum += n;
            alternate = !alternate;
        }
        return (sum % 10 == 0);
    }
}

4. C++

Ideal para sistemas de alto rendimiento o embebidos donde la eficiencia de memoria es clave.

C++

#include <iostream>
#include <string>
#include <algorithm>

bool validateLuhn(const std::string& cardNumber) {
    int sum = 0;
    bool isSecond = false;

    // Recorremos desde el final hacia el principio
    for (int i = cardNumber.length() - 1; i >= 0; i--) {
        int d = cardNumber[i] - '0';

        if (isSecond) {
            d = d * 2;
            if (d > 9) d -= 9;
        }

        sum += d;
        isSecond = !isSecond;
    }

    return (sum % 10 == 0);
}

int main() {
    std::string test = "79927398713";
    std::cout << (validateLuhn(test) ? "Válido" : "Inválido") << std::endl;
    return 0;
}

5. PHP

Muy utilizado en plataformas de e-commerce para validar datos de tarjetas antes de enviarlos a la pasarela de pago.

PHP

function validateLuhn($number) {
    $sum = 0;
    $shouldDouble = false;

    // Invertir y recorrer
    for ($i = strlen($number) - 1; $i >= 0; $i--) {
        $digit = (int)$number[$i];

        if ($shouldDouble) {
            $digit *= 2;
            if ($digit > 9) {
                $digit -= 9;
            }
        }

        $sum += $digit;
        $shouldDouble = !$shouldDouble;
    }

    return ($sum % 10 === 0);
}

echo validateLuhn("79927398713") ? "Válido" : "Inválido";

6. Swift

La opción estándar para aplicaciones iOS. Útil para validar números de tarjeta en el Apple Wallet o apps financieras.

Swift

func isValidLuhn(_ number: String) -> Bool {
    let reversedDigits = number.compactMap { Int(String($0)) }.reversed()
    var sum = 0
    
    for (index, digit) in reversedDigits.enumerated() {
        if index % 2 == 1 {
            let doubled = digit * 2
            sum += doubled > 9 ? doubled - 9 : doubled
        } else {
            sum += digit
        }
    }
    
    return sum % 10 == 0
}

print(isValidLuhn("79927398713")) // true

7. Rust

Para quienes buscan seguridad de memoria y velocidad extrema. Aquí usamos un enfoque funcional.

Rust

fn validate_luhn(number: &str) -> bool {
    number.chars()
        .rev()
        .enumerate()
        .map(|(i, c)| {
            let mut digit = c.to_digit(10).unwrap() as i32;
            if i % 2 == 1 {
                digit *= 2;
                if digit > 9 { digit -= 9; }
            }
            digit
        })
        .sum::<i32>() % 10 == 0
}

fn main() {
    let card = "79927398713";
    println!("Es válido: {}", validate_luhn(card));
}

8. Go (Golang)

Go es muy eficiente para procesar grandes volúmenes de datos. Aquí usamos un enfoque directo y tipado.

Go

package main

import "fmt"

func validateLuhn(number string) bool {
    var sum int
    shouldDouble := false

    // Recorremos el string desde el final al principio
    for i := len(number) - 1; i >= 0; i-- {
        // Convertimos el byte de carácter ASCII a su valor entero
        digit := int(number[i] - '0')

        if shouldDouble {
            digit *= 2
            if digit > 9 {
                digit -= 9
            }
        }

        sum += digit
        shouldDouble = !shouldDouble
    }

    return sum%10 == 0
}

func main() {
    fmt.Println(validateLuhn("79927398713")) // true
}

9. C#

En C#, podemos usar LINQ para hacer el código extremadamente compacto y declarativo, lo que lo hace muy legible.

C#

using System;
using System.Linq;

public class LuhnValidator {
    public static bool IsValid(string number) {
        return number.Reverse()
            .Select((c, i) => (int)char.GetNumericValue(c) * (i % 2 == 1 ? 2 : 1))
            .Select(n => n > 9 ? n - 9 : n)
            .Sum() % 10 == 0;
    }
}

// Uso:
// Console.WriteLine(LuhnValidator.IsValid("79927398713"));

10. Ruby

Ruby brilla por su capacidad de escribir código que parece lenguaje natural. Es ideal para scripts de validación rápidos.

Ruby

def valid_luhn?(number)
  digits = number.chars.map(&:to_i).reverse
  
  sum = digits.each_with_index.map do |digit, index|
    if index.odd?
      val = digit * 2
      val > 9 ? val - 9 : val
    else
      digit
    end
  end.sum
  
  (sum % 10).zero?
end

puts valid_luhn?("79927398713") # true

11. C

#include <stdio.h>
#include <string.h>
#include <ctype.h>

int luhn_check(const char *number) {
    int digits[20], len = 0;
    
    // Extraer solo dígitos
    for (int i = 0; number[i] != '\0' && len < 20; i++) {
        if (isdigit(number[i])) {
            digits[len++] = number[i] - '0';
        }
    }
    
    if (len < 2) return 0;
    
    int total = 0;
    int double_digit = 0;
    
    // Recorrer de derecha a izquierda
    for (int i = len - 1; i >= 0; i--) {
        int d = digits[i];
        
        if (double_digit) {
            d *= 2;
            if (d > 9) d -= 9;
        }
        
        total += d;
        double_digit = !double_digit;
    }
    
    return total % 10 == 0;
}

int main(void) {
    printf("%d\n", luhn_check("4532015112830366")); // 1 (válido)   ✅
    printf("%d\n", luhn_check("1234567890123456")); // 0 (inválido) ❌
    return 0;
}

12. Kotlin

fun luhnCheck(number: String): Boolean {
    val digits = number.filter { it.isDigit() }.map { it.digitToInt() }
    if (digits.size < 2) return false

    val total = digits
        .reversed()
        .mapIndexed { i, d ->
            if (i % 2 == 1) {
                val doubled = d * 2
                if (doubled > 9) doubled - 9 else doubled
            } else d
        }
        .sum()

    return total % 10 == 0
}

fun luhnGenerate(partial: String): String {
    val digits = partial.filter { it.isDigit() }.map { it.digitToInt() }

    val total = digits
        .reversed()
        .mapIndexed { i, d ->
            if (i % 2 == 0) {
                val doubled = d * 2
                if (doubled > 9) doubled - 9 else doubled
            } else d
        }
        .sum()

    val checkDigit = (10 - (total % 10)) % 10
    return "$partial$checkDigit"
}

// Ejemplos
fun main() {
    println(luhnCheck("4532015112830366"))    // true  ✅
    println(luhnCheck("1234567890123456"))    // false ❌
    println(luhnGenerate("453201511283036"))  // 4532015112830366
}

13. SQL (PostgreSQL)

CREATE OR REPLACE FUNCTION luhn_check(number TEXT)
RETURNS BOOLEAN AS $$
DECLARE
    digits  INT[];
    total   INT := 0;
    d       INT;
    i       INT;
    len     INT;
BEGIN
    -- Extraer solo dígitos y convertir a array
    SELECT ARRAY(
        SELECT substring(number, s, 1)::INT
        FROM generate_series(1, length(number)) AS s
        WHERE substring(number, s, 1) ~ '[0-9]'
    ) INTO digits;

    len := array_length(digits, 1);
    IF len < 2 THEN RETURN FALSE; END IF;

    FOR i IN 1..len LOOP
        -- Iterar de derecha a izquierda
        d := digits[len - i + 1];

        IF i % 2 = 1 THEN
            d := d * 2;
            IF d > 9 THEN d := d - 9; END IF;
        END IF;

        total := total + d;
    END LOOP;

    RETURN total % 10 = 0;
END;
$$ LANGUAGE plpgsql IMMUTABLE STRICT;

-- Uso
SELECT luhn_check('4532015112830366');  -- true  ✅
SELECT luhn_check('1234567890123456');  -- false ❌

Fórmulas Luhn para Excel y Google Sheets

Implementaciones completas del algoritmo de validación de números de tarjeta en hojas de cálculo, desde fórmulas universales modernas hasta compatibilidad con versiones antiguas.


Requisitos previos

Antes de copiar cualquier fórmula, elimina espacios y guiones del número de tarjeta. El número debe estar en una celda como texto puro: 4532015112830366, no 4532 0151 1283 0366.

Para limpiar la celda automáticamente, usa:

=SUSTITUIR(SUSTITUIR(A1," ",""),"-","")

(en versiones en inglés: SUBSTITUTE)


Fórmula universal — Excel 365 y Google Sheets modernos

Funciona con tarjetas de cualquier longitud (14, 15, 16 dígitos). Requiere la función LET y SECUENCIA / SEQUENCE.

Google Sheets

=LET(
  raw,     SUSTITUIR(SUSTITUIR(A2," ",""),"-",""),
  len,     LARGO(raw),
  pos,     SECUENCIA(1,len),
  d,       VALOR(EXTRAE(raw,pos,1)),
  desde_d, len-pos+1,
  d2,      SI(RESIDUO(desde_d,2)=0, d*2, d),
  adj,     SI(d2>9, d2-9, d2),
  SI(RESIDUO(SUMA(adj),10)=0,"✅ VÁLIDO","❌ INVÁLIDO")
)

Excel 365 (en inglés)

=LET(
  raw,        SUBSTITUTE(SUBSTITUTE(A2," ",""),"-",""),
  len,        LEN(raw),
  pos,        SEQUENCE(1,len),
  d,          VALUE(MID(raw,pos,1)),
  from_right, len-pos+1,
  d2,         IF(MOD(from_right,2)=0, d*2, d),
  adj,        IF(d2>9, d2-9, d2),
  IF(MOD(SUM(adj),10)=0,"✅ VALID","❌ INVALID")
)

Excel 365 (en español)

=LET(
  raw,        SUSTITUIR(SUSTITUIR(A2," ",""),"-",""),
  len,        LARGO(raw),
  pos,        SECUENCIA(1,len),
  d,          VALOR(EXTRAE(raw,pos,1)),
  desde_d,    len-pos+1,
  d2,         SI(RESIDUO(desde_d,2)=0, d*2, d),
  adj,        SI(d2>9, d2-9, d2),
  SI(RESIDUO(SUMA(adj),10)=0,"✅ VÁLIDO","❌ INVÁLIDO")
)

LAMBDA reutilizable — definirla una vez, usarla en toda la hoja

Guarda la función con un nombre y úsala como cualquier otra fórmula nativa.

Cómo guardar una LAMBDA en Excel 365

  1. Ve a Fórmulas → Administrador de nombres → Nueva
  2. Nombre: LUHN
  3. Se refiere a:
=LAMBDA(num,
  LET(
    raw,   SUBSTITUTE(SUBSTITUTE(num," ",""),"-",""),
    len,   LEN(raw),
    pos,   SEQUENCE(1,len),
    d,     VALUE(MID(raw,pos,1)),
    fr,    len-pos+1,
    d2,    IF(MOD(fr,2)=0,d*2,d),
    adj,   IF(d2>9,d2-9,d2),
    IF(MOD(SUM(adj),10)=0,"✅ VÁLIDO","❌ INVÁLIDO")
  )
)

Uso posterior: =LUHN(A2) — igual que cualquier función nativa.

Cómo guardar una LAMBDA en Google Sheets

  1. Ve a Datos → Funciones con nombre → Añadir función
  2. Nombre: LUHN
  3. Argumentos: num
  4. Definición:
=LET(
  raw,   SUSTITUIR(SUSTITUIR(num," ",""),"-",""),
  len,   LARGO(raw),
  pos,   SECUENCIA(1,len),
  d,     VALOR(EXTRAE(raw,pos,1)),
  fr,    len-pos+1,
  d2,    SI(RESIDUO(fr,2)=0,d*2,d),
  adj,   SI(d2>9,d2-9,d2),
  SI(RESIDUO(SUMA(adj),10)=0,"✅ VÁLIDO","❌ INVÁLIDO")
)

Uso posterior: =LUHN(A2)


Validación en masa — rangos completos

Google Sheets: BYROW para columnas enteras

=BYROW(A2:A100, LAMBDA(celda,
  SI(ESBLANCO(celda), "",
    LET(
      raw,  SUSTITUIR(SUSTITUIR(celda," ",""),"-",""),
      len,  LARGO(raw),
      pos,  SECUENCIA(1,len),
      d,    VALOR(EXTRAE(raw,pos,1)),
      fr,   len-pos+1,
      d2,   SI(RESIDUO(fr,2)=0,d*2,d),
      adj,  SI(d2>9,d2-9,d2),
      SI(RESIDUO(SUMA(adj),10)=0,"✅ VÁLIDO","❌ INVÁLIDO")
    )
  )
))

Esta fórmula va en una única celda (p. ej. B2) y rellena automáticamente toda la columna B con el resultado de cada número en A2:A100.

Excel 365: MAP para rangos

=MAP(A2:A100, LAMBDA(celda,
  IF(ISBLANK(celda), "",
    LET(
      raw,  SUBSTITUTE(SUBSTITUTE(celda," ",""),"-",""),
      len,  LEN(raw),
      pos,  SEQUENCE(1,len),
      d,    VALUE(MID(raw,pos,1)),
      fr,   len-pos+1,
      d2,   IF(MOD(fr,2)=0,d*2,d),
      adj,  IF(d2>9,d2-9,d2),
      IF(MOD(SUM(adj),10)=0,"✅ VÁLIDO","❌ INVÁLIDO")
    )
  )
))

Fórmulas SUMPRODUCTO — sin LET (Excel 2016–2019)

Para versiones de Excel que no tienen LET ni SEQUENCE. Las fórmulas son específicas para cada longitud de tarjeta.

Tarjetas de 16 dígitos (Visa, Mastercard, Discover)

Pega el número sin espacios en A2.

=SI(
  RESIDUO(
    SUMAPRODUCTO(
      SI(VALOR(EXTRAE(A2,{1,3,5,7,9,11,13,15},1))*2>9,
         VALOR(EXTRAE(A2,{1,3,5,7,9,11,13,15},1))*2-9,
         VALOR(EXTRAE(A2,{1,3,5,7,9,11,13,15},1))*2)
    )
    +SUMAPRODUCTO(VALOR(EXTRAE(A2,{2,4,6,8,10,12,14,16},1))),
  10)=0,
  "✅ VÁLIDO", "❌ INVÁLIDO"
)

En inglés:

=IF(
  MOD(
    SUMPRODUCT(
      IF(VALUE(MID(A2,{1,3,5,7,9,11,13,15},1))*2>9,
         VALUE(MID(A2,{1,3,5,7,9,11,13,15},1))*2-9,
         VALUE(MID(A2,{1,3,5,7,9,11,13,15},1))*2)
    )
    +SUMPRODUCT(VALUE(MID(A2,{2,4,6,8,10,12,14,16},1))),
  10)=0,
  "✅ VALID", "❌ INVALID"
)

Tarjetas de 15 dígitos (American Express)

=SI(
  RESIDUO(
    SUMAPRODUCTO(
      SI(VALOR(EXTRAE(A2,{2,4,6,8,10,12,14},1))*2>9,
         VALOR(EXTRAE(A2,{2,4,6,8,10,12,14},1))*2-9,
         VALOR(EXTRAE(A2,{2,4,6,8,10,12,14},1))*2)
    )
    +SUMAPRODUCTO(VALOR(EXTRAE(A2,{1,3,5,7,9,11,13,15},1))),
  10)=0,
  "✅ VÁLIDO", "❌ INVÁLIDO"
)

Tarjetas de 14 dígitos (Diners Club)

=SI(
  RESIDUO(
    SUMAPRODUCTO(
      SI(VALOR(EXTRAE(A2,{2,4,6,8,10,12,14},1))*2>9,
         VALOR(EXTRAE(A2,{2,4,6,8,10,12,14},1))*2-9,
         VALOR(EXTRAE(A2,{2,4,6,8,10,12,14},1))*2)
    )
    +SUMAPRODUCTO(VALOR(EXTRAE(A2,{1,3,5,7,9,11,13},1))),
  10)=0,
  "✅ VÁLIDO", "❌ INVÁLIDO"
)

Fórmula con celdas auxiliares — versiones muy antiguas (Excel 2010–2013)

Para hojas que no admiten arrays literales {...}, la solución más robusta es usar una fila de columnas auxiliares ocultas.

Estructura de ejemplo para un número de 16 dígitos en A1:

Celda Fórmula Descripción
B1 =EXTRAE($A$1,1,1)*1 Dígito 1
C1 =EXTRAE($A$1,2,1)*1 Dígito 2
Q1 =EXTRAE($A$1,16,1)*1 Dígito 16
B2 =SI(B1*2>9,B1*2-9,B1*2) Dígito 1 duplicado
C2 =C1 Dígito 2 sin cambio
D2 =SI(D1*2>9,D1*2-9,D1*2) Dígito 3 duplicado
(alternar)
B3 =RESIDUO(SUMA(B2:Q2)+Q1,10)=0 Resultado final

Oculta las filas 1 y 2 una vez configuradas. Solo la celda B3 muestra el resultado.


Fórmula compacta solo para verificar el dígito de control

Si ya tienes los primeros N-1 dígitos y solo quieres calcular qué dígito de control añadir:

Google Sheets / Excel 365 — Generar dígito de control

=LET(
  raw,  SUSTITUIR(SUSTITUIR(A2," ",""),"-",""),
  len,  LARGO(raw),
  pos,  SECUENCIA(1,len),
  d,    VALOR(EXTRAE(raw,pos,1)),
  fr,   len-pos+2,
  d2,   SI(RESIDUO(fr,2)=0,d*2,d),
  adj,  SI(d2>9,d2-9,d2),
  RESIDUO(10-RESIDUO(SUMA(adj),10),10)
)

Esta fórmula toma los primeros 15 dígitos de una tarjeta y devuelve el dígito de control que completaría un número válido.


Detección del emisor por prefijo

Combina la validación Luhn con la detección automática del tipo de tarjeta:

=SI(Y(LARGO(A2)=16, IZQUIERDA(A2,1)="4"), "Visa",
  SI(Y(LARGO(A2)=16, O(VALOR(IZQUIERDA(A2,2))>=51, VALOR(IZQUIERDA(A2,2))<=55)), "Mastercard",
    SI(Y(LARGO(A2)=15, O(IZQUIERDA(A2,2)="34", IZQUIERDA(A2,2)="37")), "American Express",
      SI(Y(LARGO(A2)=16, IZQUIERDA(A2,4)="6011"), "Discover",
        "Desconocido"
      )
    )
  )
)

Tabla de compatibilidad

Versión LET + SEQUENCE SUMPRODUCT array BYROW/MAP LAMBDA
Excel 2010–2013 ⚠️ limitado
Excel 2016–2019
Excel 2021
Microsoft 365
Google Sheets

Números de prueba

Estos números son seguros para probar tus fórmulas — pasan Luhn pero no son tarjetas reales:

Tipo Número Resultado esperado
Visa 16 dígitos 4532015112830366 ✅ VÁLIDO
Mastercard 16 dígitos 5425233430109903 ✅ VÁLIDO
Amex 15 dígitos 374251018720955 ✅ VÁLIDO
Discover 16 dígitos 6011000990139424 ✅ VÁLIDO
Inválido genérico 1234567890123456 ❌ INVÁLIDO
Dígito cambiado 4532015112830367 ❌ INVÁLIDO

Las fórmulas en español usan la localización española de Excel/Sheets (punto y coma como separador de argumentos en algunas configuraciones regionales). Si tu instalación usa coma como separador, sustitúyelos en consecuencia.

Para generar un número de tarjeta válido (o cualquier número que cumpla con el algoritmo de Luhn), la estrategia es siempre la misma:

  1. Generar todos los dígitos excepto el último (el dígito de control) de forma aleatoria.
  2. Calcular la suma de Luhn de esos dígitos según las reglas habituales.
  3. Calcular el dígito de control necesario para que la suma total sea múltiplo de 10.

Aquí tienes cómo implementarlo en tres lenguajes representativos:

1. Python (Enfoque funcional)

Python

import random

def generate_luhn_number(base_number_str):
    # Calcular la suma de los dígitos dados según Luhn
    digits = [int(d) for d in base_number_str]
    # Invertimos para aplicar la regla de "cada segundo dígito"
    payload = digits[::-1]
    
    total = 0
    for i, d in enumerate(payload):
        # En el cálculo, los dígitos del payload ya están en posiciones impares (index 0, 2, 4...)
        # si queremos que el nuevo número (índice 0) sea el "check digit", 
        # debemos tratar los índices pares como "dobles"
        val = d * 2 if i % 2 == 0 else d
        total += (val - 9 if val > 9 else val)
        
    # El check digit es lo que falta para llegar al siguiente múltiplo de 10
    check_digit = (10 - (total % 10)) % 10
    return base_number_str + str(check_digit)

# Ejemplo: Generar un número de 15 dígitos al azar y calcular el 16vo
base = "".join([str(random.randint(0, 9)) for _ in range(15)])
print(f"Número válido: {generate_luhn_number(base)}")

2. JavaScript (Enfoque imperativo)

JavaScript

function generateLuhn(baseNumber) {
    let sum = 0;
    let shouldDouble = true; // Empezamos duplicando el primer dígito desde la derecha
    
    // Recorremos de derecha a izquierda
    for (let i = baseNumber.length - 1; i >= 0; i--) {
        let digit = parseInt(baseNumber[i]);
        if (shouldDouble) {
            digit *= 2;
            if (digit > 9) digit -= 9;
        }
        sum += digit;
        shouldDouble = !shouldDouble;
    }
    
    let checkDigit = (10 - (sum % 10)) % 10;
    return baseNumber + checkDigit;
}

// Ejemplo
let randomBase = Math.floor(Math.random() * 1000000000000000).toString();
console.log("Número válido:", generateLuhn(randomBase));

3. C# (Enfoque moderno)

C#

public static string GenerateLuhn(string baseNumber) {
    int sum = 0;
    bool doubleDigit = true;

    for (int i = baseNumber.Length - 1; i >= 0; i--) {
        int n = int.Parse(baseNumber[i].ToString());
        if (doubleDigit) {
            n *= 2;
            if (n > 9) n -= 9;
        }
        sum += n;
        doubleDigit = !doubleDigit;
    }

    int checkDigit = (10 - (sum % 10)) % 10;
    return baseNumber + checkDigit;
}

La lógica matemática detrás del generador

La clave está en esta línea que verás repetida en todos los lenguajes: check_digit = (10 - (sum % 10)) % 10

  • sum % 10: Obtiene el residuo actual de la suma.
  • 10 - (sum % 10): Calcula cuánto falta para llegar a la siguiente decena.
  • % 10: Se aplica para el caso especial en que la suma ya es múltiplo de 10 (donde el residuo es 0, y 10 - 0 nos daría 10, pero necesitamos un solo dígito, es decir, 0).

Nota sobre Seguridad 🏴‍☠️

Generar números con el Algoritmo de Luhn es excelente para validar formularios o generar IDs internos. Sin embargo, recuerda:

Advertencia: El algoritmo de Luhn es un método de detección de errores, no es un método de cifrado ni seguridad. Generar números que pasen el test de Luhn no significa que sean números de tarjetas reales, pero nunca debes generar ni usar secuencias de números que imiten tarjetas reales en entornos de producción sin autorización, ya que podrías colisionar con cuentas reales existentes.