Proyecto Multiplataforma – Episodio 3 (Aplicación Cliente «Windows/Linux»)

Hola amigos.

En este Episodio vamos a crear nuestra Aplicación Cliente la cual enviará las peticiones http GET a nuestro Servidor Datasnap para obtener información de los métodos Usuario y Habitaciones.

Escribiremos el código necesario para procesar los dos tipos de RESPONSE que vimos en el Episodio 2.2 y mostraré las diferencias de serializar los objetos JSONObject y FDJSONDataSets. Esto lo haremos en dos aplicaciones independientes que nos servirán de prueba para después implementar el código realizado en la Aplicación Cliente principal.

***En el siguiente video vamos a consumir los métodos Usuario() y Habitaciones() que nos regresarán la clase TJSONObject.

***En el siguiente video vamos a consumir los métodos getUsuario() y getHabitaciones() que nos regresarán la clase TFDJSONDataSets.

Ya vimos como enviar las peticiones GET y consumir los métodos de nuestro Servidor DataSnap y la implementación de los métodos en la Aplicación Cliente se harán con aquellos que nos regresan la clase TJSONObject ya que nos permitirá desarrollar clientes desde cualquier herramienta de programación y no solo desde RAD Studio, sin embargo ustedes podrán decidir cual será su favorito.

Dicho lo anterior, todas las peticiones GET, PUT y POST que se agreguen a partir ahora se escribirán con Response TJSONObject y solo haremos un método para las peticiones PUT y POST como ejemplo ilustrativo de la clase TFDJSONDataSets los cuales se consumirá en una pequeña aplicación por separado, los demás métodos se consumirán directamente en nuestra Aplicación Cliente.

***En el siguiente video vamos a implementar los métodos vistos anteriormente en nuestra Aplicación Cliente y se compilará para el Escritorio de Windows (Win10) y para el Escritorio de Linux (Ubuntu).

Con esto terminamos el Episodio 3. En el siguiente Episodio veremos como generar una app Android a partir de nuestra Aplicación Cliente Base.

***Mientras tanto los invito a iniciar sus pruebas con peticiones GET y consumir sus métodos con la opción que mas les interese.

Nos vemos en la próxima

Nos vemos en la próxima entrada.

Proyecto Multiplataforma – Episodio 2.2 (Creación de Métodos)

Hola amigos.

En éste Episodio vamos a escribir un par de métodos para poder pasar a la siguiente etapa que es la construcción de las Aplicaciones Cliente y de esa forma avanzar con el proyecto.

Posteriormente cada método que se agregue en el Servidor, se hará la el proceso para el llamado del mismo desde la Aplicación Cliente para avanzar de forma paralela.

Primero vamos a rediseñar el método ValidaUsuario() ya que requerimos que además de validar que el usuario existe es necesario obtener la información del usuario que inició sesión en el sistema y mostrarla en la ventana principal.

Posteriormente vamos a crear el método Habitaciones() el cual nos regresará la información de las habitaciones para generar lo que en hotelería se le conoce como Rack de Habitaciones.

Un Rack permite visualizar de forma inmediata el estado de las habitaciones (Disponible, Ocupada, Limpia, Sucia, etc.) en una relación Habitación / Día, la siguiente imagen nos muestra un ejemplo de éste visor .

Vamos a mostrar dos formas diferentes de regresar objetos JSON para que ustedes decidan cual se adapta a sus necesidades. Para realizar esto se van a utilizar las siguientes clases:

  • TJSONObject
    • Con esta clase se creará el mapeo JSON del DataSet para que puedan escribir Aplicaciones Cliente con cualquier herramienta y poder serializar el contenido con formato JSON y extraer la información contenida.
  • TFDJSONDataSets
    • Con esta clase se creará el mapeo JSON del DataSet en formato «mime encoded binary content» por lo que solo será útil cuando se use RAD Studio en ambos extremos es decir tanto en el Servidor como en el Cliente.

***En el siguiente video vamos a crear los métodos con el objeto TJSONObject.

***En el siguiente video vamos a crear los métodos con el objeto TFDJSONDataSets

Con esto terminamos el Episodio 2 el cual hice en dos partes para separar el motor y los métodos que serán consumidos.

En el siguiente Episodio comenzaremos a diseñar nuestra aplicación de Escritorio para las plataformas Windows y Linux.

***Mientras tanto los invito a experimentar con la creación de métodos utilizando los dos enfoques TJSONObject y TFDJSONDataSets .

Nos vemos en la próxima

Nos vemos en la próxima entrada.

Proyecto Multiplataforma – Episodio 2.1 (Núcleo del proyecto)

Hola amigos

En este Episodio vamos a crear el núcleo de nuestro proyecto que nos permita la comunicación a través de Internet, de Red local o en el Localhost a las Aplicaciones Cliente desarrolladas en diferentes plataformas.

Construiremos una aplicación que permitirá a las Aplicaciones Cliente invocar los métodos implementados utilizando una tecnología llamada DataSnap la cual genera automáticamente la interfaz de comunicación entre las aplicaciones Cliente y el Servidor.

Antes de iniciar la creación de nuestro Servidor DataSnap vamos a definir algunos métodos que serán invocados desde las aplicaciones Cliente.

Métodos Get

  1. Validación de Usuario para Iniciar Sesión en el Sistema
  2. Obtener Información de las Habitaciones para crear el Rack de Habitaciones
  3. Obtener Detalle de una Habitación en particular
  4. Obtener Información de Huéspedes o un Huésped en particular.
  5. Obtener Listado de Productos o un solo Producto
  6. Obtener Estado de Cuenta

Métodos Post

  1. Check In
  2. Consumos
  3. Pagos
  4. Check Out
  5. Clientes

Creando el Servidor DataSnap

***En el siguiente video veremos como crear un Servidor DataSnap de forma simple y rápida. Una vez creado probaremos su funcionamiento con dos métodos que seguramente ya conocen EchoString() y ReverseString().

Como pudieron ver, crear el motor base de un Servidor DataSnap es muy fácil y muy rápido.

A continuación vamos a construir un método para validar la existencia de un usuario, dicho método requerirá el envío de los elementos USUARIO y CLAVE, se realizará la consulta a la base de datos y el retorno del método será de tipo Boolean, es decir Falso o Verdadero según corresponda.

Método para la Validación de Usuarios

***El siguiente video muestra como crear el Método para validad Usuario y Contraseña.

Ahora sabemos que crear un método con acceso a base de datos es muy fácil, éste método es muy simple porque solo valida si existe o no el registro solicitado y nos regresa un campos Booleano, muy simple pero la idea era mostrar la capacidad de generar métodos en el servidor DataSnap.

En el siguiente Episodio veremos ***Bloopers incluidos 🙂 *** como se regresan DataSets a las aplicaciones cliente y la facilidad con la que lo podemos hacer con nuestra querida herramienta de trabajo.

***Mientras tanto, los invito a experimentar con mas métodos que regresen datos simples y cualquier duda o comentario con gusto la ampliamos 😀

Nos vemos en la próxima

Nos vemos en la próxima entrada.

Proyecto Multiplataforma – Episodio 1 (Diseño de la Base de Datos)

Hola amigos,

Ya estamos listos para iniciar nuestro Proyecto Multiplataforma y lo primero que vamos a hacer es darle un nombre. En realidad no fue demasiado el tiempo que ocupé pero entre varios nombres que se me ocurrieron he elegido el siguiente:

***Aztlán Resort***

Si deseas conocer que es Aztlán da clic aquí.

Ahora, vamos a determinar «el que», «el como» y «el para qué» del proyecto y a partir de ahí crear nuestra base de datos.

Propósito

  • El sistema debe solicitar el registro del usuario que desea acceder al sistema. (Los perfiles de usuario no los veremos por ahora).
  • El sistema debe permitir al usuario ver el rack de habitaciones disponibles y ocupadas.
  • El sistema debe controlar las entradas de huéspedes.
  • El sistema debe permitir realizar cargos de consumos a la cuenta del huésped.
  • El sistema debe permitir a los clientes la búsqueda de habitaciones y ver sus detalles; costo, tipo de habitación, fotos, etc…
  • El sistema debe permitir consultar el estado de cuenta del huésped.
  • El sistema debe controlar la salida y los pagos de los huéspedes.

Definición

Nuestro sistema deberá contar con la información necesaria para poder controlar las habitaciones, su ocupación, todos los consumos de los huéspedes durante su estancia y la salida y pagos del huésped.

De acuerdo a lo anterior vamos a proceder a definir las tablas y su relación entre ellas, No vamos a profundizar en el diseño de una base de datos compleja ya que no es el objetivo que buscamos con el proyecto.

Tablas de la Base de Datos

  1. Habitaciones
    • Tabla Catálogo
    • Esta tabla contendrá las habitaciones del hotel así como la información de precios y disponibilidad.
  2. Tipo Habitación
    • Tabla Catálogo
    • Esta tabla contendrá los tipos de habitación y sus detalles.
  3. Reservas
    • Tabla Operativa
    • En esta tabla se registrarán las entradas y salidas de las habitaciones del Hotel y nos servirá para mostrar el rack de habitaciones.
  4. Clientes
    • Tabla Catálogo
    • Esta tabla contendrá la información de los huéspedes del Hotel
  5. Consumos
    • Tabla Operativa
    • En ésta tabla se registrarán todos los cargos por consumos durante la estancia del huésped en el Hotel.
  6. Pagos
    • Tabla Operativa
    • En esta tabla se registrarán todos los pagos que realice el huésped.
  7. Empleados
    • Tabla Catálogo
    • En esta tabla se registrarán a los usuarios del sistema.
  8. Productos
    • Tabla Catálogo
    • Catálogo de productos que el Hotel comercializa.

