Ir al contenido
wannacode

Expresiones regulares

En resumen: Una expresión regular (regex) es un patrón que describe cómo es un texto: con ella compruebas, buscas, reemplazas y divides texto en lugar de escribir muchos ifs.

Problema

Dieciséis líneas para una fecha

El formulario de reservas pide una fecha con la forma dd.mm.aaaa. La compruebas carácter por carácter:

function isDate(s) {
if (s.length !== 10) {
return false;
}
for (let i = 0; i < s.length; i++) {
const c = s[i];
if (i === 2 || i === 5) {
if (c !== ".") {
return false;
}
} else if (c < "0" || c > "9") {
return false;
}
}
return true;
}
console.log(isDate("05.03.2026"));
console.log(isDate("5.3.2026"));
console.log(isDate("05/03/2026"));
Salida
true
false
false

Dieciséis líneas, y la función solo comprueba el formato: dos dígitos, un punto, dos dígitos, un punto, cuatro dígitos. Si además quieres aceptar / en lugar del punto, o un día de una sola cifra, añadirás otras tantas líneas. Un número de teléfono, un email o una matrícula te costarán otra función como esta cada uno.

La idea

Un patrón en lugar de código

Describes cómo es el texto, y JavaScript hace la comprobación:

function isDate(s) {
return /^\d{2}\.\d{2}\.\d{4}$/.test(s);
}
console.log(isDate("05.03.2026"));
console.log(isDate("5.3.2026"));
console.log(isDate("05/03/2026"));
Salida
true
false
false

El patrón se lee de izquierda a derecha: \d es un dígito, {2} significa «dos veces», \. es un punto. Un patrón así se llama expresión regular (regular expression, regex).

El patrón va entre dos barras: /.../. Es un literal de expresión regular, igual que "..." es un literal de cadena. test(texto) devuelve true si encuentra el patrón en cualquier parte del texto. Por eso el patrón empieza con ^ («el principio del texto») y termina con $ («el final»): así tiene que cubrir todo el texto, del primer carácter al último.

Míralo

Cómo busca el patrón

replace no comprueba todo el texto. Busca los trozos que encajan con el patrón y los reemplaza. Así recorre la línea:

const line = "120 € el 05.03.2026, vence 15.04.2026";
const masked = line.replace(/\d{2}\.\d{2}\.\d{4}/g, "[fecha]");
console.log(masked);
Paso a paso, en palabras
  1. La línea es una secuencia de caracteres, cada uno con su posición.
  2. Desde la posición 0: «1» y «2» son dígitos, pero después viene «0» en lugar de un punto. El intento falla.
  3. Un intento nuevo desde cada carácter siguiente: «20 », «0 », « €»… todos fallan.
  4. Desde la posición 9 encajan las diez partes: la primera coincidencia.
  5. La búsqueda sigue después de la coincidencia, desde la coma, y encuentra la segunda.
  6. replace con la bandera g reemplaza las dos coincidencias por «[fecha]».

El patrón se prueba desde cada posición, una tras otra. En cuanto una parte no coincide, el intento falla y empieza uno nuevo desde el carácter siguiente. Por eso 120 no pasó: dos dígitos, y después un 0 en lugar de un punto. Cuando encuentra una coincidencia, la búsqueda sigue después de ella.

La bandera g (global) después del patrón, /.../g, dice «todas las coincidencias». Sin ella, replace reemplaza solo la primera.

La idea

Clases de caracteres

Entre corchetes enumeras qué caracteres se permiten en una posición. Una matrícula de coche son una o dos letras, cuatro dígitos y dos letras:

const plates = ["CA1234BX", "B7788KK", "CA12345BX", "ca1234bx"];
for (const plate of plates) {
console.log(plate, /^[A-Z]{1,2}\d{4}[A-Z]{2}$/.test(plate));
}
Salida
CA1234BX true
B7788KK true
CA12345BX false
ca1234bx false
Patrón Coincide con un carácter
[ABE] A, B o E
[0-9], [A-Z] del rango: un dígito, una letra latina mayúscula
[^0-9] cualquier cosa excepto un dígito (^ al principio de los corchetes)
\d un dígito, lo mismo que [0-9]
\s un espacio, un tabulador, un salto de línea
\w una letra latina, un dígito o _
. cualquier carácter (excepto el salto de línea)

