Patrones1 caza jugable incluida

Reglas de contraseñas con lookaheads en regex (y cuándo no usarlos)

Cómo comprobar “al menos una mayúscula, un dígito y ocho caracteres” en una sola regex con lookaheads, por qué la versión sin orden fijo los necesita, las trampas de los patrones habituales y por qué comprobar cada regla por separado suele ser mejor diseño.

Logo de Regex Hunter

· 6 min de lectura

/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/

atrapa

  • Secret123
  • aB3defgh
  • Tr0ub4dor
  • 9Lives_cat

deja libre

  • secret123
  • SECRET123
  • Secretpass
  • Ab1

“Al menos 8 caracteres, con una mayúscula, una minúscula y un dígito.” Es una de las cosas que más se le piden a una regex, y es una buena forma de aprender los lookaheads, porque las reglas no dicen dónde va cada carácter.

Este artículo construye el patrón regla a regla, repasa las trampas y termina defendiendo no usar una única regex gigante. Todos los ejemplos son interactivos, así que edita los patrones y mira cómo cambian las líneas.

Respuesta rápidaJavaScript
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/

Al menos 8 caracteres, con al menos una minúscula, una mayúscula y un dígito, en cualquier orden.

Acepta

  • Secret123
  • 9Lives_cat
  • aB3defgh

Rechaza

  • secret123
  • SECRET123
  • Ab1

01Por qué un patrón normal no puede hacerlo

Un patrón se lee de izquierda a derecha. Para decir “contiene un dígito” escribirías algo como .*\d.*, y eso por sí solo está bien. Pero “contiene un dígito y contiene una mayúscula”, en cualquier orden, obliga a enumerar todos los órdenes:

Patrón31 car.
/^(.*[A-Z].*\d.*|.*\d.*[A-Z].*)$/
Pruébalo en la Zona de pruebas
Dos reglas, dos órdenes. Tres reglas necesitarían seis ramas; cuatro reglas, veinticuatro.

Eso no escala. Necesitas una forma de comprobar una regla sin avanzar por el texto.

02Los lookaheads comprueban sin avanzar

Un lookahead (aserción de anticipación) (?=…) comprueba que algo coincide desde la posición actual y luego vuelve a donde empezó. Así, varios lookaheads al principio pueden recorrer cada uno la contraseña entera, una regla cada uno.

Una regla: “más adelante hay un dígito en algún sitio”:

Coincidencias3/3 como se esperaba
/^(?=.*\d)/

Debe coincidir2/2

  • abc1
  • 1abc

No debe1/1

  • abcd
Pruébalo en la Zona de pruebas
(?=.*\d) mira hacia delante buscando “cualquier cosa y luego un dígito”. Si está, la coincidencia tiene éxito en la posición 0.

El .* delante de \d es lo que hace funcionar el “en algún sitio”. Sin él, (?=\d) significaría “el carácter siguiente es un dígito”.

03Apilar las reglas

Cada regla tiene su propio lookahead, todos anclados al principio, y después la comprobación de longitud consume el texto:

Coincidencias7/7 como se esperaba
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/

Debe coincidir3/3

  • Secret123
  • aB3defgh
  • 9Lives_cat

No debe4/4

  • secret123
  • SECRET123
  • Secretpass
  • Ab1
Pruébalo en la Zona de pruebas
Tres lookaheads (una minúscula, una mayúscula, un dígito) y luego 8 o más caracteres cualesquiera.

Léelo como una lista de condiciones. Añadir una regla es añadir un lookahead, en cualquier orden:

Coincidencias3/3 como se esperaba
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/

Debe coincidir2/2

  • Secret123!
  • p@ssW0rd

No debe1/1

  • Secret123
Pruébalo en la Zona de pruebas
Un cuarto lookahead exige un símbolo: cualquier carácter que no sea letra ni dígito.

Un lookahead que prohíbe algo se escribe (?!…). “Ningún espacio en ningún sitio”:

Coincidencias2/2 como se esperaba
/^(?!.*\s)(?=.*\d).{8,}$/

