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.

· 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.
/^ (?=.*[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:
/^(.*[A-Z].*\d.*|.*\d.*[A-Z].*)$/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”:
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:
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/Debe coincidir3/3
- Secret123
- aB3defgh
- 9Lives_cat
No debe4/4
- secret123
- SECRET123
- Secretpass
- Ab1
Léelo como una lista de condiciones. Añadir una regla es añadir un lookahead, en cualquier orden:
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/Debe coincidir2/2
- Secret123!
- p@ssW0rd
No debe1/1
- Secret123
Un lookahead que prohíbe algo se escribe (?!…). “Ningún espacio en ningún sitio”:
/^(?!.*\s)(?=.*\d).{8,}$/Debe coincidir1/1
- correct1horse
No debe1/1
- correct 1 horse
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:
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.
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
- El artículo completo sobre lookahead y lookbehind profundiza más, con tableros interactivos.
- Hechicería regex es un pack de práctica con cazas de lookarounds más difíciles.