Las mayúsculas invierten el significado: \D es «no un dígito», \S es «no un espacio», \W es «no \w».

Cuidado con \w: en JavaScript son solo letras latinas, sin acentos.

console.log(/^\w+$/.test("Sofia"));
console.log(/^\w+$/.test("Zürich"));
console.log(/^\p{L}+$/u.test("Zürich"));
console.log(/^[A-Za-zü]+$/.test("Zürich"));
Salida
true
false
true
true

Para un texto con letras como «ü» o «é», enuméralas en la clase, [A-Za-zü], o usa \p{L} con la bandera u: \p{L} es una letra de cualquier idioma, y u (unicode) le dice al patrón que entienda clases como esta.

Más precisamente, para curiosos
  • [A-Z] es un rango según los códigos de los caracteres. Contiene solo las 26 letras latinas, sin «É», «Ñ» ni «Ü», que están en otro lugar de la tabla. Para un texto en inglés basta con [A-Za-z]. Si también necesitas letras con acento, añádelas: [A-Za-zÉéÑñÜü].
  • Las matrículas búlgaras de verdad usan solo 12 letras, las que se ven igual en cirílico y en latino: [ABEKMHOPCTYX].

Pruébalo

El punto

Alguien olvidó la barra invertida delante de los puntos:

¿Qué imprimirá el programa?

const pattern = /^\d{2}.\d{2}.\d{4}$/;
console.log(pattern.test("05.03.2026"));
console.log(pattern.test("05-03-2026"));
console.log(pattern.test("05.03.26"));
Respuesta

La idea

Cuántas veces

Después de un carácter o de una clase dices cuántas veces puede repetirse. Esto es un cuantificador:

Cuantificador Significa
? cero o una vez (puede estar o no)
* cero o más
+ una o más
{4} exactamente 4
{1,2} de 1 a 2
{2,} 2 o más

Un número de móvil búlgaro empieza con 0 o +359, después 87, 88 u 89, y 7 dígitos más:

const mobile = /^(\+359|0)8[789]\d{7}$/;
console.log(mobile.test("0888123456"));
console.log(mobile.test("+359877654321"));
console.log(mobile.test("0888 123 456"));
console.log(mobile.test("088812345"));
Salida
true
true
false
false

| significa «o». Los paréntesis mantienen \+359|0 juntos: sin ellos, la «o» partiría todo el patrón en dos. Los paréntesis hacen más cosas, que veremos en la próxima lección. El + del principio lleva \, porque si no, es un cuantificador.

Un cuantificador también funciona sobre un grupo entero entre paréntesis. Aquí tienes un precio con parte decimal o sin ella:

const price = /^\d+([.,]\d{1,2})? €$/;
console.log(price.test("12 €"));
console.log(price.test("12.5 €"));
console.log(price.test("12,50 €"));
console.log(price.test(".50 €"));
Salida
true
true
true
false

([.,]\d{1,2})? significa «un punto o una coma y uno o dos dígitos», y todo este bloque puede faltar.

La idea

Literal o cadena

En el literal /.../ la barra invertida es solo para el patrón: \d es un dígito, \. es un punto, sin duplicar nada.

A veces construyes el patrón a partir de texto, por ejemplo cuando una parte viene de una variable. Entonces es una cadena entre comillas para new RegExp(...), y pasa por dos capas: primero JavaScript lee la cadena, después el patrón. En una cadena, \ empieza un carácter especial: \n es un salto de línea, \" es una comilla. \d no es uno de ellos, y JavaScript simplemente quita la barra invertida, sin error y sin aviso:

const date = "05.03.2026";
const pattern = new RegExp("^\d{2}\.\d{2}\.\d{4}$");
console.log(pattern.source);
console.log(pattern.test(date));
Salida
^d{2}.d{2}.d{4}$
false

source muestra el patrón que recibió RegExp: d{2} significa «dos letras d». Por eso, en una cadena, cada \ del patrón se escribe \\:

