in
Inicio › Blog › List.Generate en Power Query M: el bucle que casi nadie conoce
Power BI

List.Generate en Power Query M: el bucle que casi nadie conoce

Jose Luis Rico 2 abril 2026 ⏱ 10 min lectura
List.Generate en Power Query M para crear bucles y cálculos iterativos

Si llevas un tiempo usando Power Query, ya dominas los pasos habituales: limpiar columnas, filtrar filas, combinar tablas. Pero hay una categoría de problemas que, tarde o temprano, te va a hacer chocar contra una pared: los cálculos donde cada fila depende del resultado de la anterior.

Un saldo acumulado. Una tabla de amortización. Una simulación de stock con reposición. En Excel tienes fórmulas que se propagan hacia abajo. En Python tienes un for. En Power Query M… no tienes nada de eso. O eso parece.

Existe List.Generate. Es la función de bucle que nadie enseña, que no aparece en los tutoriales de iniciación, y que puede resolver en diez líneas lo que otros intentan con columnas auxiliares, índices y merges imposibles. En este artículo vas a aprender cómo funciona, con dos ejemplos reales que puedes adaptar a tu trabajo hoy mismo.

El problema: Power Query no tiene bucle for

Imagina que tienes una tabla con los ingresos y gastos de cada mes. Quieres calcular el saldo acumulado: el dinero que queda en caja al final de cada mes, teniendo en cuenta el saldo del mes anterior.

En Excel lo resuelves con una fórmula en C2 que suma C1 + B2 – A2 y la arrastras hacia abajo. Tres segundos.

En Power Query, cada fila se procesa de forma independiente. No puedes referenciar el resultado de la fila anterior mientras estás calculando la fila actual. Si intentas hacer esto con una columna personalizada, Power Query no tiene forma de «recordar» qué calculó en el paso anterior.

Las alternativas habituales son costosas: exportar a Excel, calcular allí y reimportar; usar DAX con sus propias limitaciones de contexto; o construir estructuras recursivas que se vuelven inmanejables. List.Generate cambia eso.

¿Qué es List.Generate?

List.Generate es una función de Power Query M que genera una lista paso a paso, donde cada elemento se construye a partir del anterior. Sigue ejecutándose mientras se cumpla una condición y se detiene cuando esa condición falla.

La función tiene esta firma:

List.Generate(
    initial   as function,
    condition as function,
    next      as function,
    optional selector as function
)

Cuatro argumentos. Vamos con cada uno.

initial — el punto de partida

Es una función sin argumentos (por eso lleva () =>). Define el estado inicial: el valor o registro con el que arranca el bucle.

() => [mes = 1, saldo = 0]

Aquí el estado inicial es un registro con dos campos: el mes (empezamos en 1) y el saldo (empieza en 0).

condition — ¿seguimos o paramos?

Es una función que recibe el estado actual y devuelve true o false. Mientras devuelva true, el bucle continúa. En cuanto devuelve false, se detiene.

(estado) => estado[mes] <= 12

Esto dice: sigue generando elementos mientras el mes sea menor o igual a 12.

next — cómo pasamos al siguiente estado

Es la función más importante. Recibe el estado actual y devuelve el siguiente estado. Aquí es donde ocurre el cálculo que hace depender cada paso del anterior.

(estado) => [
    mes   = estado[mes] + 1,
    saldo = estado[saldo] + 500
]

Cada iteración avanza el mes en 1 y suma 500 al saldo.

selector — qué queremos extraer (opcional)

Si no lo especificas, List.Generate devuelve una lista con todos los estados completos (registros). Con el selector puedes extraer solo el campo que te interesa.

(estado) => estado[saldo]

Esto devolvería solo los saldos, sin el campo mes.

La analogía

Piensa en List.Generate como una cadena de montaje: la cadena empieza con una pieza inicial (initial); en cada estación se comprueba si la pieza cumple el criterio para seguir avanzando (condition); si sí, se transforma para crear la siguiente pieza (next); y al final se extrae la parte que nos interesa de cada pieza (selector). La cadena se detiene cuando una pieza no cumple el criterio.

