Portada de UrsaKit que explica los grupos, lookarounds y cuantificadores de las expresiones regulares

Los grupos regex, los lookarounds y los cuantificadores son las tres piezas que se conocen antes de entenderlas, y ese orden explica casi toda la confusión que genera una expresión regular. Esta guía cubre esas tres, además de los flags que deciden cómo se aplica el patrón y el backtracking que convierte un patrón que funciona en una pestaña congelada.

Cada ejemplo se puede probar en el probador de expresiones regulares de UrsaKit, que resalta las coincidencias mientras escribes y explica el patrón token a token.

01Una expresión regular es un pequeño programa

Conviene decir qué hace el motor: recorre la entrada posición a posición, intenta casar el patrón y puede tener éxito, fallar o retroceder para probar otro camino. Todo lo sorprendente de las regex viene de ese último paso.

/^\d{4}-\d{2}-\d{2}$/     cuatro dígitos, guion, dos dígitos, guion, dos dígitos
 ^                    $      anclas: inicio y fin de la entrada

Las anclas importan más que cualquier otro carácter. Sin ^ y $, un patrón que casa fechas también casará los diez primeros caracteres de cualquier cadena más larga que empiece por una fecha.

02Los grupos de captura deciden qué recibes

Los paréntesis hacen dos cosas: agrupan un subpatrón para un cuantificador o una alternancia, y guardan el texto que coincide. La sintaxis de esta guía sigue la definición de JavaScript; la referencia de grupos y retroreferencias de MDN recoge todas las variantes.

;/^(\d{4})-(\d{2})-(\d{2})$/.exec('2026-10-04')
// [ '2026-10-04', '2026', '10', '04' ]
// el índice 0 es la coincidencia completa; 1..n son los grupos

Los grupos numerados son frágiles: insertar un grupo en medio del patrón renumera todos los siguientes. Los grupos con nombre dejan clara la intención y sobreviven a las ediciones:

;/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/.exec('2026-10-04')
// groups: { year: '2026', month: '10', day: '04' }

Cuando un grupo existe solo para aplicar un cuantificador o una alternancia a varios caracteres, hazlo sin captura. El resultado queda más legible y no pagas por una captura que nunca lees.

SintaxisNombreEfecto
(abc)Grupo de capturaAgrupa y guarda la coincidencia
(?:abc)Grupo sin capturaSolo agrupa
(?<name>abc)Grupo con nombreGuarda la coincidencia en groups.name
(?=abc)Lookahead positivoCondición sobre lo que sigue
(?!abc)Lookahead negativoLo que sigue no debe ser eso
(?<=abc)Lookbehind positivoCondición sobre lo que precede
(?<!abc)Lookbehind negativoLo que precede no debe ser eso

03Los lookarounds añaden condiciones sin consumir texto

Un lookahead comprueba el texto que viene y deja el cursor donde estaba, así que el resto del patrón todavía puede casar esos caracteres. Por eso es una "condición" y no una "coincidencia". MDN documenta las aserciones lookahead y lookbehind; el lookbehind es el más reciente, llegó a JavaScript con ES2018.

/\d+(?= dollars)/.exec('42 dollars')    // coincide con '42', no con '42 dollars'
/(?<=\$)\d+/.exec('$42')               // coincide con '42'; el $ no se consume

El uso clásico es una regla de contraseña que debe cumplir varias condiciones a la vez sin importar el orden:

;/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/.test('UrsaKit2026')
// true: al menos una minúscula, una mayúscula, un dígito y 8 caracteres

Usa un lookahead para dividir sin perder el delimitador.

'a1b2c3'.split(/(?=\d)/) devuelve ['a', '1b', '2c', '3'] porque el lookahead marca la posición sin eliminar el carácter. split(/(\d)/) mantendría los dígitos como elementos separados.

04Codicioso, perezoso y el coste del backtracking

Los cuantificadores son codiciosos por defecto: toman todo lo que pueden y luego devuelven caracteres hasta que el resto del patrón encaja. Añadir ? los vuelve perezosos: toman lo mínimo y van creciendo.

"<p>hi</p>"   /<.+>/   -> "<p>hi</p>"    codicioso: el último ">"
"<p>hi</p>"   /<.+?>/  -> "<p>"          perezoso: el primer ">"

Un cuantificador perezoso no es automáticamente más rápido. Cambia el orden de búsqueda, y ante una coincidencia fallida el motor sigue explorando alternativas. La estructura cara es el anidamiento: un cuantificador dentro de otro que puede casar los mismos caracteres.

(a+)+$     sobre "aaaaaaaaaaaaaaaaaaaaaaaaaaaaab"
           -> el motor prueba 2^n formas de partir las a, y falla