const date = "05.03.2026";
console.log(/^\d{2}\.\d{2}\.\d{4}$/.test(date));
console.log(new RegExp("^\\d{2}\\.\\d{2}\\.\\d{4}$").test(date));
console.log(/^C:\\Temp$/.test("C:\\Temp"));
Salida
true
true
true
Quieres en el patrón Literal Cadena para new RegExp
\d (un dígito) /\d/ "\\d"
\. (un punto de verdad) /\./ "\\."
\\ (una barra invertida de verdad) /\\/ "\\\\"

En el literal, / también lleva \: /\//. Si no, cerraría el patrón.

Los caracteres que necesitan \ cuando los quieres tal cual son . * + ? ( ) [ ] { } | ^ $ \.

Pruébalo

Encuentra el error

El programa debería imprimir «12 € y 50 céntimos», pero imprime esto:
Salida
0 € y 0 céntimos

Elige la línea con el error.

const price = "12.50";
console.log(price.split("."));
console.log(price.split(/./));
Salida
[ '12', '50' ]
[ '', '', '', '', '', '' ]

La idea

Reemplazar y dividir con un patrón

replace y split reciben un patrón. Así limpias y separas un texto que la gente escribe como le da la gana:

const address = "Envío: calle Mayor 12, portal B";
console.log(address.replace(/\s+/g, " "));
const items = "pan, leche;café té".split(/[,;\s]+/);
console.log(`${items.length}: ${items.join(" | ")}`);
console.log("Código SUMMER10, código summer25".replace(/summer\d+/gi, "***"));
Salida
Envío: calle Mayor 12, portal B
4: pan | leche | café | té
Código ***, código ***
  • \s+ es «uno o más espacios»: cada grupo de espacios se convierte en uno solo.
  • [,;\s]+ es «comas, puntos y comas o espacios, los que haya»: el separador puede ser cualquiera de ellos.
  • i después del patrón es una bandera (flag): el patrón no distingue entre mayúsculas y minúsculas. Las banderas se escriben una junto a otra: gi es «todas las coincidencias, sin distinguir mayúsculas».

replace con texto en lugar de un patrón reemplaza solo la primera aparición. Para todas es replaceAll("viejo", "nuevo"). Si no necesitas un patrón, usa texto: no tienes que pensar en puntos ni en barras invertidas.

\b es un límite de palabra: el lugar entre una letra y algo que no es una letra. Con él reemplazas solo palabras completas. Pero en JavaScript \b, igual que \w, conoce solo las letras latinas sin acentos, incluso con la bandera u:

const text = "éxito y éxitos";
console.log(text.replace(/éxito/g, "triunfo"));
console.log(text.replace(/\béxito\b/g, "triunfo"));
console.log(text.replace(/(?<!\p{L})éxito(?!\p{L})/gu, "triunfo"));
Salida
triunfo y triunfos
éxito y éxitos
triunfo y éxitos

Sin \b, «éxitos» se convierte en «triunfos». Con \b, JavaScript no ve la «é» como una letra y no reemplaza nada. Para letras así, el límite se escribe con una comprobación de entorno (lookaround): (?<!\p{L}) es «delante no hay ninguna letra», y (?!\p{L}) es «detrás no hay ninguna letra». No toman ningún carácter. Solo comprueban.

Más precisamente, para curiosos
  • \B es lo contrario de \b: un lugar dentro de una palabra. También conoce solo las letras latinas sin acentos, así que con la «é» lo hace al revés: se salta la «é» de «café» y encuentra la de «éxito». Solo dentro de una palabra es (?<=\p{L})é con la bandera u: «delante hay una letra».
  • ^ es el principio del texto, y $ es el final: text.replace(/^0+/, "") quita los ceros iniciales.
  • Las banderas son letras después del patrón: g (todas), i (sin distinguir mayúsculas), u (unicode), m y s (en la próxima lección). (?i) al principio del patrón es un error en Node 22.
  • "a|b".split(/|/) es una trampa como la del punto: | es «o», y el patrón está vacío, así que divide entre cada dos caracteres. "a|b".split("|") busca texto normal y no tiene este problema.