Relaciones

La Tabla HABITACIONES se relaciona con la Tabla TIPO_HABITACION a través del campo HABITACIONES.TIPO_HAB_ID con el campo TIPO_HABITACION.TIPO_HAB_ID.

La tabla RESERVAS se relaciona con la Tabla HABITACIONES a través del campo RESERVAS.HABITACION_ID con el campo HABITACIONES.HABITACION_ID y también con la Tabla CLIENTES a través del campo RESERVAS.CLIENTE_ID con el campo CLIENTES.CLIENTE_ID.

La Tabla CONSUMOS se relaciona con la Tabla RESERVAS a través del campo CONSUMOS.RESERVA_ID con el campo RESERVAS.RESERVA_ID y con la Tabla PRODUCTOS a través del campo CONSUMOS.PRODUCTO_ID con el campo PRODUCTOS.PRODUCTO_ID.

Por último la Tabla PAGOS se relaciona con la Tabla RESERVAS a través del campo PAGOS.RESERVA_ID con el campo RESERVAS.RESERVA_ID.

Diagrama Entidad Relación

Como pueden observar todas las tablas tienen una relación excepto la tabla EMPLEADOS la cual solo tendrá la función de permitir la entrada de usuarios al sistema.

*** No estamos considerando ningún acceso restringido (privilegios de usuario) ni contraseñas cifradas ya que no es parte del objetivo del proyecto.

Motor de Base de Datos

En mi caso he decidido utilizar el motor de Firebird para generar la Base de Datos del sistema, ustedes pueden elegir la que consideren mejor.

Campos Auto Incrementables

Para la asignación de nuestras llaves primarias como campos auto-incrementables se creó el generador (GEN_CAT_ID) el cual será ejecutado vía disparadores (Triggers) configurados en los eventos Before_Insert de cada una de las Tablas del sistema como se podrá observar en la definición de la base de datos que se muestra a continuación.


/******************************************************************************/
/*         Generated by IBExpert 2021.8.1.1 26/08/2021 05:48:13 p. m.         */
/******************************************************************************/

SET SQL DIALECT 3;

SET NAMES ISO8859_1;

CREATE DATABASE 'C:\AZTLAN\BASE\AZTLAN.FDB'
USER 'SYSDBA' PASSWORD 'masterkey'
PAGE_SIZE 16384
DEFAULT CHARACTER SET ISO8859_1 COLLATION ISO8859_1;

/******************************************************************************/
/*                                 Generators                                 */
/******************************************************************************/

CREATE GENERATOR GEN_CAT_ID START WITH 0 INCREMENT BY 1;
SET GENERATOR GEN_CAT_ID TO 0;

/******************************************************************************/
/*                                   Tables                                   */
/******************************************************************************/

CREATE TABLE RESERVAS (
    RESERVA_ID       INTEGER NOT NULL,
    HABITACIONES_ID  INTEGER NOT NULL,
    CLIENTES_ID      INTEGER NOT NULL,
    FH_RESERVA       DATE NOT NULL,
    CHECKIN          TIMESTAMP NOT NULL,
    CHECKOUT         TIMESTAMP NOT NULL,
    ESTATUS          VARCHAR(10) NOT NULL
);

CREATE TABLE CLIENTES (
    CLIENTE_ID       INTEGER NOT NULL,
    NOMBRE           VARCHAR(100) NOT NULL,
    DIRECCION        BLOB SUB_TYPE 1 SEGMENT SIZE 80,
    TELEFONO         VARCHAR(15),
    GENERO           VARCHAR(10) NOT NULL,
    DNI              VARCHAR(100),
    ESTATUS          VARCHAR(10) NOT NULL
);

CREATE TABLE EMPLEADOS (
    EMPLEADO_ID      INTEGER NOT NULL,
    NOMBRE           VARCHAR(30) NOT NULL,
    MATERNO          VARCHAR(30) NOT NULL,
    PATERNO          VARCHAR(30),
    USUARIO          VARCHAR(25) NOT NULL,
    CLAVE            VARCHAR(100) NOT NULL,
    ESTATUS          VARCHAR(10) NOT NULL
);

CREATE TABLE PRODUCTOS (
    PRODUCTO_ID      INTEGER NOT NULL,
    TIPO             VARCHAR(25) NOT NULL,
    NOMBRE           VARCHAR(100) NOT NULL,
    COSTO            DOUBLE PRECISION NOT NULL,
    DESCRIPCION      BLOB SUB_TYPE 1 SEGMENT SIZE 80 NOT NULL,
    ESTATUS          VARCHAR(10) NOT NULL
);

CREATE TABLE CONSUMOS (
    CONSUMO_ID       INTEGER NOT NULL,
    PRODUCTOS_ID     INTEGER NOT NULL,
    RESERVAS_ID      INTEGER NOT NULL,
    FH_CONSUMO       DATE NOT NULL,
    CANTIDAD         DOUBLE PRECISION NOT NULL,
    COSTO            DOUBLE PRECISION NOT NULL,
    ESTATUS          VARCHAR(10) NOT NULL
);

CREATE TABLE PAGOS (
    PAGO_ID          INTEGER NOT NULL,
    RESERVAS_ID      INTEGER NOT NULL,
    IMPORTE          DOUBLE PRECISION NOT NULL,
    TIPO             VARCHAR(20) NOT NULL,
    OBSERVACIONES    BLOB SUB_TYPE 1 SEGMENT SIZE 80 NOT NULL,
    FH_PAGO          DATE NOT NULL,
    ESTATUS          VARCHAR(10) NOT NULL
);

CREATE TABLE HABITACIONES (
    HABITACION_ID    INTEGER NOT NULL,
    TIPO_HAB_ID      INTEGER NOT NULL,
    NUMERO           VARCHAR(10) NOT NULL,
    DETALLES         BLOB SUB_TYPE 1 SEGMENT SIZE 80,
    ESTATUS          VARCHAR(10) NOT NULL
);

CREATE TABLE TIPO_HABITACION (
    TIPO_HAB_ID      INTEGER NOT NULL,
    TIPO             VARCHAR(100) NOT NULL,
    DESCRIPCION      BLOB SUB_TYPE 1 SEGMENT SIZE 80 NOT NULL,
    FOTO             VARCHAR(100) NOT NULL,
    COSTO            DOUBLE PRECISION NOT NULL,
    ESTATUS          VARCHAR(10) NOT NULL
);

/******************************************************************************/
/*                                Primary keys                                */
/******************************************************************************/

ALTER TABLE RESERVAS ADD CONSTRAINT PK_RESERVAS PRIMARY KEY (RESERVA_ID);
ALTER TABLE CLIENTES ADD CONSTRAINT PK_CLIENTES PRIMARY KEY (CLIENTE_ID);
ALTER TABLE EMPLEADOS ADD CONSTRAINT PK_EMPLEADOS PRIMARY KEY (EMPLEADO_ID);
ALTER TABLE PRODUCTOS ADD CONSTRAINT PK_PRODUCTOS PRIMARY KEY (PRODUCTO_ID);
ALTER TABLE CONSUMOS ADD CONSTRAINT PK_CONSUMOS PRIMARY KEY (CONSUMO_ID);
ALTER TABLE PAGOS ADD CONSTRAINT PK_PAGOS PRIMARY KEY (PAGO_ID);
ALTER TABLE HABITACIONES ADD CONSTRAINT PK_HABITACIONES PRIMARY KEY (HABITACION_ID);
ALTER TABLE TIPO_HABITACION ADD CONSTRAINT PK_TIPO_HABITACION PRIMARY KEY (TIPO_HAB_ID);

/******************************************************************************/
/*                                Foreign keys                                */
/******************************************************************************/

ALTER TABLE RESERVAS ADD CONSTRAINT FK_RESERVAS_1 FOREIGN KEY (HABITACIONES_ID) REFERENCES HABITACIONES (HABITACION_ID);
ALTER TABLE RESERVAS ADD CONSTRAINT FK_RESERVAS_2 FOREIGN KEY (CLIENTES_ID) REFERENCES CLIENTES (CLIENTE_ID);
ALTER TABLE CONSUMOS ADD CONSTRAINT FK_CONSUMOS_1 FOREIGN KEY (PRODUCTOS_ID) REFERENCES PRODUCTOS (PRODUCTO_ID);
ALTER TABLE CONSUMOS ADD CONSTRAINT FK_CONSUMOS_2 FOREIGN KEY (RESERVAS_ID) REFERENCES RESERVAS (RESERVA_ID);
ALTER TABLE PAGOS ADD CONSTRAINT FK_PAGOS_1 FOREIGN KEY (RESERVAS_ID) REFERENCES RESERVAS (RESERVA_ID);
ALTER TABLE HABITACIONES ADD CONSTRAINT FK_HABITACIONES_1 FOREIGN KEY (TIPO_HAB_ID) REFERENCES TIPO_HABITACION (TIPO_HAB_ID);

/******************************************************************************/
/*                                  Triggers                                  */
/******************************************************************************/

SET TERM ^ ;

/******************************************************************************/
/*                            Triggers for tables                             */
/******************************************************************************/

/* Trigger: RESERVAS_BI */
CREATE TRIGGER RESERVAS_BI FOR RESERVAS
ACTIVE BEFORE INSERT POSITION 0
as
begin
  if (new.reserva_id is null) then
    new.reserva_id = gen_id(gen_cat_id,1);
end
^

/* Trigger: CLIENTES_BI */
CREATE TRIGGER CLIENTES_BI FOR CLIENTES
ACTIVE BEFORE INSERT POSITION 0
as
begin
  if (new.cliente_id is null) then
    new.cliente_id = gen_id(gen_cat_id,1);
