Ir al contenido
Toova
Todas las herramientas

10 Trucos de Regex que Todo Desarrollador Debe Conocer

Toova

Las expresiones regulares son una de esas herramientas que al principio parecen impenetrables y de repente hacen clic. Una vez que hacen clic, empiezas a verlas en todas partes: validación de entradas, análisis de logs, pipelines de búsqueda y reemplazo, enrutamiento de URLs. Pero la mayoría de los desarrolladores solo usa un puñado de características — clases de caracteres, cuantificadores, anclas — y deja el resto de la especificación sin explorar.

Esta guía cubre diez características de regex que van más allá de lo básico. Cada una resuelve un problema real que los patrones más simples no pueden manejar limpiamente. Todos los ejemplos usan sintaxis de JavaScript, que también es válida para cualquier entorno compatible con ECMAScript.

Puedes probar cada patrón de este artículo usando el Tester de Regex de Toova sin escribir ni una línea de código de configuración.

1. Lookaheads: Coincidir Sin Consumir

Un lookahead asegura que un patrón deba (o no deba) seguir a la posición actual, sin hacer que el motor avance más allá de esos caracteres. El texto coincidente no incluye lo que comprueba el lookahead.

Sintaxis del lookahead positivo: (?=...)

// Lookahead positivo: coincide con "foo" solo cuando le sigue "bar"
const re1 = /foo(?=bar)/;
re1.test('foobar'); // true
re1.test('foobaz'); // false

Sintaxis del lookahead negativo: (?!...)

// Lookahead negativo: coincide con "foo" cuando NO le sigue "bar"
const re2 = /foo(?!bar)/;
re2.test('foobaz'); // true
re2.test('foobar'); // false

Un uso práctico: coincidir con un número de precio solo cuando va seguido de un símbolo de moneda, sin incluir el símbolo en el valor capturado. O validar que una contraseña contenga al menos un dígito usando (?=.*\d) sin especificar dónde debe aparecer el dígito.

Los lookaheads tienen ancho cero — no consumen caracteres. Puedes apilar múltiples lookaheads en la misma posición para imponer varias condiciones independientes simultáneamente.

2. Lookbehinds: Comprobar lo que Vino Antes

Un lookbehind es el espejo de un lookahead: comprueba el texto que precede a la posición actual sin incluirlo en la coincidencia.

Sintaxis del lookbehind positivo: (?<=...)

// Lookbehind positivo: coincide con "bar" solo cuando está precedido por "foo"
const re3 = /(?<=foo)bar/;
re3.test('foobar'); // true
re3.test('bazbar'); // false

Sintaxis del lookbehind negativo: (?<!...)

// Lookbehind negativo: coincide con "bar" cuando NO está precedido por "foo"
const re4 = /(?<!foo)bar/;
re4.test('bazbar'); // true
re4.test('foobar'); // false

Los lookbehinds llegaron a ECMAScript 2018 y son compatibles con todos los navegadores modernos y Node.js 10+. Un caso de uso habitual: extraer la parte del valor de un par clave-valor como name=Alice coincidiendo todo lo que sigue a name= sin incluir la clave en la coincidencia.

Nota: a diferencia de los lookaheads, las expresiones lookbehind en JavaScript no pueden contener patrones de longitud variable — la expresión lookbehind debe tener una longitud máxima fija o acotada.

3. Grupos de Captura Nombrados: Patrones Autodocumentados

Los grupos de captura estándar se referencian por número: $1, $2, etcétera. Cuando agregas o eliminas un grupo, cada referencia posterior se rompe. Los grupos nombrados resuelven esto al permitirte adjuntar una etiqueta a cada grupo.

const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const m = '2026-05-10'.match(dateRe);
console.log(m.groups.year);  // "2026"
console.log(m.groups.month); // "05"
console.log(m.groups.day);   // "10"

Los grupos nombrados también están disponibles en cadenas de reemplazo mediante $<name>:

// Referencia hacia atrás usando el grupo nombrado en el patrón
const quoteRe = /(?<q>['"]).*?\k<q>/;
quoteRe.test('"hello"'); // true
quoteRe.test('"hello''); // false

El nombre del grupo debe ser un identificador JavaScript válido. Usa nombres descriptivos que reflejen lo que captura el grupo — year, port, protocol — y tus patrones se vuelven prácticamente autodocumentados. También puedes usar \k<name> dentro del propio patrón para hacer una referencia hacia atrás a un grupo nombrado, como se muestra arriba.

4. Cuantificadores Non-Greedy: Coincidir con lo Mínimo

Por defecto, los cuantificadores (*, +, ?) son greedy — coinciden con tantos caracteres como sea posible. Añadir un ? después del cuantificador lo hace non-greedy (también llamado lazy o reluctant), coincidiendo con el menor número de caracteres posible.

const html = '<a>click</a>';

// Greedy (por defecto) — coincide con la cadena más larga posible
/<.+>/.exec(html)?.[0]; // '<a>click</a>'

// Non-greedy — coincide con la cadena más corta posible
/<.+?>/.exec(html)?.[0]; // '<a>'

Esto importa cuando se analiza HTML, XML o cualquier formato donde el mismo delimitador puede aparecer varias veces. La versión greedy absorbe todo desde la primera apertura hasta el último cierre de etiqueta en toda la cadena. La versión non-greedy se detiene en el primer cierre válido.

Lo mismo aplica a +? (uno o más, lazy) y ?? (cero o uno, lazy). Los cuantificadores non-greedy no cambian qué puede coincidir — cambian qué coincidencia válida se selecciona cuando existen varias opciones.

5. Evitar el Backtracking Catastrófico

El backtracking es la forma en que los motores de regex se recuperan de un intento de coincidencia fallido — prueban un camino diferente a través del patrón. En la mayoría de los casos esto es invisible y rápido. Pero ciertos patrones pueden hacer que el motor explore un número exponencialmente creciente de caminos, paralizando un proceso Node.js incluso con una cadena de entrada modesta.

El patrón peligroso clásico son los cuantificadores anidados como (a+)+ aplicados a una cadena como aaaaab. El motor prueba todas las formas posibles de dividir los caracteres a entre los grupos interno y externo antes de concluir que no hay coincidencia.

Los grupos atómicos ((?>...)) evitan esto diciéndole al motor que no haga backtracking en un grupo una vez que ha coincidido. JavaScript no admite grupos atómicos de forma nativa, pero puedes emular el comportamiento posesivo con un lookahead:

// Sin grupo atómico — el motor hace backtracking en (\d+)
// Con grupo atómico — una vez que (\d+) coincide, no se permite backtracking
// JavaScript no admite grupos atómicos de forma nativa,
// pero se pueden emular con un truco de lookahead:
const re5 = /(?=(\d+))\1(?!\d)/; // emula el cuantificador posesivo \d++

La regla más segura: evita cuantificadores directamente anidados dentro de otros cuantificadores a menos que tengas un motivo específico. Reescribe los patrones para ser más precisos sobre qué coinciden. También puedes usar la herramienta Text Diff para comparar la salida de dos patrones equivalentes en paralelo mientras refactorizas.

6. Operaciones de Conjunto en Clases de Caracteres (Flag Unicode v)

ECMAScript 2024 introdujo el flag v, que habilita operaciones de conjunto dentro de las clases de caracteres. Esto te permite expresar "todas las letras excepto las vocales" o "letras mayúsculas que también son ASCII" como una definición de clase limpia en lugar de una alternancia engorrosa.

// La sustracción de clases de caracteres POSIX no existe en JS,
// pero el modo de conjuntos Unicode (flag `v`) agrega operaciones de conjunto:
const lettersNoVowels = /[a-z--[aeiou]]/v;
lettersNoVowels.test('b'); // true
lettersNoVowels.test('e'); // false

El flag v admite tres operaciones dentro de las clases de caracteres:

  • Sustracción: [A--B] — caracteres en A pero no en B
  • Intersección: [A&&B] — caracteres tanto en A como en B
  • Unión: [AB] — caracteres en A o en B (igual que las clases de caracteres estándar)

Node.js 20+ y todos los navegadores evergreen admiten el flag v. Es un superconjunto del flag u — no combines ambos; usa v solo cuando necesites sus características.

7. Límites de Palabra: Coincidencia de Palabras Completas

El ancla \b coincide con la posición entre un carácter de palabra (\w) y un carácter que no es de palabra. No consume caracteres — solo afirma la posición. Su inverso \B coincide con cualquier posición que no sea un límite de palabra.

const sentence = 'cat concatenate';

// Sin \b — "cat" se encuentra también dentro de "concatenate"
/cat/g.exec(sentence); // coincide con "cat" en "cat" Y en "concatenate"

// Con \b — solo la palabra completa "cat"
/\bcat\b/g.exec(sentence); // coincide únicamente con "cat" aislado
// \B es lo inverso: coincide dentro de una palabra, no en un límite
/\Bcat\B/.test('concatenate'); // true — "cat" está dentro de la palabra

Los límites de palabra son esenciales cuando se buscan identificadores en código o prosa. Sin ellos, buscar una variable llamada id también coincidiría con indexOf, invalid y grid. Usa \btermino\b para restringir las coincidencias a ocurrencias aisladas.

Una advertencia importante: \b usa la definición de JavaScript de carácter de palabra ([a-zA-Z0-9_]). Los caracteres acentuados y las letras no latinas se tratan como caracteres que no son de palabra. Para límites de palabra que reconozcan Unicode, combina el flag v con clases de propiedades Unicode.

8. Modo Multilínea: Anclas por Línea

Por defecto, ^ coincide solo con el inicio de la cadena y $ solo con el final. El flag m (multiline) cambia esto: ^ coincide con el inicio de cada línea y $ con el final de cada línea.

const text = 'line one\nline two\nline three';

// Sin el flag m — ^ solo coincide con el inicio de toda la cadena
/^line/.test(text); // true (solo la primera línea)

// Con el flag m — ^ coincide con el inicio de CADA línea
const matches = text.match(/^line/gm);
console.log(matches); // ['line', 'line', 'line']

Esto es indispensable cuando se procesa texto multilínea como archivos de log, archivos de configuración o código. Los usos habituales incluyen extraer líneas que empiezan con una palabra clave, reemplazar tokens al final de línea o validar que cada línea en un bloque coincide con un patrón.

No confundas el flag m con el flag s (dotAll). El flag s hace que . coincida también con los caracteres de nueva línea. El flag m no afecta a . en absoluto — solo cambia el comportamiento de ^ y $.

9. Escapes de Propiedades Unicode: Coincidencia de Caracteres Internacionales

El flag u habilita los escapes de propiedades Unicode, que te permiten coincidir caracteres basándote en su categoría Unicode, script u otra propiedad. Esta es la forma correcta de coincidir letras, dígitos o puntuación en todos los sistemas de escritura humanos — no solo ASCII.

// El flag u habilita los escapes de propiedades Unicode
const letters = /\p{L}+/u;
letters.test('Héllo');   // true
letters.test('你好');    // true
letters.test('12345');   // false

// Coincidir solo letras mayúsculas de todos los sistemas de escritura
const upper = /\p{Lu}+/u;
upper.test('ABC');  // true
upper.test('abc');  // false
// Coincidir emoji (categoría Unicode general: Símbolo, Otro)
const emoji = /\p{So}/u;
emoji.test('🚀'); // true

Las propiedades Unicode más utilizadas son:

  • \p{L} — cualquier letra (todos los scripts)
  • \p{Lu} — letras mayúsculas
  • \p{Ll} — letras minúsculas
  • \p{N} — cualquier número
  • \p{Nd} — dígitos decimales
  • \p{P} — puntuación
  • \p{Script=Latin} — caracteres del script latino
  • \p{Emoji} — caracteres emoji

Usa \P{...} (P mayúscula) para negar — coincidiendo con todo lo que no tiene la propiedad especificada. La lista completa de propiedades Unicode admitidas y sus valores se mantiene en la documentación de MDN.

10. Patrones Verbosos Mediante Ensamblado de Cadenas

Muchos sabores de regex (Python, Ruby, .NET, PCRE) admiten un modo verbose o extendido (flag x) que permite espacios en blanco y comentarios dentro de los patrones. JavaScript no tiene este flag — el flag x no es válido en ECMAScript.

La solución estándar es ensamblar patrones a partir de constantes de cadena nombradas y combinarlas con new RegExp():

// JavaScript no tiene un flag x nativo,
// pero puedes construir patrones legibles como constantes de cadena:
const YEAR  = '(?<year>\\d{4})';
const SEP   = '-';
const MONTH = '(?<month>\\d{2})';
const DAY   = '(?<day>\\d{2})';
const datePattern = new RegExp(YEAR + SEP + MONTH + SEP + DAY);

Cada constante describe qué coincide, y el patrón final se lee como una frase. Este enfoque también facilita componer subpatrones compartidos a través de múltiples definiciones de regex en una base de código, y probar cada componente de forma independiente.

Para patrones menos complejos, mantener todo el regex en una línea con comentarios en línea en un bloque de comentario cercano suele ser suficiente. El objetivo es garantizar que el próximo desarrollador (incluido tu yo futuro) pueda entender el patrón sin necesidad de pasarlo por un decodificador.

Poniéndolo Todo Junto

Estas diez técnicas cubren una gran parte del "¿por qué falla este regex en casos extremos?" superficie. Los lookaheads y lookbehinds te permiten afirmar contexto sin consumirlo. Los grupos nombrados mantienen los patrones legibles a través de refactorizaciones. Los cuantificadores non-greedy evitan la sobrecoincidencia accidental. Los escapes de propiedades Unicode manejan entradas que van más allá de ASCII.

La mejor manera de ganar fluidez es experimentar con patrones reales contra datos reales. Usa el Tester de Regex para iterar rápidamente. Cuando tu patrón produce una salida que necesita compararse o limpiarse, la herramienta Text Diff muestra exactamente qué cambió entre ejecuciones. Y si tu regex está analizando JSON, el Formateador JSON te permite inspeccionar el resultado estructurado sin salir del navegador.

Para una referencia completa de toda la sintaxis y los flags de regex en JavaScript, el Cheatsheet de Regex de MDN es la mejor página para marcar como favorita. Para depuración interactiva de patrones con visualización completa de coincidencias, regex101.com admite el modo JavaScript con una explicación integrada de cada componente de un patrón.