EJERCICIO 07 · TRIGGERS DE TABLA
Triggers de Tabla
Dificultad: 6/10Ejercicios anteriores
¿Qué tienes construido hasta ahora?
- Enum
"Rental Status"con los estados de maquinaria, extensible. - Tabla
"Maquinaria"(50100): FlowFields con"Date Filter"conectado a"No. Alquileres","Tiene Historial"y"Ultimo Cambio Estado". - Tabla
"Cabecera Alquiler"(50103): FlowFields"No. Lineas"y"Descripcion Maquina". - Tabla
"Linea Alquiler"(50101): PK compuesta,TableRelationhacia ambas tablas. - Tabla
"Historial Estado Maquina"(50102):AutoIncrement,TableRelationcondicional según"Tipo Origen". - Codeunit
"Maquinaria Stats"(50100) con comparativa trimestral usandoSetRange+CalcFields. - Pages
"Maquinaria List","Maquinaria Card"y"Cabecera Alquiler Card".
Hasta ahora el modelo de datos está completo: tablas, relaciones, FlowFields y FlowFilters. Pero las tablas no reaccionan a nada todavía. Si alguien crea un alquiler, la tabla no asigna ningún valor automáticamente. Si alguien borra una cabecera con líneas activas, BC lo permite sin protestar.
En este ejercicio vas a añadir los triggers de tabla que dan vida al proyecto, repartidos entre las tres tablas principales. El diagrama muestra qué trigger cubre cada momento del ciclo de vida de un alquiler:
OnInsert
Asignar fecha y estado inicial
OnValidate
Calcular días, validar fechas
OnModify
Auditar usuario y registrar cambio en historial
OnDelete
Impedir borrar con líneas activas
OnRename
Bloquear cambio de PK
Ejercicio 01
OnInsert · OnModify
Inicialización y auditoría en Cabecera Alquiler
Objetivo: Hacer que
"Cabecera Alquiler" se autocomplete al
crearse (fecha de inicio = hoy, estado = Disponible) y que registre el usuario que
hizo el último cambio cada vez que se modifique.
Instrucciones
- Abre
CabeceraAlquiler.Table.aly añade dos campos físicos nuevos:"Creado Por"(Code[50]) y"Modificado Por"(Code[50]), ambos conEditable = false. - Escribe el trigger
OnInsert()al nivel raíz de la tabla (fuera defieldsykeys): asignaRec."Fecha Inicio" := Today,Rec."Estado" := Rec."Estado"::DisponibleyRec."Creado Por" := UserId. - Escribe el trigger
OnModify(): asignaRec."Modificado Por" := UserId.
Pista de Código
AL — CabeceraAlquiler.Table.al (triggers raíz)
// ── Campos nuevos (dentro de fields) ────────────────────────
field(9; "Creado Por"; Code[50]) { Editable = false; }
field(10; "Modificado Por"; Code[50]) { Editable = false; }
// ── Triggers raíz (fuera de fields y keys) ──────────────────
trigger OnInsert()
begin
Rec."Fecha Inicio" := Today;
Rec."Estado" := Rec."Estado"::Disponible;
Rec."Creado Por" := UserId;
end;
trigger OnModify()
begin
Rec."Modificado Por" := UserId;
end;
⚠️ Recuerda: Los triggers de tabla se ejecutan siempre,
independientemente de desde qué pantalla llegue la operación. El
OnModify se
disparará también desde el codeunit "Maquinaria Stats" o desde cualquier otro
proceso que modifique la tabla.
Ejercicio 02
OnValidate · Cascada
Validación de fechas y cálculo de días en Linea Alquiler
Objetivo: Añadir a la tabla
"Linea Alquiler" un campo
"Dias Alquiler" que se calcule automáticamente cuando el usuario introduzca
la fecha de fin, y que impida fechas de fin anteriores a la de inicio.
Instrucciones
- Abre
LineaAlquiler.Table.aly añade el campo"Dias Alquiler"(Integer) conEditable = false. - En el campo
"Fecha Fin", añade el triggerOnValidate():- Si
Rec."Fecha Fin" < Rec."Fecha Inicio"lanza unErrorcon el mensaje 'La fecha de fin no puede ser anterior a la de inicio.' - Si la validación pasa, calcula:
Rec."Dias Alquiler" := Rec."Fecha Fin" - Rec."Fecha Inicio".
- Si
- Añade también un
OnValidate()en"Fecha Inicio"que dispare en cascada la revalidación de"Fecha Fin"si este ya tiene valor:if Rec."Fecha Fin" <> 0D then Rec.Validate("Fecha Fin").
Pista de Código
AL — LineaAlquiler.Table.al (campos y triggers)
// ── Campo nuevo ─────────────────────────────────────────────
field(6; "Dias Alquiler"; Integer) { Editable = false; }
// ── OnValidate en Fecha Inicio: cascada hacia Fecha Fin ─────
field(4; "Fecha Inicio"; Date)
{
trigger OnValidate()
begin
if Rec."Fecha Fin" <> 0D then
Rec.Validate("Fecha Fin"); // revalida para recalcular días
end;
}
// ── OnValidate en Fecha Fin: validación + cálculo ───────────
field(5; "Fecha Fin"; Date)
{
trigger OnValidate()
begin
if Rec."Fecha Fin" < Rec."Fecha Inicio" then
Error('La fecha de fin no puede ser anterior a la de inicio.');
Rec."Dias Alquiler" := Rec."Fecha Fin" - Rec."Fecha Inicio";
end;
}
💡 Recuerda:
Rec.Validate("Fecha Fin") no solo asigna el
valor: dispara el OnValidate del campo destino completo, incluyendo la
validación de integridad referencial y cualquier otra lógica que tenga. Úsalo siempre
que quieras que un cambio en un campo reaccione correctamente a través de la cadena
de validaciones.
Ejercicio 03
OnDelete
Protege la cabecera contra borrados peligrosos
Objetivo: Impedir que se borre una
"Cabecera Alquiler" que
tenga líneas asociadas en "Linea Alquiler", y aprovechar el FlowField
"No. Lineas" que ya tienes para hacer la comprobación de forma elegante.
Instrucciones
- En
CabeceraAlquiler.Table.al, añade el triggerOnDelete(). - Dentro del trigger, llama primero a
Rec.CalcFields("No. Lineas")para calcular el FlowField. - Si
Rec."No. Lineas" > 0lanza unErrorcon el mensaje: 'No se puede eliminar una cabecera con %1 línea(s) asociada(s).', pasandoRec."No. Lineas"como parámetro. - Añade también el trigger
OnRename()que bloquee cualquier cambio de PK con el mensaje: 'Los alquileres no pueden ser renombrados.'
Pista de Código
AL — CabeceraAlquiler.Table.al (triggers OnDelete y
OnRename)
trigger OnDelete()
begin
Rec.CalcFields("No. Lineas"); // forzamos el cálculo del FlowField
if Rec."No. Lineas" > 0 then
Error(
'No se puede eliminar una cabecera con %1 línea(s) asociada(s).',
Rec."No. Lineas"
);
end;
trigger OnRename()
begin
Error('Los alquileres no pueden ser renombrados.');
end;
⚠️ Ojo: Dentro de un trigger
OnDelete, los FlowFields
no se calculan automáticamente: debes llamar a CalcFields explícitamente
antes de leer su valor, igual que harías desde un codeunit.
Ejercicio 04
OnModify · Historial automático
Registra cada cambio de estado en el historial automáticamente
Objetivo: Completar el ciclo del proyecto conectando la tabla
"Maquinaria" con la tabla "Historial Estado Maquina" mediante
el trigger OnModify(), de modo que cualquier cambio en el campo
"Estado" genere una fila de historial de forma completamente automática.
Instrucciones
- Abre
Maquinaria.Table.aly añade el triggerOnModify(). - Dentro del trigger, declara una variable de tipo
Record "Historial Estado Maquina"y otra de tipoRecord "Maquinaria"llamadaMaquinaAntes. - Usa
MaquinaAntes.Get(Rec."No.")para leer el estado antes del cambio (el registro aún no ha sido guardado en este punto). - Si
MaquinaAntes."Estado" <> Rec."Estado", inserta un nuevo registro en"Historial Estado Maquina"con:"No. Maquina","Estado Anterior"=MaquinaAntes."Estado","Estado Nuevo"=Rec."Estado"y"Fecha Cambio"=CurrentDateTime.
Pista de Código
AL — Maquinaria.Table.al (trigger OnModify)
trigger OnModify()
var
Historial : Record "Historial Estado Maquina";
MaquinaAntes : Record "Maquinaria";
begin
// Leemos el estado guardado ANTES de este modify
MaquinaAntes.Get(Rec."No.");
// Solo insertamos historial si el estado ha cambiado realmente
if MaquinaAntes."Estado" <> Rec."Estado" then begin
Historial.Init();
Historial."No. Maquina" := Rec."No.";
Historial."Estado Anterior" := MaquinaAntes."Estado";
Historial."Estado Nuevo" := Rec."Estado";
Historial."Fecha Cambio" := CurrentDateTime;
Historial."Tipo Origen" := Historial."Tipo Origen"::Manual;
Historial.Insert(true); // true para ejecutar el OnInsert de la tabla
end;
end;
💡 Recuerda: Dentro de
OnModify, Rec contiene
los valores nuevos que están a punto de guardarse. Para leer los valores
anteriores debes hacer un Get explícito sobre el mismo registro,
ya que en este momento el registro viejo aún existe en la base de datos sin modificar.
💡 Bonus: Ahora que el historial se inserta automáticamente desde
OnModify, el trigger OnValidate del campo "Estado"
en MaquinariaCard.Page.al ya no necesita hacerlo manualmente. Puedes
simplificarlo para que solo muestre el aviso de Retirado, y dejar toda la
lógica de persistencia en la tabla donde siempre debió estar.