Debe coincidir1/1

  • correct1horse

No debe1/1

  • correct 1 horse
Pruébalo en la Zona de pruebas
(?!.*\s) falla si aparece un carácter de espacio en blanco en cualquier punto más adelante.

04Trampas de los patrones habituales

Olvidar las anclas. Sin ^ y $, .{8,} solo necesita ocho caracteres en algún sitio, y cada lookahead se ejecuta desde donde resulte que empieza la coincidencia. Mantén ^ delante para que todos los lookaheads empiecen en la posición 0, y $ al final para que la comprobación de longitud cubra toda la cadena.

Una longitud máxima que no funciona. .{8,64} sin $ coincide tan tranquilo con los primeros 64 caracteres de una contraseña de 500. Con $ la rechaza, que es lo que querías.

Un “símbolo” demasiado estrecho. Una clase como [!@#$%^&*] rechaza ?, ~, -, los espacios y cualquier carácter ajeno al alfabeto inglés. [^A-Za-z0-9] (“ni letra ni dígito”) es lo que la gente suele querer decir.

Letras fuera de la A a la Z. [A-Z] no cuenta É como mayúscula. Con el modificador u, \p{Lu} y \p{Ll} son cualquier letra mayúscula y minúscula.

05Cuándo no usarlos

Una regex da una sola respuesta: pasa o no pasa. Tu formulario de registro necesita decirle a la persona qué regla no ha cumplido:

const rules = [
  { test: (p) => p.length >= 12, message: "Use at least 12 characters" },
  { test: (p) => /\d/.test(p),   message: "Add a number" },
  { test: (p) => /[A-Z]/.test(p), message: "Add a capital letter" },
];

const problems = rules.filter((r) => !r.test(password)).map((r) => r.message);

Cada comprobación es diminuta, legible y fácil de cambiar, y los mensajes de error salen gratis.

También vale la pena preguntarse si quieres reglas de composición. Las directrices del NIST para contraseñas (SP 800-63B) desaconsejan expresamente reglas como “una mayúscula, un dígito, un símbolo”. Empujan a la gente hacia Password1!. Las directrices priorizan la longitud, permitir frases de contraseña largas (de al menos 64 caracteres, con espacios incluidos) y comprobar las contraseñas nuevas contra listas de contraseñas filtradas conocidas.

Así que el patrón con lookaheads merece la pena conocerlo, tanto para contraseñas como para cualquier comprobación del tipo “debe contener todo esto, en cualquier orden”. Para un formulario de registro real, una longitud mínima más una comprobación de contraseñas filtradas suele ser mejor opción.

06Pruébalo

Atrapa las cadenas que contienen a la vez un dígito y una letra, en cualquier orden:

Caza jugablepar 20

Atrapa las cadenas con al menos una letra y al menos un dígito, en cualquier orden.

Objetivos · 0/6 atrapados

  • a1objetivo
  • 1aobjetivo
  • abc123objetivo
  • 123abcobjetivo
  • x9yobjetivo
  • R2D2objetivo

Inocentes · 0 errores

  • abcinocente
  • 123inocente
  • ---inocente
  • a-binocente
  • 4-5inocente
  • !!inocente

Escribe una regex: el tablero se actualiza mientras escribes.

Pruébalo en la Zona de pruebas
¿Te gustó? Cada día llega una nueva.Jugar la caza de hoy

07Lista de comprobación

  • (?=.*X) significa “X aparece en algún punto más adelante”. Apila uno por regla.
  • (?!.*X) significa “X no aparece en ningún punto más adelante”.
  • Ancla con ^ para que cada lookahead empiece en la posición 0, y con $ para que un límite de longitud lo cubra todo.
  • [^A-Za-z0-9] es un “símbolo” mejor que una lista escogida a mano.
  • En los formularios, comprobaciones separadas dan mensajes de error separados.
  • La longitud y una comprobación de contraseñas filtradas son mejores que las reglas de composición.

08Sigue practicando