Informes y Documentos
Reports y DataItems
Dificultad: 8/10Los 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:
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:
- El Dataset (código AL): Decide qué datos exactos de la base de datos se extraen, iteran y preparan. Es la cocina que recolecta los ingredientes.
- El Layout (diseño Word/RDLC/HTML): Una vez los datos están compilados y limpios en memoria, se inyectan dentro de una plantilla visual para ser dibujados en un papel virtual con tablas, logos y texto formateado.
¿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.
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 |
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.
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:
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 |
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
FindSetniNext. - Usa
OnPreDataItempara filtrar y ordenar, yOnAfterGetRecordpara calcular. Nunca al revés: filtrar enOnAfterGetRecordes ineficiente. DataItemLinkes el equivalente alSubPageLinkde las pages: conecta el DataItem hijo con el padre.DataItemTableFilterpermite aplicar un filtro fijo al DataItem que el usuario no puede cambiar (similar aSourceTableViewen pages).- Las variables declaradas en el Report son globales: accesibles desde cualquier trigger y cualquier DataItem.