Informes y Documentos

Reports y DataItems

Dificultad: 8/10

Los Reports son los objetos que generan los documentos finales que verá el usuario: PDFs comerciales, facturas con logotipo, albaranes, listados contables mensuales. Si una Query consulta datos internamente, un Report los presenta al usuario en un documento imprimible o exportable.

Estructura base de un Report

Un Report en AL tiene cuatro secciones principales:

AL
report 50100 "Lista de Socios"
{
    Caption         = 'Listado de Socios';
    UsageCategory   = ReportsAndAnalysis;
    ApplicationArea = All;

    // 1. Dataset: qué datos se leen y cómo
    dataset
    {
        dataitem(Socio; Socio)
        {
            column(No_Socio;  "No.")    { Caption = 'Número'; }
            column(Nombre;    Nombre)  { Caption = 'Nombre'; }
            column(Email;     Email)   { Caption = 'Email'; }
        }
    }

    // 2. RequestPage: pantalla previa de filtros (opcional)
    requestpage { }

    // 3. Rendering: qué layout visual usar
    rendering
    {
        layout(RDLCLayout)
        {
            Type       = RDLC;
            LayoutFile = 'src/layouts/ListaSocios.rdlc';
        }
    }
}

La arquitectura de un Report

Se divide en dos capas totalmente distintas:

¿Qué es un DataItem?

Un DataItem representa una tabla de BC desde la que vas a leer datos para el informe. Lo más importante: un DataItem funciona como un bucle automático. BC recorre todos los registros de esa tabla (con los filtros que definas) y por cada registro ejecuta el bloque de contenido del informe una vez.

Imagina que tienes 200 socios. Si creas un DataItem sobre esa tabla, BC recorrerá cada uno automáticamente sin que tú tengas que escribir ningún bucle ni contador.

AL
dataset
{
    dataitem(Socio; Socio)  // NombreLogico ; NombreFisicoTabla
    {
        // Las columnas son los campos que quieres usar en el diseño
        column(No_Socio; "No.")  { Caption = 'Número de Socio'; }
        column(Nombre;   Nombre) { Caption = 'Nombre'; }
        column(Email;    Email)  { Caption = 'Correo Electrónico'; }
    }
}

Triggers de un DataItem

Cada DataItem tiene tres triggers donde puedes añadir lógica propia:

Trigger Cuándo se ejecuta Para qué sirve
OnPreDataItem() Una sola vez, antes de empezar a leer Establecer filtros, ordenar los datos
OnAfterGetRecord() En cada registro leído Calcular valores, transformar texto, acumular totales
OnPostDataItem() Una sola vez, al terminar todos los registros Mostrar subtotales, hacer limpieza de variables
AL
dataitem(Socio; Socio)
{
    column(No_Socio;        "No.")       { }
    column(Nombre_Socio;    Nombre)     { }
    column(Dias_Como_Socio; DiasComoSocio) { }  // columna calculada con variable

    trigger OnPreDataItem()
    begin
        SetCurrentKey(Nombre);  // ordenamos por nombre antes de leer
    end;

    trigger OnAfterGetRecord()
    begin
        // Calculamos los días como socio por cada registro
        DiasComoSocio := Today() - "Fecha Alta";

        // Si no tiene teléfono, ponemos un texto amable
        if Telefono = '' then
            Telefono := 'No registrado';
    end;
}

var
    DiasComoSocio: Integer;  // variable global del Report

Jerarquía de DataItems — Anidación

Un informe de una sola tabla está bien para listados simples. Pero en la realidad casi siempre quieres mostrar datos relacionados: un socio y todos sus préstamos, o una factura con todas sus líneas.

Para eso puedes anidar DataItems: poner un DataItem dentro de otro. El DataItem interior (hijo) se repetirá automáticamente dentro del contexto del exterior (padre). BC sabe qué préstamos pertenecen a qué socio porque tú le indicas el campo de enlace con DataItemLink.

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

    dataitem(Prestamo; Prestamo)    // NIVEL 2 - Hijo (va DENTRO del padre)
    {
        // Para cada socio, solo lee los préstamos donde "No. Socio" coincida
        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)       { }
        }
    }
}

Ordenar los datos en cada nivel

Puedes controlar el orden en que aparecen los datos usando SetCurrentKey dentro de OnPreDataItem() y SetAscending para la dirección:

AL
dataitem(Socio; Socio)
{
    trigger OnPreDataItem()
    begin
        SetCurrentKey(Nombre);  // socios ordenados de la A a la Z
    end;

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

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

Triggers globales del Report

Además de los triggers de cada DataItem, el Report tiene dos triggers globales que se ejecutan una sola vez:

Trigger Cuándo se ejecuta Uso típico
OnPreReport() Una vez antes de procesar cualquier dato Validar opciones del usuario, comprobar fechas
OnPostReport() Una vez cuando el informe ha terminado Mostrar mensaje de confirmación, registrar la ejecución
AL
trigger OnPreReport()
begin
    // Validamos que la fecha "Desde" no sea posterior a "Hasta"
    if (FechaDesde <> 0D) and (FechaHasta <> 0D) then
        if FechaDesde > FechaHasta then
            Error('La fecha "Desde" no puede ser posterior a la fecha "Hasta".');
end;

trigger OnPostReport()
begin
    Message('Informe generado correctamente.');
end;

💡 Tips

  • Un DataItem es un bucle automático: BC recorre todos los registros de la tabla por ti, no tienes que escribir ningún FindSet ni Next.
  • Usa OnPreDataItem para filtrar y ordenar, y OnAfterGetRecord para calcular. Nunca al revés: filtrar en OnAfterGetRecord es ineficiente.
  • DataItemLink es el equivalente al SubPageLink de las pages: conecta el DataItem hijo con el padre.
  • DataItemTableFilter permite aplicar un filtro fijo al DataItem que el usuario no puede cambiar (similar a SourceTableView en pages).
  • Las variables declaradas en el Report son globales: accesibles desde cualquier trigger y cualquier DataItem.
← Volver a Teoría