Aspectos a tener en cuenta en el despliegue de una aplicación xbase

Esta entrada se publicó originalmente en Harbour Magazine, mi publicación sobre el lenguaje de programación Harbour.

El despliegue es la actividad consistente en instalar una aplicación en el ordenador de nuestro usuario. Una vez que hemos probado nuestro programa y consideramos que está listo para que nuestro usuario lo utilice, debemos proceder a instalarlo en su PC. En este momento se plantean distintas maneras de realizar el despliegue, que varían en función del tipo de software que realicemos. Si quieres leer más sobre el tema te recomiendo la entrada Cincomundos de JoelonSoftware — https://www.joelonsoftware.com/2002/05/06/five-worlds/.

En mi caso, desarrollo software ‘empaquetado’, es decir aplicaciones que el usuario instala o actualiza por si mismo, y no tengo manera de acceder al PC del usuario, por lo que el despliegue toma una gran importancia. No me puedo permitir que un usuario actualice un programa y se produzca un error, por lo que tengo que realizar una serie de acciones dentro del programa que garanticen que la actualización se realiza de manera correcta. Esto no forma parte del despliegue propiamente dicho, pero son aspectos muy importantes cuando necesitas que tu programa no de un error tras una instalación.

1.- Comprobar que existen los ficheros de la aplicación.

El primer aspecto a considerar es que existan todos los ficheros de datos de la aplicación en la ruta predeterminada del programa. Por ello al arrancar nuestro programa debemos hacer esta comprobación, y en caso de que falte algún fichero proceder a crearlo. Una manera sencilla es la que muestro a continuación:

METHOD CheckFiles() CLASS tApplication
LOCAL i := 0
LOCAL nLen := 0
LOCAL aFiles := { "familias.dbf", "familias.cdx", ;
? "productos.dbf", "productos.cdx", ;
? "tickets.dbf", "tickets.cdx", ;
? "lineas.dbf", "lineas.cdx" }
// compruebo que están los ficheros
nLen := Len( aFiles )
FOR i := 1 TO nLen
? IF !File( ::cDbfPath + aFiles[ i ] )
? Ut_Actualizar()
? Ut_Indexar()
? EXIT
? ENDIF
NEXT

Más adelante explicaré que hacen las funciones Ut_Actualizar() y Ut_Indexar(), pero básicamente comprueban que los ficheros tienen todos los campos y vuelven a generar índices.

2.- Comprobar si ha cambiado la versión del programa.

Conjuntamente con la comprobación del punto anterior hago la comprobación de que haya cambiado la versión del programa. Las versiones de mis programas siempre tienen la siguiente forma: “x.y.z” donde:

  • x indica la versión mayor. Cuando introduzco funcionalidades que supone añadir tablas a una aplicación incremento la versión mayor.
  • y indica la versión menor. Cuando libero una actualización siempre incremento la versión menor, y cuando esta actualización supone añadir o modificar un campo de una tabla incremento la decena de este número, pasando por ejemplo de la 1.1.a a la 1.10.a.
  • z indica una corrección de errores de la versión. No añado funcionalidades, únicamente corrijo errores de la versión menor actual.

Cuando cambia la versión mayor o la decena de la versión menor llamo a Ut_Actualizar() y Ut_Indexar().

3.- Creación de las tablas del programa desde código.

Llegamos a la marte más importante de la entrada: debes tener escrita la definición de tablas de tu aplicación, de manera que esta pueda crearlas o modificar su estructura cada vez que lo necesites. Ten en cuenta que estamos hablando de software empaquetado, en que no es posible que vayas al PC de tu usuario a modificar la estructura de una tabla usando un programa de tipo DBU.

Mi función Ut_Actualizar lo hace de esta manera:

// productos
oSay:SetText( 'Fichero de Productos' )
dbCreate( oApp():cDbfPath + 'pr', { ;
{ 'PrNombre', 'C', 40, 0 }, ; // Nombre del producto
{ 'PrFaNombre', 'C', 40, 0 }, ; // Nombre de la familia
{ 'PrPrecio', 'N', 6, 2 }, ; // Precio de compra
{ 'PrIVA', 'N', 5, 2 } } ) // Precio de venta
CLOSE ALL
use &( oApp():cDbfPath + 'pr' ) new
SELECT pr
IF File( oApp():cDbfPath + 'productos.dbf' )
DELETE file &( oApp():cDbfPath + 'productos.cdx' )
APPEND from &( oApp():cDbfPath + 'productos' )
dbCommitAll()
dbCloseAll()
DELETE file &( oApp():cDbfPath + 'productos.dbf' )
ENDIF
dbCloseAll()
rename &( oApp():cDbfPath + 'pr.dbf' ) to &( oApp():cDbfPath + 'productos.dbf' )

Lo que hago es crear la tabla con el nombre del alias que luego usaré con ella, incorporo los datos de la tabla real que luego borro, y por último renombro el fichero que he creado con el alias en el nombre real de la tabla. Esto para cada una de las tablas de mi aplicación. Cuando tengo que modificar un campo de una tabla lo hago en esta función, de manera que al arrancar de nuevo el programa las tablas se modifican de manera automática.

Una vez creadas las tablas lo que hago es crear los índices sobre las mismas. Como has podido ver, el fichero índice lo he borrado antes de incorporar los datos de la tabla real, por lo que ahora tengo que crearlo.

// productos
dbCloseAll()
IF File( oApp():cDbfPath + 'productos.cdx' )
 DELETE File ( oApp():cDbfPath + 'productos.cdx' )
ENDIF
Db_OpenNoIndex( "productos", "pr" )
oSay:SetText( i18n( "Fichero de productos" ) )
oMeter:SetRange( 0, LastRec() / nPaso / nPaso )
PACK
INDEX ON Upper( prnombre ) TAG pr01 ;
FOR ! Deleted() ;
Eval ( oMeter:SetPos( nMeter++ ), Sysrefresh() ) EVERY nPaso
INDEX ON Upper( prfanombre ) + Upper( prnombre ) TAG pr02 ;
FOR ! Deleted() ;
Eval ( oMeter:SetPos( nMeter++ ), Sysrefresh() ) EVERY nPaso
UtResetMeter( oMeter, @nMeter )

4.- Controlar todos los aspectos relacionados con la modificación de campos de las tablas

Es evidente que si has incluido un nuevo campo en una tabla, este aparecerá en alguno de tus formularios. Pero también debes mostrarlo en tus rejillas de datos previa a la edición de un registro, o incluir la nueva ordenación en la rejilla en que muestras la tabla. Y tener en cuenta tus clases que muestran datos.

En mis programas uso el interfaz que he llamado Interfaz completa de documento único o Full Single Documento Interface https://www.alanit.com/?s=fsdi, y una de sus funcionalidades es que guardo la configuración de la rejilla de datos de cada mantenimiento. Si añado un nuevo campo, este campo no se muestra en la rejilla, porque cuando guardé la configuración ese campo no existía. Por eso cada vez que añado un campo — modifico +10 la versión menor de mi aplicación — tengo que borrar la configuración almacenada de la rejilla para mostrar todos los campos de la misma.

Si en tu aplicación realizas más cosas relacionadas con el despliegue de tu aplicación, te agradecería que no explicaras en los comentarios.