Ejemplo 1: saldo acumulado mes a mes

El escenario

Tienes esta tabla con los ingresos y gastos de cada mes:

MesIngresosGastos
18.0005.200
29.5006.100
37.8004.900
410.2007.300
58.6005.800

Quieres añadir una columna con el saldo acumulado al final de cada mes. El saldo del mes 1 es Ingresos - Gastos. El del mes 2 es el saldo del mes 1 más el neto del mes 2. Y así sucesivamente.

El código M completo

Abre el editor avanzado de Power Query y escribe esta consulta. La tabla fuente se llama TablaSaldos.

let
    // 1. Cargamos la tabla fuente
    Fuente = TablaSaldos,

    // 2. Extraemos las columnas como listas
    ListaIngresos = Fuente[Ingresos],
    ListaGastos = Fuente[Gastos],
    NumFilas = List.Count(ListaIngresos),

    // 3. Generamos la lista de saldos acumulados
    ListaSaldos = List.Generate(
        () => [
            i = 0,
            saldo = ListaIngresos{0} - ListaGastos{0}
        ],
        (e) => e[i] < NumFilas,
        (e) => [
            i = e[i] + 1,
            saldo = if e[i] + 1 < NumFilas
                then e[saldo]
                    + ListaIngresos{e[i] + 1}
                    - ListaGastos{e[i] + 1}
                else e[saldo]
        ],
        (e) => e[saldo]
    ),

    // 4. Índice para recuperar el saldo por posición
    TablaConIndice = Table.AddIndexColumn(
        Fuente, "idx", 0, 1, Int64.Type
    ),
    TablaConSaldo = Table.AddColumn(
        TablaConIndice,
        "Saldo acumulado",
        each ListaSaldos{[idx]},
        type number
    ),
    TablaFinal = Table.RemoveColumns(
        TablaConSaldo, {"idx"}
    )

in
    TablaFinal

Explicación paso a paso

Pasos 1 y 2. Cargamos la tabla y extraemos las dos columnas como listas. En M, Tabla[Columna] devuelve una lista. Esto nos permite acceder a los valores por índice con Lista{i}, que es lo que necesitaremos dentro del bucle.

Paso 3: el List.Generate.

  • initial: arrancamos con i = 0 (primer mes) y saldo igual al neto del primer mes. Nótese que ya calculamos el primer saldo aquí, no en next.
  • condition: mientras i < NumFilas, seguimos. Cuando i alcance el número de filas, paramos.
  • next: avanzamos i en 1 y calculamos el nuevo saldo sumando el saldo anterior más el neto del mes siguiente. El if protege contra acceder a un índice fuera de rango en la última iteración.
  • selector: extraemos solo el campo saldo de cada estado, no el índice.

El resultado es una lista de 5 números: el saldo acumulado al final de cada mes.

Paso 4. Añadimos un índice a la tabla original con Table.AddIndexColumn y usamos ese índice para recuperar el saldo correspondiente de ListaSaldos. Al final eliminamos la columna auxiliar de índice.

Ejemplo 2: tabla de amortización de préstamo

Este ejemplo es más completo y demuestra el potencial real de List.Generate: generar una tabla entera donde cada fila es un estado distinto del proceso.

El escenario

Tienes un préstamo con estos parámetros:

  • Capital: 50.000 €
  • Tipo de interés anual: 4,5 %
  • Número de cuotas: 60 (5 años, pago mensual)

Quieres generar la tabla de amortización completa: para cada cuota, cuánto corresponde a intereses, cuánto amortiza capital y cuál es el saldo pendiente. En el sistema de amortización francés (cuota constante), la cuota mensual se calcula con:

cuota = capital × r / (1 - (1 + r)^(-n))

Donde r es el tipo de interés mensual (tipo_anual / 12) y n es el número de cuotas.

