Expresiones regulares en Power Query: Text.RegexTest, Extract y Replace
TL;DR: Power Query no tiene soporte nativo de REGEX. La solución más completa es usar Web.Page() para ejecutar JavaScript dentro de M, lo que permite implementar Text.RegexTest, Text.RegexExtract y Text.RegexReplace como funciones reutilizables con la misma sintaxis que Excel 365. Requiere acceso a internet en el Gateway o Desktop para ejecutar Web.Page.
Excel ya tiene REGEXTEST, REGEXEXTRACT y REGEXREPLACE para Microsoft 365 desde 2024. Power Query todavía no. Para quienes procesan texto complejo —emails, códigos de producto, números de teléfono, patrones en campos de texto libre— esto es una limitación real.
La solución que la comunidad desarrolló usa un truco elegante: Web.Page() puede ejecutar HTML con JavaScript embebido, y JavaScript tiene soporte REGEX nativo completo.
Por qué Web.Page() para REGEX
Web.Page(html) renderiza HTML y devuelve el contenido resultante como tabla. Si ese HTML contiene un <script> que escribe el resultado de una operación regex con document.write(), podés capturar el output en M y usarlo como cualquier otro valor.
No es una solución oficial de Microsoft, pero funciona en Power BI Desktop y en Gateway cuando hay acceso a internet. Para ambientes offline o con restricciones de red, existe la alternativa de Python (ver al final).
Text.RegexTest — probar si un texto cumple un patrón
Devuelve true si el texto coincide con el patrón, false si no.
let
Text.RegexTest = (text as text, pattern as text, optional caseSensitivity as number) =>
let
caseSensitivityValue = if caseSensitivity = null then 0 else caseSensitivity,
regexModifier = if caseSensitivityValue = 0 then "" else "i",
regexExpression = "/" & pattern & "/" & regexModifier,
htmlContent =
"<script>" &
"var matches = """ & text & """.match(" & regexExpression & ");" &
"document.write(matches ? 'true' : 'false');" &
"</script>",
resultText = Web.Page(htmlContent)[Data]{0}[Children]{0}[Children]{1}[Text]{0},
resultValue = if resultText = "true" then true else false
in
resultValue
in
Text.RegexTest
Parámetros:
– text — el texto a evaluar
– pattern — expresión regular (sin barras delimitadoras, igual que en Excel)
– caseSensitivity — 0 = sensible a mayúsculas (default), 1 = insensible
Ejemplo de uso:
// ¿Es un email válido?
Text.RegexTest("usuario@empresa.com", "[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}", 1)
// → true
// ¿Empieza con código de producto (3 letras + guion + números)?
Text.RegexTest("ABC-12345", "^[A-Z]{3}-\d+$", 0)
// → true
Text.RegexExtract — extraer texto que coincide con el patrón
Extrae la parte del texto que coincide con el patrón.
let
Text.RegexExtract = (
text as text,
pattern as text,
optional returnMode as number,
optional caseSensitivity as number
) =>
let
returnModeValue = if returnMode is null then 0 else returnMode,
caseSensitivityValue = if caseSensitivity = null then 0 else caseSensitivity,
regexModifier = if caseSensitivityValue = 1 then "g" else "gi",
regexExpression = "/" & pattern & "/" & regexModifier,
javascriptLogic = if returnModeValue = 0 then
"var match = str.match(regex); var res = match ? match[0] : null;"
else if returnModeValue = 1 then
"var res = str.match(regex);"
else
"var match = regex.exec(str); res = match ? match.slice(1) : null;",
htmlContent = Text.Combine({
"<script>",
"var regex = ", regexExpression, ";",
"var str = """, text, """;",
javascriptLogic,
"document.write(res)",
"</script>"
}),
resultText = Web.Page(htmlContent)[Data]{0}[Children]{0}[Children]{1}[Text]{0}
in
resultText
in
Text.RegexExtract
Ejemplos:
// Extraer el dominio de un email
Text.RegexExtract("usuario@empresa.com", "(?<=@)[^.]+\.[a-z]+")
// → "empresa.com"
// Extraer solo los dígitos de un código
Text.RegexExtract("ABC-12345-XY", "\d+")
// → "12345"
Text.RegexReplace — reemplazar partes del texto
Reemplaza las coincidencias del patrón con el texto indicado.
let
Text.RegexReplace = (
text as text,
pattern as text,
replacement as text,
optional occurrence as number,
optional caseSensitivity as number
) =>
let
occurrenceValue = if occurrence = null then 0 else occurrence,
caseSensitivityValue = if caseSensitivity = null then 0 else caseSensitivity,
regexModifier = if caseSensitivityValue = 0 then "g" else "gi",
regexExpression = "/" & pattern & "/" & regexModifier,
javascriptCode =
"var regex = " & regexExpression & ";" &
"var str = '" & text & "';" &
"var replacement = '" & replacement & "';" &
"var occ = " & Text.From(occurrenceValue) & ";" &
"var matches = [];" &
"var match;" &
"while ((match = regex.exec(str)) !== null) {" &
" matches.push({index: match.index, length: match[0].length});" &
"}" &
"if (occ === 0) {" &
" document.write(str.replace(regex, replacement));" &
"} else {" &
" var targetIndex = occ > 0 ? occ - 1 : matches.length + occ;" &
" if (targetIndex < 0 || targetIndex >= matches.length) {" &
" document.write(str);" &
" } else {" &
" var target = matches[targetIndex];" &
" document.write(str.substring(0, target.index) + replacement + str.substring(target.index + target.length));" &
" }" &
"}",
htmlContent = "<script>" & javascriptCode & "</script>",
resultText = Web.Page(htmlContent)[Data]{0}[Children]{0}[Children]{1}[Text]{0}
in
resultText
in
Text.RegexReplace
Ejemplos:
// Enmascarar número de teléfono
Text.RegexReplace("Cel: 099-555-1234", "\d{4}$", "****")
// → "Cel: 099-555-****"
// Normalizar separadores en fechas
Text.RegexReplace("2026/06/14", "/", "-")
// → "2026-06-14"
Cómo usarlas en una consulta real
Lo más práctico es definir las funciones como consultas auxiliares en el panel de consultas y referenciarlas desde otras consultas:
- Nueva consulta en blanco → pegar el código de cada función → renombrar como
fnRegexTest,fnRegexExtract,fnRegexReplace. - En la consulta de datos, agregar una columna personalizada que llame a la función:
= Table.AddColumn(
Origen,
"Es email válido",
each fnRegexTest([Correo], "[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}", 1),
type logical
)
Limitaciones y alternativa offline
Limitación principal: Web.Page() requiere conexión a internet. En entornos con Gateway sin acceso a internet o con políticas de seguridad restrictivas, las funciones fallan.
Alternativa para entornos offline: activar Python en Power BI Desktop y usar re de la librería estándar:
import pandas as pd
import re
dataset['tiene_codigo'] = dataset['campo'].apply(
lambda x: bool(re.match(r'^[A-Z]{3}-\d+$', str(x))) if pd.notna(x) else False
)
Esta alternativa requiere Python instalado y configurado en opciones de Power BI Desktop, y no funciona en servicio sin un Gateway configurado con Python.
¿Cuándo conviene usar REGEX en Power Query?
- Validar formatos: emails, códigos, RUT, CUIT, números de teléfono
- Extraer fragmentos de texto libre: fechas en comentarios, montos en descripciones
- Limpiar datos heterogéneos de fuentes externas (ERPs, SharePoint, CRM)
- Normalizar campos de texto antes de cargar al modelo
Para transformaciones simples (quitar caracteres específicos, reemplazar texto fijo), Text.Remove, Text.Replace y Text.Select son más rápidas y no dependen de Web.Page. REGEX conviene cuando el patrón es dinámico o complejo.
Si trabajás con texto en Power BI y necesitás limpiar HTML además de aplicar patrones, combiná estas funciones con Html.Table que vimos en Convertir HTML a texto en Power Query: primero limpiás las etiquetas, después aplicás REGEX al texto resultante.

Deja una respuesta