end
^

/* Trigger: EMPLEADOS_BI */
CREATE TRIGGER EMPLEADOS_BI FOR EMPLEADOS
ACTIVE BEFORE INSERT POSITION 0
as
begin
  if (new.empleado_id is null) then
    new.empleado_id = gen_id(gen_cat_id,1);
end
^

/* Trigger: PRODUCTOS_BI */
CREATE TRIGGER PRODUCTOS_BI FOR PRODUCTOS
ACTIVE BEFORE INSERT POSITION 0
as
begin
  if (new.producto_id is null) then
    new.producto_id = gen_id(gen_cat_id,1);
end
^

/* Trigger: CONSUMOS_BI */
CREATE TRIGGER CONSUMOS_BI FOR CONSUMOS
ACTIVE BEFORE INSERT POSITION 0
as
begin
  if (new.consumo_id is null) then
    new.consumo_id = gen_id(gen_cat_id,1);
end
^

/* Trigger: PAGOS_BI */
CREATE TRIGGER PAGOS_BI FOR PAGOS
ACTIVE BEFORE INSERT POSITION 0
as
begin
  if (new.pago_id is null) then
    new.pago_id = gen_id(gen_cat_id,1);
end
^

/* Trigger: HABITACIONES_BI */
CREATE TRIGGER HABITACIONES_BI FOR HABITACIONES
ACTIVE BEFORE INSERT POSITION 0
as
begin
  if (new.habitacion_id is null) then
    new.habitacion_id = gen_id(gen_cat_id,1);
end
^

/* Trigger: TIPO_HABITACION_BI */
CREATE TRIGGER TIPO_HABITACION_BI FOR TIPO_HABITACION
ACTIVE BEFORE INSERT POSITION 0
as
begin
  if (new.tipo_hab_id is null) then
    new.tipo_hab_id = gen_id(gen_cat_id,1);
end
^
SET TERM ; ^

El siguiente paso es inicializar algunas tablas para poder construir los métodos de nuestro Servidor DataSnap.

Tipo de Habitación

INSERT INTO TIPO_HABITACION (TIPO_HAB_ID, TIPO, DESCRIPCION, FOTO, COSTO, ESTATUS)
VALUES (NULL, 'SENCILLA', 'Habitación Sencilla una cama Matrimonial', 'C:\AZTLAN\FOTOS\SENCILLA.JPG', 1200, 'DISPONIBLE');
INSERT INTO TIPO_HABITACION (TIPO_HAB_ID, TIPO, DESCRIPCION, FOTO, COSTO, ESTATUS)
VALUES (NULL, 'DOBLE', 'Habitación Doble dos camas Matrimoniales', 'C:\AZTLAN\FOTOS\DOBLE.JPG', 1800, 'DISPONIBLE');
INSERT INTO TIPO_HABITACION (TIPO_HAB_ID, TIPO, DESCRIPCION, FOTO, COSTO, ESTATUS)
VALUES (NULL, 'JUNIOR SUITE', 'Habitación Junior Suite una cama Queen Size', 'C:\AZTLAN\FOTOS\JUNIORSUITE.JPG', 2800, 'DISPONIBLE');
INSERT INTO TIPO_HABITACION (TIPO_HAB_ID, TIPO, DESCRIPCION, FOTO, COSTO, ESTATUS)
VALUES (NULL, 'MASTER SUITE', 'Habitación Master Suite una cama King Size', 'C:\AZTLAN\FOTOS\MASTERSUITE.JPG', 4500, 'DISPONIBLE');
INSERT INTO TIPO_HABITACION (TIPO_HAB_ID, TIPO, DESCRIPCION, FOTO, COSTO, ESTATUS)
VALUES (NULL, 'TEMATICA', 'Habitación Temática una cama Queen Size', 'C:\AZTLAN\FOTOS\TEMATICA.JPG', 1500, 'DISPONIBLE');
COMMIT WORK;

Habitaciones

INSERT INTO HABITACIONES (HABITACION_ID, TIPO_HAB_ID, NUMERO, DETALLES, ESTATUS)
VALUES (NULL, 1, '100', 'Pantalla plana, Internet, Cafetera', 'LIMPIA');
INSERT INTO HABITACIONES (HABITACION_ID, TIPO_HAB_ID, NUMERO, DETALLES, ESTATUS)
VALUES (NULL, 2, '101', 'Pantalla plana, Internet, Servibar, Cafetera', 'LIMPIA');
INSERT INTO HABITACIONES (HABITACION_ID, TIPO_HAB_ID, NUMERO, DETALLES, ESTATUS)
VALUES (NULL, 3, '111', 'Pantalla plana, Internet, Servibar, Escritorio, Cafetera', 'LIMPIA');
INSERT INTO HABITACIONES (HABITACION_ID, TIPO_HAB_ID, NUMERO, DETALLES, ESTATUS)
VALUES (NULL, 4, '120', 'Pantalla plana, Internet, Servibar, Escritorio, Balcón, Cafetera', 'LIMPIA');
INSERT INTO HABITACIONES (HABITACION_ID, TIPO_HAB_ID, NUMERO, DETALLES, ESTATUS)
VALUES (NULL, 5, '430', 'Pantalla Plana, Cafetera',  'LIMPIA');
COMMIT WORK;

Empleados

INSERT INTO EMPLEADOS (EMPLEADO_ID, NOMBRE, MATERNO, PATERNO, USUARIO, CLAVE, ESTATUS)
VALUES (NULL, 'ELISEO', 'G.', 'N.', 'INSTALADOR', 'DelphiAccess', 'SUPER')

Con esto hemos terminado de diseñar nuestra Base de Datos que por supuesto nada tiene que ver con un ERP Hotelero, sin embargo, nos servirá perfectamente para los fines de éste proyecto multiplataforma.

En el siguiente episodio comenzaremos a construir los métodos REST de nuestro Back-End.

*** Mientras tanto los invito a publicar sus comentarios y/o sugerencias acerca de esta entrada.

Nos vemos en la próxima

Nos vemos en la próxima entrada.

Proyecto Multiplataforma

Hola amigos,

En el segundo Embarcadero Dev Lounge Latinoamérica al cual fui invitado hablamos acerca de porqué desarrollar para Linux, los pros y los contras de aprovechar dicha plataforma y algunas estadísticas acerca de segmento de mercado ocupado tanto por la versión Servidor como de la versión Escritorio y me recordó que en el CodeRage XII presenté la experiencia que obtuve al desarrollar una aplicación multiplataforma.

En dicha presentación mostré un ejercicio que intentaba transportar una aplicación de escritorio VCL a Linux utilizando el nuevo FMXLinux y por supuesto quise hacerlo también a las diversas plataformas que tenía a la mano, es decir, una Laptop con Windows, una Tableta con Android, una Laptop con OSX (Air Pro) y una Máquina Virtual con Linux (Ubuntu).

El resultado de ese primer intento es la siguiente imagen:

Dicha imagen muestra visualmente que se puede crear la misma aplicación para las diversas plataformas, sin embargo, aún no estaba de todo lista ya que por falta de tiempo en ese momento, agregué accesibilidad de Base de Datos solo a la aplicación Linux y a las demás plataformas solo interfaz gráfica, es decir, sin acceso a Base de Datos.

Y bueno, como ya estarán enterados, próximamente se lanzará al mercado el nuevo Delphi 11, por lo que aprovecharé este lanzamiento para continuar con mi proyecto multiplataforma el cual intenta mostrar que podemos desarrollar un Proyecto Multiplataforma con nuestra querida herramienta de trabajo.

Así que para iniciar esta aventura comenzaré por plasmar la idea de lo que quiero desarrollar.

Espero que el poco tiempo libre que me queda en el día a día me permita avanzar a un buen ritmo y sobre todo aprendiendo algo nuevo cada día.

Idea de concepto

Desarrollar un API que corra en un servidor (Windows/Linux) que permita acceder a él desde cualquier aplicación cliente ya sea desarrollada con Delphi o con cualquier otro lenguaje.

Alcance del proyecto

Gestionar la operación básica de un hotel desde el registro del huésped (CheckIn), el uso de las instalaciones (Consumos) hasta la salida del Hotel (CheckOut).

Etapas

Las etapas iniciales de éste proyecto contemplan las siguientes tareas:

  1. Creación de la Base de Datos (Firebird/PostgreSQL/Interbase/MySQL – La que se quiera utilizar). ***Ver el Episodio 1***
  2. Creación del núcleo del proyecto (API REST) con métodos para gestionar la operación del Hotel. ***Ver Episodio 2.1*** ***Ver Episodio 2.2***
  3. Creación de un cliente de escritorio (Windows / Linux). ***Ver Episodio 3***
  4. Creación de un cliente Móvil (Android).
  5. Creación de un cliente Web (Herramienta por definir).

Nota: Como en la vida real, dichas etapas podrán sufrir cambios de acuerdo a las necesidades y/o nuevas ideas que surjan durante el desarrollo de cada una de ellas ya sea propias o de ustedes que me acompañaran en este viaje.

Nos vemos en la próxima

Nos vemos en la próxima entrada.

Agregando reporteador a Delphi 10.2 Starter

Hola amigos,

Como hemos visto en los artículos anteriores, la Edición Delphi 10.2 Starter no cuenta con algunos componentes importantes para el desarrollo de aplicaciones y una de estas limitaciones es que no dispone de la posibilidad de generar reportes, por tal razón, comencé a investigar si existía algún componente que nos permitiera obtener reportes en nuestras aplicaciones y que además se pudiera instalar en ésta Edición de Delphi 10.2 Starter.