Ese patrón es el caso de manual de backtracking catastrófico: 30 caracteres pueden significar mil millones de intentos. Reescribirlo elimina la ambigüedad, porque cada a solo puede consumirse de una forma:

a+$        sobre "aaaaaaaaaaaaaaaaaaaaaaaaaaaaab"   -> falla de inmediato

No ejecutes una regex no confiable sobre una entrada no confiable en el hilo principal.

Un patrón con cuantificadores anidados puede bloquear el bucle de eventos durante minutos con una cadena de 30 caracteres. En el navegador es una pestaña congelada; en un servidor, una petición que nunca responde.

05Los flags deciden cómo se aplica el patrón

Los flags no cambian el patrón, cambian el comportamiento del motor. La referencia de flags de JavaScript los lista todos, incluidos los escapes de propiedades Unicode como \p{Script=Greek}, que solo funcionan en modo Unicode.

FlagSignificadoTrampa
gBusca todas las coincidencias en lugar de la primeraUn patrón con g reutilizado conserva lastIndex
iSin distinguir mayúsculasCon Unicode usa iu o el plegado de mayúsculas es inconsistente
m^ y $ también casan en saltos de líneaFácil de activar sin querer al copiar un patrón
s. también casa saltos de líneaSin él, . se detiene en \n
uModo Unicode; habilita \p{...}Los escapes inválidos pasan a ser errores, que suele ser lo deseable
yPegajoso: casa exactamente en lastIndexLo usan tokenizadores y analizadores

06Cuando una regex es la herramienta equivocada

La regex es excelente para texto plano y regular: líneas de log, identificadores, separadores, validaciones simples. Es la herramienta equivocada para estructuras anidadas:

TareaMejor herramienta
Validar JSONUn analizador JSON; el validador JSON indica línea y columna
Analizar HTML o XMLUn analizador DOM, no <.+>
Extraer de un CSV con campos entre comillasUn analizador CSV
Comprobar que un correo existeEnviar un mensaje de confirmación
Validar una URL completaEl constructor URL

07Un flujo de trabajo que encuentra errores en vez de adivinarlos

  1. Pega primero una muestra real — Pon la entrada real en el panel de prueba antes de escribir el patrón. Un patrón escrito contra texto inventado solo pasa pruebas inventadas.
  2. Construye token a token — Añade una pieza, mira el resaltado, añade la siguiente. El probador de UrsaKit explica cada token, así que (?:\s+)? deja de ser ruido y pasa a ser "grupo opcional de espacios".
  3. Decide las anclas a propósito — Define si quieres el patrón en cualquier posición o en toda la cadena y añade ^ / $ en consecuencia.
  4. Prueba los casi-aciertos — Introduce entradas que no deberían coincidir: un carácter de más, un guion en lugar de un espacio, un salto de línea final.
  5. Revisa los flags — g, m y s cambian los resultados más a menudo que el propio patrón.
  6. Inserta desde la referencia en lugar de reescribir — Al hacer clic en un token de la referencia se inserta en el cursor, lo que evita los errores de escape de copiar un patrón de un documento.

Cuando el patrón ya coincide con lo esperado, compara el texto antes y después con el comparador de diferencias: una regex que se come un carácter en silencio se detecta mucho mejor así.

08Fuentes

Preguntas frecuentes

¿Cuál es la diferencia entre un grupo de captura y uno sin captura?

Un grupo de captura (foo) guarda lo que coincide para que puedas leerlo; un grupo sin captura (?:foo) solo agrupa el patrón para cuantificadores y alternancias y no aparece en el resultado.

¿Un lookahead consume el texto que comprueba?

No. El lookahead mira el texto que viene y el motor continúa desde donde estaba, así que el resto del patrón todavía puede coincidir con esos mismos caracteres.

¿Los lookbehind funcionan en todas partes?

Llegaron a JavaScript con ES2018 y están en los navegadores modernos y Node.js, pero motores antiguos y algunos entornos no JavaScript los rechazan o limitan su longitud variable.

¿Por qué mi regex congela el navegador?

Cuantificadores anidados como (a+)+ permiten al motor probar un número exponencial de formas de partir la misma entrada. Reescribe el patrón para que cada carácter solo pueda consumirse de una manera.

¿Cuándo no debería usar una regex?

Para estructuras anidadas o recursivas como JSON, HTML o un lenguaje de programación: necesitan un analizador, no un patrón.

¿Qué cambia el flag g?

Sin g el motor devuelve la primera coincidencia; con g sigue buscando. El comportamiento no cambia, pero el lastIndex con estado de un mismo objeto RegExp explica los resultados sorprendentes al reutilizarlo.

Volver a los artículos

Artículos relacionados

Herramientas relacionadas