console.log("café éxito".replace(/\Bé/g, "e"));
console.log("café éxito".replace(/(?<=\p{L})é/gu, "e"));
console.log("a|b".split(/|/));
console.log("a|b".split("|"));
console.log("a a".replace("a", "b"), "a a".replaceAll("a", "b"));
Salida
café exito
cafe éxito
[ 'a', '|', 'b' ]
[ 'a', 'b' ]
b a b b

Pruébalo

Código postal

Un código postal búlgaro tiene exactamente cuatro dígitos. ¿Qué falta para que el programa imprima true para "1000" y false para "100" o "10000"?

const code = "1000";
console.log(/^$/.test(code));

Ejercicio

Comprobaciones con un patrón

Resuelve los ejercicios en tu editor. Lee líneas mientras haya, así (por ahora, solo cópialo):

import { createInterface } from "node:readline";
const rl = createInterface({ input: process.stdin });
for await (const line of rl) {
// line es la siguiente línea, sin "\n"
}

En la lección «Archivos» leerás las mismas líneas desde un archivo.

1. Fecha. Comprueba si cada fecha tiene la forma dd/mm/aaaa. El día va de 01 a 31, y el mes de 01 a 12. Pista: el día es 0[1-9] o [12]\d, o 3[01]. En el literal, / se escribe \/.

Entrada
01/01/2020
32/01/2020
15/05/2021
Salida
01/01/2020 - Válida
32/01/2020 - No válida
15/05/2021 - Válida

2. Número de móvil. La gente escribe los números con espacios y guiones. Quítalos con replace y comprueba lo que queda con el patrón de número de móvil de la lección.

Entrada
0888 123 456
+359 87 765 4321
02 812 3456
0899-12-345
Salida
0888 123 456 - válido
+359 87 765 4321 - válido
02 812 3456 - no válido
0899-12-345 - no válido

3. Email. Comprueba si cada línea parece un email: una o más letras latinas, dígitos, ., + o -, después @, y después un nombre de dominio con al menos un punto (empresa.es, mail.empresa.es).

Entrada
lucia.garcia@empresa.es
lucia@empresa
@empresa.es
lucia garcia@empresa.es
Salida
lucia.garcia@empresa.es - sí
lucia@empresa - no
@empresa.es - no
lucia garcia@empresa.es - no

4. Lista de la compra. Los productos están separados por comas, puntos y comas, barras verticales o espacios, a veces varios a la vez. Imprímelos numerados.

Entrada
pan, leche;café té|queso
Salida
1. pan
2. leche
3. café
4. té
5. queso
Más precisamente, para curiosos
  • El patrón de la fecha también acepta 31/02/2026. Una regex no puede saber de forma razonable si el día existe en ese mes. El patrón es solo el primer filtro. El Date integrado no ayuda aquí: no rechaza el 31 de febrero. Lo pasa al 3 de marzo (en Date los meses se cuentan desde 0, así que febrero es 1):
console.log(new Date(2026, 1, 31).toDateString());
Salida
Tue Mar 03 2026

Así que comprueba el día tú mismo: construye la fecha y mira si el mes sigue siendo el mismo.

  • La regla completa de una dirección de email (RFC 5322) permite comillas, comentarios y espacios en ciertos lugares. Un patrón que lo cubra todo tiene cientos de caracteres, y nadie lo mantiene. La comprobación de verdad es enviar un email con un enlace de confirmación.

Más ejercicios

  • Contraseña. Comprueba si la contraseña tiene al menos 8 caracteres y al menos un dígito. Hazlo con dos test: /^.{8,}$/ y /\d/. ¿Por qué en el segundo no necesitas ^ ni $?
  • Hora. Comprueba una hora con la forma hh:mm, de 00:00 a 23:59.
  • Solo dígitos. De "Tel.: +359 (88) 812-34-56" deja solo los dígitos y el +, con un solo replace y [^...].

Test

Ponte a prueba

1. ¿Qué da /\d{4}/.test("12026")?
2. ¿Con qué coincide . (un punto) en un patrón?
3. ¿Cuál significa «uno o más»?
4. ¿Coincide "Zürich" con /^\w+$/ en JavaScript?
5. ¿Cuántas partes da "pan, leche;café".split(/[,;]\s*/)?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·