Intenté instalar algunos reporteadores conocidos sin éxito ya que éstos no tenían compatibilidad con Delphi 10.2 Starter, finalmente encontré una biblioteca llamada Virtual Print Engine (VPE) en su edición Community la cual instalé sin problemas ya que no tiene dependencia con componentes de Delphi y además sigue la misma filosofía ya que ésta biblioteca tiene el mismo concepto de Delphi Starter que es el principo de equidad el cual menciona que si utilizas la biblioteca para generar ganancias, les gustaría que se comprara la versión de pago lo cual me parece por demás aceptable.

La edición Community de VPE al ser gratuita también tiene sus limitaciones pero después de instalarlo y hacer algunas pruebas de concepto puedo asegurarles que es mas que suficiente para crear cualquier tipo de documento y exportarlo a PDF.

Para éste artículo requerimos de descargar el instalador de Report Engine and PDF Library, la versión que encontré disponible para éste tutorial es para Delphi XE2, sin embargo, no tuve problemas para instalarlo y utilizarlo en Delphi 10.2 Starter.

Al terminar la descarga procederemos a instalar la versión de 32 bits, recuerden que Delphi Starter solo tiene soporte para la arquitectura de 32bits y no para arquitectura de 64bits.

Ejecutamos el programa vpec32.exe el cual instalará el motor de impresión en nuestra maquina.

Agregar el componente a Delphi es muy fácil, solo cargamos el proyecto VpevclXe2.dproj en nuestro IDE lo compilamos y finalmente lo instalamos. El componente se instalará en el grupo System con el nombre TVPEngine como pueden observar en la siguiente imagen.

Nota 1: Antes de compilar deberá agregar el Path del componente en las opciones de Delphi en Tools –> Options — Delphi Options –> Library  –> Library Path como se observa en la siguiente imagen.

Nota 2: Si al compilar les genera el siguiente error:

[MakeDir Error] Unable to create directory «.\Win32\Debug\». Access to the path ‘.\Win32\Debug\’ is denied.
Failed

Deberán ejecutar Delphi con permisos de Administrador.

Ya estamos listos para comenzar a experimentar con éste nuevo componente, para ello vamos a abrir el proyecto que hemos estado desarrollando en ésta serie y vamos a hacer algunos cambios a la interfáz gráfica que me parecen adecuados.

Vamos a eliminar los componentes TDBGrid y TDataSurce de nuestra forma ya que no serán necesarios y agregamos un TButton y el nuevo componente TVPEngine.

Usted decida como quiere ver su interfáz gráfica, en mi caso se verá de la siguiente forma:

Antes de continuar les diré que el concepto del Virtual Printer Engine hace que no solo sea un simple reporteador sino un motor de informes para crear dinámicamente cualquier tipo de documento ya sea simple o complejo. La disposición de objetos me recordó viejos tiempos donde todo lo hacíamos a mano (me ha traido gratos recuerdos), sin embargo, a pesar de que en apariencia no se vea «tan profesional» nos proporciona el control total de como queremos mostrar la información .

El proceso para generar un informe es muy simple,

  • Se abre el TPVEngine
  • Se colocan los objetos (texto, imagen, lineas, etc) en las coordenadas deseadas,
  • Se muestra y se imprime o se genera un archivo PDF
  • Se cierra elTPVEngine.

Nota importante: En la edición Community la cual es gratuita, no se tiene la posibilidad de imprimir los documentos desde código, primero se tiene que visualizar en pantalla y después se podrá mandar a imprimir desde su visor, las ediciones de pago si que tienen mayores caracterìsticas.

Ya estamos listos para escribir código. Nuestra ventana tiene dos botones nuevos, uno para generar el documento en pantalla (Vista Previa) y el otro para exportar la información en un archivo PDF (Exporta a PDF).

Como ya les comenté el proceso es muy sencillo y lo vamos a hacer por separado para cada uno de los botónes con el objeto de que se tenga más comprensión del mismo.

Generamos la consulta de los tipos de cambio en un rango de fechas presionando el botón Vista Previa:

Se ejecuta el siguiente proceso:

  • Abrimos la consulta pasando el rango de fechas donde deseamos obtener.
  • Si se encuentra información, se genera el reporte.
    • Se genera el encabezado
    • Se genera el detalle de la información
  • Se muestra la Vista Previa del reporte
  ZQuery1.Close;
  ZQuery1.ParamByName('ini').asString := FormatDateTime('yyyy-mm-dd',dtpInicial.Date);
  ZQuery1.ParamByName('fin').AsString := FormatDateTime('yyyy-mm-dd',dtpFinal.Date);
  ZQuery1.Open;

  if not ZQuery1.IsEmpty then
  begin
    generaReporte;
    Report.Preview;
  end;

  ZQuery1.Close;
end;

En el procedimiento generaReporte se encuentra el código para la impresión de la información enmcabezado y detalle de la información.

procedure TForm1.GroupHeader(Fecha: String);
var
   y: double;
begin
  Report.TextAlignment := ALIGN_LEFT;
  y := Report.Print(1.5, Report.nBottom + 0.2, Format('[N S 14 C Blue]%s', [Fecha]));
  y := y + 0.1;
  Report.Print(1.5, y, '[S 10 C Purple]Clave');
  Report.Write(3.5, y, 6, VFREE, '[R]Tipo de Cambio');
  Report.Write(6.5, y, 19.5, VFREE, '[L]Descripción');
end;

procedure TForm1.DetailLine(color: TColor; Clave: String; TC:Real; Descrip: String);
var
  y: double;
begin
  y := Report.nBottom;
  Report.BkgMode := VBKG_SOLID;
  Report.BkgColor := color;
  Report.PenColor := color;
  Report.Box(1.5, y, 19.5, LineHeight+0.3);
  Report.BkgMode := VBKG_TRANSPARENT;

  Report.WriteBox(1.5, y, -4, LineHeight+0.3, Format('[S 7 C Black L]%s', [Clave]));
  Report.TextAlignment := ALIGN_RIGHT;
  Report.WriteBox(3.5, y, 6, LineHeight+0.3, Format('%4.5n', [TC]));
  Report.TextAlignment := ALIGN_LEFT;
  Report.WriteBox(6.5, y, 19.5, LineHeight+0.3, Format('%s', [Descrip]));
end;

procedure TForm1.generaReporte;
var
  I: integer;
  y: double;
  color: TColor;
  FechaCorte: TDateTime;
begin
  if Report.IsOpen then Report.CloseDoc;
  Report.OpenDoc;
  Report.RenderPrintBox(0, 0, '[S 14 C Black]x');
  LineHeight := -Report.nRenderHeight;
  Report.AutoBreakMode := AUTO_BREAK_NO_LIMITS;
  Report.SetPen(0.05, PS_SOLID, COLOR_BLACK);
  Report.Picture(1.5, 1.0, 8.5, 3.0, 'Imagenes\DenM.bmp');
  Report.WriteBox(9.5, 2.0, 19.5, 3, '[N S 9 CE I C Black BC Gray TO]Tipos de Cambio del ' +
                   FormatDateTime('dd "de" mmmm "de" yyyy',ZQuery1.ParamByName('INI').AsDate) + ' al ' +
                   FormatDateTime('dd "de" mmmm "de" yyyy',ZQuery1.ParamByName('FIN').AsDate) );
  y := Report.nBottom + 1;
  Report.Line(1.5, y, 19.5, y);
  Report.nBottom := y + 0.1;
  GroupHeader(FormatDateTime('d "de" mmmm "de" yyyy', ZQuery1.FieldByName('FECHA').AsDateTime));
  FechaCorte := ZQuery1.FieldByName('FECHA').AsDateTime;

  for I := 0 to ZQuery1.RecordCount-1 do
  begin
    if FechaCorte <> ZQuery1.FieldByName('FECHA').AsDateTime then
    begin
      GroupHeader(FormatDateTime('d "de" mmmm "de" yyyy', ZQuery1.FieldByName('FECHA').AsDateTime));
      FechaCorte := ZQuery1.FieldByName('FECHA').AsDateTime;
    end;
    if I mod 2 = 0 then
       color := COLOR_GRAY
    else
       color := COLOR_LTGRAY;
    DetailLine(color, ZQuery1.FieldByName('Clave').AsString,
	                  ZQuery1.FieldByName('TipCamb').AsFloat,
					  ZQuery1.FieldByName('Descripcion').AsString);
    ZQuery1.Next;
  end;

  Report.PageBreak;
  Report.SetFont('Arial', 10);
  Report.BkgMode := VBKG_SOLID;
  Report.BkgColor := clRed;
end;

Las funciones que se utilizan cuentan con toda la información necesaria para «pintar» la información, a continuación estudiaremos algunas.

Report.RenderPrintBox(0, 0, ‘[S 14 C Black]x’);

Se genera el espacio de trabajo desde el punto [0,0] , (S)tamaño de letra 14 y0(C)olor Negro.

Report.SetPen(0.05, PS_SOLID, COLOR_BLACK);

Asignamos el estilo de la pluma con espesor de 0.5, (PS)estilo sólido y (C)olor Negro.

Report.Picture(1.5, 1.0, 8.5, 3.0, ‘Imagenes\DenM.bmp’);

Con éste método se inserta la imagen DenM.bmp en la posición [1.5,1.0] [8.5,3.0].

Report.WriteBox(9.5, 2.0, 19.5, 3, ‘[N S 9 CE I C Black BC Gray TO]TEXTO’);

