Informes y Documentos

Jerarquía en Reports

Dificultad: 9/10

Si la factura de la luz fuera un informe con un solo DataItem, tendrías una sola cara blanca y fea. Los reports reales tienen jerarquías de datos (anidación) para explicar desgloses: cabecera con todos sus hijos, socios con todos sus préstamos, facturas con todas sus líneas.

Sintaxis de la anidación

Para anidar un segundo DataItem dentro de uno padre basta con declararlo dentro del bloque del padre. El DataItemLink es la clave que conecta el hijo con el padre:

AL
dataitem(Socio; "Socio")       // PADRE
{
    column(SocioNombre; Nombre) { }

    dataitem(Prestamo; "Prestamo")  // HIJO (va DENTRO del padre)
    {
        DataItemLink = "No. Socio" = FIELD("No.");  // enlace padre → hijo
        column(FechaDevolucion; "Fecha Devolucion") { }
    }
}

¿Cómo funciona el bucle anidado?

Cuando anidas un DataItem dentro de otro, creas un bucle hijo que se dispara por cada vuelta individual que da el bucle padre. El flujo es siempre el mismo:

  1. El bucle padre (DataItem: Socio) coge al Socio A (iteración 1).
  2. Inmediatamente el motor desciende al bucle hijo (DataItem: Préstamo). El hijo solo itera los préstamos cuyo No. Socio coincida con el padre actual.
  3. El bucle hijo itera los 4 préstamos del Socio A, enviando sus columnas al layout para imprimir.
  4. Termina el hijo. El padre recupera el control y salta al Socio B (iteración 2).
  5. Se repite la cascada hijo para el Socio B... y así sucesivamente hasta el último socio.

Anidación de 3 niveles

Puedes anidar tantos niveles como necesites. El mismo principio se aplica a cada nivel adicional:

AL
dataitem(Socio; Socio)              // NIVEL 1 — Padre
{
    column(Socio_No;     "No.") { }
    column(Socio_Nombre; Nombre) { }

    dataitem(Prestamo; Prestamo)    // NIVEL 2 — Hijo
    {
        DataItemLink = "No. Socio" = FIELD("No.");

        column(Prestamo_No;   "No.")          { }
        column(Prestamo_Dias; "Dias Prestamo") { }

        dataitem(Linea; "Linea Prestamo")  // NIVEL 3 — Nieto
        {
            DataItemLink = "No. Prestamo" = FIELD("No.");

            column(Linea_Libro;    "Titulo Libro") { }
            column(Linea_Devuelto; Devuelto)       { }
        }
    }
}

Acumular totales entre niveles

Un patrón muy habitual es acumular totales del hijo en variables del padre. El truco está en reiniciar el contador en el padre (OnAfterGetRecord del padre) e irlo sumando en el hijo:

AL
dataitem(Socio; Socio)
{
    column(Socio_No;       "No.")          { }
    column(Socio_Nombre;   Nombre)         { }
    column(Socio_TotalDias; TotalDiasSocio) { }  // columna calculada con variable

    trigger OnAfterGetRecord()
    begin
        TotalDiasSocio := 0;  // reiniciamos al cambiar de socio
    end;

    dataitem(Prestamo; Prestamo)
    {
        DataItemLink = "No. Socio" = FIELD("No.");

        // Solo préstamos activos o en devolución
        DataItemTableFilter = Estado = FILTER(Activo | "En Devolucion");

        column(Prestamo_No;    "No.")          { }
        column(Prestamo_Dias;  "Dias Prestamo") { }
        column(Prestamo_Estado; Estado)         { }

        trigger OnAfterGetRecord()
        begin
            TotalDiasSocio += "Dias Prestamo";  // acumulamos en la variable del padre
        end;
    }
}

var
    TotalDiasSocio: Integer;

DataItemTableFilter — Filtro fijo en el hijo

DataItemTableFilter permite aplicar un filtro permanente al DataItem que el usuario no puede modificar desde la RequestPage. Es el equivalente al SourceTableView de las pages:

AL
dataitem(Prestamo; Prestamo)
{
    DataItemLink        = "No. Socio" = FIELD("No.");
    DataItemTableFilter = Estado = FILTER(Activo | "En Devolucion");  // nunca mostrará Devueltos

    column(Prestamo_No;    "No.")  { }
    column(Prestamo_Dias;  "Dias Prestamo") { }
}

Ejemplo completo: socios con préstamos y totales

AL
report 50101 "Socios con Prestamos"
{
    Caption         = 'Socios y sus Préstamos';
    UsageCategory   = ReportsAndAnalysis;
    ApplicationArea = All;

    dataset
    {
        // ── NIVEL 1: SOCIO ─────────────────────────────
        dataitem(Socio; Socio)
        {
            column(Socio_No;        "No.")          { }
            column(Socio_Nombre;    Nombre)         { }
            column(Socio_TotalDias; TotalDiasSocio) { }

            trigger OnPreDataItem()
            begin
                SetCurrentKey(Nombre);  // orden alfabético
            end;

            trigger OnAfterGetRecord()
            begin
                TotalDiasSocio := 0;  // reinicio al cambiar de socio
            end;

            // ── NIVEL 2: PRÉSTAMO ───────────────────────────
            dataitem(Prestamo; Prestamo)
            {
                DataItemLink        = "No. Socio" = FIELD("No.");
                DataItemTableFilter = Estado = FILTER(Activo | "En Devolucion");

                column(Prestamo_No;     "No.")           { }
                column(Prestamo_Inicio; "Fecha Inicio")  { }
                column(Prestamo_Dias;   "Dias Prestamo") { }

                trigger OnPreDataItem()
                begin
                    SetCurrentKey("Fecha Inicio");
                    SetAscending("Fecha Inicio", false);  // más recientes primero
                end;

                trigger OnAfterGetRecord()
                begin
                    TotalDiasSocio += "Dias Prestamo";
                end;

                // ── NIVEL 3: LÍNEA ──────────────────────────────
                dataitem(LineaPrestamo; "Linea Prestamo")
                {
                    DataItemLink = "No. Prestamo" = FIELD("No.");

                    column(Linea_Libro;    "Titulo Libro") { }
                    column(Linea_ISBN;     ISBN)            { }
                    column(Linea_Devuelto; Devuelto)        { }
                }
            }
        }
    }

    var
        TotalDiasSocio: Integer;

    rendering
    {
        layout(RDLCLayout)
        {
            Type       = RDLC;
            LayoutFile = 'src/layouts/SociosPrestamos.rdlc';
        }
    }
}

💡 Tips

  • DataItemLink es el equivalente al SubPageLink de las pages: conecta el hijo con el padre por campo.
  • DataItemTableFilter aplica un filtro fijo que el usuario no puede cambiar. Úsalo para excluir registros que nunca deben aparecer en el informe.
  • Para acumular totales del hijo en el padre: reinicia la variable en OnAfterGetRecord del padre y súmala en OnAfterGetRecord del hijo.
  • Usa SetAscending(campo, false) para ordenar de mayor a menor (fechas recientes primero, Z→A...).
  • No hay límite teórico de niveles de anidación, pero más de 3 o 4 niveles suele indicar que el diseño del informe puede simplificarse.
← Volver a Teoría