El código M completo

let
    // 1. Parámetros del préstamo
    Capital = 50000,
    TipoAnual = 0.045,
    NumCuotas = 60,
    TipoMes = TipoAnual / 12,

    // 2. Cuota mensual fija (sistema francés)
    Cuota = Capital * TipoMes
        / (1 - Number.Power(1 + TipoMes, -NumCuotas)),

    // 3. Tabla de amortización con List.Generate
    ListaAmortizacion = List.Generate(
        // Estado inicial: cuota 1
        () => [
            n_cuota = 1,
            saldo = Capital,
            interes = Capital * TipoMes,
            amortizacion = Cuota - Capital * TipoMes,
            saldo_nuevo = Capital
                - (Cuota - Capital * TipoMes)
        ],

        // Condición: n_cuota <= NumCuotas
        (e) => e[n_cuota] <= NumCuotas,

        // Siguiente: parte de e[saldo_nuevo]
        (e) => [
            n_cuota = e[n_cuota] + 1,
            saldo = e[saldo_nuevo],
            interes = e[saldo_nuevo] * TipoMes,
            amortizacion = Cuota
                - e[saldo_nuevo] * TipoMes,
            saldo_nuevo = e[saldo_nuevo]
                - (Cuota - e[saldo_nuevo] * TipoMes)
        ],

        // Selector: registro con valores redondeados
        (e) => [
            Cuota = e[n_cuota],
            Interes = Number.Round(e[interes], 2),
            Amortizacion = Number.Round(e[amortizacion], 2),
            Cuota_total = Number.Round(Cuota, 2),
            Saldo_pendiente = Number.Round(e[saldo], 2)
        ]
    ),

    // 4. Convertimos la lista de registros en tabla
    TablaAmortizacion = Table.FromRecords(ListaAmortizacion),

    // 5. Ajustamos tipos de columna
    TablaFinal = Table.TransformColumnTypes(
        TablaAmortizacion, {
            {"Cuota", Int64.Type},
            {"Interes", Currency.Type},
            {"Amortizacion", Currency.Type},
            {"Cuota_total", Currency.Type},
            {"Saldo_pendiente", Currency.Type}
        }
    )

in
    TablaFinal

Explicación paso a paso

Parámetros (pasos 1 y 2). Definimos las variables del préstamo y calculamos la cuota fija. Esto es aritmética pura en M, sin nada especial.

El List.Generate (paso 3). El estado de cada iteración es un registro con cinco campos:

  • n_cuota: número de cuota actual
  • saldo: saldo pendiente al inicio de esa cuota (lo que se muestra en la tabla)
  • interes: intereses que corresponden a esa cuota (saldo × tipo_mensual)
  • amortizacion: capital que se amortiza (cuota_fija - intereses)
  • saldo_nuevo: saldo pendiente después de pagar la cuota, que será el saldo de la iteración siguiente

En initial calculamos todos estos valores para la cuota 1, partiendo del capital completo. En next, cada campo del nuevo estado toma el saldo_nuevo del estado anterior como punto de partida — exactamente lo que haría un bucle for.

El selector no filtra un solo campo sino que devuelve un registro con los nombres de columna ya definidos y los valores redondeados a dos decimales.

Conversión a tabla (paso 4). List.Generate devuelve una lista de registros porque el selector devuelve un registro en cada iteración. Table.FromRecords convierte esa lista directamente en una tabla, creando una columna por cada campo del registro.

Resultado: una tabla de 60 filas con la amortización completa. Las primeras cuotas tienen más intereses y menos amortización; las últimas, al revés, porque el saldo pendiente va bajando mes a mes.

Cómo integrar el resultado en tu consulta

List.Generate siempre devuelve una lista. Lo que hay dentro depende del selector:

  • Si el selector devuelve un número o texto → lista de valores simples
  • Si el selector devuelve un registro → lista de registros
  • Si no hay selector → lista con los estados completos