Se imprime un texto libre a partir de las cordenadas [9.5,2.0] [19.5,3.0] pero además se le indica las propiedades con las que será mostrado, (N) le indica que no guarde ésta configuración para los demás textos, (S) tamaño de letra 9, (CE)ntrado, fuente (I)talica, (C)olor Negro y (BC)Fondo Gris (TO)Sólido

Al dar clic al botón de Vista Previa el programa nos mostrará el reporte como se ve en la siguiente imagen:

Una vez que tenemos la Vista Previa podremos imprimir el documento y es que la edición Community de VPE no permite enviar a impresión desde código y solo puede hacerse desde la Vista Previa, eso y mucho mas se podrá hacer si se adquiere una de las versiones de pago.

Ahora vamos a crear un archivo PDF con el código asignado al botón Exporta a PDF.

En éste botón se ejecuta el siguiente proceso:

  • Abrimos la consulta pasando el rango de fechas donde deseamos obtener.
  • Si se encuentra información, se genera el reporte.
    • Se genera el encabezado
    • Se genera el detalle de la información
  • Se exporta la información a un archivo PDF
procedure TForm1.Button4Click(Sender: TObject);
begin
  ZQuery1.Close;
  ZQuery1.ParamByName('ini').asString := FormatDateTime('yyyy-mm-dd',dtpInicial.Date);
  ZQuery1.ParamByName('fin').AsString := FormatDateTime('yyyy-mm-dd',dtpFinal.Date);
  ZQuery1.Open;

  if not ZQuery1.IsEmpty then
  begin
    generaReporte;
    ExportDocumentNoAsk(Report, ExtractFilePath(Application.ExeName) + 'Report_' +
                                FormatDateTime('yyyymmdd_hhnnss', now) + '.pdf');
  end;

  ZQuery1.Close;
end;

Éste proceso creará el archivo PDF y posteriormente nos lo mostrará, ésto se genera en el procedimiento ExportDocumentNoAsk(). Éste procedimiento sustituye al Visor de reportes del botón anterior.

procedure TForm1.ExportDocumentNoAsk(Doc: TVPEngine; FileName: String);
begin
   if Doc.WriteDoc(FileName) then
   begin
     MessageDlg('Archivo PDF "' + FileName + '" creado satisfactoriamente.',
                 mtInformation, [mbOk], 0);
     ShellExecute(0, nil, PCHAR(Filename), nil, nil, SW_SHOWMAXIMIZED);
   end
   else begin
          MessageDlg('Error creating PDF file "' + FileName + '"!' + #10 +
                     'Possible reasons:' + #10 +
                     ' - the file is open in Acrobat Reader' + #10 +
                     ' - hard disk full' + #10 +
                     ' - no access rights to the folder' + #10 +
                     ' - out of memory' + #10 +
                     ' - export module missing', mtInformation, [mbOk], 0);
   end;
end;

Al terminar nos mostrará el archivo PDF creado como se ve en la imagen siguienre:

Hay muchas otras funciones, procedimientos que pueden estudiarse en el manual de control de referencia

Hasta aquí llegamos con la tercera entrega de la serie en la cual hemos abarcado solo tres características de uso común en cualquier aplicación (Consumo de webService, Acceso a Base de Datos y Generación de Reportes) y que la edición Starter de Delphi 10.2 no tiene disponibles.

Como siempre, espero que ésta información sea de su agrado pero sobre todo de utilidad. Por supuesto que el código presentado en todos mis artículos son hechos de la forma más básica posible con la intención de que sea fácil de entender por aquellos que están dando sus primeros pasos y nutrida por los grandes maestros que nos visitan.

Solo me resta decir que sigo investigando que mas se puede hacer, si alguien tiene alguna idea que quieran que se realice, con gusto estudiaremos si es posible desarrollarla con Delphi 10.2 Starter.

Hasta la próxima


Todo el código escrito aquí es de libre descarga y utilización solo te pido, si te parece correcto, que menciones su origen y claro cualquier mejora que le hagas publicala que se agradecerá enormemente.

Muchas gracias.

Acceso a base de datos SQLite desde Delphi Starter

Hola amigos,

En ésta entrada vamos a realizar la conexión con una base de datos SQLite utilizando Delphi Starter Edition, versión 10.2 Tokyo y como ya se habrán dado cuenta, ésta edición de Delphi no cuenta con los componentes necesarios para la conexión a bases de datos, sin embargo, si tenemos la posibilidad de instalar componentes de terceros para poder acceder a motores de base de datos y expandir las características de nuestra herramienta de desarrollo.

Los componentes que utilizaremos en éste tutorial son los componentes ZEOS que nos permitiran dar continuidad a ésta serie aprovechando las facilidades que nos ofrece Delphi Starter Edition.

Para realizar ésta entrada utilizaremos la aplicación que desarrollamos en el artículo anterior «Consumir un servicio web con Delphi 10.2 Starter y cURL« donde obtenemos información del tipo de cambio del día del peso mexicano contra monedas de otros países.

Una vez que obtenemos dicha información tendrémos la capacidad de almacenarla para efectos de mantener un histórico de «paridades» y poder consultar el valor de una moneda en un día determinado.

Para poder llevar a cabo éste tutorial necesitamos descargar el componente de acceso base de datos ZEOS el cual pueden descargar del siguiente enlace: ZeosLib versión 7.2.1 Release Candidate

El motor de base de datos que usarémos será SQLite donde crearemos dos tablas y una vista para utilizarlas en nuestra aplicación, dicha base de datos estará incluida en los archivos de descarga al final de éste tutorial, sin embargo, si deseas crearla tu mismo se entregarán los DDL para que las generes con el administrador SQLite de tu preferencia, si no conoces alguno puedes descargar SQLite Studio Database Manager que me parece un muy buen administrador.

Comenzaremos por instalar los componentes ZEOS versión 7.2.1 rc, el paquete que se usará para Delphi Starter es el contenido en el directorio DelphiXE10.

Carga el proyecto ZeosDbo.bpg en Delphi y compila todos los bpl’s en el siguiente orden:

• ZCore.bpl
• ZParseSql.bpl
• ZPlain.bpl
• ZDbc.bpl
• ZComponent.bpl

Para que puedan compilar los BPL’s es necesario agregar los siguientes directorios a la ruta de las librerias/bibliotecas que se localiza en el menu Tools -> Options -> Delphi Options -> Library -> Library Path.

C:\Compartidos\Starter\ZEOSDBO-7.2.1-rc\packages\DelphiXE10;
C:\Compartidos\Starter\ZEOSDBO-7.2.1-rc\src\component;
C:\Compartidos\Starter\ZEOSDBO-7.2.1-rc\src\dbc;
C:\Compartidos\Starter\ZEOSDBO-7.2.1-rc\src\parsesql;
C:\Compartidos\Starter\ZEOSDBO-7.2.1-rc\src\plain;
C:\Compartidos\Starter\ZEOSDBO-7.2.1-rc\src\Core

Una vez que compilamos correctamente el proyecto ZeosDbo aparecerá el grupo Zeos Access en la paleta de herramientas como se muestra en la siguiente imagen:

El siguiente paso es la creación de la base de datos SQLite que utilizaremos en ésta sección y como les comenté pueden usar la base de datos que está incluida en las descargas al final de éste tutorial o pueden crearla con el Lenguaje de Definición de Datos (DDL)  desde cualquier Administrador SQLite.

--
-- File generated with SQLiteStudio v3.1.1 on mar may 30 21:19:36 2017
--
-- Text encoding used: System
--
PRAGMA foreign_keys = off;
BEGIN TRANSACTION;

-- Table: TC_CATALOGO
DROP TABLE IF EXISTS TC_CATALOGO;

CREATE TABLE TC_CATALOGO (
    ID          INTEGER      NOT NULL
                             PRIMARY KEY AUTOINCREMENT,
    CLAVE       STRING (20)  NOT NULL,
    DESCRIPCION STRING (200) 
);


-- Table: TC_HISTORICO
DROP TABLE IF EXISTS TC_HISTORICO;

CREATE TABLE TC_HISTORICO (
    ID      INTEGER        NOT NULL,
    FECHA   DATE           NOT NULL,
    TIPCAMB DECIMAL (7, 4),
    PRIMARY KEY (
        ID,
        FECHA
    )
);


-- View: TC_LISTADO
DROP VIEW IF EXISTS TC_LISTADO;
CREATE VIEW TC_LISTADO AS
    SELECT CAT.CLAVE,
           HIST.FECHA,
           HIST.TIPCAMB,
           CAT.DESCRIPCION
      FROM TC_HISTORICO HIST
           INNER JOIN
           TC_CATALOGO CAT ON CAT.ID = HIST.ID;


COMMIT TRANSACTION;
PRAGMA foreign_keys = on;

Despues de crear las tablas vamos a inicializar el catálogo de monedas para que se tenga la información necesaria para enlazar la información que vamos a recibir del Servicio Web de Banxico.

INSERT INTO TC_CATALOGO (DESCRIPCION,CLAVE,ID)
VALUES	('Tipo de cambio pesos por dólar E.U.A. Fecha de liquidación.','DLL01',1),
		('Tipo de cambio Pesos por dólar E.U.A. Fecha de determinación (FIX)','DLL02',2),
		('Cotización de las divisas que conforman la canasta del DEG Respecto al peso mexicano Euro','EURO',3),
		('Cotización de la divisa Respecto al peso mexicano Dólar Canadiense','DLLCA',4),
		('Cotización de las divisas que conforman la canasta del DEG Respecto al peso mexicano Yen japonés','YEN',5),
		('Cotización de las divisas que conforman la canasta del DEG Respecto al peso mexicano Libra esterlina.','LIBRA',6);

En éste momento ya tenemos los componentes de acceso a base de datos y la base de datos listos, ahora comenzaremos a dar forma a nuestro proyecto.

