M es el lenguaje de fórmulas que está detrás de cada paso que haces con clics en Power Query, tanto en Power BI como en Excel. Puedes hacer la mayor parte de la limpieza de datos sin escribir M, pero aprender a leerlo y editarlo un poco te permite arreglar pasos rotos, reutilizar lógica y resolver problemas que los botones no resuelven. Esta introducción práctica cubre la estructura de una consulta, la sintaxis básica y el puñado de funciones que vas a usar más.
Dónde vive M (y por qué ya lo estás usando)
Cada vez que quitas una columna, cambias un tipo de dato o filtras filas en el Editor de Power Query, este escribe una línea de M por ti. Para verla, abre Transformar datos y elige Inicio > Editor avanzado. También puedes activar la barra de fórmulas (Vista > Barra de fórmulas) para ver el M del paso seleccionado en la lista de Pasos aplicados.
Si Power Query es nuevo para ti, empieza con nuestra guía de Power Query en Excel; todo lo que verás allí aplica también en Power BI.
Anatomía de una consulta: let ... in
Casi todas las consultas tienen la misma forma. Un bloque let define pasos con nombre, e in indica cuál es el resultado final:
let
Source = Csv.Document(
File.Contents("C:\Data\sales.csv"),
[Delimiter = ",", Encoding = 65001, QuoteStyle = QuoteStyle.Csv]
),
PromotedHeaders = Table.PromoteHeaders(Source, [PromoteAllScalars = true]),
ChangedTypes = Table.TransformColumnTypes(
PromotedHeaders,
{{"OrderDate", type date}, {"Region", type text}, {"Amount", type number}}
),
FilteredRows = Table.SelectRows(ChangedTypes, each [Amount] > 0)
in
FilteredRows
Lo más importante que debes notar:
- Cada paso tiene la forma Nombre = expresión y se separa con comas. El último paso antes de
inno lleva coma. - Cada paso normalmente recibe el paso anterior como primer argumento. Así se construye la cadena de transformaciones.
- Los nombres de pasos con espacios se escriben como
#"Tipo cambiado". Renombrarlos sin espacios (por ejemplo,ChangedTypes) hace el código más fácil de leer. - M distingue mayúsculas y minúsculas.
Table.SelectRowsfunciona;table.selectrowsno. Los nombres de columna también distinguen mayúsculas.
Los bloques básicos: valores y tipos
M trabaja con un grupo pequeño de tipos de valores. Reconocerlos hace que los mensajes de error sean mucho más fáciles de entender:
"Hello" // text
42 // number
#date(2026, 10, 25) // date
true // logical
{1, 2, 3} // list
[Name = "Ana", Sales = 1200] // record
#table({"A", "B"}, {{1, 2}}) // table with one row
Las llaves { } crean listas, los corchetes [ ] crean registros (y sirven para acceder a sus campos), y las tablas son, en esencia, listas de registros con las mismas columnas. Los comentarios se escriben con // para una línea o /* ... */ para varias.
Los tipos importan. Definir el tipo de cada columna (type date, type number, Int64.Type, type text) afecta cómo se cargan los datos al modelo, cómo se comprimen y si los pasos siguientes funcionan. Una causa muy común de errores es una columna de texto que debería ser numérica.
La palabra clave "each" y el guion bajo
Vas a ver each por todas partes. Es una forma corta de escribir una pequeña función que se ejecuta una vez por fila (o por elemento). Dentro de ella, [Amount] significa "el campo Amount de la fila actual", y _ significa "el elemento actual completo":
// These two lines do exactly the same thing
Table.SelectRows(Sales, each [Amount] > 0)
Table.SelectRows(Sales, (row) => row[Amount] > 0)
Cuando entiendes esto, casi todo el M generado se vuelve legible: "selecciona las filas de Sales donde, para cada fila, Amount es mayor que cero".
Las funciones que más vas a usar
| Tarea | Función | Ejemplo |
|---|---|---|
| Filtrar filas | Table.SelectRows | Table.SelectRows(T, each [Region] = "East") |
| Agregar una columna | Table.AddColumn | Table.AddColumn(T, "Net", each [Amount] - [Cost]) |
| Cambiar tipos | Table.TransformColumnTypes | {{"Amount", type number}} |
| Transformar valores en su lugar | Table.TransformColumns | {{"Name", Text.Trim}} |
| Conservar o quitar columnas | Table.SelectColumns / Table.RemoveColumns | Table.RemoveColumns(T, {"Notes"}) |
| Agrupar y resumir | Table.Group | Totales por Region con List.Sum |
| Combinar dos tablas | Table.NestedJoin | Combinar Sales con Product por ProductID |
| Limpiar texto | Text.Trim, Text.Proper, Text.Upper | Text.Proper("ana lópez") |
| Trabajar con fechas | Date.Year, Date.Month, Date.StartOfMonth | Date.Year([OrderDate]) |
Ejemplo práctico: limpiar y enriquecer una tabla de ventas
Esta es una consulta realista que limpia una columna de texto, agrega una columna condicional, extrae el año y calcula un margen de forma segura:
let
Source = Sales,
CleanRegion = Table.TransformColumns(
Source,
{{"Region", each Text.Proper(Text.Trim(_)), type text}}
),
AddSize = Table.AddColumn(
CleanRegion,
"Order Size",
each if [Amount] >= 1000 then "Large"
else if [Amount] >= 250 then "Medium"
else "Small",
type text
),
AddYear = Table.AddColumn(AddSize, "Year", each Date.Year([OrderDate]), Int64.Type),
SafeMargin = Table.AddColumn(
AddYear,
"Margin %",
each try ([Amount] - [Cost]) / [Amount] otherwise null,
type number
)
in
SafeMargin
Hay varios detalles que vale la pena aprender de este ejemplo:
- Text.Proper(Text.Trim(_)) se aplica a cada valor de Region: quita los espacios de más y corrige las mayúsculas, así " east" y "EAST" terminan como "East".
- if ... then ... else es la forma de escribir condiciones en M. A diferencia de Excel, no es una función, y el
elsees obligatorio. - El último argumento de
Table.AddColumndefine el tipo de la columna, así que no necesitas un paso extra de "Tipo cambiado". - try ... otherwise atrapa errores, como una división entre cero o un valor faltante, y devuelve un valor alternativo en lugar de romper la actualización.
Cómo escribir tus propias funciones
Cuando repites la misma lógica en varias consultas, conviértela en una función. Crea una Consulta en blanco, abre el Editor avanzado y escribe algo como esto:
// Query named fnAddTax
(amount as number, rate as number) as number =>
amount * (1 + rate)
// Using it in another query
Table.AddColumn(Sales, "Amount With Tax", each fnAddTax([Amount], 0.07), type number)
La flecha => separa los parámetros del cuerpo de la función. Las funciones también son lo que Power Query crea por detrás cuando combinas todos los archivos de una carpeta.
Plegado de consultas: por qué importa el orden de los pasos
Cuando tu origen es una base de datos, Power Query intenta traducir tus pasos a una consulta nativa (normalmente SQL) para que la base de datos haga el trabajo. A esto se le llama plegado de consultas (query folding). Los filtros y la eliminación de columnas colocados al principio suelen plegarse; algunos pasos personalizados, como agregar un índice o ciertas funciones propias, pueden detener el plegado de todos los pasos que vienen después. En Power BI Desktop puedes hacer clic derecho en un paso y revisar Ver consulta nativa, o fijarte en los indicadores de plegado de Power Query, para saber qué se está plegando. La documentación de Microsoft Learn indica qué conectores y pasos lo admiten. Si sabes SQL, nuestra guía de SQL para Power BI explica cuándo conviene llevar la lógica al origen.
Próximos pasos
Abre una de tus consultas actuales en el Editor avanzado y simplemente léela, paso a paso, usando los patrones de este artículo. Después cambia el nombre de los pasos, agrega una columna editando el M directamente y confirma que todavía se actualiza sin errores. Cuando tus datos estén limpios, modélalos bien con nuestra guía de esquema estrella.
Mira: lecciones en video


Preguntas frecuentes
¿Necesito aprender M para usar Power Query?
No. La mayoría de las transformaciones se hacen con los botones de la cinta. Saber leer y editar un poco de M te ayuda cuando necesitas corregir errores, reutilizar lógica o hacer algo que la interfaz no ofrece.
¿El lenguaje M es lo mismo que DAX?
No. M prepara y transforma los datos antes de cargarlos al modelo, mientras que DAX calcula medidas y columnas dentro del modelo. Tienen sintaxis y propósitos distintos.
¿M distingue mayúsculas y minúsculas?
Sí. Los nombres de funciones, de columnas y las comparaciones de texto distinguen mayúsculas en M, y es una causa común de errores para principiantes.
¿Qué significa 'each' en Power Query?
Es una forma corta de escribir una función de un argumento que se ejecuta una vez por fila o elemento. Dentro de ella, [Columna] se refiere a un campo de la fila actual y _ al elemento actual.
Marca esta lección como completada para seguir tu progreso.