De lista de valores simples a tabla — usa Table.FromList:

Tabla = Table.FromList(
    ListaSaldos,
    Splitter.SplitByNothing(),
    {"Saldo acumulado"}
)

Splitter.SplitByNothing() indica que cada elemento de la lista va a una fila sin dividirlo. El tercer argumento son los nombres de columna.

De lista de registros a tabla — usa Table.FromRecords:

Tabla = Table.FromRecords(ListaAmortizacion)

Power Query crea automáticamente una columna por cada campo del registro. Es el método más directo cuando el selector ya devuelve registros bien definidos.

Cuándo usar List.Generate y cuándo no

List.Generate es una herramienta potente, pero no es la herramienta para todo. Antes de usarla, plantéate estas preguntas.

Úsala cuando:

  • Cada fila depende del resultado de la anterior. Es el caso de uso central. Si el cálculo de la fila N necesita el valor de la fila N-1, List.Generate es prácticamente la única opción en M.
  • Necesitas simular un proceso iterativo. Amortizaciones, depreciaciones, simulaciones de stock, crecimiento compuesto con condiciones variables.
  • El número de iteraciones lo determina una condición, no solo el número de filas de una tabla existente.

No la uses cuando:

  • Hay una función nativa que hace lo mismo. List.Accumulate resuelve la acumulación simple de forma más legible. List.Generate brilla cuando el estado tiene múltiples campos o la condición de parada es compleja.
  • El cálculo no depende de la fila anterior. Si puedes resolverlo con una columna personalizada estándar, hazlo así. Más simple, más mantenible.
  • Tienes millones de filas. List.Generate no está optimizada para volúmenes muy grandes. Con decenas de miles de filas puede volverse lenta. En esos casos, valora si el cálculo puede hacerse en el origen de datos —SQL, una vista, un notebook de Databricks— antes de llegar a Power Query. En el artículo sobre arquitectura de datos moderna con Databricks y Power BI explico cuándo tiene sentido trasladar estas transformaciones al pipeline de datos.
  • Otra persona tendrá que mantener la consulta y no conoce M avanzado. El código de List.Generate no es intuitivo a primera vista. Si priorizas la mantenibilidad del equipo sobre hacer todo en Power Query, a veces es mejor sacar el cálculo al origen o documentarlo bien.

Conclusión

List.Generate es una de las funciones más infravaloradas de Power Query M. Lleva años en la especificación del lenguaje y sin embargo la mayoría de analistas nunca la toca porque nunca la vieron en un tutorial.

Si te quedas con una idea de este artículo, que sea esta: cuando necesites que un cálculo dependa del resultado del paso anterior, no abandones Power Query ni busques workarounds. Prueba List.Generate primero.

Lo que has aprendido hoy:

  • Los cuatro argumentos de List.Generate y qué hace cada uno.
  • Cómo calcular un saldo acumulado mes a mes sin salir de Power Query.
  • Cómo generar una tabla de amortización completa con un solo bloque de código M.
  • Cómo convertir el resultado en una tabla usable con Table.FromList o Table.FromRecords.
  • Cuándo esta función es la herramienta correcta y cuándo no lo es.

¿Lo has probado ya en algún modelo tuyo? ¿Tienes algún caso donde necesitabas exactamente esto y lo resolviste de otra forma? Cuéntamelo en los comentarios.

Comentarios

Deja tu comentario

Los comentarios son revisados antes de publicarse.

Jose Luis Rico Rocamora

Jose Luis Rico Rocamora

Power BI & Analytics Specialist · PL-300 Certified

Especialista en Business Intelligence con más de 10 años de experiencia en retail, finanzas y operaciones. En Datency comparto lo que aprendo y aplico en el día a día.

¿Necesitas aplicar esto
en tu empresa?

Diseño e implemento soluciones de Power BI adaptadas a tu negocio. Hablamos sin compromiso y vemos qué tiene sentido en tu caso.

Scroll al inicio