Abra el proyecto wsStarter.dproj que se creo en el tutorial anterior (Si no lo has creado, puedes descargarlo siguiendo éste enlace).

Agregue los siguientes componentes a la forma principal.

• TButton
• TDataSource;
• TDateTimePicker
• TDBGrid
• TZConnection;
• TZQuery;

Después de organizar a tu gusto los componentes que se agregaron ya tendremos nuestra forma similar a la siguiente imagen:

Configuramos la conexión en el componente ZConnection1  asignando la ruta de la base de datos y el protocolo del motor de la base de datos que vamos a usar, en éste caso sqlite-3.

Ésta configuración de la base de datos está en tiempo de diseño pero lo recomendable es hacerlo en tiempo de ejecución ya que la base de datos puede ser colocada en cualquier lugar ya sea en la misma máquina o en otra máquina de la red. Para configurarla en tiempo de ejecución pueden hacer lo siguiente:

procedure TForm1.FormCreate(Sender: TObject);
begin
  ZConnection1.Database := ExtractFilePath(Application.ExeName) + 'base\TipoCambio.sqlite3';
end;

(También puede ser a través de la lectura de un archivo INI, XML o el mismo registro de windows, como mejor les parezca.)

Ahora vamos a decidir que vamos a hacer con la información que se reciba del Servicio Web de Banxico. Yo he decidido mostrar la información seleccionando la fecha deseada en el componente TDateTimePicker1 y mostrar los tipos de cambio de ese día (si ya se ha descargado), para ello utilizaremos la vista que previamente se creo en la base de datos llamada TC_LISTADO ejecutandola desde el componente ZQuery1 el cual ya debe estar enlazado al DataSource y a su vez el DataSource enlazado al DBGrid.

La consulta es muy simple y  es la siguiente:

select * from TC_LISTADO list
where list.FECHA = :fecha

Ya estamos listos para modifcar el proyecto y acceder a una base de datos. Comenzamos agregando las siguientes unidades a la sección USES de nuestra unidad uXMLclass.pas.

• ZDataset,
• ZAbstractConnection;

Posteriormente agregamos una variable y una propiedad a la clase TRequestResponse para la conexión con la base de datos.

• ZQryTC: TZQuery;

• Fconexion: TZAbstractConnection;

• property conexion: TZAbstractConnection read Fconexion write Fconexion;

Finalmente harémos una modificación al procedimiento TRequestResponse.BuscaTC() para leer el XML de Respuesta obteniendo la Moneda, la Fecha y el Tipo de Cambio y agregando ésta información a nuestra tabla de históricos para consultas posteriores.

function TRequestResponse.buscaTC(xml: IXMLDocument): TStrings;
var
  I: integer;
  nodoData: IXMLNode;
  Linea: string;
  valor: variant;
  moneda: integer;
begin
  Result := TStringList.Create;
  Result.Text := '';
  nodoData := xml.ChildNodes[1].ChildNodes[1];
  if nodoData <> nil then
  begin
    ZQryTC := TZQuery.Create(nil);
    ZQryTC.Connection := Fconexion;
    ZQryTC.SQL.Text := 'INSERT INTO TC_HISTORICO(ID, FECHA, TIPCAMB) ' + #13 +
                       'VALUES(:id, :periodo, :tc) ';
    for I := 0 to nodoData.ChildNodes.Count-1 do
    begin
      if nodoData.ChildNodes[I].NodeName = 'bm:Series' then
      begin
         if pos('FIX',nodoData.ChildNodes[I].Attributes['TITULO']) > 0 then
            ZQryTC.ParamByName('ID').Value := 2
         else
         if pos('Euro',nodoData.ChildNodes[I].Attributes['TITULO']) > 0 then
            ZQryTC.ParamByName('ID').Value := 3
         else
         if pos('Canadiense',nodoData.ChildNodes[I].Attributes['TITULO']) > 0 then
            ZQryTC.ParamByName('ID').Value := 4
         else
         if pos('Yen',nodoData.ChildNodes[I].Attributes['TITULO']) > 0 then
            ZQryTC.ParamByName('ID').Value := 5
         else
         if pos('Libra',nodoData.ChildNodes[I].Attributes['TITULO']) > 0 then
            ZQryTC.ParamByName('ID').Value := 6
         else
            ZQryTC.ParamByName('ID').Value := 1;

         moneda := ZQryTC.ParamByName('ID').Value;

         ZQryTC.ParamByName('PERIODO').AsString :=
                 nodoData.ChildNodes[I].ChildNodes[0].Attributes['TIME_PERIOD'];

         valor := NULL;
         if nodoData.ChildNodes[I].ChildNodes[0].Attributes['OBS_VALUE'] <> '' then
            ZQryTC.ParamByName('TC').AsString :=
                 nodoData.ChildNodes[I].ChildNodes[0].Attributes['OBS_VALUE']
         else
            ZQryTC.ParamByName('TC').Value := valor;

         ZQryTC.ExecSQL;

         Result.Add(Moneda.ToString + #9 +
                    nodoData.ChildNodes[I].ChildNodes[0].Attributes['TIME_PERIOD'] + #9 +
                    nodoData.ChildNodes[I].ChildNodes[0].Attributes['OBS_VALUE'] );

      end;
    end;
    ZQryTC.Free;
  end;
end;

Con el código anterior recorremos los nodos de XLM para obtener la información que necesitamos, la insertamos en la base de datos y finalmente regresamos un informe de lo descargado a la forma principal como se puede observar en las siguientes imagenes:

Pareciera que la información mostrada careciera de sentido, sin embargo, la parte final de éste tutorial es mostrar un listado de los tipos de cambio de una fecha dada utilizando la vista que creamos anteriormente la cual es disparada por la siguiente consulta.

select * from TC_LISTADO
where FECHA = :fecha

Para ejecutar la consulta utilizamos el evento OnClick del  botón [Listado] y escribimos el siguiente código:

procedure TForm1.Button3Click(Sender: TObject);
begin
  ZQuery1.Close;
  ZQuery1.ParamByName('FECHA').Value := DateTimePicker1.Date;
  ZQuery1.Open;
end;

Ya tenemos listo todo y solo presionamos el botón [Listado] para que la información sea presentada en el Grid de nuestra forma como se observa en la siguiente imagen.

Como pueden ver, acceder a una base de datos con Delphi Starter no tiene mayor complicación y las posibilidades que se nos presentan son muchas, solo recuerden, de acuerdo a la licencia de ésta edición de Delphi Starter se nos indica que si logran obtener una ganancia igual o mayor a 1,000 dólares deben de adquirir una versión de pago, los invito a que hagan el esfuerzo, les aseguro que no se arrepentiran.

Y bueno hemos llegado al final de éste tutorial, espero que sea de su agrado y sobre todo que sea de utilidad.

Hasta la próxima


Todo el código escrito aquí es de libre descarga y utilización solo te pido, si te parece correcto, que menciones su origen y claro cualquier mejora que le hagas publicala que se agradecerá enormemente.

Muchas gracias.

Consumir un servicio web con Delphi 10.2 Starter y cURL

Hola amigos,

En ésta entrada les hablaré acerca de la versión ‘gratuita’ de Delphi llamada Delphi Starter Edition, versión 10.2 Tokyo la cual nos permite, según su contrato de licencia, generar aplicaciones comerciales aunque con algunas limitaciones propias de éste tipo de ediciones.

Si quieren conocer acerca de éste producto, su licencia de uso y las limitaciones propias de la versión, les proporciono una lista de enlaces que me parecen muy interesantes.

Quiero mencionar que la intención de ésta entrada es la de mostrar que con ésta versión podemos comenzar a desarrollar soluciones reales y no solo el famoso «Hola Mundo». Por supuesto al ser un producto gratuito tiene algunas limitaciones pero nada que nos evite hacer uso de nuestras capacidades y dar forma a aplicaciones de alto rendimiento.

Para ésta entrada les sugiero que descarguen el programa soapUI el cual es open source  y que nos facilitará la vida al generar un cliente para el consumo de Servicios Web así como descargar e instalar Delphi 10.2 Tokio Starter.

Una de las limitantes de la versión Starter es que no cuenta con el asistente para la importación del archivo WSDL el cual genera la unidad que implementa los métodos del Servicio Web para su consumo y aunque ésta unidad se puede obtener vía línea de comandos he querido hacerlo con una solución alternativa a través de un proyecto llamado cURL el cual está orientado a la transferencia de archivos y que soporta varios protocolos como FTP, FTPS, HTTP, HTTPS, TFTP, SCP, SFTP, Telnet, DICT, FILE y LDAP entre otros.

Cabe mencionar que éste proyecto lo conocí hace unos años, para ser exactos el 14 de Marzo de 2011, cuando nuestro amigo Domingo Seaone  publicó en el foro delphiaccess punto com una implementación de ésta biblioteca desarrollando una unidad con Delphi y que me ha permitido dar solución a varios requerimientos de clientes a lo largo de éstos 6 años que han pasado.

Pues bien, comenzaremos por obtener los archivos XML de Request y Response del Servicio Web a través de la aplicación soapUI, para ello usaremos el Servicio Web del Banco de México para consultar el tipo de cambio del peso frente al Dólar, el Euro, la Libra Esterlina y el Yen. La url del servicio web es la siguiente:

http://www.banxico.org.mx/DgieWSWeb/DgieWS?WSDL

Creamos un nuevo proyecto SOAP seleccionamos el método tiposDeCambioBanxico y enviamos la petición, el Servicio Web nos regresará la información en un archivo soap XML conteniendo los diferentes tipos de cambio como se muestra en la imagen siguiente:

La versión Starter de Delphi tampoco incluye el asistente XML Data Binding por lo que tenemos que construir el REQUEST y el RESPONSE de forma manual, bueno, no todo en la vida es fácil, pero veamos la situación como una buena oportunidad para aprender algo nuevo. 🙂

Ya estamos listos para comenzar nuestro tutorial, como primer paso vamos a crear una clase que generará la estructura del mensaje de petición SOAP para enviarla a través de cURL al servidor del Servicio Web y serializar la respuesta SOAP para obtener (en este caso)  los tipos de cambio disponibles del peso mexicano contra otras monedas.

La clase contendrá las propiedades, variables y métodos necesarios para el consumo del Servicio Web como se muestra a continuación.

TRequestResponse = class

type

  TRequestResponse = class
  private
    xmlArch: IXMLDocument;
    envelopeSOAP: IXMLNode;

    Curl: TCURL;
    curlResult: CURLcode;
    curlError: string;

    Stream: TMemoryStream;
    StreamOut: TMemoryStream;

    function creaRequest: IXMLDocument;
    function enviaRequest(url, Post: AnsiString; out Reply: AnsiString): Boolean;
    function RecursiveFindNode(ANode: IXMLNode; const SearchNodeName: string): IXMLNode;
    function buscaTC(xml: IXMLDocument): TStrings;

  public
    function ConsultaTipoDeCambio(endPoint: AnsiString): TStrings;

  end;

El método creaRequest genera el mensaje de petición SOAP utilizando el objeto IXMLDocument el cual se encargará de dar el formato adecuado para poder enviarlo al servidor donde está alojado el Servicio Web que queremos consumir.

function TRequestResponse.creaRequest: IXMLDocument;

function TRequestResponse.creaRequest: IXMLDocument;
begin
  xmlArch := NewXMLDocument;
  xmlArch.Encoding := 'UTF-8';
  envelopeSOAP := xmlArch.AddChild('soapenv:Envelope');
  with envelopeSOAP do
  begin
    Attributes['xmlns:ws'] := 'http://ws.dgie.banxico.org.mx';
    Attributes['xmlns:soapenv'] := 'http://schemas.xmlsoap.org/soap/envelope/';
    Attributes['xmlns:xsd'] := 'http://www.w3.org/2001/XMLSchema';
    Attributes['xmlns:xsi'] := 'http://www.w3.org/2001/XMLSchema-instance';
    AddChild('soapenv:Header');
    with AddChild('soapenv:Body') do
    begin
      with AddChild('ws:tiposDeCambioBanxico') do
      begin
        Attributes['soapenv:encodingStyle'] := 'http://schemas.xmlsoap.org/soap/encoding/';
      end;
    end;
  end;
  Result := xmlArch;
end;

Con éste código se genera el mensaje SOAP necesario para realizar la petición al Servicio Web y que será enviado por medio de cURL.

Por supuesto que habrá formas más efectivas de hacer esto, sin embargo ésta me parece a mi la más simple y controlada.

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
                  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.dgie.banxico.org.mx">
	<soapenv:Header/>
	<soapenv:Body>
		<ws:tiposDeCambioBanxico soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
	</soapenv:Body>
</soapenv:Envelope>

A continuación agregaremos la unidad creada por mi amigo Domingo Seoane la cual encapsula el método para enviar la petición al Servicio Web, dicha unidad se llama uCurl.pas la cual publicó en DelphiAccess.

A continuación agregamos la función para realizar la llamada a dicha unidad y que llamaremos enviaRequest. En ésta función necesitaremos la URL del Servicio Web y el Request que hemos creado con la función creaRequest, dicha función regresará dos valores, uno de tipo out donde obtendrémos el Response del Servicio Web y un boolean que nos indicará si la función fué procesada correctamente o no.

function TRequestResponse.enviaRequest(url, Post: AnsiString; out Reply: AnsiString): Boolean;

function TRequestResponse.enviaRequest(url, Post: AnsiString; out Reply: AnsiString): Boolean;

//******************************************************************************
      function ReadFromStream(Buffer: PAnsiChar; Size, Count: Integer;
        Stream: TStream): Integer; cdecl;
      begin
        Result:= Stream.Read(Buffer^,Size*Count) div Size;
      end;

      function SaveToStream(Buffer: PAnsiChar; Size, Count: Integer;
        Stream: TStream): Integer; cdecl;
      begin
        Result:= Stream.Write(Buffer^,Size*Count) div Size;
      end;

      function MemoryStreamToString(M:TMemoryStream): AnsiString;
      begin
        SetString(Result, PAnsiChar(M.Memory), M.Size);
      end;

      procedure ErrorCurl(const mensaje: string);
      begin
        raise Exception.Create(mensaje);
        exit;
      end;
//******************************************************************************

begin
  Result:= false;
  Curl:= curl_easy_init;
  if Curl <> nil then
  try
    if curl_easy_setopt(Curl, CURLOPT_VERBOSE, TRUE) <> CURLE_OK then
      ErrorCurl('No se pudo asignar CURLOPT_VERBOSE.');
    if curl_easy_setopt(Curl, CURLOPT_USE_SSL, CURLUSESSL_ALL) <> CURLE_OK then
      ErrorCurl('No se pudo asignar CURLOPT_USE_SSL.');
    if curl_easy_setopt(Curl, CURLOPT_SSL_VERIFYPEER, FALSE) <> CURLE_OK then
      ErrorCurl('No se pudo asignar CURLOPT_SSL_VERIFYPEER.');
    if curl_easy_setopt(Curl, CURLOPT_URL, PAnsiChar(url)) <> CURLE_OK then
      ErrorCurl('No se pudo asignar CURLOPT_URL.');
    if curl_easy_setopt(Curl, CURLOPT_POST, 1) <> CURLE_OK then
      ErrorCurl('No se pudo asignar CURLOPT_POST.');
    if curl_easy_setopt(Curl, CURLOPT_READFUNCTION, @ReadFromStream) <> CURLE_OK then
      ErrorCurl('No se pudo asignar CURLOPT_READFUNCTION.');
    if curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, @SaveToStream) <> CURLE_OK then
      ErrorCurl('No se pudo asignar CURLOPT_WRITEFUNCTION.');

    Stream    := TMemoryStream.Create;
    StreamOut := TMemoryStream.Create;

    try
      with TStringList.Create do
      try
        Add(unicodeString(Post));
        SaveToStream(Stream);
      finally
        Free;
      end;
      Stream.Position:= 0;
      if curl_easy_setopt(Curl, CURLOPT_INFILE, Stream) <> CURLE_OK then
        ErrorCurl('No se pudo asignar CURLOPT_INFILE.');
      if curl_easy_setopt(Curl, CURLOPT_POSTFIELDSIZE, Stream.Size) <> CURLE_OK then
        ErrorCurl('No se pudo asignar CURLOPT_POSTFIELDSIZE.');
      Header := nil;
      Header := curl_slist_append(Header, 'SOAPAction: ""');
      Header := curl_slist_append(Header, 'Accept-Encoding: gzip, deflate');
      Header := curl_slist_append(Header, 'Content-Type: text/xml;charset=UTF-8;');
      Header := curl_slist_append(Header, 'User-Agent: Apache-HttpClient/4.1.1 (java 1.5)');
      try
        if curl_easy_setopt(Curl, CURLOPT_FILE, StreamOut) <> CURLE_OK then
          ErrorCurl('No se pudo asignar CURLOPT_FILE.');
        if curl_easy_setopt(Curl, CURLOPT_HTTPHEADER, Header) <> CURLE_OK then
          ErrorCurl('No se pudo asignar CURLOPT_HTTPHEADER.');

        curlResult := curl_easy_perform(Curl);

        if curlResult = CURLE_OK then
        begin
          Reply := MemoryStreamToString(StreamOut);
          curlError := '';
          result := true;
        end
        else begin
               curlError := curl_easy_strerror(curlResult);
               result := false;
        end;

      finally
        curl_slist_free_all(Header);
      end;
    finally
      Stream.Free;
      StreamOut.Free;
    end;
  finally
    curl_easy_cleanup(Curl);
  end;
end;

Ya estamos a punto de terminar la clase que consumirá el Servicio Web, solo nos falta serializar el Response del Servicio Web lo cual harémos manualmente debido a que tenemos ciertas limitantes al no tener disponible el asistente XML Data Binding en el IDE de nuestro Delphi Starter. Cabe mencionar que si ustedes tienen una forma más elegante de hacerlo, no duden en retroalimentarnos para enriquecer éste tutorial.

Para ello vamos a utilizar dos funciones, una que tome prestada del sitio  stackoverflow y una que escribiremos nosotros mismos, dichas funciones son

function RecursiveFindNode(ANode: IXMLNode; const SearchNodeName: string): IXMLNode;

{
   Función basada en la original que se tomó del siguiente enlace
   http://stackoverflow.com/questions/28901757/delphi-find-xml-node
}
function TRequestResponse.RecursiveFindNode(ANode: IXMLNode; const SearchNodeName: string): IXMLNode;
var
  I: Integer;
begin
  if CompareText(ANode.NodeName, SearchNodeName) = 0 then
      Result := ANode
  else if not Assigned(ANode.ChildNodes) then
          Result := nil
       else begin
              for I := 0 to ANode.ChildNodes.Count - 1 do
              begin
                Result := RecursiveFindNode(ANode.ChildNodes[I], SearchNodeName);
                if Assigned(Result) then
                   Exit;
              end;
       end;
end;

function buscaTC(xml: IXMLDocument): TStrings;

