Expresiones regulares: guía práctica con ejemplos reales

Tienes un archivo con miles de líneas donde las fechas aparecen como 15/03/2025 y necesitas convertirlas a 2025-03-15 para importarlas a una base de datos. Con métodos de texto normales tendrías que partir cada línea, revisar posiciones y reconstruir la cadena. Con una expresión regular basta una línea de código:

const texto = "Factura emitida el 15/03/2025, vence el 14/04/2025";
const convertido = texto.replace(/(\d{2})\/(\d{2})\/(\d{4})/g, "$3-$2-$1");
// "Factura emitida el 2025-03-15, vence el 2025-04-14"Una expresión regular es un patrón que describe un conjunto de cadenas de texto. Sirve para buscar, validar, extraer y reemplazar. La sintaxis básica es casi idéntica en JavaScript, Python, PHP, Java, C# y la mayoría de editores de código, aunque cada uno tiene diferencias puntuales que conviene conocer. Esta guía se centra en lo que funciona en todos, y señala dónde cambian.
Las piezas básicas
Toda expresión regular se arma con unas pocas piezas. Si memorizas estas tablas, puedes leer la mayoría de patrones que encuentres.
Caracteres y clases
| Símbolo | Significado | Ejemplo | Coincide con |
|---|---|---|---|
. | Cualquier carácter excepto salto de línea | c.sa | casa, cosa, c-sa |
\d | Un dígito | \d\d | 42, 07 |
\w | Letra, dígito o guion bajo | \w+ | usuario_1 |
\s | Espacio, tabulación o salto de línea | a\sb | "a b" |
[abc] | Uno de los caracteres de la lista | gr[ia]s | gris, gras |
[^abc] | Cualquier carácter que no esté en la lista | [^0-9] | cualquier cosa menos un dígito |
[a-z] | Un rango | [A-F0-9] | un dígito hexadecimal en mayúscula |
\. | Un punto literal (escapado) | \d+\.\d+ | 3.14 |
Las versiones en mayúscula invierten el significado: \D es cualquier cosa que no sea dígito, \S cualquier cosa que no sea espacio.
Cuantificadores
| Símbolo | Significado | Ejemplo |
|---|---|---|
* | Cero o más veces | ab* coincide con a, ab, abbb |
+ | Una o más veces | \d+ coincide con 7 o con 2025 |
? | Cero o una vez (opcional) | colou?r coincide con color y colour |
{3} | Exactamente 3 veces | \d{3} |
{2,4} | Entre 2 y 4 veces | \w{2,4} |
{2,} | 2 o más veces | \d{2,} |
Anclas y grupos
| Símbolo | Significado |
|---|---|
^ | Inicio de la cadena (o de la línea en modo multilínea) |
$ | Final de la cadena (o de la línea en modo multilínea) |
\b | Límite de palabra |
( ) | Grupo de captura: guarda lo que coincide para usarlo después |
(?: ) | Grupo sin captura: agrupa sin guardar |
a|b | Alternativa: a o b |
Validar: el patrón completo con anclas
El error más común al validar es olvidar las anclas. El patrón \d{5} encuentra cinco dígitos seguidos dentro de cualquier texto, así que "abc123456xyz" pasaría la validación de un código postal. Con anclas, ^\d{5}$ exige que la cadena entera sea exactamente cinco dígitos.
Así se valida en los tres lenguajes más comunes en la web:
// JavaScript
const esCodigo = /^\d{5}$/.test("08001"); // true# Python
import re
es_codigo = re.fullmatch(r"\d{5}", "08001") is not None # True<?php
// PHP: el patrón va entre delimitadores, aquí barras
$esCodigo = preg_match('/^\d{5}$/', '08001') === 1; // trueEn Python, re.fullmatch ya exige que coincida la cadena completa, así que no hacen falta ^ y $. Usa siempre cadenas crudas, con el prefijo r, para que Python no interprete las barras invertidas antes de que lleguen al motor de expresiones regulares. En PHP puedes elegir otro delimitador, como #, cuando el patrón contiene muchas barras.
Extraer: grupos de captura
Los paréntesis no solo agrupan: guardan lo que coincidió. Volvamos a las fechas, ahora para extraer sus partes:
import re
m = re.search(r"(\d{2})/(\d{2})/(\d{4})", "Pago recibido el 05/11/2024")
if m:
dia, mes, anio = m.groups()
print(anio, mes, dia) # 2024 11 05Grupos con nombre
Cuando hay varios grupos, numerarlos se vuelve confuso. Los grupos con nombre hacen el código más legible, pero aquí aparece la primera diferencia entre lenguajes:
| Lenguaje | Sintaxis del grupo | Cómo se lee |
|---|---|---|
| JavaScript | (?<anio>\d{4}) | m.groups.anio |
| Python | (?P<anio>\d{4}) | m.group("anio") |
| PHP | (?<anio>\d{4}) o (?P<anio>\d{4}) | $m['anio'] |
const m = "2024-11-05".match(/(?<anio>\d{4})-(?<mes>\d{2})-(?<dia>\d{2})/);
console.log(m.groups.dia); // "05"Referencias en el reemplazo
Para usar lo capturado en un reemplazo, JavaScript y PHP usan $1, $2; Python usa \1, \2 dentro de una cadena cruda:
re.sub(r"(\d{2})/(\d{2})/(\d{4})", r"\3-\2-\1", "15/03/2025") # '2025-03-15'<?php
echo preg_replace('#(\d{2})/(\d{2})/(\d{4})#', '$3-$2-$1', '15/03/2025');Codicioso frente a perezoso
Por defecto, los cuantificadores son codiciosos: intentan abarcar lo máximo posible. Esto sorprende a todo el mundo la primera vez. Si quieres extraer etiquetas de <b>hola</b> con el patrón <.+>, obtienes la cadena completa, porque .+ se extiende hasta el último > que encuentra.
Agregando ? después del cuantificador lo vuelves perezoso: <.+?> se detiene en el primer > posible y devuelve <b> y </b> por separado. Una alternativa más eficiente y explícita es usar una clase negada: <[^>]+> significa "un signo menor, luego cualquier cosa que no sea mayor, luego un mayor".
Aun así, no uses expresiones regulares para analizar HTML completo. Para eso existen analizadores como DOMParser en el navegador, BeautifulSoup en Python o DOMDocument en PHP.
Modificadores: mayúsculas, líneas y puntos
| Efecto | JavaScript | Python | PHP |
|---|---|---|---|
| Ignorar mayúsculas | /patrón/i | re.IGNORECASE | /patrón/i |
| Todas las coincidencias | /patrón/g | re.findall o re.finditer | preg_match_all |
^ y $ por línea | /patrón/m | re.MULTILINE | /patrón/m |
| El punto incluye saltos de línea | /patrón/s | re.DOTALL | /patrón/s |
| Modo Unicode | /patrón/u | activo por defecto en cadenas | /patrón/u |
Tildes y eñes: el detalle que rompe validaciones en español
En JavaScript, \w solo reconoce letras ASCII, incluso con el modificador u. Un patrón como ^\w+$ rechaza "Muñoz" y "José". La solución es usar propiedades Unicode, disponibles con el modificador u:
/^\p{L}+$/u.test("Muñoz"); // true
/^\w+$/.test("Muñoz"); // falseEn Python 3, \w ya incluye letras con tilde cuando trabajas con cadenas de texto normales, pero también incluye dígitos y guion bajo. Si necesitas solo letras, el módulo estándar re no admite \p{L}; un truco habitual es [^\W\d_], que significa "caracteres de palabra que no sean dígitos ni guion bajo".
En PHP, agrega el modificador u cuando trabajes con texto UTF-8 y usa \p{L} para letras. Sin u, el motor trata cada byte por separado y una "ñ" se convierte en dos caracteres extraños.
Mirar sin consumir: lookahead y lookbehind
Las aserciones comprueban una condición sin incluirla en la coincidencia. El ejemplo clásico es exigir requisitos a una contraseña:
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$Cada (?=...) revisa desde el inicio si más adelante hay una minúscula, una mayúscula y un dígito. Después, .{8,} exige al menos ocho caracteres. Este patrón funciona igual en los tres lenguajes.
El lookbehind, (?<=...), mira hacia atrás. Por ejemplo, (?<=\$)\d+ extrae los números precedidos por un signo de dólar sin incluir el signo. JavaScript lo admite desde ES2018 con patrones de longitud variable; Python y PHP exigen, en general, que lo que va dentro del lookbehind tenga longitud fija.
Patrones prácticos listos para adaptar
- Quitar espacios repetidos: reemplaza
\s+por un espacio simple. - Números con decimales opcionales:
-?\d+(?:[.,]\d+)? - Hashtags con tildes (JavaScript o PHP con
u):#(\p{L}[\p{L}\d_]*) - Líneas vacías en modo multilínea:
^\s*$ - Correo con validación básica:
^[^\s@]+@[^\s@]+\.[^\s@]+$
Sobre el correo: la especificación real de las direcciones de email es tan permisiva que ningún patrón razonable la cubre completa. Usa una validación sencilla como la anterior para detectar errores de tipeo y confirma que la dirección existe enviando un mensaje de verificación.
Un riesgo de rendimiento que conviene conocer
Algunos patrones con cuantificadores anidados, como (a+)+$, pueden tardar un tiempo exponencial con ciertas entradas que no coinciden, porque el motor prueba millones de combinaciones antes de rendirse. Si tu aplicación evalúa patrones sobre texto enviado por usuarios, esto puede bloquear el servidor. Para prevenirlo, evita grupos repetidos que contengan a su vez cuantificadores sobre lo mismo, limita la longitud de la entrada antes de evaluarla y, cuando sea posible, prefiere clases negadas como [^,]* en lugar de .*.
Para practicar
Herramientas como regex101 permiten probar patrones en vivo, elegir el lenguaje y ver una explicación de cada parte. Prueba estos tres ejercicios antes de usar expresiones regulares en un proyecto real:
- Escribe un patrón que valide horas en formato de 24 horas, de
00:00a23:59. Pista: la primera cifra depende de la segunda. - Extrae todos los dominios de una lista de correos, sin la arroba.
- Convierte nombres de variables de
camelCaseasnake_casecon un solo reemplazo.
Cuando un patrón empiece a ocupar más de una línea, documéntalo con un comentario o divídelo en partes. Python ofrece re.VERBOSE y PHP el modificador x, que permiten escribir el patrón en varias líneas con comentarios. Quien tenga que mantenerlo dentro de seis meses, probablemente tú, lo va a agradecer.
Si quieres conocer otros artículos parecidos a Expresiones regulares: guía práctica con ejemplos reales puedes visitar la categoría Programacion.
Deja una respuesta

Contenido que te pude interesar