Informes y Documentos
Jerarquía en Reports
Dificultad: 9/10Si 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:
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:
- El bucle padre (
DataItem: Socio) coge al Socio A (iteración 1). - Inmediatamente el motor desciende al bucle hijo (
DataItem: Préstamo). El hijo solo itera los préstamos cuyoNo. Sociocoincida con el padre actual. - El bucle hijo itera los 4 préstamos del Socio A, enviando sus columnas al layout para imprimir.
- Termina el hijo. El padre recupera el control y salta al Socio B (iteración 2).
- 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:
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:
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:
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
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
DataItemLinkes el equivalente alSubPageLinkde las pages: conecta el hijo con el padre por campo.DataItemTableFilteraplica 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
OnAfterGetRecorddel padre y súmala enOnAfterGetRecorddel 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.