function TRequestResponse.buscaTC(xml: IXMLDocument): TStrings;
var
  I: integer;
  nodoData: IXMLNode;
  Linea: string;
begin
  Result := TStringList.Create;
  Result.Text := '';
  nodoData := xml.ChildNodes[1].ChildNodes[1];
  if nodoData <> nil then
  begin
    for I := 0 to nodoData.ChildNodes.Count-1 do
    begin
      if nodoData.ChildNodes[I].NodeName = 'bm:Series' then
      begin
         Result.Add(nodoData.ChildNodes[I].Attributes['TITULO'] + #9 +
                    nodoData.ChildNodes[I].Attributes['BANXICO_UNIT_TYPE'] + #9 +
                    nodoData.ChildNodes[I].ChildNodes[0].Attributes['TIME_PERIOD'] + #9 +
                    nodoData.ChildNodes[I].ChildNodes[0].Attributes['OBS_VALUE'] );
      end;
    end;
  end;
end;

Por último vamos a ewscribir el código de la función principal de nuestra clase llamado ConsultaTipoDeCambio y que requiere de la URL del Servicio Web la cual enviaremos desde la forma principal.

function TRequestResponse.ConsultaTipoDeCambio(endPoint: AnsiString): TStrings;

function TRequestResponse.ConsultaTipoDeCambio(endPoint: AnsiString): TStrings;
var
  nodoBody: IXMLNode;
  nodoResult: IXMLNode;
  xml: IXMLDocument;
  response: AnsiString;

begin
  xml := creaRequest;
  xml.XML.SaveToFile(ExtractFilePath(Application.ExeName) + 'request_' +
                               formatDateTime('yyyymmdd_hhnnss',now) + '.xml' );
  if sendRequest(endPoint, AnsiString(xml.XML.Text), response) then
  begin
    try
      xmlArch := NewXMLDocument;
      xmlArch.XML.Text := response;
      xmlArch.Active := true;
      nodoBody := xmlArch.ChildNodes[1].ChildNodes[1];
      if nodoBody <> nil then
      begin
        nodoResult := RecursiveFindNode(NodoBody, 'result');
        if nodoBody <> nil then
        begin
          xml := TXMLDocument.Create(nil);
          xml.XML.Text := nodoResult.ChildNodes[0].NodeValue;
          xml.Active := true;
          xml.XML.SaveToFile(ExtractFilePath(Application.ExeName) +
                                                'response.xml', TEncoding.ANSI);
          Result := BuscaTC(xml);
        end
        else begin
               Result.Text := 'ERROR: No se pudo acceder al nodo "Result"';
        end;
      end
      else begin
             Result.Text := 'ERROR: No se pudo acceder al nodo "Body"';
      end;
    except
      on e: exception do
         Result.Text := 'ERROR: ' + e.Message;
    end;
  end;

end;

Ya tenemos nuestra unidad uXMLClass.pas la cual contiene la clase TRequestResponse completa y lista para ser usada por el programa principal.

En la forma principal vamos a agregar dos componentes TButton y un componente TMemo. Perdón pero suelo ser minimalista 😀

Acomodamos los componentes a nuestro gusto y escribiremos el siguiente código en el evento OnClick del componente TButton.

procedure TForm1.Button1Click(Sender: TObject);
var
  ReqResp: TRequestResponse;
  retorno: TStrings;
begin
  ReqResp := TRequestResponse.Create;
  retorno := TStringList.Create;
  retorno := ReqResp.ConsultaTipoDeCambio(AnsiString('http://www.banxico.org.mx/DgieWSWeb/DgieWS'));
  Memo1.Lines := retorno;
end;

Finalmente ejecutamos nuestra aplicación y deberíamos obtener la siguiente respuesta.

Con ésto concluimos éste pequeño tutorial donde pretendemos mostrar que se puede trabajar con ésta edición de Delphi y crear aplicaciones listas para dar solución a requerimientos reales. Por supuesto que hay de requerimientos a requerimientos pero la idea es comenzar nuestro «propio negocio» y poder obtener ingresos suficientes para adquirir una de las versiones de pago y dar el siguiente paso para que nuestro negocio se reafirme y podamos dar soluciones completas y mas profesionales.

Antes de despedirme les quiero decir que vamos a estar desarrollando algunos temas con Delphi Starter con lo que pretendemos aportar nuestro granito de arena, si tienen algún tema que pueda ser creado con ésta versión con gusto lo intentaremos desarrollar.

Les agradeceré que dejen sus comentarios, críticas y/o palabras de aliento que me darán el oxígeno necesario para continuar con ésta serie. 🙂

Hasta la próxima


Todo el código escrito aquí es de libre descarga y utilización solo te pido, si te parece correcto, que menciones su origen y claro cualquier mejora que le hagas publicala que se agradecerá enormemente.

Muchas gracias.

Delphi 10.2 Tokio

Hola amigos,

Después de un par de semanas donde tuve algunos problemas con mi sitio, hemos regresado con una nueva imagen y no encontré mejor forma que hablando de la característica más esperada de Delphi.

En efecto, la posibilidad de desarrollar aplicaciones que corran en Linux se ha hecho realidad, se está hablando por todo el mundo y mi bitácora no iba a ser la excepción.

Para poder generar mi primer aplicación he seguido el video tutorial de mi buen amigo Luis Felipe González quien de una forma sencilla y muy didáctica muestra como instalar una máquina virtual con Ubuntu Server 16.04.2 para desarrollar aplicaciones Delphi.

No voy a repetir lo que nuestro amigo amablemente nos ha proporcionado para respetar su trabajo y darle los créditos que merece, así que pueden acceder al video tutorial dando clic en el siguiente enlace:

Delphi 10.2 Tokyo. Configurar el Ambiente Linux usando Ubuntu 16.04.2 LTS

Siguiendo el video tendremos lo necesario para crear nuestra primer aplicación en Linux, sin embargo, ésta entrada mostrará la instalación de Delphi 10.2 Tokio y para poder realizarla he utilizado el Trial que está disponible en la página de Embarcadero y que puedes descargar desde el siguiente enlace Start for free.

Una vez descargado el instalador procedemos a ejecutarlo para iniciar el proceso.

 

 

Para iniciar nos muestra la pantalla de selección de las plataformas que se desean instalar, para éste ejercicio solo he seleccionado Delphi con todas las plataformas disponibles, Windows 64 bits, Windows 32 bits, OS X, iOS, Android y el ya famoso Linux 64 bits.

 

 

Posteriormente tenemos que seleccionar opciones adicionales, para éste ejercicio solo vamos a seleccionar solo unas cuantas, una vez que estén instalado nuestra herramienta podrán seleccionar de nuevo las que se deseen tener instaladas como lo veremos mas adelante.

Damos clic en el botón Install para continuar.

 

 

A continuación nos pide leer cuidadosamente las licencias de cada uno de las dependencias involucradas. Si están de acuerdo con las licencias presione el botón Agree all para continuar.

 

 

Con ésto terminamos el proceso de instalación de la herramienta. Presionamos el botón START WORKING para que se muestre el IDE de Delphi 10.2 Tokio.

 

 

El IDE de Delphi 10.2 Tokio no sufrió cambios aparentes en comparación con Delphi 10.1 Berlín, sin embargo, se reorganizaron algunos elementos del menú para un mejor manejo y una navegación más rápida como lo podemos observar en la siguiente lámina obtenida del Webinar del lanzamiento de Delphi 10.2 Tokio.

 

En éste punto ya tenemos listo nuestro Delphi para comenzar a experimentar, pero antes procederemos a configurar nuestro ambiente de desarrollo que es el que nos permitirá acceder a Linux desde Delphi.

Lo primero que necesitamos es transferir el archivo LinuxPAServer19.0.tar.gz que se localiza en el directorio PAServer de nuestra instalación a Linux utilizando un cliente de ftp, en mi caso Bitvise SSH Client que es el que utilizo como Terminal SSH y FTP.

Ya teniendo nuestro PAServer en Linux procederemos a descompactarlo con la instrucción que nos mostró Luis Felipe en su video, ejecutamos el paserver, no asignamos contraseña y ya tenemos nuestro ambiente de trabajo en linux listo para aceptar llamadas desde Delphi.

tar -xvzf LinuxPAServer19.0.tar.gz

 

 

Solo nos falta crear el perfil de la conexión con el PAServer que está corriendo en la maquina remota Linux, para ello damos clic en el Menu ToolsOptions y seleccionamos la opción SDK Manager, damos clic en el boton Add… y nos pedirá tres datos IP Remota, Puerto y Contraseña, en éste caso la IP de mi Servidor Linux es 192.168.1.67, el puerto es el asignado por el PAServer 64211 y no asigné contraseña. Presionamos el botón de Test Connection. y si se siguió paso a paso el tutorial de Luis Felipe verémos la confirmación de que la conexión con el servidor Linux a sido satisfactoria.

 

 

 

Ya está todo listo para comenzar a escribir código y para ello crearemos un nuevo proyecto de tipo consola dando clic en la opción Create a new project… – Delphi Projects ó a través del Menú File – New – Other – Delphi Projects

 

Asignamos la plataforma de Linux 64-bits, escribimos nuestro mensaje en el bloque correspondiente, ejecutamos y listo, ya hemos generado nuestro primer «Hola Mundo» corriendo en Linux como se puede observar en la siguiente imagen.

 

 

Con ésto hemos terminado éste pequeño ejercicio pero muy esperanzador en cuanto al potencial que tenemos con ésta nueva versión de Delphi.

Quiero finalizar parafraseando a Neil Armstrong.

«Es un pequeño paso para el programador, pero un gran paso para la comunidad»

 

 

 

 

 

 

Hasta la próxima