Sunteți pe pagina 1din 425

Curso GeneXus 9.

0
Diciembre de 2006

MONTEVIDEO – URUGUAY Av. 18 de Julio 1645 P.4 +598 2 402-2082


CHICAGO – USA 400 N. Michigan Ave. Suite 1600 +(312) 836-9152
MEXICO CITY – MEXICO Calle Leibnitz N° 20, desp. 801 +52 55 5255-4733
SAO PAULO – BRAZIL Rua Samuel Morse 120 Conj. 141 +55 11 5502 6722
Copyright © ARTech Consultores S. R. L. 1988-2006.
Todos los derechos reservados. Este documento no puede ser reproducido de ninguna forma sin el consentimiento expreso
de ARTech Consultores S.R.L. La información contenida en este documento es para uso personal del lector.

MARCAS REGISTRADAS
ARTech y GeneXus son marcas registradas de ARTech Consultores S.R.L.
Todas las otras marcas mencionadas en este documento son propiedad de sus respetivos titulares.
Contenido
Introducción teórica ........................................................................................................ 1

Objeto transacción........................................................................................................ 31
Nomenclatura GIK ............................................................................................ ...45
Tipos de datos..................................................................................................... 51
Dominios ............................................................................................................ 55
Análisis de impacto, reorganizar, especificar, generar............................................... 64
Modos en ejecución.............................................................................................. 72

Integridad referencial ........................................................................................... 73


Índices ............................................................................................................... 76
Manejo de nulos ................................................................................................. 81
Tabla base y tabla extendida ................................................................................ 85
Descripciones en vez de códigos ........................................................................... 89
Reglas ................................................................................................................ 94
Tipos de diálogo ................................................................................................ 106

Atributos fórmula ....................................................................................................... 115


Fórmulas horizontales ....................................................................................... 120
Fórmulas verticales ........................................................................................... 123

Comunicación entre objetos ......................................................................................... 130

Orden de ejecución de reglas y fórmulas ....................................................................... 139


Eventos de disparo ........................................................................................... 145
Orden de ejecución .................................................................................... 151, 152
Eventos en transacciones ................................................................................... 160

Integridad transaccional .............................................................................................. 169

Objetos reporte y procedimiento .................................................................................. 181

Objeto web panel ....................................................................................................... 252

Patterns .................................................................................................................... 312

Subtipos.................................................................................................................... 335

Structured Data Types (SDTs)...................................................................................... 359

Business Components ................................................................................................. 379

Knowledge Manager.................................................................................................... 391

Puesta en producción .................................................................................................. 396

Introducción a metodología GeneXus ............................................................................ 400


Nota al estudiante

Con el objetivo de brindar a los desarrolladores GeneXus una fuente única de información potenciada con una
ingeniería de búsqueda que les permita acceder rápida y fácilmente a la información técnica (release notes,
manuales, tips, whitepapers, etc.) sobre los diferentes productos que desarrolla ARTech, se creó la GXDL
(GeneXus Developer Library), una biblioteca que centraliza toda esta información técnica.

Puede ser consultada online o puede ser utilizada en forma local mediante una sencilla instalación:
http://www.gxtechnical.com/gxdl.

Por sugerencias o comentarios acerca de la presente edición, escríbanos a la dirección


cursogenexus@artech.com.uy.

Departamento de Capacitación
ARTech
INTRODUCCIÓN TEÓRICA

1
Herramientas y Metodologías

REALIDAD

BASE
DE PROGRAMAS
DATOS

Nuestra tarea como profesionales de la informática consiste en desarrollar y mantener aplicaciones


para apoyar al usuario en su actividad. Para realizar esta tarea existen diferentes herramientas y
metodologías.

GeneXus es una herramienta para el desarrollo de aplicaciones sobre bases de datos. Su objetivo es
permitir la implantación de aplicaciones en el menor tiempo y con la mejor calidad posible.

A grandes rasgos, el desarrollo de una aplicación implica tareas de análisis, diseño e


implementación. La vía de GeneXus para alcanzar el objetivo anterior es liberar a las personas de
las tareas automatizables (como el diseño de la base de datos), permitiéndoles así concentrarse en
las tareas realmente difíciles y no automatizables (como comprender los problemas del usuario).

GeneXus emplea una metodología que tiene un enfoque muy diferente al de las metodologías más
comúnmente utilizadas. Por tanto, aprender a utilizar GeneXus adecuadamente va más allá de
conocer un nuevo lenguaje: lo más importante es aprender su metodología.

2
Modelado de la realidad a
partir de las visiones de
los usuarios

Satisface
MODELO DE
LA REALIDAD
Ingeniería Inversa

BASE VISIONES
DE DE
PROGRAMAS
DATOS USUARIOS

El primer problema al que nos enfrentamos en el desarrollo de aplicaciones es la obtención del


conocimiento de la realidad.

Nadie dentro de la empresa conoce los requerimientos y el alcance de la aplicación a desarrollar


como un todo. Entonces, ¿cómo logramos obtener el conocimiento de la realidad de una forma lo
suficientemente objetiva y detallada al mismo tiempo, que nos permita construir un modelo
corporativo?

Este conocimiento se encuentra en cada una de las visiones de los usuarios. Cada usuario conoce
bien los objetos con los que trabaja cotidianamente, la información que se maneja en ellos, las
reglas que deben seguirse, los cálculos que deben realizarse.

Por lo tanto, el punto de partida de la metodología GeneXus es: describir las visiones de los
usuarios para modelar el sistema; y a partir del modelo de la realidad definido, GeneXus
construye el soporte computacional -base de datos y programas- en forma totalmente
automática.

3
Comparación
de
Metodologías

Para fijar ideas, comparemos las metodologías tradicionales más utilizadas actualmente, con la
metodología utilizada por GeneXus, conocida como metodología incremental.

Las metodologías tradicionales difieren entre sí en varios aspectos, pero tienen una característica en
común: separan el análisis de los datos del de los procesos.

A continuación veremos un esquema general que podría aplicarse a cualquiera de las metodologías
tradicionales actuales y estudiaremos sus problemas.

4
Metodología Tradicional

5
ANÁLISIS
DE
DATOS
REALIDAD

MODELO
DE
DATOS

BASE
DE
DATOS

GENERACIÓN/
INTERPRETACIÓN
ANÁLISIS
FUNCIONAL

ESPECIFICACIÓN PROGRAMAS
FUNCIONAL
PROGRAMACIÓN

La primera tarea que se realiza generalmente es el Análisis de Datos, donde se estudia la


realidad en forma abstracta, identificando los objetos existentes y sus relaciones y se obtiene
como resultado un Modelo de Datos con las entidades y relaciones encontradas (Modelo
ER). Es fácil ver que existe una correspondencia muy simple entre el modelo de datos y la
base de datos necesaria para soportarlo. La siguiente tarea entonces, es diseñar esa base de
datos, partiendo del modelo de datos.

Construir un sistema integrado, requiere de un modelo de datos corporativo, y dependiendo


del tamaño de la empresa, ésta puede resultar una tarea de enorme complejidad.

Cuando esto ocurre, la complejidad suele atacarse dividiendo el sistema en módulos (“divide
and conquer”), cada uno de los cuales soluciona los problemas operativos de un área
específica de la empresa. De esta forma la tarea de modelado se simplifica notablemente,
pero como contrapartida los módulos operan sin una real integración, lo que provoca que
exista información redundante, con todos los problemas que ello acarrea.

En caso de que luego se intente realizar la integración de esos módulos, habrá que realizar
modificaciones sobre los modelos de datos, lo que a pesar de su complejidad es una tarea
realizable. Sin embargo las modificaciones que deberán efectuarse sobre los programas
asociados tienen un costo tan elevado que suelen tornar inviable la integración.

Con la división del sistema en módulos la empresa tendrá resueltos sus problemas operativos,
pero aparecerán indefectiblemente problemas de carencia de información que permita
orientar la gestión y la toma de decisiones de alto nivel. En la órbita gerencial la información
que se necesita es principalmente de naturaleza corporativa, por lo que la división del sistema
en módulos no satisface las necesidades actuales de obtención de información.

GeneXus soluciona este problema, brindando herramientas y una metodología que hacen
posible y sencilla la creación y mantenimiento de sistemas corporativos.

6
Una vez obtenido el modelo de datos, el siguiente paso de una metodología tradicional es diseñar la base de datos.
Existe una transformación trivial entre un buen modelo de datos y el diseño de la base de datos necesaria para
soportarlo.

Sin embargo, el modelo de datos no es suficiente para desarrollar los programas de aplicación, ya que el mismo
describe los datos, pero no los comportamientos. Se recurre, entonces, a una tarea llamada Análisis Funcional,
donde se estudia la empresa desde el punto de vista de las funciones existentes, y el resultado de dicha tarea es una
Especificación Funcional.

Sería deseable que la especificación funcional fuera independiente de la especificación de datos, lo que no ocurre en
las metodologías estudiadas. Las modificaciones en el diseño de la base de datos implican la necesidad de revisar las
especificaciones funcionales, siendo esta la causa fundamental de los grandes costos de mantenimiento que tienen
estos sistemas.

Una vez que se cuenta con la base de datos y la especificación funcional, ya están dadas las condiciones para crear los
programas de la aplicación.

Para ello se cuenta hoy en día con varias alternativas:

• Programación en lenguajes de tercera y cuarta generación (L3G, L4G)


• Generación/Interpretación

Desde un punto de vista conceptual no hay diferencia entre la programación en lenguajes de 3ra. y de 4ta. generación.
En ambos casos el analista debe estudiar el problema, transformarlo en un algoritmo y codificarlo en el lenguaje de
programación elegido.
La principal diferencia radica en que en los lenguajes de 4ta. generación se escribe mucho menos código
(aproximadamente 10 veces menos) y, como consecuencia, la productividad es mucho mayor y el número de errores
cometidos es mucho menor.

El problema de que las descripciones de los procesos dependen de la base de datos afecta a ambos por igual, por lo
que se concluye que los lenguajes de 4ta. generación permiten aumentos de productividad muy grandes sobre los de
3ra. en la tarea de desarrollo, pero no ayudan en la etapa de mantenimiento.

Otra alternativa es la utilización de un “generador”: una herramienta que puede interpretar una especificación
funcional y producir automáticamente los programas. Aquí existe una diferencia conceptual importante con el caso
anterior. En este caso el analista describe el problema de una forma declarativa (no existe algoritmo ni codificación
procedural alguna). Por ello la especificación funcional debe ser formal y rigurosa. Existen actualmente en el mercado
varias herramientas de esta clase.

Existe asimismo otra categoría de herramientas conceptualmente idéntica: la de aquellas que simplemente
“interpretan” la especificación. La programación en ambos casos es totalmente automática, por lo que el aumento de
productividad sobre las herramientas de 3ra. y 4ta. generación es muy grande.

El problema visto -las descripciones de los procesos dependen de la base de datos- afecta también a las aplicaciones
generadas mediante estas herramientas. Como consecuencia, los Generadores e Intérpretes (como los lenguajes de
4ta. generación) no ayudan lo suficiente en la tarea de mantenimiento.

En definitiva, desde el punto de vista del mantenimiento ninguna de las herramientas ayuda mucho, dado que en
todas ellas se utilizan descripciones de procesos que pueden perder su validez cuando existen modificaciones de
archivos y que, por ello, deben ser revisadas y mantenidas manualmente. El costo de mantenimiento, claro está,
difiere enormemente entre las distintas alternativas vistas, ya que en el caso de la utilización de Generadores o
Intérpretes, una vez modificadas las especificaciones funcionales la generación de los programas es automática.

Si el siguiente postulado: “puede obtenerse una base de datos estable” fuera verdadero, los problemas anteriores
serían irrelevantes. Sin embargo, sobran contraejemplos que hablan de lo contrario.

Conclusiones:
No es posible hacer de forma abstracta un modelo de datos detallado de la empresa y con el suficiente nivel de detalle
y objetividad porque nadie la conoce como un todo. Por el contrario, es necesario recurrir a múltiples interlocutores,
cada uno de los cuales aporta su propia subjetividad. Como consecuencia, durante todo el ciclo de vida de la aplicación
se producen cambios en el modelo.

7
Aun si se considerara la situación ideal, en la cuál se conocen exactamente las necesidades y por tanto es posible
definir la base de datos óptima, el modelo, de todas formas, no podrá permanecer estático, ya que debe acompañar
la evolución de la empresa.

Todo esto sería poco importante si la especificación funcional y la base de datos fueran independientes. Sin
embargo, dado que la especificación funcional se refiere a la base de datos, las inevitables modificaciones en esta
última, implican modificaciones (manuales) en la primera.

Las principales consecuencias de lo anterior son los altos costos de mantenimiento: en la mayoría de las empresas
que trabajan de una manera convencional se admite que el 80% de los recursos que teóricamente están destinados
al desarrollo, realmente se utilizan para hacer mantenimiento de las aplicaciones ya implementadas.

Dado que es muy difícil en este contexto determinar y propagar las consecuencias de los cambios de la base de
datos sobre los procesos, es habitual que en vez de efectuarse los cambios necesarios, se opte por introducir
nuevos archivos redundantes con la consiguiente degradación de la calidad de los sistemas y el incremento de los
costos de mantenimiento.

8
Metodología GeneXus

Los fundadores de ARTech han desarrollado una amplia actividad de consultoría internacional en
diseño de grandes bases de datos, trabajando con verdaderos modelos corporativos con cientos de
tablas.

En su trayectoria, se convencieron de que no debía perderse más tiempo buscando algo que no
existe: las bases de datos estables.

Luego de importantes investigaciones, desarrollaron una teoría, organizaron una metodología e


implementaron una herramienta para posibilitar el desarrollo incremental y permitir la convivencia
con las bases de datos reales –inestables- a un costo mínimo.

9
Desarrollo con GeneXus

REALIDAD
DESCRIPCIÓN
DE OBJETOS

Utilizando GeneXus, la tarea básica del analista es la descripción de la realidad. Sólo el ser humano
puede desarrollar esta tarea ya que sólo él puede entender el problema del usuario.

El analista GeneXus trabaja en alto nivel, en vez de realizar tareas de bajo nivel como: diseñar
archivos, normalizar, diseñar programas, programar, buscar y eliminar los errores de los programas.

Para comenzar el desarrollo de una aplicación con GeneXus, el primer paso consiste en crear un
nuevo proyecto o base de conocimiento.

Una vez creada una nueva base de conocimiento (en inglés: knowledge base; abreviado: KB), el
siguiente paso es describir las visiones de los usuarios. Para ello se deben identificar los objetos de
la realidad (prestando atención a los sustantivos que los usuarios mencionan en sus descripciones,
como por ejemplo: clientes, productos, facturas) y pasar a definirlos mediante objetos GeneXus.

Con la definición de estos objetos, GeneXus puede extraer el conocimiento y diseñar la base de
datos y los programas de la aplicación en forma automática.

10
Desarrollo con GeneXus

REALIDAD
DESCRIPCIÓN
DE OBJETOS

BASE BASE DE
DE CONOCIMIENTO
DATOS

PROGRAMAS

A partir de los objetos definidos en la base de conocimiento, GeneXus genera


automáticamente tanto los programas de creación / reorganización de la base de
datos como los programas de la aplicación.

Luego, si un objeto de la realidad cambia, si se identifican nuevas o diferentes características


del mismo, o si se encuentran objetos aún no modelados, el analista GeneXus debe reflejar
dichos cambios en los objetos GeneXus que correspondan, y la herramienta se encargará
automáticamente de realizar las modificaciones necesarias tanto en la base de datos como en
los programas asociados.

La metodología GeneXus es una metodología incremental, pues parte de la base de que la


construcción de un sistema se realiza mediante aproximaciones sucesivas.

En cada momento el analista GeneXus define el conocimiento que tiene y luego cuando pasa
a tener más conocimiento (o simplemente diferente) lo refleja en la base de conocimiento y
GeneXus se ocupará de hacer automáticamente todas las adaptaciones en la base de datos
y programas.

Si GeneXus no fuera capaz de realizar automáticamente las modificaciones en la base de


datos y programas conforme se realicen cambios que así lo requieran, el desarrollo
incremental sería inviable.

11
Comparación de Metodologías
ANÁLISIS
DE
DATOS
REALIDAD

DESCRIPCIÓN
DE OBJETOS
MODELO
DE
DATOS

BASE BASE DE
DE CONOCIMIENTO
DATOS
GENERACIÓN/
INTERPRETACIÓN

ANÁLISIS
FUNCIONAL ESPECIFICACIÓN PROGRAMACIÓN PROGRAMAS
FUNCIONAL

Como se ha visto, existen varios caminos para la implementación de aplicaciones:

1. Programación utilizando lenguajes de 3era. generación.

2. Programación utilizando lenguajes de 4ta. generación

3. Generación / interpretación automática de la especificación funcional.

4. Desarrollo incremental.

La productividad en el desarrollo de aplicaciones aumenta de arriba a abajo.

Disminuir en gran forma el mantenimiento de las aplicaciones (datos y programas), sólo se


consigue con el desarrollo incremental.

12
Objetos GeneXus
(más importantes)

Transacciones Reportes Procedimientos Work Panels Web Panels


(Trns) (Rpts) (Procs) (Wkps) (Wbps)

y hay más, que veremos...

Una vez creada una base de conocimiento, el siguiente paso consiste en comenzar a describir
los objetos de la realidad mediante objetos GeneXus.

Los objetos GeneXus más importantes son:

Transacciones
Permiten definir los objetos de la realidad que el usuario manipula (ej: clientes, productos,
proveedores, facturas, etc.). Son los primeros objetos en definirse, ya que a través de las
transacciones, GeneXus infiere el diseño de la base de datos.
Además de tener por objetivo la definición de la realidad y la consecuente creación de la base
de datos normalizada, cada transacción tiene asociada una pantalla para ambiente windows y
otra para ambiente Web, para permitir al usuario dar altas, bajas y modificaciones en forma
interactiva a la base de datos. El analista GeneXus decidirá si trabajar en ambiente windows,
Web, o ambos, y GeneXus generará los programas para ello.

Reportes
Permiten recuperar información de la base de datos, y desplegarla ya sea en la pantalla, en un
archivo o impresa en papel. Son los típicos listados o informes. No permiten actualizar la
información de la base de datos1.

Procedimientos
Tienen las mismas características que los reportes, pero a diferencia de éstos, permiten además
la actualización de la información de la base de datos.

Work Panels
Permiten al usuario realizar interactivamente consultas a la base de datos, a través de una
pantalla. Ejemplo: un work panel permite al usuario ingresar un rango de caracteres, y
muestra a continuación todos los clientes cuyos nombres se encuentran dentro del rango.
Son objetos muy flexibles que se utilizan exclusivamente en ambiente windows y se prestan
para múltiples usos. No permiten la actualización de la base de datos, sino solo su consulta1.

------------------------------------------------------------------------------------------------------
1 No es inherente a este tipo de objetos la modificación de la base de datos, pero como

veremos más adelante, existen unos componentes llamados “business components” que lo
harán posible, rompiendo con su naturaleza de solo consulta.

13
Web Panels
Son similares a los work panels, salvo que son usados a través de browsers en ambiente
Internet/Intranet/Extranet.

Existen algunos tipos más de objetos GeneXus, algunos de los cuales veremos en este curso, como
los Subtype Groups y los Structured Data Types, que si bien son objetos que se definen en la
base de conocimiento (KB) mediante el mismo procedimiento que los ya mencionados, tienen
algunas características que los diferencian de la mayoría de ellos, como el hecho de no generar
programas propios, sino que su conocimiento es reutilizado dentro de los otros objetos.

14
Proceso de desarrollo de una
aplicación con GeneXus
Transacciones Reportes Procedimientos Work Panels Web Panels
(Trns) (Rpts) (Procs) (Wkps) (Wbps)

Base
Basede
deConocimiento
Conocimiento

Base
de
Datos

Los primeros objetos que se definen son las transacciones, ya que es a partir de ellas que
GeneXus extrae el conocimiento necesario para diseñar el modelo de datos normalizado (en
3era. forma normal). Luego se van definiendo los demás objetos que correspondan.

15
Creación de la Base de Datos

Transacciones Reportes Procedimientos Work Panels Web Panels


(Trns) (Rpts) (Procs) (Wkps) (Wbps)

Base
Basede
deConocimiento
Conocimiento

Base Programas
de Creación
Datos BD

GeneXus genera automáticamente los programas necesarios para crear la base de datos y
los ejecuta. De esta manera obtenemos la base de datos creada por GeneXus en forma
automática.

16
Generación de los
Programas de la aplicación

Transacciones Reportes Procedimientos Work Panels Web Panels


(Trns) (Rpts) (Procs) (Wkps) (Wbps)

Base
Basede
deConocimiento
Conocimiento

Base Programas de Aplicación


de
Datos (Trns, Rpts, Procs, Wkps, Wbps, DVs)

Luego, GeneXus genera programas de aplicación para interactuar con la base de datos
previamente creada.

17
Resultado final en la Etapa de Desarrollo
Transacciones Reportes Procedimientos Work Panels Web Panels
(Trns) (Rpts) (Procs) (Wkps) (Wbps)

Base
Basede
deConocimiento
Conocimiento

Base Programas de Aplicación


de (Trns, Rpts, Procs, Wkps, Wbps, DVs)
Datos

Una vez creada la base de datos y generados los programas, contamos con una aplicación pronta
para ejecutar.

18
Las visiones de los usuarios cambian
Nuevas Nuevos Nuevos Nuevos Nuevos
Transacciones Reportes Procedimientos Work Panels Web Panels

Base
Basede
deConocimiento
Conocimiento

Base Nueva
Nueva Programas de Aplicación
de Base
Base (Trns, Rpts, Procs, Wkps, Wbps, DVs)
Datos de
de
Datos
Datos

Durante el ciclo de vida de la aplicación, surgirá repetidamente la necesidad de hacer modificaciones


en la base de conocimiento, ya sea porque las visiones de los usuarios cambian, porque se deben
hacer correcciones, o simplemente agregar nuevo conocimiento.

Las modificaciones que se realicen sobre la base de conocimiento serán analizadas por GeneXus
para evaluar si es necesario efectuar cambios en la base de datos (por ejemplo:
modificación/creación de tablas/índices), o no.

En caso de detectar cambios para efectuar en la base datos, GeneXus detallará los mismos en un
reporte de análisis de impacto (IAR: Impact Analisis Report), que es un reporte que explicita
todos los cambios sobre tablas, índices, datos, etc. que habría que realizar para reflejar la nueva
realidad.

Asimismo, en el reporte de análisis de impacto se informan los eventuales problemas que los
cambios en cuestión podrían ocasionar, como inconsistencias o redundancias.

19
Análisis de Impacto Totalmente Automático
Nuevas Nuevos Nuevos Nuevos Nuevos
Transacciones Reportes Procedimientos Work Panels Web Panels

Análisis
Base
Basede
deConocimiento
Conocimiento
de
impacto

Base Nueva
Nueva Programas de Aplicación
de Base
Base (Trns, Rpts, Procs, Wkps, Wbps, DVs)
Datos de
de
Datos
Datos

Algunas veces la nueva base de datos coincide con la anterior. Otras veces esto no ocurre, y la base
de datos debe sufrir alguna modificación para representar la nueva realidad.

El analista debe estudiar el reporte de análisis de impacto y resolver si desea realizar


efectivamente los cambios en la base de datos, o renunciar a ello dejando la base de datos como
estaba.

20
Generación de Programas de
Reorganización de la Base de Datos
Nuevas Nuevos Nuevos Nuevos Nuevos
Transacciones Reportes Procedimientos Work Panels Web Panels

Programas Base
Basede
deConocimiento
Conocimiento
de
Reorganiz.

Programas de Aplicación
Base Nueva
Nueva
de Base
Base (Trns, Rpts, Procs, Wkps, Wbps, DVs)
Datos de
de
Datos
Datos

Si el analista opta por aplicar los cambios propuestos, decimos que optó por reorganizar la base de
datos. Utilizamos este término para referirnos a la acción de aplicar cambios físicos sobre la base de
datos.

GeneXus generará los programas que implementan las modificaciones sobre las
estructuras físicas de la base de datos, y mediante su ejecución nos brindará la nueva versión
de la base de datos con los cambios efectuados.

21
Análisis automático del impacto
de los cambios sobre los programas
Nuevas Nuevos Nuevos Nuevos Nuevos
Transacciones Reportes Procedimientos Work Panels Web Panels

Análisis
Base de Impacto
Basede
deConocimiento
Conocimiento sobre los
programas

Nuevos Programas de
Nueva
Aplicación
Base
de (Trns, Rpts, Procs, Wkps, Wbps, DVs)
Datos

Ya sea que se requiera reorganizar la base de datos o no, considerando las nuevas definiciones
introducidas y a pedido del analista, GeneXus estudiará el impacto de los cambios sobre los
programas actuales.

El analista podrá seleccionar sobre cuáles objetos quiere realizar el análisis (si sobre todos, los que
hayan sufrido cambios, o algunos en particular) y para cada objeto analizado, GeneXus indicará si
es necesario realizar cambios en su programa de aplicación, o no.

22
Generación automática de nuevos programas
Nuevas Nuevos Nuevos Nuevos Nuevos
Transacciones Reportes Procedimientos Work Panels Web Panels

Base
Basede
deConocimiento
Conocimiento
Generación
de
programas

Nuevos Programas de
Nueva Aplicación
Base
(Trns, Rpts, Procs, Wkps, Wbps, DVs)
de
Datos

Por último, a pedido del analista, GeneXus proseguirá con la generación/regeneración de


los programas de aplicación que sean necesarios, obteniendo así una nueva versión de la
aplicación.

23
Nueva realidad, con los cambios en la aplicación
Nuevas Nuevos Nuevos Nuevos Nuevos
Transacciones Reportes Procedimientos Work Panels Web Panels

Base
Basede
deConocimiento
Conocimiento

Nueva
Base Nuevos Programas de Aplicación
de
Datos

De modo que nuevamente contaremos con una aplicación pronta para ejecutar, con los cambios
aplicados.

24
Metodología Incremental
Consiste en construir una aplicación mediante aproximaciones
sucesivas.

DEFINICION
INICIAL

La construcción automática de la base de datos y programas, permite a GeneXus aplicar


esta metodología de desarrollo, conocida como metodología incremental.

Como ya hemos explicado, este proceso se realiza mediante aproximaciones sucesivas.

25
Modelos

Î Dentro de una base de conocimiento coexisten varios modelos

Î En particular, existe un modelo que se crea automáticamente al


crear una nueva base de conocimiento: el modelo de Diseño

BASE DE CONOCIMIENTO

modelo
de
Diseño

El modelo de Diseño corresponde a la representación lógica del sistema, esto es, permite
describir la aplicación sin implementarla.

Esto significa que no existirá una base de datos física asociada a este modelo, así como
tampoco programas de aplicación ejecutables. Estos últimos existirán solo a un nivel
conceptual o lógico.

Son los objetos GeneXus que el analista haya definido dentro de este modelo los que
principalmente describen al sistema. En particular, de las transacciones especificadas1
GeneXus obtiene el diseño lógico de la base de datos, y ellas, en conjunto con el resto de
los objetos definidos, constituyen la descripción lógica de los programas.

Los programas serán el resultado de la codificación de los objetos GeneXus en el lenguaje


de programación elegido por el analista, sobre una base de datos física.

Pero esta implementación (base de datos y programas), que es enteramente realizada por
GeneXus, ¿dónde se realiza?

Aquí es donde entran en juego los otros modelos que pueden definirse dentro de la base de
conocimiento. Cada uno de estos otros modelos, contendrán una implementación del
sistema.

--------------------------------------------------------------------------------------------------
1 En conjunción con los grupos de subtipos (objetos Subtype Group) y los índices de

usuario.

26
Modelos

Î Existen otros modelos que son creados en forma explícita por el


analista

Î ¿Por qué tener varios de estos modelos, en lugar de uno solo?


Î Entre otras razones: para tener cualquier número de implementaciones del
mismo sistema, asociadas a diferentes plataformas o ambientes (por
ejemplo: iSeries, PC, Client/Server, Web, etc.).

BASE DE CONOCIMIENTO

modelo otro
otro otro
de modelo
modelo modelo
Diseño

A diferencia del modelo de Diseño que es creado automáticamente, estos otros modelos
son creados en forma explícita por el analista. Al hacerlo, éste debe ingresar los datos de la
plataforma o ambiente de implementación correspondiente (lenguaje de programación,
DBMS, etc.) para que GeneXus pueda implementar el sistema para ese modelo.

Los objetos GeneXus definidos en el modelo de Diseño se copian al nuevo modelo y éstos
son los que GeneXus codifica para dicho modelo de acuerdo a su plataforma.

27
Tipos de Modelo
Î Cada modelo tiene un tipo:

Î Design (Diseño):
ÎUn sólo modelo en la misma base de conocimiento.
ÎNo tiene base de datos ni programas de aplicación
asociados.

Î Prototype/Production (Prototipo/Producción):
ÎPuede haber varios modelos en la misma base de
conocimiento.
ÎCada uno de estos modelos tiene una base de datos
asociada y programas de aplicación que se generan para la
plataforma o ambiente elegido.

Por cada base de conocimiento, habrá un y solo un modelo de Diseño, cuya característica
fundamental es que representa al sistema conceptualmente.

Los otros dos tipos se parecen mucho entre sí, dado que todo modelo de Prototipo o Producción
tiene asociada una plataforma o ambiente que le permite implementar físicamente el sistema,
siendo ésta la característica fundamental que diferencia a estos modelos del de Diseño. La principal
diferencia entre ellos es conceptual (no funcional) y radica en el uso que se hace de los mismos:

- Un modelo de Prototipo se utiliza en la etapa de desarrollo, justamente para prototipar la


aplicación –de allí su nombre- probando lo modelado, haciendo modificaciones, volviendo a probar.

-Un modelo de Producción, por el contrario, se utiliza en la etapa de puesta en producción, cuando
el prototipo fue aprobado y la aplicación o los cambios efectuados ya pueden ser llevados al cliente.

Cuando una aplicación implementada en GeneXus ha sido puesta en producción, necesariamente


deberá haber al menos 3 modelos definidos en la base de conocimiento:

- el de Diseño, pues se crea automáticamente al crear la base de conocimiento,

- uno de Prototipo, utilizado para ir desarrollando y probando la aplicación,

- uno de Producción, pues es necesario tener una imagen fiel de la aplicación que se lleva al
cliente, en la plataforma de producción, como veremos oportunamente.

28
Ciclos de desarrollo

Diseño Prototipo Producción

En el desarrollo incremental de una aplicación, pasaremos repetidamente del modelo de


Diseño al modelo de Prototipo con el que estemos probando la aplicación, y de éste
nuevamente al modelo de Diseño. Con menor frecuencia se pasará al modelo de Producción.

Ciclo de prototipación
El bucle Diseño-Prototipo se recorre repetidamente, construyendo y probando sucesivos
prototipos.

Ciclo de producción
El bucle Diseño-Producción se recorre menos frecuentemente, ya que este ciclo corresponde
a la puesta en producción del sistema y esto se realiza solamente cuando el prototipo ha
sido aprobado o luego de haber instrumentado y probado algún cambio.

29
Ventajas de la Prototipación

• Permite ver resultados en forma temprana


• Permite el seguimiento de los requerimientos del usuario
• Detección de errores en forma temprana
• Logra el compromiso de los usuarios con el desarrollo
• Sistemas de mejor calidad

Toda comunicación es susceptible de errores:

• El usuario olvida ciertos detalles


• El analista no toma nota de algunos elementos
• El usuario se equivoca en algunas apreciaciones
• El analista malinterpreta algunas explicaciones del usuario

Como la implementación de sistemas es habitualmente una tarea que insume bastante tiempo, y muchos
de estos problemas sólo son detectados en las pruebas finales del sistema, el costo en tiempo y dinero de
solucionarlos se torna muy grande. Sabido es que la realidad no permanece estática, por lo que no es
razonable suponer que se pueden mantener congeladas las especificaciones mientras se implementa el
sistema. Sin embargo, debido al tiempo que suele insumir la implementación, muchas veces esto se hace
y se acaba implementando una solución relativamente insatisfactoria.

El impacto de estos problemas disminuiría mucho si se consiguiera probar cada especificación


inmediatamente y saber cuál es la repercusión de cada cambio sobre el resto del sistema. Una primera
aproximación a esto, ofrecida por diversos sistemas, es la posibilidad de mostrar al usuario formatos de
pantallas, informes, etc., animados por menús. Esto permite ayudar al usuario a tener una idea de qué
sistema se le construirá, pero al final siempre se presentan sorpresas.

Una situación bastante diferente sería la de poner a disposición del usuario para su ejecución, una
aplicación funcionalmente equivalente a la deseada hasta en los mínimos detalles. Y esto es lo que ofrece
GeneXus! Un prototipo GeneXus es una aplicación pronta funcionalmente equivalente a la aplicación de
producción.

Así es que la aplicación puede ser totalmente probada antes de ponerse en producción; y durante las
pruebas, el usuario final puede probar de una forma natural no solamente formatos de pantallas,
informes, etc., sino también fórmulas, reglas del negocio, estructuras de datos, etc., y trabajar con datos
reales.

Esto solo es posible gracias a la construcción automática que realiza GeneXus del soporte computacional
(base de datos y programas).

30
OBJETO TRANSACCIÓN

El análisis de toda aplicación GeneXus comienza con el diseño de las transacciones.

Las transacciones permiten definir los objetos de la realidad.

Para identificar cuáles transacciones deben crearse, se recomienda prestar atención a los
sustantivos que el usuario menciona cuando describe la realidad.

Además de tener por objetivo la definición de la realidad y la consecuente creación de la


base de datos normalizada, las transacciones, al igual que la mayoría de los objetos
GeneXus que estudiaremos, provocan la generación de programas. En particular los
programas correspondientes a las transacciones tienen por objeto permitir dar altas, bajas y
modificaciones en forma interactiva en las tablas que tengan implicadas, controlando estos
programas la integridad referencial de los datos (ya sea en ambiente Win y/o Web según lo
indique el analista GeneXus).

31
Elementos

Para una transacción se definen:

• Estructura
• Form GUI-Windows
• Form Web
• Reglas
• Eventos
• Subrutinas
• Propiedades
• Documentación
• Ayuda

Algunos elementos de las transacciones, que iremos viendo son:


Estructura: Permite definir los atributos (campos) que componen la transacción y la relación entre
ellos. A partir de la estructura de las transacciones, GeneXus inferirá el diseño de la base de datos:
tablas, claves, índices, etc.
Form GUI-Windows: Cada transacción contiene un Form (pantalla) GUI-Windows (Graphical User
Interface Windows) mediante el cual se realizarán las altas, bajas y modificaciones en ambiente
Windows.
Form Web: Cada transacción contiene un Form (pantalla) Web mediante el cual se realizarán las
altas, bajas y modificaciones en ambiente Web.
Reglas: Las reglas permiten definir el comportamiento particular de las transacciones. Por ejemplo,
permiten definir valores por defecto para los atributos, definir chequeos sobre los datos, etc.
Eventos: Las transacciones soportan la programación orientada a eventos. Este tipo de
programación permite definir código ocioso, que se activa en respuesta a ciertas acciones
provocadas por el usuario o por el sistema.
Subrutinas: Permiten definir rutinas locales a la transacción.
Propiedades: Permiten definir ciertos detalles referentes al comportamiento de la transacción.
Documentación: Permite la inclusión de texto técnico, para ser utilizado como documentación del
sistema.
Ayuda: Permite la inclusión de texto de ayuda, para ser consultado por los usuarios en tiempo de
ejecución de la transacción.

Algunos de estos elementos también están asociados a otros tipos de objetos GeneXus.

32
Estructura

Ejemplo: Se necesita registrar información de proveedores.

Se define transacción “Supplier”, con estructura:

SupplierId* Identificador de proveedor


SupplierName Nombre de proveedor
SupplierAddress Dirección de proveedor
SupplierPhone Teléfono de proveedor

La estructura de una transacción permite definir qué atributos la integran y cómo están
relacionados.

A modo de ejemplo, si en una aplicación se necesita registrar información de proveedores, claramente


habrá que definir una transacción, a la que podemos dar el nombre “Supplier”, y su estructura podría ser
la siguiente:

SupplierId*
SupplierName
SupplierAddress
SupplierPhone

Esta lista de nombres (uno de los cuales está sucedido del símbolo asterisco) corresponde a los atributos
que interesa mantener acerca de los proveedores.

Entonces, creamos una transacción de nombre “Supplier” cuya estructura se compone de los atributos
SupplierId, SupplierName, SupplierAddress y SupplierPhone.

Esto significa que cada proveedor se identificará por un código SupplierId (lo que queda determinado por
el asterisco a continuación del atributo1), tendrá un nombre SupplierName, una dirección
SupplierAddress y un teléfono SupplierPhone.

Para cada atributo definido en la estructura, deberemos indicar cosas tales como su tipo de datos,
descripción y algunos detalles más que veremos.

--------------------------------------------------------------------------------------------------------------
1 El asterisco corresponde a una notación teórica que utilizamos para indicar que el atributo es
identificador. Como veremos, nuestro asterisco en GeneXus aparece representado por un ícono de llave
y el usuario podrá configurarlo mediante un menú contextual que le ofrecerá esta posibilidad.

33
Estructura

Vista de la estructura en GeneXus:

Atributos Clave
En la página anterior hemos explicado que el asterisco a continuación del atributo SupplierId indica que se trata
del identificador de la transacción. Toda transacción debe tener un identificador, esto es, un atributo o conjunto
de atributos que definan la unicidad.

En el ejemplo no podrán existir dos proveedores con el mismo valor de SupplierId. En definitiva se trata del
concepto de clave primaria, en tanto, para hacer la elección de los atributos que la componen, se deben tener
en cuenta los requisitos del objeto de la realidad.

En los casos en los cuales no se pueda determinar un identificador, se debe optar por crear un atributo artificial
(no existente en la realidad), y que su valor se asigne internamente, por ejemplo, en forma correlativa.

Como se puede observar en el editor de transacciones de GeneXus, un ícono de llave representa el asterisco que
nosotros utilizamos como notación teórica.

Atributo “descriptor”
El ícono con una lupa representa al atributo que mejor describe o representa a la transacción. En otras palabras
sería el atributo que tiene mayor carga semántica en la transacción.

Por defecto el primer atributo en la estructura de la transacción que sea de tipo de datos character, se definirá
como “atributo descriptor”. Es posible definir a otro atributo como descriptor utilizando el menú popup
correspondiente, así como no definir ninguno.

34
Estructura

Ejemplo: Se necesita registrar información referente a


facturas de venta.

Se define transacción “Invoice”, con estructura:

InvoiceId* Identificador de factura


CustomerId Identificador de cliente
CustomerName Nombre de cliente
InvoiceDate Fecha de factura
InvoiceTotal Importe total de factura
( ProductId* Identificador de producto
ProductDescription Descripción de producto
ProductPrice Precio de producto
InvoiceLineQuantity Cantidad de producto llevada en la línea
InvoiceLineAmount ) Importe de línea de factura

Niveles de una transacción


La transacción “Invoice” consta de dos niveles: el primer nivel queda implícito por lo que no necesita un
juego de paréntesis; el segundo nivel corresponde al conjunto de atributos que se encuentra entre
paréntesis1.

El hecho de definir un segundo nivel significa que existen varias instancias del mismo, para cada
instancia del nivel anterior. En el ejemplo, un cabezal de factura tiene varios productos.

Cada nivel de una transacción define un grupo de atributos que deben operar en conjunto, es decir, se
ingresan, se eliminan o se modifican conjuntamente en la base de datos.

Llamaremos transacción plana a una transacción de un solo nivel. Así, la transacción “Supplier” es una
transacción plana.

En cambio, la transacción “Invoice” tiene dos niveles. Es común hablar de “cabezal” para referirnos al
primer nivel y de “líneas” para referirnos al segundo.

Para cada nivel de la transacción, se debe indicar cuáles de sus atributos actúan como identificador. El
identificador de cada nivel puede estar compuesto de un solo atributo, como es el caso de las
transacciones que hemos visto hasta ahora, o puede estar conformado por varios atributos.

En la transacción “Invoice” el atributo InvoiceId es el identificador del primer nivel, y el atributo


ProductId es el identificador del segundo nivel. Esto último significa que para un número de factura
dado, InvoiceId, no puede repetirse el valor del atributo ProductId en distintas líneas.

Una transacción puede contener varios niveles paralelos, así como anidados.

--------------------------------------------------------------------------------------------------------------
1 Al igual que el asterisco es una notación teórica que representa que el atributo que lo antecede es

identificador en la transacción, el juego de paréntesis también es utilizado como notación teórica, para
representar que los atributos contenidos forman parte de un nivel anidado, y que tiene una
representación visual en GeneXus distinta, pero que indica lo mismo.

35
Estructura

Vista de la estructura en GeneXus

En GeneXus queda visualmente claro el nivel correspondiente a las líneas de la factura.


A cada nivel de una transacción se le debe asignar un nombre, tipo1 y descripción (salvo al primer nivel,
que recibe como nombre el de la transacción).

Niveles paralelos y anidados


Una transacción puede tener varios niveles de anidación, así como niveles paralelos.
Por ejemplo, en el hipotético caso de que una factura pueda abonarse en varios pagos, podríamos definir
dos tipos de estructura, dependiendo de lo que se quiera representar:

InvoiceId* InvoiceId*
CustomerId CustomerId
CustomerName CustomerName
InvoiceDate InvoiceDate
InvoiceTotal InvoiceTotal
(ProductId* (ProductId*
ProductDescription ProductDescription
ProductPrice ProductPrice
InvoiceLineQuantity InvoiceLineQuantity
InvoiceLineAmount) InvoiceLineAmount
(InvoicePaymentDate* Fecha de pago (InvoicePaymentDate*
InvoicePaymentAmount) Importe pagado InvoicePaymentAmount))

Con la estructura de la izquierda se define que una factura tiene muchos productos y muchos pagos, pero
no hay una relación directa entre los productos y los pagos (a no ser el hecho de pertenecer a la misma
factura). En la estructura de la derecha se registran los pagos por producto llevado.
Es sencillo comprender que el segundo y tercer nivel de la transacción de la izquierda, son paralelos.
Ambos se encuentran anidados al primer nivel, pero entre ellos, son paralelos. En la estructura de la
derecha, son todos niveles anidados.

--------------------------------------------------------------------------------------------------------------
1 Como veremos luego, el tipo que se define para un nivel de una transacción, será utilizado para trabajar
con business components, concepto relacionado a las transacciones.

36
Definición del modelo de datos a partir de las
estructuras de las transacciones
Tabla
Transacción Transacción INVOICE
“Supplier” “Invoice”
InvoiceId*
SupplierId* InvoiceId* CustomerId
SupplierName CustomerId CustomerName
SupplierAddress CustomerName InvoiceDate
SupplierPhone InvoiceDate InvoiceTotal
InvoiceTotal
( ProductId* Tabla
ProductDescription INVOICELINE
Tabla ProductPrice
SUPPLIER InvoiceLineQuantity InvoiceId*
SupplierId* InvoiceLineAmount ) ProductId*
SupplierName ProductDescription
SupplierAddress ProductPrice
SupplierPhone InvoiceLineQuantity
InvoiceLineAmount

GeneXus utiliza la estructura de las transacciones para capturar el conocimiento necesario para definir
automáticamente cuál es el modelo de datos que debe crear.

Para poder realizar la normalización de la base de datos llevándola a 3era. forma normal, GeneXus
debe extraer las dependencias funcionales existentes entre los atributos definidos en la base de
conocimiento.

En la base de conocimiento de nuestro ejemplo, hemos definido a la transacción “Proveedores” y de su


estructura GeneXus extrae las siguientes dependencias funcionales:

SupplierId Æ {SupplierName, SupplierAddress, SupplierPhone}

Dadas estas dependencias funcionales, GeneXus determina que debe crear una tabla que tendrá por
defecto el mismo nombre que la transacción (SUPPLIER)1, y que estará conformada ni más ni menos
que por los cuatro atributos anteriores, siendo SupplierId la clave primaria de la misma:

SUPPLIER SupplierId SupplierName SupplierAddress SupplierPhone

Diremos que la transacción “Supplier” tiene asociada la tabla SUPPLIER en el entendido de que cuando
se ingresen, modifiquen o eliminen datos por medio de la transacción, éstos se estarán almacenando,
modificando o eliminando físicamente en la tabla asociada.

------------------------------------------------------------------------------------------------------------
1 En la documentación, para distinguir el nombre de una tabla del nombre de una transacción
escribiremos el nombre de la tabla todo en mayúscula.

37
A partir de la estructura de la transacción “Invoice”, GeneXus determina que debe crear dos tablas:

Tabla INVOICE, correspondiente al primer nivel de la transacción:

INVOICE InvoiceId CustomerId CustomerName InvoiceDate InvoiceTotal


Clave primaria: InvoiceId

Tabla INVOICELINE correspondiente al segundo nivel de la transacción:

INVOICELINE InvoiceId ProductId ProductDescription ProductPrice

InvoiceLineQuantity InvoiceLineAmount
Clave primaria: {InvoiceId, ProductId}
Clave foránea: InvoiceId

ya que las dependencias funcionales son:

InvoiceId Æ {CustomerId, CustomerName, InvoiceDate, InvoiceTotal}


{InvoiceId, ProductId} Æ {ProductDescription, ProductPrice, InvoiceLineQuantity,
InvoiceLineAmount}

Observemos que la clave primaria de la tabla INOVICELINE es la concatenación del identificador del
primer nivel, InvoiceId, con el identificador del segundo nivel, ProductId. El caso es general: la clave
primaria de la tabla correspondiente a un nivel n de una transacción se obtiene de concatenar los
identificadores de los n-1 niveles anteriores anidados, con el identificador de ese nivel.

GeneXus asigna un nombre predeterminado a las tablas que crea. A la tabla asociada al primer nivel de
una transacción le asigna el mismo nombre que el de la transacción; y a las tablas de niveles
subordinados les asigna la concatenación de los nombres de los niveles. Por esto es que la tabla
asociada al segundo nivel de la transacción “Invoice” recibe el nombre INVOICELINE, dado que el
nombre del primer nivel es el de la transacción, INVOICE, y el del segundo nivel es LINE. Los nombres
de las tablas pueden ser modificados por el analista GeneXus cuando así lo desee.

38
Al definir las nuevas transacciones:

Transacción “Customer”
CustomerId*
CustomerName
CustomerAddress
CustomerGender Sexo del cliente

Transacción “Product”

ProductId*
ProductDescription
ProductPrice
ProductStock

Luego de haber modelado la transacción “Invoice”, nos damos cuenta que hay información de clientes y de
productos que nos interesa mantener independientemente de las facturas. Es decir, los clientes y los
productos son dos objetos de la realidad independientes de las facturas, por lo tanto creamos las dos
nuevas transacciones “Customer” y “Product” detalladas arriba.

Dependencias funcionales
Con estas nuevas transacciones definidas, aparecen nuevas dependencias funcionales:
CustomerId Æ {CustomerName, CustomerAddress, CustomerGender}
ProductId Æ {ProductDescription, ProductPrice, ProductStock}

que conducen directamente a la creación de dos nuevas tablas:

CUSTOMER CustomerId CustomerName CustomerAddress CustomerGender

Clave primaria: CustomerId

y:

PRODUCT ProductId ProductDescription ProductPrice ProductStock

Clave primaria: ProductId

39
Normalización: cambios en las tablas
Tabla
INVOICE
Tabla CUSTOMER
InvoiceId* CustomerId*
CustomerId CustomerName Tabla
CustomerName CustomerAddress
SUPPLIER
InvoiceDate CustomerGender
SupplierId*
InvoiceTotal
SupplierName
Tabla SupplierAddress
INVOICELINE Tabla PRODUCT SupplierPhone

InvoiceId* ProductId*
ProductId* ProductDescription
ProductDescription ProductPrice
ProductPrice ProductStock
InvoiceLineQuantity
InvoiceLineAmount

El conjunto total de dependencias funcionales existentes requiere que deban realizarse algunas
modificaciones en las tablas INVOICE e INVOICELINE diseñadas previamente para que el diseño de la base
de datos permanezca en 3era. forma normal1.

Si representamos sobre las tablas CUSTOMER e INVOICE las dependencias funcionales encontradas para sus
atributos:

podemos ver claramente que INVOICE viola la 3era. forma normal (existe una dependencia funcional
transitiva):

InvoiceId Æ CustomerId
CustomerId Æ CustomerName
InvoiceId Æ CustomerName
por lo tanto GeneXus normaliza, quitando el atributo CustomerName de la tabla INVOICE:

Ahora CustomerName solo estará en la tabla CUSTOMER.

-------------------------------------------------------------------------------------------------------------------
1Por más información sobre las formas normales (3era. forma normal, etc.) le recomendamos la lectura del
documento “Fundamentos de bases de datos relacionales”, el cual está incluido en el capítulo de “Anexos”
del curso GeneXus no presencial. De lo contrario, puede pedírselo al docente.

40
Ahora veamos las dependencias funcionales encontradas en las tablas PRODUCT e INVOICELINE:

podemos percibir claramente que la tabla INVOICELINE está violando la 3era. forma normal (existen atributos que
están dependiendo en forma parcial de la clave primaria):

ProductId Æ ProductDescription ProductId Æ ProductPrice


{InvoiceId, ProductId} Æ ProductDescription {InvoiceId, ProductId} Æ ProductPrice

Por lo tanto GeneXus normaliza, quitando los atributos ProductDescription y ProductPrice de la tabla INOVICELINE:

ProductDescription y ProductPrice solo quedarán en la tabla PRODUCT.

41
GeneXus establece las relaciones
por los nombres de atributos

CONCEPTOS IGUALES DEBEN TENER EL MISMO NOMBRE

Transacción “Invoice” Transacción “Customer”


InvoiceId* CustomerId*
CustomerId SI CustomerName
CustomerName
Transacción “Invoice” Transacción “Customer”
InvoiceId* NO CustomerId*
InvoiceCustomerId

CONCEPTOS DIFERENTES NO DEBEN TENER EL MISMO NOMBRE

Transacción “Invoice” Transacción “VendorInvoice”


InvoiceId* VendorInvoiceId*
NO
Date Date
CustomerId SupplierId
CustomerName SupplierName

Conceptos iguales deben tener el mismo nombre y conceptos diferentes deben ser nombrados
diferentes.

GeneXus establece las relaciones a través de los nombres de los atributos, de modo que cuando
encuentra atributos de igual nombre en distintas transacciones, entiende que se trata del mismo
concepto, y mediante dicho conocimiento es que puede normalizar.

En el ejemplo que venimos viendo, cuando GeneXus encuentra el atributo de nombre CustomerId
tanto en la transacción “Customer” como en la transacción “Invoice”, analiza que: el atributo se llama
igual en ambas transacciones, así que se refiere al mismo concepto. En la transacción “Customer”,
CustomerId está marcado como identificador, lo que significa que será clave primaria en la tabla
física CUSTOMER; entonces en la tabla física INVOICE será clave foránea.

El atributo CustomerName, por su parte, también se encuentra tanto en la transacción “Customer”


como en la transacción “Invoice”, pero a diferencia de CustomerId, no está marcado como
identificador en ninguna transacción del modelo; por tanto GeneXus entiende que se trata de un
atributo secundario. Las dependencias funcionales indican que a CustomerName lo determina
CustomerId:

InvoiceId Æ CustomerId
CustomerId Æ CustomerName

así que GeneXus incluirá CustomerName en la tabla física CUSTOMER (y no en la tabla física
INVOICE).

Atributos primarios y secundarios


Un atributo se califica como primario cuando el mismo es identificador en alguna transacción del
modelo. En el ejemplo que venimos viendo, CustomerId es un atributo primario ya que es
identificador en la transacción “Customer”.

CustomerName, en cambio, es un atributo secundario ya que no es identificador en ninguna


transacción del modelo.

42
Atributos almacenados e inferidos
Al definir las transacciones “Customer” y “Product”, hemos incluido en ellas ciertos atributos que no
hemos eliminado de la transacción “Invoice”.

Los atributos CustomerId y ProductId, se incluyeron en las transacciones “Customer” y “Product”


respectivamente, y al ser denotados como identificadores de las mismas, pasaron a ser atributos
primarios.

El atributo CustomerName, por su parte, se agregó en la transacción “Customer”; y los atributos


ProductDescription y ProductPrice se incluyeron en la transacción “Product”. Estos son atributos
secundarios.

Todos estos atributos han quedado en más de una transacción: se han dejado en la transacción
“Invoice” tal como se habían definido en un principio, y se han incluido en las transacciones
“Customer” y “Product” respectivamente, porque nos hemos percatado de la necesidad de crear estos
objetos.

A continuación presentamos las 3 estructuras de las transacciones en cuestión, para poder


visualizarlas juntas:

Probablemente usted no comprenda la razón por la cual los atributos secundarios CustomerName,
ProductDescription y ProductPrice se han dejado en la estructura de la transacción “Invoice”.

La explicación es la siguiente: las estructuras de las transacciones no son equivalentes a


estructuras de tablas físicas. En las estructuras de las transacciones se pueden incluir ciertos
atributos que no estarán en la o las tablas físicas asociadas, ya que a la hora de reorganizar la base
de datos GeneXus analizará el conjunto total de dependencias funcionales existentes en la base de
conocimiento, y creará -o modificará, según el caso- las tablas físicas, dejándolas en 3ª forma
normal.

Ahora, ¿con qué finalidad hemos dejado los atributos secundarios CustomerName, ProductDescription
y ProductPrice en la estructura de la transacción “Invoice”? Los hemos dejado para poder incluirlos en
alguno de los forms (GUI-Windows y/o Web) asociados a la transacción “Invoice” y así poder
visualizar los valores de dichos atributos en tiempo de ejecución.

Dichos atributos, como hemos explicado, no quedarán almacenados ni en la tabla INVOICE, ni en la


tabla INVOICELINE; sin embargo, en tiempo de ejecución cuando el usuario ingrese a través de
alguno de los forms (GUI-Windows y/o Web) un valor de CustomerId (atributo que sí se almacenará
en la tabla INVOICE siendo clave foránea), a continuación se mostrará el CustomerName
correspondiente. Decimos que el atributo CustomerName es un atributo inferido en la transacción
“Invoice”, ya que su valor no se encuentra almacenado en ninguna de las tablas asociadas a la
transacción, sino que se infiere –es decir, se obtiene- de la tabla CUSTOMER, dado el valor del
atributo CustomerId.

Análogamente, los atributos ProductDescription y ProductPrice también son inferidos en la


transacción “Invoice”, ya que no se encuentran almacenados en las tablas asociadas a la misma, sino
que sus valores se infieren de la tabla PRODUCT, para ser mostrados en pantalla.

43
Es conveniente usar padrones
para los nombres de atributos

• Facilitan la tarea de nombrado

• Facilitan la tarea de integración de bases de


conocimiento

• Facilitan la lectura del código generado

44
Nombrado de atributos:
Nomenclatura GIK

Componente de Entidad + Categorí


Categoría
[+ Calificador + Complemento]

…y en inglés:

ARTech ha definido un estándar para la nomenclatura de atributos: el GeneXus Incremental Knowledge


Base (GIK) que es utilizado por la comunidad de usuarios GeneXus.

En esta nomenclatura, el nombre de un atributo se forma con 4 componentes (algunos opcionales,


señalados entre paréntesis rectos):

Componente de Entidad + Categoría [+ Calificador + Complemento] 1

A continuación describimos en qué consiste cada componente:

Componente de Entidad (u Objeto):Una Entidad es un actor (ej: Customer), objeto o evento (ej:
Vendor Invoice, factura de venta) que interviene en una aplicación dada, representado por una
transacción Genexus2. Un Componente de Entidad, representa a cualquier nivel subordinado o paralelo
que defina la entidad.

Ejemplo: la factura tiene los siguientes componentes, Invoice (cabezal), InvoiceLine (líneas de la
solicitud).
Se sugiere un largo de un entorno de 10 caracteres para representar el componente de la Entidad.

Categoría: Es la categoría semántica del atributo, es decir, define el rol que el mismo asume dentro del
objeto y dentro del entorno de la aplicación. Se sugiere que no supere los 10 caracteres.

Ejemplos: Id (identificador), Code (código), Name (nombre), Date (fecha), Description (descripción),
Price (precio), Stock.

--------------------------------------------------------------------------------------------------------------
1 Para países que utilicen lenguas en las que los adjetivos suelan colocarse después del sustantivo. En el

inglés esto es al revés, por lo que la categoría (el sustantivo) va al final.


2 O un conjunto de Transacciones paralelas y/o subordinadas, de las que hablaremos más adelante.

45
Calificador: Es cualquier adjetivo o adverbio, en el entorno de 10 caracteres, que agregue
diferenciación conceptual al nombre del atributo para hacerlo único.
En general refiere al texto que califica la categoría: Fecha de vencimiento,
Ejemplos: Start (inicial), End (final), Due (vencimiento)

Complemento: Texto libre hasta completar la cantidad de caracteres significativos (30) para el nombre.

Tal como se muestra en la transparencia, algunos ejemplos de nombres de atributos son:

Nota 1: Las letras mayúsculas permiten establecer fronteras entre los componentes que forman a los
nombres de atributos.

Nota 2: Para cada componente se pueden utilizar la cantidad de caracteres que se deseen, aunque se
sugiere utilizar 10 y que el nombre completo del atributo no supere los 30.

46
Demo
ÎCreación de base de conocimiento
ÎCreación de transacciones

Para crear una base de conocimiento, se debe seleccionar en la barra de menú de GeneXus, el ítem
File / New Knowledge Base. A continuación aparecerá un diálogo que solicitará la ubicación y
nombre de la base de conocimiento a crear.

Una vez creada la base de conocimiento, la misma quedará abierta en el modelo de Diseño, para
que se empiecen a crear las transacciones.

Antes que nada debemos posicionarnos (haciendo clic simplemente) en la carpeta “Objects” del
árbol que se encuentra en la división izquierda de la pantalla, ya que los objetos que vayamos
creando irán quedando dentro de dicha carpeta.

La creación de objetos, se realiza mediante el ítem Object / New Object de la barra de menú de
GeneXus.

Al seleccionar el ítem Object / New Object se desplegará un diálogo en el cual se deberá elegir el
tipo de objeto que se desea crear (en este caso el tipo de objeto: transacción), dar un nombre al
objeto que se está creando (por ejemplo: “Customer”), una descripción larga, y se podrán indicar
algunas cosas más que iremos viendo.

Una vez creada la transacción, la misma quedará abierta para que se defina su estructura.

47
Definición de atributos

Para definir un atributo, simplemente se debe digitar en el primer campo de una línea (o rama)
de la estructura, el nombre del atributo que se desea crear. Mediante la tecla de tabulación se
puede pasar a los siguientes campos para indicar el tipo de datos del atributo, así como su
descripción, si admitirá valores nulos de la base de datos, y en el caso que el mismo vaya a ser
una fórmula (concepto que veremos en breve), cómo calcularla. Con la tecla Enter se puede
pasar a la siguiente línea, para definir otro atributo.

Una vez definidos los atributos en la estructura de la transacción, solamente restará guardar /
salvar los cambios.

Si se necesita modificar el nombre de un atributo, su tipo de datos, descripción, nulabilidad, o


fórmula, bastará con hacer doble clic sobre el campo implicado, y el mismo se habilitará y se
podrá editar. Luego se deberán guardar/salvar los cambios, nuevamente.

Para indicar que uno o más atributos son identificadores en la transacción, se los debe
seleccionar y presionar CTRL + K; en consecuencia aparecerán con un símbolo de llave.

Para definir que comienza otro nivel en la transacción, se debe digitar CTRL + L, y
automáticamente se producirá una indentación y el usuario deberá darle nombre a ese nivel,
así como definir el nombre de su tipo de datos1 y una descripción para el nivel.

Para indicar que un atributo de uno de los niveles de la transacción será el atributo
“descriptor”, se lo debe seleccionar y presionar CTRL+D.

GeneXus cuenta con menús pop up2, que son menús que se abren cuando el usuario está
posicionado en determinado contexto y da clic con el botón derecho del mouse. Por ejemplo, al
hacer clic con el botón derecho del mouse sobre un atributo de la estructura, se abre un
menú pop up sobre el atributo seleccionado, que ofrece varias utilidades, como por ejemplo
indicar que el atributo es clave (opción “Toggle key”), quitarlo de la estructura, pasarlo a un
siguiente nivel de anidación, etc.

Cada atributo tiene propiedades. Algunas de ellas (las fundamentales y obligatorias) son las
que se ofrecen directamente en la estructura para su ingreso “inline”. Para acceder al diálogo
que permite configurar todas las propiedades de un atributo, se debe seleccionar el ítem
Properties del menú contextual.

----------------------------------------------------------------------------------------------------
1 Veremos su utilidad cuando estudiemos los “business components”.
2 También llamados “contextuales” dado que varían según el contexto.

48
La posibilidad de modificar las propiedades de un atributo solo estará disponible en el modelo de Diseño, ya que
es en este modelo de la base de conocimiento en el que se diseñan y editan las transacciones y atributos.

El diálogo de configuración de las propiedades de un atributo tiene 4 solapas: General, Control Info, Help y
Documentation.

1) General: Contiene información general del atributo.

Name: Es el nombre del atributo. Se utiliza para identificarlo.

Description: La “Descripción” o más propiamente “Nombre largo” es una descripción ampliada del atributo. Debe
identificar bien al atributo, con independencia del contexto, y principalmente debe ser entendible por el usuario final.
Debe ser un identificador global del atributo, es decir, no se le debe asignar a dos atributos en la base de conocimiento
la misma descripción, ya que será a través de esta descripción que el usuario final podrá seleccionar atributos para
definir sus propias consultas a la base de datos, con GeneXus Query, ejecutando “reportes dinámicos” (tema bastante
simple, pero que no se abordará en este curso).

Relacionadas a esta propiedad, están las propiedades Title y Column Title, que por defecto toman el mismo valor que
se especifica en Description, pudiéndose modificar. Estas propiedades se describen a continuación:

Title: La descripción que se ingrese aquí será colocada por defecto al lado del atributo cada vez que se utilice en
salidas “planas” como en el primer nivel de los forms de las transacciones, o en reportes generados con el Report
Wizard. El valor de esta propiedad inicialmente se hereda de la propiedad Description del atributo, pudiendo ser
modificada por el programador.

Column Title: La descripción que se ingrese aquí será colocada por defecto como título del atributo cada vez que se lo
incluya en la columna de un grid (grilla). El valor de esta propiedad inicialmente se hereda de la propiedad
Description del atributo, pudiendo ser modificada por el programador.

Domain: Permite asociarle un dominio1 al atributo. Al hacerlo, ciertas propiedades del atributo se deshabilitarán (como
Data Type y Length) tomando los valores del dominio. En caso de que el atributo no pertenezca a un dominio, el
programador dejará el valor [none] en esta propiedad, y las correspondientes al tipo de datos del atributo estarán
habilitadas para ser ingresadas.

Data Type: Permite indicar el tipo de datos del atributo. Aquí se podrá elegir uno de los tipos de datos soportados por
GeneXus. Dependiendo del tipo de datos que se seleccione habrá ciertas propiedades, u otras, para configurar.

Length: Permite indicar el largo del atributo. Si en la propiedad Data Type se indica que el atributo es numérico,
entonces se deberá tener en cuenta que el largo incluya las posiciones decimales, el punto decimal y el signo. Esta
propiedad estará deshabilitada cuando el tipo de datos elegido no requiera establecer un largo (por ejemplo, si se trata
del tipo de datos Date).

Decimals: Si en la propiedad Data Type se indica que el atributo es numérico, se ofrecerá esta propiedad, para que
se especifique la cantidad de decimales. El valor 0 en este campo, indicará que se trata de un entero.

Signed: Si en la propiedad Data Type se indica que el atributo es numérico, se ofrecerá esta propiedad para que se
indique si manejará signo o no. El valor por defecto es “False”, lo que indica que no se representarán valores
negativos.

Value Range: Permite indicar un rango de valores válidos para el atributo. Por ejemplo:
• 1:20 30: - significa que los valores válidos son entre 1 y 20; y 30 o mayor.
• 1 2 3 4 - significa que los valores válidos son 1, 2, 3 o 4.
• 'S' 'N' - significa que los valores válidos son 'S' o 'N'.

Picture: Permite indicar el formato de edición para la entrada y salida del atributo. Dependiendo del tipo de datos del
atributo, aparecerán determinadas propiedades bajo esta categoría.

GeneXus provee más propiedades para los atributos que las recién mencionadas. Dependiendo del valor que se elija
para determinada propiedad, se ofrecerán ciertas propiedades relacionadas, u otras. Recomendamos para la lectura de
otras propiedades, acceder al Help de GeneXus.

-------------------------------------------------------------------------------------------------------------------------------1 Los
dominios se abordarán más adelante en el curso, pero a modo de resumen, el objetivo de los dominios es realizar
definiciones de datos genéricas. Por ejemplo: se puede definir un dominio de nombre Precio y tipo de datos
Numeric(10,2) y luego asociarle este dominio a todos los atributos que almacenan precios. Esto tiene la ventaja de que
si después se desea modificarlo a por ejemplo Numeric(12,2), hay que modificar solamente la definición del dominio y
automáticamente todos los atributos basados en ese dominio heredan el cambio.

49
2) Control Info

A un atributo se le puede asociar un tipo de control. Los tipos de controles posibles son:
- Edit
- Radio Button
- Check Box
- Combo Box
- List Box
- Dynamic Combo Box
- Dynamic List Box

La asociación de cierto tipo de control a un atributo, sirve para especificar el tipo de control por defecto que se utilizará
para el atributo cada vez que se lo incluya en un form.

Cuando se define un atributo el tipo de control que tiene asociado es Edit, pudiendo el programador cambiarlo a
cualquiera de los otros tipos.

En la solapa Control Info del diálogo de edición de las propiedades de un atributo es donde el programador podrá
cambiar el tipo de control asociado al atributo y dependiendo del tipo de control seleccionado, se solicitará distinta
información complementaria.

Luego, cada vez que se agregue el atributo en un form se presentará automáticamente con el tipo de control que
tenga asociado aquí.

Nota
En caso de asociar al atributo el tipo Edit, se podrá especificar que “disfrace” sus valores, mostrando los de otro
atributo (propiedad InputType), e incluso que sugiera los valores posibles al usuario, a medida que éste vaya
digitando (propiedad Suggest), entre otras, como veremos luego, cuando estudiemos “Descripciones en lugar de
códigos”.

También se puede elegir el color de la fuente de letra que se desea asociar por defecto al atributo, así como el color de
fondo, de modo que cada vez que se agregue el atributo en un form, se presente automáticamente con los colores que
se le hayan asociado.

3) Help

Esta solapa permite que el programador ingrese un texto de ayuda asociado al atributo, para que el usuario final pueda
consultarlo en tiempo de ejecución. Si el usuario final solicita ayuda (presionando F1), estando posicionado en el
atributo, se le desplegará el texto que se haya ingresado aquí.

Más adelante, veremos otros temas relacionados al help de una aplicación GeneXus.

4) Documentación

Esta solapa permite que el programador ingrese documentación técnica del atributo, útil para los desarrolladores.

50
Tipos de Datos

ƒ Numeric, Character, Date

ƒ VarChar
- Equivalente a Character, salvo en la forma en que se almacena en la BD.
- Propiedades Maximum Length y Avarage Length asociadas.

ƒ Long Varchar
- Permite almacenar textos largos, comentarios, etc. (memo).

ƒ DateTime
- Permite almacenar una combinación de fecha y hora.

ƒ Blob
- Permite almacenar cualquier tipo de información: texto, imágenes,
videos, planillas, etc., en la base de datos.
- Win / Web ofrecen manipulación distinta de este tipo de datos

ƒ Numeric: Permite almacenar datos numéricos. Cuando se selecciona este tipo de datos se debe indicar
la cantidad total de dígitos del número, la cantidad de posiciones decimales, y si permite signo o no.

Ejemplo:
Si definimos que el tipo de datos del atributo InvoiceTotal es numérico de largo 10, con decimales 2, y
sin signo, estamos especificando que representará números con hasta 7 dígitos en la parte entera y 2
decimales (7 dígitos en la parte entera + punto + 2 dígitos para los decimales = 10 dígitos).

ƒ Character: Permite almacenar cualquier tipo de texto (caracteres y dígitos). Para este tipo de datos,
se debe indicar el largo.

Ejemplo: El atributo CustomerName que utilizamos para almacenar el nombre de un cliente, es de tipo
Character y si sabemos que nunca un nombre tendrá más de 20 caracteres, podemos fijar el largo: 20.

ƒ Date: Permite almacenar una fecha.


Ejemplo: El atributo InvoiceDate que utilizamos para almacenar la fecha de una factura, será de este
tipo de datos.
El formato a utilizar para las fechas (día-mes-año, mes-día-año), se configura en forma genérica para
todo el modelo dentro de un tipo especial de objeto, el objeto Language correspondiente al lenguaje en
el que se generará el modelo. El objeto “language” existe para poder tener una misma aplicación
traducida en cualquier lenguaje.
La elección de presentar el año con 2 dígitos o 4, se configura con la propiedad Picture de cada
atributo.

51
• VarChar: Permite almacenar texto de largo variable. Su función, en contraposición al Character, es optimizar el
almacenamiento en la base de datos.
Cuando se selecciona el tipo de datos VarChar en el diálogo de definición del atributo se agregan las 2 siguientes
propiedades: Maximum Length y Average Length.
La primera es para indicar el largo máximo de caracteres que se podrán almacenar, mientras que la segunda es
para indicar el largo promedio de caracteres que se suele almacenar por lo general.
Ejemplo: Cuando se define un atributo de tipo Character y largo 60, si se le ingresa un dato que ocupa 25
caracteres, la capacidad restante de almacenamiento del atributo (35 caracteres) se rellena con blancos. El tipo
de datos Varchar, en cambio, optimiza el almacenamiento de la siguiente forma: se le define Average Length
(por ejemplo: 25), y Maximum Length (por ejemplo: 60); entonces, si el dato tiene largo menor o igual que 25,
se lo almacena en el campo (rellenado con blancos) mientras que en los casos que el dato sea de largo mayor que
25, se almacenan los primeros 25 caracteres en el campo, y el resto en un área de overflow.
Como contrapartida a la ventaja de la optimización del almacenamiento, para los casos en que se utilice el área de
overflow, será necesario realizar un acceso adicional tanto para la lectura como para la escritura del dato.
El tipo de datos Varchar es equivalente al tipo Character en todos los sentidos, salvo en la forma en que se
almacena en la base de datos. Se le pueden aplicar todas las funciones y operadores existentes para el tipo de
datos Character. Si el DBMS no soporta este tipo de datos, se creará el atributo de tipo Character.

ƒ Long Varchar: Permite definir un atributo memo; es decir, se utiliza normalmente para almacenar textos
largos, comentarios, etc. Al seleccionar este tipo de datos, se debe indicar un largo máximo.
Existen dos funciones para manipular este tipo de datos: GXMLines y GXGetMLi.

GXMLines retorna la cantidad de líneas que tiene un atributo Long Varchar, teniendo como parámetros el nombre
del atributo, y la cantidad de caracteres que se desea considerar por línea.

Ejemplo: &cantlin = GXMLines( AtribMemo, 40 ).

GXGetMLi por su parte, extrae una línea del atributo Long Varchar (para luego imprimirla, por ejemplo); teniendo
como parámetros el nombre del atributo, el número de línea deseado, y la cantidad de caracteres que se desea
considerar por línea.

Ejemplo: &txt = GXGetMLi( AtribMemo, 1, 40 ).

Generalmente se usan estas 2 funciones en combinación, ya que primero se suele consultar la cantidad de líneas
que tiene cierto atributo Long Varchar, indicando la cantidad deseada de caracteres por línea, y luego se prosigue
extrayendo el contenido de las líneas, utilizando un bucle hasta llegar a la última línea, y de esta forma se
imprimen, por ejemplo.
ƒ DateTime: Permite almacenar una combinación de fecha y hora.
La propiedad Picture de este tipo de datos, permite elegir qué se desea mostrar de la fecha, y qué se desea
mostrar de la hora.
Acerca de la fecha se puede elegir: no manejarla, manejarla y presentar el año con 2 dígitos, o manejarla y
presentar el año con 4 dígitos. Acerca de la hora se puede elegir: manejar solo 2 dígitos para la hora (no
manejando minutos ni segundos), manejar 2 dígitos para la hora y 2 dígitos para los minutos (no manejando
segundos), o manejar 2 dígitos para la hora, 2 dígitos para los minutos y 2 dígitos para los segundos.
Los valores anteriores no afectan la forma de almacenar el tipo de datos sino específicamente la forma de
aceptar o mostrar su contenido.
Nota: En caso de no manejar la fecha, sino solo la hora, el valor de fecha que quedará en el campo será el
mínimo soportado por el DBMS, y será reconocido por GeneXus como fecha vacía o nula. En lo que respecta a la
hora, los valores no aceptados (minutos y/o segundos) serán almacenados con valor cero.

ƒ Blob: Ante el creciente manejo de imágenes digitalizadas, videos, planillas así como documentos de todo tipo,
las aplicaciones requieren cada vez más mantener y trabajar con este tipo de información.
El tipo de datos Blob permite almacenar esta diversidad de información en la propia base de datos, aprovechando
así los diferentes mecanismos de integridad y control que proveen los DBMSs.
Este tipo de datos solamente se puede utilizar cuando se cuenta con un DBMS.
Dependiendo de si se implementa la aplicación en ambiente Win o Web, será la forma de manipulación de un
atributo de este tipo de datos. El ambiente Web ofrece más funcionalidades para trabajar con atributos de tipo
Blob de forma muy amigable.
Para profundizar en el conocimiento de este tipo de datos puede dirigirse al Help de GeneXus.

52
Definición de variables

Î En todo objeto GeneXus es posible definir variables.

Î Las variables son únicamente visibles dentro del objeto; es


decir, son locales.

Para definir variables en determinado objeto, estando dentro del mismo, se debe presionar el botón
de la barra de herramientas “Fast Access” de GeneXus, y se abrirá el diálogo de definición de
variables, mostrado en la transparencia.

Este diálogo muestra variables definidas por defecto (como por ejemplo la variable Today que contiene la
fecha del sistema) para el objeto, y permite definir variables nuevas mediante los botones “Add” y “Add
Based On”.

Botón “Add”
Al seleccionar el botón “Add”, se abre el siguiente diálogo para la definición de una variable:

Este diálogo solicita el nombre de la variable, su descripción, tipo de datos y largo, o dominio, de forma
análoga a cuando se define un atributo.
La propiedad Dimensions permite indicar si la variable será escalar o si se tratará de un vector (1
dimensión) o matriz (2 dimensiones). El valor que ofrece por defecto esta propiedad es escalar.

53
Botón “Add Based On”
El botón “Add Based On” ofrece una forma rápida de definir una variable. Cuando se selecciona, se despliega un
diálogo que muestra todos los atributos definidos en la base de conocimiento; en dicho diálogo, simplemente se debe
seleccionar un atributo, y a continuación se definirá automáticamente una variable con el mismo nombre y la misma
definición que el atributo. Vale aclarar que se pueden seleccionar varios atributos, creándose en tal caso una variable
por cada atributo seleccionado, con sus mismas características.

Botones “Edit”, “Remove”, “Copy”, Paste”


El botón “Edit” permite editar una variable que se haya seleccionado previamente, mientras que el botón “Remove”,
permite borrar una o más variables que se hayan seleccionado a la vez.

El botón “Copy” ofrece la posibilidad de copiar en el portapapeles la definición completa de las variables que se hayan
seleccionado previamente, para luego poder pegar dichas definiciones de variables en otro objeto de la base de
conocimiento, con el botón “Paste” de este mismo diálogo; es decir, en el objeto destino, se deberá entrar a este
mismo diálogo, y presionar el botón “Paste”.

Por otra parte, es posible definir variables dentro del editor de código de cada objeto (source, eventos, etc.), haciendo
uso del menú contextual (botón derecho) luego de escribir el nombre de la variable. Esto es, al escribir
&nombreDeVariable (ej: &x) y presionar botón derecho del mouse, se abrirá el siguiente menú contextual:

También es posible editar variables luego de definidas, de esta misma forma. Si al escribir &nombreDeVariable (ej:
&x), la misma ya existe definida para el objeto, el menú contextual mostrará:

Al seleccionar la opción “Define” se abrirá el mismo diálogo que al presionar el botón “Add”. Análogamente, al
seleccionar la opción “Edit” de este menú, se abrirá el mismo diálogo que al presionar el botón “Edit” del diálogo de
definición de variables mostrado en la transparencia.

54
Dominios

• Objetivo: Realizar definiciones genéricas.

• ¿Cuándo debemos usar dominios?


• Atributos y/o variables con la misma definición

Ejemplo:

Atributos ProductPrice: Precio de producto


InvoiceLineAmount: Importe total de línea

Dominio Price: Numeric(10.2)

Es común tener en una base de conocimiento atributos que comparten definiciones similares pero
que no tienen relación entre sí. Por ejemplo, es común definir todos los nombres como atributos de
tipo character y largo 20; o todos los importes, como atributos de tipo numérico y largo 10.2.

El objetivo de los dominios es permitir realizar definiciones genéricas. A modo de ejemplo, el


atributo InvoiceLineAmount es de tipo numérico y largo 10.2, y al mismo tiempo, el atributo
ProductPrice es del mismo tipo y largo, en la misma base de conocimiento. De modo que podríamos
definir un dominio de nombre Price, que sea de tipo numérico con largo 10.2, y a cada uno de los
atributos anteriores le asignaríamos dicho dominio. La ventaja de hacerlo así es que si en el futuro
surge la necesidad de cambiar la definición de los atributos que representan importes, haríamos el
cambio una sola vez (en el dominio Price), propagándose éste automáticamente a los atributos
InvoiceLineAmount y ProductPrice por pertenecer a él.

Así como podemos asociarle a un atributo un dominio, también lo podemos hacer para una variable.
Un mismo dominio puede asignarse tanto a atributos como a variables, ya que su objetivo es
exactamente el mismo.

¿Cómo definir un dominio?


Existen varios caminos:
1) El ítem: Advanced / Domain de la barra de menú de GeneXus ofrece un diálogo para realizar el
mantenimiento de los dominios de la base de conocimiento; esto es: crear dominios, modificarlos y
eliminarlos (la eliminación de un dominio solo se permitirá si el mismo no está asignado a ningún
atributo ni variable).
2) Dado que en la pantalla de configuración de las propiedades de un atributo es donde se le asigna
a un atributo un dominio existente, en dicha pantalla se ofrece un botón para crear un dominio
nuevo. Ídem con el diálogo de definición de variables.
3) En la estructura de la transacción es posible definir un nuevo domino en el campo de definición
del tipo de datos de un atributo, simplemente escribiendo sobre esa misma línea, el nombre del
dominio, y asignándole el tipo de datos. Por ejemplo, digitando Price = Numeric(10.2) sobre la
columna Type del atributo que se está definiendo, queda también definido el dominio de nombre
Price, con el tipo de datos Numeric(10.2).

Estos accesos para trabajar con dominios solo se encuentran habilitados en el modelo de Diseño de
la base de conocimiento, al igual que todas las funcionalidades que puedan implicar modificaciones
en la base de datos física.

55
Forms

ÎCada transacción tiene asociado un form GUI-


Windows y un form Web.

ÎAmbos forms son creados por defecto al grabar la


estructura de la transacción, pudiendo ser
modificados por el programador.

Para cada transacción, GeneXus crea un form GUI-windows y un form Web, los cuales serán la interfaz
con el usuario, en ambiente windows y Web respectivamente.

Ambos forms son creados por defecto por GeneXus al momento de grabar la estructura de la
transacción, y contienen todos los atributos incluidos en la misma, con sus respectivas descripciones,
además de algunos botones.

Si bien son creados por defecto, es posible modificarlos para dejarlos más vistosos, cambiar por ejemplo
controles de tipo edit a otros tipos de controles, agregar y/o quitar botones, etc.

Para editar el form GUI-windows de una transacción, se debe seleccionar la solapa Form que se
encuentra en la barra de edición del objeto, mientras que para editar el form Web, se debe seleccionar la
solapa Web Form en la misma barra.

56
Form GUI-Windows
de la transacción “Invoice”

GRID

A través del form de la transacción (GUI-Windows o Web según el ambiente de trabajo) el usuario podrá
ingresar, modificar y eliminar registros en tiempo de ejecución.

En el ejemplo se muestra el form GUI-Windows correspondiente a la transacción de facturas (“Invoice”).

57
Form Web
de la transacción “Invoice”

Control “Error Viewer”


exclusivo de Web

botón “Get”

GRID

En el ejemplo se muestra el form Web correspondiente a la transacción “Invoice”. A través de este form el
usuario final podrá ingresar, modificar y eliminar facturas en la aplicación Web.
Pueden verse algunas diferencias en el diseño gráfico del form Web respecto al form Win, pero podemos ver
que todos los controles que están en el form Win, también lo están en el Web. El recíproco no se cumple: el
botón “Get” al lado de la clave primaria aparece en el form Web y no en el Win que hemos visto1. El control
“Error Viewer” aparece también solamente en el form Web. Y el grid correspondiente a las líneas de la factura,
contiene en el form Web una primera columna con un check box, que el grid del form Win no la incluye.
Comentaremos a continuación el control Error Viewer. Sobre el botón “Get” entenderemos el por qué de esta
diferenciación un poco más adelante, cuando estudiemos el diálogo con validación a nivel del cliente. Con
respecto al check box también veremos este tema más adelante, cuando estudiemos la ejecución.
En una aplicación con interfaz Win, los mensajes que deban mostrarse al usuario en tiempo de ejecución se
desplegarán en una ventana de Windows independiente, que no programamos nosotros. En Web, en cambio,
los mensajes2 deben mostrarse dentro de la página HTML que contiene el form de la transacción. Es por este
motivo que existe el control Error Viewer, para poder ubicar y programar el lugar donde queremos que los
mensajes generales le sean desplegados al usuario final de una transacción Web.
Podremos especificar entre otras cosas el lugar donde queremos que este control se ubique dentro del form, su
tipo de letra, color, etc.
Existen algunas diferencias más entre ambos forms, pero son menores dado que representan formas distintas
de realizar lo mismo: por ejemplo, mientras que el botón para confirmar las acciones y comenzar la grabación
de los registros de la transacción en el form Win tiene como texto “Confirm”, en el Web su texto es “Apply
Changes”. Análogamente, mientras el botón para eliminar todo (cabezal y líneas) tiene como texto solo
“Delete” en el form Win, en el Web dice “Delete All”. La funcionalidad es la misma.
Por último vale mencionar que en el form Web se puede percibir el botón Check, que permite chequear que
todo lo que ha ingresado el usuario hasta el momento sea correcto.
Más adelante, cuando estudiemos cómo se trabaja en ejecución con las transacciones, arrojaremos más luz
sobre estos asuntos.

-----------------------------------------------------------------------------------------------------------------------
1 Más adelante cuando estudiemos el diálogo con validación a nivel del cliente, veremos que en ambiente Win
se podrá optar por trabajar con dicho diálogo o no, y dependiendo de ello, el botón “Get” estará presente en el
form o no.
2 Cuando estudiemos el diálogo con validación a nivel del cliente en Web, veremos que muchos de los mensajes

se desplegarán en cajas de texto sobre la pantalla, específicamente sobre los campos que el usuario haya
ingresado y en los que se deba informar de algo al usuario. Es decir, funcionarán de manera combinada el
control Error Viewer junto con los mensajes interactivos en cajas de texto.

58
Paletas de herramientas para
diseño de forms Win y Web

Insertar controles:

Cortar, copiar y pegar controles:

Alinear, distribuir y uniformizar los tamaños de los controles


(solo disponible para form GUI-Windows):

Podemos definir un control como un área de la interfaz con el usuario, que tiene una forma y un
comportamiento determinado.
Existen distintos controles:

- Form: Un form puede verse en sí mismo como un control.


- Texto: Permite colocar texto fijo (por ejemplo las descripciones de los atributos en el form Win:
“Customer Name”, “Customer Id”, o cualquier otro texto).
- Atributo/Variable: Permite colocar atributos o variables.
- Línea: Con este control se dibujan líneas horizontales o verticales (form Win).
- Recuadro: Permite definir recuadros de distintos tamaños y formas (form Win).
- Grid: Permite definir grillas de datos.
- Botón: Permite incluir botones en los forms.
- Bitmap: Permite definir bitmaps estáticos.

La mayoría de los controles anteriores (salvo línea y recuadro) están disponibles tanto para ser utilizados en
interfaz GUI-Windows como Web. A su vez el “tab control” es un control que solo está disponible para ser
utilizado en interfaz GUI-Windows, mientras que otros controles solo pueden utilizarse en interfaz Web (text
blocks, tablas, grids freestyle, Error Viewer, etc.).

Tab Control: tiene una o varias solapas, en las cuales se pueden distribuir controles. Cada solapa tiene un
título y un área útil para que se le incluyan controles. El uso de este control es útil para los casos en los
cuales se quiere distribuir los datos en distintos grupos, para presentarlos de forma amigable para el usuario.
Por ejemplo, si queremos dividir la información de la transacción “Customer” en 2 solapas: una para ingresar
los datos generales del cliente, y otra para ingresar los datos comerciales, podemos insertar en el form un
Tab Control con dos solapas, y distribuir los controles de texto, atributo, etc. en cada una de ellas.

Paleta de herramientas para insertar controles: Cuando se está editando un form, se encuentra
disponible una paleta de herramientas que ofrece los controles posibles de insertar en el mismo.
Simplemente se deberá seleccionar en la paleta de herramientas el control que se desee insertar en el form,
para lo cual se deberá hacer clic en el ícono que lo represente; seguidamente se deberá hacer clic en el form,
en el lugar que se desee ubicar el control, y se insertará el control elegido en el lugar del form que se haya
indicado; en caso de ser necesario, se abrirá el diálogo correspondiente a las propiedades del control, para
que se indiquen aquellas que sean de carácter obligatorio.

59
Controles en form Web

• Cada control del form Web podrá tener una clase asociada, de
un objeto theme (tema) determinado, asociado al objeto.

• Al crear una KB, se crea por defecto el tema “Default” y todos


los objetos que se creen tendrán este tema.

• Esto permitirá independizar el diseño de la interfaz, de la


programación.
• Existe el Editor de temas, un utilitario que usarán los diseñadores
gráficos del sitio
• Cada tema tendrá definidas muchas clases para cada tipo de control
• El analista solo asocia un tema al objeto, y una clase a cada control
del form y puede olvidarse del diseño de los mismos.

• El control hereda el diseño de la clase del tema al que esté


asociado

Una de las ventajas de las aplicaciones con interfaz Web frente a las mismas aplicaciones con interfaz GUI-
Windows, refiere a los aspectos de diseño. Las aplicaciones Web, programadas en HTML, pueden llegar a
ser verdaderamente vistosas.

Para lograr separar los aspectos de diseño gráfico de un sitio web, de los aspectos de programación
propiamente dichos, existe un tipo de objeto llamado Theme (Tema, en español), y un utilitario
independiente para editar objetos de este tipo, el Editor de Temas. Este utilitario puede también invocarse
desde GeneXus mediante el ítem Tools/Theme Editor estando en el modelo de diseño.

El objetivo de esta separación es poder paralelizar el desarrollo de un sitio Web, permitiéndole al


programador abocarse a las tareas de programación, y apoyarse en un diseñador gráfico para que defina
las cuestiones de diseño. De esta manera el diseñador gráfico configurará el “tema” elegido por el
programador, utilizando este utilitario independiente, y el programador sólo deberá aplicarlo a los objetos
de su base de conocimiento (para esto existe una propiedad de nombre “Theme” a nivel de modelo y otra
propiedad de nombre “Theme” a nivel de cada objeto1 ).

Un objeto “tema” contendrá un conjunto de clases, para cada tipo de control de los que pueden aparecer
en el form Web de un objeto GeneXus. Por ejemplo, tendrá varias clases para el control botón, algunas
para el control atributo, una clase para el control Error Viewer, etc.

Cuando se crea una base de conocimiento, automáticamente aparecerá dentro de la carpeta “Objects”
(que contendrá todos los objetos GeneXus que se vayan creando) un objeto de tipo Theme cuyo nombre
es “Default”. Este tema contiene un conjunto de clases asociadas a los distintos controles existentes2.

Los objetos con form Web que se vayan creando tendrán por defecto asociado este Theme, y cada
control que aparezca en sus forms tendrá asociado una clase de ese tema, para este control.

-------------------------------------------------------------------------------------------------------------
1 Al final de este capítulo se mencionará que al igual que cada atributo tiene un conjunto de propiedades
configurables por el programador, así cada modelo y cada objeto. Por ejemplo, la propiedad Theme se
encontrará para configurar en los diálogos de propiedades asociados a cada modelo Web y a cada objeto
que tenga form Web (transacciones y Web Panels).
2 No solo existen clases para los controles en un tema, pero de esto no nos ocuparemos en este curso.

60
Controles en form Web
Ejemplo: transacción “Customer” con tema “Default”

De este modo, cuando el analista crea la transacción “Customer” e ingresa su estructura, al grabar
podrá apreciar que el form Web tendrá automáticamente el aspecto que se muestra en la pantalla de
arriba a la izquierda. ¿Quién dio formato a todos los controles si no lo hizo el analista explícitamente?

Pues bien, todas las transacciones tendrán asociado por defecto el theme “Default” y todos los controles
que se coloquen en el form, tendrán clases de este tema asociadas.

Si sobre el botón “Apply Changes”, con el botón derecho del mouse se editan sus propiedades, se abrirá
un diálogo (que se presenta abajo a la izquierda) y en el mismo se puede observar la propiedad Class,
que tiene configurada la clase BtnEnter que es una clase de botón. Esta propiedad puede ser cambiada
por el analista (podemos ver que se trata de un combo box). All cliquear el combo box podremos ver
una lista de clases posibles para ese botón (son las que figuran en el tema asociado), pero también
aparece la opción “(none)” para no asociarle clase alguna. Esto es lo que hemos seleccionado en la
pantalla de la derecha (el valor “none” para la propiedad Class del botón) y podemos ver su repercusión
inmediata en el botón en el form.

Si se comparan los dos diálogos de propiedades del control “Apply Changes”, donde lo único que ha sido
modificado explícitamente por el analista ha sido la clase, podemos ver que implícitamente se han
modificado algunas propiedades (el BackColor, ForeColor, BorderWith, etc.). Esto se debe a que estas
propiedades se heredan de la clase una vez definida, pero si el analista así lo desea, puede modificarlas
para ese control botón, independizándolo de seguir el comportamiento de su clase.

Sobre este tema podrá profundizar si así lo desea en el curso “Desarrollo de Aplicaciones para Internet”,
tanto presencial como no presencial.

Aquí solamente pretendemos tener un barniz del uso de los temas y las clases de los controles.

61
Demo

οCómo crear un modelo dentro de la base de


conocimiento?

WIZARD MANUAL

Para definir un nuevo modelo -a excepción del de Diseño, que se crea automáticamente-, debe
seleccionarse la opción File / New Model.

GeneXus provee de un wizard para guiar al usuario en los pasos de creación del nuevo modelo.

Si el usuario prefiere no utilizar el wizard e ingresar los distintos datos sin esta guía, deberá
presionar el botón Manual Creation de la primera pantalla del wizard, y éste se cerrará y se abrirá
el diálogo Model Properties.

A continuación se detalla parte de la información que se deberá ingresar:

Information

Name: Nombre del modelo que se está definiendo.

Type: Tipo de modelo (Prototipo o Producción).

Language: Idioma en el que saldrán impresos los textos de los botones, mensajes, etc., que son
generados automáticamente por GeneXus en los programas de la aplicación. Los lenguajes
soportados son: Español, Inglés, Italiano, Portugués y Chino. El valor del lenguaje solo puede
modificarse en el modelo de Diseño de la base de conocimiento; los demás modelos heredarán
este valor y no podrán modificarlo.

Se debe suministrar información del ambiente o plataforma para el modelo que se está definiendo:

Environment

Language: Este es el lenguaje en el cuál serán generados los programas usados para la creación
y reorganización de la base de datos, y también es el lenguaje predeterminado en el que serán
generados todos los objetos. Pueden definirse ambientes secundarios en la solapa Environments
del diálogo, que permitirán generar algunos de los objetos en otros lenguajes. Los lenguajes
soportados por GeneXus son: .NET, .NET Mobile, C/SQL, Cobol para iSeries, Java, RPG para
iSeries, Visual Basic, Visual FoxPro.

62
User Interface: Una vez que el lenguaje ha sido elegido, se puede seleccionar el tipo de interfaz que se quiere usar
para el ambiente principal: Win o Web Form. Existen lenguajes que solo soportan un tipo de interfaz.

DBMS: Aquí se debe seleccionar el DBMS (Database Manager System) sobre el cuál operarán los programas en
ejecución. La lista solo incluirá aquellos soportados por el lenguaje e interfaz previamente seleccionados.

Target Path: Directorio donde estarán ubicados los programas generados. Este directorio será creado bajo el
directorio de la base de conocimiento. El valor predeterminado es DATAnnn, donde nnn representa el número de
modelo (GeneXus numera secuencialmente los modelos a medida que se van creando).

A continuación se muestra una tabla con los valores de DBMSs posibles para cada lenguaje:

Properties
Haciendo clic en el botón Properties se pueden configurar las propiedades para el lenguaje seleccionado. Solemos
llamar a estas propiedades: propiedades a nivel del modelo o más reducido: propiedades del modelo.

DBMS Options
Haciendo clic en el botón DBMS Options se puede configurar la información requerida para el acceso a la Base de
Datos (Data Source, etc.) para el lenguaje y DBMS seleccionados.

Execution
Haciendo clic en este botón, se pueden especificar configuraciones de ejecución para el lenguaje seleccionado.

Save/Load
Estos botones permiten almacenar la información de las propiedades del modelo en un archivo (y luego recuperarlo).
La extensión predeterminada de este archivo es GMP (GeneXus Model Properties).

From Model
Permite copiar la información en forma directa, desde otro modelo de Prototipo / Producción dentro de la base de
conocimiento.

Una vez creado un modelo, es posible modificar sus propiedades. Para ello, se debe seleccionar la opción File / Edit
Model, y se presentará el diálogo Model Properties.

Suele ser usual acceder al diálogo Model Properties, en especial para utilizar los botones Properties, DBMS Options
y Execution para configurar sus propiedades con los valores particulares que se requiera para el modelo. Sin
embargo no es usual cambiar en el diálogo Model Properties lo configurado en la sección Enviroment (ya que si se
desea probar la aplicación para otra plataforma / ambiente, la forma correcta de proceder es crear otro modelo para la
plataforma deseada).

63
¿Qué son los conceptos...?

ÎAnálisis de Impacto

ÎReorganizar

ÎEspecificar

ÎGenerar

Cuando se pasa a un modelo de Prototipo o Producción en una base de conocimiento, GeneXus


compara las definiciones realizadas en el modelo de Diseño, con respecto a las definiciones que
existan en el modelo al cual se está ingresando.

Por ejemplo, si se está ingresando a un modelo de Prototipo o Producción recientemente definido (y


por ende vacío), todos los objetos definidos en el modelo de Diseño serán nuevos para dicho
modelo. Y las transacciones definidas, tal como es su objetivo, provocarán la creación de las tablas
que corresponda en la base de datos asociada al modelo de Prototipo o Producción.

Esta comparación que hace GeneXus entre las definiciones del modelo de Diseño de una base de
conocimiento y las definiciones de cierto modelo de Prototipo o Producción al cual el programador
pasa, se llama análisis de impacto. Este nombre es autodescriptivo: GeneXus analiza el impacto
causado por las definiciones del modelo de Diseño sobre un modelo de Prototipo o Producción. El
resultado del análisis de impacto es un reporte de análisis de impacto (IAR: Impact Analisis
Report) que informa al programador qué cambios físicos o estructurales habría que realizar en la
base de datos asociada al modelo de Prototipo o Producción en cuestión.

Si el programador está de acuerdo con los cambios estructurales informados en el reporte de


análisis de impacto, podrá proseguir, pasando a reorganizar la base de datos. El término
reorganizar refiere a efectuar cambios físicos en la base de datos.

Si en cambio el programador encuentra que algo de lo informado en el reporte de análisis de


impacto no era lo que pretendía lograr, podrá no efectuar la reorganización, y volver al modelo de
Diseño para realizar las redefiniciones que crea convenientes.

Cuando se decide efectuar una reorganización GeneXus genera programas (en el lenguaje elegido
en la definición de la plataforma del modelo) que implementan las modificaciones a realizar en la
base de datos. La ejecución de estos programas tiene por resultado la obtención de una nueva
versión de la base de datos con los cambios efectuados.

64
Inmediatamente después de reorganizar la base de datos, se copiarán automáticamente las nuevas definiciones
del modelo de Diseño al modelo de Prototipo o Producción en el cual se esté trabajando (destino); esto se llama
Copy Model y significa que el modelo de Prototipo o Producción quedará con exactamente las mismas definiciones
que el modelo de Diseño.

El siguiente paso será obtener los programas de aplicación asociados a los objetos GeneXus. Para ello el
programador GeneXus deberá especificar y generar los programas de aplicación.

Especificar un objeto significa que GeneXus analizará todo lo definido en cada uno de los elementos que lo
componen: estructura, forms, u otros elementos según corresponda. GeneXus verificará la sintaxis de las
definiciones, la validez de lo definido, y como resultado de la especificación mostrará al usuario un listado de
navegación, en el cual informará la lógica que ha interpretado, y si hay alguna advertencia o error.

Luego de especificar un objeto (o conjunto de objetos) y verificar el listado de navegación resultante, el


programador podrá indicar a GeneXus que prosiga con la generación de los programas de aplicación.
Generar un objeto, significa que GeneXus escribirá líneas de código que implementen la programación del mismo,
en el lenguaje elegido.

Otro ejemplo
Como hemos explicado, cuando se pasa a un modelo de Prototipo o Producción GeneXus realiza una comparación
de las definiciones del modelo de Diseño con respecto a las definiciones que existan en el modelo al cual se está
pasando. Esto es, un análisis de impacto.

Si se pasa a un modelo de Prototipo o Producción al cual ya se había pasado anteriormente alguna vez,
seguramente dicho modelo ya tenga algunas definiciones1 , a diferencia del ejemplo anterior en el cual se trataba
de un modelo nuevo y vacío.

Si por ejemplo el modelo de Prototipo o Producción al cual se está pasando tiene definida una transacción
“Customer” con la estructura:

Customer
CustomerId* - Numeric(6)
CustomerName - Character(20)
CustomerAddress - Character(30)
CustomerGender - Character(1)

y en el modelo de Diseño se encuentran definidas las transacciones “Customer” y “Product” con las siguientes
estructuras:

Customer Product
CustomerId* - Numeric(4.0) ProducId* - Numeric(4.0)
CustomerName - Character(30) ProductDescription - Character(20)
CustomerAddress - Character(30) ProductPrice - Numeric(10.2)
CustomerGender - Character(1) ProductStock - Numeric(4.0)
CustomerEMail - Character(30)

el análisis de impacto determinará que en el modelo de Prototipo al cual se está ingresando, se deberá:

1.Agregar el atributo CustomerEMail en la tabla CUSTOMER de la base de datos.


2.Agrandar el largo del atributo CustomerName a Character(30) en la tabla CUSTOMER de la base de datos.
3.Crear la tabla PRODUCT con sus atributos en la base de datos.

Estos cambios se informarán en un reporte de análisis de impacto (IAR: Impact Analisis Report), y el
programador deberá estudiarlo para decidir si efectuar la reorganización o no.

-------------------------------------------------------------------------------------------------------------------------
1 Cabe la posibilidad de que en una base de conocimiento haya algún modelo de Prototipo o Producción sin objetos.

Esto puede ocurrir por el simple motivo de que se haya creado un modelo, pero luego no se haya ejecutado una
reorganización en el mismo, ni se hayan copiado las definiciones del modelo de Diseño (Copy Model) tampoco. Sin
embargo, el ejemplo trata de un modelo Prototipo en el cual ya se ha ejecutado una reorganización y seguidamente
se han copiado las definiciones del modelo de Diseño (Copy Model) al mismo.

65
En el caso de decidir reorganizar la base de datos, seguidamente se copiarán automáticamente las nuevas
definiciones del modelo de Diseño al modelo de Prototipo, quedando éste con exactamente las mismas definiciones
que el modelo de Diseño. (Copy Model).

Por último sólo restará que el programador GeneXus especifique y genere los programas correspondientes a los
objetos que hayan sufrido cambios.

Concluyendo, hemos explicado varios conceptos que son muy importantes, viendo una primera aplicación de ellos,
y luego una segunda aplicación de los mismos, con el objetivo de entender qué realiza cada una de estas
operaciones y en qué orden se ejecutan.

66
Ítem “Build” de la barra de menú de GeneXus

Opciones del ítem “Build” para especificar y generar

El ítem Specify se habilitará cuando se esté en un objeto abierto; el objetivo de este ítem es especificar el
objeto activo.

El ítem Specify..., abrirá un diálogo de selección de objetos para seleccionar cuáles objetos se desean
especificar.

Y el ítem Build All..., permitirá especificar todos los objetos del modelo.

Vale aclarar que será exactamente lo mismo seleccionar el ítem Specify... y elegir todos los objetos del
modelo para ser especificados, que seleccionar directamente el ítem Build All....

¿Cómo indicar que se desea generar los objetos?

Luego de especificar uno o varios objetos -mediante cualquiera de los ítems anteriores- GeneXus presentará
un listado de navegación por cada objeto especificado; cada listado de navegación informará cuál fue la lógica
interpretada para el objeto, y si será necesario generar el programa de aplicación asociado al mismo, o no1. La
ventana que contiene los listados de navegación incluirá un botón de nombre Generate, que podrá ser
seleccionado por el programador para continuar con la generación de los programas de aplicación que sean
necesarios.

Además del botón Generate, se ofrecerá también un botón Cancel. Es importante tener claro que en caso de
seleccionarlo, quedará pendiente la generación de los programas de aplicación asociados a los objetos que
fueron especificados; esto significa que en la siguiente oportunidad que el programador seleccione el botón
Generate –así haya especificado en ese momento un solo objeto o varios- se generarán además de los
programas que correspondan ser generados en esa ocasión, todos los programas pendientes de generación,
por haberse especificado anteriormente sin continuar con la generación2.

--------------------------------------------------------------------------------------------------------------------
1 El motivo por el cual un listado de navegación podrá informar que no será necesario generar el programa
asociado a un objeto, es que el objeto no haya sufrido cambios con respecto a la última vez que se generó, y
por ende el programa generado antes seguirá estando vigente ahora.

2 Por esto solemos decir que podemos elegir qué objetos especificar, pero no cuáles generar.

67
¿En qué modelos se especifican y generan los objetos? ¿Diseño? ¿Prototipo? ¿Producción?

En los tres tipos de modelos es posible especificar los objetos, pero solamente en los modelos de Prototipo y
Producción se podrán generar los programas de aplicación asociados a ellos.

Recordemos que todo modelo de Prototipo o Producción tiene asociada una plataforma (base de datos,
lenguaje de programación, interfaz GUI-Windows / Web). En cambio, el modelo de Diseño no tiene asociada
una plataforma y por tanto no se generará para el mismo base de datos ni programas. Es por esto que en el
modelo de Diseño el programador podrá especificar objetos con el único objetivo de estudiar los listados de
navegación resultantes y con ello poder analizar la lógica interpretada por GeneXus para los mismos. Sin
embargo, luego de estudiar listados de navegación en el modelo de Diseño, no será posible generar (no se
ofrecerá el botón Generate).

Es en los modelos de Prototipo y Producción en los que a continuación de la especificación de los objetos se
ofrecerá el botón Generate en la ventana que contiene los listados de navegación, para que el programador
pueda continuar con la generación de los programas de aplicación, en la plataforma definida para el modelo.

¿Qué objetos GeneXus se suelen especificar y generar? ¿Todos? ¿Algunos?

Solamente es necesario especificar los objetos que hayan sufrido cambios; esto es, objetos nuevos que se
hayan definido, u objetos existentes que se hayan modificado.

Surge la necesidad en este momento de explicar un punto de suma importancia: en qué modelo de una base
de conocimiento se deben realizar las distintas definiciones y/o modificaciones de objetos.

Únicamente en el modelo de Diseño de una base de conocimiento se podrán definir o editar


transacciones, definir o editar atributos, y realizar modificaciones en las estructuras de las
transacciones en general (incluyendo definiciones de dominios, índices y subtipos1).

Para ser más exactos diremos que todas las operaciones que puedan provocar cambios estructurales
en las tablas de la base de datos podrán realizarse solamente en el modelo de Diseño. En modelos de
Prototipo y Producción estarán deshabilitadas las operaciones de este tipo, y para realizarlas el programador
deberá pasar al modelo de Diseño de la base de conocimiento.

En modelos de Prototipo / Producción se podrán realizar definiciones que no provoquen cambios estructurales
en las tablas de la base de datos (por ejemplo, crear o modificar objetos reportes, procedimientos, work
panels, etc.; modificar reglas o forms de las transacciones, etc.1) y automática e instantáneamente se
estarán realizando las mismas definiciones en el modelo de Diseño.

¿Cuál es la forma de trabajo entonces? Inicialmente se comienza a trabajar en el modelo de Diseño de una
base de conocimiento. Se definen las primeras transacciones y demás objetos y definiciones que se consideren
oportunas. Luego el analista deberá crear un modelo de Prototipo para probar la ejecución de las definiciones
realizadas; habrá un análisis de impacto, reorganización, actualización del modelo de Prototipo (Copy Model), y
el analista especificará y generará los programas de aplicación.

Se podrá ejecutar la aplicación definida hasta el momento, y luego de ello se continuará trabajando en el
modelo de Prototipo.

Todas las definiciones y/o modificaciones de objetos que se efectúen en el modelo de Prototipo, se efectuarán
automáticamente en el modelo de Diseño también; de modo que ambos modelos irán quedando
instantáneamente con exactamente las mismas definiciones (sincronizados). Por este motivo es que
aconsejamos trabajar en el modelo de Prototipo.

Cuando surja la necesidad de realizar definiciones que puedan afectar el diseño de la base de datos (por
ejemplo, crear una nueva transacción, modificar la estructura de una transacción existente, modificar un
dominio, etc.), habrá que pasar necesariamente al modelo de Diseño; pero si no, recomendamos trabajar
en el modelo de Prototipo, ya que automáticamente el modelo de Diseño se estará actualizando
también.

--------------------------------------------------------------------------------------------------------------------
1 Más adelante en el curso se irán introduciendo estos temas.

68
¿Qué pasa si trabajamos en el modelo de Diseño? Si en el modelo de Diseño realizamos al menos una
definición que implique modificar el diseño de la base de datos, cuando el programador pase al modelo de
Prototipo, GeneXus detectará que habrá cambios físicos para hacer. Habrá un análisis de impacto,
reorganización y actualización del modelo de Prototipo (Copy Model), y lo único que le restará hacer al analista
será especificar y generar los objetos nuevos (que nunca hayan sido especificados y generados), y los
objetos existentes que hayan sido modificados luego de su última especificación.

Sin embargo si se trabaja en el modelo de Diseño solamente definiendo objetos que no implicarán modificar
estructuralmente la base de datos, cuando el programador pase al modelo de Prototipo, GeneXus no detectará
cambios físicos para hacer, y no efectuará ninguna operación, ni siquiera la actualización del modelo de
Prototipo (Copy Model). De modo que las definiciones y modificaciones que se hayan hecho en el modelo de
Diseño, no se copiarán automáticamente al modelo de Prototipo, y será responsabilidad del programador
hacerlo, seleccionando uno de los ítems posibles para ello1.

Es sumamente importante que un modelo de Prototipo/Producción con el cual se esté trabajando en


determinada etapa (veremos que podrán haber modelos en una base de conocimiento con los cuales no se esté
trabajando momentáneamente), esté actualizado con respecto al modelo de Diseño (es decir, que siempre que
se trabaje en el modelo de Diseño, al pasar al modelo de Prototipo, se ejecute la operación Copy Model, ya sea
automáticamente luego de ejecutar una reorganización, o a pedido del analista si no hubo reorganización).

Si se pasa a un modelo de Prototipo/Producción y éste no se actualiza con las definiciones del modelo de
Diseño (Copy Model), los objetos del modelo de Prototipo/Producción al cual se pasó estarán desactualizados.
Si se especifican y generan estos objetos, deberá comprenderse que se tratará de definiciones viejas; y si se
ejecuta la aplicación generada, no se verán las nuevas definiciones realizadas, ya que las mismas quedaron
hechas en el modelo de Diseño, pero faltó que fueran copiadas al modelo de Prototipo, y recién luego de ello,
se tendrían que haber especificado y generado.

Además, si el analista llegara a modificar objetos desactualizados en el modelo de Prototipo, la próxima vez
que realice una actualización del modelo de Prototipo (Copy Model), se perderán las modificaciones realizadas
en versiones viejas de los objetos, ya que se traerán las versiones de los objetos del modelo de Diseño.

Concluyendo, hemos recomendado en qué modelo definir qué objetos, cuándo habrá que pasar de un modelo a
otro, y cuáles pasos seguir.

Como dijimos anteriormente, si bien es posible hacerlo en Diseño, la especificación de los objetos suele
realizarse en el modelo de Prototipo. En el modelo de Diseño no se generan programas a continuación de las
especificaciones, y el único motivo por el cual se podría necesitar especificar algún objeto, sería porque se vaya
a seguir trabajando en el modelo de Diseño realizando definiciones, y se necesite corroborar la lógica
interpretada por GeneXus acerca de uno o varios objetos que se hayan definido. De modo que no es necesario,
ni mucho menos obligatorio, estar especificando los objetos en el modelo de Diseño.

En los modelos de Prototipo y Producción, en cambio, lógicamente será necesario especificar y generar los
objetos que hayan sufrido cambios, previamente a la ejecución de la aplicación.

Otras opciones del ítem “Build”

Todas las opciones que se describen a continuación se encuentran disponibles únicamente para modelos de
Prototipo y Producción.

Build / Impact Database


Al seleccionar esta opción, se ejecutará un análisis de impacto para el modelo en el cual se esté trabajando.
Seguidamente se desplegará el reporte de análisis de impacto correspondiente.

Build / Impact From


Un análisis de impacto siempre se efectúa comparando las definiciones del modelo de Diseño con las
definiciones del modelo actual o destino. No obstante, esta opción permite realizar un análisis de impacto
tomando cualquier modelo definido en la base de conocimiento como base, en relación al modelo actual.
Al seleccionar esta opción se desplegará un diálogo para seleccionar cuál de todos los modelos definidos en la
base de conocimiento se desea tomar como base para realizar un análisis de impacto para el modelo actual.

Es fundamental tener bien claras todas las acciones posibles que pueden desencadenarse al efectuar un
análisis de impacto:

-------------------------------------------------------------------------------------------------------------------
1 Existen algunas opciones del menú que permiten hacer esto, con algunas variaciones y son las que se
presentan al final de esta página, bajo el título “Otras opciones del ítem Build”.

69
Sin embargo, puede ocurrir que no se desee ejecutar un análisis de impacto, ni reorganización consecuente, ni
actualización total del modelo actual, sino que solo se deseen copiar algunos de los objetos del modelo de Diseño
al modelo actual.

Build / Impact Objects


Se desplegará la lista de objetos que tengan diferencias en el modelo de Diseño con respecto al modelo actual y
se podrán seleccionar algunos de ellos, para copiarlos al modelo actual.

Importante: Deberá tenerse en cuenta que aunque la lista muestre todos los objetos que tengan diferencias en
el modelo de Diseño con respecto al modelo actual, sólo podrán ser copiados aquellos objetos que usen la misma
estructura de base de datos en ambos modelos. Por ejemplo, una transacción no podrá copiarse al modelo actual
si se le ha agregado un nuevo atributo (en el modelo de Diseño) y aún no se ha efectuado la reorganización
correspondiente. Resulta sencillo de entender que no sea posible solamente copiar una transacción al modelo
actual, si la misma tiene implicada una reorganización pendiente para hacer.
Para cada objeto de la lista que se haya seleccionado e intentado copiar al modelo actual, se informará si fue
posible realizar la copia o no (y en caso negativo, el motivo).

Build / Create Database


Se ejecutará un análisis de impacto para el modelo en el cual se esté trabajando, con la particularidad de que se
analizará el impacto causado por las definiciones del modelo de Diseño, sobre el modelo actual como si estuviera
vacío. En consecuencia el reporte de análisis de impacto informará que se deberán crear todas las tablas con sus
estructuras vacías.

70
Ejecución de las aplicaciones

ÎBuild / Run:

Bajo el ítem Build de la barra de menú de GeneXus, se encuentra la opción Run para ejecutar las aplicaciones
generadas.

Al seleccionar Build/Run o F5, se desplegará el diálogo de ejecución (Execution Dialog) mostrado arriba.

El diálogo de ejecución ofrece todos los programas posibles de ser compilados y ejecutados.
Estos son:

1) Developer Menu: Es un programa que GeneXus genera automáticamente, y el mismo contiene


invocaciones a todos los objetos del modelo, para poder ser ejecutados, y testeados.

2) Objetos que se hayan definido main: Todo objeto puede ser definido main, lo cual significa que será el
principal de un ejecutable.

GeneXus generará un ejecutable que contendrá al objeto mismo y a todos los que éste llame. Esto permitirá
ejecutarlo independientemente (en lugar de ejecutarlo mediante el Developer Menu).

71
Modos de las transacciones
en tiempo de ejecución

• Al ejecutar una transacción se pueden distinguir los siguientes


modos, dependiendo de la operación que se realice:

Modo Insert: Indica que se está efectuando una inserción

Modo Update: Indica que se está efectuando una actualización

Modo Delete: Indica que se está efectuando una eliminación

Modo Display: Indica que se está efectuando una consulta

Dependiendo del ambiente de generación, habrá algunas diferencias en lo que se refiere a la


operativa de las transacciones en tiempo de ejecución. No obstante, más allá de la plataforma, cada
vez que se realice una operación de inserción, actualización, eliminación, o consulta a la base de
datos a través de una transacción, habrá un modo asociado.

72
INTEGRIDAD
REFERENCIAL

73
Diagrama de Bachman

COUNTRY CountryId*
CountryName
1

CUSTOMER CustomerId*
N CustomerName
………
CountryId

El concepto de integridad referencial es un concepto que tiene que ver con las bases de datos
relacionales.

Se refiere a que debe haber consistencia entre los datos de las distintas tablas de una base de datos
relacional.

Las tablas de una base de datos relacional se encuentran relacionadas por atributos que tienen en
común. Estas relaciones implican que los datos de las tablas no son independientes, sino que al
insertar, modificar y eliminar registros en una tabla, se deben tener en cuenta los datos de las otras
tablas para que siempre se conserve la consistencia de la información en la base de datos.

Si tenemos un modelo de datos relacional con las tablas:


COUNTRY (CountryId, CountryName)
Clave Primaria: CountryId

CUSTOMER (CustomerId, CustomerName, CustomerAddress, CustomerGender, CountryId)


Clave Primaria: CustomerId
Clave Foránea: CountryId (COUNTRY)

El hecho de que el atributo CountryId en la tabla CUSTOMER sea una clave foránea con respecto a la
tabla COUNTRY, establece una relación entre ambas tablas. La relación entre ellas puede verse en el
diagrama que mostramos arriba (Diagrama de Bachman).

En el Diagrama de Bachman, la flecha simple representa la existencia de una instancia de la tabla


apuntada, para cada instancia de la otra (es decir, que para cada cliente existe un y solo un país). La
flecha doble representa la ocurrencia de varias instancias de la tabla apuntada, para cada instancia de
la otra tabla (es decir, que para cada país, existen muchos clientes).

Se dice que la relación entre la tabla COUNTRY y la tabla CUSTOMER es 1 a N (1 a muchos).

Recíprocamente, la relación entre CUSTOMER y COUNTRY es N a 1 (muchos a 1).

74
Diagrama de Bachman

COUNTRY Hay que verificar existencia del COUNTRY referenciado.

1
En transacción “Customer”:
CUSTOMER • si se inserta nuevo registro, o
N • si se modifica el CountryId de un registro

COUNTRY En transacción “Country”:


• si se quiere eliminar un registro
1

CUSTOMER Hay que verificar la no existencia de


N ningún CUSTOMER que lo referencie.

En la terminología GeneXus, decimos que existe una relación de subordinación entre ambas tablas.
Decimos que:

COUNTRY está superordinada a CUSTOMER


CUSTOMER está subordinada a COUNTRY
Significando que:

• Cuando se crea o modifica un registro en la tabla subordinada (CUSTOMER), debe existir el


registro relacionado en la tabla superordinada (COUNTRY).

• Cuando se elimina un registro en la tabla superordinada (COUNTRY), no deben existir registros


relacionados en la tabla subordinada (CUSTOMER).

Debido a esta relación entre las tablas, la información contenida en ellas no es independiente, y es
necesario realizar controles para que los datos sean consistentes. A estos controles se les llama de
Integridad Referencial y básicamente son los siguientes:

• Cuando se inserta o modifica un registro en la tabla CUSTOMER, el valor ingresado en el atributo


que es clave foránea (CountryId), debe existir como valor de clave primaria de un registro en la
tabla COUNTRY.

• Cuando se elimina un registro en la tabla COUNTRY, no deben existir registros en la tabla


CUSTOMER cuyos valores de la clave foránea (CountryId), sean iguales al valor de la clave primaria
del registro que se desea eliminar.

GeneXus genera los programas asociados a las transacciones, incluyendo en el código generado
estos controles de Integridad Referencial. Por esta razón, si el usuario final inserta (o modifica) un
cliente a través de la transacción "Customer", se validará automáticamente que el valor ingresado en
el código de país CountryId, exista como clave primaria de un registro en la tabla COUNTRY. En caso
de fallar este control de integridad referencial, un mensaje se le desplegará al usuario indicándole
que no se encontró ese país.

75
Índices

Los índices son vías de acceso eficientes a las tablas.

GeneXus crea automáticamente algunos de ellos, y los otros deberán ser creados por el
programador cuando éste así lo determine, basándose en criterios de optimización.
Existen cuatro tipos de índices en GeneXus:

• Primarios
• Foráneos
• De usuario
• Temporales
De todos ellos, los únicos que no son creados automáticamente por GeneXus son los “de usuario”.

En cuanto a los tipos de índices que son creados por GeneXus, la diferencia que hay entre ellos es
el momento en que son creados y el tiempo durante el cual se mantienen.

- Los índices primarios y foráneos son creados al momento de crear o reorganizar las
tablas que componen la base de datos, y de ahí en más son mantenidos
automáticamente por GeneXus.

- Los índices temporales, en cambio, son creados al ejecutar las aplicaciones, para
acceder a tablas ordenadas por algún atributo o conjunto de atributos para el/los que no
existe un índice de usuario creado; éstos se crean en tiempo de ejecución de las
aplicaciones, se utilizan, y luego se eliminan.

ÍNDICES PRIMARIOS Y FORÁNEOS

GeneXus crea para cada tabla un índice por su clave primaria (índice primario), y un índice por
cada clave foránea que la tabla tenga (índices foráneos). ¿Por qué crear índices primarios y
foráneos para las tablas desde el comienzo en forma automática, siendo que luego deben ser
mantenidos?

Sean las tablas COUNTRY y CUSTOMER, que vimos un par de páginas atrás, creadas en nuestro
modelo de datos a partir de las estructuras de las transacciones "Country" y "Customer”. Existe
entre estas tablas una relación 1-N, que viene dada por el hecho de que el atributo CountryId en
la tabla CUSTOMER es una clave foránea con respecto a la tabla COUNTRY.

76
Índices primarios y foráneos
PK PK
CountryId* ICountry CustomerId* ICustomer
CountryName CustomerName
...
FK ICustomer1
CountryId

CountryId CountryName CustomerId CustomerName CountryId

1 Uruguay 4 Ana Diez 1


2 United States 1 Juan Pérez 1
3 China 3 María Donoso 1
2 Jessica Deep 2

Si en transacción “Country” El programa debe buscar sobre CUSTOMER


queremos eliminar si ∃ registro con CountryId = 2
“United States”: Æ para hacer eficiente esta búsqueda:
índice foráneo (ICustomer1)

Por existir esta relación, GeneXus incluye en los programas asociados a las transacciones
"Country" y "Customer", los controles de integridad referencial pertinentes. Estos controles son:
• Si el usuario final inserta o modifica un cliente a través de la transacción "Customer", se
validará automáticamente que el valor ingresado en la clave foránea CountryId exista como clave
primaria de un registro en la tabla COUNTRY. En caso de fallar este control de integridad
referencial, se le indicará al usuario que no se encontró ese país.
Para controlar esto, se debe buscar en la tabla COUNTRY la existencia de un registro que tenga
ese valor de CountryId como clave primaria; dado que se debe consultar la tabla COUNTRY,
buscando por la clave primaria, resulta evidente que la búsqueda puede optimizarse si existe un
índice por la clave primaria en dicha tabla.
• Si el usuario final intenta dar de baja un país a través de la transacción “Country”, se validará
automáticamente que no existan clientes con dicho país asociado, como clave foránea; en caso de
encontrar un registro en la tabla CUSTOMER, cuyo valor de clave foránea CountryId sea el que se
desea eliminar, se le indicará al usuario que no es posible eliminar el país (ya que de lo contrario
quedarían datos inconsistentes en la base de datos).
Para controlar esto se debe consultar la tabla CUSTOMER, buscando por la clave foránea
CountryId. Esta búsqueda será óptima si existe un índice por CountryId en la misma.

Control de unicidad de clave primaria


Otro control que GeneXus también incluye en los programas asociados a las transacciones es la
unicidad de la clave primaria; esto es, en ninguna tabla podrán existir dos registros con el mismo
valor en la clave primaria.
Para controlar esto, cuando el usuario final intenta insertar un registro se valida automáticamente
que el valor ingresado para la clave primaria no exista ya como clave primaria de otro registro en
la tabla.
Para hacer esta búsqueda con eficiencia, se debe utilizar el índice primario de la tabla.
Concluyendo, GeneXus al crear cada tabla de la base de datos crea también su índice primario, y
un índice foráneo por cada clave foránea que la tabla contenga.
La creación de estos índices permite realizar los controles de integridad referencial y de
unicidad de clave primaria accediendo a las tablas de forma eficiente.

77
Índices de usuario
• Los crea el analista sobre una tabla. Deberá
categorizarlos según si aceptará valores repetidos
(duplicate) o no (unique).

• Si en la realidad modelada, un mismo nombre se puede repetir


para dos clientes:
CustomerId CustomerName CountryId
1 Ana 1
2 Ana 2
3 María 1

ÍNDICES DE USUARIO

Estos índices deben ser definidos explícitamente por el analista. No son definidos
automáticamente. Se dividen en duplicate y unique.

Un índice de usuario duplicate es aquel que se define para atributos de una tabla para los que
pueden existir varios registros con el mismo valor en los mismos (es decir, se define para
atributos que no son una clave candidata).
Este tipo de índices se define fundamentalmente para acceder a los datos ordenados por
determinados atributos de forma eficiente.
A modo de ejemplo, suponiendo que el nombre de cliente no es clave en la tabla CUSTOMER (sus
valores se pueden repetir) podremos definir un índice de usuario duplicate por el atributo
CustomerName, siendo muy útil para realizar consultas y listados que se necesite salgan
ordenados por nombre.

Un índice de usuario unique se utiliza para especificar que un conjunto de atributos es clave
candidata en una tabla (diferente de la clave primaria).
Esta es la forma de representar claves candidatas en el modelo de datos. Con ello logramos que
GeneXus incorpore automáticamente el control de unicidad correspondiente en las transacciones
asociadas.
A modo de ejemplo, si el nombre de cliente no se puede repetir, la forma de representarlo y lograr
que GeneXus lo controle automáticamente es definiendo en la tabla CUSTOMER un índice de
usuario unique por el atributo CustomerName.

78
Índices de usuario
• Si un mismo nombre no puede repetirse (es por tanto un
atributo clave de la tabla)

CustomerId CustomerName CountryId


1 Ana Diez 1
2 Ana Pérez 2
3 María Rua 1

• La forma de definir claves candidatas en el modelo de datos es


a través de índices unique. GeneXus pasará a incorporar
controles en las transacciones, utilizando el índice, para no
permitir la inserción de registros duplicados.
Si se intenta ingresar un nuevo cliente con nombre “Ana Pérez”
la transacción dará un error de registro duplicado.

Índices unique (claves candidatas)

En una tabla de la base de datos pueden existir varios conjuntos de atributos cuyos valores sean
únicos en la realidad. Se dice que cada uno de esos conjuntos es una clave de la tabla. Luego, el
analista elige una de las claves como la clave primaria.
GeneXus identifica la clave primaria de la tabla de acuerdo a los atributos que fueron calificados
por el analista con el símbolo de llave.

Supongamos que en la realidad además de poder identificar a un cliente por su código, también se
lo puede identificar por su cédula de identidad. En este caso tanto el atributo CustomerId como el
atributo CustomerSSN (donde se almacena la cédula de identidad) serían claves de la tabla
CUSTOMER. Al indicar que CustomerId es el identificador de la transacción, GeneXus creará
automáticamente un índice primario por dicho atributo y controlará la unicidad de los valores
ingresados para el mismo.

¿Y qué sucederá con la cédula de identidad del cliente? Al ser este atributo clave, quisiéramos que
GeneXus obrara de igual manera, no permitiendo que se ingrese un registro, si es que ya existe
otro con el mismo valor de cédula de identidad (CustomerSSN). Para poder controlar esto de
forma eficiente, GeneXus debería contar con un índice por cada atributo clave.

La forma de definir en GeneXus que un atributo o conjunto de atributos es clave alternativa o


candidata y que por lo tanto se debe chequear su unicidad, es definiendo un índice de usuario
compuesto por ese atributo o conjunto de atributos, y calificándolo de “unique” en lugar de
“duplicate”, que es el valor por defecto de un índice de usuario.

A partir de allí, GeneXus incluirá en la lógica de la transacción ese control de unicidad utilizando
ese índice definido por el usuario.

En resumen, las transacciones GeneXus realizan automáticamente los siguientes


controles:
• Integridad referencial
• Unicidad de clave (tanto primaria como candidatas)

79
Índices temporales
• Son creados automáticamente, bajo ciertas condiciones,
cuando son necesarios, y se eliminan cuando termina la
ejecución del objeto que los creó.

• Si se desea acceder a los datos ordenados por determinados


atributos, pero no se desea crear un índice permanente para
ello: GeneXus creará un índice temporal.

• Se crean solamente en algunas plataformas (como Visual FoxPro


con base de datos local, o en iSeries).

• En otras plataformas, las consultas para las cuales se quiere


obtener el resultado ordenado por determinados atributos, y no
existe el índice de usuario, son resueltas por el DBMS
correspondiente sin la creación de índices temporales.

• El usuario puede resolver dejar de utilizar un índice temporal,


creando un índice de usuario.

ÍNDICES TEMPORALES

Si se desea acceder a los datos ordenados por determinados atributos, pero no se desea crear un
índice permanente para ello (por ejemplo, porque se trata de una consulta que se realiza con muy
poca frecuencia), entonces, dependiendo de la plataforma, se creará un índice temporal.

80
Manejo de nulos

• Para cada atributo no inferido, ni identificador en la


estructura de una transacción, es posible definir si admitirá
nulos o no, en la tabla asociada

La permisión o no del valor <NULL> en la base de datos es muy importante en el modelo relacional.
Permitir el “valor” null para un atributo dado, significa que puede, bajo ciertas circunstancias, ser
“ignorado” dado que es “valor no especificado”. Por otro lado, si un atributo no permite valor null,
un valor válido siempre deberá asignarse a él.

La propiedad Nulls (presentada como columna en el editor de estructuras de transacciones),


permite configurar para cada atributo si admite o no valor null en la tabla asociada. Los atributos
para los cuales puede configurarse esto, es para aquellos almacenados en la(s) tabla(s) asociada(s)
a la transacción (es decir, no inferidos) siempre y cuando no sean atributos primarios en dichas
tablas (ya que por definición las claves primarias no soportan valor null).

Resumiendo: el objetivo de esta propiedad es definir qué valor se va a almacenar en la base de


datos cuando no digitemos nada en el campo (el valor <NULL> o el valor empty).

81
Manejo de nulos

• Valores posibles que ofrece la propiedad Nulls:


- No: el atributo en la tabla asociada, no permitirá valor null
- Yes: el atributo en la tabla asociada, sí admitirá valor null
Valor por defecto

• Atributos parte de la clave primaria no soportan


valor null (propiedad Nulls = No, siempre)

• Atributos clave foránea sí, y esto tendrá repercusión


en los controles de integridad referencial.

Los valores posibles de configurar para la propiedad Nulls son:

No: significa que el atributo no permitirá el valor null en la tabla asociada (valor por defecto)
Yes: significa que el atributo sí admitirá el valor null en la tabla asociada

La definición de nulls es utilizada por GeneXus al momento de crear / reorganizar las tablas de la
base de datos, ya que el soporte o no soporte de nulabilidad de los atributos en su tabla,
se define a nivel de la base de datos.

O sea que modificar el valor de la propiedad Nulls para un atributo implicará ejecutar una
reorganización (para redefinir a nivel de la base de datos el soporte de nulabilidad del atributo en su
tabla).

82
Manejo de nulos

• Repercusión en controles de integridad referencial

CountryId* CountryId* CustomerId*


CountryName CityId* CustomerName
CityName FK
CountryId
compuesta CityId

1) CountryId y CityId con propiedad Nulls=No

• Se realizan los chequeos de IR mostrados arriba

2) CityId con propiedad Nulls=Yes

• Se agrega un control de IR en CUSTOMER Æ si se deja nulo CityId, se


realizará chequeo contra COUNTRY.

Repercusión en controles de integridad referencial

La definición de nulls para atributos que conforman una clave foránea le dice a GeneXus cuán
fuerte es la referencia a la otra tabla.

Si ninguno de los atributos que componen una clave foránea permiten valores nulos (caso 1),
se tratará de una referencia fuerte (también conocida como “not null reference”), ya que
establece que la FK deberá siempre apuntar a un registro existente de la tabla referenciada.

Por el contrario, una clave foránea que tenga al menos un atributo que soporte nulos (caso 2),
establece una referencia débil (también conocida como “null reference”), ya que si alguno de
los atributos que conforman la clave foránea son nulls, entonces la referencia no será
chequeada.

Cuando una clave foránea es compuesta y están permitidos los nulos para algunos de sus
atributos, pueden aparecer nuevas referencias (no chequeadas en caso de tratarse de
referencias fuertes) si los atributos que restan componen también una clave foránea. Un
ejemplo es el que presentamos arriba, donde en caso de que el usuario deje nulo el valor de
CityId para un cliente, se realizará el chequeo de IR contra COUNTRY, para asegurarse que el
país ingresado sea correcto.

83
Manejo de nulos

• Diferencia entre valor empty y null para atributos:

• empty: es un valor (0 para Numeric, “” para Character, etc.)


• null: si está permitido, no es un valor. Significa que el valor debe
ser considerado como:
• no especificado
• no disponible
• no asignado
• desconocido

• Métodos según si atributo permite null o empty:


• IsEmpty
• IsNull
• SetEmpty
• SetNull

Métodos para trabajar con nulos y vacíos


IsEmpty, IsNull: devuelven true en caso de que el atributo contenga valor empty o null
respectivamente.

SetEmpty, SetNull: configuran en el atributo el valor empty o null, respectivamente.

Ejemplos:

* error(‘You must specify a name’) if CustomerName.IsEmpty();

* msg(‘Warning: You didn’t specify a Country’) if ContryId.IsNull();

84
TABLA BASE
Y
TABLA EXTENDIDA

85
Tabla Base y Tabla Extendida

Definición

Dada una tabla X de la base de datos, a la cual llamamos “tabla


base”, se denomina “tabla extendida” de la misma al conjunto de
atributos conformado por:

• Atributos que pertenecen a la tabla X.


• Atributos de toda tabla Y, tal que la relación entre la tabla
extendida determinada hasta el momento y la tabla Y sea
N-1.

Los criterios de normalización del diseño de la base de datos apuntan a minimizar la posibilidad de
inconsistencia en los datos. Una base de datos diseñada de esta manera tiene una serie de ventajas
importantes (tal es así que actualmente la normalización de datos es un estándar de diseño), pero
se deben tener en cuenta también algunos inconvenientes.

El inconveniente más notorio es que los datos se encuentran dispersos en muchas tablas, y por lo
tanto cuando se quieren hacer consultas más o menos complejas a la base de datos, se debe
consultar una cantidad importante de tablas.

Así, por ejemplo, si el siguiente Diagrama de Bachman representa nuestro modelo de datos:

para listar las facturas sería necesario consultar las tablas: INVOICE e INVOICELINE (líneas de
Facturas), CUSTOMER, COUNTRY y PRODUCT.
Para simplificar esta tarea GeneXus utiliza el concepto de tabla extendida.

Llamamos tabla base a cualquier tabla de la base de datos en la cual estemos posicionados en
determinado momento; y dada cierta tabla base, su tabla extendida comprenderá a todos los
atributos de la propia tabla base, más todos los atributos de las tablas que tengan información
relacionada unívocamente con la tabla base (relación N-1 desde la tabla base, directa e
indirectamente).

86
Tabla Base y Tabla Extendida

INVOICE CUSTOMER COUNTRY


InvoiceId* CustomerId* CountryId*
InvoiceDate CustomerName CountryName
CustomerId CountryId

INVOICELINE PRODUCT
InvoiceId* ProductId*
ProductId* ProductDescription
InvoiceLineQuantity ProductPrice
InvoiceLineAmount ProductStock

Utilizando el Diagrama de Bachman es fácil determinar cuál es la tabla extendida correspondiente


a una tabla base cualquiera:
Partiendo de la tabla base, se deben seguir las relaciones N-1, (es decir, se deben seguir las flechas
que tienen punta doble partiendo desde la tabla base, y punta simple en el otro extremo).
Todas las tablas a las cuales se pueda llegar siguiendo las flechas que representan relaciones N-1
desde la tabla base, formarán parte de su tabla extendida.

87
Tabla Base y Tabla Extendida

Para cada tabla de nuestro modelo de datos, describimos cuál es su tabla extendida.

88
Descripciones en vez de Códigos

Country Transaction
CountryID*
CountryName

Customer Transaction
CustomerID*
CustomerName
CountryID
CountryName
Este atributo no es CountryName, sino
CountryId disfrazado de CountryName.
Se muestra (y acepta) en ejecución el valor de
CountryName, mientras que lo que se almacena
es CountryId.

Hasta ahora hemos visto que el usuario final debe manipular los códigos con los que el sistema identifica
a sus entidades. Por ejemplo, a la hora de ingresar un nuevo cliente a un sistema, vimos que el usuario
final ingresa el código del país de ese cliente y a continuación se le muestra en forma inferida, el nombre
correspondiente a ese país.

Para ello debe memorizar un código que no tiene demasiada carga semántica (ninguna cuando éstos son
numéricos) o apelar a prompts para poder buscar por descripción o nombre y recuperar así el código a ser
colocado en el campo.

Sin embargo no es necesario condenar al usuario a que manipule códigos. Puede manipular las
descripciones, que son las que contienen la semántica y el programa se encargará del resto. Es posible
(tanto en Win como Web, Java y .Net) que el usuario trabaje en pantalla con la descripción,
CountryName, y que automáticamente se resuelva la manipulación del código, CountryId.

¿Cómo? Modificando el valor de la propiedad InputType del atributo CountryId.

El usuario verá en pantalla el CountryName, pero el atributo que muestra esos valores ¡no es
CountryName! Por el contrario, es el atributo: CountryId “disfrazado” de CountryName.

Mientras el usuario escribe “Uruguay” internamente se realiza una búsqueda en la tabla COUNTRY para
recuperar el código correspondiente a ese nombre. Ese valor, en nuestro ejemplo, 1, es el que realmente
queda almacenado allí. Pero para el usuario esto es absolutamente transparente.

Para que esto pueda lograrse, deberá haber una relación biunívoca entre el código (o identificador) y la
descripción. Es decir, para cada descripción solo existirá un código asociado. En el ejemplo, no deberá
haber dos o más países con el mismo nombre y distinto identificador, porque en ese caso no es posible
realizar el matcheo.

Dicho en otras palabras: el atributo descriptivo deberá ser una clave candidata de la tabla, es decir,
deberá existir un índice unique para ese atributo.

En caso de no existir, en el listado de navegación de la transacción se mostrará la siguiente advertencia:

Spc0107 Candidate Key CountryName for CountryId may have duplicated values.

y en ejecución, si el usuario selecciona un valor duplicado (para el que existen varios CountryId) dará un
error “Country is ambiguous”, dado que no sabrá con cuál quedarse.

89
Descripciones en vez de Códigos
Propiedad InputType del atributo identificador

Valores posibles:

• Values
atributo real: lo que
muestra es su contenido

• Descriptions
atributo disfrazado: toma
las descripciones de
otro atributo, aunque su
contenido sigue siendo el
propio.

En la figura se muestra el diálogo de definición del atributo CountryId.

Como podemos ver, en la solapa Control Info del diálogo de definición del atributo, aparece la
propiedad InputType. Esta propiedad solo se ofrece cuando el atributo está asociando a un
control de tipo Edit (no Radio Button, Combo, etc.).

Esta propiedad puede tomar uno de dos valores:

• Values: representa el comportamiento conocido, es decir, el control en el form en ejecución


mostrará los valores que contiene (códigos).

• Descriptions: aquí es donde le indicamos que queremos que “disfrace” sus valores en ejecución,
mostrándole al usuario no sus valores reales, sino los asociados y definidos en la propiedad que se
habilita: Descriptions from.

En la propiedad Descriptions from se debe indicar de qué atributo se tomarán las descripciones.
Evidentemente este atributo no puede ser cualquiera: debe tener una relación biunívoca con el que
estamos definiendo, y además, debe ser un atributo de descripción, es decir, un atributo cuyo
tipo de datos sea Character o Varchar.

De lo contrario se arrojará el siguiente error de especificación:


spc0112: Item value %1 for control %2 must be Character or VarChar.

90
Como en casi todos los casos, cuando una propiedad tiene que ver con la forma en que un atributo
funciona a nivel de pantalla (control), existe la posibilidad de configurar su comportamiento:

1- En forma centralizada: Es decir, a nivel de la definición del atributo en sí (en la solapa Control Info
asociada a la definición del atributo), para que aplique por defecto en todos los forms en donde el atributo
esté presente como control

2- En forma particular: Es decir, para un control atributo en concreto de un form determinado (en la
solapa Control Info asociada a la definición del control).

Definición en forma centralizada (a nivel del atributo)

En el caso de configurar la propiedad IntputType a nivel de atributo, en toda transacción en la que este
atributo sea clave foránea (FK), GeneXus colocará en el form de la transacción, el valor de la propiedad
Title, del atributo elegido como descriptor (en este caso el title de CountryName) y a su lado al
atributo identificador (en este caso la FK CountryId), que mostrará los valores de CountryName, pero
almacenará los de CountryId.

Como resulta evidente, ya no es necesario que en el form se coloque el atributo real CountryName, porque
el atributo CountryId “se disfrazó” de él. El usuario no sabrá que mientras él está seleccionando o
escribiendo un nombre de país, en realidad lo que se está grabando en ese atributo es el código o
identificador correspondiente.

GeneXus quita automáticamente del form el atributo descriptor al momento en que la FK es definida con
InputType= Description.

Sin embargo, el atributo descriptor debe estar presente en la estructura de la transacción, pues de lo
contrario dará el siguiente error al especificar:

Spc0109 CountryName is (part of) a candidate key. It must be referenced in the transaction’s
structure.

En la transacción en la que el atributo identificador con InputType=“Description” es clave primaria, como


resulta evidente, no toma ningún efecto esta propiedad. Es decir, en la transacción “Country” el atributo
CountryId se verá en ejecución conteniendo códigos, y el atributo CountryName esperará ser ingresado con
datos del usuario.

91
Descripciones con autocomplete

Propiedad Suggest

Podemos hacer que en lugar de que


el usuario tenga que recordar los
valores existentes para ingresar uno
correcto, se le sugieran los valores
posibles para ese control, de modo
que él pueda elegir el que deseaba y
que será necesariamente correcto.

Aquí estamos combinando 2 propiedades: InputType y Suggest, lo


que no es obligatorio.

La propiedad Suggest posibilita brindarle una ayuda útil al usuario final, para que elija en tiempo de
ejcución para un control edit, entre un conjunto de valores posibles y no tenga que recordar
“exactamente” el valor.

Al igual que la propiedad InputType, aplica a controles edit y puede ser usada tanto en transacciones,
como en Web y Work panels, objetos de los que hablaremos más adelante.

Habilitando la propiedad Suggest en un control edit, se conseguirá que una lista de posibles valores
para ese control sea desplegada al usuario, debajo del control.

La lista de sugerencias puede ser calculada de un modo incremental (la lista será actualizada con las
entradas del usuario) o haciendo pedidos explícitos (el usuario manualmente tendrá que hacer un
pedido para que se calculen las sugerencias y se le muestren). La actualización de la lista es
asincrónica y los tiempos de cálculo dependen de la calidad de la conexión.

Esta propiedad es independiente de la propiedad InputType, aunque la utilización combinada de


ambas producirá aplicaciones con interfaces mucho más amigables para el usuario.

92
Descripciones con autocomplete
Propiedad Suggest

El control para CountryId


disfrazará sus valores por
los de CountryName, que
ahora le serán sugeridos al
usuario a medida que vaya
digitando letras del nombre

Valores posibles:
• Incremental
• No
• OnRequest

Si miramos un par de páginas atrás, cuando vimos la propiedad InputType, teníamos la misma
pantalla de propiedades para el atributo CountryId, solo que el valor que teníamos especificado en la
propiedad Suggest era el valor por defecto: “No”. En ese caso el usuario tenía que digitar el nombre
del país sin recibir ayuda alguna. Si se cambia el valor de esta propiedad a “Incremental” u
“OnRequest” se brindará ayuda al usuario para que elija en tiempo de ejecución entre los valores
posibles y no tenga que recordar “exactamente” el valor.

Valores de la propiedad Suggest:

•Incremental: Se le irán sugiriendo al usuario los valores que correspondan con lo que haya
digitado hasta el momento. Es incremental, puesto que va reduciendo el rango de lo mostrado
interactivamente a medida que el usuario va agregando letras a lo que lleva digitado hasta el
momento.

•OnRequest: La única diferencia que tiene el valor Incremental es que en este caso no es
interactivo. El usuario ingresará el substring que recuerde de la palabra y luego pedirá explícitamente
las sugerencias de las palabras que empiecen por o contengan esa cadena. Por ejemplo, si el usuario
final sabe que un cliente que está buscando tiene apellido Pérez, pero no recuerda el nombre, puede
escribir “Pérez” y pedir las sugerencias.
Si tuviera configurado el valor “Incremental” en esta propiedad, entonces al digitar “P” de “Pérez” ya
se le traerían todos los nombres de clientes que empiezan con “P” y se iría afinando la búsqueda a
medida que siga escribiendo letras.

•No: no sugiere nada. Este es el valor por defecto, y en este caso el usuario no recibirá ningún tipo
de ayuda para ingresar el valor en el campo.

Para lograr que el usuario trabaje con descripciones en lugar de códigos, otra posibilidad es
configurar que el atributo CountryId utilice el control: combo box dinámico (indicándose también en
este caso, que se muestren los nombres de los países existentes en la tabla COUNTRY
(CountryName), mientras que al momento de seleccionar uno en el combo, quedará el código
correspondiente en el atributo CountryId.

93
Reglas
• Se utilizan para definir el comportamiento de las transacciones.
• Algunas reglas:
ƒDefault
ƒAsignación
ƒMsg
ƒError
ƒNoaccept
ƒAdd
ƒSubtract
ƒSerial
ƒUpdate

• Pueden incluir: atributos, variables, constantes y funciones.


• Son LOCALES a la transacción.
• Programación DECLARATIVA.

Las reglas, en el objeto transacción, cumplen un rol muy importante ya que permiten programar su
comportamiento (por ejemplo: asignar valores por defecto, definir controles sobre los datos, etc.).

Se escriben de forma declarativa, es decir que el orden en el que se escriben no significa que sea el
orden en el que se ejecutarán.

Pueden involucrar a los atributos definidos en la estructura de la transacción1, así como a


variables definidas dentro del objeto, constantes y funciones.

Son solo válidas dentro de la transacción en la que están definidas, es decir, son locales.

--------------------------------------------------------------------------------------------------------
1 Todas las reglas de transacciones pueden involucrar atributos de las tablas base asociadas a la

transacción; y la mayor parte de ellas puede también involucrar atributos de las tablas extendidas de
dichas tablas base. Para poder referenciar un atributo en una regla, el mismo deberá estar incluido en
la estructura de la transacción (ya sea que pertenezca a alguna de las tablas base asociadas a la
transacción, o a sus tablas extendidas).

94
Algunas reglas válidas para transacciones son:

Default
OBJETIVO: Permite asignar un valor por defecto a un atributo o variable; el valor por defecto inicializará al atributo o
variable si se está realizando una inserción por medio de la transacción (modo Insert), pero el usuario final podrá
cambiarlo si ese valor no es el que desea.

SINTAXIS: Default( att | &var, exp );

DONDE:
• att: es un atributo perteneciente a alguna de las tablas base asociadas a la transacción.
• var: es el nombre de una variable.
• exp: es una expresión que puede involucrar constantes, funciones, variables u otros atributos.

FUNCIONALIDAD: Esta regla asigna el valor de la expresión exp como valor por defecto del atributo att o variable var,
cuando la transacción se ejecuta en modo Insert.

Esta regla no es válida para atributos que forman parte de la clave primaria de alguno de los niveles de la transacción,
porque es disparada luego de que la clave es ingresada (ya que solo en ese momento el modo es conocido y esta
regla se dispara solo en modo Insert).

EJEMPLOS:
Default( InvoiceDate, &today ); /*Regla definida en la transacción “Invoice”*/

Cuando se está insertando una factura nueva, se sugiere como valor de InvoiceDate el valor contenido en la variable
del sistema today.

Default( InvoiceDate, Today()); /*Regla definida en la transacción “Invoice”*/

Análoga a la anterior, solo que en lugar de utilizar la variable del sistema today, se utiliza la función Today que
devuelve la fecha correspondiente al día.

Default( InvoiceLineAmount, ProductPrice*InvoiceLineQuantity); /* Regla definida en “Invoice” */

Cuando se está insertando una línea de factura, se sugiere que el atributo InvoiceLineAmount (importe de la línea)
tome el valor resultante de evaluar la expresión ProductPrice*InvoiceLineQuantity (precio del producto por cantidad
llevada del mismo).

Nota: El tipo de datos de la expresión debe coincidir con el tipo de datos del atributo o variable

Regla de asignación

OBJETIVO: Permite asignar a un atributo o a una variable, el valor de una expresión.

SINTAXIS: att | &var = exp [if cond] [on evento/momento de disparo];

DONDE:
• att: es un atributo perteneciente a alguna de las tablas base asociadas a la transacción, o a sus tablas extendidas
(debe estar declarado en la estructura).
• var: es el nombre de una variable.
• exp: es una expresión que puede involucrar constantes, funciones, variables u otros atributos, y debe ser del mismo
tipo que att o var.
• cond: es una expresión booleana (puede contener los operadores lógicos and, or, not)
• evento/momento de disparo: es uno de los eventos predefinidos de GeneXus disponibles para reglas de
transacciones, que permiten definir el momento específico de ejecución de una regla.

FUNCIONALIDAD: Una regla de este tipo permite asignar al atributo o variable de la izquierda, el valor resultante de
evaluar la expresión. Como puede verse, la condición es opcional; de no ponerla, la asignación se realiza siempre.

La asignación a un atributo, implica su actualización en el registro que corresponda. Se pueden definir reglas de
asignación a atributos de alguna de las tablas bases asociadas a la transacción, e incluso de sus tablas extendidas.
Esto significa que pueden actualizarse atributos inferidos en una transacción (siendo necesario declararlos en la
estructura).

EJEMPLO:

InvoiceLineAmount = ProductPrice*InvoiceLineQuantity; /*Regla definida en la transacción “Invoice”*/

Si el importe de cada línea de una factura se calcula siempre multiplicando el precio del producto por la cantidad
llevada del mismo, podemos utilizar esta regla de asignación para liberar al usuario de realizar el cálculo.

95
Error

OBJETIVO: Permite desplegar un mensaje de error si la condición se satisface. Sirve para definir los controles que deben
cumplir los datos.

SINTAXIS: Error( ‘msg’ | &var | character expresion, msgId ) if cond [on evento/momento de disparo];

DONDE:
• msg: es un string con un mensaje de error a desplegar.
• var: es el nombre de una variable de tipo character, que contiene un string con un mensaje de error a desplegar.
• character expression: es una expresión cuyo tipo resultante es character y que será desplegada.
• msgId: es un string (sin espacios en blanco ni comillas) que será utilizado solo si la transacción es definida también
como business component1.
• cond: es una expresión booleana (que puede contener los operadores lógicos and, or, not)
• evento/momento de disparo: es uno de los eventos predefinidos de GeneXus disponibles para reglas de transacciones,
que permiten definir el momento específico de ejecución de una regla.

FUNCIONALIDAD: Esta regla despliega el mensaje del parámetro msg, var o character expresion, si la condición cond que
se evalúa resulta verdadera. El mensaje de error se despliega en una ventana popup cuando el ambiente de trabajo es
Windows y en el control Error Viewer y/o un cuadro de texto cuando el ambiente de trabajo es Web, deteniendo
cualquier actualización a la base de datos. Cuando la transacción se ejecuta como business component1, de
dispararse el error, generará una entrada en el SDT messages, con identificador msgId.

EJEMPLOS:

Error(‘No se permiten clientes sin nombre’) if CustomerName.isEmpty();


/*Regla definida en la transacción “Customer”*/

Se evalúa la condición CustomerName.isEmpty(), y si ésta se satisface, se despliega el mensaje ‘No se permiten clientes
sin nombre’ en pantalla. No se permite continuar hasta que el usuario ingrese un nombre en el campo CustomerName o
abandone la transacción (en cuyo caso no se hará en la base de datos actualización alguna).

Error(‘No se permite eliminar facturas’) if Delete;


/* Regla definida en la transacción “Invoice” */

Se necesita prohibir la eliminación de facturas. Con esta regla, si el usuario intenta realizar una eliminación, la condición
dará True y se disparará la regla, evitando la eliminación.

Msg

OBJETIVO: Permite desplegar un mensaje de advertencia si la condición se satisface.

SINTAXIS: Msg( ‘msg’ | &var | character expresion, msgId) if cond [on evento/momento de disparo];

DONDE:
msg, var, character expresion, msgId, cond, evento/momento de disparo: son los mismos que para la regla error.
Observar que la sintaxis es exactamente la misma.

FUNCIONALIDAD: Esta regla se utiliza para presentar mensajes de advertencia al usuario. Despliega el mensaje del
primer parámetro, si la condición se satisface, análogamente a la regla Error; pero a diferencia de esta última, permite
continuar con la ejecución si la condición sigue satisfaciéndose. Del mismo modo, si la transacción es business
component1, de dispararse la regla genera entrada en el SDT messages.

Los mensajes de advertencia se despliegan en una ventana popup en ambiente Windows y en el Error Viewer o cuadro de
texto en ambiente Web.

EJEMPLO:

Msg( ‘No se permiten clientes sin nombre’ ) if CustomerName.isEmpty();


/*Regla definida en la transacción “Customer”*/

Se evalúa la condición CustomerName.isEmpty(), y si ésta se satisface se despliega el mensaje ‘No se permiten clientes
sin nombre’. A diferencia de lo que ocurre con la regla Error, aquí sí se permite continuar la ejecución, pues no se trata de
un error sino de una advertencia.

--------------------------------------------------------------------------------------------------------------------------------
1 Estudiaremos este tema más adelante en el curso.

96
Noaccept
OBJETIVO: Permite indicar que los atributos no aceptarán valores por parte del usuario (serán solo de salida).

SINTAXIS: Noaccept( att1[, atti]…) [if cond];

DONDE:
atti: es un atributo perteneciente a alguna de las tablas bases asociadas a la transacción.
cond: es una expresión booleana (puede contener los operadores lógicos and, or, not).
evento/momento de disparo: es uno de los eventos predefinidos de GeneXus disponibles para reglas de transacciones,
que permiten definir el momento específico de ejecución de una regla.

FUNCIONALIDAD: En una transacción, todos los atributos que pertenecen a las tablas base asociadas a la transacción, por
defecto son aceptados. Si queremos que algunos atributos con estas características no sean aceptados, entonces
contamos con la regla Noaccept.

EJEMPLO:
Noaccept( IvoiceDate) if Update; /* Regla definida en la transacción “Invoice” */

Si se está modificando una factura (modo Update), no se permite que se modifique su fecha.

Subtract
OBJETIVO: Sustrae el valor de un atributo al valor de otro atributo, si se satisface la condición especificada.

SINTAXIS: subtract(att1, att2) [if cond];

DONDE:
att1, att2: son atributos pertenecientes a alguna de las tablas base asociadas a la transacción, o a sus tablas extendidas
(y deben estar declarados en la estructura)
cond: es una expresión booleana (que puede contener los operadores lógicos and, or, not).

FUNCIONALIDAD: La sustracción se realiza teniendo en cuenta el modo en el que se esté trabajando en la transacción
(Insert, Update o Delete).
En modo:
- Insert: se le sustrae al valor del atributo att2, el valor del atributo att1
- Delete: se le suma al valor de att2, el valor del atributo att1
- Update: se le sustrae al valor del atributo att2, la diferencia entre el valor nuevo y el viejo de att1

EJEMPLO:

En la transacción “Invoice”, cada vez que se ingresa una línea con un producto que se está comprando, se debe disminuir
el stock del mismo, según la cantidad llevada.

Esto podría hacerse en forma sencilla con la siguiente regla de asignación:


ProductStock = ProductStock – InvoiceLineQuantity;

Si prestamos atención, sin embargo, vemos que esta regla no nos sirve, pues no está condicionada a un modo particular,
razón por la cuál se disparará tanto cuando se está insertando una nueva línea en la factura, como cuando se está
eliminando o modificando una ya existente. Y en estos últimos dos casos es incorrecto disparar la regla.

De hecho, cuando se esté eliminado una línea existente, debe realizarse la operación contraria, es decir, se debe
“devolver” al stock lo que se había quitado cuando se insertó la línea.

Lo correcto entonces, teniendo en cuenta todos los casos posibles sería tener 3 reglas:

ProductStock = ProductStock – InvoiceLineQuantity if Insert;


ProductStock = ProductStock + InvoiceLineQuantity if Delete;
ProductStock = ProductStock + old(InvoiceLineQuantity) – InvoiceLineQuantity if Update;

Aquí estamos utilizando la función “old”, que devuelve el valor almacenado del atributo (es el valor antes de modificarlo).
Para evitar tener que hacer todo esto, GeneXus provee la regla subtract que se encarga de hacer la asignación correcta
de acuerdo al modo. Entonces podemos sustituir las 3 asignaciones anteriores, por:

subtract( InvoiceLineQuantity, ProductStock);

Esta regla tiene la inteligencia para, dependiendo del modo, restar o sumar.

97
Add

OBJETIVO: Suma el valor de un atributo al valor de otro atributo, si se satisface la condición especificada
SINTAXIS: add( att1, att2) [if cond];

DONDE:
att1, att2: son atributos pertenecientes a alguna de las tablas base asociadas a la transacción, o a sus tablas extendidas
(y deben estar declarados en la estructura).
cond: es una expresión booleana.
FUNCIONALIDAD: La adición se realiza teniendo en cuenta el modo en el que se esté trabajando en la transacción (Insert,
Update o Delete).

En modo:
- Insert: se le suma al valor del atributo att2, el valor del atributo att1
- Delete: se le sustrae al valor de att2, el valor del atributo att1
- Update: se le suma al valor del atributo att2, la diferencia entre el valor nuevo y el
viejo de att1

EJEMPLO:
Definimos en la transacción “Customer”, un atributo de nombre CustomerTotalPurchases, para registrar el importe total
de compras efectuadas por el mismo. El comportamiento que se desea es que cada vez que se cree una factura para un
cliente dado, se le sume el total de la factura (InvoiceTotal) al total de compras efectuadas por el cliente
(CustomerTotalPurchases).

Al igual que vimos en la regla subtract, no debemos olvidar que en la transacción “Invoice” podemos también eliminar y
modificar facturas, y no solo crearlas; por lo tanto es importante tener en cuenta el modo de trabajo en la transacción
(Insert, Update, Delete). GeneXus nos libera de tener que considerar nosotros a los modos, teniendo que escribir las
siguientes tres reglas de asignación en la transacción “Invoice”:

CustomerTotalPurchases = CustomerTotalPurchases + InvoiceTotal if Insert;


CustomerTotalPurchases = CustomerTotalPurchases – InvoiceTotal if Delete;
CustomerTotalPurchases = CustomerTotalPurchases – old(InvoiceTotal) + InvoiceTotal if Update;

y en su lugar nos provee de la regla add, que se encarga de sumar o restar, dependiendo del modo.
Así es que si en la transacción “Customer” agregamos el atributo CustomerTotalPurchases (total de compras del cliente):

CustomerId*
CustomerName
CustomerAddress
CustomerGender
...
CustomerTotalPurchases

y en la transacción “Invoice” inferimos al atributo CustomerTotalPurchases, ya que pertenece a la tabla extendida y


podemos definir la regla:

add( InvoiceTotal, CustomerTotalPurchases );

y logramos el comportamiento deseado, que es:

• si se inserta una factura (Insert): se le suma al valor del atributo CustomerTotalPurchases el valor del atributo
InvoiceTotal
• si se elimina una factura (Delete): se le sustrae al valor del atributo CustomerTotalPurchases el valor del atributo
InvoiceTotal
• si se modifica una factura (Update): se le suma al valor del atributo CustomerTotalPurchases, la diferencia entre el valor
nuevo y el viejo de InvoiceTotal

98
Serial
OBJETIVO: Permite numerar serialmente atributos numéricos.

SINTAXIS: Serial( att1, att2, step);

DONDE:
• att1: es un atributo perteneciente a alguna de las tablas base asociadas a la transacción (es decir, no
inferido), que desea autonumerarse (debiendo estar declarado en la estructura).
• att2: Tiene que pertenecer a una tabla directamente superordinada a la del atributo att1.
• step: es el paso o incremento de la serialización.

FUNCIONALIDAD: El propósito de esta regla es asignar un número correlativo a att1 cada vez que se inserta un
registro en la tabla a la que pertenece att1. Se toma el valor de att2 (att2 contiene el último número utilizado en
la autonumeración), se le suma el valor del parámetro step, y el valor resultante se asigna tanto al atributo att1
del nuevo registro, como al atributo att2 para conservar el último número asignado.

Es decir, cuando se está insertando un registro por medio de una transacción en la cual se ha
definido la regla: Serial(att1, att2, step);, se accede al att2 (habrá un solo valor de este atributo relacionado,
pues pertenece a una tabla directamente superordinada1), se le suma el valor step, y se asigna el valor obtenido
tanto a att1 del registro que va a ser insertado, como a att2 perteneciente a una tabla directamente
superordinada con respecto a la tabla que contiene a att1.

Si se diseña a la transacción “Invoice” conteniendo un Número de Línea de Factura (atributo InvoiceLineId)


como identificador único del segundo nivel, la estructura de la transacción sería:

InvoiceId*
CustomerId
CustomerName
InvoiceDate
InvoiceLastLineId
(InvoiceLineId*
ProductId

ProductDescription
……)

En este diseño el atributo ProductId no es identificador único del nivel, sino clave foránea únicamente.

Cada línea tiene un número de línea que la identifica en forma única, y es posible ingresar el mismo producto en
distintas líneas.

Podría ser útil asignar por medio del sistema, números correlativos al campo InvoiceLineId, definiendo la
regla:

serial(InvoiceLineId, InvoiceLastLineId, 1);

El primer parámetro de la regla serial define cuál es el atributo a numerar automáticamente, en el segundo
parámetro debe indicarse un atributo cuya función es guardar el último valor asignado hasta el momento, y por
último el tercer parámetro es para indicar el incremento (en este caso se incrementa de uno en uno).

El segundo parámetro (en el ejemplo InvoiceLastLineId) debe pertenecer a una tabla directamente
superordinada a la tabla que contiene el atributo que se desea numerar automáticamente (InvoiceLineId). La
regla serial lo requiere así. En el ejemplo, se puede observar que InvoiceLastLineId se encuentra en la tabla de
clave InvoiceId*, la cual es directamente superordinada respecto a la tabla que contiene el atributo a numerar
(InvoiceLineId).

Es decir, cada factura tendrá en el cabezal un atributo que almacenará el último número de línea asignado hasta
el momento (InvoiceLastLineId). La regla serial está implementada de forma tal que

99
necesita este atributo (para fijarse el último número utilizado, sumarle el incremento, y asignar el próximo número a la
nueva línea).

Consideración:
La regla serial es útil a la hora de autonumerar líneas, no así cabezales (por ejemplo identificadores de facturas, de
clientes, etc.). El motivo es el siguiente: para utilizar la regla serial, se requiere definir un atributo en una tabla
directamente superordinada; esto resulta bien sencillo si se desean autonumerar líneas ya que alcanza con incluir este
atributo en el nivel de la estructura inmediatamente superior al del atributo a autonumerar.
Sin embargo, si lo que se quiere numerar automáticamente es un atributo del primer nivel (cabezal), allí no existe un
nivel inmediatamente superior en la estructura; esto no es un problema sin solución ya que para definir una relación
de superordinación/subordinación entre tablas, no solo está la posibilidad de diseñarlo en una misma estructura de
transacción con más de un nivel; también transacciones distintas con atributos en común, provocan la creación de
tablas relacionadas, y por ende, con relaciones de superordinación / subordinación entre ellas.
El siguiente diseño incluye a una tabla de nombre NUMBER superordinada a las tablas INVOICE y CUSTOMER:

NumberCode*
NUMBER
NumberLast

InvoiceId* CustomerId*
InvoiceDate INVOICE CUSTOMER CustomerName
NumberCode NumberCode
CustomerId …
Como se puede
… observar, la tabla NUMBER tiene solamente 2 atributos: NumberCode que sería un código para
almacenar el tipo de número que se brinda (número de factura, número de cliente, etc.) y NumberLast para almacenar
el último número dado para cada NumberCode.
La estructura de la tabla NUMBER sería:

NumberCode* NumberLast
CUSTOMER 20 último identificador de cliente asignado
INVOICE 10 último identificador de factura asignado

Para obtener este diseño, deberíamos definir una transacción “Number” con estructura:
NumberCode*
NumberLast

Y relacionarla con las transacciones en las cuales se utilizaría la regla serial:

InvoiceId* CustomerId*
InvoiceDate CustomerName
CustomerId …
CustomerName Inferido: está en la tabla NumberCode
NumberCode NUMBER directamente NumberLast
NumberLast subordinada
(InvoiceLineId*
ProductId
ProductDescription
…)

100
Finalmente, agregando en la transacción “Invoice” las siguientes reglas, lograríamos autonumerar las facturas:

serial(InvoiceId, NumberLast, 1);


equal(NumberCode, ‘INVOICE’);

La regla equal asigna un valor (‘INVOICE’) a un atributo (NumberCode) cuando se ejecuta la transacción en modo insert,
y filtra los registros que tengan dicho valor en el atributo, cuando se ejecuta la transacción en modo update y delete.

Y en la transacción “Customer” la situación es análoga; con las reglas siguientes podemos autonumerar clientes:

serial( CustomerId, NumberLast, 1 );


equal( NumberCode, ‘CUSTOMER’ );

Sin embargo, contamos con una solución mucho más simple para autonumerar cabezales: cuando una tabla tiene una
clave simple (es decir formada por un solo atributo) y el tipo de datos es numérico, puede numerarse automáticamente
utilizando la funcionalidad que brindan los manejadores de base de datos para esto. La forma de indicarlo en GeneXus es
configurando la propiedad Autonumber del atributo clave:

Si en la propiedad Autonumber de un atributo numérico clave, se selecciona el valor True, significa que se realizará la
numeración automática del mismo. Se agregarán las siguientes propiedades en el diálogo:

Start: Mediante esta propiedad se configura a partir de qué número comienza la numeración automática.

Step: Mediante esta propiedad es posible configurar el incremento del campo (entre dos registros).

For replication: Esta propiedad es sólo válida para el motor de base de datos SQL Server; el valor Yes le indica a éste
que no debe aplicar la propiedad en caso de que la tabla sea receptora de replicación (sino que debe mantener los
números que le vienen por replicación).

Para profundizar en el manejo de esta propiedad, recomendamos acceder al Help de GeneXus.

101
Update
OBJETIVO: Posibilita actualizar en el form de una transacción (win/web) atributos de la tabla extendida (inferidos).

SINTAXIS: Update( att1[, atti …]);

DONDE:
atti: es un atributo perteneciente a la tabla extendida de alguna de las tablas bases asociadas a la transacción.

FUNCIONALIDAD: En una transacción, todos los atributos que pertenecen a las tablas base asociadas a la transacción, por
defecto son aceptados y los que perteneciendo a la tabla extendida, no pertenecen a la base, son inferidos, y por tanto no
aceptados.
Pero si queremos que algunos de estos atributos inferidos sean aceptados, para que el usuario pueda modificar desde el
form su valor, entonces contamos con la regla Update.

EJEMPLO:

InvoiceId* CustomerId*
InvoiceDate CustomerName
CustomerId …
CustomerName

update(CustomerName);

102
Conceptos importantes sobre reglas de
transacciones

• ¿En qué nivel de una transacción se ejecutarán


las reglas definidas en la misma?

• ¿Cómo condicionar reglas para que se ejecuten


en determinados modos?

¿En qué nivel de una transacción se ejecutarán las reglas definidas en la misma?

La mayor parte de las veces no es necesario agregar explícitamente en la definición de las reglas el nivel
de la transacción en el cual se desea que se disparen, ya que los atributos involucrados en las reglas le
dan la pauta a GeneXus del nivel en el cual corresponde ejecutarlas.

Por ejemplo, si una regla referencia únicamente a atributos del primer nivel de la transacción en la cual
se encuentra definida (ya sea en la propia regla o en la condición de disparo), GeneXus entenderá que la
misma estará asociada al primer nivel de la transacción.

Análogamente, si una regla referencia solamente a atributos del segundo nivel de la transacción en la
cual se encuentra definida (ya sea en la propia regla o en la condición de disparo), GeneXus entenderá
que la misma estará asociada al segundo nivel de la transacción.

En el caso que una regla referencie atributos de varios niveles, GeneXus entenderá que la regla estará
asociada al último de los niveles de los atributos involucrados, ya que será en el último nivel en el que
contará con los valores de todos los atributos implicados.

A continuación presentamos ejemplos concretos:

1) Si se define la siguiente regla en la transacción “Invoice”:


Default(InvoiceDate, &today);

como el único atributo que se menciona en la regla es InvoiceDate, y es un atributo del primer nivel de
la transacción, GeneXus determinará que se tratará de una regla asociada al primer nivel.

2) Si se define la siguiente regla en la transacción “Invoice”:


subtract( InvoiceLineQuantity, ProductStock );

como los dos atributos que se mencionan en la misma se encuentran en el segundo nivel de la
transacción, GeneXus determinará que se tratará de una regla asociada al segundo nivel.

103
3) Si se define la siguiente regla en la transacción “Invoice”:
InvoiceLineDiscount= InvoiceLineAmount * CustomerDiscountPercentage/100;

siendo InvoiceLineDiscount (descuento correspondiente a una línea) un atributo perteneciente al segundo nivel de
la transacción “Invoice” y CustomerDiscountPercentage (porcentaje de descuento otorgado a un cliente) un
atributo declarado en el primer nivel de la transacción “Invoice”, GeneXus determinará que se tratará de una regla
asociada al segundo nivel de la transacción.

Cuando nos referimos a que una regla está asociada a determinado nivel, significa que la misma se ejecutará para
cada instancia con la cual se trabaje a través de ese nivel (si se cumple la condición de disparo de la regla, claro
está).

En el caso del primer ejemplo visto, la regla Default(InvoiceDate, &today) es una regla asociada al primer nivel
de la transacción “Invoice”. Esto significa que se ejecutará para todo cabezal de factura que se inserte a través del
primer nivel de la transacción “Invoice” (la regla Default tiene la particularidad de dispararse únicamente cuando
el modo de ejecución es Insert).

En el caso del segundo ejemplo visto, la regla subtract(InvoiceLineQuantity, ProductStock) es una regla asociada
al segundo nivel de la transacción “Invoice”. Esto significa que se ejecutará para toda línea de factura que se
inserte, actualice o elimine a través del segundo nivel de la transacción “Invoice”.

En el caso del tercer ejemplo, la regla:


InvoiceLineDiscount = InvoiceLineAmount * CustomerDiscountPercentage/100 es una regla asociada al segundo
nivel de la transacción “Invoice”. De modo que esta regla se ejecutará para toda línea de factura que se inserte,
actualice o elimine a través del segundo nivel de la transacción “Invoice”.

Concluyendo, tal como se desprende de todo lo explicado, para cada factura con la cual se trabaje a través de la
transacción “Invoice”:
- para el cabezal: se ejecutarán las reglas asociadas al primer nivel
- para cada una de las líneas: se ejecutarán las reglas asociadas al segundo nivel

Es importante saber que como norma general GeneXus siempre determina que una regla se dispare en el
primer momento en que sea posible, es decir, en aquel momento en el que cuente con todos los datos
necesarios. Y solamente en algunos casos que así lo requieran, una misma regla se volverá a disparar más
adelante.

¿Qué nivel de disparo por defecto se le asociará a una regla que no referencia atributos?

Cuando no hay atributos involucrados en una regla, el nivel asociado por defecto a la regla será el primero.

Por ejemplo, la siguiente regla definida en la transacción “Invoice”:


Error(‘No se permite eliminar facturas’) if Delete;
no tiene atributos involucrados, por lo tanto, el nivel asociado por defecto a la regla será el primero.

¿Cuándo hay que especificar explícitamente el nivel de disparo de una regla?

Existe una cláusula opcional de nombre Level que permite modificar el nivel por defecto de disparo de una regla,
cambiándolo por un nivel posterior.

Es decir, si por ejemplo una regla se ejecuta por defecto para el primer nivel de una transacción y se desea que se
ejecute para el segundo, se deberá agregar a la regla el componente Level seguido de un atributo o conjunto de
atributos del segundo nivel. Esto hará que la regla se ejecute para cada una de las instancias correspondientes a
las líneas, y no para la instancia correspondiente al cabezal como era el comportamiento por defecto.

Por ejemplo, si definimos la siguiente regla en la transacción “Invoice”:


msg(‘La fecha de la factura es mayor a la fecha actual’) if InvoiceDate > &Today;
por defecto esta regla estará asociada al primer nivel de la transacción, ya que el único atributo referenciado en la
regla se encuentra en el primer nivel de la transacción. Si por algún motivo deseamos que la regla se ejecute para
cada una de las instancias correspondientes a las líneas en vez de ejecutarse para la instancia correspondiente al
cabezal, tendremos que agregar en la definición de la regla, la cláusula Level seguida de uno o varios atributos
del segundo nivel:
msg(‘La fecha de la factura es mayor a la fecha actual’) if InvoiceDate>&Today Level InvoiceLineAmount;

104
Agregar la cláusula Level a una regla solamente tiene sentido si a continuación de la misma se mencionan
atributos que son de algún nivel posterior a los niveles de los atributos implicados en la definición de la regla en
sí.

En el ejemplo que sigue, el hecho de haber agregado la cláusula Level a la regla no aporta más información de la
ya aportada por el atributo implicado en la definición de la regla en sí:

msg(‘La fecha de la factura es mayor a la fecha actual’) if InvoiceDate > &Today Level CustomerId;

Es fácil comprender que el atributo InvoiceDate ya le da la pauta a GeneXus de que se trata de una regla asociada
al primer nivel, así que lo especificado por la cláusula Level en el ejemplo no aporta más información y por lo
tanto su agregado es innecesario.

Por último, en el ejemplo que sigue:

InvoiceLineDiscount= InvoiceLineAmount * CustomerDiscountPercentage/100 Level InvoiceDate;

si bien se incluyó la cláusula Level en la definición de la regla, como el atributo que sigue a la cláusula es de un
nivel superior al nivel de los atributos referenciados en la regla, la cláusula Level definida no aportará información
útil en este caso tampoco. Es decir, no es posible que habiendo involucrados atributos de un segundo nivel en una
regla, la misma se ejecute en el primer nivel, ya que en el primer nivel no se tiene la información del o de los
niveles inferiores (además de que hay N instancias para el o los niveles inferiores). De modo que la regla seguirá
estando asociada al segundo nivel de la transacción “Invoice”, no habiendo aportado información útil la cláusula
Level en este ejemplo.

Concluyendo, la cláusula Level solamente tiene sentido que sea agregada para modificar el nivel por defecto de
disparo de una regla, a un nivel posterior.

¿Cómo condicionar reglas para que se ejecuten en determinados modos?

GeneXus provee las siguientes funciones booleanas para poder incluirlas en la condición de disparo de las
reglas con el objetivo de limitarlas a que se ejecuten puntualmente en algunos de los modos posibles:

• Insert
• Update
• Delete

Ejemplos de uso (todas las reglas corresponderán a la transacción “Invoice”)

1) Noaccept( InvoiceDate ) if Update;

Si se está modificando una factura (modo Update), no se permite que se modifique su fecha. Si se definiera la
regla sin la condición de disparo que hemos explicitado, el atributo InvoiceDate se deshabilitaría en todos los
modos de ejecución.

2) Error( ‘No se permite eliminar facturas’ ) if Delete;

Si se intenta eliminar una factura, se disparará el error deteniendo la eliminación. Como no hay atributos
involucrados en la regla, por defecto el nivel asociado a la regla será el primero.

3) Error( ‘No se permite eliminar líneas de facturas’ ) if Delete Level InvoiceLineQuantity;

Si se intenta eliminar una línea de una factura, se disparará el error deteniendo la eliminación. Observar que se ha
explicitado en la regla la cláusula Level seguida de un atributo del segundo nivel de la transacción “Invoice”, para
indicar que se desea que la misma esté asociada al segundo nivel de la transacción.

105
Tipos de diálogo

• Diálogo campo a campo


Las validaciones, disparo de reglas y fórmulas y grabaciones, se
van efectuando en forma interactiva, en la medida que el usuario
va trabajando en la pantalla (Ej: Vb, Vfp con interfaz Win)

• Diálogo a pantalla completa (full screen)


Todas las validaciones, disparo de reglas y fórmulas y
grabaciones, se efectúan cuando se confirma la pantalla completa
(Ej: Java y .Net con interfaz Win)

• Diálogo con Validación a nivel del cliente (CSV)


Híbrido entre los dos anteriores
• Disparo de algunas reglas y fórmulas interactivas
• Grabaciones cuando se confirma la pantalla completa
(aquí se redisparan reglas y fórmulas)
(Ej: Java y .Net, Win y Web)

GeneXus genera distintos tipos de diálogos para las transacciones. El diálogo utilizado para un modelo en
particular, dependerá de su ambiente o plataforma (en particular de su interfaz, Win o Web).

Los dos tipos de diálogo clásicos son:

• campo a campo
• a pantalla completa (full screen)

Luego se ha creado un híbrido entre ambos, que funciona en algunos aspectos como el diálogo a pantalla
completa y en otros como el campo a campo. Le llamaremos: diálogo con validación a nivel del cliente
(Client Side Validation).

Diálogo campo a campo

En este tipo de diálogo, cada vez que se digita un valor en un campo y se abandona, se controla
inmediatamente su validez. Las reglas y fórmulas1 se van disparando a medida que se va pasando por los
campos, y los datos de la tabla extendida se van infiriendo ni bien se van ingresando los valores de las
claves foráneas.

Generalmente la grabación de los datos en este tipo de diálogo es por registro, vale decir:

• Ni bien se terminan de ingresar los datos del cabezal y se pasa a las líneas, el registro correspondiente al
cabezal es grabado.
• Ni bien se terminan de ingresar los datos de una línea y se pasa a la siguiente, el registro correspondiente
a la línea de la cual se salió, es grabado.

La grabaciones se van realizando interactivamente, y si se realiza explícitamente una confirmación de datos,


se grabará puntualmente el registro con el cual se esté trabajando (el correspondiente al cabezal o el
correspondiente a cierta línea en la cual el cursor se encuentre).

Los generadores Visual Basic y Visual FoxPro trabajan con este tipo de diálogo.

------------------------------------------------------------------------------------------------------------------
1 Estudiaremos las fórmulas un poco más adelante.

106
Tipos de diálogo

Client Server Client Server

• Ingreso de datos • Validaciones • Ingreso de datos • Validaciones


• Confirmación • Controles IR • Validaciones • Controles IR
• inferencias • Controles IR • inferencias
• Reglas • Inferencias • Reglas
• Fórmulas • Algunas reglas • Fórmulas
• Grabaciones • Fórmulas • Grabaciones
• Confirmación

Diálogo a pantalla completa Diálogo con CSV

Diálogo a pantalla completa (full screen)

En este tipo de diálogo no se realizan validaciones de los datos, controles de integridad referencial (IR),
inferencias de la tabla extendida, ni disparos de reglas y fórmulas a medida que los datos van siendo
ingresados por el usuario en la pantalla.

Las validaciones, reglas y fórmulas se disparan solamente en dos momentos:

1. Al ingresar a la transacción (reglas que no dependen de nada, no tienen condiciones de disparo).


2. Al confirmar los datos.

La confirmación de los datos determina la grabación de la pantalla completa, disparando previamente


todas las reglas, validaciones y haciendo la carga de los atributos de la tabla extendida.

Está en la naturaleza de los generadores Java y .Net trabajar con este tipo de diálogo. Sin embargo existe la
posibilidad de modificar este comportamiento, para que el usuario final pueda tener mayor interacción con la
aplicación de manera tal que no deba esperar a confirmar la transacción para ver los atributos inferidos, así
como las validaciones y disparo de reglas y fórmulas. En lo que sigue explicaremos el diálogo a pantalla
completa con Validación a nivel del Cliente.

Diálogo a pantalla completa con Validación a nivel del Cliente (Client Side Validation)

La base de este diálogo es a pantalla completa, pero el usuario tendrá mayor interacción con la aplicación.

Tanto las validaciones de datos, como los controles de IR y disparo de algunas reglas y fórmulas, además de
realizarse cuando se confirma la transacción (como en el diálogo full screen clásico), también se realizarán
antes, en forma interactiva a medida que el usuario vaya ingresando y abandonando los campos de la pantalla.

Las grabaciones de los registros sí se realizarán a pantalla completa, tras la confirmación.

De modo que se trata de un híbrido entre los diálogos campo a campo y a pantalla completa.

Nota: en el caso de tratarse de ambiente Web, la pantalla que se ejecuta en el cliente es la ejecutada en el
browser, y el servidor en este caso es el Servidor Web que ejecuta la aplicación. Si bien la naturaleza de
internet no posibilitaba la validación a nivel del cliente (es decir, enviar partes de la lógica de las transacciones
al browser), con el advenimiento de Ajax1, un conjunto de tecnologías existentes operando juntas, esto ahora
es posible.

------------------------------------------------------------------------------------------------------------------
1 Puede encontrar un artículo interesante “Ajax: A New Approach to Web Applications” en
http://www.adaptivepath.com/publications/essays/archives/000385.php

107
Diálogo con Validación a nivel del cliente
(Client Side Validation)

• En su base es full screen, con algunas validaciones en el cliente


que le permiten incrementar la interacción con el usuario final

• Generadores .Net y Java

• interfaz Win: es configurable (propiedad CSV)

• interfaz Web: no es configurable, siempre se realizará.

• Valores de la propiedad CSV (solo para Win)


• Yes: Activa las validaciones, inferencias y disparo de reglas y fórmulas en la
medida que el usuario va ingresando datos en la pantalla. Estas acciones se
repiten cuando se confirma. (valor predeterminado)
• No: Todas las validaciones y disparo de reglas y fórmulas se realizan cuando se
confirma.

• Propiedad a nivel de modelo y de objeto

Mientras que para aplicaciones .Net y Java con interfaz Web siempre se trabajará con diálogo con validación
a nivel del cliente (WCSV: Web Client Side Validation) posible solo gracias a la irrupción del conjunto de
tecnologías conocido como Ajax en el mundo de Internet, para el caso de las mismas aplicaciones para
interfaz Win existe la posibilidad de configurar una propiedad de nombre ‘Client Side Validation’, para poder,
si así se desea, trabajar con el diálogo base, exclusivamente a pantalla completa.

Interfaz Win

Cuando se trabaja con generadores .Net o Java y ambiente Windows, la validación a nivel del cliente
ofrece una alternativa al diálogo a pantalla completa para proveer mayor interacción con el usuario final.

La propiedad Client Side Validation admite los siguientes valores:

- Yes: Si se selecciona este valor, en la medida que el usuario final vaya pasando por los campos de la
pantalla y/o ingresando información en ellos, se irán validando los datos, infiriendo los atributos de la tabla
extendida, y disparándose las reglas y fórmulas definidas. Todas estas operaciones se ejecutarán en forma
interactiva para que el usuario final pueda ir viendo resultados. De todas formas, las características del
diálogo a pantalla completa se mantendrán, en el sentido de que cuando el usuario final confirme los datos,
se grabarán los de la pantalla completa (disparándose previamente todas las validaciones y acciones, esta
vez en el servidor). Este es el valor por defecto.
-No: Indica que el diálogo a pantalla completa será puro, es decir, sin el agregado de las validaciones en
forma interactiva.

La propiedad Client Side Validation se encuentra disponible tanto a nivel de modelo como a nivel de
objeto. Esto significa que el valor que se configure en la propiedad a nivel del modelo determinará el
comportamiento de todas las transacciones, a excepción de aquellas que tengan configurado el valor
contrario en su propiedad a nivel del objeto.

Cuando se crea un modelo de prototipo con interfaz Win y generador Java o .NET, uno de los pasos del
Wizard de creación de dicho modelo contiene un combo titulado “Client Side Validation” que ofrece los dos
valores posibles: ‘Yes’ o ‘No’. De esta manera el analista ya puede determinar si desea que su aplicación
tenga un diálogo a pantalla completa (propiedad CSV = No) o si por el contrario, desea incrementar la
interactividad de las transacciones, con validación a nivel del cliente (propiedad CSV = Yes). También se
puede configurar esta propiedad a nivel del modelo más tarde (editando las propiedades del modelo), así
como a nivel de objeto si se desea que ciertas transacciones tengan el comportamiento contrario al
configurado para el modelo.

108
Diálogo con Validación a nivel del cliente
(Client Side Validation)

• En la medida que el usuario final va ingresando información en la


pantalla, se van validando los datos, realizando las inferencias, y
disparándose las reglas y fórmulas definidas.
• Procesamiento de datos (grabaciones) a “pantalla completa”
• Inferencia de modo:

WIN: WEB:
CSV=No Æ Hay botón get / no se infiere el modo Hay botón get / no se infiere el modo
CSV=Yes Æ No hay botón get / se infiere el modo

Resumen sobre diálogo a pantalla completa con validación a nivel del cliente:

Mientras el usuario va trabajando con la pantalla, ingresando datos en los distintos campos y
abandonándolos, se irán realizando validaciones de los mismos, controles de IR e inferencias de la tabla
extendida, así como disparo de reglas y fórmulas que involucren a esos atributos que se abandonan.
Esto le brindará al usuario la idea de una alta interacción.

Luego, cuando el usuario termina de trabajar con la instancia de cabezal y líneas, confirma presionando
el botón de confirmación, y allí se realiza el procesamiento de datos a “pantalla completa”. Aquí vuelven
a validarse los datos, a dispararse los controles de IR e inferencias, junto con las reglas y fórmulas,
además de realizarse las grabaciones de todos los registros correspondientes (cabezal y líneas). Todo
esto se realiza en el servidor, como si nada hubiese ocurrido en el cliente (tiene que ver con aspectos
de seguridad, dado que si no vuelven a realizarse las validaciones luego en el servidor, podría haberse
“colado” algo en el camino al servidor que burle la seguridad).

109
Diálogo a pantalla completa
Propiedad: Confirmation

• Se cuenta también con la propiedad “Confirmation”, útil


fundamentalmente en diálogo a pantalla completa.

• Posibilita trabajar “con” o “sin” confirmación explícita del usuario:


• Con confirmación: validación en 2 pasos (grabación en el último).

WIN: cambia texto del botón, WEB: no cambia texto, “confirm” se muestra
“confirm” en el 2do. paso en el 2do. paso en el Error Viewer

• Sin confirmación: se valida y graba en 1 solo paso


• En Win: no hay botón “Confirm”, solo Insert o Update, dependiendo del modo.
• En Web: no hay mensaje de confirmación en el Error Viewer

La propiedad Confirmation se encuentra disponible tanto a nivel de modelo como a nivel de objeto. Esto
significa que el valor configurado en la propiedad a nivel de modelo determinará el comportamiento de todos
los objetos del modelo, a excepción de aquellos que tengan configurado otro valor en forma particular en su
propiedad a nivel de objeto.
Esta propiedad permite configurar:
• para transacciones: si se desea o no, que antes de que se graben directamente los datos, se le solicite
confirmación al usuario para hacerlo.
• para reportes y procedimientos: si se desea o no, que antes de que se comience a ejecutar el proceso, se le
solicite al usuario confirmación para hacerlo.
Los valores que admite esta propiedad son:
• Always prompt: Si se selecciona este valor, se le solicitará confirmación al usuario.
• Never prompt: Si se selecciona este valor, no se le solicitará confirmación al usuario.
• Do not prompt on first level: Este valor se ofrece únicamente para los generadores Visual Basic y Visual
Fox Pro, y aplica solamente a transacciones. Si se selecciona el mismo, se le solicitará confirmación al usuario
antes de que se graben los datos en cada nivel de la transacción, a excepción del primero.
El trabajo con confirmación reviste relevancia fundamentalmente en el caso en que se trabaje con diálogo a
pantalla completa (sin validaciones del lado del cliente), pues como dijimos, es en ese tipo de diálogo en el
que el usuario no tiene una respuesta interactiva por parte del sistema respecto a los datos que acaba de
ingresar y requiere de una primera confirmación para obtener alguna respuesta. Por ejemplo, puede haber
ingresado un valor que no era el que deseaba para una clave foránea que corresponde al país del cliente
(suponiendo que no disfraza los valores del atributo y trabaja directamente con los códigos1), y en ese caso
como las inferencias no se realizarán interactivamente, puede querer realizar una confirmación en dos pasos,
de modo tal que en el primero se le muestren los datos inferidos y así ya sabrá que el país que digitó era el
deseado. En caso que no lo fuera, podrá modificarlo antes de que la información sea efectivamente grabada
en la base de datos (cosa que se logrará recién en el 2do. paso).
Para interfaz Web (que trabaja con CSV) así como para Win con validación a nivel del cliente, no resulta
necesario este tipo de comportamiento, ya que las inferencias y validaciones se van disparando
interactivamente a medida que el usuario va trabajando con la interfaz. Si igualmente se utiliza
Confirmación, las validaciones, controles de IR e inferencias, las reglas que no dependen de las grabaciones
de los registros, y disparo de fórmulas se ejecutarán tres veces (una en el primer paso de la confirmación (en
el cliente), segunda vez en el 2do. paso de la confirmación (en el cliente) y una última vez en el servidor,
junto con el resto de las reglas, cuando los registros pasan a grabarse en la base de datos.
------------------------------------------------------------------------------------------------------------
1 “Descripciones en vez de códigos” dentro del tema Integridad Referencial en este manual.

110
Transacciones en tiempo
de ejecución
WIN

eliminación
de líneas
(tecla Supr)

¿Cómo trabaja el usuario final en ejecución con las transacciones?

En el ejemplo de arriba la transacción “Invoice” se está ejecutando en un ambiente GUI-Windows, y


.Net con validación a nivel del cliente. Por tanto, ni bien el usuario ingresa un valor para el atributo
que es PK y abandona el campo, se infiere el modo (será Insert en caso en que no exista factura
con ese identificador en la tabla, u Update en caso contrario).

Update
En caso de existir registro con ese identificador, se edita en los campos del form, así como se
recuperan también las líneas de la tabla INVOICELINE asociadas y se editan en el grid.
En este caso el usuario podrá tanto modificar los valores editados (tanto de cabezal como de
líneas), para los atributos para los que esto esté permitido (los que no sean read only), insertar
nuevas líneas en el grid, así como eliminar alguna línea existente (posicionándose en la línea y
presionando la tecla DEL-Suprimir del teclado).
Luego de efectuadas todas las modificaciones (de datos preexistentes, inserción de nuevas líneas o
marcado para la eliminación de líneas mediante la tecla DEL), se presiona el botón de “Confirm”1
para aplicar los cambios.
En cambio, si lo que se desea es eliminar la factura entera, en ese y solo ese caso se presiona el
botón Delete.

Insert
En caso de no existir registro en la tabla INVOICE con el valor ingresado por el usuario en la PK,
entonces los campos del form estarán vacíos esperando ser ingresados por el usuario. El botón de
confirmación dirá “Insert”. El grid no contendrá líneas ingresadas. El usuario ingresará por tanto los
datos para el cabezal y las líneas, pudiendo arrepentirse de una línea que ingresó previamente y
borrarla utilizando el mismo procedimiento indicado antes (tecla DEL-Suprimir del teclado sobre la
línea).
Obsérvese que en este caso no tendrá sentido presionar el botón “Delete”, dado que los registros
aun no existen en la base de datos, por lo que no hay nada que borrar. Simplemente cerrando la
transacción, o pasando a editar otro registro, lo digitado por el usuario en la pantalla se perderá sin
grabarse.
Luego de ingresados sus datos, el usuario confirmará la pantalla mediante el botón “Insert”
(teniendo luego que reconfirmar si se trabaja con confirmación; botón “Confirm”).

---------------------------------------------------------------------------------------------------------
1 Si se trabaja con confirmación el botón dirá “Update” en primera instancia y luego “Confirm”. Si se

trabaja sin confirmación, solo dirá “Update”.

111
Transacciones en tiempo
de ejecución
WEB

eliminación de
líneas
(&GxRemove)

Para la misma transacción, generada en ambiente Web la historia es muy similar.

Existen algunas pequeñas diferencias, no obstante. Mientras que en ambiente Win contamos con el
control Grid que muestra la cantidad de líneas por pantalla que determine el analista cuando diseña
el form de la transacción, permite sin embargo el desplazamiento vertical (barra de scroll),
permitiendo entonces ingresar tantas líneas como se desee, con tan solo hacer scroll cuando se han
ingresado todas las líneas de la pantalla.
En Web, sin embargo, al no contar con esta facilidad, siempre se mostrará un número de líneas
vacías fijas para ser ingresadas por el usuario (valor configurable por el analista a nivel del grid, en
su propiedad “Rows”). Por defecto se muestran 5 líneas vacías.
Si el usuario ingresó las 5 líneas y necesita ingresar más, le bastará con presionar “Apply Changes”
y 5 líneas vacías más se adicionarán al formulario (si se trabaja con confirmación, esto dará la
oportunidad de agregar las líneas sin ingresar todo lo anterior en la base de datos).

Otra diferencia respecto al tratamiento de las líneas con respecto a Win es que en el grid del form
Web se incorpora automáticamente una primera columna conformada una variable del sistema de
nombre &GxRemove, en la forma de check box. Como lo indica su nombre, se utiliza para permitir
al usuario en ejecución marcar varias líneas para ser eliminadas.
Lo que en Win se realizaba mediante la tecla DEL, aquí se realiza marcando el check box de la línea
que quiere eliminarse.
Cuando se ingresa en la transacción en modo Insert, el check box no aparecerá en el grid, dado que
no existe ninguna línea aún ingresada, que pueda ser por tanto eliminada.
Al aplicar los cambios (Apply Changes) esta primera columna aparecerá para las líneas ingresadas
en el form.
Por lo demás, el modo de operación (cómo insertar, modificar, o eliminar registros) es análogo al de
Win.

112
Algunos elementos más
de las transacciones

Propiedades
• Propiedades

• Documentación

• Ayuda Ayuda

Documentación

Propiedades

Las propiedades asociadas a un objeto –en este caso nos referimos a una transacción, pero vale
en general, independientemente de cuál objeto se trate- permiten definir ciertos detalles
referentes al comportamiento general del objeto.

Para ingresar al diálogo de propiedades de un objeto, teniendo el objeto abierto, se deberá


seleccionar el ítem Object / Properties de la barra de menú de GeneXus, o el botón de la barra
de herramientas “Fast Access” que señalizamos en la transparencia.

Sin tener el objeto abierto, es posible editar sus propiedades haciendo botón derecho sobre el
nombre del objeto, y seleccionando el ítem Properties del menú contextual.

Mediante cualquiera de los caminos anteriores, se abre un diálogo que ofrece las propiedades
asociadas al objeto activo, y donde el analista podrá especificar sus valores, definiendo así detalles
referentes al comportamiento del objeto.

Por ejemplo, la propiedad Confirmation de la que hablamos antes, así como la propiedad Client
Side Validation aparecen en este diálogo en caso de estar trabajando en un modelo .Net o Java
Win, dado que como dijimos oportunamente, pueden configurarse a nivel de objeto.

La propiedad Confirmation también aparecerá en este diálogo si el modelo es .Net o Java Web; no
así la otra. Asimismo dentro de la sección Web Transaction properties del diálogo de configuración
de propiedades, se encontrará la propiedad Theme para asociarle un objeto tema a la transacción.
Cada modelo, dependiendo de su configuración (lenguaje, interfaz, etc.), tendrá algunas
propiedades u otras para configurar.

A lo largo del curso iremos viendo algunas de estas propiedades. Para profundizar en el manejo de
alguna propiedad en particular, recomendamos acceder al Help de GeneXus.

113
Documentación

Los objetos GeneXus ofrecen una sección para que el analista pueda incluir documentación técnica acerca del
objeto.

Para ingresar a la sección de documentación de un objeto, teniendo al objeto abierto, se deberá seleccionar la
solapa Documentation, o el ítem Object / Documentation de la barra de menú de GeneXus.

También el botón de la barra de herramientas “Fast Access” que señalizamos en la transparencia, conducirá a la
sección de documentación del objeto activo.
Se abrirá un editor html para que el analista ingrese la documentación técnica correspondiente.

Ayuda

Los objetos GeneXus tienen una sección específica para poder escribir la Ayuda para el usuario final en el uso del
objeto.

Para ingresar a la sección de ayuda de un objeto, teniendo al objeto abierto, se deberá seleccionar la solapa Help,
o el ítem Object / Help de la barra de menú de GeneXus.

También el botón de la barra de herramientas “Fast Access” que señalizamos en la transparencia.

Se abrirá un editor Html para que el programador ingrese el texto de ayuda que se le desplegará al usuario cuando
ejecutando el objeto, solicite ayuda.

Algunas consideraciones importantes

Si se ingresa texto de ayuda para objetos y/o atributos y/o variables de la base de conocimiento, luego de
especificar y generar las aplicaciones, se deberá generar el Help. Para ello se deberá seleccionar el ítem Build /
Generate Help.

Al seleccionar el ítem Build / Generate Help, se desplegará una pantalla para elegir entre dos formatos: CHM y
WEB. También se deberá seleccionar el lenguaje en el que se generará el Help1.

CHM: Es el formato que se deberá elegir para aplicaciones GUI-Windows. Permite empaquetar toda la ayuda
generada en un único archivo de formato CHM, brindando la posibilidad de buscar en la ayuda generada, se definirá
un índice, etc.

WEB: Es el formato que se deberá elegir para aplicaciones Web. En tiempo de ejecución se abrirá un browser con el
texto de la ayuda en formato HTML.

En el caso de elegir el formato CHM, se solicitará el path del compilador de Help (este compilador viene con Visual
Studio, pero no se instala automáticamente, sino que hay que instalarlo por separado).

Cualquiera sea el formato de ayuda elegido, se generará un directorio HELP bajo el directorio del modelo, en el que
se creará un archivo con extensión HTML por cada objeto/atributo/variable que tenga ayuda.

Si el formato elegido es CHM, se creará además un archivo de nombre APPHLP.CHM en el directorio del modelo, y
un archivo de definición de proyecto (de extensión HHP) que será utilizado por el compilador de Help, para la
generación del CHM.

El funcionamiento en ejecución será el siguiente:


• Cuando el usuario final seleccione el botón de Ayuda de la transacción: se desplegará la ayuda del objeto.
• Cuando el usuario final presione F1: se desplegará la ayuda del atributo en el cual se encuentre el cursor; y si ese
atributo no tiene definido texto de ayuda, se desplegará la ayuda del objeto.

-------------------------------------------------------------------------------------------------------------------------
1 Esto está directamente vinculado a la posibilidad de tener un texto de help por cada uno de los objetos Language,
utilizados para poder traducir la aplicación. Es decir, la posibilidad de tener un texto de Help para cada idioma
posible. Así deberá seleccionarse aquí cuál de esos textos de help (el correspondiente a qué lenguaje) deberá
tomarse para generar el help.

114
ATRIBUTOS FÓRMULA

115
Características

• Relación entre atributos, constantes y/o funciones.

• Definición Global: definidas a nivel de la base de


conocimiento.

• Atributo Virtual: no se almacena en la tabla.

• Llamaremos tabla base del atributo fórmula a la tabla


“asociada” al mismo

• Son calculadas siempre que se hace referencia al atributo

Cuando el valor de un atributo puede calcularse a partir del valor de otros atributos, constantes y/o
funciones, el mismo puede ser definido como fórmula.

Para definir que un atributo es una fórmula y especificar cómo calcularla, existen tres posibilidades,
todas ellas disponibles únicamente en el modelo de Diseño:

1) En la estructura de una transacción, así como se define para cada atributo su nombre, tipo de
datos y descripción, es posible definir que el atributo es una fórmula e indicar cuál es el cálculo que
debe hacerse. Esta vía directa en la estructura de una transacción que permite definir que un
atributo es una fórmula, se habilita tanto para un atributo ya definido y salvado previamente, como
para un atributo que se está definiendo en el momento.

2) En la estructura de una transacción, es posible pulsar el botón derecho del mouse sobre un
atributo que haya sido definido y salvado previamente; se abrirá el menú pop up correspondiente,
se deberá seleccionar el ítem Formula del mismo, y a continuación se presentará una pantalla de
edición de fórmula para que se indique cómo calcularla.

3) Seleccionando el ítem Advanced / Formula de la barra de menú de GeneXus, se abrirá un


diálogo ofreciendo todos los atributos definidos en la base de conocimiento, para que se seleccione
uno, y se edite la forma de calcularlo.

La definición de un atributo como fórmula -indicando cuál es el cálculo que debe hacerse para
obtener su valor- es una definición global. Dicho de otro modo, se trata de una definición a nivel
de la base de conocimiento y no a nivel de un objeto específico, independientemente de por cual de
las vías se realice su definición. Siempre que se referencie/utilice un atributo fórmula en cualquier
objeto de la base de conocimiento en el cual sea válido, GeneXus tendrá el conocimiento de qué
cálculo habrá que hacer para obtener su valor; así, en los programas generados asociados a los
objetos que utilicen fórmulas, incluirá el código necesario para que en tiempo de ejecución se
dispare el cálculo y se muestre el valor resultante.

116
Los atributos definidos como fórmula, a menos que se especifique lo contrario, no estarán almacenados en
la base de datos. Dado que sus valores se pueden calcular a partir de los valores de otros atributos,
constantes y/o funciones, resulta innecesario y sería redundante almacenarlos (motivo por el cual decimos
que son atributos virtuales).

A pesar de que los atributos definidos como fórmula no se crearán como campos físicos en ninguna tabla de
la base de datos, cada uno de ellos estará “asociado” a una tabla (siguiendo los mismos criterios de
normalización que se utilizan para determinar en qué tabla corresponde almacenar un atributo común).
Expresiones del estilo: “la tabla donde está el atributo fórmula x” no son correctas; nos referimos en cambio
a la tabla “asociada” o tabla “base” de un atributo fórmula.

117
Fórmulas vs. Asignaciones

atributo = expresión

• Puede especificarse de dos maneras:


• mediante una fórmula
• mediante una regla de asignación

• Diferencias:

fórmula Æ atributo virtual


regla Æ atributo almacenado

fórmula Æ global
regla Æ local

Cuando el valor de un atributo puede obtenerse mediante un cálculo, existen dos opciones para reflejar este
conocimiento:

• Definir al atributo como fórmula con el cálculo correspondiente


ó
• Reflejar el cálculo mediante una regla de asignación, dentro de la transacción.

La sintaxis de definición en ambos casos es similar, sin embargo, existen diferencias importantes entre
ambas alternativas:

1. Un atributo definido como fórmula no se creará como campo físico en una tabla de la base de datos (a no
ser que se especifique lo contrario, definiéndolo como redundante, como se verá más adelante). En cambio,
si se especifica su cálculo por medio de una regla de asignación dentro de la transacción, el atributo no
presentará ninguna diferencia con respecto al resto de los atributos que no son fórmula y al igual que los
otros, se almacenará en la tabla que corresponda.

2. La asignación de un cálculo a un atributo mediante la definición del atributo como fórmula es una
definición global. Esto significa que el valor del atributo en tiempo de ejecución siempre se mostrará
actualizado al ejecutar todo programa que lo utilice, ya que su valor se calculará en el momento. Por el
contrario, la asignación de un cálculo a un atributo mediante la definición de una regla de asignación en una
transacción, es una definición local. Esto significa que solamente si se ejecuta la transacción que tiene
definida esa regla1, el cálculo se efectuará y se almacenará el valor resultante en el campo físico
correspondiente. De existir otro objeto definido en la base de conocimiento que actualice la tabla que
contiene al atributo cuyo valor se debe calcular, habrá que definir explícitamente en el mismo la asignación
del cálculo al atributo.

--------------------------------------------------------------------------------------------------------------
1 La transacción podrá ejecutarse como tal o como business components, como veremos más adelante. En

cualquiera de estos casos, las reglas de la transacción se ejecutarán.

118
Clasificación

• Fórmulas Horizontales o Expresión


• Permiten definir una o varias expresiones aritméticas
condicionales

• Fórmulas Verticales
• SUM
• COUNT
Son incondicionales

• Fórmulas Aggregate/Select
• Aggregate: Sum, Count
• Select: Max, Min, Find
Son condicionales

• Fórmulas Compuestas
Varias expresiones Horizontales y/o Aggregate/Select condicionales

Podemos dividirlas conceptualmente en cuatro categorías:

• Fórmulas Horizontales o Expresión:


Una fórmula de este tipo, permite definir una o varias expresiones condicionales.
Los atributos involucrados en la fórmula deberán pertenecer a la tabla extendida de la tabla asociada al
atributo fórmula (tabla base de la fórmula).
Son condicionales, en el sentido que pueden incluir condición de disparo.

• Fórmulas Verticales:
Las fórmulas verticales son dos: SUM y COUNT.
SUM permite sumar y COUNT permite contar todas las ocurrencias del atributo referenciado, en la tabla en la
que se encuentre.
El atributo referenciado en la fórmula deberá pertenecer a una tabla directamente subordinada a la tabla
asociada (tabla base) del atributo fórmula, y no podrá ser un atributo primario.
Son incondicionales, significando esto que no permiten que se les incluya condición de disparo ni condiciones
sobre los registros a ser sumados/contados.

• Fórmulas Aggregate / Select:


Una fórmula de este tipo permite buscar, sumar, contar atributos que cumplan determinadas condiciones, en
cualquier tabla del modelo.
Son condicionales, en el sentido que pueden incluir condición de disparo y también condiciones sobre los
registros a ser buscados/sumados/contados.

• Fórmulas Compuestas:
Una fórmula de este tipo está compuesta por varias fórmulas Aggregate/Select condicionales, pudiendo también
contener expresiones horizontales.

Como se puede percibir, dependiendo de la clase de fórmula que se defina, se podrán involucrar en la misma
atributos de tablas que tengan determinada relación en particular con respecto a la tabla asociada al atributo
fórmula.

En el presente curso no ahondaremos en las últimas dos clases de fórmulas. Para un estudio de las mismas
recomendamos dirigirse al curso no presencial de GeneXus, donde encontrará un tratamiento profundo de estos
temas.

119
Fórmulas Horizontales

atributo = exp1 [if cond1]


[; exp2 [if cond2]]
........................
[; expn [ otherwise | if condn]]

Donde:
• expi : cualquier expresión válida (atributos, constantes,
funciones, operadores).
Los atributos involucrados deben ser de la tabla extendida.

• condi : condición de disparo, es cualquier expresión lógica


válida. Los atributos involucrados deben ser de la tabla
extendida.

expi : puede ser cualquier expresión válida, pudiendo contener atributos pertenecientes a la tabla
extendida de la tabla asociada al atributo que se está definiendo como fórmula y/o constantes y/o
funciones y/o operadores aritméticos (+,-,*,/,^) y/o operadores de strings (+) y/o operadores de fechas (+,-
). Otra posibilidad es que contenga una invocación con udp (comando que veremos en breve) a un
procedimiento GeneXus o a un programa externo que retorne un valor. El resultado -ya sea de las
expresiones o el retornado por el programa invocado- deberá ser del mismo tipo de datos que el del atributo
que se está definiendo como fórmula.

condi : es cualquier expresión lógica válida, pudiendo contener atributos pertenecientes a la tabla
extendida de la tabla asociada al atributo que se está definiendo como fórmula y/o constantes y/o
funciones y/o operadores lógicos (and, or, not) y/o operadores de comparación (>, >=, <, <=, =, like). La
primera condición que al ser evaluada dé True, provocará que el resultado de la fórmula sea el de la
expresión de la izquierda de esa condición (las demás no se seguirán evaluando).

otherwise: cuando ninguna de las condiciones evaluadas dan True, si existe una expi con cláusula
otherwise, el resultado de la fórmula será el de la expresión expi que anteceda a esta cláusula.

120
Fórmulas Horizontales: Ejemplos

Transacción “Customer” TABLA CUSTOMER


CusteromerId*
CustomerName CustomerId*
CustomerTotalPurchases CustomerName
CustomerTotalPayments CustomerTotalPurchases
CustomerBalance CustomerTotalPayments

CustomerBalance = CustomerTotalPurchases
- CustomerTotalPayments

En la transacción "Customer" del ejemplo, como se puede observar, además de los atributos
correspondientes a los datos personales del cliente, existen los atributos CustomerTotalPurchases
(total de compras del cliente) y CustomerTotalPayments (total de pagos del cliente), y se necesita
saber en todo momento cuál es el saldo del cliente, por lo qué también aparece el atributo
CustomerBalance.
Este saldo del cliente se calcula siempre igual: como la diferencia entre su total de compras
CustomerTotalPurchases y su total de pagos CustomerTotalPayments.
Como el cálculo que debe realizarse es siempre ese, tenemos dos alternativas para definirlo:

1. Definir las siguientes reglas en la transacción “Customer”:

CustomerBalance = CustomerTotalPurchases – CustomerTotalPayments;


Noaccept(CustomerBalance);
/*para que el usuario no pueda modificar el valor calculado de CustomerBalance*/

2. Definir a CustomerBalance como un atributo fórmula horizontal.

Analizando las diferencias entre ambas alternativas decidimos optar por la segunda. Para ello, lo
único que tendremos que hacer estando en el modelo de Diseño, es abrir la transacción "Customer"
y agregar en su estructura al atributo CustomerBalance. Se deberá ingresar el nombre, descripción
y tipo de datos del atributo, y en el campo fórmula lo siguiente: CustomerTotalPurchases –
CustomerTotalPayments.

Con esta definición GeneXus tendrá el conocimiento de que el atributo CustomerBalance será un
atributo virtual y sabrá de qué forma calcular su valor. Cuando el analista pase a un modelo de
Prototipo o Producción y haya un análisis de impacto, la definición del atributo CustomerBalance no
causará la creación del campo físico en la tabla CUSTOMER.

Este es un ejemplo de fórmula horizontal, debido a que se trata de un cálculo aritmético que
involucra atributos de la tabla extendida de la tabla asociada al atributo definido como fórmula
(en particular, en este caso los atributos involucrados pertenecen a la propia tabla base del atributo
fórmula).

121
Fórmulas Horizontales: Ejemplos
InvoiceId*
CustomerId CustomerId*
InvoiceDate CustomerName CountryId*
InvoiceTotal CountryId CountryName

INVOICE CUSTOMER COUNTRY

INVOICELINE PRODUCT ProductId*


ProductName
InvoiceId* ProductPrice
ProductId* ProductStock
InvoiceLineQuantity
InvoiceLineAmount

InvoiceLineAmount =
ProductPrice*InvoiceLineQuantity if InvoiceLineQuantity<=100;
ProductPrice*InvoiceLineQuantity*0.9 otherwise;

Supongamos que queremos definir al atributo InvoiceLineAmount (importe de línea de factura) como la
siguiente fórmula: InvoiceLineQuantity * ProductPrice (incondicional).
Al hacerlo, si bien el atributo estará “asociado” a la tabla INVOICELINE, no se almacenará en ella (diremos que
INVOICELINE será su tabla base).

La definición de esta fórmula se compondrá de una expresión aritmética sin condiciones de disparo, porque su
valor siempre se calcula de la misma forma. En la expresión aritmética intervienen los atributos
InvoiceLineQuantity y ProductPrice, ambos pertenecientes a las tablas base y extendida del atributo fórmula
respectivamente.

Si, en cambio, se necesita definir que cuando la cantidad de producto llevada por el cliente supere las 100
unidades, se haga un 10% de descuento al importe de línea correspondiente, la fórmula se transformará en la
que aparece arriba en la transparencia, en la que hay dos expresiones condicionales.

Si bien elegimos condicionar la segunda expresión con la cláusula otherwise, hubiese sido equivalente definir:

ProductPrice * InvoiceLineQuantity if InvoiceLineQuantity <=100;


ProductPrice * InvoiceLineQuantity * 0,9 if InvoiceLineQuantity > 100;

ya que las dos condiciones de disparo son disjuntas y no dejan fuera ninguna posibilidad.

122
Fórmulas Verticales

atributo = SUM(att) | COUNT(att)

Características:
• Suman o cuentan todas las ocurrencias del atributo att en
una tabla.
• La tabla asociada a att debe ser directamente
subordinada de la tabla base del atributo fórmula.
• att no puede formar parte de ninguna clave del modelo
(no puede ser atributo primario).
• Son incondicionales.

Las fórmulas verticales son dos: SUM y COUNT.

SUM permite sumar los valores contenidos en el atributo att para todos los registros de la tabla asociada a
él, que necesariamente deberá ser directamente subordinada a la del atributo que estamos definiendo
como fórmula.
COUNT permite contar todas las ocurrencias del atributo referenciado en la fórmula, que también debe
pertenecer a una tabla directamente subordinada a la tabla base del atributo que se está definiendo
como fórmula.

Las fórmulas verticales son incondicionales, es decir, no permiten que se les incluya condiciones sobre los
registros de la tabla subordinada a ser sumados/contados.

Sintaxis: SUM(att) | COUNT(att)

donde:

att: Puede ser un atributo almacenado en una tabla directamente subordinada a la tabla base del
atributo que se está definiendo como fórmula, o un atributo fórmula horizontal o vertical asociado también
a una tabla directamente subordinada a la tabla base del atributo fórmula. En cambio, no podrá ser un
atributo fórmula Aggregate/Select, ni involucrar directa o indirectamente a un atributo fórmula
Aggregate/Select.
No puede formar parte de ninguna clave del modelo.
Para el caso de fórmula SUM, debe ser de tipo numérico.
El tipo de datos del atributo fórmula debe ser numérico.

123
Fórmulas Verticales

Características (continuación):

• Se consideran los registros “relacionados”:

Relación de subordinación directa:

Tabla Tabla
Base Navegada

los registros están relacionados

• Para cada registro de la tabla base para el que se está


evaluando la fórmula, se suman/cuentan los registros de
la tabla navegada relacionados.

Registros sumados/contados:
Si bien no pueden establecerse condiciones sobre los registros de la tabla subordinada para que éstos
sean utilizados en la suma/cuenta (incondicionalidad de las fórmulas verticales), en la resolución de
la fórmula GeneXus no considera todos los registros de la tabla subordinada.

Por el contrario, solo se suman/cuentan aquellos que estén “relacionados” con el registro de la tabla
base para el cuál la fórmula se está evaluando.

Quedará más claro este punto con los ejemplos que veremos a continuación.

124
Fórmulas Verticales: Ejemplos

Transacción "Invoice"
InvoiceId*
CustomerId INVOICE InvoiceId*
CustomerId
CustomerName 1
InvoiceDate
InvoiceDate
InvoiceTotal = SUM(InvoiceLineAmount)
N
(ProductId*
ProductDescription INVOICELINE
ProductPrice
InvoiceId*
InvoiceLineQuantity
ProductId*
InvoiceLineAmount = ProductPrice*InvoiceLineQuantity
InvoiceLineQuantity
)

• InvoiceTotal e InvoiceLineAmount son fórmulas Æ no están almacenados


• En el cálculo de InvoiceTotal solo intervienen los registros que cumplan:
INVOICELINE.InvoiceId = INVOICE.InvoiceId

Queremos definir en la transacción "Invoice" que el importe total (InvoiceTotal) se calcule automáticamente
como la suma de los importes de las líneas (InvoiceLineAmount).

El atributo InvoiceTotal se encuentra en el primer nivel de la transacción "Invoice" (siendo su tabla asociada:
INVOICE) y el atributo InvoiceLineAmount se encuentra en el segundo nivel de la transacción "Invoice"
(siendo su tabla asociada: INVOICELINE).

Es posible definir al atributo InvoiceTotal como la fórmula vertical: SUM(InvoiceLineAmount) ya que el


atributo que necesitamos referenciar en la fórmula para ser sumado (InvoiceLineAmount) pertenece a una
tabla directamente subordinada (INVOICELINE) a la tabla base (INVOICE) del atributo que queremos definir
como fórmula (InvoiceTotal).

Las fórmulas verticales no permiten que se les incluya condiciones de filtro. Por ejemplo, no podremos definir
con una fórmula vertical “contar las líneas de la factura que cumplan que la cantidad de producto llevada
supere las 100 unidades”. Solamente podremos “contar las líneas de la factura” (todas).

Sin embargo, como resulta evidente en el ejemplo que venimos viendo, los registros de INVOICELINE que
intervendrán en el cálculo serán sólo los relacionados con el registro de INVOICE para el que se esté
calculando InvoiceTotal. Es decir, se contemplarán todos aquellos registros de INVOICELINE que cumplan,
para el registro actual de INVOICE:
INVOICELINE.InvoiceId = INVOICE.InvoiceId.

De modo que si bien las fórmulas verticales no permiten que se les incluya condiciones sobre los registros de
la tabla directamente subordinada a ser sumados/contados, en la resolución de la fórmula GeneXus no
considera todos los registros de la tabla subordinada, sino solamente aquellos que estén relacionados con el
registro de la tabla base para el cuál se esté evaluando la fórmula.

En este ejemplo, el atributo sumado (InvoiceLineAmount) es un atributo fórmula horizontal asociado a la


tabla INVOICELINE que es directamente subordinada a la tabla INVOICE.

125
Fórmulas Verticales: Ejemplos

Transacción "Invoice"
InvoiceId*
CustomerId INVOICE InvoiceId*
CustomerId
CustomerName 1
InvoiceDate
InvoiceDate
InvoiceLines = COUNT(InvoiceLineQuantity)
N
(ProductId*
ProductDescription INVOICELINE
ProductPrice
InvoiceId*
InvoiceLineQuantity
ProductId*
InvoiceLineAmount = ProductPrice*InvoiceLineQuantity
InvoiceLineQuantity
)

• InvoiceLines e InvoiceLineAmount son fórmulas Æ no están almacenados


• En el cálculo de InvoiceLines solo intervienen los registros que cumplan:
INVOICELINE.InvoiceId = INVOICE.InvoiceId

Si necesitamos contar la cantidad de líneas que tiene una factura, podemos agregar un atributo en el primer
nivel de la estructura de la transacción "Invoice" (de nombre InvoiceLines), y definirlo como la fórmula:

COUNT(InvoiceLineQuantity)
o:
COUNT(InvoiceLineAmount)

Es exactamente lo mismo optar por referenciar a cualquiera de los atributos anteriores en la fórmula COUNT,
ya que el primero es un atributo almacenado en la tabla INVOICELINE y el segundo es un atributo fórmula
asociado a la tabla INVOICELINE, la cual es directamente subordinada a la tabla INVOICE (asociada al
atributo InvoiceLines que se está definiendo como fórmula). En cambio a los atributos InvoiceLineId y
ProductId no es posible referenciarlos en la definición de la fórmula COUNT porque si bien se encuentran en
la tabla INVOICELINE, forman parte de alguna clave.

126
Fórmulas Verticales: Ejemplos
Transacción “Customer"
CustomerId* CUSTOMER
CustomerName
CustomerTotalPurchases= SUM(InvoiceTotal)
1 CustomerId*
CustomerName
CustomerTotalPayments
CustomerTotalPayments
CustomerBalance N

InvoiceId* INVOICE
CustomerId 1
InvoiceDate N
INVOICELINE
InvoiceTotal =
SUM( InvoiceLineAmount) InvoiceId*
ProductId*
InvoiceLineQuantity
CUSTOMER e INVOICE:
Relación 1-N Æ hay subordinación InvoiceLineAmount =
directa Æ vale la fórmula ProductPrice*InvoiceLineAmount

En este otro ejemplo, el atributo CustomerTotalPurchases de la transacción "Customer" representa el total de


compras efectuadas por el cliente. Este total de compras se puede calcular como la suma de los totales de las
facturas realizadas para ese cliente.

Por tanto podemos definirlo como la fórmula:

CustomerTotalPurchases = SUM( InvoiceTotal )

GeneXus la interpretará como una fórmula vertical, ya que la tabla base es CUSTOMER e InvoiceTotal está
asociado a INVOICE, tabla directamente subordinada a CUSTOMER.

Observar que en este caso, InvoiceTotal también es una fórmula vertical.

Como restricción, no se pueden utilizar más de dos fórmulas SUM anidadas. Es decir, es posible definir una
fórmula SUM que involucre otra fórmula SUM, pero ésta última no puede involucrar otra fórmula SUM.

Lo mismo ocurre con la anidación de fórmulas COUNT.

127
Fórmulas Verticales

Características (continuación):

• Navegación vertical: Performance y Redundancia

• Equivalencia entre SUM redundante y regla add

CustomerTotalPurchases = SUM(InvoiceTotal) Y redundante



CustomerTotalPurchases atributo secundario (no fórmula) y
calculado mediante la siguiente regla (en "Invoice"):
add(InvoiceTotal, CustomerTotalPurchases)

Navegación vertical: Performance y Redundancia

El hecho de que una fórmula no esté almacenada puede ocasionar en algunos casos problemas de
performance debido al tiempo que puede demorar el cálculo. Esto dependerá de la cantidad de registros
que se sumen o cuenten.

Supongamos, por ejemplo, que tenemos miles de facturas, cada una de las cuales puede tener miles de
líneas. Si frecuentemente se requiere sacar listados de las facturas mostrando sus totales, y los totales se
calculan mediante la definición de un atributo fórmula SUM, por cada una de las miles de facturas, se
deberán navegar miles de registros correspondientes a sus líneas para realizar el cálculo del total. Esto
representa un costo elevado de performance.

Para evitar este inconveniente, GeneXus provee la posibilidad de definir a un atributo fórmula como
redundante. Al hacerlo, el atributo deja de ser virtual y pasa a estar almacenado en la tabla asociada.

En tiempo de ejecución, al utilizar una transacción que contiene un atributo fórmula redundante, el
comportamiento es el siguiente: la fórmula se dispara efectuándose el cálculo y el resultado se almacena
en el campo físico de la base de datos.

En los programas generados correspondientes a transacciones que tienen involucrados atributos fórmulas
redundantes, GeneXus incorpora rutinas que se encargan de almacenar los datos redundantes, siempre
que se recalculen.

Al consultar un atributo fórmula redundante, no se dispara la fórmula para obtener el cálculo sino que se
toma el valor almacenado del campo de la base de datos.

Un atributo fórmula redundante:


• A diferencia de un atributo fórmula simple: estará almacenado en la base de datos.
• A diferencia de un atributo común: GeneXus tendrá el conocimiento de cómo calcular su valor y en los
programas generados correspondientes a transacciones, incluirá rutinas para mantener actualizado el
valor almacenado.

128
Equivalencia entre definir un atributo fórmula SUM redundante y utilizar regla add

Definir un atributo como fórmula SUM redundante es equivalente a que el atributo sea común (no fórmula) y
definir una regla add en las transacciones en las cuales se encuentre, para efectuar el mismo cálculo. Ambas
cosas son equivalentes, con la salvedad de que la definición de la fórmula es global (está asociada al
atributo), mientras que la definición de la regla es local (es decir, la regla add se va a ejecutar solamente
cuando se hagan ABM1 a través de la transacción en la cual está definida la regla).

Por ejemplo, definir al atributo InvoiceTotal como la fórmula: SUM(InvoiceLineAmount) redundante, es


equivalente a que el atributo InvoiceTotal no sea definido como fórmula, e incluir la regla:
add(InvoiceLineAmount, InvoiceTotal) en la transacción "Invoice".

Si optamos por lo último:

− Cada vez que se inserte una línea, la regla add sumará el valor de InvoiceLineAmount al valor de
InvoiceTotal.
− Cada vez que se elimine una línea, la regla add restará el valor de InvoiceLineAmount al valor de
InvoiceTotal.
− Cada vez que cambie el valor de InvoiceLineAmount de una línea (porque se haya modificado el valor de
alguno de los atributos que participan en la definición de su fórmula), la regla add sumará a InvoiceTotal, la
diferencia entre el valor nuevo y el viejo de InvoiceLineAmount.

Como sabemos, de definir al atributo InvoiceTotal como la fórmula: SUM(InvoiceLineAmount), el


comportamiento sería el mismo.

Una de las diferencias a tener en cuenta a la hora de decidir si calcular el valor de un atributo mediante la
definición de una fórmula o regla, es que con la primera alternativa, el atributo no se almacenará y con la
segunda, si. Sin embargo, como nos estamos refiriendo a definir al atributo InvoiceTotal como la fórmula:
SUM(InvoiceLineAmount) redundante, el atributo InvoiceTotal se almacenará en su tabla asociada (INVOICE),
de igual forma que si se define al atributo InvoiceTotal común (no fórmula) y se incluye la regla:
add(InvoiceLineAmount, InvoiceTotal) en la transacción "Invoice" para efectuar el cálculo.

Concluyendo, estas dos alternativas son equivalentes, ya que en tiempo de ejecución en la medida que se
vayan insertando, modificando o eliminando líneas por medio de la transacción "Invoice", el valor del atributo
InvoiceTotal se irá calculando y su valor quedará almacenado en un atributo físico de la tabla INVOICE.

-------------------------------------------------------------------------------------------------------------------1 ABM
= Altas, Bajas y Modificaciones.

129
COMUNICACIÓN
ENTRE OBJETOS

130
Comunicación entre objetos

Procedimiento

Web Panel Transacción

Reporte PDF

Los objetos GeneXus pueden comunicarse entre ellos o con otros programas externos.

Un objeto GeneXus puede llamar o ser llamado por otro objeto, intercambiando información a
través de parámetros1.

Veremos a continuación cómo invocar desde un objeto a otro, y cómo especificar los parámetros
(en el objeto llamador y en el llamado) para el intercambio de la información.

El esquema presentado arriba ilustra las posibles interacciones entre objetos GeneXus para una
aplicación Web. Obsérvese que la flecha simple entre Web Panel y Reporte PDF (así como entre
Transacción y Reporte PDF) indica que un Web Panel podrá invocar a un Reporte2 pero un Reporte
no podrá invocar a un Web Panel (o transacción Web).

El esquema de interacción entre los objetos GeneXus Win es más flexible pues todos los objetos
pueden interactuar con todos. En ese caso habrá que sustituir al objeto Web Panel por el objeto
Work Panel, y los reportes tendrán también más flexibilidad, pues podrán invocarse en cualquier
circunstancia, y podrán emitirse en formatos varios (y no solo PDF).

----------------------------------------------------------------------------------------------------------
1 Para aplicaciones Web también se utilizan cookies para el intercambio de información.
2 En Web solamente podrá invocarse a reportes PDF, como mencionaremos en el capítulo que

estudia el objeto Reporte.

131
Comunicación entre objetos
Win - Web
2 posibilidades:

1) PgmName.Call(par1, …, parN) /*Invocación a PgmName*/

Parm(par1, …, parN); /*Declaración de parámetros


en el objeto invocado*/

2) att|&var = PgmName.Udp(par1, …, parN) /*Invocación a PgmName*/

Parm(par1, …, parN , parsalida); /*Declaración de parámetros


en el objeto invocado*/

CALL - Permite invocar a un objeto GeneXus o a un programa externo, tanto sin pasarle
parámetros, como pasándole.

UDP (User Defined Procedure) - Permite invocar a un objeto GeneXus o programa externo
tanto sin pasarle parámetros como pasándole, y con la particularidad de que el programa
llamado retornará necesariamente al menos un valor al programa que lo invocó. En Win la
utilización de UDP es más amplia que en Web, pues en Win todo objeto al finalizar su ejecución
devuelve el control al objeto llamador; sin embargo en Web esto no sucede en todos los objetos,
por lo que UDP se utiliza únicamente para invocar a procedimientos (debido a que estos cumplen la
condición de ejecutar y devolver el control al llamador).

Una invocación (ya sea con CALL o UDP) podrá escribirse en distintas partes del objeto llamador,
dependiendo de si el mismo es una transacción, web panel, procedimiento, etc.:

A su vez UDP puede utilizarse también en la definición de un atributo fórmula. Es decir, se define
que cierto atributo es una fórmula y que la definición de la misma consiste en la invocación a un
procedimiento utilizando UDP.

PARM – Cuando un objeto es invocado desde otro con parámetros, debe tener declarada la lista de
parámetros que recibe. Esta declaración se realiza mediante la regla: PARM.

A continuación daremos más detalles acerca del uso de CALL, UDP y PARM.

132
Comunicación entre objetos
Win - Web
2 posibilidades – Ejemplos:

1) Ej: RListInvoice.Call(InvoiceId)

NOTAR PREFIJO
En el reporte ListInvoice Æ Parm(InvoiceId)

2) Ej: &discount = PCalcDiscount.udp(ProductId,CustomerId)

En el proc CalcDiscount Æ Parm(ProductId,CustomerId , &discount);

Aquí mostramos un ejemplo de uso de CALL para realizar una invocación y otro ejemplo de uso de
UDP.

Dependiendo de qué objeto llamador se trate, estas invocaciones podrán escribirse en una sección u
otra del mismo, pero independientemente de eso, aquí apuntamos a mostrar que CALL permite
invocar a un objeto con estilo de invocación a un programa, mientras que UDP invoca a un objeto
con estilo de invocación a una función.

En el primer ejemplo se está utilizando CALL para invocar a un reporte (objeto ListInvoice)
pasándole 1 parámetro (InvoiceId). En el reporte invocado se ha declarado el parámetro que recibe
(en su sección de reglas, mediante la regla parm).

En el segundo ejemplo se está utilizando UDP para invocar a un procedimiento (objeto


CalcDiscount) pasándole 2 parámetros (ProductId, CustomerId). Ahora, observemos en la sintaxis
de la invocación al procedimiento, que el mismo retorna un valor (en la variable &discount). Por
este motivo, en el procedimiento invocado se han declarado 3 parámetros utilizando la regla parm:
los 2 parámetros recibidos + el parámetro de retorno en último lugar.

Podemos ver entonces que cuando se utiliza CALL para invocar a un objeto enviándole N
parámetros, se deben declarar los N parámetros (posicionales y del mismo tipo de datos que los
enviados) en el objeto invocado mediante la regla parm.

En cambio cuando se utiliza UDP para invocar a un objeto enviándole N parámetros:

• en la regla parm del objeto invocado se deben declarar N + 1.


• El último parámetro declarado en la regla parm del objeto invocado, corresponde al que se
encuentra al principio de todo en la invocación, es decir, al que recibe el valor retornado.
• En algún lugar del objeto invocado se le deberá asignar valor al parámetro de retorno.

133
Convención de nombrado de los objetos
GeneXus invocados

En la invocación a un objeto GeneXus, el nombre del objeto que


se invoca debe estar formado por: : Prefijo + nombre del
objeto

Tanto si se utiliza, CALL como UDP para invocar a un objeto GeneXus, el nombre del objeto que se
invoca debe estar formado por: un prefijo (que identifica al tipo de objeto) + el nombre del objeto.

La tabla de la transparencia muestra algunos prefijos según el tipo de objeto.

Por ejemplo, si se desea invocar desde algún objeto GeneXus a la transacción “Invoice” el nombre
que se deberá escribir en la invocación es TInvoice. A su vez, si se desea invocar desde algún objeto
GeneXus a un reporte de nombre “CustomersReport”, el nombre que se deberá escribir en la
invocación es RCustomersReport.

Recomendamos utilizar el ítem: Insert/Object de la barra de menú de GeneXus para seleccionar los
objetos que se deseen invocar, ya que automáticamente se insertarán con el prefijo que corresponda
y el analista no tendrá que recordarlo de memoria.

Nota: Si el objeto invocado es un programa externo, el nombre del mismo deberá incluirse entre
comillas en la invocación.

134
¿Qué declarar en la regla parm:
variable o atributo?

• Variable: Se podrá utilizar libremente, en la lógica del objeto


invocado:
• como condición de filtro por =, >, >=, <, <=, LIKE, etc.
• para alguna operación aritmética
• como bandera
• etc.

• Atributo: Automáticamente el mismo actuará como filtro por igualdad


en el objeto, no siendo posible modificar el valor recibido.

Al definir una invocación a un objeto (ya sea utilizando CALL o UDP), si se tienen que enviar datos
por parámetro al objeto invocado, resulta evidente determinar si enviar atributos y/o variables: si
un dato a ser enviado por parámetro, se encuentra en el objeto invocador, en un atributo, habrá
que enviar al mismo; y si se encuentra en una variable, habrá que enviar a la variable.

Sin embargo, al declarar la lista de parámetros en el objeto invocado, el programador GeneXus


deberá decidir para cada parámetro, si declararlo mediante un atributo o una variable,
independientemente de cómo haya sido enviado.

¿Cuál es la diferencia entre declarar un parámetro como variable o como atributo en la regla parm
del objeto invocado? Si se declara una variable, se la podrá utilizar libremente en la lógica del
objeto invocado: se la podrá utilizar como condición de filtro por igualdad, por mayor, mayor o
igual, menor, menor o igual, LIKE, etc.; se la podrá utilizar para alguna operación aritmética, como
bandera, o lo que se necesite. Si en cambio se declara un atributo, automáticamente el mismo
actuará como filtro por igualdad en el objeto, no siendo posible modificar el valor recibido.

Cuando lleguemos a la etapa del curso en la cual podamos invocar a reportes pasándoles
parámetros así como a otros objetos, podremos terminar de comprender esto ya que la práctica
nos permitirá ver este tema.

135
Definición de parámetros de entrada (in),
salida (out),entrada-salida (inout) en Parm

• Para cada parámetro que se declara en la regla parm, es posible definir si


se desea que el mismo opere:
− de entrada (in)
− de salida (out)
− de entrada-salida (inout)

• Ejemplo: parm(out:&par1, in:&par2, &par3, inout:&par4);

• Ventajas:
• Mejor especificación de la semántica de las interfaces.
• Independencia del lenguaje de generación.
• Optimizar el pasaje de parámetros de las aplicaciones de acuerdo a la
arquitectura en la que éstas se implementan (ventaja contrapuesta a la
anterior).

Para cada parámetro que se declara en la regla parm, es posible definir si se desea que el mismo opere: de
entrada (in), de salida (out), o de entrada-salida (inout).

Ejemplo: parm(out:&par1, in:&par2, &par3, inout:&par4);

Como se puede percibir claramente en la sintaxis del ejemplo, el primer parámetro definido es de salida, el
segundo parámetro es de entrada, y el cuarto parámetro es de entrada-salida. Cuando no se especifica
nada, como es el caso del tercer parámetro de la sintaxis, dependerá de lo siguiente:

• si el objeto fue invocado con CALL, el parámetro, será de entrada-salida.


• si el objeto fue invocado con UDP, y se trata del último parámetro, será de salida; y si se trata de otro
parámetro distinto del último, dependerá del lenguaje de generación.

Declarar explícitamente cómo se desea que cada parámetro opere, tiene las siguientes ventajas:
1. Mejor especificación de la semántica de las interfaces; es decir, tanto para GeneXus como para el
programador cuando trabaje con un objeto, será claro tener definido en la regla parm el objetivo de cada
parámetro, es decir:
- si el mismo vendrá con valor y luego de la ejecución del objeto invocado, se
devolverá al objeto invocador el valor con que haya quedado (inout).
- si el mismo vendrá con valor y luego de la ejecución del objeto invocado, no se
devolverá al objeto invocador el valor con que haya quedado (in).
- si el mismo no vendrá con valor y luego de la ejecución del objeto invocado, se
devolverá al objeto invocador el valor que tenga (out).

2. Independencia del lenguaje de generación; es decir, si se define explícitamente cómo se desea que cada
parámetro opere, al generar las aplicaciones utilizando diferentes lenguajes de generación no estará
cambiando el comportamiento de los parámetros en base al comportamiento por defecto del lenguaje de
generación correspondiente.

3. Optimizar el pasaje de parámetros de acuerdo a la arquitectura en la que éstas se generen (siendo una
ventaja contrapuesta a la anterior); esto se refiere a lo siguiente: para la mayoría de los lenguajes es más
eficiente pasar los parámetros por referencia (inout) que por valor (in / out); pero en Java, por ejemplo, los
parámetros solo se pueden pasar por valor, por lo que para lograr la funcionalidad de pasarlos por referencia
es necesario hacer conversiones de parámetros, lo cual puede redundar en un overhead importante; por otro
lado, cuando se trata de aplicaciones distribuidas (por ejemplo Java con RMI o HTTP), la utilización de
parámetros de tipo out tiene la ventaja de que no es necesario enviar al parámetro en la invocación, a
diferencia de si los parámetros se definen de inout (que implica que haya que pasar todos los parámetros);
esto tiene como consecuencia que se envíen más bytes de los necesarios, lo cual es inconveniente
especialmente en entornos tales como Internet.

136
Comunicación entre objetos
Web

Más posibilidades para definir invocacionesÆ Función Link

1) control.Link = PgmName.Link([,par1 …, parN])


Ej: imagen.Link = TCustomer.Link() Los parámetros son
opcionales, y en
caso de haber, se
declaran con regla
parm

2) control.Link = Link(URL)

Ej: imagen.Link = Link(‘http://www.artech.com.uy’)

La función Link se asocia a la propiedad link de un control dentro de cualquier evento de una
transacción o web panel1, teniendo como resultado que al hacer clic sobre dicho control se realizará la
llamada al objeto o URL referenciada en el link.

PgmName (el objeto invocado) podrá ser un web panel, transacción, o reporte PDF 2.

--------------------------------------------------------------------------------------------------------
1 a excepción del evento Load de los web panels (que veremos más adelante)
2 también un procedimiento HTTP, pero no profundizaremos sobre este concepto en este curso

137
Comunicación entre objetos
Web

Más posibilidades para definir invocaciones Æ Comando Link

1) PgmName.Link([,par1 …, parN])
Ej: TCustomer.Link(CustomerId)

2) Link(URL)
Ej: Link(‘http://www.google.com’)

El comando Link puede ser utilizado dentro de cualquier evento de una transacción o web panel1.

Cuando se ejecute el evento, al llegar a la sentencia con el comando Link, se redireccionará en


forma automática a la URL especificada.

En caso de utilizarse el comando Link como en el ejemplo 1, invocando a un PgmName (siendo


PgmName un web panel, transacción o reporte PDF), será equivalente a la utilización de Call.
Opcionalmente se podrán pasar parámetros al objeto invocado, debiendo declararse los mismos en
el objeto llamado, con la regla parm.

--------------------------------------------------------------------------------------------------------
1 a excepción del evento Load de los web panels (que veremos más adelante)
2 también un procedimiento HTTP, pero no profundizaremos sobre este concepto en este curso

138
ORDEN DE EJECUCIÓN DE REGLAS Y
FÓRMULAS

139
Orden de ejecución de
reglas y fórmulas
Transacción "Invoice"
InvoiceId* “Customer”
InvoiceDate CustomerId*
CustomerId CustomerName
CategoryId
CustomerTotalPurchases
CustomerTotalPurchases
CategoryDiscount
InvoiceDiscount = InvoiceSubTotal *CategoryDiscount “Category”
InvoiceShippingCharge = Max( ShippingDate, ShippingDate CategoryId*
<=InvoiceDate,,ShippingCharge) CategoryDiscount
InvoiceSubTotal = SUM( InvoiceLineAmount )
InvoiceTotal = InvoiceSubTotal – InvoiceDiscount “Shipping”
+ InvoiceShippingCharge ShippingDate*
(ProductId* ShippingCharge
ProductPrice
ProductStock “Product”
InvoiceLineQuantity ProductId*
ProductPrice
InvoiceLineAmount) = InvoiceLineQuantity *
ProducStock
ProductPrice

Reglas:
Add( InvoiceTotal, CustomerTotalPurchases);
Error( ‘Insufficient Stock’ ) if ProductStock<0;
Subtract( InvoiceLineQuantity, ProductStock);

La forma de programar el comportamiento de las transacciones es definiendo reglas, las cuales se


escriben de forma declarativa. A su vez si hay cálculos para efectuar, se puede optar por la
alternativa de definir atributos fórmula.

El programador GeneXus en ningún momento especifica la secuencia de ejecución de las reglas y


fórmulas definidas en una transacción, sin embargo al momento de generar, GeneXus determina las
dependencias existentes entre las reglas y fórmulas definidas.

Supongamos que estamos definiendo una aplicación para una empresa que vende determinados
productos, y que cuenta con un servicio de entrega a domicilio que lleva la mercadería a sus
clientes. Y definimos entre otras, las siguientes 5 transacciones:
"Customer" (para registrar los clientes de la empresa)
“Category” (a las que pertenece cada cliente)
“Shipping” (envíos: guarda un histórico de costos de envío)
"Invoice" (facturas que se emiten a los clientes)
"Product" (productos vendidos por la empresa)

Se resalta la estructura de la transacción "Invoice", con sus atributos fórmulas (algunas


horizontales, otras verticales, otras aggregate/select) y sus reglas declaradas.

¿En qué orden se dispararán las reglas y fórmulas de la transacción "Invoice"?

140
Árbol de evaluación
R. Add(InvoiceTotal, CustomerTotalPurchases);
F. InvoiceTotal = InvoiceSubTotal - InvoiceDiscount +
InvoiceShippingCharge
F. InvoiceDiscount = InvoiceSubTotal*CategoryDiscount CustomerTotalPurchases
F. InvoiceShippingCharge = MAX( ShippingDate, ShippingDate <=
InvoiceDate,,ShippingCharge)
F. InvoiceSubTotal = SUM( InvoiceLineAmount )
F. InvoiceLineAmount = InvoiceLineQuantity *ProductPrice InvoiceTotal
R. Subtract(InvoiceLineQuantity, ProductStock) ;
R. Error( ‘Insuffcient Stock’) if ProductStock < 0 ;

InvoiceDiscount
InvoiceShippingCharge
error (‘Insufficient Stock ’)
InvoiceSubTotal ShippingDate InvoiceDate

ShippingCharge
ProductStock InvoiceLineAmount CategoryDiscount

InvoiceLineQuantity ProductPrice

Al momento de generar el programa asociado a la transacción "Invoice", GeneXus extraerá las


dependencias existentes entre las reglas y fórmulas definidas; construirá lógicamente un árbol de
dependencias (o árbol de evaluación) que determinará la secuencia de evaluación.

Podemos imaginar que el árbol se ejecuta de abajo hacia arriba, es decir que cada vez que
cambia el valor de un atributo, se ejecutan todas las reglas y fórmulas que dependen de ese
atributo (y que en el árbol se encuentran hacia arriba).

Por ejemplo, si cambia la cantidad de una línea de una factura (InvoiceLineQuantity), como este
atributo interviene en la fórmula que calcula el importe de la línea (InvoiceLineAmount), dicha
fórmula se redisparará. Por cambiar el importe de una línea, deberá redispararse la fórmula
correspondiente al subtotal de la factura (InvoiceSubTotal) y en consecuencia, también deberá
recalcularse la fórmula correspondiente al descuento (InvoiceDiscount), ya que depende del
subtotal. Deberá redispararse también la fórmula correspondiente al total de la factura
(InvoiceTotal) ya que depende tanto del valor de InvoiceSubTotal como del valor de
InvoiceDiscount. Por último, por cambiar el total también se tendrá que disparar la regla
Add(InvoiceTotal, CustomerTotalPurchases);.

Además de dispararse todas las fórmulas y reglas involucradas en la rama derecha del árbol desde
el atributo InvoiceLineQuantity, también se dispararán las fórmulas y reglas involucradas en la rama
izquierda. Es decir, que al cambiar el valor del atributo InvoiceLineQuantity, se redisparará también
la regla Subtract(InvoiceLineQuantity, ProductStock); y en consecuencia, por modificar esta regla
el valor del atributo ProductStock, se evaluará si habrá que disparar la regla Error(‘Stock
Insuficiente’) if ProductStock < 0;

Concluyendo, las reglas y fórmulas que se definen en una transacción suelen estar interrelacionadas
y GeneXus determina las dependencias entre ellas así como su orden de evaluación.

Observemos las 2 últimas reglas definidas:


Subtract(InvoiceLineQuantity, ProductStock);
Error(‘Insufficient Stock’) if ProductStock < 0;

141
Estas reglas están interrelacionadas porque las dos involucran al atributo ProductStock. Ahora, mientras la
segunda solamente consulta su valor, la primera lo actualiza. Entonces, la regla que actualiza al atributo será la
que se disparará primero, y luego se disparará la que lo consulta.

Toda regla que actualice el valor de un atributo se disparará antes que una regla que lo consulte (esto se puede
observar claramente en el árbol). Por este motivo es que la regla Error consulta si el atributo ProductStock quedó
con valor negativo; porque como dijimos la sustracción se realizará primero.

En la programación clásica se suele consultar primero si alcanza el stock, y en caso de que sea suficiente recién se
hace la sustracción. Por eso quienes están aprendiendo GeneXus pueden intuitivamente escribir la regla:
Error(‘Insufficient Stock') if InvoiceLineQuantity > ProductStock. Esta sintaxis es correcta, sin embargo no es
correcta su lógica ya que como venimos explicando, en el árbol de evaluación determinado por GeneXus primero
se disparará la regla Subtract y luego la regla Error; por lo tanto tendremos que especificar que se dispare el
mensaje de error si es que quedó el stock con valor negativo, dado que ya se habrá ejecutado la sustracción al
momento de consultar el valor de ProductStock.

Así que la regla que se debe definir es:

Error(‘Insufficient Stock’) if ProductStock < 0;

Y no:

Error('Insufficient Stock') if InvoiceLineQuantity > ProductStock;

Cuando se dispara una regla Error, se detiene cualquier actualización a la base de datos y se desarma el
árbol de evaluación, quedando todo en el estado anterior a producirse el error. Siguiendo el ejemplo que
veníamos viendo, si al dispararse la regla Subtract el stock quedara negativo, se dispararía la regla Error. Como
consecuencia de dispararse la regla Error, se desharía el Subtract que se había ejecutado, así como todas las
demás reglas y fórmulas que se hayan ejecutado (recálculo de los atributos InvoiceLineAmount, InvoiceSubTotal,
...., CustomerTotalPurchases).

¿Cómo podemos ver el orden de evaluación determinado por GeneXus?

Al especificar una o varias transacciones, seleccionando la opción Detailed Navigation, se detallará en el listado
de navegación resultante el orden de ejecución de todas las reglas y fórmulas definidas en la transacción.

142
Alteraciones del orden de disparo
de las reglas
Error

Total Total
SupplierId* Calculado Ingresado
InvoiceId*
...
InvoiceEntTotal Entered Total
InvoiceLineAmount
( ProductId*
InvoiceLineQuantity
InvoiceLinePrice
InvoiceLineAmount = InvoiceLinePrice * InvoiceLineQuantity)
...
InvoiceCalcTotal = SUM(InvoiceLineAmount) Calculated Total

Error(‘The calculated total doesn’t match with the entered total') if


(InvoiceEntTotal<>InvoiceCalcTotal) On AfterLevel Level ProductId;

En la mayoría de los casos el orden de ejecución de las reglas definido por GeneXus a partir de
nuestras especificaciones es el deseado. Pero en algunos casos podemos querer cambiar el momento
de disparo de una regla.

Ejemplo:
Definimos una transacción para registrar las facturas que nos entregan nuestros proveedores.
El identificador del primer nivel es compuesto por el identificador de proveedor y el identificador de
factura, ya que el número de factura no nos sirve como identificador único, porque proveedores
distintos pueden repetir el mismo número de factura.
Para cada factura de un proveedor que se ingrese, nos interesa controlar que el total que venga
escrito en la factura (y que se ingresará en el atributo InvoiceEntTotal) sea correcto. Para hacer este
control, definimos al atributo InvoiceCalcTotal como fórmula vertical SUM(InvoiceLineAmount), y
agregamos una regla Error que se disparará si no coinciden los valores de los atributos
InvoiceEntTotal y InvoiceCalcTotal:
Error('El total ingresado no coincide con el total calculado') if InvoiceCalcTotal <> InvoiceEntTotal;
Si construimos el árbol de evaluación correspondiente a las fórmulas y regla que hemos definido en
esta transacción:

143
vemos que las dependencias indican que cada vez que se agreguen, modifiquen o eliminen valores de los atributos
InvoiceLinePrice e InvoiceLineQuantity en las líneas, se recalculará el valor del atributo InvoiceLineAmount
correspondiente; en consecuencia, se recalculará el valor del atributo fórmula InvoiceCalcTotal que hemos definido
para tener el total calculado de la factura; y como este atributo está involucrado en la condición de disparo de la
regla Error, si se cumple dicha condición de disparo, se disparará la regla Error(‘El total ingresado no coincide con
el total calculado’) if InvoiceCalcTotal <> InvoiceEntTotal.

Ahora, prestemos atención a que la condición de disparo “InvoiceCalcTotal <> InvoiceEntTotal” se va a cumplir
repetidamente en la medida que el operador vaya ingresando líneas, porque para cada línea que se ingrese se
calculará el valor del atributo fórmula InvoiceLineAmount de la línea, y en consecuencia se recalculará el valor del
atributo fórmula InvoiceCalcTotal. Pero el valor calculado de este atributo no coincidirá con el valor ingresado en el
atributo InvoiceEntTotal hasta que no se hayan ingresado todas las líneas de la factura; entonces, se disparará la
regla Error(‘The calculated total doesn’t match with the entered total’) if InvoiceCalcTotal <> InvoiceEntTotal;.

Concluimos entonces que en este caso no nos sirve lo que determina el árbol de evaluación, ya que no queremos
que se evalúe la condición de disparo de la regla Error cada vez que el operador ingrese, modifique o elimine líneas,
sino que recién necesitamos que se evalúe cuando el usuario haya terminado de trabajar con todas las líneas de la
factura.

GeneXus ofrece eventos o momentos de disparo en las transacciones, que ocurren antes o después de
determinada acción, como la grabación del cabezal, o de una línea. Las reglas de las transacciones pueden
condicionarse de manera tal de dispararse en el preciso instante en que ocurre alguno de esos eventos de disparo.
Siguiendo el ejemplo que veníamos viendo, existe un evento de disparo que ocurre luego de iterar en un nivel y
salir del mismo. La sintaxis de este evento de disparo es: AfterLevel Level Atributo, debiendo ser Atributo un
atributo perteneciente al nivel que se ha iterado y se abandona.

De modo que a la regla Error de nuestro ejemplo, le agregaríamos este evento de disparo, y quedaría definida de la
siguiente forma:

Error(‘The calculated total doesn’t match with the entered total’) if InvoiceCalcTotal<>InvoiceEntTotal On
AfterLevel Level InvoiceLinePrice;

Con este evento de disparo que hemos agregado a la regla logramos controlar lo que deseábamos en el momento
adecuado.

Además de este evento de disparo, existen otros que veremos a continuación.

144
Eventos de disparo

Î La mayoría de las reglas de transacciones permiten que se les


agregue de ser necesario un evento o momento de
disparo.

Î Al agregar un evento o momento de disparo a una regla,


estaremos especificando que la regla se deberá ejecutar en
ese determinado momento.

•BeforeValidate
Î Eventos de disparo:
•AfterValidate
•BeforeInsert, BeforeUpdate, BeforeDelete
•AfterInsert, AfterUpdate, AfterDelete
•AfterLevel
•BeforeComplete
•AfterComplete

Al momento de la confirmación de la transacción, ocurre una serie de acciones que es necesario conocer para poder
programar correctamente el comportamiento de las reglas.
Para una transacción de dos niveles, podríamos enumerarlas como sigue:
• validación de los datos del cabezal
• grabación física del cabezal (ya sea inserción, modificación o eliminación)
• validación de los datos de la primera línea
• grabación física de los datos de la primera línea
• validación de los datos de la segunda línea
• grabación física de los datos de la segunda línea
•…
• validación de los datos de la n-ésima línea
• grabación física de los datos de la n-ésima línea
• commit

La acción de “validación de los datos del cabezal” ocurre cuando se han validado todos y cada uno de los campos
ingresados en el cabezal. Observar que en este punto ya se han disparado todas las reglas que correspondían a
atributos del cabezal y que no tenían evento de disparo asociado (ejemplo: Default(InvoiceDate, &today)).
Inmediatamente después se grabará el registro correspondiente al cabezal.

Análogo es el caso de las líneas: “la validación de los datos de una línea” ocurre cuando ya se han validado todos y
cada uno de los datos de la línea, y también se han disparado todas las reglas correspondientes según el árbol de
evaluación (ejemplo: subtract( InvoiceLineQuantity, ProductStock)). Inmediatamente después de esta acción de
validación, se grabará físicamente el registro correspondiente a la línea.

Cada transacción, al terminar de trabajar con un cabezal y sus líneas, realiza un commit (es automático; será
colocado en el código generado por GeneXus, a menos que el analista especifique lo contrario, como veremos más
adelante). Es decir, si se van a ingresar los datos de dos facturas distintas utilizando la transacción “Invoice”, luego
de ingresados los datos de la primera, se commitearán sus registros, y luego se ingresará la segunda, al cabo de lo
cuál se commitearán sus registros.

Los eventos de disparo de reglas permiten definir que se ejecuten antes o después de alguna de las acciones que
acabamos de enumerar. Veremos cuándo ocurre cada evento de disparo.

145
Evento de disparo: BeforeValidate
Este evento de disparo ocurre un instante de tiempo antes de que la información de la instancia con la que se está
trabajando (cabezal o línea x) sea validada (o confirmada). Es decir, ocurrirá un instante de tiempo antes de la
acción de “validación del cabezal” o “validación de la línea”, según corresponda. Observar que aquí también se
habrán disparado todas las reglas según el árbol de evaluación que no estén condicionadas a evento de disparo
alguno.

Eventos de disparo: AfterValidate, BeforeInsert, BeforeUdate, BeforeDelete

El evento de disparo AfterValidate permite especificar que una regla se ejecute inmediatamente antes de que se
grabe físicamente cada instancia del nivel al cual está asociada la regla, en la tabla física correspondiente, y después
de que se hayan validado los datos de esa instancia.

En otras palabras, si se le agrega el evento de disparo AfterValidate a una regla, la misma se ejecutará para cada
instancia del nivel al cual esté asociada, inmediatamente antes de que la instancia se grabe físicamente (ya
sea que se inserte, modifique o elimine) como registro en la tabla física asociada al nivel.

EJEMPLOS

1. Hay veces en las que no contamos con la posibilidad de utilizar la propiedad Autonumber para numerar de forma
automática y correlativa los atributos que son clave primaria simple. Tal funcionalidad es provista por los
manejadores de base de datos (DBMSs) y GeneXus la aprovecha y permite usarla; sin embargo en los casos en los
que no se trabaja con un manejador de base de datos, no contamos con la posibilidad de utilizar esta facilidad.

En esos casos en los que necesitamos numerar de forma automática y correlativa ciertos atributos, y no podemos
utilizar la propiedad Autonumber, debemos resolverlo programándolo. Para ello solemos definir una transacción
conteniendo dos atributos, uno para almacenar un literal y otro para almacenar el último número asignado
automáticamente al atributo descripto por el literal1; la transacción conlleva la creación de una tabla, y definimos un
procedimiento que consulta esa tabla, obtiene el último número asignado para el atributo a ser numerado, le suma
uno y devuelve el próximo número, además de actualizarlo en la tabla.
Para invocar al procedimiento de numeración automática se debe definir en las transacciones que lo requieran una
regla del siguiente estilo:

CustomerId = PGetNumber.udp( ‘CUSTOMER’ ) if Insert on AfterValidate;

En este caso se está queriendo autonumerar el atributo CustomerId de la transacción “Customer”

Del mismo modo, si queremos autonumerar el identificador de facturas, escribiríamos en la transacción “Inovice” la
siguiente regla:

InvoiceId = PGetNumber.udp( ‘INVOICE’ ) if Insert on AfterValidate;

De esta forma definimos que se efectúen numeraciones automáticas en las transacciones únicamente cuando se
realicen inserciones (por la condición de disparo: if Insert) e inmediatamente antes de que se grabe
físicamente cada instancia a ser insertada (por el evento de disparo: on AfterValidate) a través del primer nivel
de la transacción (porque en las dos reglas de invocación mostradas, hay solamente un atributo involucrado que
pertenece al primer nivel de las transacciones "Customer" e "Invoice" respectivamente).

El motivo por el cual agregamos el evento de disparo on AfterValidate a estas reglas es para invocar al
procedimiento de numeración automática inmediatamente antes de que se inserte el registro en la base de datos y
luego de la validación, intentando de esta forma tener el mayor grado de seguridad posible de que el número
asignado será utilizado (y no perder números). Piense unos instantes el lector cuándo se dispararía la regla anterior
de no estar condicionada a evento de disparo alguno, y qué podría pasar en el caso de que fallara la validación de
alguno de los datos del cabezal. La respuesta es simple: se perdería un número. Es decir, si el número de factura
anterior fuera 5 y el usuario quisiera ingresar la siguiente factura, la regla de asignación con udp que invoca la
procedimiento de numeración se dispararía ni bien se ingresara a la transacción estando en modo insert, ya que
involucra al primer atributo del cabezal. El procedimiento devolvería el número 6, y si validando los datos del
cabezal se encuentra algún error que no permite continuar con el proceso, y se abandonara la transacción, por
ejemplo, ese número 6 se habrá perdido y la próxima factura, que correlativamente debería tener el número 6 no lo
tendrá, tendrá el 7.

----------------------------------------------------------------------------------------------------------------------------
1 Cuando vimos la regla serial introdujimos este tema de numeración de cabezales, con la transacción “Number” que
era la que tenía el literal: NumberCode y el último número asignado a la transacción correspondiente a ese literal:
NumberLast

146
Existen tres eventos de disparo que ocurren en el mismo momento que el AfterValidate, pero que ya contienen
intrínseco el modo. Ellos son: BeforeInsert, BeforeUpdate y BeforeValidate.
Es equivalente escribir la regla presentada antes como lo hicimos, a escribirla:

InvoiceId = PGetNumber.udp( ‘INVOICE’ ) on BeforeInsert;

Observar que aquí es redundante condicionar la regla a “If Insert”. Por tanto, valen las siguientes equivalencias:

on BeforeInsert ∼ If Insert on AfterValidate


on BeforeUpdate ∼ If Update on AfterValidate
on BeforeDelete ∼ If Delete on AfterValidate

Si hacemos un esquema de las acciones que rodean al evento de disparo, quedarán claros los dos sinónimos
elegidos para este evento (AfterValidate y BeforeInsert para modo insert)

VALIDACIÓN DE LOS DATOS


AfterValidate – BeforeInsert – BeforeUpdate – BeforeDelete
tiempo

GRABACIÓN DEL REGISTRO (insert, update, delete según corresponda)

2) Si definimos una regla a la cual le incluimos también el evento de disparo on AfterValidate, u on


BeforeInsert, BeforeDelete, BeforeUdate pero a diferencia de los ejemplos recién vistos, se referencia en la
regla al menos un atributo del segundo nivel de la transacción en la cual se está definiendo la regla, la misma estará
asociada al segundo nivel1. Por lo tanto, la regla se ejecutará inmediatamente antes de que se grabe
físicamente cada instancia correspondiente al segundo nivel de la transacción.

Eventos de disparo: AfterInsert, AfterUpdate, AfterDelete

Así como existe un evento de disparo que permite definir que determinadas reglas se ejecuten inmediatamente
antes de que se produzca la grabación física de cada instancia de un nivel (AfterValidate, BeforeInsert,
BeforeUpdate, BeforeDelete), también existen eventos de disparo para definir que ciertas reglas se ejecuten
inmediantamente después de que se inserten, modifiquen o eliminen físicamente instancias de un nivel.
Estos eventos son AfterInsert, AfterUpdate y AfterDelete.

El evento de disparo AfterInsert permite definir que una regla se ejecute inmediatamente después de que se inserte
físicamente cada instancia del nivel al cual está asociada la regla; el AfterUdate luego de que se actualice
físicamente la instancia, y el AfterDelete luego de que se elimine.

EJEMPLOS

Supongamos que en la transacción "Customer" queremos invocar a un reporte que realice la impresión de los datos
de cada cliente con el cual se trabaje por medio de la transacción.

¿En qué momento debemos realizar la invocación al reporte desde la transacción?

Caso 1: RPrintCustomer.call( CustomerId ) on AfterValidate;

No es adecuado agregarle este evento de disparo a la regla de invocación al reporte, porque éste se invocaría
inmediatamente antes de la grabación física de cada cliente. En consecuencia, el reporte no encontraría al
cliente con sus datos en la tabla CUSTOMER (si se estaba insertando un cliente por medio de la transacción), o lo
encontraría con sus datos desactualizados (si se estaba modificando un cliente por medio de la transacción). Si en
cambio se estaba eliminando un cliente por medio de la transacción, el reporte encontraría los datos del cliente en la
tabla CUSTOMER y los listaría justamente antes de la actualización física (eliminación).

Si se desea esto, es decir, emitir un listado con los datos de cada cliente que se elimine, sería adecuado definir la
siguiente regla:

RPrintCustomer.call( CustomerId ) on BeforeDelete;


o su equivalente:
RPrintCustomer.call( CustomerId ) if delete on AfterValidate;

--------------------------------------------------------------------------------------------------------------------------
1 Existe otra forma de provocar que una regla que contiene atributos de un nivel determinado, se dispare en el nivel
siguiente, mediante la cláusula Level que mencionamos cuando vimos conceptos importantes sobre reglas de
transacciones.

147
para restringir el disparo de la regla únicamente a cuando se esté eliminando un cliente, porque es el único caso en
el sería correcto utilizar el evento de disparo AfterValidate (ya que justamente necesitamos emitir el reporte antes
de la eliminación).

Caso 2: RPrintCustomer.Call( CustomerId ) on AfterInsert;

El evento de disparo AfterInsert ocurre inmediatamente después de que se inserte físicamente cada instancia
asociada a cierto nivel de la transacción (en este caso, como el único atributo involucrado en la regla es CustomerId,
se trata de una regla asociada al primer y único nivel de la transacción "Customer").

Como lo indica claramente su nombre, el evento de disparo AfterInsert sólo ocurre al insertar una nueva instancia
(precisamente luego de ser insertada como registro físico). Es por ello que cuando se agrega el evento de disparo on
AfterInsert a una regla, no es necesario agregarle la condición de disparo if insert.

Es correcto agregarle este evento de disparo a la regla de invocación al reporte, ya que el reporte se invocaría
inmediatamente después de que se inserte físicamente cada cliente. Así que el reporte encontraría al cliente
con sus datos en la tabla CUSTOMER y los imprimiría.

Lo que debe quedar claro es que con esta definición el reporte se invocará únicamente luego de realizar inserciones.

Caso 3: RPrintCustomer.Call( CustomerId ) on AfterUpdate;

El evento de disparo AfterUpdate ocurre inmediatamente después de que se actualice físicamente cada instancia
asociada a cierto nivel de la transacción (en este caso, como el único atributo involucrado en la regla es CustomerId,
se trata de una regla asociada al primer y único nivel de la transacción "Customer").

Es adecuado agregarle este evento de disparo a la regla de invocación al reporte, ya que el reporte se invocaría
inmediatamente después de que se actualice físicamente un cliente. Así que el reporte encontraría al cliente
con sus datos actualizados en la tabla CLIENTES y los imprimiría.

El reporte se invocará únicamente luego de realizar actualizaciones.

Caso 4: RPrintCustomer.Call( CustomerId ) on AfterDelete;

El evento de disparo AfterDelete ocurre inmediatamente después de que se elimine físicamente cada instancia
asociada a cierto nivel de la transacción (en este caso, como el único atributo involucrado en la regla es CustomerId,
se trata de una regla asociada al primer y único nivel de la transacción "Customer").

No es adecuado agregarle este evento de disparo a la regla de invocación al reporte, porque el reporte se invocaría
inmediatamente después de la eliminación física de cada cliente. En consecuencia, el reporte no encontraría al
cliente con sus datos en la tabla CUSTOMER.

Caso 5: RPrintCustomer.Call( CustomerId ) on AfterInsert, AfterUpdate;


RPrintCustomer.Call( CustomerId ) if delete on AfterValidate;

Para finalizar, estas dos reglas son las adecuadas para invocar a un reporte en la transacción "Customer", con el
objetivo de imprimir los datos de cada cliente con el cual se trabaje, abarcando los tres modos de trabajo.

Como se puede observar en la primera regla es posible incluir varios eventos de disparo separados por coma,
cuando los mismos aplican a una misma regla.

Es decir, es lo mismo definir estas dos reglas independientes:

RPrintCustomer.Call( CustomerId ) on AfterInsert;


RPrintCustomer.Call( CustomerId ) on AfterUpdate;

que esta regla:

RPrintCustomer.Call( CustomerId ) on AfterInsert, AfterUpdate;

148
Caso 6: Si definimos una regla a la cual le incluimos el evento de disparo on AfterInsert, pero a diferencia de los
ejemplos recién vistos, se referencia en la regla al menos un atributo del segundo nivel de la transacción en la cual se
está definiendo la regla, la misma estará asociada al segundo nivel. Por lo tanto, la regla se ejecutará
inmediatamente después de que se inserte físicamente cada instancia correspondiente al segundo nivel de la
transacción.

Análogo es el caso de on AfterUpdate y on AfterDelete.

Ampliamos el esquema que habíamos efectuado antes, de las acciones que rodean a los eventos de disparo vistos
hasta ahora:

VALIDACIÓN DE LOS DATOS


AfterValidate – BeforeInsert – BeforeUpdate – BeforeDelete
tiempo

GRABACIÓN DEL REGISTRO (insert, update, delete según corresponda)


AfterInsert – AfterUpdate – AfterDelete

Este esquema se repite para cada instancia del nivel. Por ejemplo, pensemos en el ingreso de las líneas de una
factura. Para cada línea ocurrirá este esquema, por lo que podemos pensar en un loop que se repite hasta que se
termina de grabar la última línea.

La acción que sucede a la grabación de la última línea sería el abandonar ese nivel (en este caso el de las líneas de
factura). Y luego de esa acción, a menos que venga otro nivel con el que se volvería a ingresar en el esquema
anterior, ocurrirá la última acción en la ejecución, que es el commit.
Entre la acción de abandonar el nivel, y el commit tendremos un evento (que admite dos nombres distintos) y otro
para luego del commit. Son los que veremos a continuación pero que ya mostramos en esquema:

VALIDACIÓN DE LOS DATOS CABEZAL


AfterValidate – BeforeInsert – BeforeUpdate – BeforeDelete
GRABACIÓN DEL REGISTRO (insert, update, delete según corresponda)
AfterInsert – AfterUpdate – AfterDelete

VALIDACIÓN DE LOS DATOS LÍNEA


AfterValidate – BeforeInsert – BeforeUpdate – BeforeDelete
GRABACIÓN DEL REGISTRO (insert, update, delete según corresponda)
tiempo

AfterInsert – AfterUpdate – AfterDelete


loop

ABANDONAR NIVEL 2
AfterLevel - BeforeComplete
COMMIT
AfterComplete

149
Eventos de disparo: AfterLevel, BeforeComplete

El evento de disparo AfterLevel permite definir que una regla se ejecute inmediatamente después de terminar
de iterar determinado nivel.

SINTAXIS: regla [if condición de disparo] [on AfterLevel Level atributo];

DONDE:
regla: es una regla de las permitidas en transacciones
condición de disparo: es una expresión booleana que permite involucrar atributos, variables, constantes y funciones,
así como los operadores Or, And, Not.
atributo: es un atributo perteneciente al nivel para el cual se desea que luego de ser iterado, se ejecute la regla.

FUNCIONALIDAD:

Si el atributo que se especifica a continuación del evento de disparo AfterLevel pertenece al segundo nivel de la
transacción, la regla se ejecutará cuando se hayan terminado de iterar todas las líneas del segundo nivel.

Y si el atributo que se especifica a continuación del evento de disparo AfterLevel pertenece al primer nivel -siguiendo
el mismo concepto- la regla se ejecutará cuando se haya terminado de iterar por todos los cabezales. Observar que
esto se da al final de todo, es decir, una vez que se hayan ingresado todos los cabezales y sus líneas y se cierre la
transacción (en ese momento se habrán iterado todos los cabezales). Por lo tanto, si el atributo especificado
pertenece al primer nivel, la regla se disparará una vez sola antes del Evento Exit (es un evento que se ejecuta una
sóla vez cuando se cierra una transacción en tiempo de ejecución, como veremos).

Ejemplo: Rever el ejemplo presentado antes, donde teníamos una transacción para representar las facturas que nos
entregan nuestros proveedores, y donde queríamos controlar que el total calculado de cada factura coincidiera con el
total ingresado. Allí teníamos la regla:

Error('El total ingresado no coincide con el total calculado') if InvoiceCalcTotal<>InvoiceEntTotal;

que necesitáramos se disparara luego de ingresadas todas las líneas de la factura. Por tanto, el evento de disparo
apropiado será AfterLevel Level att, donde att sea cualquier atributo de las líneas.

El evento de nombre BeforeComplete, en este caso, coincide con el AfterLevel. Si observamos el esquema
presentado en la página anterior, podemos ver que el instante de tiempo que hay entre que se abandona el último
nivel y se realiza el commit es el instante en que ocurren estos eventos. Son dos nombres para referirnos a lo mismo.

Cuidado que esto es así siempre y cuando el nivel abandonado sea el último. Supóngase por ejemplo una transacción
con dos niveles paralelos. Por ejemplo, si agregamos al cliente sus direcciones de mail y sus números telefónicos
(puede tener varios):

CustomerId*
CustomerName

(CustomerPhone*
…)
(CustomerEMail*
…)

El momento en que deberá dispararse una regla condicionada a: On AfterLevel Level CustomerPhone NO
COINCIDIRA con el de una regla condicionada a on BeforeComplete.

Mientras que la primera se disparará cuando se abandona el nivel de los teléfonos, y antes de entrar a validar todos
los emails, la segunda se disparará después de abandonar este último nivel.
En este caso el evento BeforeComplete coincidirá con el AfterLevel Level CustomerEMail.

Evento de disparo: AfterComplete

Este evento corresponde al instante de tiempo que sucede al commit. Hablaremos más de este evento unas páginas
adelante, cuando estudiemos la integridad transaccional.
Si se abre la transacción de facturas, se ingresan 3 facturas (cabezal y sus respectivas líneas) y se cierra la
transacción, ocurrirán 3 commits (uno al final de cada ingreso de cabezal + líneas) y 3 eventos AfterComplete.

150
Ejemplo en transacción de 2 niveles
Interactivamente (Web o Win con Client Side Validation) y antes de confirmar:

REGLAS STAND-ALONE

EVALUACION DE REGLAS Y
FORMULAS SEGÚN ARBOL

PARA
EVALUACION DE REGLAS Y
FORMULAS SEGÚN ARBOL CADA
LINEA

El siguiente ejemplo pretende mostrar visualmente en qué momentos se irán disparando las reglas y
fórmulas definidas en una transacción1.

Esto dependerá en parte del tipo de diálogo, aunque el orden será el mismo para cualquiera de ellos.
La diferencia radicará en el momento en que se realizará la grabación de los registros y el disparo de
reglas que estén condicionadas a eventos.

Si tenemos un diálogo con validación a nivel del cliente (.Net o Java, ya sea interfaz Win con la
propiedad Client Side Validation en Yes, o interfaz Web) entonces las reglas que no posean evento
de disparo, así como las fórmulas, se ejecutarán en forma interactiva a medida que el usuario vaya
pasando por los campos (ejecución en el cliente).

El disparo de reglas y fórmulas se irá haciendo de acuerdo al árbol de evaluación, siguiendo el orden
que éste determina.

Una vez que el usuario confirme la pantalla (esto es, en Win: presione el botón “Confirm”, cuyo
título será: Add, Update, Delete, Confirm, según el caso; en Web: presione el botón “Apply
Changes”), los datos se enviarán al servidor y allí se realizarán las validaciones y disparo de reglas y
fórmulas mencionadas antes, nuevamente, además de dispararse las reglas que estén condicionadas
a eventos de disparo, por primera vez, junto con las grabaciones de los registros correspondientes,
en el orden que se muestra en la siguiente hoja. Esto se realizará en el servidor.

Si el diálogo es a pantalla completa, en el cliente no se disparará nada, y todo se hará en el


servidor, como se muestra en la siguiente hoja.

Con respecto al diálogo campo a campo, si bien el orden será el mismo que el de la siguiente hoja,
la diferencia se encuentra en el momento en que esto se realiza: en este tipo de aplicaciones, será
absolutamente interactivo, dado que cuando el usuario pase en la pantalla del cabezal a las líneas, o
de una línea a la siguiente, ya en ese momento se realizará la grabación.

151
Ejemplo en transacción de 2 niveles
Al confirmar los datos, se ejecutan en el siguiente orden:

REGLAS STAND-ALONE

EVALUACION REGLAS Y FÓRMULAS SEGÚN ARBOL

BeforeValidate
VALIDACIÓN
AfterValidate / BeforeInsert / Update / Delete
GRABACION DEL CABEZAL
AfterInsert / Update / Delete

EVALUACION DE REGLAS Y
PARA CADA
FORMULAS SEGÚN ARBOL
LINEA
BeforeValidate
VALIDACIÓN
AfterValidate / BeforeInsert / Udpate / Delete
GRABACION DE LA LINEA
AfterInsert/Update/Delete
ABANDONAR NIVEL 2
AfterLevel Level attNivel2 - BeforeComplete
COMMIT
AfterComplete

Reglas stand alone


Las reglas stand alone son aquellas que:
1. Pueden ejecutarse con la información provista por los parámetros recibidos.
2. No dependen de nada para ejecutarse.

Ejemplos de reglas stand alone (por poder ejecutarse con la información provista por los parámetros):
• &A = parámetro2;
• Msg( ‘...’ ) if parámetro1 = 7;

Ejemplos de reglas stand alone (por no depender de nada para ejecutarse):


• msg( ‘You are in the invoice transaction’);
• &A = 7;

Por lo tanto, son las primeras reglas que pueden ejecutarse.

Luego de la ejecución de las reglas stand alone, se ejecutan las reglas asociadas al primer nivel de la
transacción, que no tengan evento de disparo definido, siguiendo el orden de dependencias determinado
por GeneXus (así como las fórmulas asociadas al primer nivel). A modo de ejemplo, se disparará la regla:
“Default( InvoiceDate, &Today);”

Después de ejecutadas las reglas mencionadas para el cabezal, se ejecutarán todas las reglas que tengan
como evento de disparo BeforeValidate, ya que inmediatamente después ocurrirá la acción de
validación (o confirmación) de la información de ese primer nivel.

Inmediatamente después de la validación del primer nivel se ejecutan las reglas asociadas al primer nivel
de la transacción que incluyan en su definición el evento de disparo AfterValidate, o los BeforeInsert,
BeforeUpdate, BeforeDelete, dependiendo del modo en el que se esté.

Por ejemplo: Si no podemos autonumerar las facturas con la propiedad Autonumber por no ser soportada
por el DBMS elegido:
InvoiceId = PGetNumber.udp(‘INVOICE’) on BeforeInsert;

152
Seguidamente a la ejecución de las reglas asociadas al primer nivel con alguno de estos eventos de disparo se
ejecuta la acción de grabación; es decir, se grabará físicamente la instancia correspondiente al primer nivel
de la transacción como registro físico en la tabla correspondiente (en este ejemplo, en la tabla: INVOICE).
Inmediatamente después de haberse grabado esa instancia:

• si la grabación correspondió a una inserción: se ejecutarán las reglas asociadas al primer nivel de la
transacción con evento de disparo AfterInsert.
• si la grabación correspondió a una actualización: se ejecutarán las reglas asociadas al primer nivel de la
transacción con evento de disparo AfterUpdate.
• si la grabación correspondió a una eliminación: se ejecutarán las reglas asociadas al primer nivel de la
transacción con evento de disparo AfterDelete.

Si se trata de una transacción de dos niveles, como en este caso, a continuación se ejecutará para cada una
de las líneas:

En primer lugar, las reglas asociadas al segundo nivel de la transacción que no tengan evento de disparo
definido, siguiendo el orden de dependencias determinado por GeneXus (así como las fórmulas asociadas al
segundo nivel). Ejemplos de ello son la regla
Subtract( InvoiceLineQuantity, ProductStock );
la fórmula
InvoiceLineAmount = InvoiceLineQuantity*ProductPrice.

Después de ejecutadas las reglas mencionadas para una línea, se ejecutarán todas las reglas que tengan
como evento de disparo BeforeValidate, dado que inmediatamente después ocurre la validación de la
línea; esto es una acción que ocurre a continuación de haber terminado de trabajar con la línea.

Inmediatamente después de la validación de la línea, se ejecutarán las reglas asociadas al segundo nivel
de la transacción que incluyan en su definición alguno de los eventos de disparo: AfterValidate,
BeforeInsert, BeforeUpdate, BeforeDelete.

Seguidamente a la ejecución de las reglas asociadas al segundo nivel con alguno de estos eventos de
disparo se ejecutará la acción de grabación; es decir, se grabará físicamente la instancia correspondiente a
la línea como registro físico en la tabla correspondiente (en este ejemplo, en la tabla: INVOICELINE).

Inmediatamente después de haberse grabado la instancia correspondiente a la línea como registro físico en
la tabla correspondiente:

• si la grabación correspondió a una inserción: se ejecutarán las reglas asociadas al segundo nivel de la
transacción con evento de disparo AfterInsert.
• si la grabación correspondió a una actualización: se ejecutarán las reglas asociadas al segundo nivel de la
transacción con evento de disparo AfterUpdate.
• si la grabación correspondió a una eliminación: se ejecutarán las reglas asociadas al segundo nivel de la
transacción con evento de disparo AfterDelete.

Todas estas operaciones sombreadas de gris claro, se ejecutarán en el orden descrito, para cada una de las
líneas.

Luego de la iteración de todas las líneas, podemos suponer la existencia de una acción que podríamos
llamar abandono del segundo nivel. Luego de la misma se ejecutarán las reglas definidas con evento de
disparo AfterLevel Level Atributo del 2do nivel. Si no existe otro nivel, como es el caso del ejemplo,
Aclaración importante:
entonces coincidirá Todas las
con el evento operaciones
de disparo sombreadas, tanto en gris claro como en gris oscuro, se
BeforeComplete.
ejecutan únicamente si se trata de una transacción de dos niveles; de modo que cuando se trata de una
transacción de un nivel, tales operaciones no se ejecutarán. El motivo de los dos sombreados distintos, es
para diferenciar el conjunto de operaciones que se ejecuta para cada una de las líneas (sombreado gris claro)
de las operaciones que se ejecutan solamente una vez finalizada la iteración en las líneas (sombreado gris
más oscuro). A continuación seguimos explicando en orden, el resto de las operaciones que se ejecutan, así
sea que se trate de una transacción de un nivel o dos.

Luego de haberse ejecutado todas las operaciones explicadas hasta el momento, se efectuará un commit,
siempre y cuando el ambiente o plataforma de trabajo sea Cliente/Servidor.

153
A continuación se ejecutarán las reglas con evento de disparo AfterComplete.

Es de fundamental importancia que quede claro que todas las operaciones explicadas se ejecutarán en el orden
en el que se han descrito, para cada factura con la cual se trabaje por medio de la transacción "Invoice" (ya sea
que se ingrese, modifique o elimine).

Puede ser útil tener en cuenta que se han resaltado en negrita las acciones cada vez que se las ha mencionado.
Las mismas son: validación, grabación , abandono del segundo nivel y commit.

Es indispensable asimilar el orden en el que se ejecutan las reglas en una transacción, cuáles son los eventos de
disparo disponibles para asignarles, cuándo se disparan exactamente, y qué acciones ocurren antes y después de
cada evento de disparo, ya que solamente conociéndolos bien se podrá programar el comportamiento de las
transacciones adecuadamente. Es sencillo comprender que si necesitamos programar determinados controles o
acciones en las transacciones, tendremos que saber bien si hacerlo antes de que se grabe el cabezal, después de
que se haya grabado el mismo, para cada una de las líneas después de que se hayan grabado, o antes, después
del commit o antes, por lo tanto es fundamental tener claro todo este tema.

Nos ha faltado mencionar cuándo ocurrirá una regla condicionada al evento de disparo: AfterLevel Level Atributo
1er nivel.

Diferencia entre aplicación 3 capas Win y aplicación Web


Aquí aparece una diferencia importante entre Win y Web, que tiene que ver con las características inherentes a
cada una. Mientras que en Win la capa de la aplicación que se encuentra en el cliente se mantiene conectada al
servidor de manera continua (el servidor mantiene “estado”) , en Web la conexión es discreta: se logra cuando
deben intercambiar información y luego se desconectan; no se mantiene un estado. Esto establece algunas
diferencias en el comportamiento de las transacciones.

Mientras que una aplicación Win mantiene la conexión con el servidor de manera tal que éste pueda mantener el
estado de una transacción que se esté ejecutando: controlar cuándo se abrió, cuántas instancias se han
ingresado y cuándo se ha cerrado la misma, en Web esto no es posible (no mantiene estado).
Por tanto una regla condicionada al evento de disparo AfterLevel Level atributo del 1er. nivel solo se disparará
en una transacción Win (para una Web no tiene sentido) y lo hará una sola vez, cuando se cierra la
transacción. Recordemos que una regla con evento de disparo AfterLevel Level Atributo 1er nivel se ejecuta
luego de que se haya iterado por todos los cabezales, y esto se da al final de todo, es decir, una vez que se haya
trabajado con todos los cabezales y sus líneas y se cierre la transacción (en ese momento se habrá iterado por
todos los cabezales).

No podemos dejar de mencionar algo que también para Win se ejecutará una única vez en la ejecución de una
transacción: el Evento Start y el Evento Exit.

Como mencionaremos luego, en una transacción Web estos eventos se ejecutarán una vez por cada instancia de
transacción con la que se trabaje.

154
Ejemplos

¿Cuándo se dispararán las siguientes reglas?

• PSomething.call( InvoiceId ) if Insert;


Luego de validado el campo InvoiceId e inferido que se está en modo Insert

• PSomething.call( InvoiceId ) on BeforeInsert;

Luego de disparadas todas las reglas y fórmulas según árbol, y validados


todos los datos del cabezal. Un instante antes de insertar el registro.

• PSomething.call( InvoiceId, ProductId ) on BeforeInsert;


Luego de disparadas todas las reglas y fórmulas según árbol, y validados
todos los datos de la línea. Un instante antes de insertar el registro.

• PSomething.call( InvoiceId ) on BeforeInsert Level ProductId;


Ídem que el anterior. Observar que Level ProductId especifica que se está
hablando del BeforeInsert de las líneas y no del cabezal.

155
Ejemplos

Algunas reglas están mal programadas. ¿Cuáles?

• InvoiceDate = &today on AfterInsert;


Incorrecto: El último momento para asignar valor a un atributo del cabezal
es inmediatamente antes de su grabación (BeforeInsert)

• PSomething.call( InvoiceDate ) on AfterInsert;


Correcto: aquí se está pasando el valor de un atributo del cabezal; mientras
se esté en la instancia de la factura se tiene ese valor en memoria. Último
momento posible para utilizarlo AfterComplete.

• PSomething.call( InvoiceId, ProductId ) on AfterLevel Level


ProductId;
Incorrecto: la regla, sin el evento de disparo está asociada al 2do. Nivel, es
decir, se dispararía por cada línea. Pero el evento de disparo la condiciona a
ejecutarse al salir de las líneas. ¿Qué valor tendría ProductId?

156
Reglas con el mismo evento de disparo

• Son disparadas en el orden en que fueron definidas

• Ejemplo 1
‘xxx’.call() On AfterComplete;
‘yyy’.call() On AfterComplete;

• Ejemplo 2
‘pgmname’.call( CustomerId, &flag) On AfterComplete;
error(' ') if &flag = 'N’ On AfterComplete;

Reglas con el mismo evento de disparo


Cuando en una transacción se definen dos o más reglas con el mismo evento de disparo, y no existe
ninguna dependencia entre ellas, las mismas se ejecutarán respetando el orden de definición.

Ejemplos:
1) Se definen las siguientes reglas en una transacción:

‘xxx’.Call() on AfterComplete;
‘yyy’.Call() on AfterComplete;

Como las dos reglas definidas están condicionadas con el mismo evento de disparo, y no existe
ninguna dependencia entre ellas, las mismas se ejecutarán en el mismo orden en el cual se han
escrito.

2) En una transacción se necesita invocar a un procedimiento que realiza determinada validación y


retorna un valor ‘S’ o ‘N’; si el valor devuelto es ‘N’, se debe dar un mensaje de error.

Para resolver esto, evaluaremos dos posibilidades:

2.1) Definir las reglas:


PXXX.call(&flag) on AfterValidate;
error(‘…’) if &flag=‘N’ on AfterValidate;

2.2) O definir las reglas:


&flag = PXXX.udp() on AfterValidate;
error(‘…’) if &flag=‘N’ on AfterValidate;

157
En la primera alternativa, se ha definido una regla call y una regla error. Ambas reglas tienen el mismo
evento de disparo, y aparentemente existiría dependencia entre ellas, ya que la regla de error está
condicionada al valor de la variable &flag, y la variable &flag se pasa por parámetro en la regla call.

Sin embargo, si bien la dependencia nos puede parecer evidente porque en el procedimiento
programaremos a la variable &flag, de salida, en la sección de reglas de la transacción -que es donde se
encuentran las reglas que estamos viendo-, el especificador de GeneXus no puede saber si los parámetros
pasados en un call son de entrada, de salida, o de entrada-salida; en consecuencia el especificador no
encontrará interdependencia entre las reglas call y error, ya que la variable &flag podría ser pasada como
variable de entrada al procedimiento, y en ese caso por ejemplo, no habría una dependencia por la cual
primero se deba ejecutar la regla call y luego la regla error.

Así que concluyendo, no se detectan dependencias entre las reglas call y error de la alternativa 2.1), por lo
que las mismas se dispararán entonces en el orden en el que estén escritas. Es importante ver que si las
reglas call y error estuvieran escritas en orden inverso (es decir, primero la regla error y después la regla
call), el comportamiento no será el esperado en muchos casos.

Con respecto a la segunda alternativa, observemos que la misma consiste en una regla con udp y una regla
error. Ambas reglas tienen el mismo evento de disparo, y en este caso sí existe dependencia entre ellas, ya
que la regla error está condicionada al valor de la variable &flag, y como la invocación al procedimiento se
realiza con udp, para el especificador de GeneXus queda claro que la variable &flag vuelve modificada del
procedimiento; por lo tanto el especificador de GeneXus entiende que primero se debe disparar la
invocación al procedimiento con udp y luego la regla error, porque la variable &flag se carga mediante la
invocación al procedimiento con udp, y luego de que dicha variable tenga valor, es que habrá que evaluar si
disparar la regla error, o no.

En el caso 2.2) entonces, independientemente del orden de definición de ambas reglas, la invocación al
procedimiento con udp se disparará primero, y luego de ello, se disparará la regla error (en caso de que se
cumpla la condición de disparo, claro está).

Por esta razón se recomienda que siempre que se quieran definir validaciones de este tipo, se utilice udp en
lugar de call.

158
Consideración importante acerca del
disparo de reglas

• Reglas que no tienen evento de disparo asociado, se ejecutarán una vez o


dos o tres, dependiendo de si se trabaja con Confirmation (y en Win con
Client Side Validation)

• Ejemplo: Client Side Validation √ Reglas que no tienen evento de disparo


Confirmation √ asociado, se dispararán 3 veces

• ¡Cuidado si la regla es una invocación a un procedimiento que actualiza la


BD!

Æ Para que no se dispare más de una vez, habrá que asignarle evento de disparo
específico a la regla, (o bien, para Win, dejar el diálogo a pantalla completa para esta
transacción), o estudiar bien la lógica del procedimiento y tener en cuenta la doble o
triple ejecución del mismo.

• Esto no sucederá con reglas de GeneXus (como subtract, add) que actualizan
la BD porque GX tiene la inteligencia para realizar el update solo al confirmar
(lo mostrado en forma interactiva se calcula en memoria).

Es importante tener en cuenta que las reglas que no tienen evento de disparo asociado, se ejecutarán una
vez o dos o tres, dependiendo de lo que se haya configurado en la propiedad del modelo Confirmation y
de tratarse de una aplicación .Net o Java Win, de la propiedad Client Side Validation (recordemos que
en Web siempre se trabaja con este tipo de diálogo)

Por ejemplo, si se trabaja en Web o en Win (con la propiedad: Client Side Validation=Yes) y propiedad
Confirmation=No, valor por defecto, las reglas que no tengan evento de disparo asociado se dispararán:
primero en forma interactiva en la medida que el usuario final vaya trabajando en el form, y luego
nuevamente cuando el usuario final efectúe la confirmación.

Es especialmente importante considerar esto en aquellos casos de reglas que consistan en


invocaciones a procedimientos que actualicen la base de datos.
Si se tiene una invocación a un procedimiento que actualiza la base de datos, habrá que optar por alguna
de las siguientes alternativas para evitar que se dispare más de una vez:
• asignarle evento de disparo específico a la regla
• o bien decidir si configurar o no la propiedad CSV del modelo con Yes (solo Win) y la confimation
• o estudiar bien la lógica del procedimiento y tener en cuenta la doble o triple ejecución del mismo

Si trabajando en Win, en lugar de configurar la propiedad: Client Side Validation= Yes, se la dejara con
valor No y se configurara la propiedad Confirmation= Yes, estaríamos en la misma situación de doble
disparo de las reglas que no tienen evento de disparo. En este caso no se dispararían en forma interactiva
las reglas (ya que la propiedad Client Side Validation=No) pero se dispararían en la primera confirmación
del usuario (en la cual se efectúan las validaciones pero no se graba) y en la reconfirmación del usuario
(en la cual se efectúan las validaciones y se graba).

Por último, si se configurara Confirmation= Yes (y en Win Client Side Validation= Yes), las reglas sin
evento de disparo asociado tendrían un triple disparo.

159
EVENTOS EN TRANSACCIONES

En las transacciones se permite la programación dirigida por eventos, que es un estilo de


programación en el cuál se define código que permanece ocioso, hasta que suceden eventos
provocados por el usuario o por el sistema, que provocan que el código definido se ejecute.

Los eventos son acciones reconocidas por un objeto que pueden suceder o no. A cada evento se le
puede asociar código, que se ejecutará solamente si el evento se produce.

El código que se le puede asociar a un evento se escribe siguiendo el estilo procedural; y cuando el
evento se produce, el código asociado al mismo se ejecutará secuencialmente.

160
Eventos en Transacciones

• Evento Start

• Evento ‘User Event’

• Evento After Trn

• Evento Exit

Los eventos Start y Exit difieren de acuerdo a la interfaz que se utilice.

Como en Web no se mantiene un estado en el servidor que permita saber qué es lo que se ejecutó
en el cliente, no es posible saber si se está ingresando la primera instancia de una factura, o si es la
n-ésima. Por esta razón, mientras que el evento Start se ejecutará en una aplicación Win una sola
vez cuando se abre la transacción, y luego con cada iteración no vuelve a ocurrir, en Web esto no
es posible, por lo que se disparará el evento cada vez que se envíe al servidor la información de la
instancia con la que se esté trabajando.

Análogas consideraciones podemos hacer para el caso del evento Exit: en una aplicación Win es
posible saber que se está abandonando la transacción, por lo que puede capturarse ese evento. En
cambio en una aplicación Web esto no será posible, razón por la cuál el evento se ejecutará por
cada iteración, al final de la misma.

161
Evento Start
• Se ejecuta:

• Win: una sola vez, cuando se abre una transacción en


tiempo de ejecución.

• Web: cada vez que se somete el form de la


transacción al servidor.

SINTAXIS: Event Start EJEMPLO: Event Start


código &entrada=Now()
EndEvent EndEvent

El evento Start es un evento del sistema, por lo tanto ocurre automáticamente. ¿En qué momento se
ejecuta? La respuesta dependerá de si se trata de una transacción Win o Web.

Win: Cuando comienza la ejecución del programa asociado a una transacción, es decir, ni bien se abre
una transacción en tiempo de ejecución. Entonces, en el evento Start de una transacción se puede incluir
código que se desee se ejecute una única vez, cuando comience la ejecución de la transacción.
Generalmente el código que se incluye en este evento, es para inicialización (ejemplo, asignar valores a
variables para inicializarlas una única vez en la ejecución del programa).

EJEMPLO:
En una transacción nos interesa capturar la fecha y hora de entrada a la misma. Para ello en el evento
Start le asignamos a una variable de nombre &entrada y tipo de datos DateTime, el resultado de la
función Now() que devuelve la fecha y hora actual:

Event Start
&entrada = Now()
EndEvent

Web: Se ejecutará cada vez que se someta el form de la transacción, es decir cuando se presione
cualquier botón del form (“Get”, “Apply Changes”, botones de navegación, botón Select o cualquier
botón con un evento de usuario asociado).

Notas generales:
En el evento Start fundamentalmente se trabaja con variables. En cuanto a utilizar atributos en este
evento, ya sea para evaluarlos y/o usarlos de algún modo menos para actualizarlos, se debe tener en
cuenta que los únicos atributos que se tienen disponibles son los que se reciben por parámetro
en la regla parm. Ningún otro atributo tendrá valor en este evento, pues todavía no se ha editado
ninguna instancia de la transacción.

162
Evento Exit
• Se ejecuta:

• Win: una sola vez, cuando se cierra la transacción en


tiempo de ejecución.

• Web: se ejecuta cada vez que se somete el form de la


transacción al servidor. Por ello no suele utilizarse.

SINTAXIS: Event Exit


código
Endevent

• Ejemplo: En Win, llamar a un procedimiento que graba la


fecha/hora de entrada y la fecha/hora de salida del programa,
para cada usuario en una tabla de control.

El evento Exit es un evento del sistema (por lo tanto ocurre automáticamente) y es lo último en
ejecutarse. ¿En qué momento se ejecuta? Otra vez, esto dependerá de la interfaz:
• En Win se ejecuta una sola vez cuando se cierra una transacción en tiempo de ejecución.
• En Web, ocurre una única vez, al final de c/iteración (es lo último que se ejecuta).

Al igual que en el evento Start, en el evento Exit fundamentalmente se trabaja con variables.

En cuanto a utilizar atributos en este evento, ya sea para evaluarlos y/o usarlos de algún modo salvo
actualizarlos, se debe tener en cuenta que los únicos atributos que se tienen disponibles son
los que se reciben por parámetro en la regla parm. Ningún otro atributo tendrá valor en este
evento.

WIN
EJEMPLO: Cada vez que se cierre cierta transacción en tiempo de ejecución, invocaremos a un
procedimiento que grabe la fecha y hora de entrada a la transacción (que se capturó en el evento Start,
en una variable &in) y la fecha y hora de salida de la misma, para cada usuario en una tabla de control:

Event Exit
&user = userid()
&out = now()
PControlStore.call(&in, &out, &user)
Endevent

¿Cuál es la diferencia en Win entre definir código en el evento Exit o en cambio definir una
regla con evento de disparo on AfterLevel Level atributo 1er nivel, siendo que ambas cosas se
dispararían una única vez al cerrar la transacción, una a continuación de la otra?
Si se define una regla con el evento de disparo AfterLevel Level atributo 1er nivel, la misma se
disparará una sola vez cuando el usuario cierre la transacción, existiendo la posibilidad de retorno. Es
decir, si fuera necesario, se podría definir una regla Error para mantener la pantalla abierta. El Evento
Exit en cambio, si bien ocurre también una vez sola al cerrar la transacción, ya no brinda la posibilidad
de retorno. Cuando ocurre el Evento Exit, la pantalla ya se cerró.

WEB
Como se dispara cada vez que se dibuja la pantalla, al final, no hace las veces de un verdadero exit, por
lo que no suele utilizarse en este ambiente.

163
Eventos de Usuario
• Además de los eventos ofrecidos por GeneXus, el analista puede
definir eventos creados por él, llamados eventos de usuario.

• Cada evento de usuario luego se asocia a algún control del form de


los que aceptan evento de usuario (depende de la interfaz):
• En Win: botón y/o tecla de función.
• En Web: botón, imagen o text block tecla de función
(solo para Win)

SINTAXIS: Event ‘nombre de evento de usuario’ <Key>


código
Endevent

Web: 1. Evento Start


Orden de 2. Lectura de atributos y variables del form
ejecución 3. Evento de usuario seleccionado
4. Evento Exit

Como se puede observar en la sintaxis, se le debe dar un nombre a un evento de usuario, debiéndose
declarar a continuación de la palabra Event, encerrado entre comillas simples.

EJEMPLO:
Se desea que en la transacción "Invoice", el usuario tenga la posibilidad de imprimir la factura con la cual
esté trabajando, presionando F7 (esto solo es válido para Win):
Event ‘Print Invoice’ 7 //evento definido en la transacción "Invoice"
RPrintInvoice.Call( InvoiceId )
EndEvent

Win: ¿Cómo asociar un evento de usuario a una tecla de función o a un botón?


Como también se puede observar en la sintaxis, opcionalmente se puede especificar a continuación del
nombre del evento, el número correspondiente a una tecla de función. Por ejemplo, si se quiere que el
evento se ejecute cuando el usuario presione la tecla de función F6, habrá que poner a continuación del
nombre del evento, el número: 6.
Para asociar el evento a un botón, se debe insertar un control botón en el form GUI-Windows, y a
continuación se abrirá automáticamente un diálogo con las propiedades del botón; allí se deberá seleccionar
el evento de usuario definido.
Si se trata de un botón ya insertado en el form, sólo habrá que abrir el diálogo con las propiedades del
botón, y seleccionar allí, el evento de usuario definido.

Web: ¿Cómo asociar un evento de usuario a un control?


En el caso de interfaz web, además de los botones, también las imágenes y los text blocks admiten la
asociación de evento de usuario. Para realizar la asociación se debe insertar el control correspondiente en el
form Web y luego en las propiedades del control, se deberá seleccionar donde dice OnClickEvent uno de los
eventos existentes, o se puede crear uno nuevo.

Nota: se pueden ejecutar eventos asociados a botones con Alt+<letra>. Se logra colocando un ‘&’ en el
Caption del botón, antes de la letra con la que se desea acceder al evento. (Tanto Win como Web)
Ejemplo: si se desea que el código del evento de usuario asociado a un botón de caption “MyEvent” se
pueda ejecutar también con Alt+E, entonces en el Caption del botón, tendremos que escribir “My&Event” y
se verá en el form web (en diseño) con un infraguión antes de la letra correspondiente, mientras que en
ejecución no se percibirá nada:

164
Evento After Trn

• Ocurre inmediatamente después de la ejecución de las


reglas con evento de disparo AfterComplete.

• Sintaxis:
Event After Trn
código
Endevent

• Ejemplo: Event After trn


Return
EndEvent

El evento After Trn de las transacciones ocurre inmediatamente después de la ejecución de las reglas
con evento de disparo AfterComplete. Por consiguiente, el código que se incluya en este evento se
ejecutará luego de culminada cada iteración completa por medio de la transacción (es decir,
luego de haberse grabado cada cabezal con sus correspondientes líneas como registros
físicos en las tablas que corresponda y de haberse efectuado COMMIT).

Existen las siguientes alternativas para programar comportamientos que se deseen ejecutar luego de
cada iteración completa por medio de una transacción:

1. Definir reglas individuales con evento de disparo AfterComplete y dejar el evento After Trn sin código
2. Definir todas las sentencias en el evento After Trn con estilo procedural, y no definir reglas con
evento de disparo AfterComplete
3. Definir ambas cosas: algunas reglas con evento de disparo AfterComplete y código en el evento After
Trn

Como venimos explicando, primero se ejecutan las reglas definidas con evento de disparo
AfterComplete, e inmediatamente después de las mismas se ejecuta el código definido en el evento
After Trn.

Un concepto que es muy importante tener claro es que tanto en reglas con evento de disparo
AfterComplete como en el evento After Trn, se conocen los valores de los atributos del primer nivel de
la transacción.

Es decir, si bien ya se grabaron físicamente los registros correspondientes al cabezal y las líneas de
cierta iteración completa, e incluso se efectuó COMMIT, aún se tienen disponibles los valores de los
atributos del primer nivel, pudiendo estos utilizarse para pasarlos por parámetro en una invocación, o
evaluar su valor, o usarlos de algún modo salvo actualizarlos 1.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

1 Hay dos motivos por los cuales no es posible actualizar atributos en reglas con evento de disparo
AfterComplete ni en el evento After Trn. El primer motivo es que ya se han hecho las grabaciones
correspondientes e incluso se ha efectuado COMMIT, de modo que ya es tarde para asignar valores a
atributos. Y además, en lo que respecta al evento After Trn, en los eventos no se permite realizar
asignaciones a atributos.

165
Un detalle a tener en cuenta es que en el evento After Trn -como en todo evento- es posible incluir comandos, a
diferencia de en la sección de reglas de una transacción, en la que no es posible.

EJEMPLOS:

1. Event After Trn


return /*el comando return hace que se abandone el programa y se vuelva al objeto llamador*/
EndEvent

Si en el evento After Trn de una transacción se incluye el comando return, al terminar de ejecutarse la primera
iteración completa, se ejecutará el evento After Trn y se cerrará el programa correspondiente a la transacción,
volviendo al objeto llamador.

2. Event After Trn // Evento After Trn en la transacción "Invoice"


RPrintInvoice.Call( InvoiceId )
Msg(‘Recuérdele al cliente nuestra promoción XXX’)
Endevent

3. Se necesita controlar la cantidad de iteraciones completas realizadas por medio de una transacción Win durante una
sesión. Para ello definiremos código en varios eventos, no sólo en el evento After Trn.

Event Start
&veces = 0
EndEvent

Event After Trn


&veces += 1
EndEvent

Event Exit
Msg( ‘Se han realizado la siguiente cantidad de iteraciones completas: ’ + str( &veces ) )
EndEvent

Con este ejemplo logramos dejar bien en claro que el evento Start se ejecuta una sola vez en ambiente Win, ni bien se
abre una transacción en tiempo de ejecución, el evento Exit se ejecuta una sola vez cuando se cierra una transacción en
tiempo de ejecución (lo veremos a continuación), y el evento After Trn por su parte, se ejecuta una vez por cada
iteración completa culminada.

Si la misma transacción se generara en ambiente Web, el resultado sería absolutamente distinto: el mensaje siempre
mostraría 1, ya que el start y el exit se ejecutan en este caso por cada iteración.

166
Ejemplo en transacción de 2 niveles
Resumiendo, al confirmar los datos, se ejecutan en orden todo lo siguiente:
START (lo primero en ejecutarse)
REGLAS STAND-ALONE

EVALUACION REGLAS Y FÓRMULAS SEGÚN ARBOL

BeforeValidate
VALIDACIÓN
AfterValidate / BeforeInsert / Update / Delete
GRABACION DEL CABEZAL
AfterInsert / Update / Delete

EVALUACION DE REGLAS Y
PARA CADA
FORMULAS SEGÚN ARBOL
LINEA
BeforeValidate
VALIDACIÓN
AfterValidate / BeforeInsert / Udpate / Delete
GRABACION DE LA LINEA
AfterInsert/Update/Delete
ABANDONAR NIVEL 2
AfterLevel Level attNivel2 - BeforeComplete
COMMIT
AfterComplete After TRN
EXIT (lo último en ejecutarse)

Para completar el diagrama visto anteriormente, agregamos al comienzo la ejecución automática del
código asociado al evento START y al final la ejecución automática del código asociado al evento EXIT.
Además, incluimos la ejecución del evento After TRN en el lugar que corresponde a su ejecución.

167
Consideraciones

• En los Eventos no se permite asignar valores a


los atributos.

• Eventos Start y Exit: son sin tabla base.

• Eventos de usuario y After Trn: son con tabla


base.

No se permite asignar valores a atributos en los eventos.

Los valores de los atributos pueden modificarse en las transacciones:


• haciéndolo el usuario final, en tiempo de ejecución, a través del form (sólo atributos de las tablas bases
asociadas a la transacción, o aquellos de la extendida permitidos por regla update)
• mediante reglas definidas por el programador (atributos de las tablas bases asociadas a la transacción y sus
extendidas)

Solemos decir que los eventos Start y Exit son sin tabla base. Con esta expresión nos referimos a que en
los eventos Start y Exit no hay consulta activa a la base de datos (ya que en el evento Start aún no se ha
hecho la consulta y en el evento Exit en Win ya se está cerrando el programa asociado a la transacción y en
Web se está cerrando la instancia y ya no disponemos de la consulta). Por este motivo es que no se conocen
valores de atributos en los eventos Start y Exit, salvo los recibidos por parámetro.

Por el contrario solemos decir que los eventos After Trn y de usuario son con tabla base, ya que cuando
los mismos se ejecutan, sí hay una consulta en edición. Entonces, en particular en el evento After Trn, se
conocen los valores de los atributos del primer nivel (el segundo nivel ya se ha iterado a esa altura y no hay
posibilidad de posicionamiento en alguna línea en particular); y en lo que respecta a los eventos de usuario
se disponen los atributos de todos los niveles 1.

Es fundamental comprender que así se disponga de los valores de ciertos atributos u otros dependiendo del
evento, los mismos podrán utilizarse para ser evaluados y/o pasados por parámetro a objetos que se
invoquen, y/o para alguna otra operación cualquiera que no sea asignarles valor.

Concluyendo, en ningún evento (no sólo de transacciones, sino de ningún objeto GeneXus) se permite realizar
asignaciones a atributos.

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

1 Si en un evento de usuario se referencian atributos de un segundo nivel u otro nivel subordinado, cuando
el evento de usuario se ejecute se tendrán en cuenta los atributos de aquella línea en la que se esté
posicionado; al momento de ejecutarse el evento de usuario se considerarán los valores de los atributos de
dicha línea. Y si el usuario no se había posicionado explícitamente en determinada línea, por defecto la línea
que estará seleccionada será la primera, así que se considerarán los valores de los atributos de la misma.

168
INTEGRIDAD TRANSACCIONAL

169
¿Qué es el concepto: integridad transaccional?

Un conjunto de actualizaciones a la base de datos tiene


integridad transaccional cuando en caso de una
finalización “anormal”, la base de datos permanece en
estado consistente.

Muchos manejadores de bases de datos (DBMSs) cuentan con sistemas de recuperación ante
fallos, que permiten dejar la base de datos en estado consistente cuando ocurren imprevistos
tales como apagones o caídas del sistema.

170
¿Qué es el concepto: unidad de trabajo lógica
(UTL)?

Una unidad de trabajo lógica (UTL) es un conjunto


de operaciones a la base de datos, que deben
ejecutarse o bien todas o bien ninguna de ellas.

Los manejadores de bases de datos (DBMSs) que ofrecen integridad transaccional permiten
establecer unidades de trabajo lógicas (UTLs), que corresponden ni más ni menos que al
concepto de transacciones de base de datos.

171
¿Qué es efectuar COMMIT?

• El comando COMMIT permite especificar que cierto conjunto de operaciones


realizadas sobre una base de datos, ha culminado de efectuarse
correctamente:
...........
Operación sobre Base de Datos
Operación sobre Base de Datos
COMMIT Finaliza UTL
Comienza UTL
Operación sobre Base de Datos
Operación sobre Base de Datos
Operación sobre Base de Datos
Operación sobre Base de Datos
COMMIT Finaliza UTL

• De modo que efectuar COMMIT en una base de datos, significa que se da por
finalizada una unidad de trabajo lógica (UTL).

Podemos ver que una unidad de trabajo lógica (UTL) queda definida por el conjunto de
operaciones entre un par de Commits.

172
¿Qué es efectuar ROLLBACK?

• Hacer ROLLBACK (vuelta a atrás) provoca que se deshagan


todas las operaciones efectuadas en la base de datos que no
hayan quedado con COMMIT.

• Esto se resuelve deshaciendo todas las operaciones posteriores


al último COMMIT.

173
Unidades de trabajo lógicas (UTLs)
por defecto en GeneXus

Î Todo objeto GeneXus transacción y todo objeto GeneXus


procedimiento, determina unidades de trabajo lógicas (UTL).

Î Es decir, las transacciones y procedimientos son los únicos objetos


GeneXus (*) que permiten actualizar la base de datos, y por
defecto GeneXus incluye en los programas generados asociados a
los mismos, la sentencia COMMIT.

Î En el objeto procedimiento GeneXus incluye un COMMIT


automático al final del Source.

Î En el objeto transacción GeneXus incluye un COMMIT automático al


final de cada instancia, inmediatamente antes de las reglas con
evento de disparo AfterComplete

(*) una excepción la brindan los Business Components, pero no realizan commit automáticamente.

Es importante aclarar que GeneXus incluye la sentencia COMMIT en los programas generados asociados a
transacciones y procedimientos, sólo en ambientes de trabajo Cliente/Servidor (incluyendo, por tanto, los
ambientes Web). El motivo de esto es que en ambientes Cliente/Servidor existe un DBMS que asegura la
integridad transaccional, por lo tanto GeneXus efectúa la tarea de definir las unidades de trabajo lógicas
(UTLs). En cambio, en ambientes de trabajo que no son Cliente/Servidor, GeneXus no incluye sentencias
COMMIT pues no hay un DBMS por detrás que maneje la integridad transaccional.

¿Dónde incluye GeneXus COMMIT exactamente?

En cada procedimiento: al final del programa fuente.

En cada transacción: inmediatamente antes de las reglas con evento de disparo AfterComplete. Es decir,
que por cada iteración completa que se efectúe en tiempo de ejecución por medio de la transacción, habrá
un COMMIT, justo antes de las reglas con evento de disparo AfterComplete.

Nota: El nuevo tipo de datos Business Component que veremos más adelante permite actualizar la base
de datos desde cualquier objeto GeneXus, pero también como veremos, no realiza automáticamente un
COMMIT.

174
Personalización de unidades de trabajo lógicas
(UTLs) por defecto en GeneXus

• Propiedad Commit on Exit de transacciones y


procedimientos:

Valores:
• Yes (Default): Se ejecuta COMMIT
• No: No se ejecuta COMMIT

GeneXus ofrece una propiedad a nivel de cada objeto transacción y procedimiento, para definir
si se desea que su programa generado efectúe COMMIT, o no. El nombre de la propiedad es
Commit on Exit y su valor por defecto es Yes (por eso, toda transacción y procedimiento por
defecto efectúa COMMIT).

Si se desea que cierta transacción o procedimiento no tenga en su programa generado COMMIT,


bastará con cambiar el valor de la propiedad Commit on Exit a No.

175
Personalización de unidades de trabajo lógicas
(UTLs) por defecto en GeneXus

• Ejemplo de uso de Commit on Exit = No

call
Trn. “X” Proc. “Y”

Commit on Exit = Yes Commit on Exit = No

Es muy importante invocar desde la Trn. “X” al Proc. ”Y” utilizando


un evento de disparo que consideremos adecuado y que ocurra
antes de la ejecución del COMMIT de la Trn “X”.

¿Por qué motivo se puede necesitar no efectuar COMMIT en una transacción o


procedimiento?
Para personalizar una unidad de trabajo lógica (UTL). Es decir, podemos necesitar ampliar una
unidad de trabajo lógica (UTL) para que varias transacciones1 y/o procedimientos, conformen una
única unidad de trabajo lógica (UTL).

Ejemplo (mostrado arriba):

La transacción “X” invoca al procedimiento “Y”, y se desea que ambos objetos conformen una única
UTL. La transacción actualiza ciertos registros, y el procedimiento otros, y se desea que ese conjunto
total de operaciones conforme una única UTL (para asegurarnos de que si ocurre una falla, quede
efectuado el conjunto completo de actualizaciones a la base de datos, o nada).

Para lograrlo podemos eliminar el COMMIT del procedimiento y dejar que se realice en la transacción
(al retornar del procedimiento a la transacción, para que se ejecute al final de todas las
operaciones); de modo que configuraríamos la propiedad Commit on Exit del procedimiento con
valor: No y dejaríamos la propiedad Commit on Exit de la transacción con el valor por defecto:
Yes. Pero además de esto, es fundamental que la invocación al procedimiento se realice antes de
que se ejecute el COMMIT en la transacción (ya que la idea es que ambos objetos conformen
una única UTL, y para ello el COMMIT debe efectuarse en la transacción al retornar del
procedimiento); así que la invocación al procedimiento deberá definirse en la transacción, con un
evento de disparo que ocurra antes de la ejecución del COMMIT (dependiendo de si la transacción es
de un nivel o más, y de los requerimientos, podría servir AfterInsert por ejemplo, AfterUpdate, o
AfterLevel Level Atributo del 2do nivel, o BeforeComplete, pero no AfterComplete).

No existe una única solución para personalizar una UTL. Lo fundamental es analizar cuál objeto
puede hacer COMMIT (pudiendo haber más de una posibilidad) y una vez que se decida cuál objeto
efectuará COMMIT, las invocaciones que se requieran hacer, deberán efectuarse en momentos
adecuados, considerando si ya se efectuó el COMMIT o no.

-----------------------------------------------------------------------------------------------------------
1 En ambiente Web existe una importante restricción a este respecto: si desde una transacción se

invoca a otra, el Commit que realice una no aplica sobre los registros
ingresados/modificados/eliminados por la otra. Es decir, el Commit de cada transacción solo tiene
“visibilidad” sobre los registros operados por esa transacción, y no por la otra, por lo que dos
transacciones distintas no pueden quedar incluidas en una misma UTL. No puede realizarse
personalización en este caso, al contrario de lo que sucede con los procedimientos, donde el
comportamiento es idéntico al de ambiente Win.

176
Por ejemplo, para que la transacción y procedimiento vistos conformen una única UTL, podríamos haber optado
también por la alternativa de que no efectúe COMMIT la transacción (Commit on Exit = No), sino que lo haga
el procedimiento al final de todo; y de hacerlo así, no sería un error –como sí lo sería en la solución anterior-
invocar al procedimiento utilizando el evento de disparo AfterCompete, porque la transacción no hará COMMIT,
sino que lo hará el procedimiento.

Concluyendo, es cuestión de decidir cuál objeto hará COMMIT y que las invocaciones que se deban hacer, se
hagan en momentos adecuados, para que la UTL personalizada quede bien definida.

Otro ejemplo:
Sea la transacción “Invoice” estudiada hasta el momento, en un modelo de Prototipo cliente/servidor.
Supongamos que no modificamos el valor predeterminado de la propiedad Commit on Exit.
Supongamos ahora que el usuario ejecuta la transacción, ingresando la factura 1 con todas sus líneas. Luego
pasa a ingresar la factura 2 y cuando está ingresando la 3era. línea de la misma, ocurre un apagón. Al
recuperarse la energía y reiniciarse la ejecución, ¿qué registros habrán quedado grabados en las tablas y
cuáles se habrán perdido?

La factura 1 íntegra estará grabada (cabezal y sus líneas). ¿Por qué? Pues porque al terminar de ingresarla y
pasar a ingresar la factura 2, se efectuó un Commit. La factura 2 con los registros que se habían grabado hasta
el momento de la falla de energía, se habrá perdido. ¿Por qué? Pues porque la transacción realiza el rollback de
todo lo que se hubiere efectuado luego del último Commit. El cabezal de la factura 2 y las 2 líneas que se
habían ingresado no estaban “commiteadas” aún.

Observar entonces que el Commit no es por transacción entera (es decir, todas las iteraciones del cabezal y
sus líneas) sino por cada instancia de cabezal + líneas.
Si el Commit se realizara una única vez antes de cerrar la transacción, entonces si se hubieran ingresado 29
facturas y a la trigésima se cayera el sistema, se perderían las 29 facturas anteriores (se desharía todo, ya que
aún no se habría alcanzado el Commit). Esto no es así, y si ocurriera una caída del sistema a la trigésima
factura ingresada, las 29 anteriores quedarán grabadas (no así la trigésima).

177
Personalización de UTLs
en ambiente Web
• No puede definirse una UTL compuesta por varias transacciones Web.

call
Trn.“X” Trn.”Y”

UTL 1 UTL 2
• Una transacción Web solo puede Commitear los registros insertados por ella
misma, o por procedimientos en una cadena de invocaciones, pero no puede
Commitear los registros insertados por otra transacción.

call call
UTL
Trn.“X” (luego del
Trn.”Y” (antes del
Proc.”Z”
Commit) Commit)

No pueden quedar dentro


de una misma UTL

En ambiente Web los registros “visibles” para ser commiteados por una transacción son los
actualizados por la propia transacción, y por los procedimientos que ésta invoque antes de su
Commit, pero no los de otra transacción.

Cada transacción trabaja, así, sobre UTLs distintas.

Es por ello que en el primer ejemplo presentado arriba, donde la transacción “X” llama a la
transacción “Y” luego de haber insertado un registro, aunque la transacción “Y” realice un Commit al
final de que cabezal y líneas sean ingresados, este Commit no valdrá sobre el registro que había
sido ingresado previamente por la transacción “X”. Este registro quedará “perdido”, sin Commit.

Por la forma de trabajo en Internet, las transacciones Web “viven” solamente el tiempo entre que el
usuario de un navegador selecciona el link o presiona un botón y la nueva página es mostrada.
Toda modificación a la base de datos que se haga durante la “vida” de la transacción debe ser
confirmada o eliminada antes de que la Transacción Web termine su ejecución y retorne la página
resultante.

Como consecuencia, una Transacción Web inicia una UTL (unidad de trabajo lógica) al comenzar a
ejecutar y la cierra (ya sea por COMMIT o ROLLBACK) antes de terminar. No puede formar parte de
otra UTL. Si un programa llama a una Transacción Web, ésta iniciará otra (nueva) UTL.

En cambio no sucede lo mismo con los procedimientos. En el segundo ejemplo mostrado arriba,
vemos que podemos formar una UTL que engloba a la transacción “Y” y al procedimiento “Z”… sin
embargo no podemos incluir a la transacción “X” en la misma UTL.

178
Personalización de (UTLs)

• Si deseamos que las inserciones mediante dos transacciones distintas


conformen una única UTL:

Trn.“X” Trn.”Y”

Tenemos una solución: utilizar Business Components y el comando Commit al


terminar de insertar mediante las variables Business Components los registros
asociados a ambas transacciones (se verá más adelante).

Si se necesita que las operaciones de dos o más transacciones (con o sin procedimientos incluidos)
conformen una misma UTL, se pueden emular las transacciones con Web panels y Business
Components y utilizar el comando Commit.

Dejamos aquí anotado simplemente el tema, para volver a él luego de estudiados los Business
Components, donde nos será posible comprender esta solución.

179
Comandos COMMIT y ROLLBACK de GeneXus

• GeneXus ofrece los comandos: COMMIT y ROLLBACK

• Se pueden incluir en Procedimientos, Work y Web Panels, así


como en combinación con Business Components.

• Ejemplo (usuario final decide si ejecutar Commit o Rollback):


Se invoca desde una transacción a varios procedimientos
consecutivos, se les configura a todos ellos la propiedad
Commit on exit = No… y en el último procedimiento se le
pregunta al usuario si confirma; dependiendo de la respuesta
del usuario, habrá que ejecutar el comando COMMIT o
ROLLBACK

180
OBJETOS REPORTE
y PROCEDIMIENTO

181
Reportes y Procedimientos

Reportes

• Procesos no interactivos de consulta de la base de


datos.

Procedimientos

• Procesos no interactivos de consulta y actualización


de la base de datos.

Reportes:
Definen procesos no interactivos de consulta a la base de datos. Los reportes son listados que
pueden (dependiendo de la interfaz win o web) emitirse por impresora, visualizarse por pantalla, o
grabarse en un archivo.

Procedimientos:
Definen procesos no interactivos de actualización de la base de datos. Los procedimientos pueden
hacer todo lo que los reportes hacen y además actualizar la base de datos1. Por este motivo, todo lo
que se trate en la presente exposición referido al objeto reporte, será también válido para el objeto
procedimiento.

----------------------------------------------------------------------------------------------------------
1 Como veremos más adelante, existe un tipo de datos especial, que no es estrictamente un tipo de

datos, sino algo un poco más complejo, el business component, por medio del cuál se podrán
realizar actualizaciones a la base de datos en cualquier objeto GeneXus. Por tanto, utilizando
variables de tipo de datos business component, podrán realizarse actualizaciones incluso en los
objetos que por naturaleza no ofrecen esta posibilidad, como los reportes.

182
Características

• Definición procedural

• Definición sobre la base de conocimiento

• Independencia de la base de datos:


definición a nivel de atributos

Definición procedural
A diferencia de las reglas de las transacciones donde las especificaciones se realizan en forma
declarativa y GeneXus determina en el momento de generar el programa la secuencia de ejecución,
en los reportes y procedimientos las especificaciones se realizan en forma procedural. De esta forma,
la secuencia de ejecución es determinada por el analista, utilizando para ello un lenguaje bastante
simple que contiene comandos de control, de impresión, de acceso a la base de datos, etc.

Definición sobre la base de conocimiento


La gran potencia del lenguaje de reportes y procedimientos radica en que las definiciones se hacen
sobre la base de conocimiento y no directamente sobre el modelo físico (tablas, índices, etc.). Esto
nos permite utilizar automáticamente todo el conocimiento ya incorporado o generado por GeneXus a
partir de las especificaciones realizadas.

Por ejemplo, si deseamos desplegar el resultado de una fórmula alcanza con nombrar al atributo
fórmula en el lugar adecuado y GeneXus disparará su cálculo desplegando el resultado, sin necesidad
de que el analista tenga que brindar ninguna otra información. La información de cómo se calcula un
atributo fórmula está contenida en la base de conocimiento.

También podremos utilizar el concepto de tabla extendida, ya que GeneXus conoce las relaciones
entre las tablas de la base de datos, por lo que el analista no necesita explicitar estas relaciones a la
hora de recuperar datos.

Independencia de la base de datos: definición a nivel de atributos


La definición de reportes y procedimientos se hace a nivel de atributos: no es necesario indicar
explícitamente cuáles tablas serán recorridas ni mediante qué índices. Con solo mencionar los
atributos a los que se desea acceder es suficiente para que GeneXus determine esta información. Esto
es posible porque GeneXus tiene un completo conocimiento de la estructura de la base de datos.

De esta manera logramos una real independencia de la base de datos, ya que cualquier cambio en las
tablas será manejado automáticamente por GeneXus y de esta forma, para actualizar los programas
alcanzará, gran parte de las veces, con regenerar los objetos sin tener que modificar nada de lo
programado en ellos.

183
Elementos

• Layout
• Source
• Reglas y Propiedades
• Condiciones
• Ayuda
• Documentación

Como en las transacciones, pueden definirse


variables que serán locales al objeto.

Para cada reporte (procedimiento) se puede definir:

• Layout: Así como las transacciones tienen una pantalla (form), los reportes tienen un “layout” de
la salida. En esta sección se define la presentación del reporte: los datos que se quieren listar y el
formato de la salida.

• Source: Aquí se escribe el código correspondiente a la lógica del reporte. También pueden
definirse al final del código subrutinas1 que podrán ser invocadas desde el propio código mediante el
comando adecuado.

• Reglas-Propiedades: Definen aspectos generales del reporte, como su nombre, descripción, tipo
de salida (impresora, archivo, pantalla), parámetros que recibe el objeto, etc.

• Condiciones: Condiciones que deben cumplir los datos para ser recuperados (filtros).

• Ayuda: Permite la inclusión de texto de ayuda, para ser consultado por los usuarios en tiempo de
ejecución, para el uso del reporte. Se puede redactar una ayuda para cada lenguaje.

• Documentación: Permite la inclusión de texto técnico, para ser utilizado como documentación del
sistema.

---------------------------------------------------------------------------------------------------
1 No se tratarán en el presente curso. Véase Curso No Presencial de GeneXus.

184
Ejemplo

• Queremos implementar un listado como el que


sigue:
área con datos fijos

área con datos fijos

área con datos


variables
(acceso a la
base de datos)

Por ejemplo, supongamos que queremos implementar un reporte para imprimir el identificador,
nombre y país de todos nuestros clientes y queremos que el listado luzca como se muestra en la
figura.

Para ello, debemos identificar en la salida del listado las distintas áreas que lo componen. A cada
una de ellas la representaremos con un print block.

Los primeros dos print blocks lucirán en GeneXus tal cuál las primeras dos áreas señaladas pues
éstas contienen únicamente textos, líneas, recuadros. También podríamos haber fusionado estas dos
áreas convirtiéndolas en una y utilizando por tanto un único print block.

El tercer print block será el correspondiente al área de datos variables de la figura anterior, que
representa información que debe ser extraída de la base de datos.
Lo que queremos mostrar en este caso es el identificador y nombre de cada cliente, junto con el
nombre del país al que pertenece. Esta información es la representada por los atributos CustomerId,
CustomerName y CountryName de la base de conocimiento de la aplicación, por lo que el tercer
print block contendrá los tres controles atributo CustomerId, CustomerName y CountryName.

Transformando las áreas en print blocks, el Layout del reporte nos quedará como el que figura en la
página siguiente.

185
Layout: ejemplo
Nombre de cada print block print block

Layout:
•Sucesión de print blocks
•No importa el orden de definición
•Cada print block debe tener un nombre único
•Solo se declaran, son invocados desde el Source con el
comando “print” (Ej.: print header)

El Layout de un reporte será una sucesión de print blocks que no tienen por qué seguir el orden en
el que se desea que aparezcan en la salida.

En el ejemplo anterior, el mismo reporte habría sido impreso si se hubieran especificado los print
blocks en el orden inverso (o en cualquier orden).

Aquí simplemente se declaran. El orden en el que se ejecutan queda determinado en la sección


Source que es la que contiene la lógica del reporte. Desde allí serán invocados mediante un comando
específico para tal fin (el comando print).

Por esta razón, cada print block deberá tener un nombre único para poder ser referenciado luego
desde el Source.

En el ejemplo, para listar todos los clientes, el print block de nombre “customer” deberá ser invocado
dentro de una estructura repetitiva en el Source. Esta estructura repetitiva es el comando For each
que estudiaremos luego.

186
Layout: print block

• Para definir los print blocks tenemos los


siguientes controles disponibles:

• Texto
• Línea
• Recuadro
• Atributo/Variable
• Bitmap

y para insertar un print block en el Layout aparece el control


“print block”

El print block es un tipo de control válido solamente en reportes y procedimientos, que es insertado
y eliminado del Layout por el analista, y que contendrá otros controles -atributos, textos, recuadros,
líneas, etc.-, siendo estos últimos los que efectivamente especifican qué es lo que se quiere
desplegar en la salida.

Para insertar los controles en el Form de una transacción contábamos con una toolbar. La misma
toolbar se utiliza para insertar los controles en el Layout. De hecho esta toolbar está disponible para
todos los objetos GeneXus que se creen, y en cada caso tendrá habilitados los controles disponibles
según el tipo de objeto y la interfaz (Win o Web).

Para los reportes (y procedimientos) se habilita el ícono correspondiente al print block para poder
insertar este tipo de control en el Layout.

Como todo control, el print block tiene propiedades que pueden ser configuradas por el usuario. En
particular, tiene la propiedad “Name”, muy importante dado que es el identificador del print block.
Con este identificador es que el print block puede ser invocado desde el Source para ser impreso.

187
Source

• Define la lógica del reporte


• Programación procedural
• Lenguaje muy simple
• Comandos usuales de control
• If, Do-case, Do-while, For
• Comandos de impresión
• Print, Header, Footer, etc.
• Comando de acceso a la base de datos y de actualización
• For each – new - delete
• Comandos para salir de un bucle, abandonar el programa,
invocar a otro objeto, invocar a una subrutina, etc.
• Exit, Return, Call, Do, etc.

En esta sección se define la lógica del reporte (o procedimiento).

El lenguaje que se utiliza para programar el código fuente de los reportes y procedimientos es muy
simple, y consta de algunos comandos que iremos viendo.

El estilo de programación es procedural –imperativo– por lo que el Source será una sucesión de
comandos para los que el orden es fundamental: el orden en el que estén especificados
corresponderá, salvo excepciones, al orden en el que serán ejecutados.

Existen, como en todo lenguaje imperativo, comandos de control para la ejecución condicional (if,
do case), la repetitiva (do while, for), para invocar a otro objeto (call), para cortar las iteraciones
dentro de un bucle (exit) o abandonar el programa (return), así como también comandos
específicos de este lenguaje: para imprimir un print block del Layout (print), para acceder a la base
de datos (for each), para insertar nuevos registros en una tabla (new: solo en procedimientos),
para invocar a una subrutina (do), etc.

Al final de la sucesión de comandos que constituye el código general o principal del reporte, pueden
definirse subrutinas que podrán ser invocadas (mediante el comando do) desde el código general.
No pueden ser invocadas desde otro objeto (son locales).

Por su importancia, empezaremos estudiando en profundidad el comando de acceso a la base de


datos, fundamental a la hora de recuperar la información almacenada. Luego se tratarán
brevemente los comandos de control, que son comunes a todos los lenguajes de programación
imperativa, los comandos de asignación y los de impresión.

188
Comando for each

• Se utiliza para acceder a la información de la base


de datos.

• Con un for each se recorre una tabla de la base de


datos: la tabla base del for each.

• Para cada registro de esa tabla, se quiere hacer


algo con la información asociada.
(Ejemplo: imprimirla)

La definición del acceso a la base de datos para recuperación de información se realiza con un único
comando: el comando for each1.

Usando el for each se define la información a la que se va a acceder. La forma de hacerlo se basa en
nombrar los atributos a utilizar.

Así, con este comando se definen qué atributos se necesitan y en qué orden se van a recuperar, y
GeneXus se encarga de encontrar cómo hacerlo. No se especifica de qué tablas se deben obtener, ni
qué índices se deben utilizar para acceder a esas tablas: eso lo inferirá GeneXus. Evidentemente esto
no siempre es posible, y en tales casos GeneXus da una serie de mensajes de error indicando por qué
no se pueden relacionar los atributos involucrados.

La razón por la cuál no se hace referencia al modelo físico de datos es porque de esta manera la
especificación del reporte es del más alto nivel posible, de tal forma que ante cambios en la
estructura de la base de datos la especificación del mismo se mantenga válida la mayor parte de las
veces.

Cuando aparece un for each se está indicando que se quiere recuperar información de la base de
datos. Concretamente GeneXus sabe que con un for each se quiere recorrer (o navegar) una
tabla. Para cada registro de esa tabla, se quiere hacer algo con la información asociada (ej:
imprimirla).

Por lo tanto, todo comando for each tendrá una tabla física asociada: la tabla que será recorrida o
navegada. A esta tabla la llamaremos tabla base del for each.

------------------------------------------------------------------------------------------------------------
1 Cuando estudiemos los business components veremos que utilizando su método Load también se

consigue consultar la base de datos.

189
Comando for each

• Ejemplo: Listado de clientes

CUSTOMER COUNTRY

• Layout:

• Source:
for each
print customer
endfor

Intuitivamente resulta claro que con este comando estamos queriendo listar identificador, nombre y
país de cada uno de los clientes de la base de datos. Es decir, queremos que se recorra la tabla
CUSTOMER, y para cada cliente se recupere de la tabla COUNTRY el nombre del país al que
pertenece, imprimiendo esta información, junto con el identificador y nombre del cliente. (Observar
que la tabla COUNTRY pertenece a la extendida de CUSTOMER)

¿Cómo infiere esto GeneXus si todo lo que hicimos en el for each del ejemplo fue nombrar los
atributos que nos interesaba mostrar?

190
Comando for each

• Tabla que se recorre: CUSTOMER


• Tabla que se accede para cada cliente: COUNTRY

CUSTOMER COUNTRY

INTERPRETACIÓN
For each record in table CUSTOMER
Find the corresponding CountryName in table COUNTRY
print customer
endfor

Dentro de todo for each se navega -recorre o itera- la tabla base, pero puede accederse a las
tablas que constituyen su tabla extendida para recuperar información, que por pertenecer a la
extendida estará unívocamente relacionada con cada registro de la tabla base con el que se esté
trabajando en cada iteración (el concepto de tabla extendida es muy importante en este comando y
sugerimos repasar su definición).

Es por ello que en el for each del ejemplo, la tabla base será CUSTOMER, y además se accederá
“para cada” cliente, no solo a los datos de su registro, sino a los del registro asociado en la tabla
COUNTRY (que está en la extendida de CUSTOMER). Decimos entonces que se recorre CUSTOMER
y se accede además a COUNTRY para buscar el resto de la información requerida.

Como podemos ver claramente en el ejemplo presentado, no le damos explícitamente a GeneXus


esta información. No es necesario, ya que GeneXus conoce las relaciones entre las tablas, y en base
a los atributos mencionados dentro del for each, puede encontrar sin necesidad de más
información una tabla extendida que los contenga.

La tabla base de esa extendida es la que elige como tabla base del for each.

191
Comando for each: determinación tabla base

• El acceso a la base de datos queda determinado


por los atributos que son utilizados dentro del
comando for each.

• Para ese conjunto de atributos, GeneXus buscará


la mínima tabla extendida que los contenga.

• Su tabla base será la tabla base del for each.

A la tabla base correspondiente a esa tabla extendida la llamaremos tabla base del for each
y será recorrida en forma secuencial, ejecutando para cada registro lo que se indique en los
comandos internos al for each.

192
Comando for each: determinación tabla base
{CustomerId, CustomerName,
CountryName} ⊂ ext(CUSTOMER)

{CustomerId, CustomerName,
CountryName} ⊂ ext(INVOICE)

Pero:
ext(CUSTOMER) < ext(INVOICE)

ext(CUSTOMER) es la mínima tabla


extendida que contiene a los atributos
del for each.

Tabla base: CUSTOMER

Para el ejemplo presentado en el que se quieren listar de cada uno de los clientes su identificador,
nombre y nombre de país, si observamos los atributos utilizados dentro del for each, vemos que
ellos son los contenidos en el print block de nombre “customer”: CustomerId, CustomerName y
CountryName.

¿En qué tablas están estos atributos?

• CustomerId está en 2 tablas:


- CUSTOMER como clave primaria (PK).
- INVOICE como clave foránea (FK).
• CustomerName está solo en CUSTOMER (es un atributo secundario).
• CountryName está solo en COUNTRY (es un atributo secundario).

GeneXus conoce las relaciones entre las tablas. Podemos ver el diagrama de Bachman
correspondiente a las tablas en las cuales aparecen los atributos del for each (Tools/Diagrams).

Aquí puede verse claramente el por qué del requerimiento de que la tabla extendida sea la mínima
(entendiendo por mínima aquella que involucra menor cantidad de tablas). La tabla extendida de
INVOICE también contiene a todos los atributos del for each, pero no es mínima, pues la de
CUSTOMER también los contiene.

Por lo tanto, se va a recorrer secuencialmente la tabla CUSTOMER, y para cada registro de esa
tabla, se va a acceder a la tabla COUNTRY, para recuperar el registro de la misma que cumpla:
COUNTRY.CountryId = CUSTOMER.CountryId y para el mismo se va a recuperar el valor del atributo
CountryName, para poder imprimirlo, junto con el código y nombre del cliente.

193
Listado de navegación

tabla base

Se resuelve la consulta ordenada


por la PK de la tabla base

tabla base: la que se navega

Se accede para recuperar info


relacionada (CountryName)

Listado de navegación

GeneXus ofrece para todos sus objetos un listado conocido como listado de navegación, que es el
resultado de la especificación del objeto. Este listado es muy útil para los reportes, ya que indica
cuáles son las tablas que se están accediendo en cada for each del Source, si existe un índice para
recuperar los datos de la tabla base, y en caso de que así sea cuál es ese índice (su nombre), si se
aplican filtros sobre los datos o se van a listar todos, etc.

De esta manera, el analista no tiene que ejecutar el objeto para verificar que la lógica sea la
esperada. Con estudiar el listado de navegación ya tiene la información necesaria para saber si se
está recorriendo la tabla esperada, si se están aplicando correctamente los filtros deseados, etc.

Como puede verse en el listado correspondiente al reporte del ejemplo, muestra para el comando
for each del Source, cuál es su tabla base, por qué orden se va a resolver esa consulta (será el
orden en el que se imprimirán los resultados), si existe un índice que satisfaga ese orden cuál es su
nombre, y además aparecen dos elementos más: los filtros de navegación y el diagrama de tablas.

Los filtros de la navegación indican qué rango de la tabla base se va a a recorrer. En el ejemplo
se va a recorrer toda la tabla base del for each: empezando por el primer registro de CUSTOMER, y
hasta que se alcance el fin de tabla (utilizando el índice ICUSTOMER).

El diagramita de tablas muestra la tabla base del for each con su clave primaria, e indentadas
todas las tablas de la extendida que deban accederse para recuperar información asociada al
registro de la tabla base con el que se esté trabajando en cada iteración del for each.

En el comando for each del ejemplo no aparece explícitamente ninguna información respecto al
orden en el que queremos que se imprima la información. En este caso GeneXus elige como orden
la clave primaria de la tabla base del for each. Es por esta razón que para el for each del
ejemplo GeneXus determinó que el orden será el correspondiente al atributo CustomerId, clave
primaria de la tabla CUSTOMER.

194
For each: cláusulas Where

• Permiten establecer filtros sobre los datos a recuperar


• Ejemplo:

for each
where CustomerName >= &Start when not &Start.IsEmpty()
where CustomerName <= &End when not &End.IsEmpty()
print customer
endfor

• Solo para los registros que cumplan las condiciones booleanas de las
cláusulas where deben ejecutarse los comandos internos al for each.

• Las cláusulas where aplican siempre y cuando se satisfagan las


condiciones de sus cláusulas when (solo válidas para arquitectura
cliente/servidor).

Para restringir los datos que se quieren listar en un for each se utilizan las cláusulas where del
comando.

Si en el listado de clientes no queremos listar todos los clientes, sino solo aquellos cuyo nombre
esté dentro de un rango ingresado por el usuario, entonces debemos agregar al for each que
habíamos visto una clálusula where, para especificar los filtros deseados sobre los datos:

for each
where (CustomerName >= &Start) and (CustomerName <= &End)
print customer
endfor

donde las variables &Start y &End deben definirse en el reporte con el mismo tipo de datos que
CustomerName, y cargarse con valores fijos ó recibidos por parámetro1.

Con la cláusula where definida le estamos diciendo a GeneXus que no queremos quedarnos con
todos los registros de la tabla base, sino solo con aquellos para los que se satisfaga la condición
booleana de la cláusula.

En el ejemplo escribimos una sola cláusula where con una condición compuesta, pero podríamos
haber programado lo mismo con dos cláusulas where, como se muestra arriba en la transparencia.

Es decir, cuando aparecen varios “where” la condición de filtro que se va a aplicar sobre los datos es
la conjunción booleana de todas las condiciones de los “where” que aparezcan.

Observemos que en la transparencia las cláusulas where están a su vez condicionadas con
claúsulas when. Esto se lee de la siguiente forma: se aplicará el filtro establecido por el where
solo cuando se satisfaga la condición del when.
En el ejemplo, solo se aplicará el primer filtro: “CustomerName >= &Start” si la variable &Start no
está vacía. Si está vacía, este filtro no se aplicará. Análogo es el caso de la segunda cláusula when.
Observar que si &Start y &End están vacíos, no aplicará ninguna de las cláusulas where, y por tanto
se listarán todos los clientes (como si las cláusulas where no hubiesen sido escritas).

----------------------------------------------------------------------------------------------------------
1 A través de un objeto que los pide al usuario, como un Work Panel para ambiente Win y un Web

Panel para ambiente Web. En el caso de ambiente Win, también podrá utilizarse la función Ask para
pedir datos al usuario en ejecución.

195
Cada condición booleana de un “where” puede estar compuesta de varias expresiones booleanas
concatenadas con los operadores lógicos and, or y not.

La cláusula when para condicionar la aplicación de una cláusula where solo puede utilizarse en arquitecturas
cliente/servidor. Si no es el caso, y deseamos que si el usuario no ingresa valores en las variables (las deja
vacías), no se realicen los filtros sobre los datos, podemos hacer:

For each
where CustomerName >= &Start or &Start.IsEmpty()
where CustomerName <= &End or &End.IsEmpty())
print customer
Endfor

Un cliente será impreso si cumple con las condiciones de ambos “where”. Si la variable &Start es vacía
entonces todo registro cumplirá la condición del primer where, ya que es una condición compuesta por “or”,
donde una de las subexpresiones da True, por lo que la expresión completa también dará True.

Lo mismo ocurre si la variable &End está vacía.

Por lo tanto, si ambas variables están vacías, todos los registros cumplirán con las condiciones de los
“where”, y por lo tanto no se aplicará filtro alguno sobre los datos. Se listarán todos los clientes.

Pedido de datos al usuario del reporte

Interfaz Win: GeneXus cuenta con la función estándar Ask, que se ejecuta antes de entrar al objeto y pide
al usuario el ingreso de un valor.

Utilizaremos esta función para pedir al usuario los valores que cargaremos en las variables &Start y &End
por medio del comando de asignación. En el Source del reporte escribimos:

&Start = Ask( ‘Ingrese nombre de cliente inicial:’ )


&End = Ask( ‘Ingrese nombre de cliente final:’ )

Notas
• No importa el lugar del Source donde se especifiquen estos comandos, dado que la función Ask se
ejecutará siempre al principio.

• Lo mismo puede programarse utilizando la regla de asignación, en lugar del comando de asignación, en
las Rules del reporte, en lugar de hacerlo en el Source.

Interfaz Web: El reporte no tendrá ningún tipo de interacción con el usuario, por lo que la función Ask no
tendrá efecto alguno. Por tanto si el reporte va a ejecutarse en el marco de una aplicación Web, deberán
pedirse estos valores al usuario en uno de los objetos diseñados específicamente para tal fin: esto es, el
Web Panel y luego invocar al reporte enviándole estos datos por parámetro. También podrá utilizar esta
solución en un ambiente Win, utilizando el objeto análogo: el Work Panel1.

-----------------------------------------------------------------------------------------------------------
1 Para ver cómo realizar esto, puede dirigirse al capítulo del objeto correspondiente (Web Panel o Work
Panel según corresponda), y estudiar el primer panel presentado, clasificado como panel de entrada.

196
Listado de navegación

Listado de navegación

Aparece un nuevo elemento en este listado que no estaba presente antes, cuando no teníamos
cláusulas where: las constraints (restricciones).

¿Qué información nos brinda este listado de navegación?

• que la tabla base del for each seguirá siendo CUSTOMER

• que se seguirá ordenando la consulta por CustomerId, utilizando el índice ICUSTOMER


correspondiente

• que seguirá recorriendo toda la tabla CUSTOMER en busca de la información

•pero que para cada cliente evaluará si cumple con las restricciones que aparecen enumeradas
(CustomerName>= &Start si &Start no está vacía y CustomerName<=&End si &End no está vacía)
y solo en caso de que las cumpla, ejecutará para ese cliente los comandos que aparecen dentro del
for each. En este caso, imprimirá los valores de los atributos del print block “customer”:
CustomerId, CustomerName, CountryName.

• que debe acceder a la tabla COUNTRY cuya clave primaria es CountryId para obtener algún dato
(CountryName)

197
For each: cláusulas Where

• Atributos permitidos: los de la tabla extendida de


la tabla base del for each

• Ejemplo:
for each
where CountryName = ‘Uruguay’
print customer
endfor

Los atributos utilizados en las condiciones de filtro pueden ser cualesquiera de la tabla extendida
del for each.

En el ejemplo, si bien la tabla base del for each es CUSTOMER, estamos filtrando los datos a
recuperar utilizando el atributo CountryName, que es de la tabla COUNTRY, perteneciente a la
extendida de CUSTOMER.

En este ejemplo tampoco se explicita nada con respecto al orden, por lo cuál los datos aparecerán
ordenados por la clave primaria de la tabla base, es decir, por identificador de cliente, CustomerId.

198
For each: cláusula Order
• Permite establecer el orden en el que se quieren
recuperar los datos.
• Ejemplos:
for each order CustomerName
print customer
endfor
for each
order CustomerName when not (&Start.IsEmpty() and
&End.IsEmpty())
print customer
endfor
• Para determinar orden descendente se deben colocar
paréntesis rodeando a los atributos del orden.
Ejemplo: order (CustomerName)

Si queremos realizar un listado de todos los clientes pero ordenado por nombre del cliente en lugar
de por código, lo único que tenemos que hacer es modificar el comando for each agregando esta
información del orden.

Esto se logra utilizando la cláusula order del for each, como se muestra en el primer ejemplo.

Como no existe un índice definido en la tabla CUSTOMER por el atributo CustomerName, GeneXus
indicará en el listado de navegación mediante una advertencia (“warning”) que no existe un
índice para satisfacer el orden, lo que podría ocasionar problemas de performance, dependiendo
de la plataforma de implementación elegida, de la cantidad de registros que deben ser leídos de la
tabla, etc.

En algunas plataformas, como VFP o iSeries (AS/400), si no existe índice para satisfacer el orden
especificado se crea uno temporal cada vez que se ejecuta el reporte y se elimina al finalizar la
ejecución. Este tipo de índice es el que llamamos índice temporal.

En otras plataformas, como VB con Access o en ambientes cliente/servidor, si no existe índice para
satisfacer el orden, el for each se traduce en una consulta SQL (“select”) que es resuelta por el
motor del DBMS de la plataforma.

Al igual que en el caso de las cláusulas where, en plataformas cliente/servidor la cláusula order
puede condicionarse, como se muestra en el segundo ejemplo.
En caso de no cumplirse la condición del when, no aplicará ese orden y de no existir orden
incondicional (sin cláusula when) como en el ejemplo, el orden a utilizar será indefinido,
significando esto que el orden resultante podrá variar de DBMS a DBMS e incluso entre ejecuciones
sucesivas.

Pueden especificarse varias cláusulas order condicionadas (con cláusula when) consecutivas en
casos de arquitecturas cliente/servidor y una sin condición (la última de la lista). La primera
cláusula order cuya condición del when se satisfaga, será la elegida y su orden el utilizado.

Cláusula Order None: cláusula que evita que GeneXus elija por defecto el orden de los atributos
de la clave primaria de la tabla base y utilice un orden de navegación indefinido.
La cláusula order none admite condición para aplicarse (when).

199
Listado de navegación

for each order CustomerName


print customer
endfor

Listado de navegación

Cuando no existe índice que satisfaga el orden de un for each, como es el caso del ejemplo, el
analista GeneXus puede resolver crearlo (índice de usuario). En la mayoría de los casos el reporte
será bastante más eficiente de esta manera, pero se debe mantener un índice más (lo que implica
mayor almacenamiento y mayor procesamiento para mantener actualizado el índice)

No es posible recomendar a priori cuál de las dos soluciones es la mejor (no índice vs índice de
usuario), por lo tanto se debe estudiar, caso por caso, la solución particular teniendo en cuenta la
plataforma de implementación y siendo fundamental la frecuencia con que se ejecutará el reporte.
De cualquier manera, si al comienzo no se definió el índice de usuario y posteriormente se decide
definirlo, alcanza con regenerar el reporte (sin modificar nada de lo programado en el mismo) y
éste pasará a utilizarlo.

El listado de navegación anterior nos informa que:

• la tabla base del for each es CUSTOMER


• y se recorrerá ordenada por CustomerName
• no existe un índice definido para ese atributo
• se recorrerá toda la tabla CUSTOMER con el orden especificado
• no hay condiciones de filtro, por lo que para todos los registros de la tabla se ejecutarán los
comandos dentro del for each (en nuestro caso, el comando print).

200
For each: cláusula Order

• Atributos permitidos:

• los de la tabla extendida del for each, salvo que


se trate de una plataforma centralizada.

• en plataforma centralizada solo pueden utilizarse


atributos de la tabla base.

De tratarse de una plataforma centralizada (Visual Basic / Access o Visual Fox Pro / DBFs), al
especificar un reporte que contenga:

For each order CountryName


Print customer
endfor

se desplegará en el listado de navegación un mensaje de error indicando que no es posible ordenar


por CountryName. El motivo es que la tabla base del for each es CUSTOMER, y CountryName no
pertenece a la misma.

En cambio, si se está generando en una plataforma cliente/servidor, no habrá ningún problema con
un reporte que contenga el comando anterior.

201
OPTIMIZACIÓN: Orden compatible
con los filtros
for each
where CustomerName >= &Start Se recorre
where CustomerName <= &End toda
print customer la tabla base
endfor

for each order CustomerName No se recorre


where CustomerName >= &Start toda
where CustomerName <= &End la tabla base:
print customer ¡optimizado!
endfor
Hay que evaluar asiduidad de esta consulta, creación
del índice, y su mantenimiento.

Si nos interesa filtrar los clientes de forma tal que sus nombres pertenezcan a un rango, en el primer
ejemplo, como no especificamos cláusula order GeneXus ordena por clave primaria, es decir, por
CustomerId.

En este caso, el listado de navegación nos va a informar que se debe recorrer toda la tabla
CUSTOMER, y para cada registro de la misma se debe evaluar si el registro cumple o no con las
condiciones (restricciones o “constraints”). En caso afirmativo, se imprimen para el mismo los datos
correspondientes.

Si en lugar de ordenar los datos por CustomerId pedimos que se ordenen por CustomerName, como
se presenta en el segundo ejemplo, la tabla base se recorre ordenada por CustomerName y como en
los filtros establecemos que queremos quedarnos solo con aquellos clientes cuyo nombre,
CustomerName, esté en el rango determinado, entonces ¡ya no será necesario recorrer toda la tabla
base para obtener los datos que cumplen con las condiciones!

Diremos que esta segunda consulta está optimizada en ese sentido. Tener en cuenta, sin embargo,
que GeneXus no tiene creado un índice en forma automática por CustomerName, y aquí habrá que
evaluar si conviene crear un índice de usuario (que debe ser mantenido por Genexus luego) o no
crear índice y que se cree un índice temporal en ejecución para resolver la consulta si el DBMS no
puede resolverlo de otra forma.

El listado de navegación nos informará si una consulta está o no optimizada, de acuerdo a si


recorrerá toda la tabla (desde “First Record” hasta “End of table”) o si recorrerá por el contrario un
rango más reducido.

202
Definición de la estrategia de
acceso a las tablas
Begin of table Begin of table
READ READ
READ READ
READ READ Start point
READ READ
READ READ
READ READ End point
READ READ
READ READ

End of table End of table

1. Se define el orden en que se recorrerá la tabla.


2. Se elige el punto de comienzo (Starting Point)
3. Se elige la posición final (End Point).
4. Se leen secuencialmente los registros entre el punto
inicial y el punto final, seleccionando los registros que
cumplen las condiciones y descartando los restantes.

Normalmente los programas procesan registros agrupando aquellos que tengan ciertas
características comunes, por ejemplo, todas las facturas de un determinado cliente, o las ventas de
una clase de artículos.

Estas características comunes se establecen a través de condiciones que determinan un filtro sobre
los registros. Tales condiciones se especifican en GeneXus de diversas formas, una de las cuales es
el caso de las cláusulas where en un For each.

En el ejemplo que estamos viendo: de todos los clientes, quiero imprimir los datos de aquellos cuyo
nombre esté en un rango determinado.

El tiempo necesario para realizar el proceso con los registros que cumplen las condiciones determina
el grado de "optimización" que tiene la estrategia de acceso elegida. Decimos que una estrategia de
acceso está más "optimizada" que otra cuando es necesario un menor tiempo para leer todos los
registros que cumplen las condiciones establecidas.

La optimización consiste en posicionarse lo más cerca posible del primer registro del grupo que
cumple las condiciones y desde allí leer secuencialmente hasta el último que las cumpla. Lo que se
establece, en definitiva, es un intervalo que cubre todos los registros que cumplen las condiciones,
de manera tal que los que están fuera de ese intervalo ni se consideran, pues ya se sabe que no
podrán cumplir las condiciones.

En este intervalo no necesariamente todos los registros cumplen la condición. En estos casos, la
mejor estrategia consiste en tener el menor intervalo tal que cubra a todos los registros que
cumplirán la condición. Igualmente las lecturas se realizarán para todos los registros de este
intervalo, pero solamente se considerarán aquellos que cumplan con las condiciones.

Para definir una buena estrategia de acceso, GeneXus cuenta con una inteligencia razonable.

Se determinará, estableciendo (de acuerdo al orden en que deban considerarse los registros y a las
condiciones), una posición inicial y una posición final en el archivo, leyendo en forma secuencial en
este intervalo.

203
For each:
cláusula Defined by

• No ofrece funcionalidad alguna en lo que respecta a los datos a


recuperar.

• Se utiliza exclusivamente para dar un elemento más para la


determinación de la tabla base del for each deseada.

• Ejemplo: for each


defined by InvoiceDate
print customer
endfor
Mín. extendida que contiene a InvoiceDate, CustomerId,
CustomerName, CountryName:

ext(INVOICE) Æ tabla base INVOICE

Puede darse el caso de que para un For each haya más de una tabla base cuya extendida contenga
a los atributos del for each, siendo mínima. Ante esta ambigüedad, GeneXus escoge la “primera” de
estas tablas extendidas mínimas.

Para resolver este tipo de ambigüedad surge la cláusula defined by, que permite nombrar atributos
de la tabla base deseada, que no se utilizarán para devolver la consulta ordenada por esos
atributos, ni para filtrar la información a recuperar, ni para ser desplegados en el listado (es decir,
no tienen funcionalidad alguna con respecto a los datos a recuperar), sino solo para aportar más
información que permita determinar la tabla base del for each.

En la cláusula Defined by se debe hacer referencia a por lo menos un atributo de la tabla base
deseada.

De la misma manera, se puede (y se suele) utilizar para modificar la que sería la tabla base en caso
de no nombrarse ningún atributo más dentro del For each. Este es el caso del ejemplo presentado,
en el que no queremos listar todos los clientes de la tabla CUSTOMER, sino por el contrario,
queremos listar todos los clientes de las facturas. De no nombrarse dentro del For each algún
atributo de INVOICE, la tabla base sería CUSTOMER.

En la mayoría de los casos no es necesario utilizar esta cláusula. Sin embargo, para reportes más o
menos complejos, aún cuando no exista problema de ambigüedad, se recomienda el uso del
Defined by porque mejora bastante el tiempo de especificación del reporte.
Sin embargo, no puede aconsejarse un uso indiscriminado. La contra de utilizar esta cláusula
cuando no es necesaria es que ata un poco más el código al diseño de las tablas.

Supóngase por ejemplo que no se tiene creada una tabla COUNTRY, y se tiene de cada cliente el
país al que pertenece, como atributo secundario. Si lo que queremos es listar los clientes y su país,
serían equivalentes:

For each For each


Defined by CountryName
print customer print customer
endfor endfor

donde customer es un print block con los atributos CustomerId, CustomerName y CountryName.
Si ahora decide crearse la tabla COUNTRY y tener en la transacción “Customer” a CountryId como
FK, si bien el primer for each continuará siendo válido y haciendo lo que queremos, el segundo
dejará de funcionar, ya que en el Defined By no hay ningún atributo de la tabla base.

204
For each:
cláusula Defined by

• Atributos permitidos: pueden aparecer varios


atributos de la tabla extendida, pero al menos uno
debe corresponder a la tabla base que se desea
(en caso contrario dará un error)

Pueden aparecer varios atributos, para el posible caso en el que no alcance uno solo para
determinar completamente la tabla base deseada, donde al menos uno de ellos deberá estar
asociado a la tabla base deseada.

Se recomienda el uso en el Defined by de atributos secundarios de la tabla base que se desea


navegar, ya que como sabemos, los atributos secundarios solo pueden estar en una tabla del
modelo y de esta forma eliminamos por completo toda posible ambigüedad.

Esto sin embargo, no es obligatorio, es decir, pueden nombrarse en el Defined by atributos


primarios cuando se sepa que no habrá ambigüedad en la elección de la tabla base.

Un error común es creer que cuando un for each tiene esta cláusula, la tabla base del mismo queda
determinada exclusivamente a partir de los atributos mencionados en el defined by.

La realidad es que los atributos del defined by arrojan una o más tablas base candidatas a ser la
tabla base del for each, pero luego hay que ver si tomando cada tabla base candidata, su extendida
contiene a todos los demás atributos del for each, además de los del defined by.

Si ninguna de las posibles tablas base candidatas cumplen esta condición, entonces el reporte dará
un error al ser especificado, y no podrá ser generado (recordemos que debe cumplirse que todos los
atributos del for each estén contenidos en una misma tabla extendida).

205
For each:
cláusula When none

• Permite ejecutar determinado código cuando en un for each


no se encuentra ningún registro que cumpla las condiciones.

• Ejemplo:
for each
where CustomerName >= &Start
where CustomerName <= &End
print customer
when none
print message
endfor

El print block message (que podrá contener un texto advirtiendo al usuario de que no existen
clientes que cumplan los filtros) se ejecuta sólo cuando no se entra en el For each, es decir, cuando
no hay ningún registro correspondiente a la tabla base del for each para el que se cumplan las
condiciones de filtro.

También se aplica a for each [selected] line, XFor Each y XFor First, comandos que veremos más
adelante.

La cláusula when none debe ser la última dentro del For each. Las acciones a realizar cuando no
existe ningún registro para el que se cumplan las condiciones, quedan determinadas por el bloque
de código que hay entre la cláusula when none del for each y el endfor.

Cuando un for each no tiene condiciones de filtro, los comandos del when none se ejecutarán solo
en el caso en que se cumpla que la tabla base del For each esté vacía, porque solo en ese caso no
habrá ningún registro que cumpla las condiciones de filtro.

Importante:

• Si aparecen atributos en el bloque de código correspondiente al when none, éstos no son tenidos
en cuenta para determinar la tabla base del for each.

• Si se incluyen for eachs dentro del when none no se infieren Joins ni filtros de ningún tipo con
respecto al for each que contiene el when none, ya que son considerados for eachs paralelos.

206
Comando For each - Sintaxis

for each
[{[order] order_attributes [when condo] }…|
[order none] [when condon]]
[{where condition when condw }…]
[defined by defined_attributes]
code1
[when duplicate
code2]
[when none
code3]
endfor

La sintaxis presentada generaliza el ejemplo con el que vinimos trabajando. Es la sintaxis general
que aplica a plataformas Cliente/Servidor. Para plataformas centralizadas existen algunas
limitaciones (cláusulas when y order none no aplican a este caso).

Order
order_attributes::= att1, …, attn
Es una lista de atributos, que indican el orden en el que será devuelta la consulta, siendo atti un
atributo de la base de conocimiento escrito simple, o entre paréntesis curvos. Cuando un atributo
del order aparece rodeado de paréntesis curvos se está indicando orden descendente para el
mismo.

Aquí pueden mencionarse atributos de la tabla extendida, a menos que se esté trabajando en
una plataforma centralizada, en cuyo caso solo podrán utilizarse atributos almacenados en la
tabla base.

Para plataforma centralizada, podrá especificarse a lo sumo una cláusula order, sin condición
(when). Sin embargo puede no especificarse cláusula order. En tal caso se asume como orden el
de la clave primaria de la tabla base, salvo excepciones que estudiaremos oportunamente.

Para plataforma cliente/servidor es posible definir varias cláusulas order condicionales, y una
incondicional, que debería ser la última listada. El por qué responde al hecho de que como
solamente una de esas cláusulas order tomará efecto, se van evaluando sus condiciones (las del
when) hasta la primera que de True, y con esa se queda. Si ninguna da true y existe una cláusula
incondicional (es decir, sin when), se tomará ese orden. Si no existe tal cláusula, el orden será
indefinido, queriendo esto significar que dependerá de la plataforma, e incluso podrá variar entre
ejecuciones sucesivas. La justificación para escribir cláusulas order condicionales, deriva de si
queremos aplicar cláusulas where condicionales. Es decir, por motivos de optimización de las
consultas.
Por ejemplo, si queremos filtrar por CustomerName > &Name when not &Name.IsEmpty(),
entonces para optimizar la consulta deberíamos ordenar por CustomerName, pero si no se va a
aplicar el filtro, dado que &Name está vacía, entonces será mejor dejar un orden indefinido.
Para ello especificamos la cláusula order condicional:
order CustomerName when not &Name.IsEmpty()

En lugar de todo lo anterior, también puede especificarse una cláusula order none que se
agrega para cuando no nos interesa un orden en particular y queremos que éste quede indefinido.

207
Elección del índice: GeneXus elige automáticamente el índice a utilizar para satisfacer el orden. En caso de que
no exista, informa de esta situación en el listado de navegación y dependiendo de la plataforma, crea un índice
temporal o deja en manos del DBMS la elección de la estrategia para procesar la consulta.

Los atributos del order son tenidos en cuenta a la hora de determinar la tabla base del for each. Pero ellos por
sí solos no la determinan. Deben examinarse también otras partes del for each.

Where
Condition
Condición booleana que deberán cumplir los datos para ser procesados dentro del for each, pudiendo ser una
condición compuesta, utilizando los operadores lógicos and, or y not.

Los atributos que aparezcan en la condición booleana pueden ser tanto de la tabla base del for each como de
la extendida.

Como se desprende de la sintaxis, para un mismo for each pueden especificarse n cláusulas where sucesivas, cada
una con una condición:
where cond1
where cond2
...
where condn

La ocurrencia de n cláusulas where es equivalente a la ocurrencia de una sola cláusula, con la conjunción
booleana de las condiciones:

where cond1 and cond2 and … and condn

Los datos de la tabla extendida del for each que cumplan con todas las condiciones de los “where” serán los
procesados en los comandos internos al for each (los del bloque de código code1).

Para el caso de plataformas cliente/servidor, al igual que ocurre con la cláusula order, podrán condicionarse los
filtros (con cláusulas when). De esta manera, primero se evalúa la cláusula when de cada cláusula where, y de
cumplirse su condición, aplicarán el filtro especificado en el where.

Para que una restricción condicional pueda ser generada como tal, se debe estar en una arquitectura
Cliente/Servidor y la condición del when tiene que ser "evaluable" por el DBMS que se está utilizando, es decir,
GeneXus tiene que saber cómo escribir la condición en el lenguaje propio del DBMS utilizado.

Si no se puede generar como tal (porque el generador no lo soporta o porque la condición no puede ser escrita en
el lenguaje del DBMS) se transformará en un filtro "común" sustituyendo el WHEN por un OR. Además, se
generará el mensaje de código spc0053 – ‘Unsupported conditional constraint”%1” changed to standard
constraint %2.’ - en el Diagrama de Navegación.

Nota: Existe también la cláusula Option Distinct del For Each que permite retornar los registros que cumplan
unicidad de valores de los atributos referenciados. No veremos esta cláusula. El lector interesado puede recurrir a
las distintas fuentes de documentación para estudiarla (Help, GXDL, Wiki, Release Notes, etc.)

Defined by
defined_attributes::= att1, att2,…,attp
Es un conjunto de atributos que serán utilizados a los solos efectos de determinar la tabla base del for each.

Al mencionar aquí algunos atributos de la tabla que se desea recorrer, éstos participarán en la determinación de la
tabla base del for each.

La cláusula defined by aparece para solucionar algunos problemas de ambigüedad en la determinación de la


tabla base (cuando existen varias tablas extendidas mínimas que contienen a los atributos del for each) o cuando
se desea que la tabla base sea otra, distinta de la que sería determinada por los atributos que aparecen en el
resto del for each (este caso cobrará sentido cuando se estudie “corte de control”). También se utiliza para
mejorar el tiempo de especificación en reportes complejos.

Los atributos de esta cláusula no determinan por sí solos la tabla base del for each. Podría darse el caso de que de
estos atributos surja determinada tabla como la candidata a tabla base, pero si luego el resto de los atributos
del for each no están contenidos en la extendida de esa tabla, el for each dará un error y el objeto que lo contiene
no podrá ser generado.

code1
Es una sucesión de comandos en los que pueden utilizarse atributos de la tabla extendida del for each. A este
bloque de código le llamaremos cuerpo del for each.

Los atributos que figuren en este bloque de código participarán en la determinación de la tabla base del for each.

208
Los comandos especificados se ejecutarán secuencialmente para los datos de la tabla extendida que cumplan las
condiciones de filtro, considerándose los datos en el orden especificado.

When Duplicate
Esta cláusula solo tiene sentido en procedimientos (dado que tiene que ver con la actualización) y se verá más
adelante.
Se ejecutará esta cláusula si dentro del cuerpo del For each code1, se intenta actualizar un atributo que es clave
candidata (tiene índice unique) y ya existe un registro con ese valor. GeneXus utiliza el índice unique para
asegurar la unicidad de esa clave candidata y en caso de encontrar duplicados, si el for each tiene programada
esta cláusula, ejecutará su código: code2 .
De no existir la cláusula no se ejecutará código alguno.

When none
En caso de que no existan datos que cumplan las condiciones de filtro no se ejecutarán los comandos del code1
sino que se ejecutarán los del bloque de código code3.

¾ Tanto para When Duplicate como para When none: Si se incluye dentro de alguno de los dos un comando for
each, no se infieren joins ni filtros con respecto al for each que los contiene (el del when none | when duplicate).
Son consideradas navegaciones independientes (la del code1, code2 y code3).

209
For eachs paralelos

• Llamamos de esta forma al caso de for eachs que están escritos


en forma secuencial (no anidada)
Ejemplo:
for each
print invoice
endfor
for each
print bill
endfor

• También aplica al caso en el que un for each aparece dentro de la


cláusula when none o when duplicate de otro.
• Las navegaciones son totalmente independientes

El For each es un comando como otros, y por tanto puede aparecer varias veces dentro del Source,
tanto en forma paralela (independiente), como anidado a otro for each.

Cuando dentro del cuerpo del for each (code1) aparece otro for each, decimos que se trata de for
eachs anidados. GeneXus soporta varios niveles de anidamiento para los for eachs.

El caso en el que un for each aparece en el bloque de código code3, si bien puede verse como un
anidamiento de for eachs porque uno aparece dentro de otro, el comportamiento en cambio, es
como el del caso de for eachs paralelos.

Un for each “anidado en el when none” de otro, solo se ejecutará si no existe ningún registro de la
tabla base del for each que lo contiene que cumpla las condiciones de filtro.

En el ejemplo, “invoice” y “bill” son dos print blocks del Layout que continen atributos de las tablas
INVOICE y BILL (recibo) respectivamente.

Hemos definido dos for eachs paralelos. El primero recorrerá todas las facturas y el segundo todos
los recibos.

210
For eachs anidados

• Se busca recuperar por cada registro del for each


principal, muchos registros del anidado

for each
...
for each Cuerpo del for each principal
...
endfor
...
when none
...
endfor

El for each es una estructura repetitiva que permite recuperar muchos registros de una tabla. Cuando
uno piensa en for eachs anidados, es evidente que lo que se busca recuperar es, por cada registro del
principal, muchos registros del anidado.

¿Qué cosas podría GeneXus detectar que se quiere hacer con este tipo de estructuras, de forma tal de
poder inferir el comportamiento automáticamente con la menor codificación posible?

• para cada registro de una tabla recuperar algunos de otra: los relacionados.
• para cada registro de una tabla recuperar todos los de otra.
• procesar información por grupos, esto es, agrupar los registros de una tabla según el valor de un
atributo o conjunto de atributos y para cada grupo, recuperar algunos registros: los correspondientes
al grupo.

Siempre la relación es uno a muchos: por cada registro de una tabla recuperar muchos de la otra
(pudiéndose tratar de la misma tabla).

Utilizando esta lógica es que GeneXus infiere las tablas base y el comportamiento de los for eachs
anidados, sabiendo que lo que se desea es implementar alguna de las tres opciones anteriores.

Por ejemplo si queremos realizar un listado de todas las facturas del sistema, donde para cada una
queremos imprimir también el detalle de la misma, debemos recorrer dos tablas: INVOICE (que
almacena los cabezales) e INVOICELINE (que almacena las líneas), y lo haremos de una forma bien
simple, sin nombrar las tablas, y sin tener que explicitar todo, como veremos.

Otro ejemplo es el de un listado de todos los clientes, donde para cada uno se quieren imprimir,
además de sus datos personales, los datos de todas sus facturas. Este comportamiento se logra con un
par de for eachs anidados, donde el primero recorrerá la tabla CUSTOMER y el segundo recorrerá la
tabla INVOICE, recuperando solo las facturas de ese cliente, como veremos en breve.

211
For eachs anidados

• Ejemplo: imprimir todas las facturas con sus respectivas


líneas

for each
print invoice_header {InvoiceId, InvoiceDate, CustomerName}
for each
print invoice_lines {ProductDescription, ProductPrice,
InvoiceLineQuantity, InvoiceLineAmount}
endfor
print invoice_total {InvoiceTotal}

endfor

Queremos realizar un listado de todas las facturas del sistema, donde para cada una se muestre
tanto información de su cabezal como de sus líneas.

En el Source programado, claramente recorremos la tabla INVOICE, imprimiendo los atributos que
nos interesan del cabezal y para cada registro de esa tabla, recorremos la tabla INVOICELINE, para
imprimir los atributos que nos interesan de sus líneas.

¡Tan simple como eso! Otra vez, nos alcanza con nombrar los atributos que queremos utilizar y del
resto se encarga GeneXus.

Observemos que ni siquiera tuvimos que especificar condición de filtro sobre los registros de
INVOICELINE a recuperar. GeneXus se da cuenta de la relación existente entre las tablas, y aplica
automáticamente la condición de filtro sobre los datos, de manera tal de solo imprimir las líneas de
“esa” factura (recordar que algo idéntico ocurría con las fórmulas verticales, como InvoiceTotal,
donde la condición de filtro sobre los registros a ser sumados o contados quedaba implícita, y no
había que especificarla).

212
For eachs anidados

• Ejemplo: imprimir todas las facturas con sus respectivas líneas

INVOICE INVOICELINE

Para cada registro de INVOICE imprimir algunos de sus datos


(InvoiceId, InvoiceDate) y de su extendida (CustomerName).

Luego navegar INVOICELINE y recuperar los registros que


correspondan a la factura que se está imprimiendo.

Los que cumplan la condición implícita:

INVOICELINE.InvoiceId = INVOICE.InvoiceId

Como para un for each simple, GeneXus debe determinar para cada for each (principal y anidado)
cuál es su tabla base. Y luego, a partir de esa determinación, inferirá la lógica correspondiente.

Más adelante veremos con exactitud cómo es que GeneXus determina cada tabla base. Aquí nos
quedaremos con la idea intuitiva de que lo hace en forma similar a como lo hacía para el caso de un
for each simple.

Encuentra, pues, que debe recorrer las tablas INVOICE e INVOICELINE.

Como esas tablas están relacionadas de acuerdo a una relación 1-N infiere más que eso: infiere
además que debe aplicar la condición sobre los datos de INVOICELINE que se indica arriba.

213
For eachs anidados

GeneXus debe:

1. Determinar la tabla base de cada for each.

2. A partir de eso definir las navegaciones que


realizará para cada for each (existen 3 casos
posibles).

La lógica de los for eachs dependerá de las


relaciones que encuentre entre las tablas
determinadas.

Cuando tenemos for eachs anidados, GeneXus debe determinar la tabla base de cada uno, y esas
serán las tablas que se navegarán.

Para cada registro de la tabla base del for each principal, se ejecutarán los comandos del cuerpo del
mismo. Entre esos comandos, se encuentra el for each interno, que se ejecutará, como cualquier
otro comando, en el lugar donde se encuentre, realizando una navegación sobre su tabla base.

Pero para la determinación de la tabla base del for each anidado, podrá influir la tabla base del for
each principal, por lo que las determinaciones no son por completo independientes, como podría
pensarse.

A partir de la determinación de las tablas base de cada for each primero y de las relaciones
que encuentre GeneXus entre las tablas involucradas luego, surgen tres posibles casos de for eachs
anidados: join, producto cartesiano y corte de control, respectivamente.

Estudiaremos en lo que sigue cada uno de estos casos con ejemplos, para luego generalizar todo lo
visto.

214
For eachs anidados:
determinación tablas base

• Se procede en forma ordenada, determinando for each


cada vez la tabla base de un nivel de anidación, ...
yendo de afuera hacia adentro: primero se for each
determina la tabla base del for each más ...
externo, luego del que está anidado endfor
a éste y así sucesivamente. ...
when none
...
• Para cada for each, intervienen únicamente endfor
los atributos propios de ese for each: del
order, where, defined by y todos los del cuerpo que no
pertenezcan a un for each anidado (tampoco intervienen
los de la cláusula when none).

Consideremos el caso más simple, de un par de for eachs anidados.

• La determinación de la tabla base del for each principal, es análoga al caso de for each
simple (sin anidamientos). En este caso se consideran todos los atributos del for each principal,
descartando los for eachs anidados que éste contenga (y todos sus atributos). GeneXus encuentra
la mínima tabla extendida que contenga a los atributos referidos y define así la tabla base a través
de la cual llega a todas las demás.

• Para determinar la tabla base del for each anidado, GeneXus se fija si los atributos
utilizados dentro del cuerpo del mismo están incluidos o no dentro de la tabla extendida
previamente determinada (la del for each principal). En caso afirmativo, GeneXus determina que la
tabla base del for each anidado será la misma que la del for each principal.
En caso contrario, se busca la mínima tabla extendida que cumpla que contiene a todos los
atributos del for each anidado. Su tabla base será la del for each. (Observar que solo en este caso
se procede a determinar la tabla base como si se tratase de for eachs independientes).

Veremos un esquema resumiendo lo anterior más adelante.

215
For eachs anidados:
determinación tablas base
Tabla base for each externo
for each
print invoice_header {InvoiceId, InvoiceDate,
for each CustomerName}
print invoice_lines
endfor INVOICE
{InvoiceTotal}
print invoice_total
endfor

for each Tabla base for each interno


print invoice_header
for each {ProductDescription, ProductPrice,
print invoice_lines InvoiceLineQuantity,
endfor InvoiceLineAmount}
print invoice_total INVOICELINE
endfor

Para el ejemplo que presentamos antes, mostramos arriba cómo se determina cada tabla base.

Para la del anidado, como los atributos que figuran no están contenidos en la tabla extendida de
INVOICE (que es la tabla base del principal), entonces se pasa a determinar su tabla base como si
se tratase de un for each independiente.

Este es el caso general, pero en algunas circunstancias particulares GeneXus toma un criterio más
exhaustivo intentando encontrar relación 1-N entre los for each y en esos casos, la determinación
de la tabla base del for each anidado (cuando los atributos del mismo no están incluidos en la
extendida del principal) no es independiente. GeneXus busca otra relación, pero no entraremos en
estos casos particulares en el presente curso. De sucederle al estudiante, lo notará claramente en el
listado de navegación.

216
For eachs anidados:
lógica asociada

• Tablas base distintas


¿Existe relación implícita que lo vincule con
¿ Fe externo un número N de registros del for each anidado?
1

Sí Join: se recuperan algunos registros del


N anidado, los relacionados. (Caso 1)
Fe anidado ?
No Producto cartesiano: se recuperan
todos los registros del anidado. (Caso 2)

• Tablas base iguales


• Corte de Control: Corresponde al caso en el que queremos
recuperar información por grupos. (Caso 3)

De la determinación de las tablas base, surgen los tres casos de for eachs anidados que se
mencionan y que estudiaremos uno a uno en lo sucesivo.

Este tema es de vital importancia, ya que la mayoría de las aplicaciones requieren


navegaciones complejas sobre las tablas, donde se requiere una mixtura de todos estos casos.

217
Caso 1: Join

• Distintas tablas base pero existe una relación 1-N directa o


indirecta entre ellas
• Ejemplo: Listado de todos los clientes y sus facturas

for each CUSTOMER


print customer {CustomerId,
for each CustomerName} CustomerId
print invoice {InvoiceId, InvoiceDate,
endfor InvoiceTotal} INVOICE
endfor

ext(principal) ∩ base(anidado) = {CustomerId}

En el for each anidado se ordena por atributo relación: CustomerId

Este es el caso en el que GeneXus determina que las tablas base de cada for each son distintas y hay
una especie de relación 1-N (pudiendo ser ésta indirecta, como veremos luego) entre las tablas que
se recorren.

Es decir, por cada registro de la tabla base del for each principal, GeneXus encuentra que hay N
relacionados con él, directa o indirectamente, en la tabla base del for each anidado.

Al encontrar esta relación, aplicará condiciones de filtro automáticas en el for each anidado, de forma
tal de solo quedarse con esos registros relacionados.

El ejemplo del reporte en el que se imprimen todas las facturas del sistema, con sus detalles, cae
dentro de esta categoría. En ese caso hay una relación 1-N directa entre las tablas que se recorren:
para cada cabezal de factura, se lista el mismo, junto con todas sus líneas de factura, es decir, todos
los registros de INVOICELINE que están relacionados con el registro de INVOICE en el que se está
posicionado en cada iteración.

Este es uno de los casos más comunes de for eachs anidados, donde se quiere recorrer una tabla, y
para cada registro de la misma, recorrer otra tabla, relacionada con la primera por una relación N-1.
GeneXus encuentra esta relación, y en la navegación interna solo recupera los registros asociados, y
de ahí el nombre de “join” para este caso.

En el ejemplo presentado arriba ocurre lo mismo. La tabla base del primer for each es CUSTOMER, y
la del segundo, INVOICE. Como encuentra atributo en común entre la tabla extendida del for each
principal y la tabla base del anidado1, CustomerId, determina que ese atributo actuará como
condición de filtro en la recorrida de la tabla del for each anidado.

------------------------------------------------------------------------------------------------------------
1 Esta es una forma de expresar formalmente lo que habíamos dicho en términos informales: relación
directa o indirecta 1-N entre las tablas base. La relación será directa cuando la tabla base del
principal tenga relación 1-N con la tabla base del anidado, es decir, sea superordinada de esta última.
La relación será indirecta cuando esto no exista una relación directa entre las tablas base, pero sí
entre la tabla extendida del primero y la tabla base del segundo. También será indirecta cuando la
tabla extendida del anidado incluya a la tabla base del principal. De esto veremos ejemplos luego.

218
Caso 1: Join

Veamos, por ahora intuitivamente, cómo hace GeneXus para determinar las tablas base. Los
atributos utilizados en el for each externo son CustomerId y CustomerName, por lo que la tabla
base de este for each será claramente CUSTOMER. Observemos que solo participan en la
determinación de esta tabla base los atributos del for each principal, no los del anidado.

Luego GeneXus debe encontrar la tabla base del for each anidado. Los atributos que participan son
InvoiceId, InvoiceDate y InvoiceTotal, es decir, los atributos internos a este for each. Observemos
que estos atributos no pertenecen a la tabla extendida del principal, que era CUSTOMER. Por tanto
se pasa a determinar su tabla base como la de cualquier for each simple: la tabla extendida de
INVOICE contiene a todos los atributos del for each anidado, y es la mínima tabla extendida que los
contiene. Por lo tanto, INVOICE será elegida como tabla base del segundo for each.

Observemos, nuevamente, que no explicitamos cláusula where en el for each anidado para filtrar las
facturas del cliente del for each principal. Justamente, por tratarse de tablas relacionadas por una
relación 1-N directa, esta condición es aplicada implícitamente por GeneXus y puede verse
claramente en el listado de navegación que se muestra arriba.

Hay otro hecho interesante que podemos observar en este listado: si bien en el for each anidado no
especificamos cláusula order, GeneXus no eligió el orden por clave primaria de la tabla base sino
por el atributo relación, CustomerId. De esta manera, está optimizando automáticamente la
consulta.

Acá se presenta, pues, una excepción a la regla que enunciaba que cuando no se especifica cláusula
order en un for each, GeneXus determina como orden el de la clave primaria de la tabla base de
dicho for each.

219
Caso 1: Join

• Ejemplo: Listado de todas las facturas por país

{CountryId, CountryName} CountryId


for each CUSTOMER COUNTRY
print country
for each CustomerId

print invoice
INVOICE
endfor
endfor
{InvoiceId, InvoiceDate, InvoiceTotal}

base(principal) ⊂ ext(anidado)

COUNTRY ⊂ ext(INVOICE) por lo que hay una relación 1-N


indirecta Æ condición implícita: se listan las facturas del país

Si queremos realizar un listado de las facturas emitidas por país, tenemos otro caso de for eachs
anidados con distintas tablas base, donde la información que se quiere listar está relacionada.

Aquí queremos recorrer las tablas COUNTRY e INVOICE.

Observemos que si bien no están relacionadas directamente, sí lo están en forma indirecta. De


hecho, COUNTRY pertenece a la tabla extendida de INVOICE. Por lo tanto, por cada factura se
puede encontrar un solo país relacionado con la misma.

Hilando fino, este es un caso un poco más complejo que el anterior, porque si bien la tabla
extendida del for each principal no tiene intersección con la tabla base del anidado (ext(COUNTRY)
∩ INVOICE = φ), sin embargo sí existe una relación 1-N indirecta, y GeneXus la encuentra.

En este caso, la tabla base del for each principal está incluida en la extendida del anidado
(COUNTRY ⊂ ext(INVOICE)), por lo que hay una relación 1-N indirecta.

Por este motivo, no necesitamos especificar cláusula where en el for each interno para filtrar las
facturas del país del for each principal.

Esto puede verse claramente en el listado de navegación, que mostrará el filtro:


“CountryId = CountryId” para el segundo for each, pero esta vez como ‘Constraint’ dado que no
puede optimizar la recorrida.

Puede probar el lector este caso en GeneXus y estudiar detenidamente el listado de navegación
resultante.

220
Caso 2: Producto Cartesiano

• Distintas tablas base, pero no existe relación 1-


N directa ni indirecta entre las mismas.

• El resultado que obtenemos es el producto


cartesiano de dichas tablas: para cada registro de
la tabla base del for each principal, se recuperan
todos los registros de la tabla base del anidado.

ext(principal) ∩ base(anidado) = φ
y
base(principal) ⊄ ext(anidado)

En este caso GeneXus no logra encontrar una relación 1-N directa o indirecta entre las tablas y por
lo tanto no aplica filtros implícitos a los registros del for each anidado, vale decir, realiza un
producto cartesiano entre las tablas.

El caso se da cuando:
• ext(for each principal) ∩ base(for each anidado) = φ y
• base(for each principal) ⊄ ext(for each anidado)

Para cada registro de la tabla base del for each principal se recorre toda la tabla base del for each
anidado.

Por ejemplo, si la tabla base de un for each fuera COUNTRY y la del anidado PRODUCT,
evidentemente no existirá relación y se hará un producto cartesiano y se recorrerá para cada país,
todos los productos.

Por supuesto que el programador podrá establecer filtros sobre los datos a recuperar, pero éstos ya
no serán condiciones implícitas inferidas por GeneXus, sino especificadas explícitamente por el
programador.

221
Caso 3: Corte de Control

• Ejemplo: Para cada cliente que tiene facturas, listar


sus facturas.

En el ejemplo que habíamos visto antes, del listado de clientes y sus facturas, ¿qué ocurre si un
cliente no tiene facturas?

Como la tabla base del for each principal es CUSTOMER, el cliente sale impreso antes de saberse si
tiene o no facturas.

Si no deseamos que esto ocurra, es decir, que salgan listados clientes que no tengan facturas,
entonces la solución es acceder únicamente a las facturas, pues si un cliente está en esta tabla,
¡es porque está en una factura!.

Pero para poder agrupar las facturas por cliente, de forma tal de poder desplegarlas de ese modo,
debemos recorrer la tabla INVOICE ordenada por CustomerId. De esta forma procesaremos la
información de un cliente, y luego pasaremos al siguiente, para procesar su información, y así
sucesivamente.

Si imaginamos un puntero que se va desplazando secuencialmente por la tabla INVOICE, podemos


escribir el pseudocódigo de nuestro reporte como sigue:

1. Para el registro apuntado, retener el valor del atributo de corte o agrupamiento, CustomerId.

2. Acceder a la tabla CUSTOMER (que está en la extendida de INVOICE) para recuperar el


CustomerName e imprimirlo junto con el CustomerId (“print customer”)

3. Mientras el valor de CustomerId del registro apuntado coincida con el valor retenido en el paso
1 (aquí se procesan todas las facturas del cliente)
a. Imprimir InvoiceId, InvoiceDate e InvoiceTotal del registro apuntado.
(“print invoice”)
b. Avanzar el puntero al siguiente registro y volver al paso 3.

4. Volver al paso 1. (cuando se llegó a este punto, es o bien porque se llegó al fin de tabla o bien se
cambió de cliente).

222
Caso 3: Corte de Control
Ejemplo: Para cada cliente que tiene facturas, listar sus facturas.
• Source
for each order CustomerId orden determina el
defined by InvoiceDate criterio de corte
print customer
for each
print invoices Cada vez que cambia el cliente se
endfor define un nuevo grupo
endfor
Se utilizó “defined by” para que la tabla base fuera INVOICE y no
CUSTOMER y así implementar corte de control y no join.

• Layout

GeneXus brinda una forma de implementar lo anterior de una forma absolutamente sencilla.

El pseudocódigo visto en la página anterior se implementa en GeneXus con el par de for eachs
anidados que se muestran arriba.

Si se compara este código con el que vimos unas páginas atrás para el caso de join, vemos que
existen solamente dos diferencias: la cláusula order que aparece en este código, en conjunción con el
defined by. Con solo esos dos cambios al listado original, cambiamos radicalmente el
comportamiento, puesto que en este caso solamente se listarán los clientes si tienen facturas.

En este caso, ambas cláusulas (order y defined by) son indispensables para que este reporte funcione
del modo que queremos. Si agregamos solo una de ellas, pero no la otra, el resultado será otro.

No en toda implementación de un corte de control deberá especificarse una cláusula defined by en el


for each principal, mas sí una cláusula order.

La cláusula order es indispensable, porque es la que especifica por qué atributo o conjunto de
atributos se realizará el corte (o agrupamiento). Es decir, especifica esa información común al grupo,
que se procesará una sola vez (dentro el código del for each externo).

La cláusula defined by no es indispensable en todos lo casos. En este sí lo fue, porque de no


especificarla, GeneXus determinaría como tabla base del for each principal CUSTOMER, que no es lo
que queremos (pues no queremos implementar un join, cosa que ya hicimos antes, sino un corte de
control, para solo recorrer la tabla INVOICE).

Pero también podríamos haber utilizado otra solución para modificar la tabla base del for each
principal: utilizar en vez del defined by el comando print if detail dentro del cuerpo del primer for
each (este comando le dice a GeneXus que tome como tabla base del for each, la que determine para
el anidado).

223
Caso 3: Corte de Control

• Condiciones que deben cumplirse para implementar Cortes


de Control:

1. for each anidados

2. Tienen la misma tabla base

3. ¿Cuántos for each? Uno más que la cantidad de cortes

4. Debemos establecer en la cláusula order de cada for


each externo, el atributo o conjunto de atributos por los que
queremos “cortar”.

Un corte de control es bien simple de implementar y puede hacerse siguiendo las consideraciones
anteriores.
En el order del for each más externo, debemos mencionar el “primer” atributo de corte, en el order
del segundo for each debemos mencionar el “segundo” atributo de corte, y así sucesivamente. No
es obligación mencionar atributo/s en el order del for each más interno (en todos los demás for
each sí lo es).

Corresponde al caso en el que nos interesa trabajar con la información de una tabla, pero agrupada
por algún atributo o conjunto de atributos.

Los cortes de control pueden ser simples, dobles, triples, etc.

A continuación veremos un ejemplo de un corte de control doble.

224
Ejemplo: Corte de control doble

Supongamos que queremos como antes listar los clientes y sus facturas, pero queremos agrupar las
facturas de cada cliente por fecha. Es decir, queremos mostrar, para cada cliente, para cada fecha,
las facturas existentes.

Ejemplo:

Customer: 1 Juan Pérez


Date: 12/05/05
Invoice Total
1 15

Date: 01/01/06
Invoice Total
9 35
3 30

Customer: 3 María Donoso


Date: 06/06/05
Invoice Total
2 20
Date: 12/08/05
Invoice Total
4 40
8 15
Date: 02/02/06
Invoice Total
7 20

Como ahora queremos agrupar por cliente, y dentro de ese grupo por fecha de factura, necesitamos
tres for eachs anidados:

For each order CustomerId


defined by InvoiceDate
print customer
for each order InvoiceDate
print date
for each
print invoice
endfor
endfor
endfor

Como ejercicio, sigamos todos los pasos que realiza GeneXus para inferir el comportamiento del
reporte.

1. Determinación de la tabla base de cada for each

Como siempre, para determinar las tablas base de for eachs anidados, se empieza de afuera hacia
adentro, determinando la de cada for each, sin tomar en cuenta los atributos de los for eachs
internos al que se está considerando.

for each order CustomerId


defined by InvoiceDate Tabla base 1er. for each
print customer Solo intervienen los atributos de los
for each order InvoiceDate lugares señalados en negrita.
print date
for each Mínima tabla extendida que los
print invoice contiene: ext(INVOICE)
endfor
endfor
endfor

225
for each order CustomerId
defined by InvoiceDate
print customer
for each order InvoiceDate Tabla base 2do. for each
print date Intervienen los atributos de los lugares señalados en
for each negrita y como todos ellos están incluidos en la
print invoice extendida del 1er. for each, entonces se determina la
endfor misma tabla base: INVOICE
endfor
endfor

for each order CustomerId


defined by InvoiceDate
print customer
for each order InvoiceDate Tabla base 3er. for each
print date Intervienen los atributos de los lugares señalados en
for each negrita y como todos ellos están incluidos en la
print invoice extendida del 2do. for each, entonces se determina la
endfor misma tabla base: INVOICE
endfor
endfor

2. Determinación de la navegación

Luego de determinadas las tablas base, GeneXus determina la navegación. Como en este caso son tres for eachs
sobre la misma tabla base, se trata de un doble corte de control.

Podemos pensar que cuando hablamos de corte de control, ya sea simple, doble, triple, cuádruple, etc., tenemos un
solo puntero, que se utiliza para avanzar en los registros de la tabla base.

Recordemos que la cláusula order es fundamental para establecer el criterio de corte en cada par de for eachs.

Como en nuestro caso queremos agrupar por CustomerId y luego, para todas las facturas con ese cliente, agrupar
por InvoiceDate, entonces tendremos que ordenar el primer for each por CustomerId y el inmediatamente anidado
por InvoiceDate.

En el ejemplo estamos diciendo que:

Mientras no se alcance el fin de tabla


Imprimir los datos del cliente de la factura actual
Mientras no cambie el cliente
Imprimir la fecha de la factura actual
Mientras no cambie la fecha
Imprimir los datos de la factura actual (nro y total)
Avanzar el puntero al siguiente registro

Recomendamos al lector implementar en GeneXus este reporte y observar detenidamente el listado de navegación.

Verá que GeneXus elige un único orden, para el que no tiene un índice creado: el compuesto por la concatenación
de los órdenes de cada for each con cláusula order. Esto resulta evidente si pensamos en términos de un único
puntero que se va desplazando por la tabla base.

226
Resumen: Determinación general de las tablas base

Se procede en forma ordenada, determinando cada vez la tabla base de un nivel de anidación,
yendo de afuera hacia adentro: primero se determina la tabla base del for each más externo, luego
del que está anidado a éste y así sucesivamente.

Determinación tabla base del for each externo


Se determina a partir de los atributos que aparecen dentro de ese for each: cláusulas order,
where, defined by y cuerpo del for each, exceptuando los atributos que estén dentro del for
each anidado. No participan los atributos que estén dentro del when none, en caso de que el for
each principal tenga esta cláusula. Al igual que en el caso de un for each simple, se encuentra la
mínima tabla extendida que contenga los atributos mencionados.

Determinación tabla base del for each anidado


Podemos vernos tentados a pensar que por analogía lo que se debería hacer es extraer los
atributos del for each anidado, y hacer lo mismo que antes, es decir, encontrar la mínima tabla
extendida que contenga a esos atributos, como si se tratase de un for each independiente. ¡Pero no
son for eachs independientes!

Para el for each anidado, GeneXus se fija primeramente si los atributos utilizados dentro del cuerpo
del mismo están incluidos o no dentro de la tabla extendida previamente determinada. En caso
afirmativo, GeneXus determina que la tabla base del for each anidado será la misma que la del for
each principal (y será un caso de corte de control).

En caso contrario, sí se determina como si fueran independientes: busca la mínima tabla extendida
que contenga a todos los atributos del for each anidado1.

Esquema general de determinación de la tabla base de un for each anidado.

Presentamos a continuación un esquema con notación matemática, que pretende mostrar


gráficamente lo que hemos dicho por escrito en los párrafos anteriores acerca de cómo se
determina la tabla base del for each anidado.

En la siguiente página mostramos otro esquema, pero esta vez con los casos de for eachs anidados
de acuerdo a las tablas base encontradas.

----------------------------------------------------------------------------------------------------------
1 Existe alguna excepción, pero no entraremos aquí en este tema.

227
Esquema de casos

Luego de determinadas las tablas base de los for eachs anidados, el siguiente paso que realiza
GeneXus es determinar la lógica que se asociará a los mismos.

Esa lógica viene determinada de acuerdo al caso (join, producto cartesiano o corte de control) en el
que caigan los for eachs anidados bajo análisis.

Un esquema de los casos puede verse más claramente con el diagrama de flujo que se muestra a
continuación:

228
Comandos de control

if cond
do while cond do case
bloque1
bloque case cond1
[else
enddo bloque1
bloque2]
[case cond2
endif
bloque2]
......
for &var=inicio to fin [step salto] [case condn
bloque bloquen]
endfor otherwise
bloquen+1
for &var in &array endcase
bloque
endfor

Los comandos introducidos son similares a los existentes en los lenguajes de programación
imperativa conocidos, por lo que no incluimos documentación de este tema. Puede encontrarla en el
curso no presencial o en el Help de GeneXus. Los dos últimos, no obstante, incorporan algunos
elementos interesantes sobre el manejo de arrays y de colecciones1 en GeneXus, por lo que
mostraremos algunos ejemplos.

For to step:
• inicio, fin son expresiones numéricas
• salto es una constante numérica
• var es alguna variable numérica
• bloque es una sucesión de comandos válidos del lenguaje

Permite iterar una cierta cantidad de veces: desde el valor inicio que toma la variable &var cuando
se ingresa al bucle, hasta el valor fin que tomará la misma luego de cierta cantidad de iteraciones.
De iteración en iteración la variable &var se va incrementando automáticamente en una cantidad
igual a salto. El valor por defecto de salto es 1, por lo que si no se especifica la cláusula step el
incremento de la variable será de uno en uno. El valor de salto puede ser negativo y en ese caso se
irá decrementando la variable de iteración en iteración.

Ejemplo
For &i = 1 to 5
&ok = PInvoicing.udp( &month )
endfor

----------------------------------------------------------------------------------------------------------
1 Las colecciones representan listas de largo variable. Se verán cuando estudiemos el tipo de datos

estructurado (SDT).

229
For in array:
• array es un vector o matriz (variable de una o más dimensiones). También puede ser una variable SDT
collection (este tema se estudiará más adelante)
• var es una variable que debe tener el mismo tipo de datos que array o compatible (en caso de tratarse
de un SDT, deberá ser un SDT correspondiente a los ítems)

Esta estructura de programación permite recorrer con menos código una variable array de una o más
dimensiones. Se almacena en la variable &var los valores de cada posición del array.

Para el caso de arrays de una dimensión, es equivalente a:


&x = 1
do while &x <= rows(&array())
&var = &Array(&x)
bloque
&x += 1
enddo

En el caso de dos dimensiones el comando es equivalente a:


&x = 1
do while &x <= rows( &array() )
&y = 1
do while &y <= cols( &array() )
&var = &array( &x, &y )
bloque
&y += 1
enddo
&x += 1
enddo

Consideraciones:

• No es posible modificar los valores del array en la recorrida. Esto significa que cambios en el valor de
&var en el alcance de la estructura, no afectan al correspondiente valor del &array(&x) (o de &array(&x,
&y))

• No es posible obtener la posición del array durante la recorrida, para esto es necesario definir una
variable que actúe como contador

Ejemplo

For &i= 1 to 4
For &j=1 to 3
&array(&i,&j) = &i + &j
endfor
endfor

230
Comandos de impresión

• Print
• Se utiliza para imprimir en la salida un print block definido
en el Layout
• Sintaxis: Print nombrePrintBlock

• Header
• Se utiliza para definir lo que se quiere imprimir como encabezado
de cada página del listado
• Sintaxis: Header
bloque
end
• Footer
• Define las líneas de pie de página a ser impresas al final de cada
página del reporte.
• Sintaxis: Footer
bloque
end

Aquí veremos algunos comandos de impresión, que permiten diseñar la salida del reporte.

Print
donde nombrePrintBlock es el identificador de un print block del Layout. Si no existe en el Layout
un print block con ese nombre, al intentar salvar el objeto se desplegará un mensaje de error
informando sobre esta situación.

De esta forma se implementa la impresión en la salida de los print blocks del Layout.

Cuando el print block que se quiere imprimir no contiene atributos, entonces este comando se
puede utilizar en cualquier lugar del Source.

Los atributos indican acceso a la base de datos y este acceso no puede realizarse en cualquier lado,
sino únicamente dentro del comando específico para ello, esto es, el comando for each.

Por tanto no es correcto escribir el comando print fuera de un “for each” si el print block que se está
queriendo imprimir contiene atributos.

La única excepción a esta regla se produce cuando los atributos del print block están incluidos entre
los parámetros recibidos por el reporte, pues en este caso no es necesario acceder a la base de
datos para recuperar sus valores, dado que ya vienen instanciados.

Header
Aquí se define lo que se quiere imprimir como encabezado de cada página del listado.
Este encabezado es opcional. Si no se especifica, entonces las páginas del listado no tendrán
encabezado.

Ejemplo: En el reporte en el que queríamos imprimir un listado con el código, nombre y país de
cada uno de los clientes de nuestro sistema, si queremos que en cada página del listado aparezca el
encabezado: “CUSTOMERS REPORT”, entonces alcanza con escribir en el Source:

Header
Print title
End

Donde “title” es el nombre de un print block del Layout que contiene este texto.

231
También podríamos haber escrito directamente: Print title al comienzo del Source, pero en este
caso, si el listado tiene varias páginas, solo saldrá impreso este texto en la primera. En el otro caso,
saldrá en cada una de las páginas, como encabezado.

Footer
Define las líneas de pie de página a ser impresas al final de cada página del reporte.

Los comandos del bloque de código son ejecutados cuando se llega al final de una página.

Ejemplo:
Footer
print endOfPageText
end

donde endOfPageText es el nombre de un print block del Layout

232
Diseño de la salida

Existen algunos comandos para diseñar la salida del reporte. Presentamos aquí algunos a los
efectos de la documentación.

MT nline: nline es el número de línea en el que se quiere empezar a imprimir el listado. En caso de
no especificarse un valor se asume el valor por defecto que es 0.
MB nline: nlíneas es el número de líneas que se desea dejar como margen inferior.
En caso de no especificarse un valor se asume el valor por defecto que es 6.
PL nline: Setea el largo de página. El número de líneas que será impreso es el número especificado
menos el margen de abajo (valor por defecto es 6). Ej: PL 66
Setea el largo de página a 66 líneas, aunque sólo 60 líneas serán impresas en el form, con un
margen inferior de 6 líneas.
CP nlines: Si queda en la página actual un número de líneas mayor o igual al número especificado,
continúa imprimiendo en la misma página. De lo contrario, pasa a imprimir en la próxima página
(fuerza a un salto de página).
Lineno nline: Define el número de línea donde va a ser impresa la siguiente línea. Si el número de
línea actual es mayor al número especificado, entonces, la línea será impresa en la próxima página.
El conteo de líneas comienza en la línea 0.
Eject: Fuerza a un salto de página.
Noskip: Tiene que estar inmediatamente después de un print block. Si el comando se encuentra
entre dos líneas, este comando las imprimirá en la misma línea.

233
Reportes PDF

• Configurar las propiedades:


• Main Program = True
• Call Protocol = HTTP
• Report Output = ‘Only to file’

• Regla
• output_file( ‘xx.pdf’, ‘PDF’)

En Web los reportes solamente pueden ser PDF y se deben configurar las propiedades y regla
anteriores para que funcionen.

Definición de un objeto como main

Al definir que un objeto es main (en este caso un reporte, pero podría ser una transacción, web
panel, etc.), GeneXus genera un programa ejecutable con la lógica del objeto mismo y la de
todos los objetos invocados directa o indirectamente por él.

El programa ejecutable generado se podrá compilar y ejecutar de forma independiente, es decir,


al seleccionar Build / Run se verá como programa independiente del Developer Menu y podrá
compilarse y ejecutarse.

La definición de un objeto como main se realiza editando las propiedades del objeto, y
configurando la propiedad Main program del mismo con valor True.

234
Condiciones

• Permiten especificar condiciones globales que


deberán cumplir los datos a ser recuperados.
Ejemplo:

En esta sección se permiten establecer condiciones que deben cumplir los datos para ser
recuperados.

Una “condición” es equivalente a la cláusula “where” del comando for each (incluso tiene la misma
sintaxis) con una salvedad: mientras que la cláusula “where” está ligada a un for each específico:
aquel al que pertenece, las “condiciones” están ligadas a todos los for eachs del Source en los que
tenga sentido aplicarlas.

¿Y para qué for eachs tiene sentido aplicarlas?


Las condiciones generalmente involucran atributos. Si en la tabla extendida de un for each se
encuentran los atributos que intervienen en una “condición”, entonces la misma se aplicará a este
for each para filtrar los datos quedándose únicamente con aquellos que satisfagan tal condición.

En el listado de navegación del reporte se indican los for eachs a los que se aplica cada condición de
las especificadas en la sección “Conditions”.

Si en el Source del reporte “PrintCustomers” tenemos:

For each
Print customer
endfor

Entonces al especificar las condiciones que se muestran en la transparencia, estaremos filtrando los
clientes, de acuerdo a las condiciones (es equivalente a tener las condiciones como “where”).

235
Condiciones

• Si el Source del reporte es:


For each
For each where CustomerName>=&start
Print customer Print customer
Endfor Endfor
For each
For each where CustomerName>=&start
Print invoice where InvoiceId < 100
Endfor Print invoice
Endfor
For each For each
Print product Print product
Endfor Endfor
Conditions: CustomerName >= &Start;
InvoiceId < 100;

donde:
• “customer” es un print block que contiene los atributos CustomerId, CustomerName,
CountryName
• “invoice” es un print block que contiene los atributos InvoiceId, CustomerId, CustomerName,
InvoiceDate, InvoiceTotal
• “product” es un print block que contiene los atributos ProductId, ProductDescription, ProductStock

Si el reporte anterior tiene definidas las condiciones mostradas arriba, el reporte será equivalente a
uno sin “condiciones” y con el Source que se muestra a la derecha.

Observemos que en este caso las condiciones se traducen en cláusulas where pero que se aplican
solo a los for eachs para los que tiene sentido aplicarlas. En la tabla extendida del último for each
no se trabaja con nombre de cliente, ni con identificador de factura. No tiene sentido aplicar las
condiciones en este for each ya que no existe ninguna relación.
En el primero, solo tiene sentido aplicar la que involucra a CustomerName y no la otra.

Observación
Los atributos involucrados en las “condiciones” no participarán en la determinación de las tablas
base de los for eachs del Source (a diferencia de los filtros que se especifican mediante cláusulas
where).

Es decir, las tablas base de los for eachs que aparezcan en el Source se determinan sin mirar las
“condiciones”. Una vez determinadas, recién en ese momento las condiciones son examinadas para
determinar a cuáles for eachs se aplicarán y a cuáles no.

Lo mismo ocurre con los atributos que se reciban como parámetro. Aplicarán como filtro global por
igualdad para los for eachs en los que tenga sentido, pero no participarán en la determinación de
las tablas base.

236
Filtros en la navegación

Formas de filtrar los datos:

• Cláusulas Where Participan en determinación de tabla base

• Condiciones NO participan en determinación de tabla base

• Regla Parm (Atributos recibidos como


parámetros)
Parm(Att1 ... Attn)

Resumimos aquí las distintas formas de filtrar en un reporte o procedimiento la información a


recuperar de la base de datos:

a. cláusulas where
b. Condiciones
c. parm( att, ..., att )

Estudiemos las diferencias y similitudes entre ellas.

1. Las cláusulas where aplican exclusivamente al for each en el que se encuentran, mientras que los
filtros especificados como condiciones o los que quedan determinados por los atributos en la regla
parm son globales, es decir, aplicarán a todos los for eachs del Source en los que tenga sentido
aplicarlos.

2. Los filtros que quedan determinados al recibir en atributos en la regla parm son filtros por
igualdad, es decir, para los for eachs en los que tenga sentido aplicarlos, se instanciarán únicamente
los registros que tengan el mismo valor que el recibido por parámetro. En cambio, los filtros
especificados en las cláusulas where de un for each o en las condiciones pueden ser expresiones
booleanas cualesquiera, incluso compuestas.

3. Mientras que los atributos que aparecen en las cláusulas where participan en la determinación de la
tabla base del for each donde se encuentran, los que aparecen en las condiciones o en la regla parm no
lo hacen. Recién entran en juego LUEGO de determinadas las tablas base de los for eachs del Source.

237
PROCEDIMIENTOS

Actualización de la base de datos

Los procedimientos definen procesos no interactivos (batch) de consulta y/o actualización de la


información de la base de datos.

En lo anterior estudiamos los reportes y mencionamos que la mayoría de las características y


conceptos introducidos eran comunes a los procedimientos.

Aquí nos abocaremos a profundizar en los comandos que son específicos para este tipo de objetos,
es decir, aquellos que tienen que ver con la actualización de la base de datos.

Dijimos que los procedimientos eran un “superset” de los reportes, en el entendido de que todo lo
que se realiza con un reporte puede realizarse con un procedimiento, pero en cambio no se cumple
el recíproco, es decir, no todo lo que se realiza con un procedimiento puede realizarse con un
reporte1.

En este capítulo estudiaremos ese “plus” que es lo que diferencia a ambos tipos de objetos y que
viene dado por la posibilidad que tienen los procedimientos de actualizar la base de datos.

Estudiaremos entonces cómo se dan las altas, bajas y modificaciones en la base de datos utilizando
procedimientos.

La pantalla de edición de procedimientos es idéntica a la de reportes.

----------------------------------------------------------------------------------------------------------
1 A menos que se utilicen business components para hacer actualizaciones a la base de datos,

como veremos cuando estudiemos este tema.

238
Actualización

• No hay un comando específico de actualización: se realiza en


forma implícita dentro del comando for each.
• Ejemplo:

Transacción “Product” Procedimiento “PriceIncrease”

ProductId* Reglas:
ProductDescription Parm( &Inf );
ProductStock
(ProductDate* Source:
ProductPrice) for each
where ProductDate = &Today
ProductPrice = ProductPrice*
(1+&inf/100)
endfor

La modificación de datos de la base de datos se realiza en forma implícita, ya que no hay un


comando específico de actualización.

Para actualizar uno o varios atributos de una tabla se utiliza el comando for each, y dentro del
mismo el comando de asignación.

Supongamos que queremos tener un proceso batch que actualice para todos los productos
almacenados que tengan el precio fijado para el día de hoy, el atributo ProductPrice para adecuarlo
según el porcentaje de inflación.

La tabla base del for each será PRODUCTLINE y estamos asignando valor a un atributo de la misma,
para todos los registros.

Se pueden actualizar varios atributos dentro del mismo for each, pudiendo éstos pertenecer tanto a
la propia tabla base como a la tabla extendida.

239
Actualización

• Atributos actualizables: los pertenecientes a la


tabla extendida del for each.

• Salvo:
• los que forman parte de la clave primaria de la tabla
base del for each.
• los que forman parte del índice por el que se está
accediendo a dicha tabla.

• La actualización se realiza en el endfor.

Supongamos que tenemos el siguiente diagrama de Bachman genérico:

A* B*
B E
C F
D
Y en el Source de un procedimiento hacemos:

for each
C = &C
E = &E
D = &D
endfor

Aquí la tabla base del for each será claramente la de clave primaria A y dentro del for each estamos
actualizando tanto atributos de la propia tabla base como de la extendida.

¿En qué momento ocurre efectivamente la actualización de los registros involucrados?

Al final de cada iteración del for each se actualiza el registro de la tabla base y el/los registro/s de
la extendida que deban ser actualizados.

Es decir, la actualización no ocurre ni bien se encuentra un comando de asignación dentro del for
each, sino luego de que se encuentran todos, para cada instancia de la tabla base, es decir, cuando
se llega al endfor para cada iteración.

240
Actualización

• Claves candidatas: índices unique


• Se utiliza cláusula when duplicate en el for each

for each ...


bloque1
[when duplicate
...]
endfor

Como vimos antes, no podemos actualizar dentro del comando for each atributos de la clave
primaria. Sin embargo podríamos querer actualizar un atributo que sin ser clave primaria, está
definido como clave candidata (mediante un índice unique).

Si el atributo es clave candidata, debe controlarse que no se dupliquen sus valores, por lo que de
encontrarse duplicado el registro en este sentido, no se permitirá hacer la actualización.

Si se desea tomar una acción en caso de que esto ocurra, el comando for each agrega la cláusula
when duplicate. Solo tiene sentido si existe alguna clave candidata para ese for each.

241
Eliminación

• Comando Delete
• Debe ir dentro de un for each
• Elimina el registro de la tabla base en el que se
esté posicionado
• Se ejecuta ni bien se encuentra el comando (y no
en el endfor)
• Ejemplo:
for each Borra todos los
defined by InvoiceDate registros de la
tabla base:
Delete
INVOICE
endfor

Para eliminar datos se utiliza el comando Delete dentro del comando for each.

El comando Delete elimina el registro en el que se está posicionado en un momento dado. Es por
ello que no puede aparecer “suelto” dentro del Source. Debe colocarse dentro de un comando for
each, cuya tabla base sea la tabla de la que se quieren eliminar registros. Solo se eliminan los
registros de la tabla base, no de la extendida.

Si deseamos eliminar todas las facturas anteriores a una fecha dada, podemos programar un
procedimiento:

for each
where InvoiceDate <=&date
for each
defined by InvoiceLineQuantity
DELETE //se eliminan las líneas
endfor
DELETE //luego de eliminar las líneas se elimina el cabezal
endfor

242
Inserción de registros

• Comando New: permite insertar un registro en


una tabla
• Ejemplo:
Procedimiento “NewPrice”
Transacción “Product”
Rules:
ProductId* Parm( &ProductId, &price );
ProductDescription
ProductStock Source:
(ProductDate* New
ProductPrice) ProductId = &ProductId
ProductDate = &Today
ProductPrice = &price
endnew

Supongamos que queremos implementar un procedimiento que haga lo siguiente: para el producto
cuyo código es recibido por parámetro, dé de alta un nuevo precio (también recibido por parámetro)
en su lista de precios, para la fecha correspondiente al día en que se ejecuta el procedimiento.

El procedimiento debe crear un nuevo registro en la tabla PRODUCTLINE, que está compuesta por
los atributos ProductId, ProductDate y ProductPrice, siendo su clave primaria una compuesta,
conformada por ProductId y ProductDate.

Para ello se utiliza el comando new que escribimos arriba. Observemos que dentro del mismo
aparecen comandos de asignación, donde se le da valor a los atributos de la tabla en la que se
quiere insertar el registro.

En este caso queremos insertar un registro en la tabla PRODUCTLINE y le especificamos mediante


asignaciones el valor que queremos que tomen los atributos de dicha tabla para ese registro.

¿Cómo entiende GeneXus que la tabla en la que queremos insertar el registro es PRODUCTLINE, si
no la mencionamos?

Cada vez que GeneXus encuentra un new, debe determinar la tabla en la que se realizará la
inserción (tabla base del new).

Esta tabla es determinada a partir de los atributos que aparecen dentro del comando new, del
lado izquierdo en una asignación.

En el ejemplo, son tres los atributos que aparecen dentro del new del lado izquierdo de un comando
de asignación.

243
Inserción de registros

• El comando new realiza el control de duplicados


(clave primaria y claves candidatas)

Ejemplo (cont.) New


ProductId = &ProductId
En caso de que exista un ProductDate = &Today
precio para ese producto ProductPrice = &price
y esa fecha, queremos When duplicate
cambiar ese precio for each
ProductPrice = &price
endfor
endnew

Solo se insertará uno, pues el comando new realiza el control de duplicados. Es decir, al
intentar insertar un nuevo registro, se controla previamente que ya no exista uno en la tabla con el
mismo valor en la clave primaria que el que se está intentando insertar. De existir claves candidatas
(definidas mediante índices unique) también se controlarán.

El comando new cuenta con una cláusula opcional: la cláusula when duplicate. Con ella se
programa la acción a realizar en caso de encontrarse duplicado el registro. Por ejemplo,
supongamos que en caso de que el producto ya tenga en su lista de precios una entrada
correspondiente a la fecha de hoy, entonces en tal caso queremos cambiar ese precio.

Para ello agregamos al comando new la cláusula when duplicate.

Aquí estamos actualizando el valor del atributo ProductPrice en caso que el registro se encuentre
duplicado. Es decir, si ya existe un registro en la tabla con los valores de &ProductId y &Today en
su clave primaria, entonces para ese registro se actualiza el precio.

Observemos que para realizar la actualización del atributo, la asignación debe estar dentro de un
comando for each. Si no colocamos el for each no se realizará la actualización del precio para ese
registro, es decir, es como si no se hubiera incluido cláusula when duplicate.

Como hemos visto, para actualizar la base de datos se emplea el comando for each, y por tanto
aquí simplemente se uniformiza el comportamiento, de manera tal que siempre que se pretenda
actualizar la base de datos vía comando, se hará con un for each (a excepción del uso de business
components, que veremos más adelante en el curso).

244
Inserción de registros

• Sintaxis del new:

new
[Defined by att1,…, attN]
bloque_asignaciones1
[when duplicate
for each
bloque_asignaciones2
endfor]
endnew

La inserción de datos en la base de datos utilizando procedimientos se realiza exclusivamente con el


comando new, que permite dar de alta un registro en una tabla de la base de datos.

En la sintaxis presentada, bloque_asignaciones1 es un bloque de código compuesto mayormente por


sucesivos comandos de asignación (aquí se asigna valor a los atributos de la tabla en la que se
insertará el registro, aunque también pueden asignarse valores a variables).

La cláusula Defined By opcional, se incorpora a los mismos efectos que lo hacía para el comando
for each: ayudar a determinar la tabla base.

El comando new realiza un control de duplicados, de manera tal que no se permitirá insertar un
registro que ya exista en la tabla.

La cláusula when duplicate del comando permite programar la acción en caso de que el registro
ya exista en la tabla base (tanto por clave primaria como por clave candidata).

Normalmente, de ocurrir lo anterior, se quiere actualizar algunos de los atributos de dicho registro.
Para ello, en bloque_asignaciones2 se realizan tales asignaciones, pero como lo que se hace es
actualizar un registro (y no insertar uno nuevo), estas asignaciones aparecen rodeadas de “for each
– endfor”, pues como hemos visto, las actualizaciones solo pueden realizarse dentro de un for each.

De no especificarse cláusula when duplicate para un new, si el registro que quiere insertarse se
encuentra duplicado no se realizará acción alguna y la ejecución continuará en el comando
siguiente. Es decir, como no puede insertar el registro porque ya existe uno, no hace nada y sigue
adelante, con el próximo comando.

245
Inserción de registros

Determinación tabla base del new:


• De existir, se consideran los atributos que se
especifiquen en la cláusula Defined by.

• Se consideran todos los atributos que aparezcan


en bloque_asingaciones1 a la izquierda en
comando de asignación.

• Estos atributos deben pertenecer a una misma


tabla física Æ tabla base

La tabla física en la que se insertará el registro del new (tabla base del new) se determina a partir
de los atributos que aparecen en bloque_asignaciones1 del lado izquierdo del comando de
asignación. Al igual que en el comando for each, se incluye la cláusula Defined by para agregar
más elementos que permitan determinar la tabla base. Es decir, la tabla base del new será aquella
en la que se encuentren físicamente almacenados los atributos att1,…, attN que figuran en la
cláusula Defined by, junto con los que aparezcan en el bloque de asignaciones, del lado izquierdo.

Observar que marcamos en negrita “físicamente almacenados” y esto introduce una diferencia
importante con respecto al comando for each: aquí, tanto los atributos del Defined by como los que
se encuentren en asignaciones del lado izquierdo, deberán pertenecer a la tabla base (no a la
extendida). Es más restrictivo, puesto que con el comando new estamos insertando un único
registro en una sola tabla de la base de datos.

GeneXus buscará una tabla física que contenga a todos estos atributos. De no existir tal tabla, al
especificar el procedimiento se desplegará un error en el listado de navegación informando de esta
situación y el objeto no será generado.

246
Inserción de registros

Determinación tabla base del new:

¿Es correcto? New


A* B* A = &A
B = &B
B C ¿Cuál será el C = &C
F comportamiento? endnew

¡No!: no existe una tabla física que incluya los atributos A, B y C.

El New solo permite insertar UN REGISTRO en UNA TABLA.

El ejemplo pretende dejar claro a qué nos referimos con tabla física, en contraposición a tabla
extendida.

Mientras que en el caso de la determinación de la tabla base del for each los atributos debían
pertenecer a una misma tabla extendida, en el New es más restrictivo: deben pertenecer a una
misma tabla física.

247
Inserción de registros

• Atributos asignados en el new


• No es necesario asignar valor a todos y cada uno de los
atributos de dicha tabla.
• Algunos atributos vienen instanciados por el contexto del
new y no es necesario asignarles valor para el registro a
insertar.
• Contexto:
• parámetros atributo
• new anidado a un for each
• new anidado a un new

• New siempre se ejecuta por la clave primaria.

Una vez que GeneXus determinó la tabla del new, los atributos pertenecientes a la misma y no
asignados explícitamente dentro de este comando tomarán como valor:
• Para aquellos que estén instanciados en el contexto donde se encuentra el new, tomarán los
valores correspondientes al contexto.
• Para los que no estén instanciados en el contexto, tomarán el valor nulo (o empty, dependiendo de
la propiedad “Empty As Null” en conjunción con la Nulls).

Atributos instanciados en el contexto:

Atributos recibidos por parámetro


Si los atributos no asignados explícitamente en el new son recibidos en la regla parm, entonces para
el registro a ser insertado toman el valor que viene en el parámetro.

New anidado a un for each


El new es un comando como otros, por lo que puede aparecer anidado a un for each (es el caso en
el que se quiere navegar determinada tabla, y para cada registro que cumpla determinadas
condiciones dar de alta un registro en otra tabla que puede también ser la misma. Veremos un
ejemplo a continuación).
En este caso, todos los atributos de la tabla del new que no estén asignados explícitamente y que
estén instanciados en el for each que contiene al new, tomarán el valor que tienen en la instancia del
for each para el registro a ser insertado.

New anidado a otro new


Dentro del comando new puede aparecer otro new sobre la misma o distinta tabla. Los atributos del
new anidado que no estén asignados explícitamente y que estén en el principal tomarán sus valores
del mismo.

248
Inserción de registros
• New anidado a un For each, con igual tabla base.

“MedicalDoctor” “ConsultationHour”
MDoctorId* MDoctorId*
ConsultationDate* Date
MDoctorName
MDoctorAddress ConsultationShift* Shift
MDoctorPhone ConsultationOfficeNbr M.Doctor’s office

Procedimiento que sustituya al médico &SourceMD (recibido por


parámetro) en todas sus consultas, por el médico &replacementMD

For each
Parm(&SourceMD, Where MDoctorId = &SourceMD
&replacementMD); Defined by ConsultationDate
new
MDoctorId = &replacementMD
endnew
Delete
endfor

Queremos escribir el Source de un procedimiento que sustituya a un médico por otro para las
consultas que el primero tenía asignadas.

Es decir, debemos recorrer la tabla CONSULTATIONHOUR que es la que representa las consultas
asignadas a cada médico en una fecha y turno determinado, filtrando por el médico que debemos
sustituir, y luego reemplazar a ese médico por el sustituto para esos registros.

Haríamos una simple actualización de registro, a no ser por el hecho de que el atributo a sustituir es
parte de la clave primaria, por lo que no podemos modificarlo.

Deberemos, por tanto, crear un nuevo registro con el médico sustituto, y el resto de los atributos
tomarán los valores del registro original. Luego borraremos el registro original, así queda el nuevo
registro, con el médico sustituto.

Observemos que dado que el new está anidado al for each, en el contexto del new existirán todos los
atributos de la tabla extendida del for each. En este caso, estarán instanciados los atributos de
CONSULTATIONHOUR y los de MEDICALDOCTOR, para un registro en particular.
Es por ello que en el new solamente asignamos valor al atributo MDoctorId, pues los demás:
ConsultationDate, ConsultationShift, ConsultationOfficeNbr están instanciados y queremos que
conserven esos valores.

Sin embargo, aquí tenemos un problema. ¿Cuál elegirá GeneXus como tabla base del new?

249
Inserción de registros
• New anidado a un For each, con igual tabla base.

For each
Where MDoctorId = &SourceMD
Defined by ConsultationDate ¿Tabla base del new?
new
MDoctorId = &replacementMD MEDICALDOCTOR
endnew
Delete
endfor

For each
Where MDoctorId = &SourceMD
Defined by ConsultationDate
new
defined by ConsultationDate CONSULTATIONHOUR
MDoctorId = &replacementMD
endnew
Delete
endfor

En este ejemplo lo que pretendemos mostrar es el cuidado que hay que tener cuando queremos que
ciertos atributos de la tabla base del new tomen sus valores del contexto.

Es decir, primero tenemos que asegurarnos que por los atributos que figuren dentro del new, quede
determinada la tabla base que deseamos, para que a partir de allí, sí puedan quedar implícitos los
atributos del contexto.

250
Restricciones

• No se realiza control de integridad referencial.

• El único control que se realiza es el de


duplicados.

• No actualiza atributos definidos como redundantes.

En los procedimientos el único control de integridad que se realiza automáticamente es el control de


duplicados. El control de integridad referencial queda a cargo del programador, lo que no ocurre en
las transacciones. Por lo tanto, en el ejemplo anterior podríamos haber eliminado primero el
cabezal, y luego las líneas, sin ningún problema. Incluso podríamos haber eliminado solo el cabezal,
y estaríamos dejando referencias colgadas (las líneas).

Por ejemplo, al dar de alta por procedimiento una nueva factura, cuando se da de alta el cabezal no
se controla que el valor de CustomerId ingresado exista en la tabla CUSTOMER.

Los atributos definidos como redundantes no se actualizan.

Un caso particular de ello son las fórmulas redundantes. Como en procedimientos no se actualizan,
hay que calcularlas y actualizarlas en forma manual.

Las fórmulas redundantes deben ser mantenidas explícitamente con los comandos de asignación.

Por ejemplo, si en la transacción "Invoice" tenemos definido el atributo InvoiceTotal como una
fórmula vertical redundante, siendo:

InvoiceTotal = SUM( InvoiceLineAmount )

y por procedimiento damos de alta una o varias líneas, debemos tener en cuenta que no se
actualizará el valor almacenado de InvoiceTotal. Habrá que actualizarlo explícitamente en el
procedimiento, por cada línea dada de alta.

251
OBJETO WEB PANEL

252
Características

• Permiten definir consultas interactivas a la base de


datos.

• Son flexibles por lo que se prestan para múltiples


usos.

Los web panels son objetos GeneXus que permiten al usuario en tiempo de ejecución, realizar
interactivamente consultas a la base de datos a través de una pantalla.

El término “interactivamente” se refiere a que el usuario podrá ingresar en la pantalla de un web panel una y
otra vez distintos valores de filtros, y consultar a continuación los datos que concuerden con los mismos.
Además, sobre los datos consultados, el usuario podrá realizar distintas acciones, como veremos.

Los web panels no permiten la actualización de la base de datos, sino sólo su consulta1.

El objetivo primordial de este objeto GeneXus es la definición de consultas interactivas a la base de datos, sin
embargo se trata de un objeto muy flexible por lo que se presta para diversos usos.

En este capítulo estudiaremos algunos detalles de este tipo de objeto.

--------------------------------------------------------------------------------------------------------------------
1 A menos que se utilicen en combinación con los business components (estudiados más adelante)

253
Elementos

• Algunos de ellos son:


• Web Form
• Reglas
• Condiciones
• Subrutinas
• Eventos
• Propiedades
• Ayuda
• Documentación

Los elementos de los web panels son:

Web Form: Cada web panel contiene un form Web, el cual debe ser diseñado por el analista agregándole
variables, atributos, así como otros controles, para que el usuario pueda interactuar con el mismo.

Reglas: Las reglas de un web panel permiten definir ciertos comportamientos puntuales de dicho objeto. Por
ejemplo, declarar qué parámetros recibe, definir qué variables no queremos que sean aceptadas en el form
sino utilizadas para desplegar información, etc.

Condiciones: Es para definir las condiciones que deben cumplir los datos a ser recuperados (filtros).

Subrutinas: Son rutinas locales al web panel.

Eventos: Los web panels emplean la programación orientada a eventos. Este tipo de programación permite
definir código ocioso, que se activa en respuesta a ciertas acciones provocadas por el usuario o por el sistema.
En esta sección de un web panel es donde se define el código ocioso asociado a los eventos que pueden ocurrir
durante la ejecución del web panel.

Propiedades: Son características a ser configuradas para definir ciertos detalles referentes al comportamiento
general del web panel.

Ayuda: Permite la inclusión de texto de ayuda, que los usuarios podrán consultar en tiempo de ejecución del
web panel.

Documentación: Permite la inclusión de texto técnico como documentación para los desarrolladores.

254
Clasificación de web panels

• Web panels de Entrada

• Web panels de Salida

• Web panels Mixtos

Todo web panel tiene un form asociado, y en el mismo, contrariamente al comportamiento del form de una
transacción, los atributos que se incluyan serán de salida, y las variables que se incluyan serán de
entrada.

Es fácil de comprender que el objetivo del form de un web panel es exactamente el contrario al objetivo del
form de una transacción, ya que:

• a través del form de una transacción se ingresan los valores de los atributos en la base de datos.
• a través del form de un web panel, se consultan / recuperan los valores de los atributos de la base de datos.

Es por esto que los atributos son de entrada en las transacciones y de salida en los web panels.

Y en lo que respecta a las variables, las mismas son de salida en las transacciones y de entrada en los web
panels.

La siguiente clasificación describe los distintos usos posibles de los web panels:

· Web panel de entrada: le damos este nombre a un web panel que tiene la única función de aceptar valores
digitados por el usuario (esto significa que su form contendrá únicamente variables).
· Web panel de salida: le damos este nombre a un web panel que tiene la única función de mostrar
información (esto significa que su form contendrá únicamente atributos, pudiendo también contener variables
a las cuales se les haya cambiado el comportamiento por defecto de ser de entrada, definiéndolas de salida y
cargándoles valores explícitamente).
· Web panel mixto: le damos este nombre a un web panel que permite tanto ingresar valores como mostrar
información (en este caso su form contendrá tanto variables como atributos, o bien sólo variables, algunas con
el comportamiento por defecto de ser de entrada y otras definidas explícitamente de salida y cargándoles
valores).

Vale aclarar que esta clasificación es independiente de la herramienta; es decir, GeneXus internamente no
clasifica a los web panels.

255
Web panel de Entrada

Event Enter

¾ Las variables adquieren el valor digitado luego de


presionar algún botón.

Event Enter
RList.call( &InitialCustomerName, &FinalCustomerName )
endevent

Denominamos web panels de entrada a aquellos web panels cuya única función es que el usuario realice
ingresos de valores por medio de los mismos. Por lo tanto, sus forms contendrán solamente variables.

Por ejemplo, un web panel de entrada puede contener dos variables &InitialCustomerName y
&FinalCustomerName como se muestra arriba. En tiempo de ejecución, el usuario podrá ingresar valores en las
variables &InitialCustomerName y &FinalCustomerName dado que en los web panels las variables son por
defecto de entrada.
En el evento Enter del web panel (asociado al botón Confirm), se invocará a un reporte, al cual se le pasarán
por parámetro las variables &InitialCustomerName y &FinalCustomerName para que el reporte liste todos los
clientes cuyos nombres se encuentren en el rango solicitado:

Event Enter
RListCustomerRange.call( &InitialCustomerName, &FinalCustomerName )
EndEvent // Enter

De modo que la definición de este web panel de entrada es para que el usuario ingrese el rango de clientes
a listar, y al seleccionar el botón Confirm, se ejecute el reporte correspondiente.

256
Web panel de Salida

tabla base:
CUSTOMER

Regla: parm(in:CustomerId);

Denominamos web panels de salida a aquellos web panels cuya única función es exhibir datos.

Para que un web panel únicamente muestre datos, su form debe contener solamente atributos, ya que los
atributos en los forms de web panels son indefectiblemente de salida1. Otra posibilidad es incluir en el form
variables, pero habrá que cambiarles su comportamiento por defecto de ser de entrada, a ser de salida, y
cargarles valores explícitamente2.

El web panel mostrado arriba ha sido creado para exhibir los datos de un cliente. Se necesita invocarlo desde
otro objeto, pasándole por parámetro el código del cliente del cual se quiere mostrar la información.

Para resolver esto, una vez creado el web panel “View Customer Data”:
• se han agregado los atributos que se desean visualizar en su form
• se ha definido la regla: Parm(in: CustomerId); en la sección de reglas del objeto

Tan sólo definiendo esto obtendremos el comportamiento deseado.

¿Qué concluirá GeneXus acerca de este web panel, cuando lo especifiquemos?

Primeramente GeneXus observará que los atributos incluidos en el form pertenecen a las tablas CUSTOMER y
COUNTRY respectivamente. El siguiente diagrama de Bachman explicita la relación entre ambas tablas:

CUSTOMER COUNTRY
--------------------------------------------------------------------------------------------------------------------
1 Al contrario de lo que sucede con los atributos en las transacciones (salvo los inferidos o los que tienen regla

noaccept o propiedad Enabled deshabilitada).


2 El comportamiento por defecto de las variables también es opuesto entre Web Panels y Transacciones.

257
Teniendo en cuenta la relación entre las tablas involucradas, GeneXus descubrirá que deberá recorrer la tabla
CUSTOMER y acceder a la tabla COUNTRY por el concepto de tabla extendida. La tabla COUNTRY no podrá ser
elegida para ser recorrida porque su tabla extendida no incluye a la tabla CUSTOMER.

Así es que GeneXus determinará un for each asociado al web panel, en este caso con tabla base CUSTOMER;
nosotros no escribimos el for each, pero GeneXus lo infiere automáticamente.

A su vez, como en la regla parm definida en el web panel se recibe un atributo, el mismo actuará como filtro
por condición de igualdad. Es decir, que al ejecutarse la recorrida de la tabla CUSTOMER (accediendo a la tabla
COUNTRY para traer el nombre de país), se filtrará por el código de cliente recibido por parámetro.

Concluyendo, se recorrerá la tabla CUSTOMER, con condición de filtro por el cliente recibido en la regla parm y
se mostrarán los datos en la pantalla. El nombre del país del cliente (CountryName) se inferirá por el concepto
de tabla extendida y se mostrará también en la pantalla.

Decimos que este web panel tiene tabla base, y la misma es CUSTOMER. Esto significa que el web panel
tiene un for each implícito / automático asociado, cuya tabla base es CUSTOMER.

258
Web panel de Salida

grid: Se cargan los


registros de la base de
datos correspondientes
en archivo temporal

tabla base:
Regla: parm(in:CustomerId);
INVOICE

Este web panel ha sido creado para mostrar las facturas de determinado cliente. Se necesita desde otro objeto,
invocar a éste, pasándole por parámetro el código del cliente del cual se quieren mostrar sus facturas.

Para resolver esto, una vez creado el web panel “View Customer Invoices”:

• se han agregado los atributos que deseamos visualizar en su form (utilizando el control grid para mostrar las
N facturas del cliente en cuestión)
• se ha definido la regla: Parm(in: CustomerId); en la sección de reglas del objeto

Este web panel, a diferencia del anterior no es plano, pero continúa siendo un web panel de salida, ya que lo
único que hace es mostrar datos de la base de datos, sin permitir que el usuario ingrese nada.

Cuando se incluye un grid en un form, se está indicando que se va a mostrar una cantidad indefinida de datos
(en este caso, facturas).

Dado que en este web panel hay involucrados atributos de las tablas CUSTOMER e INVOICE y que la relación
entre ambas tablas es:

INVOICE CUSTOMER

GeneXus determinará que recorrerá la tabla INVOICE y accederá a la tabla CUSTOMER por el concepto de tabla
extendida. La tabla CUSTOMER no podrá ser elegida para ser recorrida porque en su tabla extendida no se
encuentra la tabla INVOICE.

De modo que GeneXus determinará un for each implícito asociado al web panel, con tabla base INVOICE,
accediendo a la tabla CUSTOMER por el concepto de tabla extendida.

Como en la regla parm definida en el web panel, se recibe un atributo, el mismo actuará como filtro por
igualdad. Es decir, que al ejecutarse la recorrida a la tabla INVOICE accediendo a la tabla CUSTOMER, se
filtrará por el código de cliente recibido por parámetro.

Decimos que este web panel tiene tabla base, y la misma es INVOICE. Esto significa que el web panel tiene
un for each implícito/automático asociado, cuya tabla base es INVOICE.

259
Web panel Mixto: “Work With”
Las
variables
•Event Enter adquieren
el valor
•Evento Usuario digitado
luego de
presionar
algún
botón

Tabla Base: generales


CUSTOMER versus
particulares

Los web panels no tienen por qué ser sólo de entrada o sólo de salida. El web panel que se muestra arriba es de
entrada/salida (mixto), su form contiene tanto variables como atributos.
La funcionalidad de este web panel es cargar en el grid los datos de todos los clientes cuyos nombres cumplan
con la condición de filtro especificada. La idea es digitar sobre la variable &CustomerName el valor de filtro
deseado, y a continuación presionar el botón Search para que se ejecute la consulta en el servidor y el
resultado de la misma sea cargado en la página.

El evento asociado al botón Search puede ser el Evento Enter (evento del sistema) ó cualquier evento definido
por el usuario (volveremos sobre esto más adelante).

Las condiciones de filtro pueden definirse de dos maneras posibles:


• A nivel de un grid en particular (botón derecho sobre el grid/Conditions): de hacerlo así, se tratará de
condiciones particulares para ese grid (las que se muestran arriba).
• A nivel de todo el web panel (en la sección Conditions del objeto): de hacerlo así, se tratará de
condiciones globales, es decir que aplicarán a todos los grids del web panel en los que tenga sentido aplicarlas
(más adelante veremos web panels con múltiples grids).

En el web panel del ejemplo tenemos un sólo grid, por lo cual ambas opciones serían equivalentes desde el
punto de vista lógico. Sin embargo es recomendable escribir las condiciones a nivel del grid ya que en un futuro
podrán agregarse más grids al web panel. Además teniendo las condiciones a nivel del grid se optimiza al
momento de la especificación (ya que en caso contrario, GeneXus deberá estudiar para cada grid si aplicar las
condiciones generales a ese grid particular o no).

¿Qué lógica inferirá GeneXus al momento de la especificación del Web Panel?

Como los atributos involucrados en el web panel pertenecen algunos a la tabla CUSTOMER y otros a la tabla
COUNTRY, y en la tabla extendida de CUSTOMER está la tabla COUNTRY, GeneXus determinará que la tabla a
recorrer es CUSTOMER y que accederá por su extendida a la tabla COUNTRY para cargar el valor del atributo
CountryName. Es decir, GeneXus determinará un for each implícito asociado al web panel, con tabla base
CUSTOMER.

Las condiciones definidas antes (a nivel de grid) se incluirán en el for each implícito (como cláusulas where),
de modo tal que al ejecutarse la consulta, se recorrerá la tabla CUSTOMER, filtrando por dichas condiciones.

Es importante considerar que tanto en las condiciones globales del web panel, como en las condiciones locales a
un grid de un web panel, es posible utilizar la cláusula when al igual que cuando se definen filtros en los objetos
reportes y procedimientos.

260
Web panel ¿con tabla base?

• Decimos que un web panel es “con tabla base” cuando GeneXus


puede determinar un for each implícito asociado a él.
• Es decir, si bien el analista no escribe un for each explícitamente
en el web panel para efectuar la consulta, GeneXus lo determina
automáticamente (por eso lo llamamos: for each implícito).
• Tabla base del for each implícito = Tabla base del web panel.

• Un Web Panel es “sin tabla base” cuando GeneXus no puede


determinar una tabla de la base de datos a recorrer para mostrar
la información que se presenta en el form.
• En este caso en el form solamente aparecen variables (y no
atributos).

Un web panel es con tabla base cuando de los atributos que aparecen, GeneXus puede determinar una tabla
de la base de datos a recorrer para, recuperando sus registros, mostrar la información que aparece en los
atributos del web panel.

De este modo, es como si hubiéramos escrito un for each para navegar esa tabla base y trabajar con algunos
atributos de la misma, y de la extendida.

Si en el Web Panel no aparecieran atributos, sino solo variables, evidentemente GeneXus no podrá
determinar una tabla a ser navegada. En este caso el web panel será sin tabla base.

261
Orden de los datos a recuperar

• Botón derecho sobre el grid:

Para definir que una consulta se efectúe ordenando por ciertos atributos, y por ende que los datos extraídos de la
consulta se muestren ordenados con dicho criterio, se debe hacer clic con el botón derecho del mouse sobre el
grid, y seleccionar el ítem Order del menú pop up que se muestra arriba.

A continuación, se presentará el diálogo para que se ingresen los atributos por los que se desea ordenar.

Definir esto es equivalente a definir la cláusula order en el comando for each, y se aplica todo lo visto en dicho
tema: desde que para ordenar en forma descendente por un atributo se debe encerrar el atributo entre
paréntesis (), la creación de índices temporales cuando no exista un índice físico correspondiente a los atributos
de ordenamiento, así como la posibilidad de utilizar la cláusula when para condicionar la aplicación de ese order.

En nuestro web panel “Work With Customers” ordenamos los clientes que se listan en el grid por CustomerName.

El poder definir order para un grid permite entre otras cosas optimizar la consulta, cuando se establecen
condiciones de filtro. Así, si en las conditions generales y/o las del grid particular, siendo la tabla base CUSTOMER
establecemos los filtros, teniendo dos variables ingresadas por el usuario:
CustomerName >= &customerStartName;
CustomerName <= &customerEndName;
Entonces, de no especificar un orden por CustomerName, se deberá recorrer toda la tabla, de principio a fin,
para cargar los registros que cumplan con las condiciones. Especificando un order optimizamos la consulta.

Nota: Solamente si el form del web panel no tiene ningún grid (atributos sueltos), y se necesita definir un orden
específico para la consulta, se contará con la posibilidad de definir en la sección de reglas del web panel, la regla
de sintaxis: order(att1, att2, attN); siendo att1, att2, attN: la lista de atributos que define el orden de la
consulta.

262
Eventos en web panels

• En los web panels se utiliza la programación dirigida por


eventos.

• Eventos disponibles en web panels:

– Evento Start
– Evento Refresh
– Evento Load
– Evento Enter
– Eventos de Usuario

– Evento Click asociado a control

Dado que la programación de los Web Panels está dirigida por eventos, para poder programar adecuadamente
un objeto de este tipo es necesario conocer los eventos existentes y el momento y orden en que éstos se
disparan.

263
Evento Start

• Es un evento del sistema, que ocurre automáticamente siempre


que se hace Get o Post y es el primer evento que se ejecuta.

• No se conocen valores de atributos, salvo los recibidos por


parámetro. Esto se debe a que aún no se ha efectuado la
consulta.

• Ejemplo: se puede utilizar para que un control del form no


aparezca visible, para cargar un bitmap, para asociarle un Link a
otro control, etc.:

Event Start
&var.Visible = 0
&Update = LoadBitmap("images/edit.gif")
newControl.Link = Link(TCustomer)
endevent

En el ejemplo, tendremos 3 controles en el form: la variable de nombre var, la de nombre Update de tipo
Bitmap y un control de nombre newControl que puede ser, por ejemplo, un control imagen.

En el evento Start se le asigna a la propiedad Visible del control variable &var el valor 0, indicando que no
deberá verse en el form.

A su vez, a la variable de tipo bitmap, &Update, se le carga la imagen que contendrá, y al control que
suponemos imagen, newControl, se le define la propiedad Link, de manera tal que cuando el usuario haga
clic sobre el control, se invocará a la transacción Customer.

264
Evento Refresh

• El evento Refresh es un evento del sistema

• Se ejecuta cada vez que se realiza un Get o Post.

• Provoca que se ejecute la consulta a la base de


datos.

• Es decir, al ocurrir el evento Refresh, se ejecuta lo


codificado en dicho evento, y a continuación se ejecuta
la consulta a la base de datos. Viene seguido siempre
del evento Load.

265
Evento Load

• Cada vez que se ejecute el evento Refresh en un web panel,


seguidamente se ejecutará el evento Load.

• La cantidad de veces que el evento Load será ejecutado,


dependerá de si el web panel tiene tabla base o no la tiene:

• Tiene tabla base:


• Cuando aparecen atributos que le permiten automáticamente
determinar que se desea navegar una tabla determinada de la
base de datos
• El evento Load se ejecutará N veces

• No tiene tabla base:


• Cuando no ocurre lo anterior (en el form solo hay variables)
• El evento Load se ejecutará solamente una vez.

Cuando el web panel es con tabla base, al producirse el evento Refresh se accede a la base de datos, a esa
tabla base (la asociada al web panel), y se la recorre cargando los registros que cumplan las condiciones
(conditions del grid y generales). Ocurrirá en ese proceso un evento Load por cada registro en el que se
esté posicionado, inmediatamente antes de cargarlo. Esto nos permite realizar alguna operación que
requiera de ese registro (y de su extendida), antes de efectivamente cargarlo en el grid. Inmediatamente
luego de ejecutado el código asociado al evento Load, se cargará la línea del grid y se pasará el puntero al
siguiente registro de la tabla base, para realizar lo mismo (evento Load, carga de la línea). Este proceso se
repetirá hasta cargar todas las líneas del grid.

Si un web panel es sin tabla base, GeneXus no puede determinar automáticamente una tabla de la base de
datos a recorrer para mostrar la información que se presenta en el form. En este caso en el form solamente
aparecen variables (y no atributos) y también ocurrirán los eventos Refresh y Load, sólo que el evento Load
se ejecutará una única vez, dado que no se estará posicionado en ningún registro de ninguna tabla.

266
Evento Load
en web panel con tabla base

Luego del
evento Refresh
se ejecuta el
evento Load N
veces:
una vez por cada
registro de la
tabla base leído
para ser cargado
en la línea del
grid

Por cada registro leído en la consulta efectuada a la base de datos, se disparará el evento Load (ejecutándose
el código incluido en el mismo, y cargándose a continuación una línea en el grid con los datos asociados al
registro).

267
Evento Load
en web panel con tabla base: ejemplo

Si en el grid que muestra los clientes que cumplen con las condiciones de filtro, quisiéramos agregar una
columna al final, que marque que el cliente es moroso (deudor) si su saldo es mayor a $10.000, es decir, que
en ejecución sobresalga su condición de moroso apareciendo un literal DEBTOR en ese caso, alcanza con
agregar una variable &type al grid, de tipo Character(10) y cargarla en el evento Load del web panel como se
muestra arriba.

Para cada registro de la tabla base CUSTOMER que se vaya a cargar como línea en el grid, se ejecutará el
código del evento Load, cargándose en la columna &type el valor DEBTOR únicamente si el saldo de ese
cliente que va a listarse supera los $10.000.

Luego, si para cada cliente del grid además de mostrar su nombre, país, sexo, saldo y tipo, queremos
mostrar la cantidad de facturas que se le han emitido, alcanza con agregar una variable &quantity al grid, e
incluir en el código del evento Load, el for each para contar esas facturas.

Observar que el for each definido en el evento Load estará anidado al for each implícito (el de la tabla base),
por lo que se efectuará un join, recorriéndose solamente las facturas de ese cliente, el que se está cargando.

268
Evento Load
en web panel sin tabla base
• En un web panel sin tabla base, el evento Load se ejecutará solamente una vez.

Evento Refresh

Evento Load

Que el web panel no tenga tabla base, significa que no tiene un for each implícito asociado; por lo tanto,
cuando se ejecute el evento Refresh, no comenzará a ejecutarse ninguna consulta; se ejecutará el código
asociado al evento Refresh, y a continuación se ejecutará el código asociado al evento Load, una única vez.
Aquí es donde tendremos que cargar el grid, consultando la base de datos con un for each explícito. A
continuación vemos el código de este evento.

269
Evento Load
en web panel sin tabla base: ejemplo

Comando (que solo puede ir dentro de


evento Load) para efectivamente
cargar una línea con el valor que
tengan las variables en ese momento.

El objetivo del comando LOAD dentro del evento Load es cargar efectivamente una línea en el grid.

Una vez que se hayan asignado valores a todas las variables que sean necesarias, y se desee agregar la línea al
grid, deberá ejecutarse el comando LOAD.

Solamente se puede especificar el comando LOAD dentro del evento Load del grid de un web panel y en
ningún otro lado.

Event Load
for each
&CustomerId = CustomerId
&CustomerName = CustomerName
&CustomerGender = CustomerGender
&CustomerBalance = CustomerBalance
if CustomerBalance > 10000
&type = ‘DEBTOR’
else
&type = ‘’
endif
&quantity = 0
for each
defined by InvoiceDate
&quantity += 1
endfor
Load /* LUEGO DE HABER CARGADO TODAS LAS VARIABLES CON LOS VALORES
CORRESPONDIENTES A LA LÍNEA A SER CARGADA EN EL GRID, DEBEMOS INCLUIR EL COMANDO LOAD,
EL CUAL AGREGARÁ EFECTIVAMENTE LA LÍNEA AL
GRID. */
endfor
Endevent

Si en la codificación del evento Load definimos comandos For each y asignamos valores a las variables en las
iteraciones pero no incluimos el comando LOAD, en tiempo de ejecución estaremos asignando una y otra vez
valores a las variables, pero no se estarán agregado líneas en el grid (solamente quedará una línea en el grid
con los últimos valores cargados en las variables). Por esta razón es muy importante no olvidar escribir este
comando en el lugar apropiado.

270
Evento Enter

• Cuando se inserta un nuevo


botón en el form de un Web
Panel, por defecto aparece con
el Caption “Confirm” y aparece
asociado al evento del sistema
Enter.

• El evento Enter puede


asociarse a cualquier botón,
atributo, imagen, text block, en
la propiedad de los controles:
OnClickEvent.

• De modo que si se necesita


ejecutar acciones cuando el
usuario final haga clic en el
control asociado, en este
evento deberán codificarse.

271
Eventos de usuario

• Además de los eventos


ofrecidos por GeneXus, el
analista puede definir eventos
creados por él, llamados
eventos de usuario.

• Cada evento de usuario debe


asociarse a un control insertado
en el form del web panel de los
que soportan el OnClickEvent
(botones, text blocks,
imágenes, atributos)

• En tiempo de ejecución, el
evento de usuario ocurrirá
luego de que el usuario haga
clic sobre el control asociado al
mismo.

Casi todos los controles que aparecen en el form brindan la posibilidad de disparar un evento cuando el
usuario hace clic con el mouse sobre ellos (aparecen como hipervínculos en ejecución); se consigue de dos
maneras distintas:

1. Editando las propiedades del control, y definiendo un evento de usuario en la propiedad


OnClickEvent

2. Dándole un nombre al control y en la sección de Eventos programando:

Event nombreControl.click

Endevent

Con esta última alternativa no tendremos que definir un evento de usuario, sino que
estaremos programando el evento click del control.

272
Web panel "Work With Customer”

Acciones sobre
el cliente
seleccionado

Para que el web panel con el que venimos trabajando sea un verdadero “trabajar con” se le deben agregar
acciones a ser efectuadas sobre los clientes: la posibilidad de insertar un nuevo registro (nuevo cliente), el
modificar uno existente, o el eliminarlo (así como también poder simplemente “visualizarlo”).

Una forma de implementar esto es agregar los cuatro botones que aparecen arriba, en el form:
. un botón que ofrezca insertar un cliente (Insert)
. un botón que ofrezca modificar un cliente (Update)
. un botón que ofrezca eliminar un cliente (Delete)
. un botón que ofrezca visualizar los datos de un cliente (View)

Además debemos permitir la selección de una línea de la grilla para aplicarle alguna de las acciones
definidas en los botones del form. Para ello, accedemos a las propiedades de la grilla con botón derecho sobre el
control grid y configuramos la propiedad AllowSelection con el valor ‘True’ como muestra la figura. Al hacerlo
se nos habilitan tres propiedades más, que permiten especificar SelectionColor: el color que tendrá la línea
cuando el usuario la seleccione (haciendo clic con el mouse sobre la misma); AllowHovering: la posibilidad de
que cambie el color de las líneas cuando el usuario se desplaza con el mouse sobre ellas, y HoveringColor: el
color que tendrá una línea cuando el mouse pasa sobre ella. Estas funcionalidades se implementan con código
javaScript que se envía al Browser al ejecutar el Web Panel.

En la sección de eventos del web panel, definiremos el código asociado a estos botones. Lo veremos en la
página siguiente.

273
Eventos de usuario en el web panel
“Work With Customer”
Event ‘Insert’
Tcustomer.call(‘INS’, 0)
Endevent En las reglas de la
transacción “Customer”:
Event ‘Update’
Parm(&Mode, &CustomerId );
Tcustomer.call(‘UPD’, CustomerId)
Endevent
Variable del Variable de
sistema usuario
Event ‘Delete’
Tcustomer.call(‘DLT’, CustomerId) CustomerId = &CustomerId if not
Endevent &CustomerId.IsEmpty();

Event ‘View’
Tcustomer.call(‘DSP’, CustomerId)
Endevent

La variable &Mode es del sistema y su tipo es Character(3). Tiene la particularidad de “entender” 4 valores:

• ‘INS’: este valor indica ‘ejecutar la transacción en modo Insert’


• ‘UPD’: este valor indica ‘ejecutar la transacción en modo Update’
• ‘DLT’: este valor indica ‘ejecutar la transacción en modo Delete’
• ‘DSP’: este valor indica ‘ejecutar la transacción en modo Display’

¿Cuál es el resultado de recibir por parámetros en una transacción el modo de ejecución y la clave primaria?
El permitir insertar, modificar o eliminar puntualmente una instancia y luego retornar al objeto llamador.

Es por ello que en todos los eventos definidos en el Web Panel “Work With Customer” estamos invocando a la
transacción “Customer”, pasándole dos valores por parámetro: un literal de 3 letras, que es el modo y el código
de cliente correspondiente a la línea del grid que fue seleccionada (por ello necesitamos habilitar la selección de
líneas del grid, mediante la propiedad AllowSelection que vimos antes).

En definitiva la regla parm a definirse en la transacción "Customer", es:

parm(&Mode, &CustomerId);

Como se puede observar, no recibimos el código de cliente directamente en el atributo CustomerId, sino en una
variable. ¿Por qué?
Si declaráramos el atributo CustomerId en vez de una variable, el valor que se recibiera en él actuaría
automáticamente como filtro por igualdad. Sin embargo cuando invocamos a la transacción "Customer" con los
parámetros ‘INS’ y 0, el modo ‘INS’ indica que queremos que la transacción se ejecute en modo insert; y como
en dicho caso no tenemos que enviar el código de cliente para instanciarlo, completamos el segundo parámetro
con valor 0 (porque la cantidad –y el tipo de datos- de los parámetros enviados, debe coincidir con la cantidad –
y el tipo de datos- de los parámetros recibidos). De modo que el valor 0 es para completar el parámetro
simplemente, no para que se filtre por él tratando de instanciar un cliente de código 0.
En los otros 3 casos en que se invoca a la transacción "Customer" (con los parámetros ‘UPD’ y CustomerId ;
‘DLT’ y CustomerId ó ‘DSP’ y CustomerId respectivamente) sí se quiere filtrar por el valor del código de cliente
recibido; pero basta que haya un caso en el cual se invoque a la transacción y que no sirva filtrar por el valor
recibido, para que no sirva recibir el parámetro en el atributo y esa es la razón por la cuál se está recibiendo en
una variable. Si la clave primaria, CustomerId es autonumerada, entonces en ese caso sí podrá recibirse en
atributo.

Recuerde que a partir de la inclusión de la regla parm en un objeto, éste desaparece del Developer Menú,
debido a que desde el mismo no es posible el envío de parámetros.

274
Web panels - Funcionamiento

• Esquema de trabajo en Internet: el servidor no sabe


lo que se está haciendo en el Browser, hasta que se
someta la página. Es decir, hasta que se dispare un
evento (enter, de usuario, click).

• Orden de disparo de eventos: es diferente si se


trata de la primera carga del web panel (Get) o si
ya estaba cargado cuando se dispara un evento de
usuario, enter, click (Post)

Es importante entender que en Internet, cuando el usuario accede a una página del servidor Web para
visualizarla, el Browser baja la página al cliente. Por lo tanto, no existe forma de detectar lo que realiza el
usuario: el servidor Web volverá a tener el control cuando se dispare el evento ENTER o algún evento de usuario
o click. En ese momento se envía (se somete, se hace un post) el resultado al servidor para continuar con su
procesamiento. Es decir, una vez que el objeto web finaliza la ejecución en el servidor, no queda en memoria.
Como consecuencia, la forma en que programamos este tipo de aplicaciones presenta algunas diferencias con
respecto a lo acostumbrado en ambientes no web.
Es por esta razón que es importante destacar el orden en que se disparan los eventos y el momento en que las
variables adquieren el valor ingresado por el usuario.

El orden de ejecución de los eventos en web panels es diferente si se trata de la primera llamada al mismo (GET)
o si se disparó algún evento de usuario, enter o click (POST).

275
GET: Orden de disparo de eventos

• Al ejecutar un web panel por primera vez se


disparan los siguientes eventos:

• Start
• Refresh
• Load

La primera vez que se ejecuta el web panel (se conoce también como el momento en que se hace el “GET”
de la página) los eventos que se disparan son los siguientes y en el siguiente orden:

1. Start
2. Refresh
3. Load

Luego de esto, cuando el usuario haga clic sobre un control que tenga asociado el evento Enter o uno de usuario
o click se ejecutará nuevamente el web panel y el orden de disparo de los eventos será diferente, como se indica
en la siguiente página.

276
POST: Orden de disparo de eventos

• Resto de las ejecuciones del web panel:

• Start
• Lectura de variables
en pantalla
• Evento Enter o de
usuario (submit)
• Refresh
• Load

En el resto de las ejecuciones del web panel, que ocurren cuando se presiona un botón, o se fuerza la ejecución
del evento asociado a una imagen, text block, etc. (haciendo clic sobre el control que tiene asociado el evento de
usuario o Enter o click) momento que se conoce también como el “POST” de la página, los eventos se dispararán
en el siguiente orden:

1. Start (nuevamente se dispara el evento Start)


2. Lectura de las variables de la pantalla. Esto se realiza porque el usuario puede haberlas modificado (por
ejemplo las variables de la parte fija del web panel que están involucradas en las conditions, como en el ejemplo
que se presenta arriba, donde se quieren cargar en el grid los clientes cuyo nombre contenga el string cargado
por el usuario en la variable &CustomerName)
3. Evento Enter o click o evento de usuario (código correspondiente al evento asociado al control que se presionó
y produjo el POST).
4. Refresh
5. Load

En el ejemplo no necesitamos codificar nada en el evento asociado al botón Search. Solo lo pusimos para poder
enviar al servidor la variable con el valor que ingresó el usuario y que la página se refresque cargando en el grid
los clientes que cumplan con el filtro que el usuario estableció mediante esa variable.

277
Web panels - Variables

• Variables: adquieren valor ingresado por el usuario luego de


sometido evento (POST)

• Si en un evento se usa una variable que se carga en otro


evento Æ la variable debe estar en el form, y además debe
estar después del control en el que se carga su valor.

• Ejemplo:

Event Load
&cont+=1
Event Enter
endevent
Event Refresh if &cont<5
&cont= 0 …
endevent endevent

Relacionado con el orden de disparo de los eventos, es importante destacar el momento en que las variables
adquieren los valores ingresados por el usuario: solamente lo harán después de presionar un botón1 (que es
cuando el servidor Web tiene el control del procesamiento).

Por ejemplo, cualquier Link especificado en el evento Start a otro web panel con una variable que se ingresa en el
form no va a tener ningún valor cuando se haga clic sobre el Link.
(Ej: control.Link = HWebPanelX.Link(&var). No se debe escribir esto en el start si la &var esta en el form, porque
al momento de armarse el link no se tiene el valor de la variable)

Si en un evento se usa una variable que se carga en otro evento, entonces esa variable debe estar presente en el
form. Si no está en el form, la variable no tendrá valor cuando se disparen los eventos que la consultan (esto es
por el “orden” en que ocurren los eventos).

Además, deberá estar en el form después del control en el que se carga. Por ejemplo, si la variable se carga en el
LOAD de un grid entonces la variable tiene que estar en pantalla después del grid.

Ejemplo: web panel con grid que lista las facturas existentes, y queremos contar la cantidad de facturas que se
listan en el grid. Para ello definimos una variable &cont que debemos incrementar cada vez que se carga una
nueva línea, es decir, en el evento Load del grid.

Para que la variable se cargue correctamente, deberá incluirse luego del grid, puesto que de lo contrario ya se
habrá dibujado en la página, antes de que pueda ser cargada por el evento Load.

Gracias a tenerla en el form, cuando el usuario presione el botón Confirm que consulta por el valor de la variable,
la misma podrá tener el valor cargado antes por el Load.
Recordemos que al presionar el botón Confirm se realizará un POST al servidor, y en él se dispararán Start,
lectura de variables de pantalla (aquí se leerá el valor de &cont que había sido cargado antes por el evento Load),
luego se disparará el evento Enter asociado al Confirm y dentro del mismo se consulta por el valor de la variable,
que gracias a que fue enviada al servidor por estar en el form, tiene el valor que se había cargado antes. Luego
se dispararán Refresh y Load.
Observemos que aquí, cuando se dispare el Load, se incrementará la variable, por lo que deberemos resetearla
antes de que se empiecen a cargar las líneas, porque de lo contrario mostrará el doble de las líneas que tenía
antes. ¿Dónde resetearla?
Event Refresh
&cont = 0
endevent
-----------------------------------------------------------------------------------------------------------------------
1 O hacer clic sobre algún control del form que tenga un evento de usuario o click o Enter asociado (ya sea con la

propiedad Link, o la propiedad OnClickEvent, o el evento click).

278
Ejemplo: Supongamos que tenemos un web panel donde en un sector del form se puede ingresar usuario y
contraseña para loguearse al sistema.
En el evento donde validamos el usuario y la contraseña (asociado a algún botón o text block), guardamos en
una variable el código de usuario para poder utilizarlo en otro evento. Esto nos permitiría, por ejemplo,
llamar a un objeto que permita visualizar los datos del usuario (por ejemplo un web panel de nombre
“CustomerData”, que recibirá por parámetro el identificador de cliente).
En consecuencia, primero que nada, deberíamos programar lo siguiente en el evento donde validamos el
usuario:

Event ‘Login’
For each
Where CustomerUser = &CustomerUser
If CustomerPassword = &CustomerPassword
&CustomerId = CustomerId
Mensaje.Caption = ‘Bienvenido/a ’+trim(CustomerName)
Else
Mensaje.Caption = ‘La contraseña ingresada no es correcta’
Endif
When none
Mensaje.Caption = ‘El usuario ingresado no existe’
Endfor
Endevent

donde Mensaje es el nombre de un text block que dinámicamente (con la propiedad Caption) cambia de
texto.
Obsérvese que tenemos una tabla CUSTOMER que contiene la info de usuario y password.

Para realizar la llamada al web panel Datos del Cliente (CustomerData), existen varias alternativas, una de
las cuáles sería agregar un botón o una imagen con un evento click asociado (o definir un evento de usuario
y asociárselo al control mediante la propiedad OnClickEvent), entonces el código seria el siguiente:

Event Ver.clic // “ver” es el nombre de la imagen o botón.


HCustomerData.Call(&CustomerId)
Endevent

Repasemos entonces lo que ocurre:

1. En la primera ejecución se disparan los eventos: Start, Refresh y Load y podemos ingresar el usuario y
password en las variables respectivas.
2. Cuando presionamos el botón o text block para validar el login, se dispara el evento Start, se leen las
variables anteriores que están en pantalla, se ejecuta el código del evento Login, donde se asigna a la
variable &CustomerId el código de cliente del usuario correspondiente. Luego ocurren Refresh y Load y la
página se despliega en el Browser.
3. Ahora, ya estando logueados, cuando presionamos la imagen o botón con el evento click asociado, se
dispara el evento Start, se leen las variables que están en pantalla, se ejecuta el evento click y ahí cuando
redireccionamos al Web Panel CustomerData, la variable &CustomerId no tiene valor alguno, ya que la misma
se perdió luego de haber finalizado la ejecución del Web Panel en el punto 2.

Es por esta razón que si queremos disponer del valor de la misma, deberíamos agregar la variable
&CustomerId en el form y la ocultaríamos usando la propiedad Visible (por ejemplo en el evento Start).
Event Start
&CustomerId.Visible = 0
Endevent

Entonces en este caso, cuando el Web Panel ejecute por segunda vez, se dispararán los eventos:
1. Start
2. Se leen las variables del form (en este momento se obtiene el valor de &CustomerId)
3. Se ejecuta el evento click, y por consiguiente se llama al Web Panel con el código de cliente correcto.

Esto es porque no existe un concepto de “memoria local” para los web objects, por lo cual, si en un evento se
usa una variable que se carga en otro evento, entonces esa variable debe estar presente en el form, de
manera que, aprovechando el orden de disparo de los eventos en el POST, se obtenga el valor de la variable.

279
Definición de columnas ocultas
en el grid de un web panel
• Al hacer botón derecho sobre el grid y seleccionar Columns:

•Editar las propiedades de la columna


que se quiere ocultar:

Hay veces que por motivos de presentación, no se desea incluir ciertos atributos o variables como columnas
visibles de un grid, pero se necesita tener sus valores cargados en columnas ocultas.

¿Por qué motivo se puede necesitar definir una columna oculta en el grid de un web panel?

Un grid siempre tiene un archivo temporal asociado.

Cuando en un Web Panel se ejecuta el evento Refresh, se comienza a ejecutar la consulta a la base de datos; a
continuación por cada registro leído que cumpla con las condiciones de filtro definidas, se ejecuta el evento Load
y se cargan los datos de dicho registro, en el archivo temporal asociado al grid.

¿Qué datos de los registros se cargan en el archivo temporal? Es decir, ¿qué columnas contendrá el archivo
temporal? Una columna por cada atributo o variable mostrado en el grid, más una columna por cada atributo o
variable declarado en el grid como columna oculta.

A modo de ejemplo, si en un grid hay 2 columnas visibles con los atributos CustomerName y CustomerBalance y
ninguna columna oculta, el archivo temporal asociado al grid contendrá 2 columnas correspondientes a los
atributos CustomerName y CustomerBalance, respectivamente. Si además de esas 2 columnas visibles, se
declara el atributo CustomerId como columna no visible en el grid, el archivo temporal asociado contendrá 3
columnas correspondientes a los atributos CustomerName, CustomerBalance y CustomerId, respectivamente.

Si en el grid sólo incluimos 2 columnas visibles con los atributos CustomerName y CustomerBalance, en el caso
en que necesitemos en un evento de usuario conocer el valor del atributo CustomerId correspondiente al cliente
de cierta línea seleccionada (para escribir alguna sentencia utilizándolo), no lo tendremos. Para conocer en un
evento de usuario el valor del atributo CustomerId correspondiente a cierta línea seleccionada, tendremos que
incluirlo en el grid ya sea visible o no visible, pero debe estar presente.

Como ejemplo, pensemos en nuestro Web Panel “Work With Customers”: necesitábamos una vez que el usuario
seleccionaba una línea, y presionaba el botón “Update” llamar a la transacción “Customer” enviándole como
parámetro el CustomerId seleccionado. En este caso necesitamos tener el CustomerId en el archivo temporal, ya
sea que esté visible en el grid o no lo esté.

¿Cómo ocultar una columna en un grid?


Para ocultar una columna en un grid, debemos configurar la propiedad Visible del atributo o variable que se
desea ocultar con valor ‘False’. Para ello, debemos hacer clic con el botón derecho del mouse sobre el grid y
seleccionar las columnas (Columns) de la grilla; se abrirá el diálogo mostrado. Luego, habrá que posicionarse en
el atributo o variable que se desee definir como columna oculta, y editar sus propiedades (Properties). Por
último, se debe configurar la propiedad Visible de la columna con valor False.

280
De modo que el motivo por el cual podemos necesitar incluir un atributo o variable como columna
oculta de un grid, es porque necesitemos conocer el valor de ese atributo o variable en un evento
de usuario, pero no deseemos mostrarlo.

Así como los eventos de usuario trabajan con los datos cargados en el archivo temporal asociado al
grid, las condiciones de filtro en cambio, trabajan sobre la tabla física consultada y su tabla
extendida; por lo tanto, al definir condiciones de filtro, se podrán referenciar atributos que
pertenezcan a la tabla física que se consulta y su tabla extendida, sin la necesidad de que dichos
atributos deban estar incluidos en el grid (ni visibles ni ocultos) ni en ninguna otra sección del web
panel.
Por ejemplo, piénsese en el ejemplo que ya presentamos antes:
Event Load
if CustomerBalance > 10000
&type = 'DEBTOR'
else
&type = ''
endif

endevent

Aquí surge la pregunta: como en este evento utilizamos el atributo CustomerBalance para poder
cargar adecuadamente la variable, ¿es necesario colocarlo oculto en el grid? La respuesta es no. En
el evento Load estamos posicionados en un registro de la tabla base. Tenemos a disposición todos
los atributos de esta tabla base y de la extendida, sin necesidad de cargarlos luego en el grid.

281
Comando FOR EACH LINE

GeneXus nos provee el comando For each line para recorrer las
líneas de un grid en un web panel:

for each line [in gridName]


Sentencia 1
……
Sentencia N
endfor

ƒ in NombreGrid: solamente es necesario explicitarlo cuando hay más


de un grid en el form del web panel.
ƒ Sentencia 1, …, Sentencia N: sentencias a ejecutarse para cada
línea recorrida del grid.

282
Comando FOR EACH LINE
Ejemplo

Event ‘Delete’
for each line
if &dlt = ‘Y’
PDelCustomers.call(CustumerId)
endif
endfor
Endevent

A continuación, implementaremos un caso de selección múltiple, una operativa diferente a la presentada en el


caso del web panel Work With Customer, que permitía seleccionar una única línea por vez (selección simple).

La operativa que pretendemos ofrecer en el web panel DelCustomers presentado arriba es la siguiente: luego de
que el usuario haya ingresado un substring para filtrar los clientes y se haya cargado el grid con los clientes que
cumplan dicho filtro, el usuario podrá marcar (con un clic del mouse) qué líneas (clientes) desea eliminar.

En el ejemplo, hemos incluido en el grid del web panel “DelCustomers", una variable de nombre &dlt (definida
como check box), además de los atributos CustomerId, CustomerName, CountryId, CountryName y
CustomerAddress. De esta forma, el usuario seleccionará el check box en los clientes que desea eliminar. A su
vez, tendríamos que tener un botón "Delete" y en el código del evento asociado a dicho botón deberíamos
recorrer el grid y para cada línea seleccionada invocar a un procedimiento que haga la eliminación física de dicho
cliente.

A continuación incluimos el código del procedimiento DelCustomers que recibe en la regla parm el código del
cliente a eliminar (CustomerId).

Reglas:
Parm(CustomerId);

Source:
for each
defined by CustomerName
Delete //se elimina el cliente recibido como parámetro
Endfor

283
Variables en un grid

• Por defecto todas las variables de un grid son


Read-Only

• For each line [in grid], evento click, OnClickEvent:


modifica valor por defecto y todas las variables
del grid pasan a ser de entrada.

• Propiedad: Read Only para que alguna sea de


salida.

Cómo desplegar datos en un grid


Por defecto todo atributo y variable que está dentro de un grid se despliega en ejecución como texto, es decir que
es únicamente de lectura y por consiguiente no puede ser modificado.

Cómo aceptar datos en un grid


Es posible aceptar datos en las variables de un grid dependiendo de la programación de los eventos existentes en
el objeto:
1. Si dentro de un evento del web panel se está utilizando el comando For each line, todas las variables que
están dentro del grid pasan a ser de entrada. Es posible indicar en este caso cuáles son las variables que no
van a poder ser modificadas (Ver más abajo).
2. Si dentro de la fila hay algún control con un evento click asociado (ó evento de usuario especificado en la
propiedad OnClickEvent).

Cómo indicar que una variable no puede ser modificada

1. Para indicar que el valor de una variable en un grid no puede ser modificado, debemos configurar la propiedad
Read Only de la variable con valor ‘True’. Para ello, debemos hacer clic con el botón derecho del mouse
sobre el grid y seleccionar las columnas (Columns) de la grilla. Luego, habrá que posicionarse en la variable
que se desee definir como de sólo lectura, y editar sus propiedades (Properties). Por último, se debe
configurar la propiedad Read Only de la columna con valor True.

2. Utilizando la regla Noaccept()

284
Diferentes tipos de grilla/grid

• Grid estándar: datos repetitivos en formato fijo (línea,


columna)

• Grid Freestyle: datos repetitivos en formato libre

Se dispone de dos tipos de grilla:

• Grilla estándar: la que vimos hasta ahora, en Transacciones y Web Panels


• Grilla Freestyle

Estas grillas, agregan potencia al diseño de aplicaciones web, permitiendo al desarrollador mayor libertad a la
hora del diseño.

285
Grid estándar
Propiedades:

Establece si el grid se
cargará o no por
páginas (paginado).
Indica cantidad de
filas por página.
0 Æ todas (no habrá
paginado) Permite
selección de
línea del grid

Los grids permiten trabajar con datos repetitivos en web panels y transacciones con form HTML. Las columnas de
los grids pueden ser atributos, variables (incluyendo las de tipo bitmap), y siempre tendrán una primera fila que
corresponderá a los títulos de las columnas.

En ejecución, el grid será una tabla HTML.


Para interiorizarse de cada una de las propiedades configurables de un grid, sugerimos acceder al Help de
GeneXus. Aquí solo mencionaremos algunas como ejemplo:

ControlName: Permite indicar el nombre del control. Siempre se le asigna un nombre por defecto.

Class: Clase (del tema asociado al objeto) asociada al control. La propiedad Class solo se encuentra disponible si el
control está en el form de un objeto que tiene un Tema asociado.

BackColorStyle: Permite asignar un estilo al grid. Los estilos disponibles son:


1. None: el grid no tendrá un estilo particular, sino que tendrá el diseño del form o del control que lo contenga.
2. Header: permite especificar un color para el fondo de los títulos del grid y otro para las líneas del mismo. Las
propiedades son LinesBackColor y TitleBackColor.
3. Report: permite especificar un color para el fondo de los títulos y alternar colores para las líneas pares e impares
del grid. Las propiedades son LinesBackColor, LinesBackColorEven y TitleBackColor.
4. Uniform : permite especificar un único color para el fondo del grid(tanto el título como las líneas).

Dependiendo del valor de la propiedad “BackColorStyle”, estarán disponibles otras propiedades adicionales
relacionadas con la configuración de las líneas del grid.

Rows: Esta propiedad permite al usuario indicar la cantidad de registros que va a cargar en el grid. Aplica
únicamente a los grids que tienen tabla base. Si el valor de esta propiedad es 0, se despliegan tantas líneas como
registros resulten de la consulta asociada. El valor por defecto de esta propiedad es 0.

Collapsing:
• AllowCollapsing :True: Permite colapsar el grid en ejecución
• Collapsed :True: Arranca el grid colapsado.
Selection:
• AllowSelection: True: Especifica que es posible seleccionar una línea en la grilla.
• SelectionColor: Seleccionar el color deseado al marcar la fila
• AllowHovering: True: Marca la fila cuando el mouse se posiciona sobre la misma.
• HoveringColor: Seleccionar el color deseado

286
Grid Freestyle

• Permite “formato libre” de los


registros

• Tabla con registros repetitivos

• No posee títulos para las


columnas

• Permite tener más de un tipo de


control en una misma celda grid

• Propiedades de diseño de tablas

• Propiedades del Grid

El grid Freestyle permite al usuario definir el formato de los datos a desplegar de una forma menos
estructurada que el grid estándar.

El grid Freestyle es básicamente una tabla a la que se le pueden insertar los atributos/variables, text blocks,
imágenes, botones, web components, embedded pages, grids freestyle y/o grids que se van a mostrar
posteriormente en la pantalla. Este tipo de grid no posee títulos para las columnas y además permite tener más
de un tipo de control, atributo/variable en una misma celda, proporcionando de esta forma mayor libertad de
diseño. Cabe destacar que el grid Freestyle posee las mismas propiedades mencionadas anteriormente para el
grid estándar.
En este caso para poder visualizar las propiedades hay que seleccionar la tabla donde se encuentran los
atributos/variables.

En el ejemplo presentado arriba queremos mostrar alguna información de los clientes. El atributo CustomerPhoto
se ha incluido en la transacción “Customer” para almacenar la foto de cada cliente (es un atributo de tipo Blob).
Pero no queremos mostrar la información como lo haríamos en un grid estándar, con cada elemento de
información en una columna distinta del grid. Aquí queremos mostrar la foto y debajo el nombre del cliente.

El comportamiento de las variables dentro de un grid Freestyle es análogo al que presentan dentro de un grid
estándar, por lo tanto también quedan de ingreso si existe un For each line o For each line in <grid> dentro de
algún evento, o si se asocia un evento a cualquier control de la fila. Nuevamente este comportamiento puede
modificarse, agregando la regla noaccept o cambiando la propiedad Read Only.

287
Grid Freestyle
Propiedades

Para visualizar las propiedades de un grid Freestyle, hay que seleccionar la tabla del grid, presionar el botón
derecho del mouse y seleccionar la opción ‘Properties’.

Nuevamente, para interiorizarse de cada una de las propiedades configurables de un grid freestyle, sugerimos
acceder al Help de GeneXus. Aquí solo mencionaremos algunas como ejemplo:

Class: Permite modificar la clase de un control, ya sea en tiempo de diseño como en ejecución.
La clase debe pertenecer al tema asociado al objeto que contiene el control. La propiedad Class solo se encuentra
disponible si el control está en el form de un objeto que tiene un Tema asociado.

BackColorStyle: Permite asignar un estilo al grid. Los estilos disponibles son los mismos que para un grid
estándar (ver grid estándar)

Rows: Esta propiedad permite al usuario indicar la cantidad de registros que va a cargar en el grid. Ídem a grid
estándar.

Columns: Esta propiedad permite al usuario indicar cuántas columnas va a tener el Freestyle grid en ejecución.
Si se ingresa un valor distinto de 1, el Freestyle grid va a mostrar los registros en tantas columnas como se haya
especificado en la propiedad. Si el valor de esta propiedad es 0, se despliegan tantas columnas como registros
resulten de la consulta asociada. El valor por defecto de esta propiedad es 1. Esta es propia de este tipo de grids.

Propiedades modificables en ejecución: En tiempo de ejecución se pueden modificar algunas propiedades,


como: visible, backcolor, backColorEven, BackColorOdd, Columns, Rows y

RecordCount: La propiedad RecordCount aplica únicamente a grids que tienen tabla base y retorna un número
mayor o igual a cero representando la cantidad de registros de la tabla base del grid que cumplen las condiciones
de selección. Puede retornar -1 si no existe navegación para la tabla base del grid.

PageCount: La propiedad PageCount devuelve la cantidad de páginas del grid en base a las propiedades Rows y
Columns del mismo. Al igual que la propiedad RecordCount, devuelve –1 si el grid no tiene tabla base. Para el
caso de un grid estándar, también existe esta propiedad dinámica, pero toma en cuenta solo la propiedad Rows.

288
Paginado de grids en Web panels

• Asignarle valor a propiedad Rows para indicar


cantidad de registros a cargar por página.

• Métodos:
• Firstpage
• Nextpage
• Previouspage
• Lastpage (tabla base)
• Gotopage (tabla base)

• Propiedades:
• RecordCount (tabla base)
• PageCount (tabla base)

Descripción
El paginado del grid aplica a grids comunes y freestyle cuya propiedad ‘Rows’ tenga un valor diferente de cero.
Existen algunas diferencias relacionadas con la paginación cuando un grid tiene tabla base o no.
Podemos agregar al web panel “Work With Customer” botones de navegación para el grid (se muestran arriba) y
eventos para realizar el paginado.

Métodos
A continuación se describen los métodos disponibles:

FirstPage:
El método FirstPage lleva al usuario al primer conjunto de registros devueltos.
Los valores devueltos por este método son los siguientes:
0: Operación exitosa
1: No está habilitado el paginado en el grid

NextPage
El método NextPage lleva al usuario al siguiente conjunto de registros.
Los valores devueltos por este método son los siguientes:
0: Operación exitosa
1: No está habilitado el paginado en el grid
2: Ya se encuentra en la última página

PreviousPage
El método PreviousPage lleva al usuario al conjunto anterior de registros.
Los valores devueltos por este método son los siguientes:
0: Operación exitosa
1: No está habilitado el paginado en el grid
2: Ya se encuentra en la primera página

Lastpage
El método LastPage lleva al usuario al último conjunto de registros. Puede ser utilizado únicamente si el grid
tiene tabla base.
Los valores devueltos por este método son los siguientes:
0: Operación exitosa
1: No está habilitado el paginado en el grid
3: El grid no tiene tabla base

289
GoToPage
El método GotoPage(PageNumber) permite acceder en forma directa a un determinado conjunto de registros.
Puede ser utilizado únicamente si el grid tiene tabla base.
Los valores devueltos por este método son los siguientes:
0: Operación exitosa
1: No está habilitado el paginado en el grid

Propiedades
Cada grid dispone de las siguientes propiedades que son utilizadas en la paginación:

RecordCount
La propiedad RecordCount aplica únicamente a grids que tienen tabla base y retorna un número mayor o igual
a cero representando la cantidad de registros de la tabla base del grid que cumplen las condiciones de
selección. Puede retornar -1 si no existe navegación para la tabla base del grid.

PageCount
La propiedad PageCount devuelve la cantidad de páginas del grid en base a las propiedades Rows y Columns
del mismo. Al igual que la propiedad RecordCount, devuelve –1 si el grid no tiene tabla base.

Recomendamos estudiar las consideraciones de eficiencia relacionadas con el uso de estos métodos. Se
aconseja realizar un buen filtrado de datos del grid.

290
Reglas más utilizadas en Web Panels

A diferencia del objeto transacción, en el cual se programa su comportamiento mediante la definición de reglas,
en el objeto web panel la programación es dirigida por eventos.

Son pocas las reglas para web panels, y las mismas permiten definir comportamientos puntuales (hay algunas
reglas más además de las mencionadas, que se pueden consultar en el Help de GeneXus).

Noaccept(&variable);

En los web panels las variables que están en el form fuera de un control grid, ó que están dentro de un grid pero
hay algún evento donde se utiliza el comando For each line, o se le ha asociado evento de usuario o click a algún
atributo o variable del grid, son de entrada por defecto; es decir, el comportamiento por omisión es que en las
mismas pueden ingresarse valores.

Para definir que una variable se presente deshabilitada en un web panel, es decir, no permitiendo ingresos en la
misma, una opción es definir la regla:

noaccept(&variable);

siendo &variable una variable definida en el objeto.

La otra opción que tenemos en GeneXus para que una variable se presente deshabilitada en un web panel es
configurando la propiedad Read Only de la variable con valor ‘True’.
Ver sección Propiedades de la grilla.

Default(&variable, valor);

Asigna un valor por defecto a una variable.

&variable: es una variable definida en el objeto.


valor: puede ser un literal entre comillas, un número o una de las funciones Today(), Date() o SysDate(),
debiendo coincidir el tipo de datos del valor con el tipo de datos de la variable.

El valor por defecto se asignará a la variable al principio de la ejecución del programa.

291
Conceptos fundamentales

• Web panel con a lo sumo un grid:


• Con tabla base
• Sin tabla base

• Web panel con N grids:


• Grids paralelos
• Grids anidados

292
Web Panel “con tabla base”

Web panel es “con tabla base” cuando a lo sumo tiene un grid y


GeneXus puede determinar un for each implícito asociado a él.
Para determinar si un web panel tiene tabla base y en caso
afirmativo cuál será, al momento de especificarlo GeneXus analiza
los atributos incluidos en:

1. form: en la parte fija


2. form: en el grid (visibles o no visibles)
3. el order del grid
4. las condiciones del grid (no en las condiciones globales)
5. los eventos fuera de comandos for each

GeneXus busca la mínima tabla extendida que contenga a todos estos atributos, y la tabla base de dicha mínima
tabla extendida, será la tabla base del for each implícito (es decir, la tabla que navegará el for each), se llamará
tabla base del web panel.

Observar que GeneXus no toma en cuenta para determinar la tabla base de un web panel:

1) los atributos recibidos por parámetro


2) los atributos mencionados en las condiciones globales del web panel

Éstos actúan como filtros una vez determinada la tabla base.

293
Web panel “sin tabla base”

• Un web panel “sin tabla base” es aquel que no


tiene atributos en ninguno de los 5 lugares
puntuados antes.

• Por lo tanto GeneXus no determina un for each


implícito asociado a él.

Los web panels de entrada generalmente son web panels “sin tabla base” por el hecho de que suelen contener
solamente variables; entonces, por no contener atributos en ninguno de los 5 lugares tenidos en cuenta por
GeneXus para determinar la tabla base, son web panels “sin tabla base”.

Además del caso de los web panels de entrada, existen otros casos que ameritan la definición de web panels “sin
tabla base”.

En la próxima página vemos la resolución de una consulta con un web panel sin tabla base.

294
Web panel “sin tabla base”
Ejemplo

• Mostrar para cada cliente el total facturado, pero sólo de los


clientes que tienen facturas.

Event Load
For Each CustomerId
defined by InvoiceDate
&Customer = CustomerName
&Total =0
for Each
&Total +=InvoiceTotal
endfor
Load
endfor
EndEvent

Dado que no mencionamos atributos en ninguno de los 5 lugares tenidos en cuenta por GeneXus para determinar
la tabla base, se trata de un web panel “sin tabla base”.

Por tratarse de un web panel “sin tabla base”, el evento Load se ejecuta una sóla vez; y en el mismo codificamos
explícitamente los accesos con comando for each: cargamos valores en variables y para cargar líneas en el grid
utilizamos el comando LOAD.

Nota: Observar en este caso que todas las columnas definidas en el grid (variables: &Customer y &Total) son
exclusivamente de salida, manteniendo el comportamiento por defecto definido para las variables en un grid. Esto
se debe a que no se está utilizando el comando For each line en ninguno de los eventos del web panel, ni
tampoco hay definido un evento click asociado a un control del grid (ni OnClickEvent).

295
Comando LOAD

• El objetivo del comando LOAD es incluir efectivamente una línea


en un grid.

• Una vez que se haya asignado valores a todas las variables que
sean necesarias, y se desee agregar la línea al grid, deberá
ejecutarse el comando LOAD.

• Solamente se puede especificar el comando LOAD dentro del


evento Load del grid de un web panel.

Si en la codificación del evento Load definimos comandos For each y asignamos valores a las variables en las
iteraciones pero no incluimos el comando LOAD, en tiempo de ejecución estaremos asignando una y otra vez
valores a las variables, pero no se estarán agregado líneas en el grid (solamente quedará una línea en el grid con
los últimos valores cargados en las variables).

296
Web panel “con tabla base”
Ejemplo

• Mostrar para cada cliente el total facturado, pero sólo de los


clientes que tengan facturas.

Event Load
&Total =0
For Each
&Total +=InvoiceTotal
endfor
EndEvent

Atributo Oculto: InvoiceDate


Atributo Order: CustomerId

¡Corte de control!

Atributo en el form del web panel: CustomerName


Atributo oculto (no visible) en el grid: InvoiceDate tabla base del web panel: INVOICE
Atributo order en el grid: CustomerId

Al especificarse este web panel, GeneXus determinará que la tabla base del mismo es INVOICE, significando esto
que el web panel tiene un for each implícito asociado, con tabla base INVOICE.

Recordemos que en los web panels con tabla base, el evento Load se ejecuta N veces y es muy importante el
siguiente concepto:

Si en un web panel con tabla base, se incluye un comando for each en el evento Load, dicho for each
se anidará al for each implícito asociado al web panel. Es decir, el for each definido en el evento Load
no será un for each independiente.

En el web panel del ejemplo, codificamos lo siguiente en su evento Load:


- Inicialización de la variable &Total con valor cero
- For each cuya tabla base será INVOICE (porque el único atributo que contiene es InvoiceTotal) y dentro del
mismo incrementamos la variable &Total con el total de cada factura (InvoiceTotal) recorrida.

De modo que como el web panel tiene tabla base INVOICE, en su evento Load definimos un for each con tabla
base INVOICE también, y definimos un order indicando un criterio de agrupación, hemos implementado en el
web panel con tabla base, un corte de control.

En cambio, si no hubiésemos puesto el atributo InvoiceDate en el grid, el web panel seguiría teniendo tabla base,
pero esta vez sería CUSTOMER. En el evento Load se definió un for each con tabla base INVOICE (ya que dentro
del comando for each solamente se encuentra el atributo InvoiceTotal). El for each del evento Load no será
un for each independiente, sino que se anidará al for each implícito asociado al web panel y
estaremos implementando un join.

GeneXus analizará: ¿existe algún atributo relación entre las tablas “CUSTOMER” e “INVOICE”? Sí, CustomerId.
Por lo tanto, GeneXus definirá el siguiente filtro automático en el for each con tabla base “INVOICE”:
INVOICE.CustomerId = CUSTOMER.CustomerId.

297
En resumen, cuando se ejecute el evento Refresh del web panel, comenzará a ejecutarse el for each implícito
asociado al web panel. Y por cada cliente recorrido, justo antes de cargarse una línea en el grid con el mismo,
se ejecutará el evento Load. Entonces, para ese cliente que se venía recorriendo, se ejecutará el código
definido en el evento Load (es decir, se recorrerán sus facturas, sumando el total facturado del cliente). Al
finalizar la ejecución del evento Load, se cargará la línea en el grid. Obsérvese que aquí no es necesario
especificar comando Load para realizar la carga: se hace automáticamente por el hecho de tener una tabla
asociada a este grid.

En los eventos de usuario sucede algo parecido: cuando se incluye un for each en un evento de usuario, el
mismo no es un for each independiente tampoco, sino que también se anidará al for each implícito asociado al
web panel. GeneXus considerará qué línea del grid está seleccionada al momento de ejecutar el evento de
usuario (recordar Allowselection = True), y para los datos de la misma ejecutará el evento. De modo que si
estando seleccionada determinada línea con un cliente, se ejecuta un evento de usuario, y el mismo tiene un
for each con tabla base “INVOICE”, se recorrerán las facturas del cliente de la línea seleccionada.

Teniendo los conceptos relacionados al objeto web panel bien claros, el analista GeneXus podrá optar si definir
un web panel “con tabla base” o “sin tabla base”.

298
Web panels “con N grids”
Grids paralelos

• Cuando un web panel contiene más de un grid en su form,


GeneXus no determina una única tabla base
asociada al web panel, sino una tabla base asociada
a cada grid.

• Atributos que participan en la determinación


de la tabla base de cada grid:

• Los incluidos en el grid (se tienen en cuenta tanto los


atributos visibles como los no visibles)
• Los referenciados en Order y Conditions locales al grid

A diferencia de lo que sucedía para un web panel con un solo grid, en el caso de múltiples grids los atributos de
la parte fija del web panel no participan en la determinación de la tabla base de ninguno de ellos, pero deberán
pertenecer a la tabla extendida de alguno (para que sea posible inferir sus valores).

De no respetarse esto, al especificar al web panel, se mostrará en el listado de navegación resultante, un


warning advirtiendo de esta situación.

Los atributos utilizados en los eventos del web panel tampoco participan en la determinación de la tabla base de
ninguno de los grids. Los atributos que se incluyan en los eventos fuera de comandos for each, deberán
pertenecer a la tabla extendida de alguno de los grids (al igual que los de la parte fija).

299
Web panels “con N grids”
Grids paralelos - Ejemplos

Una tabla base por grid No busca ni establece relaciones entre ellas

Ejemplo:
Dado que el web panel “View Suppliers And Customers” tiene más de un grid en su form, GeneXus no
determinará una tabla base asociada al web panel, sino que determinará una tabla base para cada grid.

En este ejemplo, no hay definidos ni Order ni Conditions para ninguno de los grids, por lo tanto GeneXus sólo
tendrá en cuenta los atributos incluidos en cada grid para determinar sus tablas bases. La tabla base del grid que
se encuentra arriba en el form será: SUPPLIER y la tabla base del grid que se encuentra abajo en el form será:
CUSTOMER.

Es importante resaltar que GeneXus determina la tabla base de cada grid pero no busca ni establece
relaciones entre las mismas.

Al igual que en el web panel “View Suppliers And Customers”, en el web panel “View Customers And Invoices”,
GeneXus determinará la tabla base del primer grid (que será CUSTOMER) y la tabla base del segundo grid (que
será INVOICE), pero no analizará si hay atributos en común entre ellas, y por ende no definirá filtros automáticos.
Es decir que los grids tendrán asociadas navegaciones independientes o paralelas.

Si bien GeneXus no establece relaciones entre las tablas bases de los grids, el analista podrá definirlas
explícitamente. Primero estudiaremos los eventos en web panels con más de un grid, y a continuación veremos
cómo definir cargas relacionadas.

300
Web panels “con N grids”
Grids paralelos – Eventos y carga

• Un Refresh global
• No se relacionan las cargas
• Refresh independiente de grids.
• Evento Load de cada grid
• Secuencia de Carga:

Refresh
<Grid Control Name1>.Refresh
<Grid Control Name1>.Load N veces si GridControlName1 tiene
<Grid Control Name1>.Load
tabla base. Sino 1.
<Grid Control Name1>.Load
<Grid Control Name2>.Refresh
<Grid Control Name2>.Load N veces si GridControlName2 tiene
<Grid Control Name2>.Load
tabla base. Sino 1.
<Grid Control Name2>.Load

En web panels con más de un grid, existe un evento Refresh global y un evento Refresh particular para
cada grid. El evento Load, no existe global sino sólo a nivel de cada grid.
Los eventos Refresh y Load a nivel de grids, deben referenciar al grid usando la siguiente nomenclatura:

Event <Grid Control Name>.<Refresh | Load>


....
EndEvent

Por ejemplo, si en el web panel “View Suppliers And Customers”, los nombres de los grids son SuppliersGrd y
CustomersGrd respectivamente, para codificar los eventos Refresh y Load a nivel de los grids, la sintaxis deberá
ser:

Event SuppliersGrd.Refresh Event SuppliersGrd.Load


.... ....
EndEvent EndEvent

Event CustomersGrd.Refresh Event CustomersGrd.Load


.... ....
EndEvent EndEvent

Además de estos eventos a nivel de los grids, estará el evento Refresh global:

Event Refresh
....
EndEvent

La carga de los grids en un web panel se realiza para cada grid de forma independiente. Es decir, aún si los datos
que se muestran en ambos grids están relacionados, el especificador no relaciona las cargas.

La carga asociada a los grids de un web panel incluye el evento Refresh, o sea que la secuencia de carga de un
objeto con 2 grids es como se muestra arriba. La ejecución del método Refresh de cualquier grid solo se dispara
par refrescar dicho grid.

El orden en que se cargan los grids es como aparecen en el form: de arriba hacia abajo y de izquierda a derecha.
De esa manera, cada uno de los grids se cargará con los datos correspondientes.

301
Web panels “con N grids”
Grids paralelos - Comandos

• Load: La sintaxis sigue siendo Load, pero debe estar incluido


dentro del evento load asociado al grid que se está queriendo
cargar. Su uso es análogo al caso de un grid (sin tabla base).

– Event Grid1.Load
....
Load
Endevent

• For each line: Debe incluir una referencia al grid de la siguiente


forma:

– For each line IN <Grid Control Name>

El comando LOAD mantiene la misma sintaxis en Web Panels con más de un grid. Se debe incluir al mismo para
hacer cargas de grids sin tabla base, dentro del evento Load de un grid.

302
Web panels “con N grids”
Grids paralelos - Cargas Relacionadas

• Se requiere la implementación de un Web


Panel que muestre en un grid todos los países
(GrdCountries), y cada vez que el usuario
seleccione un país, que se carguen sus clientes
en un segundo grid (GrdCustomers).

SOLUCIÓN

303
Web panels “con N grids”
Múltiples grids - Cargas Relacionadas

AllowSelection = True Event Enter


&CountryId = CountryId
EndEvent // Enter

AMBOS GRIDS CON TABLA BASE

En la solución que se presenta, ambos grids han sido definidos “con tabla base”, así que cada uno de ellos tiene
un for each ímplicito asociado:

• GrdCountries tiene un for each implícito asociado que navega la tabla COUNTRY.
• GrdCustomers tiene un for each implícito asociado que navega la tabla CUSTOMER.

Debemos habilitar la selección de línea por parte del usuario para el grid de países (propiedad AllowSelection del
grid GrdCountries). Cuando el usuario la seleccione, presionará el botón “View customers” para que se
desplieguen en el grid inferior todos los clientes del país seleccionado.

Para ello necesitamos guardar en una variable el país seleccionado, para que luego, cuando se haga la carga del
grid de los clientes, puedan éstos filtrarse mostrando solo los que correspondan al valor almacenado en esa
variable.

Es decir, en el evento asociado al botón, asignaremos a la variable &CountryId el valor del país actual, es decir,
del de la línea seleccionada por el usuario.

Recordemos que cuando se presione este botón, se disparará el evento Start, luego se leerán las variables del
form (en este caso no hay), luego se disparará el evento asociado al botón (allí se le dará valor a la variable) y
luego se dispararán el evento refresh general y a continuación los eventos refresh y load para cargar nuevamente
el grid de países, e inmediatamente el refresh y load para cargar los clientes (y aquí se aplicará el filtro asociado).
Observar que no necesitamos colocar la variable &CustomerId en el form, puesto que es cargada y utilizada en la
misma ejecución en el servidor, por lo que no pierde su valor.

Existen otras soluciones posibles para implementar la carga relacionada en web panels con múltiples grids que
veremos a continuación.

304
Solución Nro. 2: Grid Grdcountries “con tabla base” y GrdCustomers “sin tabla base”

En esta segunda solución, el grid GrdCountries ha sido definido “con tabla base”, así que tiene un for each
ímplicito asociado al mismo, que navega la tabla COUNTRY.
Debemos habilitar la selección de línea por parte del usuario sobre este grid, a través de la propiedad
AllowSelection.
Una vez que el usuario se posiciona en una línea y presiona el botón “View customers” se debe refrescar el
grid GrdCustomers con los clientes que pertenezcan a ese país.
Al igual que antes, en el evento asociado al botón (en nuestro caso es el enter), debemos darle valor a la
variable para que luego pueda ser utilizada para filtrar.

Event Enter
&CountryId = CountryId
endevent

El grid GrdCustomers fue definido “sin tabla base” asociada, por lo cual debemos programar a mano la carga
del grid en el Evento Load.

Event GrdCustomers.Load
For each
where CountryId=&CountryId
&CustomerId=CustomerId
&CustomerName = CustomerName
Load
endfor
Endevent

Como el grid de clientes (GrdCustomers) es “sin tabla base”; cada vez que se ejecute Refresh para el grid
GrdCustomers, a continuación se ejecutará su evento Load una sola vez. Por lo tanto, programamos en el
evento GrdCustomers.Load un for each con tabla base CUSTOMER, filtrando los clientes del país que
habíamos cargado en una variable, y agregamos explícitamente los clientes en el grid (asignando a las
variables &CustomerId y &CustomerName los valores de los atributos CustomerId y &CustomerName
respectivamente y ejecutando el comando Load a continuación para efectuar la carga de cada línea).

Recordemos que cuando el usuario habiendo seleccionado un país en el grid correspondiente, presione el
botón “View customers” se realizará un post al servidor, donde se ejecutará el evento Start, luego se leerán
las variables de pantalla, se ejecutará el código asociado al botón (evento Enter), donde la variable para
filtrar es cargada, y a continuación se dispara el evento Refresh general, y luego el Refresh seguido de N
ocurrencias del evento Load del grid de países (1 por cada país a ser cargado) y finalmente el evento Refresh
del grid de clientes, seguido de un evento Load para ese grid (dentro del cuál se cargan las líneas con el
comando Load).

305
Solución Nro. 3: Ambos grids “sin tabla base”

En esta tercera solución ambos grids han sido definidos “sin tabla base”, por lo cual el evento Load de cada
grid se ejecutará una sóla vez.

Event GrdCountries.Load
For each
&CountryId=CountryId
&CountryName=CountryName
Load
Endfor
Endevent

Event GrdCustomers.Load
For each
where CountryId=&CurrentCountryId
&CustomerId=CustomerId
&CustomerName = CustomerName
Load
endfor
Endevent

En el evento Load correspondiente al grid GrdCountries, programamos explícitamente con comando for each
y comando Load, que se carguen todos los países de la tabla COUNTRY.

Luego codificamos el evento Enter asociado al botón “View customers”:

Event Enter
&currentCountryId = &CountryId
Endevent

En tiempo de ejecución, estas 3 soluciones vistas se comportarán igual. Como se ha mencionado


anteriormente, si se incorporan bien los conceptos teóricos vistos, el analista GeneXus podrá optar al definir
un web panel, si trabajar “con tabla base”, “sin tabla base” o combinando ambas cosas.

306
Web panels “con N grids”
Grids paralelos - Consideraciones

• Acerca de condiciones de filtro


• Es posible especificar condiciones de filtro tanto a nivel global como
a nivel de cada grid.
• Para cada grid se tendrán en cuenta las condiciones globales y las
condiciones locales (es decir: condiciones globales and condiciones
locales).
• Las globales nunca participan de la determinación de la tabla base

• No es posible nombrar a un atributo o variable de un grid


• Ejemplo: GrdCountries.CountryId / GrdCustomers.CountryId
• Sí es posible referirse a CountryId, codificar sus propiedades,
eventos y métodos
• Igualmente no se recomienda tener el mismo atributo / variable en
más de un grid, ya que las codificaciones afectarían a todos

Si por ejemplo el atributo CountryId se agregara en más de un grid y se codificara: CountryId.Visible=0, el


atributo CountryId quedaría invisible en todos los grids.

307
Web panels “con N grids”
Grids Anidados

• Varios niveles de anidación


• Anidaciones paralelas
• Ej: Countries y Customers

ejecución

• Para profundizar en este tema dirigirse


al curso de Desarrollo de aplicaciones
para Internet con GeneXus

Es posible definir grids “anidados” en un web panel.

Los grids anidados consisten en un grid Freestyle al que se puede insertar dentro de una celda otro grid estándar
u otro Freestyle.

Por ejemplo, se quiere tener un web panel que muestre los clientes, pero indentados por país, como se muestra
en la imagen en ejecución arriba a la derecha.

Para ello se define un grid freestyle con el país y dentro de éste se inserta otro grid, en este caso estándar, con
los clientes.

Puede haber grids anidados de varios niveles y puede haber también paralelos. Puede decirse que se está
definiendo un árbol en donde cada nodo es un grid.

Cada grid puede ser un freestyle o un grid estándar, aunque si es estándar no puede tener ninguno anidado. Los
grids estándar sólo pueden ser hojas del árbol de anidación.

No se ahondará en el tema en el presente curso. Podrá hacerlo en el curso de Desarrollo de aplicaciones para
Internet con GeneXus.

308
Web panels
• Tipos de Web panels
• Component
• Master Page
• Web Page

• Propiedad Type

Los objetos web pueden ser definidos con tres tipos diferentes, configurable en la propiedad Type del objeto.
Para un web panel podrá tomar uno de los valores:

•Component: (transacción ó web panel, que a partir de aquí podrá ser incluido en otro web object)
•Master Page
•Web Page (es decir, el objeto será una transacción ó web panel tal como hemos trabajado hasta el momento)

El tipo de web panel ‘Component’ se analiza en detalle en el curso Desarrollo de Aplicaciones para Internet con
GeneXus.

A continuación introduciremos el Web Panel tipo “Master Page” y su utilización.

309
Master Pages

HEADER

CONTENT
M ENU

Tener un look&feel consistente es hoy en día un deber de toda aplicación Web.


Crear y mantener cada página de una aplicación Web asegurando la consistencia con el resto del sitio toma
gran tiempo de programación.

En el ejemplo presentado arriba, hemos capturado una pantalla del sitio de Sony. Navegando entre las distintas
pantallas, nos hemos encontrado que todas ellas tienen un header común, así como un menú a la izquierda de
cada página. Ambas cosas pueden ser visualizadas en la pantalla capturada arriba. Lo que va variando a medida
que uno va navegando por las distintas páginas es lo que aparece a la derecha, y que es el contenido específico
de cada página.

¿Cómo implementar un sitio de estas características? Con el conocimiento que tenemos hasta el momento,
tendríamos que repetir la parte del Layout común a todas las páginas del sitio, así como el comportamiento
común, en cada web panel que implementa cada una de esas páginas. Eso introduce dos problemas
fundamentales: si hubiera que realizar alguna modificación a esas partes comunes a todas las páginas, habrá
que ir página por página a realizarlo. Y relacionado con esto, la posibilidad de olvidarnos de hacerlo en alguna
página, o introducir alguna variación en la misma, degradará la consistencia del sitio.

Las Master Pages proveen una forma de centralizar el layout y el comportamiento común en un solo objeto
y reutilizarlo en todo otro objeto sin tener que programar. Esto significa que la modificación de alguna parte del
layout o del comportamiento común es tan fácil como modificarla en un único objeto y ¡listo!.

Se creará un web panel categorizado como “Master Page” con todo lo que sea el Layout y comportamiento
común a todas las páginas del sitio, y en el mismo se dejará un espacio para cargar en cada oportunidad la
página que corresponda (el contenido variable del sitio). Las páginas web que implementan el contenido
variable, se implementan como Web Panels o Web Transactions comunes y corrientes (es decir de tipo “Web
Page”, ver página anterior), y se asocian a la Master Page, de manera que cada vez que se ejecuten, se carguen
con ese “contexto”.

310
Master Pages

• Propiedad Type = ‘Master Page’

• Permiten definir en un único lugar el layout y


comportamiento común a todas las páginas del sitio
(especifican el marco o contexto de toda página)

Header Marco, contexto de


todas las páginas
Menú del sitio

Lugar donde las


páginas individuales
serán cargadas.

Master Pages

Se crea entonces un objeto Web Panel y se le configura la propiedad Type con el valor ‘Master Page’. En una
misma base de conocimiento se pueden definir tantas Master Pages como se desee.

Una vez que exista al menos una Master Page en la base de conocimiento, puede ser referenciada desde
cualquier web panel o web transaction, en la propiedad MasterPage de estos objetos (que por defecto tiene el
valor “(none)”). El efecto de hacer esto será que al ejecutar estos objetos, se ejecutarán con la master page
asociada, es decir, se cargará la master page, y en el “hueco” es donde se cargará la página individual.

Se profundiza en Master Pages, Web Components, Embedded Pages, en el curso Desarrollo de Aplicaciones para
Internet con GeneXus.

311
PATTERNS

312
Patterns

• Es una herramienta que:


• es parte de GeneXus 9.0 (Tools / Patterns)
• puede ejecutarse también como aplicación independiente

• Básicamente permite aplicar a una Base de Conocimiento


cierto patrón (pattern), y que se generen todos los objetos
GeneXus necesarios para implementar el patrón, para aquellas
instancias que se hayan seleccionado.

• Existe un conjunto de patrones muy útiles ya definidos (al


instalar la herramienta se instalan) para que el desarrollador
pueda aplicarlos a sus Bases de Conocimiento rápidamente,
obteniendo por resultado funcionalidades útiles generadas
automáticamente.

• A su vez la herramienta permite crear patrones nuevos, siendo


esta una tarea un poco más compleja.

Puede obtener los requerimientos de software para instalar la herramienta Patterns de la


siguiente página del Wiki:

http://wiki.gxtechnical.com/wiki/tiki-index.php?page=PatternsInstallation

313
Patterns

Algunos patrones (patterns) existentes:

• WorkWith
• Bill Of Materials
• OAV

Catálogo:

http://wiki.gxtechnical.com/wiki/tiki-index.php?page=Business+Patterns+Catalog

Work With: Genera a partir de una transacción (o para todas aquellas transacciones que se
deseen), todos los objetos necesarios para tener una aplicación web.

Por ejemplo, si se aplica el pattern “Work With” a la transacción “Customer”, se generará un


objeto GeneXus Web Panel que permitirá al usuario final consultar interactivamente los clientes de
la base de datos (se presentará una página web vistosa conteniendo una lista con los clientes de la
tabla CUSTOMER). Este Web Panel se llamará “Work With Customers” y ofrecerá entre tantas
cosas, un link asociado a cada cliente mostrado en la lista, para que mediante su selección se
acceda a otro objeto Web Panel (de nombre “View Customer”) que mostrará todos los datos del
cliente (y su información relacionada como podrían ser facturas, recibos, etc.). El Web Panel “Work
With Customers” además ofrecerá para cada cliente mostrado, la posibilidad de modificar sus datos
(habiéndose agregado automáticamente para ello una invocación a la transacción “Customer”) así
como la posibilidad de eliminar un registro de cliente, o de insertar un cliente nuevo (invocando a
la transacción “Customer” para ello también).
Se podrán configurar variadas propiedades para agregar opciones de filtrado en el Web Panel
“Work With Customers” (por ejemplo para que el usuario final pueda consultar solamente los
clientes de cierto país, o aquellos cuyos nombres se encuentren incluidos en determinado rango);
y también configurando una simple propiedad se podrá incluir en el Web Panel “Work With
Customers” un combo box que ofrezca distintos órdenes posibles, para que el usuario final elija si
desea el resultado de la consulta ordenado por nombre de cliente, por código u otro atributo.

Si el patrón Work With se aplicara también a la transacción “Product” obtendríamos todas estas
funcionalidades para dicha instancia también, así como para cada una de las transacciones que se
deseen.

Bill of materials: Este patrón permite generar a partir de una transacción, otra que representa la
relación compuesto – componente.

OAV: Objeto-Atributo-Valor; Este patrón genera a partir de una transacción otras dos
transacciones que permiten extender la original, con el objetivo de permitir definir atributos en
runtime.

Además, seguimos trabajando en la definición de nuevos patterns. Sugerimos ver catálogo con
lista de patterns, algunos de los cuales ya están implementados y otros sugeridos para una futura
implementación.

314
Patterns

Objetivos y beneficios de la herramienta Patterns:

Gran incremento de productividad y calidad en las aplicaciones

Ejemplo de uso:

Aplicar el pattern Work With a una KB para generar la mayoría de


los web objects necesarios para obtener una aplicación web atractiva
y amigable.

315
Patterns

Utilización de la herramienta
(mostramos ejemplo aplicando pattern WorkWith)

Paso 1

- Contamos con una KB con algunas transacciones (se recomienda tener


prototipo web creado)

- Ejecutamos la herramienta Patterns

- Desde GeneXus (Tools / Patterns, en el modelo de Diseño) Æ la KB


se cerrará automáticamente, y se reabrirá cuando se cierre la
herramienta Patterns

- En forma independiente (GeneXusPatterns.exe) Æ será necesario


cerrar la KB desde GeneXus, ya que para utilizar una KB desde la
herramienta Patterns debe haberse cerrado previamente desde GeneXus
y viceversa

Si se ejecuta la herramienta Patterns en forma independiente de GeneXus, será necesario


seleccionar la KB con la cual se trabajará (para ello la herramienta Patterns ofrece el ítem:
File / Open Knowledge Base).

Cuando se trabaja con la herramienta Patterns con una KB por primera vez, se presenta un
diálogo cuyo título es “Workspace Configuration”. Este diálogo permite configurar algunas
opciones relacionadas a la KB, otras opciones relacionadas al pattern a ser aplicado, y otras
opciones relacionadas a operaciones de GeneXus (impactar, especificar, etc.) que pueden
ejecutarse utilizando la herramienta Patterns. En este punto inicial puede cerrarse este
diálogo si se desea, y posteriormente es posible ingresar al mismo, mediante el ítem Build
/ Configure GX Integration (ver paso 8).

316
Patterns

Paso 2

- Se despliega información de la KB
- Dado que muchos patterns usan TRNs, se muestra una lista de las
TRNs disponibles en el tab KB Explorer:

317
Patterns

Paso 3

- Seleccionar en el combo box mostrado en la toolbar, el


pattern que se desea aplicar:

Se ofrece por defecto la lista de patterns predefinidos.

318
Patterns

Paso 4

- Debemos obtener un ‘instance file’ por cada caso al cual queramos


aplicar el pattern.

- ‘Instance file’ = archivo XML con los datos propios de la instancia

- Si bien es posible crear cada ‘instance file’ de cero, los patterns


suelen proveer una funcionalidad para crear ‘instance files’ por
defecto, y luego poder modificarlos fácilmente.

- En el caso del pattern WorkWith, en el tab KB Explorer:


- Teniendo una TRN (o varias) seleccionada(s) Æ botón
derecho / Generate Instance File
- Doble clic en una TRN

Llamamos proceso de instanciación de un patrón al proceso de aplicar un patrón a una o


varias situaciones (instancias).

En el proceso de instanciación de un patrón las entradas son:

• Instance files: por cada situación o instancia a la cual se quiera aplicar el patrón, habrá
que crear un instance file con la información propia de esa instancia en particular (atributos
a mostrar, etc.). Cada instance file es en definitiva un archivo XML con los datos propios de
la instancia, y tal como se explica en la transparencia, los patterns suelen proveer una
funcionalidad para crear ‘instance files’ por defecto (que luego, de considerarse necesario, se
pueden modificar fácilmente).

• Template files: contienen la implementación del patrón y de su aplicación a las instancias.

El resultado que se obtiene del proceso de instanciación de un patrón (procesando los


instance files y template files) es: un conjunto de objetos GeneXus para ser consolidados
en la KB.

319
Patterns

[Paso 5]

- Los ‘instance files’ pueden editarse en el panel derecho


- Dos opciones para hacerlo: Tree View / XML View:

Cada ‘instance file’ es un archivo XML con estructura jerárquica, conteniendo cada uno de sus
nodos un conjunto de propiedades.

La herramienta patterns ofrece 2 editores para editar cada ‘instance file’ en el panel derecho:
el editor XML View y el editor Tree View.

El editor XML View permite editar los instance file directamente en su formato XML. Por su
parte el editor Tree View es mucho más amigable, sencillo de usar, y con interfaz en alto
nivel que provee mayor funcionalidad para ayudar en el proceso de edición. Por todo esto el
editor Tree View es el más usado y es el recomendado para usuarios no avanzados.

320
Patterns

Paso 6

- Los ‘instance files’ se deben grabar. Para ello, bajo el ítem


Instance se ofrecen las opciones Save y Save All.

- Los ‘instance files’ que no se han salvado aún se visualizan


con nombre: <TRN Name.Pattern>*.

- Una vez salvados se visualizan con el nombre:


TRN Name.Pattern (ej: Country.WorkWith).

Save – salva el ‘instance file’ con el que se esté trabajando.

Los ‘instance files’ que no se han salvado aún se visualizan con nombre: <TRN
Name.Pattern>*. Y una vez salvados se visualizan con el nombre: TRN Name.Pattern(por
ejemplo: Country.WorkWith).

¿Dónde se almacenan físicamente los ‘instance files’? En el subdirectorio Templates bajo el


directorio de la KB.

Seleccionando el tab Folder Explorer se pueden visualizar estos archivos:

Save All – Salva todos los ‘instance files’ (si ya existen, pregunta si reemplazar).

Es importante tener en cuenta que si se generan los ‘instance files’ por defecto nuevamente a
partir de las transacciones, serán sobrescritos.

321
Patterns

Paso 7

- Una vez creados y editados los ‘instance files’, el siguiente paso


es que la herramienta genere los objetos GeneXus que
implementan el pattern para las instancias.

- Opciones: Se genera en
- Build / Apply Pattern KBPath\Templates\Import, un
- Build / Apply and Consolidate archivo <TRNName>.xpz.xml
por cada ‘instance file’

- Posibilidad de que se consoliden automáticamente a


continuación o que lo haga después (desde la KB) el
desarrollador GeneXus.

Mediante botón derecho también es posible ejecutar estas acciones: es decir, estando posicionado en
el tab Folder Explorer, luego de haber seleccionado los instance files (.workwith files), se pueden
ejecutar las opciones Apply Pattern o Apply and Consolidate.

Nota: También es posible seleccionar una TRN o grupo de TRNs estando posicionado en el tab KB
Explorer, y mediante botón derecho ejecutar la opción Generate, Apply and Consolidate. Pero es
importante entender que seleccionando esta opción, se generarán los instance files por defecto
nuevamente para las TRNs seleccionadas, y luego de ello, se generarán los archivos
<TRNName>.xpz.xml correspondientes y se consolidarán en la KB. Si se está seguro que los instance
files por defecto no necesitan ser editados, es posible seleccionar esta opción directamente.

A su vez es importante saber que si se selecciona la opción Apply and Consolidate se efectuarán
también las siguientes acciones:

• Se configurará como default theme de la aplicación al theme Fantastic.


• El directorio Images será copiado automáticamente bajo el directorio KBPath\Templates.
• La model property "Base image path" del modelo de diseño se configurará con el valor anterior.

322
Patterns
Paso 8

- Desde la herramienta Patterns, es posible ejecutar las acciones


que se realizan también desde GeneXus: Impactar el modelo,
Especificar, Generar, Compilar y Ejecutar.

- Diálogo donde se configura:

Para hacer más fácil todo el proceso, desde la herramienta Patterns, es posible ejecutar las acciones que
se realizan con GeneXus: Impactar el modelo, Especificar, Generar, Compilar y Ejecutar.

Estas acciones se configuran en el diálogo “Workspace Configuration” que se abre en el momento de


abrir la KB con la herramienta Patterns, o seleccionando luego la opción Build / Configure GX
Integration.

Opciones disponibles en el diálogo

Model – Muestra los modelos definidos en la KB, y permite seleccionar uno de ellos (se requiere tener
los modelos creados y la creación de sus tablas hechas).

Apply and Consolidate – Al seleccionar la opción Apply and Consolidate, es posible ejecutar más
acciones además de la generación de objetos y consolidación. Este combo ofrece las siguientes
posibilidades:

• Consolidate Only (Apply and Consolidate)


• Impact Model (Apply and Consolidate + Impact Model)
• Impact, Specify (Apply and Consolidate + Impact + Specify)
• Impact, Specify, Compile (Apply and Consolidate + Impact + Specify + Compile)
• Impact, Specify, Compile, Run (Apply and Consolidate + Impact + Specify + Compile + Run)

GeneXus Version - La versión de GeneXus correspondiente a la KB se detecta automáticamente (puede


ser 8.0 o 9.0). El modelo se especificará / generará con dicha versión.

323
Build Actions- Permite seleccionar qué objetos deben especificarse y generarse al seleccionar Specify
and Generate. Las opciones disponibles son:

- Build All
- Build Pending (updated since last specification)
− Specify Consolidated Objects

Specification – Permite seleccionar el tipo de especificación / generación a ser ejecutado al seleccionar


Specify and Generate. Las opciones disponibles son:

− Full Specification
− Check Specification
− Force Generation

Run Command – En esta opción se debe indicar la URL que se ejecutará si se seleccionó la opción
Impact, Specify, Compile, Run.

Se debe configurar:

para aplicaciones .NET Æ Run Command = http://localhost/services/hhome.aspx


para aplicaciones Java Æ o http://localhost:8080/servlet/hhome

Nota: Vale aclarar que “home” es el nombre de un web panel generado por el patrón WorkWith; el
mismo ofrece links a todos los web panels WorkWith generados (y la letra h que antecede a su nombre
en la invocación, corresponde al prefijo que se agrega a todo objeto web panel main o ejecutable que se
invoca).

324
Patterns

Resultado en ejecución

325
Patterns

¿Cómo modificar los objetos generados?

• Mediante la herramienta Patterns


1. Modificando las propiedades de las instancias (en la
medida que ofrezcan lo que se desea)
2. Modificando el pattern, personalizándolo para que ofrezca
configurar propiedades que implementen las necesidades

• Mediante GeneXus
Modificando los objetos GeneXus generados (Desventaja:
en caso de querer volver a generarlos con Patterns, se
regeneran los objetos, perdiendo los cambios hechos a
los mismos)

326
Patterns

¿Cómo modificar las propiedades de las instancias?


Archivo de instancia

Editor Tree View

Propiedades configurables

Son muchas las propiedades que se ofrecen en los archivos de instancia correspondientes al pattern
WorkWith para personalizar el comportamiento de los objetos que se generarán. A continuación
describimos algunas de ellas.

El nodo Selection ofrece las propiedades relacionadas al web panel WorkWith que se generará para
la instancia. Sus sub-nodos son:

Modes
Este nodo permite definir en cuáles modos se ofrecerá invocar a la transacción. Las posibilidades y
sus valores por defecto son:

Insert: True
Update: True
Delete: True
Display: False
Export: True (exportación a planilla excel)

Para casa modo podrá especificarse una condición. Se proveen las siguientes propiedades para ese
propósito:

Insert Condition
Update Condition
Delete Condition
Display Condition

Si se define una condición asociada a un modo, la invocación para ese modo solo se habilitará si la
evaluación de la condición es verdadera (Ejemplo: CountryId=10).

327
Attributes

Este nodo permite definir cuáles atributos se desean mostrar en el grid (y para cada atributo, se
pueden personalizar varias propiedades).

Orders

Es posible ofrecer al usuario final varios órdenes posibles para ver el resultado de la consulta (es
decir, las líneas mostrando los datos en el grid). Utilizando el botón derecho del mouse se puede
definir un nuevo orden (su nombre y composición). Cada orden puede estar compuesto por varios
atributos (pudiendo indicar para cada un de ellos si se desea orden ascendente o descendente). Se
presentará un combobox en el web panel WorkWith ofreciendo todos los órdenes posibles de
seleccionar, para que el usuario final elija uno y los datos se presenten en el grid ordenados por el
mismo.

Filters

Permiten definir condiciones de filtro para que se muestren en el grid solo los registros que cumplan
con las mismas.

El nodo View por su parte, ofrece las propiedades relacionadas al web panel View que se generará
para la instancia. El web panel View muestra toda la información de un registro, que fue
seleccionado en el grid del web panel WorkWith (la información del registro es mostrada en una
solapa de un tab control, y además hay una solapa con un grid por cada tabla directamente
subordinada, para mostrar la información relacionada).

328
Observaciones

Al crear cada ‘instance file’ por default, podemos observar que las distintas propiedades del ‘instance file’
se inicializan con valores por default. Dichos valores por default para las propiedades, se especifican en
un archivo denominado NombrePattern.config (en nuestro caso WorkWith.config).

El archivo NombrePattern.config se encuentra donde está instalada la herramienta Patterns, bajo el


subdirectorio del Pattern particular que se esté utilizando. Para el caso del pattern WorkWith estará bajo:
Patterns\WorkWith (recordar que bajo el directorio de la herramienta Patterns, existe un subdirectorio
por cada patrón predefinido (WorkWith, Bill of Materials, OAV) así como deberá haber un subdirectorio
por cada patrón definido por el usuario.

Si deseamos tener un archivo de configuración NombrePattern.config por cada KB, debemos copiar este
archivo al directorio Templates que se crea bajo la KB al usar la herramienta Patterns; así la herramienta
Patterns utilizará dicho archivo ubicado en la KB para inicializar las propiedades de los ‘instance files’ que
se creen. Si la herramienta Patterns no encuentra el directorio Templates con este archivo bajo la KB,
utilizará el archivo NombrePattern.Config (en nuestro ejemplo WorkWith.config) ubicado en el directorio
Patterns\WorkWith.

El WorkWith.Config permite configurar algunos aspectos generales que aplicarán a todos los objetos. Por
ejemplo, las master pages a ser utilizadas por los objetos web, los web components utilizados como
header y footer, etc..

Para editar este archivo de configuración, la herramienta Patterns ofrece el ítem: Tools/Change
Pattern Configuration.

Ejemplo:

El pattern WorkWith además de


generar objetos nuevos, hace algunas
modificaciones a las trn’s existentes
(regla parm para recibir parámetros
del WorkWith
correspondiente, etc.).
En el archivo WorkWith.config, el
valor configurado por default para
esta propiedad es “Only rules and
events”.

Propiedad UpdateTransaction, ofrece los siguientes valores:

• Do not update: La trn no será modificada (web form, reglas y eventos serán mantenidos)
• Only rules and events (default value): Solo las reglas y eventos son modificados, pero no el web
form.
• Apply WW Style: la primera vez que el pattern sea aplicado, el comportamiento será el mismo que si
se hubiese seleccionado el valor Create Default. A partir de la segunda vez, el header, footer y botones
del form web serán modificados, pero no la Data Area. Los eventos y reglas también serán modificados.
• Create default: el web form, reglas y eventos serán modificados.

329
Ejemplo: Work With desde GeneXus

En el capítulo donde estudiamos el objeto web panel, implementamos manualmente un web panel “Work
With Customer” donde agregamos variables al grid para dar un mensaje de cliente moroso si su saldo
superaba los $10.000 y para contar la cantidad de facturas que se le realizaron, respectivamente. Habíamos
asimismo programado el evento Load para lograr lo anterior.

Todo esto puede ser codificado automáticamente por el Pattern “Work With”, si ud. agrega en la lista de
atributos del archivo de instancia correspondiente, las dos variables anteriores, y asociadas a las mismas en
el “LoadCode” el código asociado, como puede ver en la siguiente página.

330
…desde Patterns

Se definen las variables &type y &quantity haciendo botón derecho sobre el nodo Attributes y luego,
posicionándose en &type, en la ventana de la derecha, en la opción LoadCode, se ingresa el código que se
quiere ejecutar para esa variable, cuando se cargue la línea.

331
Ejemplo: Work With desde Genexus

Acciones sobre
el cliente
seleccionado

Asimismo, en el web panel Work With Customer que habíamos implementado manualmente antes, agregamos
botones para poder realizar las acciones típicas de un Work With (alta, baja, modificación, y vista de registro).

Asimismo, tuvimos que modificar la transacción “Customer” para recibir como parámetro el modo y clave, como
se puede recordar en la siguiente página.

332
Ejemplo: “Work With Customer”
desde GeneXus

Event ‘Insert’ En las reglas de la


Tcustomer.call(‘INS’, 0) transacción “Customer”:
Endevent
Event ‘Update’ Parm(&Mode, &CustomerId );
Tcustomer.call(‘UPD’, CustomerId)
Endevent CustomerId = &CustomerId if not
Event ‘Delete’ &CustomerId.IsEmpty();
Tcustomer.call(‘DLT’, CustomerId)
Endevent
Event ‘View’
Tcustomer.call(‘DSP’, CustomerId)
Endevent

Pattern crea el enumerado:

El Pattern Work With no implementa estas acciones de la misma manera. Esta herramienta crea un dominio
enumerado para los valores que puede tomar la variable &Mode y la implementación de las acciones en el web
panel Work With Customer que realiza es como se muestra en las páginas siguientes.

Pattern modificará la transacción “Customer” para agregar exactamente las mismas reglas que nosotros
escribimos en forma manual.

333
…desde Patterns

Event Start
NewControl.Link = Link(TCustomer, TrnMode.Insert, CustomerId.SetEmpty())
&Update = LoadBitmap(!"images/edit.gif")
Endevent
Event Load
&Update.Link = Link(TCustomer, TrnMode.Update, CustomerId)
Endevent
‘UPD’

Aquí podemos ver otra forma de selección de una línea del grid. En el caso anterior utilizamos la propiedad
AllowSelection del grid para habilitar la selección de una línea en ejecución por parte del usuario.

Otra opción es la que implementa el Pattern “Work With” mediante la utilización de variables (en este caso de
tipo bitmap, &update y &delete, cargadas con la imagen correspondiente en el evento Start) a las que en el
evento Load del grid se les asigna la propiedad Link para determinar el identificador de cliente que se le
envía a la transacción Customer en cada línea cargada (en el cuadro de arriba solo se incluyó el código para
cargar la variable &update, pues para la variable &delete es análogo).

Nota: Obsérvese que delante del path relativo a la imagen aparece el símbolo “!”. No le dé importancia. Se
utiliza para la herramienta de Traducción (Application Localization), para evitar que el literal que le siga sea
traducido.

Como ya mencionamos la herramienta Patterns define el tipo enumerado TrnMode y es por ello que no
aparece ‘UPD’ directamente sino su descripción TrnMode.Update.

334
USO Y RECOMENDACIONES EN EL
USO DE SUBTIPOS

335
Definición de subtipos

• Las relaciones entre atributos GeneXus se establecen a través de sus


nombres.

• Mediante subtipos se puede establecer que dos atributos que se


llaman diferente corresponden al mismo concepto.

• Se dice que el atributo A es subtipo del atributo B si se cumple una


dependencia funcional, o sea para cada valor de A existe un solo
valor de B y el valor de A es igual al valor de B.

336
Casos de uso de subtipos

• Algunos casos:
• Múltiples referencias
• Especialización de un nivel (relación 1-1)
• Subtipos recursivos
• Evitar controles de integridad referencial

337
A. Múltiples referencias

• Atributos conceptualmente iguales que cumplen


roles diferentes (ej.: reservas de pasajes).

Transacción “Reservation” Transacción “City”


PROBLEMA ReservationId* CityId *
Atributos CityId origen CityName
con
el mismo CityId destino
nombre
SOLUCION

Transacción “Reservation” Transacción “City”


ReservationId* subtipo CityId*
Desaparece ReservationCityFromId subtipo CityName
el problema ReservationCityToId

Realidad a representar/diseñar:
en cada reserva hay dos ciudades involucradas, las cuales cumplen roles diferentes.
El rol de una de las ciudades es el de ser la “ciudad de partida” (ciudad origen) y el rol de la otra es
el de “ciudad de arribo” (ciudad destino).
El dominio de ambas ciudades es el mismo, el de la tabla CITY.

La forma de representar que tanto el “origen” como el “destino” son ciudades de la tabla CITY, es
diseñando la transacción “Reservation” en la forma mencionada inicialmente en la transparencia.
Sin embargo, no es posible que en la estructura de una transacción figure el mismo atributo más de
una vez, pues no habría manera de identificarlos.
SOLUCIÓN: llamar a las dos ciudades de la reserva con diferentes nombres de atributos.
Cualquiera de las siguientes opciones es válida. Elegimos la 3era por mayor claridad.
Opción 1) ReservationCityFromId Å ciudad origen
CityId Å ciudad destino (mismo nombre que la PK de CITY)
Opción 2) CityId Å ciudad origen (mismo nombre que la PK de CITY)
ReservationCityToId Å ciudad destino
Opción 3) ReservationCityFromId Å ciudad origen
ReservationCityToId Å ciudad destino
El problema es que al poner por ejemplo ReservationCityFromId en lugar de CityId, GeneXus deja
de inferir que ReservationCityFromId corresponde al código de una ciudad de la tabla de CITY.
¿Cómo hacemos para relacionarlos, siendo que tienen diferente nombre de atributo? ver
respuesta en próxima hoja …

338
Para estos casos GeneXus provee los SUBTIPOS, que permiten definir que dos atributos que se
llaman diferente corresponden al mismo concepto.

En nuestro ejemplo, si definimos al atributo ReservationCityFromId como subtipo de CityId,


estamos especificando que si bien ReservationCityFromId y CityId son diferentes atributos (de
nombres diferentes), corresponden, no obstante, al mismo concepto (una ciudad de la tabla CITY).

Al establecer que un atributo es subtipo de otro, estamos estableciendo una dependencia funcional
entre ellos.

Si ReservationCityFromId es subtipo de CityId, entonces decimos que CityId es el supertipo de


ReservationCityFromId.

¾Los atributos que se encuentran en una relación subtipo-supertipo comparten la misma definición
(tipo de datos).

¾Se realizan los controles de integridad referencial automáticamente.

¾La tabla extendida que se obtiene con la definición del subtipo, es la misma que se obtendría si se
utilizara directamente el supertipo.

339
A. Múltiples referencias

• Con la definición de subtipos:


• se establece la siguiente relación:
ReservationCityFromId
RESERVATION CITY
ReservationCityToId
• Se hacen además automáticamente los controles de Integridad Referencial
(IR) entre ambas tablas cuando se utilizan sus correspondientes
transacciones.

• Los atributos secundarios de CITY:


pertenecen a la tabla extendida de RESERVATION, pero al existir doble
referencia no se pueden utilizar directamente desde RESERVATION
(ambigüedad de caminos y con valores de ciudades diferentes).

Solución Î definir también subtipos para los atributos secundarios de


CITY, e incluirlos en c/u de los grupos de subtipos.

IMPORTANTE:
Notar que este caso de múltiples referencias puede darse tanto:
• en la tabla base (*)
• como en la tabla extendida

(*) es el caso del ejemplo, en el que en la propia tabla (RESERVATION) hay más de una
referencia a otra tabla (CITY) y con valores diferentes.

RESUMIENDO:
siempre que desde una tabla se accede a otra que está en su tabla extendida por “más de un
camino” y con “valores diferentes”, es necesario definir SUBTIPOS, para poder llamarle
diferente a los atributos y haciéndose automáticamente todos los controles de integridad
referencial.
Una vez definidos los grupos de subtipos que sean necesarios, la forma de indicarle a GeneXus cuál
de los caminos debe tomar para acceder a la tabla destino, es mencionando los nombres de
atributos que correspondan. Ej.: mencionar ReservationCityFromName si lo que se necesita en ese
momento es el nombre de la ciudad origen, o mencionar ReservationCityToName si lo que se
necesita es el nombre de la ciudad destino.

340
A. Múltiples referencias

Nombre de c/grupo
de subtipos.

Transacción “Reservation” Tabla “Reservation”


ReservationId* ReservationId*
ReservationCityFromId ReservationCityFromId FK
ReservationCityFromName Inferido ReservationCityToId FK
ReservationCityToId
ReservationCityToName Inferido

Con el grupo estamos indicando que los atributos pertenecientes al mismo grupo de subtipos, están
relacionados. Por ej., en nuestro ejemplo, GeneXus sabrá que el atributo ReservationCityToName
será inferido a través del atributo ReservationCityToId (y no a través del ReservationCityFromId).
Esto es por pertenecer ambos al mismo grupo (al de nombre ReservationCityTo).
Cuando el usuario digite un valor sobre ReservationCityToId, no solo se va a hacer
automáticamente el control de integridad referencial (que exista un ciudad con ese código en la
tabla CITY), sino que se va a inferir en ReservationCityToName el nombre correspondiente a ese
código de ciudad.

IMPORTANTE: Todo grupo de subtipos, debe contener un atributo o conjunto de atributos, cuyos
supertipos, juntos, correspondan a la clave primaria de una tabla del modelo. Estos atributos
aparecen en la ventana de edición del grupo con la categoría “class: Primary”.
Los demás atributos del grupo deberán ser de tipo “Inferred”, es decir, deberán poder inferirse a
través de esa clave.
En caso contrario estará mal definido el grupo.

En nuestro caso, por ej. en el grupo ReservationCityTo:


- el atributo ReservationCityToId es el único que aparece como “Primary” y podemos
comprobar que existe una tabla (CITY) cuya clave primaria es el supertipo de
ReservationCityToId (CityId).
- además, el resto de los atributos de ese grupo (ReservationCityToName) aparece como
“Inferred”.
con lo cual, comprobamos que este grupo está bien definido.

341
A. Múltiples referencias en la tabla extendida

• COUNTRY pertenece a la tabla extendida de SALE por caminos diferentes y


con códigos de país diferentes.
un camino desde SALE a COUNTRY: a través del País del cliente (CountryId)

CUSTOMER
SALE COUNTRY

SELLER
Otro camino desde SALE a COUNTRY: a través del País del vendedor (CountryId)

¿qué país imprime?


¿cuál de los caminos toma?
Layout Hay una ambigüedad en el modelo de datos!

Si quisiéramos por ejemplo listar las ventas (SALE), y de c/u de ellas mostrar los datos del cliente
(nombre, país, etc.) y del vendedor (nombre, país, etc.):
• necesitamos un for each con tabla base SALE y acceder a través de su extendida a las
tablas CUSTOMER, SELLER y COUNTRY para listar los atributos secundarios del cliente,
vendedor y país respectivamente.
Problema:
Los atributos de nombre CountryId, CountryName y todos los de la tabla
extendida de COUNTRY pertenecen a la tabla extendida de SALE por dos caminos
diferentes: 1) a través del país del cliente y 2) a través del país del vendedor.

Solución:
Debemos diferenciarlos, llamarlos con diferente nombre de atributo pero
queriendo que se sigan representando todas las relaciones y haciéndose
automáticamente todos los controles de integridad referencial.

342
A. Múltiples referencias en la tabla extendida
- Solución -

Cuando queremos el país del cliente de la venta: SaleCustomerCountryName

rId
sto
me CUSTOMER
al eCu
S
SALE COUNTRY
Sal
eS e
ller
Id

SELLER
Cuando queremos el país del vendedor de la venta: SaleSellerCustomerName

Una vez definidos los dos grupos de subtipos que se muestran en la figura, y haciendo el cambio
correspondiente en la estructura de la transacción Sale, queda resuelta la ambigüedad en el modelo
de datos!

Atributos almacenados
en la tabla SALE

Atributos inferidos

343
A. Múltiples referencias en la tabla extendida
- Solución -

SaleCustomerCountryId SaleSellerCountryId

Problema resuelto!

Una vez definidos los subtipos, tenemos que recordar usar el nombre de atributo que corresponda a
lo que queremos acceder. Por ejemplo, en todos aquellos objetos GeneXus en los cuales queramos
acceder al código o al nombre del país del cliente de la venta debemos usar los atributos
SaleCustomerCountryId y SaleCustomerCountryName respectivamente.

344
B. Especialización de atributos
Ej.: Sistema para una Universidad …

PERSON datos comunes a profesores


y estudiantes

datos propios TEACHER STUDENT datos propios de


los estudiantes
de los profesores

Sistema Sistema
Teachers Students

Caso de subtipos “Especialización de atributos”:

Cuando se esta modelando una categorización.

Generalmente es utilizada, cuando un objeto del negocio comparte todas las características de otro
objeto, pero agrega algunas más. La diferencia puede estar tanto en las propiedades, como en el
comportamiento que tendrá.

Ejemplo “Sistema para una Universidad”:

En este ejemplo, el profesor y el alumno tienen roles y comportamientos claramente


diferenciados. Por ejemplo, el profesor tendrá cursos asignados, sueldo, etc. El alumno estará
inscripto a un curso, tendrá asignados pagos, asistencia, escolaridad, etc.

Estamos frente a un caso en el que los roles y el tratamiento de las entidades de la categorización
están claramente diferenciados.

Tanto los estudiantes como los docentes comparten información común (ambos tienen un nombre,
una dirección, etc) pero también tienen información que difiere, que es propia de c/u de ellos.

Para representar esta realidad, se crean las tres transacciones: “Person”, “Teacher” y “Student”.

En la transacción “Person” figura la información común. Para representar que tanto los estudiantes
como los docentes son personas, se utilizan los subtipos.

Al definir que el identificador de “Teacher” es subtipo del identificador de “Person” estamos


estableciendo esta relación.

Cada vez que se inserte un registro en la tabla TEACHER a través de su transacción, se realizará el
chequeo de integridad referencial contra “Person”. Asimismo, cada vez que se intente eliminar un
registro de “Person”, se verificará primeramente que no exista ningún registro en la tabla TEACHER
(ni en STUDENT) con el mismo valor en la clave primaria.

345
B. Especialización de atributos
Transacciones:
“Person” “Teacher” “Student”
PersonId* TeacherId* StudentId*
PersonName TeacherName StudentName
PersonAddress TeacherAddress StudentAddress
TeacherSalary StudentAverage

• Se crean 3 tablas físicas.


• Se realizan chequeos de IR contra la tabla PERSON.

La transacción “Teacher” tiene asociada una tabla que contendrá físicamente sólo dos atributos:
TeacherId y TeacherSalary.

Al ser TeacherId identificador de la transacción, será la clave primaria de la tabla asociada. Además,
al ser un subtipo de PersonId, será una clave foránea a la tabla PERSON. Por lo tanto, se harán los
chequeos de integridad referencial correspondientes.

Los atributos TeacherName y TeacherAddress son subtipos de PersonName y de PersonAddress


respectivamente y están agrupados con TeacherId, por lo que serán inferidos de la tabla PERSON, a
través de la clave foránea TeacherId (no están almacenados en la tabla TEACHER).

346
C. Subtipos recursivos

• Ejemplo: Employee-Manager

Tabla EMPLOYEE
EmployeeId*
EmployeeName
EmployeeIsManagerFlag
EmployeeManagerId
FK
Error(‘Debe ingresar un gerente para el empleado’)
if EmployeeIsManagerFlag=‘N’ and EmployeeManagerId.isnull();

Es posible tener una tabla subordinada a sí misma definiendo subtipos.

Este tipo de subtipos se utiliza para modelar las relaciones recursivas. Por ejemplo, la relación entre
Empleado y Gerente:
- cada empleado tiene un gerente. Un gerente, a su vez, es un empleado (aquí está la recursión).
- un gerente puede tener varios empleados a su cargo

Si además la realidad a representar es que “sólo los empleados que no son gerentes tienen un
gerente”, entonces, cuando se ingresan los datos hay que realizar los siguientes controles:
-cuando se ingresan los gerentes, hay que permitir dejar en nulo el atributo EmployeeManagerId.
Para esto, cambiamos a ‘Yes’ la columna Nulls del atributo EmployeeManagerId, el cual es FK en la
tabla EMPLOYEE.
que todo empleado que no es gerente, tenga un gerente. Este control lo hacemos con la regla error
que se muestra en la figura.

El atributo EmployeeManagerName no queda almacenado en la tabla EMPLOYEE, se infiere luego de


ingresar un valor en EmployeeManagerId.

Por ser EmployeeManagerId subtipo de EmployeeId, se realizan automáticamente los controles de


integridad referencial de la tabla consigo misma. Esto se puede ver en la navegación de la
transacción, como se muestra en la siguiente página.

347
C. Subtipos recursivos
• Listado de navegación detallado:

348
D. Evitar controles de integridad referencial

Purchase Order History Supplier

SupplierId* SupplierId*
SupplierName SupplierName
ProductId* …
ProductDescription
PurchaseOrderHistoryQuantity

Purchase Order Product


PurchaseOrderId* ProductId*
SupplierId ProductDescription
ProductId …
PurchaseOrderQuantity

En la figura indicamos con flechas los controles que queremos que GeneXus realice
automáticamente.

349
D. Evitar controles de integridad referencial

Purchase Order History Supplier

SupplierId* SupplierId*
SupplierName SupplierName
ProductId* …
ProductDescription
PurchaseOrderHistoryQuantity

Purchase Order Product


PurchaseOrderId* ProductId*
SupplierId ProductDescription
ProductId …
PurchaseOrderQuantity

Pero los controles que en realidad realiza GeneXus en forma automática son los que se muestran en
esta figura.

En este ejemplo, no queremos que se realice el chequeo que va de la tabla


PURCHASEORDER sobre la tabla PURCHASEORDERHISTORY, porque la orden de compra se
realiza antes de que efectivamente se realice la compra, por lo cuál, si es la primera compra que se
realiza para ese proveedor de ese producto, lógicamente los datos de esa compra no estarán aún
dados de alta en el histórico.

Observar además que en la transacción “Purchase Order” no se está haciendo automáticamente


un control que sí queremos que se haga: el control de proveedor contra la tabla SUPPLIER y de
producto contra PRODUCT. Estos controles no se están haciendo porque al efectuarse el chequeo
contra PURCHASEORDERHISTORY, se supone que ya en esa transacción se chequeó que el producto
fuera válido y que el proveedor fuera válido.

350
D. Evitar controles de integridad referencial

Purchase Order History

SupplierId*
SupplierName
Solución PurchaseOrderHistoryProductId*
PurchaseOrderHistoryProductDescription
PurchaseOrderHistoryQuantity

¿Cómo evitar que GeneXus controle que el proveedor y producto de una orden de compra
existan en la tabla de histórico de compras?
Alcanza con cambiarle el nombre de atributo al código de producto (o al código de proveedor) en el
histórico de compras, pero conservando el concepto al que corresponde (es decir, definiéndolo como
subtipo de producto o de proveedor según corresponda).

La solución elegida fue la de cambiarle el nombre al atributo que corresponde al producto en el


histórico de compras, le cambiamos a PurchaseOrderHistoryProductId. Además, definimos a dicho
atributo como subtipo de ProductId, para seguir manteniendo la relación que hay entre “Purchase
Order History” y “Product”.

351
D. Evitar controles de integridad referencial

Purchase Order History Supplier

SupplierId* SupplierId*
SupplierName SupplierName
PurchaseOrderHistoryProductId* …
PurchaseOrderHistoryProductDescription
PurchaseOrderHistoryQuantity

Purchase Order Product


PurchaseOrderId* ProductId*
SupplierId ProductDescription
ProductId …
PurchaseOrderQuantity

Luego de haber definido el grupo de subtipos mencionado, y en él a PurchaseOrderHistoryProductId


como subtipo de ProductId y a PurchaseOrderHistoryProductDescription como subtipo de
ProductDescription y usarlos en la transacción “Purchase Order History”, los controles que realiza
GeneXus son los que se muestran en la figura (los controles deseados).

Como ya no existe una tabla de clave primaria compuesta por los nombres de atributos “SupplierId,
ProductId”, los controles de que el SupplierId y el ProductId digitados en la transacción “Purchase
Order” existan se hacen sobre las tablas SUPPLIER y PRODUCT respectivamente.

352
Consideraciones

• El subtipo y supertipo serán definidos del mismo tipo,


GeneXus lo determina así y cuando se define un subtipo
éste "hereda" la definición del supertipo.

• Al menos uno de los supertipos del grupo (o conjunto de


supertipos del grupo) debe(n) corresponder a la PK de una
tabla del modelo.

• Si al definir el grupo, algún atributo queda como


“Secondary” (en lugar de “Primary” o “Inferred”), significa
que hubo un error en la definición del grupo.

353
Consideraciones
• Es posible actualizar los “subtipos inferidos”.
Ejemplo:

Primary

Inferred

Rules:

Es posible actualizar subtipos inferidos, tanto en las reglas de las transacciones como en
procedimientos.

Al ejecutar la regla de la transacción Invoice que se muestra en la figura, el atributo que se


actualiza físicamente es el supertipo CompanyPurchases de la tabla COMPANY. Vale aclarar que
CustomerPurchases (al igual que CustomerName) es subtipo inferido y por lo tanto no está
almacenado en ninguna tabla.

Tabla extendida de INVOICE: INVOICE, CUSTOMER, COMPANY.


Tabla extendida de CUSTOMER: CUSTOMER, COMPANY.
Tabla extendida de COMPANY: COMPANY.

Tabla INVOICE Tabla CUSTOMER Tabla COMPANY


InvoiceId* CustomerId* CompanyId*
InvoiceDate CompanyName
CustomerId CompanyPurchases
InvoiceTotal

354
Consideraciones
• En ambientes que generan SQL es posible ordenar por
subtipos inferidos.

• Siguiendo con el ejemplo anterior: es posible recorrer las


facturas ordenadas por nombre de cliente, CustomerName,
subtipo inferido a través de CustomerId:

For each order CustomerName


print Invoice

endfor

Esta posibilidad no está disponible para los generadores que no usan SQL.
Por ejemplo: Cobol, RPG y VFP.

355
Consideraciones

• Fórmulas verticales pueden involucrar subtipos.


Ejemplo:

En el ejemplo, se desea modelar la relación entre personas (Parejas) y sus Hijos. Para cada pareja se
desea mantener la cantidad de hijos que tiene.

Para esto se define la transacción “Person” y luego “Couple”, siendo el esposo, esposa e hijos subtipos
de personas.

Los 3 grupos de subtipos definidos son:

356
La cantidad de hijos se calcula con el atributo CoupleChildrenQuantity definido en el primer nivel de la
transacción “Couple”, como fórmula COUNT que involucra en su definición a un subtipo inferido
(CouplePersonChildAge).

357
Consideraciones

• Los subtipos inferidos no se pueden definir como


redundantes.
• Ejemplo:

No se pueden definir como redundantes en


la tabla COUPLE.

358
TIPOS DE DATOS ESTRUCTURADOS

359
Tipos de datos estructurados
Introducción
• Los lenguajes de programación manejan tipos de datos simples y tipos
de datos complejos

• Los tipos de datos complejos se construyen sobre la base de los tipos


de datos simples

• Los tipos de datos complejos conocidos como registros o tipos de


datos estructurados, permiten representar conjuntos de datos que
juntos realizan una definición

• Ejemplo:
Type Customer = Record
Id: Numeric(4)
Name: Character(30)
Country: Character(20)
City: Character(20) Luego se definen variables de este tipo,
listas de elementos de este tipo, etc.
Address: Record
CompanyAddress: Character(30)
HomeAddress: Character(30)
end;
end;

360
Tipos de datos estructurados
GeneXus

• ¿Cómo definir un tipo de datos estructurado?


1. Object / New Object
2. En el árbol del ambiente de desarrollo: Structured Data Types... y en
la ventana de al lado: botón derecho

• Editor similar al de transacciones y ofrece las mismas teclas de función:

• Generadores que soportan este tipo de definición: JAVA, .NET, VB

El editor de tipos de datos estructurados es sumamente similar al editor de transacciones y


ofrece las mismas teclas de acceso rápido.

Para cada ítem de un tipo de datos estructurado, se debe especificar:

La propiedad Name, con el nombre que identifica al ítem.


La propiedad Data type, en la cual se debe seleccionar un tipo de dato simple, o un dominio, o
un tipo de datos estructurado que ya se haya definido.
La propiedad Collection, para indicar si el ítem tiene o no múltiples instancias (en breve
veremos ejemplos que permitirán comprender su uso en detalle).
En particular los ítems que definen un nuevo nivel, se anteceden con el ícono , no se les
habilita la propiedad Data Type y se produce la indentación correspondiente para los ítems
correspondientes a dicho nivel.

Una funcionalidad interesante a tener en cuenta, es que además de definir ítem a ítem en un tipo
de datos estructurado, también está la posibilidad de definir rápidamente en la estructura de un
tipo de datos estructurado, la misma definición de cierta transacción. Para realizar esto, una vez
creado un nuevo tipo de datos estructurado y al estar editando el mismo, se debe pulsar el botón
derecho del mouse sobre su raíz, y seleccionar la opción Copy structure from… :

361
Tipos de datos estructurados
Utilización
• Se definen variables (en cualquier objeto GeneXus) cuyo tipo de datos = tipo
de datos estructurado:

• No es posible utilizar tipos de datos estructurados en la definición de


atributos

362
Tipos de datos estructurados
Ejemplos de utilización

Ejemplo # 1
• En un proc. se define una variable (&Customer) cuyo tipo de datos es del tipo
de datos estructurado: Customer
• El proc. recibe por parámetro un código de cliente, accede con comando For
Each a los datos del cliente recibido y carga los datos del cliente en la variable
&Customer:

Rule:
Parm(CustomerId);
Source:
For each
&Customer.Id=CustomerId Como se está cargando una variable
&Customer.Name=CustomerName escalar y no una lista o colección, no hay
&Customer.Country=CountryName necesidad de solicitar espacio de
&Customer.City=CityName memoria, ya que el espacio de memoria
&Customer.Address.CompanyAddress=… está creado para la variable como para
&Customer.Address.HomeAddress=… ser utilizada una vez.
Endfor

Dado que en este ejemplo se está cargando una variable escalar y no una lista o colección,
no hay necesidad de solicitar espacio de memoria, ya que el espacio de memoria está creado
para la variable como para ser utilizada una vez; así que simplemente se deben asignar los
valores que corresponda a los ítems de la variable.

Para cargar una colección, en cambio, sí habrá que solicitar espacio de memoria para la
variable para cada instancia a ser agregada en la lista; luego de solicitado el espacio de
memoria, habrá que asignar los valores que corresponda a los ítems de la variable, y por
último agregarla a la lista o colección, como veremos.

363
Tipos de datos estructurados
Ejemplos de utilización

Ejemplo # 2:
• Partiendo del tipo de datos estructurado: Customer, utilizamos la opción
Object / Save Structured Data Type As para crear un nuevo tipo de datos
estructurado de nombre: Customers

• Hacemos una modificación al tipo de datos estructurado: Customers Æ


propiedad collection de la raíz = True:

364
Tipos de datos estructurados
Ejemplos de utilización

Ejemplo # 2:
• Configurar la propiedad collection=True para un ítem, define que se trata de
una colección de esos ítems y no de uno solo.

• Notar que al configurar la propiedad collection de un ítem con valor True,


automáticamente se define un nombre por defecto para la propiedad Item name
de ese ítem:

Esto permite que podamos definir


variables del tipo de datos
estructurado Customers, y a su
vez variables del tipo de datos
Customers.CustomersItem

Por ejemplo, de haber configurado para el ítem CompanyAddress la propiedad collection con
valor True, habríamos indicado que se trata de una colección o lista (de largo variable) de
Addresses de empresa, y no de una sola. Análogamente, dado que lo que hemos implementado
es que el ítem: Customers (es decir, la raíz del tipo de datos estructurado) tenga valor True en la
propiedad collection, lo que hemos definido es que se trata de una colección de Customers y no
de un Customer solo.

Es importante notar que al configurar la propiedad collection de un ítem con valor True,
automáticamente se define para la propiedad Item name de ese ítem un nombre por defecto
(en este caso: CustomersItem). Esto permite que podamos definir variables del tipo de datos
estructurado Customers, y a su vez variables del tipo de datos Customers.CustomersItem.

Es sencillo de comprender que definir una variable del tipo Customers significará que estaremos
definiendo una colección o lista de Customers, mientras que definir una variable del tipo
Customers.CustomersItem, significará que estaremos definiendo un solo Customer (o un ítem de
la colección de Customers, que veremos enseguida cómo hacer para agregarlo a la colección).

365
Tipos de datos estructurados
Ejemplos de utilización

Ejemplo # 2:

• Implementamos proc. que carga una lista de clientes


• El proc. recibe por parámetro un rango de códigos de clientes, con comando For
Each accedemos a los clientes que se encuentren en dicho rango, y los vamos
agregando a la colección.

Rule:
Parm(&CustomerIdStart, &CustomerIdEnd);

Se definen 2 variables:
&Customers (Data Type: Customers)
&CustomersItem (Data Type: Customers.CustomersItem)

Source:
Æ

366
Tipos de datos estructurados
Ejemplos de utilización

Ejemplo # 2:

Source:

For each
where CustomerId>=&CustomerIdStart and CustomerId<=&CustomerIdEnd
&CustomersItem.Id=CustomerId
&CustomersItem.Name=CustomerName
&CustomersItem.Country=CountryName
&CustomersItem.City=CityName
&CustomersItem.Address.CompanyAddress=…
&CustomersItem.Address.HomeAddress=…
&Customers.add(&CustomersItem) /*se agrega el ítem a la lista*/
&CustomersItem = new Customers.CustomersItem() /*se solicita nuevo
espacio de memoria
para próximo ítem*/
Endfor

Como ya hemos mencionado, para una variable hay creado espacio de memoria como para ser utilizada
una vez. Si se necesita crear otra instancia para la variable, habrá que solicitar espacio de memoria para la
misma, para lo cual contamos con el operador new.

367
Tipos de datos estructurados
Ejemplos de utilización

Ejemplo # 2:

• ¿Qué pasaría si no solicitamos nuevo espacio de memoria en cada iteración del


for each? ... es decir, si omitimos el new en el código anterior ...

• En cada iteración estaríamos sobreescribiendo el mismo espacio de memoria... y


agregando a la lista siempre punteros al mismo espacio de memoria.

368
Tipos de datos estructurados
Ejemplos de utilización

Ejemplo # 2:

• Sin embargo lo que necesitamos implementar se esquematiza de la siguiente


forma:

• Por lo tanto, es necesario ir solicitando un nuevo espacio de memoria para cada


referencia a ser agregada en la lista

369
Tipos de datos estructurados
Ejemplos de utilización

Ejemplo # 3:

• Hacemos lo mismo que en el ejemplo #2 (proc. que carga una colección de


clientes), mostrando otra solución

•En este caso:


• utilizamos la definición del tipo de datos estructurado: Customer
• y definimos otro tipo de datos estructurado: CustomerList

• Se definen &CustomerList (Data Type: CustomerList)


2 variables &Customer (Data Type: CustomerList. CustomerListItem)

370
Tipos de datos estructurados
Ejemplos de utilización

Ejemplo # 3:

Source:

For each
where CustomerId>=&CustomerIdStart and CustomerId<=&CustomerIdEnd
&Customer.OneCustomer.Id=CustomerId
&Customer.OneCustomer.Name=CustomerName
&Customer.OneCustomer.Country=CountryName
&Customer.OneCustomer.City=CityName
&Customer.OneCustomer.Address.CompanyAddress=…
&Customer.OneCustomer.Address.HomeAddress=…
&CustomerList.add(&Customer) //se agrega el ítem a la lista
&Customer = new CustomerList. CustomerListItem() /* se solicita nuevo
espacio de memoria
Endfor para próximo ítem */

En este caso la variable &customersitem es del tipo de datos


ListaCustomers.ListaCustomersItem, el cual contiene un único ítem que es: OneCustomer;
y dado que el ítem OneCustomer es del tipo de datos Customer, este contiene los ítems Id,
Name, Country, City, etc.

De modo que si bien el ejemplo #3 implementa exactamente lo mismo que el ejemplo #2,
como en este último hemos optado por otra alternativa de definición de tipos de datos
estructurados, la sintaxis en este caso queda un poquito más extensa.

371
Tipos de datos estructurados
Operadores, métodos y propiedades

New
Es un operador que retorna una nueva instancia inicializada, o sea una nueva referencia o puntero al
tipo de datos que se especifica.

Sintaxis: &VbleDeTipoDeDatosEstructurado = new TipoDeDatosEstructurado()

Add
Es un método para aplicar a variable de tipo colección. Agrega un ítem a una colección, en la posición
relativa especificada.

Sintaxis: &VbleDeTipoColeccion.Add(&VbleItem [, Position])

Nota: Si se omite Position o se especifica 0, se agrega el Item al final de la colección. Position comienza
en 1.

Item
Es un método para aplicar a variable de tipo colección.

Sintaxis: &VbleDeTipoColeccion.Item(Position)

Retorna una referencia al elemento que se encuentra en la colección en la posición relativa Position.

En caso de especificar posición mayor a la última, el programa cancela.

No es válido asignar un valor con esta propiedad, por lo tanto no es correcto el código
&VbleDeTipoColeccion.item(&i) = Att. Para cambiar un valor se debe remover (método remove) y
agregar (método add).

372
Remove
Es un método para aplicar a variable de tipo colección. Elimina el elemento que se encuentre en la
colección en la posición relativa que se especifique, y corre un lugar todas las posiciones.

Sintaxis: &VbleDeTipoColeccion.Remove(Position)

Clear

Es un método para aplicar a variable de tipo colección. Elimina todos los elementos de la colección.

Sintaxis: &VbleDeTipoColeccion.Clear()

Sort

Es un método que permite ordenar los elementos de una colección.

El campo por el cual se quiere ordenar debe ir entre comillas.

Sintaxis: &VbleDeTipoColeccion.Sort(“NombreCampoPorElCualOrdenar")

Es posible ordenar en forma descendente, poniendo dentro de las comillas al nombre del campo entre
corchetes rectos.

Es posible ordenar por más de un campo, poniendo dentro de las comillas los nombres de los campos
separados por coma.

Count
Es una propiedad de variable de tipo colección. Retorna la cantidad de elementos de la colección. Es read
only.

373
Tipos de datos estructurados
Comando para recorrer colecciones

For &Var in &Array


...
Endfor

&Var: debe ser del tipo de datos de un ítem de la colección


&Array: debe ser del tipo de datos que es colección

La variable &Var va tomando los valores de cada posición de la lista

Consideraciones:
• No es posible obtener la posición del vector durante la recorrida, para esto es
necesario definir un variable que actúe como contador.
• No es posible modificar los ítems de la lista en la recorrida. Esto significa que
cambios en el valor de &Var, en el alcance de la estructura, no afectan al
correspondiente valor del &Array(X) o viceversa.
• Es posible incluir comandos de “corte” de la recorrida, al igual que en for each
o do while, como exit o return.

374
Tipos de datos estructurados
Cómo mostrarlos en form

Ejemplo: Inserción de variable &Invoice de tipo de datos estructurado Invoice:

en form de Transacción, Work panel o Web panel

Es posible insertar en el form de Transacciones, Web y Work Panels variables de tipo de datos
estructurados. También es posible hacerlo en print blocks de reportes, siempre y cuando no sean
collection.

Desplegar una collection en un form se puede hacer de forma muy sencilla: solamente es necesario
insertar la variable en el form, y quedará asociada a un grid que será cargado automáticamente, sin
necesidad de código alguno.

375
Tipos de datos estructurados
Cómo mostrarlos en form
2 posibilidades:
1) Pallete Toolbar shortcut 2) Insert/Variable
permite selección múltiple

Hay que repetir este paso para c/atributo del 1er nivel que se
desee mostrar en el form

Notar que es una sola variable definida (NO una variable por c/atributo)

376
Tipos de datos estructurados
Cómo mostrarlos en form

En cuanto al grid:

Carga automática:
¡No se requiere código!

Como hemos visto, hay 2 posibilidades para insertar una variable de tipo SDT con sus respectivos atributos en un
form / layout:

•mediante el shortcut : cada &variable.atributo uno a uno


•mediante insert/variable: múltiple selección de atributos de variable de tipo SDT

Si utilizando la opción insert/variable se selecciona algún (o algunos) atributo(s) correspondientes a una collection,
dicho(s) atributo(s) se agregarán automáticamente en un grid (y en las “Grid Properties” del grid, se podrá observar
que automáticamente se habrá asignado en el combo “Collection”, el nombre del nivel al cual pertenecen los
atributos).

Si en cambio se utiliza el shortcut para ir agregando atributos del primer nivel del SDT en el form, y se utiliza el
shortcut para agregar un grid en el form con el fin de mostrar una collection, el analista
tendrá que seleccionar explícitamente en el combo “Collection” de las “Grid Properties”, el nivel del SDT, para luego
poder seleccionar cuáles atributos de dicho nivel desea incluir en el grid.

En tiempo de ejecución, la carga del grid se realizará en forma completamente automática, con el contenido de la
collection. Esto podrá verse en el listado de navegación:

Nota: Un detalle a tener en cuenta es que independientemente de la forma en que se seleccionen los atributos de una
collection para ser mostrados en un grid, los mismos estarán en columnas visibles del grid, y los restantes atributos
del nivel (los no seleccionados) se agregarán hidden.

377
Tipos de datos estructurados
Cómo mostrarlos en form

• Propiedad CurrentItem:

• Para collections

• Permite desplegar información de los atributos del ítem


actual en el grid

• Ejemplo:
Event ‘DisplayProductDescription'
msg(&invoice.line.CurrentItem.ProductDescription)
EndEvent

Si por ejemplo en el work panel visto (Invoice) deseamos agregar un botón con un evento asociado y mostrar un
mensaje para la línea del grid seleccionada, con información de ese ítem de la colección, contamos a partir de la
versión 9.0 con la propiedad CurrentItem.

378
BUSINESS COMPONENTS

379
Business Components

Objetivo

Reutilizar la lógica del negocio definida en las transacciones. Usar el poder


de las transacciones (sin sus forms) desde otros objetos GeneXus:
- Work panels, Web panels, Procedimientos, Reportes…… y desde otra Transacción!
- Java, .Net
- Win, Web

Beneficios

- Actualización a la BD garantizando la integridad de los datos.


- Reutilización de código.
- Todos los objetos GX pueden actualizar la BD.

380
Business Components
Algunos ejemplos de uso

• Work Panels / Web Panels / Transacciones:

Definir interfaces sofisticadas permitiendo insertar los datos correspondientes a 2 o más


transacciones por medio de una (o por medio de un Work Panel o Web Panel)!

• Personalizar UTL entre 2 o más transacciones Web!


• Utilizar un único form para insertar en una Especialización u otro caso

• Procedimientos:

Utilizar concepto de BC en vez de For Each, New, Delete cuando se deseen ejecutar las reglas
definidas en la Transacción, controles IR, mantenimiento de redundancia, sin duplicar código

• Web Services:

Es posible permitir que desde fuera de la KB se consuma un BC como web service

Consumir un BC como web service

Un Web Service es un programa que puede ser invocado a través de Internet “para brindar
un servicio”, y utiliza el estándard XML para el formato de los datos recibidos/devueltos.

La versión 9.0 de GeneXus ofrece que desde fuera de la KB sea posible consumir un BC como
web service.

Para esto hay que:


Marcar a la transacción como BC:
Propiedad Business Component = True
Marcar a la transacción como Web Service:
Propiedad Expose as Web Service = True

381
Business Components
¿Cómo definir un Business Component?

1. Configurar la propiedad Business Component de la Transacción con


valor True (default=False).

2. Una vez que la propiedad Business Component de cierta Transacción =


True

En cualquier objeto GeneXus se podrá definir una variable del tipo de


datos BC asociado a la transacción, y configurar sus propiedades y
métodos.

Veamos un ejemplo …

382
Business Components
Ejemplo: Inserción de una invoice con 2 líneas mediante proc.

Business Component = True

Dos nuevos tipos de datos creados por GeneXus:


• Invoice Å nombre de la transacción
• Invoice.LineType Å nombre de la transacción.valor de la columna “Type” del nivel

En cualquier objeto será posible definir variables de estos tipos de datos… y las mismas tendrán:
• un conjunto de propiedades asociadas (los atributos de la TRN definida como BC)
• un conjunto de métodos asociados (para insertar, eliminar, recuperar, etc.)

variable del tipo de métodos


datos BC Invoice

propiedades

Las transacciones tienen la propiedad Business Component. El valor predeterminado de esta


propiedad es False. Si se cambia al valor True, la transacción puede ser invocada desde
cualquier objeto GeneXus como Business Component, sin ejecutar su form.

Una vez definida una transacción como Business Component, GeneXus creará automáticamente
un nuevo tipo de datos Business Component cuyo nombre será el de la transacción; y creará
también tantos tipos de datos como niveles posea la transacción, siendo sus nombres el
resultado de la concatenación del nombre de la transacción con el nombre dado a cada nivel
(valor de la columna “Type” del nivel).

383
Business Components
Ejemplo: Inserción de una invoice con 2 líneas mediante proc.
• Source del procedimiento:
//invoice
&Inv.InvoiceId = 1 &Inv Æ tipo de datos BC Invoice
&Inv.CustomerId = 50
&InvLine Æ tipo de datos BC Invoice.LineType
//invoiceline
&Invline.ProductId = 1
&Invline.InvoiceLineQuantity = 10
&Inv.Line.Add(&Invline)

&Invline = new Invoice. LineType() Í a partir del 2do registro a dar de alta
&Invline.ProductId = 2 en una lista es necesario el new
&Invline.InvoiceLineQuantity = 20
&Inv.Line.Add(&Invline)
&Inv.Save() // Í hay que hacer sólo &Inv.Save(), NO hay que hacer
&InvLine.Save()
&Messages= &Inv.GetMessages()
for &Message in &Messages
msg( &Message.Id) Recomendación: después de los métodos Save, Delete Load
msg( &Message.Description) y Check, manejar siempre los errores.
endfor
Los BC ignoran la property del objeto “Commit on exit”
Commit Í
Nunca hacen commit/rollback automáticamente. Recordar ponerlos explícitamente

Se ejecutan los controles de IR, mantenimiento de redundancias, fórmulas, eventos Start y


After Trn y las reglas de la transacción Invoice. El form de la trn NO se ejecuta.

384
Business Components
Reglas y eventos que se ejecutan

• Reglas: Todas las reglas son ejecutadas excepto (son ignoradas por el
especificador):
• las que incluyen user interface (Ej: call(Wxxx)).
• las que no aplican: Parm, Prompt, NoPrompt, Default_mode, etc.

• Eventos: Todos los eventos de la transacción son ignorados, excepto


los eventos Start y After TRN (y si éstos incluyen referencias a objetos
con user interface, se ignoran).

ACLARACIÓN: lo explicado que se ignora, aplica solamente a cuando la


trn es invocada como BC desde cualquier objeto GX.

385
Business Components
Manejo de errores
• Existe un tipo de datos estructurado (SDT) predefinido por GeneXus de nombre
Messages:

• Hay que definir una variable del tipo de datos Messages y asignarle el resultado
de aplicar el método GetMessages a la variable de tipo BC :

&Messages= &Inv.GetMessages()

• Se obtendrán:
• Los mensajes generados automáticamente por GeneXus que se hayan
disparado (Ej.: Record already exist)
• Las reglas Error y Msg definidas en la transacción que se hayan disparado

• Hay que recorrer la lista de errores y trabajarlos

Cuando se ejecutan los métodos: Save, Check, Load, Delete se disparan y cargan los mensajes
generados automáticamente por GeneXus así como las reglas Msg y Error definidos en la
transacción. Se recomienda que siempre se recupere la lista de estos mensajes y se haga un
“manejo de errores”.

Los mensajes más comunes generados automáticamente por GeneXus son:

Las reglas Msg y Error a partir de la versión 9.0 de GeneXus, aceptan en su definición además del
parámetro con el mensaje a desplegar, un segundo parámetro que define el Identificador del
mensaje. El objetivo de esto, es que cuando se ejecute a la transacción como Bussiness
Component, y se obtenga la lista de mensajes ocurridos luego de ejecutar una acción sobre la base
de datos, se tenga de cada mensaje, además del mensaje en sí, su identificador, siendo posible así
evaluar el identificador del mensaje para codificar el comportamiento en consecuencia:
Msg|Error(<mensaje>, <Id del mensaje>)

Ejemplos de <Id del mensaje>: "1", "2", "Error1", "Error2" o una descripción como ser
"CustomerNameCannotBeEmpty", etc.

De no especificar un identificador para el mensaje, habrá que preguntar por el texto del mensaje.

Nota: Para los mensajes generados automáticamente por GeneXus, el Id es siempre en Inglés
(independientemente del idioma seleccionado en el modelo).

386
Business Components
Métodos asociados a las variables de tipo de datos BC

PK del primer nivel de la transacción


Load(PKAttri1, ..., PKAttriN)
• Carga en variable de tipo BC toda su estructura
• Ej: &Empleado.Load(&EmployeeId)

Check()
• Valida los datos pero no actualiza la base de datos
• Usado para diálogos en los que se quiere validar los datos (dándole un feedback al usuario) antes
de actualizar la BD

Save()
• Valida los datos y actualiza la base de datos
• Sólo válido para aplicar a variables de tipo BC del primer nivel de la transacción
• Ej: &Inv.Save()
commit

Delete()
• Elimina en la base de datos el registro cargado en la variable de tipo BC
• Ej: &Empleado.Load(&EmployeeId)
&Empleado.Delete()
commit

387
Business Components
Métodos asociados a las variables de tipo de datos BC

GetMessages()
• Devuelve la lista de errores que ocurrieron luego de efectuar una operación a la BD
• Ej: &Messages= &Inv.GetMessages()

Fail()
• Devuelve True si luego de haber efectuado una operación a la BD dio algún error
• Ej: &Inv.Save()
If &Bc.Fail()
&Messages = &Inv.GetMessages()
for &Message in &Messages
.....

Success()
• Devuelve True si luego de haber efectuado una operación a la BD, la operación fue exitosa

Add(&BCLine)
• Permite agregar un ítem a una colección (en este caso, a la colección de líneas correspondiente a
un nivel subordinado de la variable BC)
• Ej.: &Inv.Line.Add(&Invline)

Los métodos GetMessages(), Fail() y Success() se han explicado recientemente al mostrar el


manejo de errores.

388
Business Components
¿Cómo programar un BC en un
work panel, web panel o reporte?
Ejemplo para un web panel, con variable de tipo de datos BC Invoice…
1. Crear en web panel variable &Invoice del tipo de datos BC Invoice
2. Insertar la variable &Invoice en el form (vale lo mismo explicado para insertar variables de tipo SDT... ya
que un BC es un SDT con la estructura de su transacción).
3. Programar los eventos:

Event 'Get' Event 'Delete'


&Invoice.Load(&Invoice.InvoiceId) &Invoice.Delete()
do "error handling" do "error handling"
EndEvent // 'Get‘ if &Invoice.Fail()
rollback
else
Event 'Save' commit
&Invoice.Save() endif
do "error handling" EndEvent // 'Delete'
if &Invoice.Fail()
rollback Sub "error handling"
else &Messages=&Invoice.GetMessages()
commit for &Message in &Messages
endif msg(&Message.Id)
EndEvent // 'Save' msg(&Message.Description)
endfor
EndSub

Desventajas con respecto a la Trn Invoice:


- Hay que poner noaccept para aquellas variables que no se deseen aceptar
- No se cuenta con la property Client Side Validation
- En la Trn no hay que hacer la codificación de estos eventos

389
Business Components
¿Qué es mejor: actualización “directa” o “usando BC”?

Directa:
New
CustomerId = &CustomeriId
CustomerName = &CustomerName
Endnew

Usando BC:
&Customer.CustomerId = &CustomerId
&Customer.CustomerName = &CustomerName
&Customer.Save()

¿Cuál de los métodos es el mejor?

Depende.

- Desde el punto de vista de la performance, la “directa” es la mejor ya que no se hacen controles.

-Desde el punto de vista de la consistencia, es mejor usar BC ya que siempre


(independientemente de si los datos vienen desde un form o de un procedimiento) se hacen todos
los controles.

Por lo tanto, una buena regla podría ser: “Usar Business Components a menos que la performance
sea crítica”.

De modo que para elegir una opción u otra, el analista deberá evaluar en cada caso qué necesita
efectuar, si la transacción implicada tiene muchas reglas definidas o no, fórmulas, chequeos de IR
relacionados, redundancias... y tener en cuenta los factores consistencia y performance.

390
KNOWLEDGE
MANAGER

391
Distribución

Se generará el
archivo
MyObjects.xpz
en la raíz de la
KB

Una vez seleccionados los objetos a distribuir (exportar) e indicado el nombre del archivo de
distribución (en el ejemplo: MyObjects), se crea automáticamente un archivo comprimido de
extensión XPZ. Este archivo comprimido contiene dentro un archivo XML que contiene la información
de los objetos y/o atributos distribuidos.
En Distribution Name podemos poner un path donde guardar el archivo de distribución. Si no se pone
path, como en el caso de arriba, entonces quedará almacenado en el directorio raíz de la Base de
Conocimiento.
En el ejemplo, si abrimos el archivo MyObjects.xpz con una herramienta tipo WinZip, veremos que
contiene un archivo de nombre MyObjects_1.xml

Distribute Options
Append to File: Si en el Distribution Name del diálogo de distribución se ingresa de nombre un
archivo que ya existe, al salir del campo se habilita esta opción para poder agregar la nueva
distribución al archivo existente.
Si se marca la opción Append to File, se crea un nuevo archivo XML dentro del mismo XPZ. Siguiendo
con el ejemplo anterior, se crearía un archivo MyObjects_2.xml en el archivo MyObjects.xpz. Si ya
existe un archivo con ese nombre, se incrementa el sufijo en uno y se vuelve a intentar.
Si no se marca la opción Append to File, el archivo se reemplaza con la nueva distribución.
Version 7.0 (And prior): Mediante esta opción es posible que el archivo de exportación resultante
sea creado con el formato utilizado hasta la versión 7.0 de GeneXus inclusive (extensión XPW).

392
Consolidación

Pide el path del archivo


a ser consolidado

La forma de importar objetos, atributos y dominios GeneXus dentro de una base de conocimiento es
mediante la opción Consolidate del Knowledge Manager. Lo que se importa son objetos
previamente distribuidos, en un archivo que puede tener tres formatos: XPZ, XML o XPW.

XPZ: Cuando se selecciona un archivo con este formato, el Knowledge Manager (KMW) recorre el
contenido del archivo XPZ y consolida cada uno de los archivos internos, siempre y cuando tengan
el formato XML apropiado.

XML: En algunos casos puede ser necesario consolidar algún XML en particular de todos los que
contiene el XPZ. Para este caso se puede descomprimir y consolidar directamente el XML.

XPW: Para poder tener compatibilidad con las versiones anteriores de GeneXus, existe un
componente encargado de realizar la conversión de un archivo XPW a un archivo XML.

La consolidación solo puede ser realizada en el modelo de Diseño. Procesos de verificación y


consolidación son ejecutados simultáneamente una vez que hemos seleccionado el archivo de
distribución y presionado Ok. Una vez que esos procesos terminan se despliega una ventana con los
resultados para que el usuario pueda observarlos.

393
GX OPEN

394
GX Open

• GX Open es un sitio que ofrece compartir proyectos


entre los miembros de la comunidad GeneXus.

• Cada proyecto puede tener varias versiones. Para cada


versión, se pueden almacenar todos los archivos
relacionados al mismo: XPZs, imágenes, propiedades
salvadas, docs, etc.

• Cada proyecto y versión tiene un foro, así los usuarios


pueden discutir acerca del mismo, y proponer cambios.

• Para bajar o subir proyectos es necesario registrarse


como miembro (sin cargo).

www.gxopen.com

395
PUESTA EN PRODUCCIÓN

396
Puesta en Producción

• Una vez que el prototipo ha sido


completamente testeado y aprobado, llega
el momento de poner en producción la
aplicación…

397
Creación de modelo de Producción

Primera vez:

1. Una vez que el prototipo ha sido aprobado, es momento de “pasar a producción”, creando un modelo de
Producción. Este modelo tendrá como plataforma la del cliente, dado que los programas que se llevarán al cliente
son los que se obtengan de aquí. Al crear este modelo, como con todo modelo de Prototipo o de Producción, se
crearán las tablas en la base de datos asociada. Para ello ocurre exactamente lo mismo que vimos para Prototipo:
GeneXus genera un programa de creación de tablas en el lenguaje de programación asociado al modelo, (cuyo
nombre depende del generador: por ejemplo, para visual basic, es RMenu , para .Net es Reor), éste programa se
compila, creándose un ejecutable que luego es corrido y obteniendo la creación de las tablas.

Ejemplo: Creamos el modelo de Producción: “Sistema Facturación y Compras”, y elegimos la misma plataforma
(recordar que esto no tiene por qué ser así: la plataforma de prototipo y producción pueden diferir). GeneXus crea
el programa Reor en .Net. Luego, si el usuario da el ok a la reorganización, este archivo Reor se compila, y el
archivo resultante Reor.exe se coloca bajo el folder bin del modelo (DATA002) y a continuación se ejecuta. Este
programa contendrá la lógica correspondiente a la creación de las tablas INVOICE, CUSTOMER, PRODUCT y
COUNTRY, con sus respectivas restricciones referenciales e índices.

2. Como sucede en el pasaje de Diseño a cualquier modelo de Prototipo, al pasar a este modelo de Producción,
todos los objetos GeneXus son copiados del modelo de Diseño al nuevo modelo. Aquí se especifican y generan,
obteniéndose por tanto los programas ejecutables bajo el directorio del modelo. Con esto tenemos todo lo
necesario para llevar la aplicación pronta al cliente.

Ejemplo: Especificamos y generamos las transacciones “Invoice”, “Customer”, “Product” y “Country” y todos los
demás objetos que hayamos creado (reportes, procedimientos, work panels, etc.).

3. Debemos llevar la aplicación a lo del cliente. De modo que debemos llevar el programa que crea la base de datos
(Rmenu.exe o Reor.exe) y ejecutarlo en la máquina del cliente, obteniendo como resultado la creación de la base
de datos. También debemos llevar los programas de la aplicación. Dependiendo de la plataforma, será necesario
registrar dlls en la máquina destino. Para algunas plataformas que requieren modificar el registry de Windows, o
que requieren seguir algunos pasos un poco más complejos de configuración, GeneXus brinda utilitarios de manera
tal de poder obtener un setup de la aplicación para ser ejecutado en el cliente y despreocuparse de hacer tales
registraciones en forma manual. Para plataformas visuales, tales como Visual Basic y Visual FoxPro GeneXus
cuenta con el Setup Wizard. Este utilitario es un wizard que va pidiendo, al pasar por sus distintas pantallas, la
información necesaria –cuál es el modelo de Producción, etc.- para poder armar el setup que incluya las dlls para
hacer tanto las registraciones necesarias en el cliente, como incluya la aplicación completa para que quede
instalada y pronta para trabajar. El análogo del Setup Wizard para plataforma Java es el Deployment Wizard.

398
La plataforma .Net por el momento no cuenta con un utilitario como estos, debido fundamentalmente a que las dlls
necesarias no tienen que ser registradas, dado que corren bajo el Framework de .Net, por lo que simplemente
deben copiarse los archivos del folder /bin que están bajo el modelo de Producción correspondiente, al cliente y
ejecutar luego el Reor.exe para crear la base de datos.

Llegado este punto, tenemos nuestro modelo de Producción como un espejo de lo que tiene el cliente
instalado.

4. Luego de la puesta en producción, surgirán cambios a realizar en la aplicación (el usuario nos pedirá cambios,
nos brindará nuevo conocimiento, etc.), que nos llevarán nuevamente a la fase de diseño, volviendo a iterar en el
ciclo Diseño-Prototipo, hasta que los cambios implementados hayan sido suficientemente testeados y aprobados.

En este punto, volveremos a pasar a Producción, lo que conducirá, otra vez, a que GeneXus realice un análisis de
impacto, comparando la base de datos de Producción, con el diseño de la misma correspondiente al modelo de
Diseño. Si encuentra cambios, entonces generará nuevamente el programa de reorganización (Reor.exe o
RMenu.exe) de la base de datos, cuya lógica implementará la modificación de la base de datos actual de
Producción, para llevarla al nuevo diseño. Este programa deberá ser llevado y ejecutado en el cliente para
transformar la base de datos que tenía (era un espejo de la de Producción) a la nueva. También deberemos
especificar y generar en el modelo de Producción los programas que hayan cambiado y llevarlos al cliente (se
utilizará el utilitario que corresponda: Setup Wizard, Deployment Wizard o se llevará el directorio /bin para
instalar la 2da versión de la aplicación al cliente).

Terminado este punto, volveremos a tener un espejo en el modelo de Producción de lo que hay en el
cliente. Tendremos que dejar congelado el modelo de Producción y volver a iterar en el ciclo Diseño-
Prototipo. Esto es fundamental porque la versión de Producción es la versión fiel de lo que tiene el
cliente.

Nota: En el caso de hacerse dos pasajes de Diseño a Producción consecutivos sin haber ido a lo del cliente, es
imprescindible guardar cada uno de los programas de reorganización ejecutados en orden. ¿Por qué? Porque si
hacemos dos o más pasajes de Diseño a Producción sin guardar cada uno de los programas de reorganización que
se ejecutaron (Reor.exe o RMenu.exe), no tendremos cada adaptación hecha la base de datos en forma
consecutiva y ¡no podremos reorganizar la base de datos del cliente!

De acuerdo a la plataforma del cliente de su aplicación, diríjase al manual de GeneXus para dicha plataforma, a la
GXDL, o a las Release Notes –en la “sección específica para la plataforma xxx”- para estudiar los detalles
particulares de la puesta en producción de una aplicación en esa plataforma.

399
INTRODUCCIÓN A LA
METODOLOGÍA GENEXUS

Presentaremos una introducción a la Metodología GeneXus.

De estar interesado en profundizar más en este tema, el alumno podrá inscribirse al curso
Gestión de Proyectos GeneXus si así lo desea, y/o recurrir a la documentación existente.

400
Filosofía de GeneXus

Integrar los sistemas en un gran


sistema corporativo

Sistema de Compras
Sistema de Ventas

Sistema de Sueldos

La filosofía de GeneXus tiende a la integración de los sistemas en un gran sistema corporativo.

Es decir, el objetivo es implementar para una empresa dada, un gran sistema integrado con
base de datos corporativa, de la cual se pueda obtener información de gestión y gerencial para
la toma de decisiones.

401
Con herramientas tradicionales...

• Suele resultar muy complejo lograr el desarrollo


de sistemas corporativos...

• Generalmente se desarrollan soluciones


independientes para cada área operativa de la
empresa:

Sistema de Sistema de
Compras Ventas

PROBLEMA: no se cuenta en la
Sistema de empresa con información
Sueldos corporativa, resultando ser
información no confiable

Implementar un sistema corporativo suele resultar una tarea muy compleja cuando se utilizan
herramientas tradicionales, por lo que generalmente no se lleva a cabo.

Esto trae como resultado que se tengan aplicaciones independientes, cada una resolviendo un
problema operativo particular, sin la posibilidad de obtener información corporativa.

Así encontramos por ejemplo en una empresa comercial: una aplicación de Ventas, otra de
Compras, otra Contable, etc.

En consecuencia no se cuenta en la empresa con información corporativa, resultando por lo tanto


ser información no confiable (por la aparición de información redundante).

402
Sistemas Corporativos
La integración de las Aplicaciones operativas de la
Organización es la base para poder construir los sistemas para
el área de Gestión y Gerencial:

INFORMACION
GERENCIAL

APLICACIONES PARA
EL ÁREA DE GESTION

APLICACIONES PARA EL AREA OPERATIVA

403
¿Cómo lograr Sistemas Corporativos?

1. Dividir el problema (modularizar)

2. Crear varios frentes de desarrollo

3. Asegurar la integrabilidad

4. Obtener una sola Base de Datos Corporativa

404
1. Dividir el problema (Modularizar)

¿Por qué modularizar?

Para:

• Dividir el problema en partes más pequeñas


• Habilitar frentes de desarrollo en paralelo
• Reducir los tiempos de desarrollo
• Incrementar la calidad de cada solución

Æ Como resultado de la modularización, se obtiene un conjunto de


módulos a ser desarrollados

La razón principal para modularizar es entonces, dividir el problema en partes más pequeñas y
habilitar varios frentes de desarrollo con el fin de hacer más eficiente la labor.

Realizar esta división (modularización) puede que no sea una tarea sencilla y dependerá de las
características de cada aplicación, sin embargo debemos hacerlo cuando el problema adquiere un
determinado tamaño tal que deba ser desarrollado por más de una persona.

No existen procedimientos exactos para esta tarea, pero cuando la realicemos no debemos olvidar
que el principal objetivo es obtener ambientes de desarrollo lo más independientes posibles,
reconociendo que seguramente los módulos no serán disjuntos y compartirán cierto
conocimiento. Debemos sin embargo, intentar que el conjunto de los objetos que compartan sea
lo más pequeño posible para poderlo administrarlo mejor, y realizar posteriormente una más fácil
integración en un único modelo corporativo.

405
2. Crear varios frentes de desarrollo
Luego de asignado cada módulo a cada persona o equipo de
desarrollo, surge la pregunta Æ ¿es conveniente realizar el desarrollo
en una sola KB o en KB’s independientes ?

KB Compras • Desarrollo: KBs independientes

• Producción: Todas las KBs son


consolidadas en KB Corporativa
KB Sueldos
KB Corporativa

KB Ventas

El desarrollo de un módulo tiene asociados ciclos de prototipación y puesta en producción


propios. Estos ciclos tienen asociados reorganizaciones de la Base de Datos (considerar que
las reorganizaciones son tareas monousuario), así como especificaciones y generaciones de
objetos.

No es recomendable que los ciclos de un módulo en particular, afecten el desarrollo de los


demás módulos que estén siendo desarrollados en forma simultánea, como ocurre en el
caso que compartan la Base de Datos. Por esta razón, es conveniente que los módulos se
implementen en Bases de Conocimiento independientes, para posteriormente integrarlos
(consolidarlos) todos, en otra Base de Conocimiento.

406
3. Desarrollar asegurando la
Integrabilidad de las KBs

• Las KBs no son disjuntas, sino que comparten cierto


conocimiento.

• La siguiente pregunta que surge es Æ ¿Cómo administrar


el conocimiento en común, para que al momento de
consolidar todas las KBs el impacto sea mínimo?

• Plantearemos el tema suponiendo por un momento, que en


una empresa se realiza la división del sistema a desarrollar
en 2 módulos y 2 equipos comienzan con el desarrollo de
KBs diferentes sin coordinar nada de antemano...

407
Ejemplo
KB 1 Æ COMPRAS KB 2 Æ VENTAS
Objetos: Objetos:

Proveedores Clientes
FacturasCompra FacturasVta
Productos - - - - - - - - - - - - - - - - - - - -Productos
Bancos - - - - - - - - - - - - - - - - - - - - - -Bancos
Agencias - - - - - - - - - - - - - - - - - - - - -Agencias
Compras Ventas
OrdenesCompra NotasCredito

DATOS PROGRAMAS DATOS PROGRAMAS

En el ejemplo tenemos dos módulos correspondientes a un sistema: el módulo de compras y el


módulo de ventas.

Serán desarrollados en forma independiente por dos equipos de desarrollo distintos, que
probarán sus prototipos con sus propios datos, hasta que esté todo listo para ponerlo en
producción.

Ahora bien, observemos que estos dos módulos no son disjuntos, sino que comparten
información común; entre otras cosas, ambos trabajan con productos, con bancos y con
agencias.

Observemos que pasará a la hora de integrar ambos módulos en la KB Corporativa (a la cual


solemos llamarla también KB “Consolidado”).

Supongamos que primeramente consolidamos el módulo de Compras…

408
Ejemplo
Consolidación de KB1 (COMPRAS)

COMPRAS
COMPRAS

KB “CONSOLIDADO”

Base PROGRAMAS
de Datos

Ha quedado en la KB “Consolidado” el conocimiento que aportó el módulo de Compras.

409
Ejemplo
Consolidación de KB2 (VENTAS)

COMPRAS
COMPRAS VENTAS
VENTAS Análisis de
Impacto

Proceso de
consolidación

KB “CONSOLIDADO”

Base PROGRAMAS
de Datos

Ahora hemos consolidado en la KB “Consolidado” el módulo de Ventas.

Como los módulos no son disjuntos, surgirán alteraciones a lo consolidado anteriormente, y los
cambios a ser efectuados se mostrarán en el reporte de análisis de impacto.

Con este ejemplo pretendemos despertar la atención sobre los problemas inherentes a la
integración de KBs, sin haber coordinado previamente el factor Integrabilidad.

410
Ejemplo
Problemas que surgen en la consolidación del conocimiento

• Objetos diferentes se llaman igual


• Objetos iguales se llaman diferente
• Atributos diferentes tienen el mismo nombre
• Atributos iguales tienen diferente nombre

KB1 Æ COMPRAS KB2 Æ VENTAS

Objeto: Invoice Objeto: Invoice


Descripción: Supplier Invoice Descripción: Customer Invoice

SupplierInvoiceId* CustomerInvoiceId*
SupplierId* CustomerId
SupplierName CustomerName
InvoiceDate InvoiceDate
(ProductId* (ProductNum*
......) .......)

¿Qué problemas encontramos en el ejemplo que venimos viendo?

1. Objetos diferentes que se llaman igual

Las Transacciones Supplier Invoice y Customer Invoice se llaman igual: Invoice


Al integrar ambos modelos solo quedará la última consolidada.

2. Atributos diferentes tienen el mismo nombre


En este caso tenemos dos atributos que son conceptualmente diferentes y tienen el mismo nombre:
InvoiceDate

Al momento de efectuar la segunda consolidación, GeneXus asumirá que se refieren al mismo


concepto y tratará de normalizar encontrando un problema: "Existe un atributo secundario en dos
tablas", por lo que informará el error y la Base de Datos no podrá ser creada ni reorganizada.

3. Atributos iguales tienen diferente nombre


También encontramos el caso de que el atributo que identifica al Producto, debería llamarse igual
en ambos objetos por tratarse del mismo concepto, pero fue llamado diferente : ProductId y
ProductNum.

Así GeneXus no puede reconocer que se está haciendo referencia al mismo elemento y no establece
ninguna relación para el concepto de Producto.

411
Ejemplo
Problemas que surgen en la consolidación del conocimiento

• Visiones diferentes de las relaciones

Por ejemplo:

KB 1 KB 2

Transacción Transacción Transacción


“Bank” “Agency” “Bancos”

BankId* AgencyId* BankId*


BankName AgencyName BankName
BankId (AgencyId*
BankName AgencyName)

Otro problema que puede ocurrir es que los desarrolladores definan visiones diferentes de la
relación entre los objetos.

Supongamos que tenemos 2 equipos desarrollando aplicaciones en un ambiente bancario.

Ambas aplicaciones, hacen referencia a las entidades Banco y Agencia.

Los 2 equipos de desarrollo tienen clara que la relación que existe entre Bancos y Agencias es
una relación de 1 a N : BANCO ---->> AGENCIAS

Sin embargo definen visiones diferentes, como se muestra arriba en la KB1 y KB2.

En la KB1 el atributo AgencyId identifica unívocamente una agencia. Y en la KB2 la agencia


queda identificada por la dupla BankId, AgencyId.

En este caso, no tenemos problema de nomenclatura, pero al integrar, solo quedará definida la
primer relación , o sea:

AgencyId*
BankId

y no:

BankId*
AgencyId*

Dado que los identificadores en las transacciones juegan un papel fundamental, esto puede
dejar inválidos objetos definidos en la segunda aplicación.

Concluimos entonces, que las relaciones entre atributos y los identificadores de las diferentes
entidades, juegan también un papel fundamental en el momento de integrar diferentes
aplicaciones.

412
¿Cómo trabajar para minimizar
los problemas de integración?

• Definir y seguir un padrón de nomenclatura


• para los objetos
• para los atributos (Nomenclatura GIK)

• Diseñar y seguir una Metodología para administrar


el conocimiento común
• a continuación...

Como hemos visto, los problemas de integración se reducen a problemas de nomenclatura y de definición
de relaciones e identificadores. Veremos posibles soluciones para minimizar estos problemas:

1. Definir y seguir un padrón de nomenclatura

Como sabemos GeneXus establece la relación entre los objetos y define la normalización de la Base de
Datos, basándose en los nombres de los atributos.

Es por eso que al momento de consolidar, la nomenclatura utilizada para los atributos juega un papel
primordial.

Sin embargo, no son menos importantes los nombres de los objetos (transacciones, procedimientos, etc.)
pues el Knowledge Manager reemplaza en la consolidación, los objetos con igual nombre.

Los nombres dados a las Tablas, Índices y Data Views serán también controlados en el momento de la
consolidación y deberán ser únicos en el Modelo consolidado, por lo que debemos intentar reducir
conflictos también en este sentido.

En cuanto a los nombres de las variables, a pesar de que no son relevantes para la consolidación, ya que
son definiciones locales, igual se sugiere tener una nomenclatura para las mismas con el fin de tener
uniformidad en el desarrollo y mejorar así la comprensión de las aplicaciones.

2. Diseñar y seguir una Metodología para administrar el conocimiento común

Una vez definidos los módulos y establecida la padronización para los objetos del sistema, es el momento
de diseñar una metodología para administrar el conocimiento de forma tal de mantener ambientes
independientes de desarrollo y asegurar su integración en un Modelo Corporativo que tenga asociada una
sola Base de Datos corporativa.

Hemos solucionado parcialmente los problemas de integración definiendo una nomenclatura standard
para objetos, pero queda aún potenciarla y asegurar la integración desde el punto de vista de
“relaciones”, es decir asegurar que los objetos compartidos por más de un módulo guarden la misma
relación con el resto de los objetos. Para ello, expondremos a continuación un esquema basado en tres
tipos de Modelos, que cumplen funciones diferentes en la tarea de administrar el conocimiento.

413
Metodología basada en 3 tipos
de Bases de Conocimiento

• Base de Conocimiento NÚCLEO: Contiene los objetos


corporativos, compartidos por las diferentes aplicaciones.

• Base de Conocimiento asociada a una APLICACIÓN:


Contiene el conocimiento de uno de los Módulos, resultado
de la modularización.

• Base de Conocimiento CORPORATIVA: Contiene la


consolidación de todas las bases de conocimiento
asociadas a las aplicaciones y el Núcleo.

414
Metodología basada en 3 tipos
de Bases de Conocimiento

KB
KB
NUCLEO
NUCLEO

KB
KB
KB
KB
KB SUELDOS
SUELDOS
COMPRAS
COMPRAS KB
VENTAS
VENTAS

KB CORPORATIVA

BASE DE
PROGRAMAS
DATOS

415
Metodología basada en 3 tipos
de Bases de Conocimiento

KB Núcleo

• ¿Será posible identificar los elementos comunes a varios


módulos antes de su desarrollo, e incluir éstos en una KB
Núcleo?

• Sí. En poco tiempo es posible definir un conjunto de


transacciones que definan cuales son las entidades y/u
objetos básicos de la organización que a priori sabemos van
a ser compartidos por los módulos.

416
Metodología basada en 3 tipos
de Bases de Conocimiento

Objetivo KB Núcleo

• Administrar en forma centralizada el conocimiento


compartido para tener un marco de referencia único. La
Base de Conocimiento Núcleo es el ambiente que habilita la
administración de este conocimiento.

• Darle nombre a lo que conocemos es de esencia corporativa,


evitando así múltiples nominaciones para un mismo objeto y
para sus atributos.

417
Metodología basada en 3 tipos
de Bases de Conocimiento
KB Núcleo = Intersección de KBs Aplicaciones

KB
COMPRAS
KB
KB VENTAS
NUCLEO

KB
SUELDOS

KB
CORPORATIVA

418
Metodología basada en 3 tipos
de Bases de Conocimiento
FOLDER
NUCLEO
KB
KB
NUCLEO
NUCLEO

FOLDER FOLDER FOLDER FOLDER FOLDER FOLDER


NUCLEO COMPRAS NUCLEO VENTAS NUCLEO SUELDOS
KB
KB KB KB
KB KB
COMPRAS
COMPRAS VENTAS SUELDOS
VENTAS SUELDOS

FOLDER FOLDER FOLDER FOLDER


COMPRAS NUCLEO VENTAS SUELDOS

KB CORPORATIVA

BASE DE PROGRAMAS
DATOS

En cada KB (Núcleo y Módulos) se deberá crear un folder, y los objetos propios de dicha KB, deberán
ubicarse dentro de ese folder.

Es decir, en la KB Núcleo habrá que crear un folder llamado Núcleo, en la KB Compras será necesario
crear un folder llamado Compras, en la KB Ventas un folder llamado Ventas, y en la KB Sueldos un folder
llamado Sueldos.

La primer KB a ser definida será la KB Núcleo. Para ello habrá que identificar los objetos comunes a todos
los módulos (por ejemplo, las transacciones Banks, Agencies, Products, Customers) y definirlos dentro del
folder Núcleo de la KB Núcleo.

Una vez definido el Núcleo, este deberá distribuirse y consolidarse en cada una de las KBs asociadas a cada
aplicación, para asegurarse el compartir todas los mismos objetos comunes, exactamente.

De modo que lo que se distribuirá será el folder Núcleo de la KB Núcleo, y se consolidará en cada una de
las KBs asociadas a una aplicación (KB Compras, KB Ventas y KB Sueldos) así como en la KB
Corporativa.

Recién luego de esto, cada desarrollador responsable de un módulo podrá comenzar a trabajar, debiendo
crear todos los objetos propios de su módulo, en el folder correspondiente a ese módulo en particular. Así,
la KB Compras tendrá 2 folders: el folder Nucleo con los objetos comunes a todos los módulos, y el
folder Compras con los objetos propios que implementen ese módulo. Análogamente, las KBs Ventas y
Sueldos tendrán el folder Nucleo y su folder propio.

Cada KB asociada a una aplicación tendrá un ambiente de Prototipación, y un ambiente de Test en la


plataforma de Producción. Estos modelos permitirán tener un diseño completo de la aplicación, minimizando
los tiempos de desarrollo pues:

1. Se estará trabajando con KBs pequeñas, asegurando la integrabilidad con el resto de las aplicaciones
2. Se estarán realizando los primeros niveles de test de funcionalidad para el módulo en forma
independiente asegurando un ciclo de prototipación dinámico
3. No se afectará al resto del desarrollo

En la medida que cada desarrollador de por finalizado su módulo, distribuirá el folder propio de su KB (por
ejemplo el folder Sueldos de la KB Sueldos) y este se consolidará en la KB Corporativa. No distribuirá el
folder Nucleo de la KB Sueldos, ya que este folder solo se distribuye de la KB Nucleo).

419
Metodología basada en 3 tipos
de Bases de Conocimiento

Administración de las KBs correspondientes a módulos

1. Se consolida el Núcleo
2. Se crea un folder propio, es decir que identifique a la aplicación,
y en el mismo se crean los objetos de la aplicación
3. Una vez finalizado el desarrollo y test del módulo, se distribuirá
el folder propio para consolidarlo en la KB Corporativa.

Æ Cambios en objetos del Núcleo se deben realizar en la KB


Núcleo y luego deben ser redistribuidos a todas las KBs.
Æ Incorporar objetos de esencia corporativa en el Núcleo

¿Cómo se procede si uno de los módulos requiere modificar un objeto del Núcleo? Es decir,
supongamos que el desarrollador de un módulo se da cuenta que le resulta necesario para su
aplicación, agregar un atributo en una transacción del Núcleo.

Esto tendrá un impacto en todas las KBs de aplicación, y por tanto deberá administrarse con
cuidado.

La forma de proceder es realizar el cambio en la KB Núcleo, y luego redistribuir el Núcleo (folder


Núcleo de la KB Núcleo) a todas las KBs.

420
Metodología basada en 3 tipos
de Bases de Conocimiento

Características de la KB Corporativa

• Contiene el Núcleo y las Aplicaciones

• Ventajas:
• Integridad total de la Base de Datos
• Minimiza la redundancia de Datos
• Base para construir la Información Corporativa de la
Organización

421

S-ar putea să vă placă și