Un proyecto paso a paso

From Apache OpenOffice Wiki
< ES‎ | Manuales‎ | GuiaAOO‎ | TemasAvanzados‎ | Macros‎ | StarBasic
Revision as of 15:40, 11 April 2013 by Salva (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search


Editing.png Esta página está en estado borrador.

Ya estamos en nuestro último capitulo, en este, pondremos en practica la mayoría de los conocimientos adquiridos. El proyecto que vamos a desarrollar, es un sistema básico pero funcional de facturación que se llamará, por supuesto; Factura Libre.


Dentro del desarrollo de software, existe un tema primario e importante que se llama Análisis y diseño de sistemas, que son las herramientas y conocimientos necesarios para desarrollar, lo más óptimamente posible, un programa. Aun y cuando seas un programador novel o autodidacta (como yo), no lo tomes como un tema menor, en tu buscador favorito encontraras amplia documentación al respecto, como este tema sale fuera del ámbito (y alcance) de estas notas, para los fines de nuestro proyecto, usaremos mucho más, al que dicen llamar, el menos común de los sentidos, el sentido común.


Tratemos de responder las tres preguntas siguientes: ¿qué?, ¿para qué? y ¿para quién?. ¿Qué?, un sistema de facturación. ¿Para qué?, para llevar el control completo del proceso de facturación. ¿Para quién?, usuario básico de computadora. El qué y para que pueden convertirse sin problema en el objetivo general de tu sistema, que no debes de perder de vista nunca, a lo largo del desarrollo de tu programa. Aun y cuando tu vayas a ser el usuario del sistema, es importante que tengas presente y consideres que un programa lo usa, siempre, un usuario final, reitero, aun y cuando tú seas la misma persona, imagínate que no es así. Parte de la idea de facilitarle la vida (informática) al usuario (no de complicársela), en la medida de lo posible, el programador tiene que cubrir las necesidades del usuario no al revés, no lo olvides, he visto no pocos sistemas, de muchos tamaños, donde el usuario tiene que cambiar sus procesos de trabajo, por responsabilidad (por decirlo de forma suave) de los programadores. Tu estas al servicio del usuario, no el al tuyo, reitero, no lo olvides.


Ya tenemos el objetivo de nuestro sistema, ahora, tratemos de ver el ¿como?. Para responder esta pregunta, es relevante saber con que recursos contamos. Primero los más importantes, los recursos humanos. En el desarrollo de un sistema, dependiendo de su tamaño y complejidad, pueden participar muchas personas; analistas de sistemas, diseñadores de interfaces, normalizadores de bases de datos, programadores, documentadores, implantadores, entre otros muchos especialistas. Para nuestro sistema, trataremos de asumir algunos de estos roles, por ahora, estamos como analistas del sistema y hemos determinado que en recursos humanos contamos con dos personas, tu y yo. Un recurso humano de suma importancia para el éxito de un sistema, es el usuario final, su conocimiento de la necesidad a cubrir por el sistema, puede ser crucial para su buen termino, puede llegar a ser tu recurso más valioso.


Ahora, pensemos en los recursos materiales a nuestra disposición, principalmente, el hardware donde se implementará el sistema, no pienses en el hardware donde se desarrollará, si no donde se utilizará, puede que tu máquina sea moderna y rápida, la del usuario tal vez no. En los recursos materiales, considera, también, el sistema o sistemas operativos donde se usará el sistema así como las versiones de estos, para nuestro caso, puede ser importante que consideres la versión de OpenOffice.org con que cuenta el usuario final.


El siguiente punto a analizar, serían los objetivos particulares del sistema, es decir, los alcances de este, que hará y que no, por ejemplo:


  • Control de clientes
  • Control de facturas
  • Control de pagos


Tienes que establecer la prioridad de estos alcances, en primer instancia, aquellos sin los cuales, el sistema deja de cumplir su objetivo general, por ejemplo, clientes y facturas, el control de usuarios, de pagos e inventario, pueden ser primarios o secundarios, según, las necesidades de tu usuario, el análisis del sistema y por supuesto, el presupuesto del cliente. Toma en cuenta que los recursos humanos y materiales pueden determinar algunos alcances del sistema, pero a su vez, los alcances pueden influir en las necesidades mínimas de estos recursos, es decir, tienes que tener una visión global de todos estos aspectos, pues unos infieren en los otros.


Nuestra siguiente tarea, es hacer un pseudo-código en lenguaje natural, por ejemplo:


  • Abrir programa.
  • Solicitar contraseña.
  • Verificar contraseña.
  • Si es correcta dar acceso.
  • Si no es correcta solicitarla otra vez.
  • Crear una nueva factura.
  • Seleccionar cliente.
  • Si no existe, dar de alta al cliente.
  • Seleccionar fecha.
  • Seleccionar productos a facturar.
  • Establecer cantidades a facturar.
  • Guardar factura.
  • Imprimir factura.
  • Reiniciar el proceso.
  • Salir del programa.


Estas instrucciones pueden ser tan genéricas o detalladas como lo consideres, su utilidad es que te van dando la pauta para el flujo de los datos, su relación entre ellos y la jerarquía de los mismos. En siguiente paso es diseñar un diagrama de flujo que refleje lo más certero el pseudo-código del paso anterior. Un diagrama de flujo es la representación gráfica de un proceso o un conjunto de procesos, para la solución de un problema, puede ser tan sencillo como el que te muestro de ejemplo, o tan complejo para representar el algoritmo completo de un sistema. Generalmente, hay un conjunto mínimo de símbolos estándar usados para representar cualquier diagrama de flujo, en tu buscador favorito encontraras suficiente documentación al respecto.


ES StarBasic ProyectoPasoAPaso.30.png


El siguiente paso es determinar que datos son los que guardará el programa, como primera aproximación tenemos la siguiente lista genérica.


  • Los datos de los clientes
  • Los datos de las facturas
  • El detalle de las facturas


Ahora, hagámosla más detallada de modo que sean casi definitivos los datos a guardar. En este punto es recomendable una conversación clara y extensa con el usuario final, de hecho, es recomendable una excelente comunicación con el usuario final a lo largo de todo el desarrollo de tu sistema, veras que será mucho mejor para los dos.


¿Que datos de los clientes y de las facturas guardaremos?, esto dependerá del alcance de tu sistema, previo acuerdo con el usuario y de las necesidades contables, que sabemos, varían de región en región. Nos avocaremos a las solicitadas en México, aunque espero sean lo suficientemente ilustrativas para sea posible adaptarlo a cualquier otra latitud o a tus necesidades particulares. Para nuestro caso, los datos mínimos que requerimos guardar para poder elaborar una factura valida son:


  • El nombre del cliente, también llamado Razón Social.
  • Su dirección fiscal completa, es decir, la declarada frente a la autoridad.
  • Su RFC, que en México es una secuencia especial alfanumérica que identifica de forma univoca a cada contribuyente.
  • La fecha y lugar de elaboración de la factura.
  • Detalle de los conceptos facturados, cantidad, descripción, importe, subtotal.
  • Los impuestos respectivos de acuerdo a su estatus fiscal y área de servicios.


Ahora, desglosaremos cada uno de estos puntos en unidades de información más pequeñas, lo más pequeñas posibles:


  • Nombre (Razón Social del cliente)
  • Calle
  • Número
  • Colonia
  • Delegación o Municipio
  • CP (Código Postal)
  • Ciudad o Estado
  • RFC
  • Número de factura
  • Cliente al que se le factura
  • Fecha de la factura
  • Cantidad
  • Descripción
  • PU (Precio Unitario)
  • Importe
  • Subtotal
  • Impuestos
  • Total


Observa que algunos de estos datos (Importe, subtotal, impuestos, total) son susceptibles de calcularse, máxime si estamos en una hoja de calculo, en un sistema pequeñito como el que estamos enfrentando, no veras mucha diferencia de desempeño entre guardar todos los datos ya calculados o calcularlos cada vez que son mostrados al usuario, en sistemas más grandes puede ser crucial esta diferencia. Por ahora podemos darnos el lujo de usar uno u otro método, pero no olvides esta importante diferencia.


Ahora, tratemos de agrupar los datos en grupos lógicos, es decir, los que tengan relación directa, unos con otros, primero los datos de los clientes:


Clientes
Nombre
Calle
Número
Colonia
Delegación o Municipio
Código Postal
Ciudad o Estado
RFC


Ahora, los datos de las facturas:


Facturas
Número
Cliente
Fecha
Subtotal
Impuesto
Total
Estado


Observa que hemos agregado un dato, Estado, este nos servirá como mínimo, para saber si una factura ya fue pagada o no, aunque puede tener muchos otros estatus, tantos como tu criterio te dicte.


Ahora, el detalle de la factura:


Detalle Factura
Número de Factura
Cantidad
Descripción
Precio
Importe


A cada grupo puedes agregar los datos que tu necesites, por ejemplo; a los clientes puedes agregarle datos como teléfonos o personas de contacto, a las facturas algunos clientes te piden que agregues números de pedido, remisión o algún otro dato, en el detalle tal vez necesites un dato para la unidad. Quita o agrega según te dicte tu experiencia y necesidad.


A cada uno de estos grupos le llamaremos tabla, al conjunto de todas nuestras tablas le llamaremos base de datos. A cada dato de cada tabla le llamaremos campo y será el encabezado de cada columna, cada fila de datos que guardemos con todos sus campos, le llamaremos registro.


Abre Base para que te muestre el Asistente para base de datos donde seleccionaremos Crear nueva base de datos para dar clic en el botón de comando Siguiente.


ES StarBasic ProyectoPasoAPaso.29.png


El siguiente paso le indicaremos que si queremos registrar la base de datos para después dar clic en el botón de comando Finalizar.


ES StarBasic ProyectoPasoAPaso.28.png


En el ultimo paso, selecciona el nombres de tu nueva base de datos (1), asegúrate de ir guardando todo en un solo directorio (2), para finalizar da clic en Guardar (3).


ES StarBasic ProyectoPasoAPaso.27.png


Crearemos una nueva tabla en vista diseño.


ES StarBasic ProyectoPasoAPaso.26.png


Agrega los siguiente campos con las siguientes características para la tabla Clientes.


Campo Tipo Tamaño
Id Entero (INTEGER)
Nombre Texto (VARCHAR_IGNORECASE) 100
Calle Texto (VARCHAR_IGNORECASE) 100
Numero Texto (VARCHAR) 50
Colonia Texto (VARCHAR_IGNORECASE) 50
Delegacion Texto (VARCHAR_IGNORECASE) 50
Cp Texto (VARCHAR) 5
Ciudad Texto (VARCHAR_IGNORECASE) 50
Rfc Texto (VARCHAR_IGNORECASE) 13


Nota que a esta tabla le hemos agregado un campo llamado Id, puedes nombrarlo también Clave o algún otro que consideres. La utilidad de este campo es identificar de forma unívoca a cada registro de nuestra tabla, en bases de datos a este campo suele llamarse clave primaria, que en las tablas de Base distingues por el icono de la llave a la izquierda del nombre del campo, esta propiedad se establece de forma automática en cada campo que establezcas como entero (Integer) y establezcas que sea Valor automático. Procura agregar al menos un campo que sea clave primaria en todas tus tablas.


ES StarBasic ProyectoPasoAPaso.25.png


Como siguiente paso guardar la nueva tabla con el nombre Clientes.


ES StarBasic ProyectoPasoAPaso.24.png


En el caso de la tabla Facturas, el número de factura es único, por lo que este puede fungir como clave primaria, hay casos (en México) en los que las facturas llevan una letra adicional cuando el emisor cuenta con sucursales, en este caso tienes que ingeniártelas para solventar esto. Los campos para esta tabla son:


Campo Tipo Tamaño
Numero Entero grande (BIGINT)
Cliente Entero (INTEGER)
Fecha Fecha (DATE)
Subtotal Doble (DOUBLE)
Impuesto Doble (DOUBLE)
Total Doble (DOUBLE)
Estado Texto (VARCHAR) 20


Es importante que no establezcas el campo Numero como valor automático porqué, salvo en contadas ocasiones, este número no empezará en cero, también observa que el campo Cliente, lo hemos establecido como entero (integer), esto es importante pues con este campo relacionaremos esta tabla con la tabla Clientes. Si tu régimen fiscal te obliga a aplicar más u otros impuestos, establécelos en esta tabla. Así mismo, si tu cliente te pide agregar algún dato como Pedido, también hazlo en esta tabla.


ES StarBasic ProyectoPasoAPaso.23.png


Guarda la tabla con el nombre Facturas.


Te queda de tarea agregar los campos para la tercer tabla llamada Detalles, cuidado con esta tabla, puede tener muchas variantes, usa la más sencilla.


ES StarBasic ProyectoPasoAPaso.22.png


Ahora crea y guarda un nuevo archivo de Calc que nos servirá como interfaz para el usuario, en el, agregaremos los controles necesarios para manipular los datos. La separación de los datos y la interfaz, es una buena practica de programación. En este punto tenemos muchas posibilidades, usaremos una combinación de formularios y cuadros de diálogo para resolverlo. Si bien los formularios tienen incorporadas propiedades simples y útiles para enlazarse y manipular bases de datos, para fines didácticos, procuráremos hacer uso de cuadros de diálogo que nos obligarán a hacer uso de más código pero en compensación repasaremos muchos de los conceptos tratados en este libro. La mayor parte del código esta suficientemente comentado, nos detendremos solo en aquellos que requieran algún comentario en especial. Cada cuadro de diálogo tendrá asociado un modulo de código, además incluye uno que se llame Utilidades para agregar las macros y funciones genéricas que nos usaremos en otros módulos.


Lo primero que tenemos que garantizar, es que los datos existan, para ello, al abrir nuestro archivo verificamos que estén registrados con la siguiente macro que tienes que asociar al evento Abrir documento de este archivo.


 Option Explicit
 Sub Inicio()
 Dim oDatos As Object
     'Verificamos si los datos existen
     oDatos = ExistenDatos()
     If IsNull( oDatos ) Then
         MsgBox "No se encontró la base de datos, el programa se cerrara", 16,"Error grave"
         'Cerramos este archivo
         ThisComponent.Close( True )        
     End If
 End Sub


La función ExistenDatos tiene el siguiente código.


 'Función para verificar que existan los datos, si existen devolvemos la referencia
 Function ExistenDatos() As Object
 Dim oDBC As Object
 Dim oBD As Object
     'El servicio de bases de datos
     oDBC = createUnoService("com.sun.star.sdb.DatabaseContext")    
     'Nos aseguramos de que esten registrados
     If oDBC.hasByName( DATOS ) Then
         'Accedemos a ellos
         oBD = oDBC.getByName( DATOS )
         'Los regresamos
         ExistenDatos = oBD
     End If
 End Function


Observa que en esta función estamos usando una constante; DATOS, te sugiero agregar un nuevo modulo para ir declarando, tanto las variables como las constantes globales que vayamos necesitando. El establecer una constante para el nombre registrado de la base de datos, nos permite cambiar en un solo lugar este nombre y usarlo a todo lo largo de nuestro programa.


 Option Explicit
 'Cambia aquí el nombre de tu conexión
 Global Const DATOS As String = "datos"


El siguiente paso es crear el cuadro de diálogo para manipular los datos de la tabla Clientes, agrega un nuevo cuadro de diálogo, agrega un control cuadro de lista (ListBox), controles cuadro de texto (TextBox), uno por cada campo de nuestra tabla, botones de comando (CommandButton) y etiquetas (Label) de modo que te quede lo más parecido a la siguiente imagen.


ES StarBasic ProyectoPasoAPaso.21.png


Todos los cuadros de texto (TextBox) tiene su propiedad Solo lectura = Si, los botones de comando (CommandButton), están desactivados, excepto Nuevo y Salir, el botón de comando Salir tiene la propiedad Tipo de botón = Cancelar.


Renombra la primer hoja del archivo a Inicio, agrega un botón de comando y asóciale la siguiente macro a su evento Ejecutar una acción.


 Option Explicit
 Sub Clientes()
 Dim oDlg As Object
     'Cargamos el cuadro de diálogo
     oDlg = CargarDialogo( "Standard","dlgClientes" )
     'Nos aseguramos de que sea válido
     If Not IsNull(oDlg) Then
         'Lo mostramos
         oDlg.execute()
         'Lo liberamos
         oDlg.dispose()
     Else
         MsgBox "No se pudo mostrar el cuadro de diálogo de Clientes"
     End If
 End Sub


El código de la función CargarDialogo es el siguiente.


 'Función para cargar un cuadro de dialogo en memoria y regresar el cuadro de dialogo
 Function CargarDialogo(Libreria As String, Nombre As String) As Object
 Dim oLibreria as Object
     'Verificamos que la libreria exista
     If DialogLibraries.hasByName( Libreria ) Then
         'Cargamos la libreria en memoria
         DialogLibraries.LoadLibrary( Libreria )
         'Referencia a la libreria
         oLibreria = DialogLibraries.getByName( Libreria )
         'Verificamos que exista el cuadro de diálogo
         If oLibreria.hasByName( Nombre ) Then
             'Creamos el cuadro de diálogo
             CargarDialogo = CreateUnoDialog( oLibreria.getByName( Nombre ) )
         End If
     End If
 End Function


En este punto, desde la interfaz de usuario que estamos construyendo, ya deberías poder mostrar y cerrar el cuadro de diálogo.


ES StarBasic ProyectoPasoAPaso.20.png


El botón de comando Salir, debe funcionar aun sin código pues establecimos su propiedad Tipo de botón en Cancelar, con lo cual podemos cerrar el cuadro de diálogo.


Si bien verificamos que existieran los datos al abrir el documento, no es mala idea volver a verificarlo antes de cargar el cuadro de diálogo. Usaremos una variable a nivel de modulo para guardar la conexión a la base de datos, también, cada control que se vaya a manipular, debe de referenciarse en una variable, por ello agregaremos las variables necesarias en el mismo nivel del modulo, de modo que sean visibles para todos los eventos y todas las macros que desarrollemos en este modulo.


 Option Explicit
 Dim oDatos As Object
 Dim lstClientes As Object
 Dim txtClave As Object
 Dim txtNombre As Object
 Dim txtCalle As Object
 Dim txtNumero As Object
 Dim txtColonia As Object
 Dim txtDelegacion As Object
 Dim txtCp As Object
 Dim txtCiudad As Object
 Dim txtRfc As Object
 Dim cmdNuevo As Object
 Dim cmdGuardar As Object
 Dim cmdEditar As Object
 Dim cmdActualizar As Object
 Dim cmdEliminar As Object
 Dim cmdCancelar As Object
 Dim cmdSalir As Object


Todos los controles los cargaremos en una sola macro que llamaremos CargarControles y cuyo código es el siguiente.


 Sub CargarControles( Dialogo As Object)
     With Dialogo
         lstClientes = .getControl("lstClientes")
         txtClave = .getControl("txtClave")
         txtNombre = .getControl("txtNombre")
         txtCalle = .getControl("txtCalle")
         txtNumero = .getControl("txtNumero")
         txtColonia = .getControl("txtColonia")
         txtDelegacion = .getControl("txtDelegacion")
         txtCp = .getControl("txtCp")
         txtCiudad = .getControl("txtCiudad")
         txtRfc = .getControl("txtRfc")
         cmdNuevo = .getControl( "cmdNuevo" )
         cmdGuardar = .getControl( "cmdGuardar" )
         cmdEditar = .getControl( "cmdEditar" )
         cmdActualizar = .getControl( "cmdActualizar" )
         cmdEliminar = .getControl( "cmdEliminar" )
         cmdCancelar = .getControl( "cmdCancelar" )
         cmdSalir = .getControl( "cmdSalir" )
     End With
 End Sub


La macro Clientes, ya con la verificación de los datos y la carga de controles, queda de la siguiente manera.


 Sub Clientes()
 Dim oDlg As Object
     'Cargamos el cuadro de diálogo
     oDlg = CargarDialogo( "Standard","dlgClientes" )
     'Nos aseguramos de que sea válido
     If Not IsNull(oDlg) Then    
         'Nos aseguramos de que existan los datos
         oDatos = ExistenDatos()
         If Not ISNull( oDatos ) Then
             'Cargamos los controles en memoria
             Call CargarControles( oDlg )
             'Lo mostramos
             oDlg.execute()
             'Lo liberamos
             oDlg.dispose()
         Else
             MsgBox "No se encontró la base de datos", 16,"Error grave"
         End If
     Else
         MsgBox "No se pudo mostrar el cuadro de diálogo de Clientes"
     End If
 End Sub


De aquí en adelante, cuando modifiquemos una macro, solo te indicare las líneas necesarias donde hacer el cambio, de este modo no repetiremos tanto código.


Lo siguiente que haremos no es indispensable, pero a parte de que se ve bonito, nos muestra la versatilidad de OOo Basic, haremos que el color de fondo de los cuadros de texto (TextBox), cambie al color que quieras cuando recibe el foco y regrese a blanco cuando sale de el, de este modo, le indicamos visualmente al usuario en que campo esta actualmente, las macros que logran esto son las siguientes.


 'Cambia el color de fondo al recibir el foco
 Sub Control_RecibeFoco(Evento As Object)
     Evento.Source.Model.Border = 0
     Call CambiaColorFF( Evento.Source, AMARILLO_PASTEL, 0 )
 End Sub
 'Cambia el color de fondo al perder el foco
 Sub Control_PierdeFoco(Evento As Object)
     Evento.Source.Model.Border = 1
     Call CambiaColorFF( Evento.Source, BLANCO, 0 )
 End Sub
 'Procedimiento que cambia el color de fondo y fuente de un control
 Sub CambiaColorFF(Control As Object, Fondo As Long, Fuente As Long)
     Control.Model.BackgroundColor = Fondo
     Control.Model.TextColor = Fuente
 End Sub


Observa que en la macro CambiaColorFF podemos cambiar tanto el color de fondo como la fuente, de este modo puedes jugar con la combinación de colores que más te guste, yo siempre dejo la fuente en negro porque tengo poca imaginación para eso de los colores. Observa que al recibir el foco uso la constante AMARILLO_PASTEL y al perder el foco uso BLANCO, estas constantes están declaradas a nivel global y tienen los siguiente valores.


 Global Const AMARILLO_PASTEL As Long = 16777164
 Global Const BLANCO As Long = 16777215


Ahora, para verlas trabajando, solo te resta asociar las macros respectivas en los eventos correspondientes de los cuadros de texto.


ES StarBasic ProyectoPasoAPaso.19.png


Asocia las mismas macros a todos los cuadros de texto (TextBox), si, las mismas a todos los controles, de hecho, mientras un control, no importa cual, soporte estas propiedades, puedes asociarle estas macros, esto es una característica muy poderosa de OOo Basic.


ES StarBasic ProyectoPasoAPaso.18.png


Mencionamos que los cuadros de texto (TextBox) son de solo lectura, esto es para que controlemos la adición y edición de los datos, también, los botones de comando (CommandButton) están desactivados de forma predeterminada y solo activamos los necesarios en el momento indicado, por ejemplo, si la tabla de clientes esta vacía, solo debe estar activado el botón de Nuevo, pero si ya hay datos, podemos activar además: Editar y Eliminar. De modo que los botones de comando se activarán y desactivaran de acuerdo a la acción que seleccione el usuario, la siguiente tabla nos ayudará a controlar el estado de cada botón.


  Nuevo Guardar Editar Actualizar Eliminar Cancelar
Nuevo ES StarBasic ProyectoPasoAPaso.31.png ES StarBasic ProyectoPasoAPaso.31.png ES StarBasic ProyectoPasoAPaso.31.png ES StarBasic ProyectoPasoAPaso.31.png
Guardar ES StarBasic ProyectoPasoAPaso.31.png
Editar ES StarBasic ProyectoPasoAPaso.31.png ES StarBasic ProyectoPasoAPaso.31.png ES StarBasic ProyectoPasoAPaso.31.png ?  ES StarBasic ProyectoPasoAPaso.31.png ? 
Actualizar ES StarBasic ProyectoPasoAPaso.31.png
Eliminar ES StarBasic ProyectoPasoAPaso.31.png ES StarBasic ProyectoPasoAPaso.31.png ES StarBasic ProyectoPasoAPaso.31.png ?  ES StarBasic ProyectoPasoAPaso.31.png ? 
Cancelar ES StarBasic ProyectoPasoAPaso.31.png ES StarBasic ProyectoPasoAPaso.31.png
Salir ES StarBasic ProyectoPasoAPaso.31.png ES StarBasic ProyectoPasoAPaso.31.png ES StarBasic ProyectoPasoAPaso.31.png ES StarBasic ProyectoPasoAPaso.31.png


El signo de interrogación nos indica que estos botones pueden o no pueden estar activados, ¿de que depende? Dependerá de la existencia o no de más registros.


Cuando abrimos nuestro cuadro de diálogo, tenemos que traer los clientes existentes, si los hay y mostrarlos en el cuadro de lista (ListBox), para ello, agregamos la siguiente línea a la macro Clientes, justo después de cargar los controles.


             'Cargamos los controles en memoria
             Call CargarControles( oDlg )
             'Traemos los nombres de los clientes si lo hay
             Call TraerClientes( oDatos )


La macro TraerClientes tiene el siguiente código.


 Sub TraerClientes( oDatos )
 Dim oConsulta As Object
 Dim sSQL As String
 Dim mClientes()
 Dim lTotalRegistros As Long
     'Obtenemos el total de clientes
     lTotalRegistros = TotalRegistros( oDatos, "Clientes", "Id" )
     'Si no hay registros
     If lTotalRegistros = 0 Then
         MsgBox "El catalogo de Clientes esta vacio, usa el botón Nuevo para agregar uno", 64,"Clientes"
     Else
         'Si hay registros construimos la consulta para regresar los clientes ordenados por Nombre
         sSQL = "SELECT Nombre FROM Clientes ORDER BY Nombre"
         'Hacemos la consulta
         oConsulta = HacerConsulta( oDatos, sSQL )
         'Movemos los datos a una matriz
         mClientes = CampoAMatriz( oConsulta, lTotalRegistros, 1 )
         'Y los agregamos al cuadro de lista
         Call MatrizACuadroLista( mClientes(), lstClientes )
         'Como si hay datos, activamos Editar y Eliminar
         cmdEditar.setEnable( True )
         cmdEliminar.setEnable( True )
         'Seleccionamos el primer cliente
         lstClientes.selectItemPos( 0, True )
     End If
 End Sub


Tenemos varias funciones subrutinas nuevas; TotalRegistros, HacerConsulta, CampoAMatriz y MatrizACuadroLista, cuyo código es el siguiente.


 'Función para enviar una consulta SQL a una base de datos
 Function HacerConsulta( BaseDatos As Object, SQL As String) As Object
 Dim oConexion As Object
 Dim oDeclaracion As Object
 Dim oResultado As Object
     'Creamos una conexion a la base de datos
     oConexion = BaseDatos.getConnection( "","" )
     'Creamos un objeto para las instrucciones SQL
     oDeclaracion = oConexion.createStatement()
     'Ejecutamos la consulta
     oResultado = oDeclaracion.executeQuery( SQL )
     'Si hay resultados
     If Not IsNull( oResultado ) Then
         oResultado.next()
         HacerConsulta = oResultado
     End If
 End Function
 'Función que devuelve el total de registros de un determinado campo y tabla
 Function TotalRegistros( BaseDatos As Object, Tabla As String, Campo As String) As Long
 Dim sSQL As String
 Dim oConsulta As Object
     'Construimos la consulta
     sSQL = "SELECT COUNT( " & Campo & " ) FROM " & Tabla
     'Hacemos la consulta
     oConsulta = HacerConsulta( BaseDatos, sSQL )
     'Obtenemos y regresamos el valor
     TotalRegistros = oConsulta.getLong(1)
 End Function
 'Función para mover los registros que regresa una consulta a una matriz
 Function CampoAMatriz( Consulta As Object, NumReg As Long, Tipo As Integer )
 Dim mDatos()
 Dim co1 As Long
 
 
     'Pasandole el número de registros hacemos un ciclo
     'determinado en vez de uno indeterminado
     NumReg = NumReg - 1
     'Redimencionamos la matriz
     Redim mDatos( NumReg )
     For co1 = 0 To NumReg
         'El tipo de campo es importante para usar el método correcto
         Select Case Tipo
             Case 1 : mDatos( co1 ) = Consulta.getString(1)
             Case 2 : mDatos( co1 ) = Consulta.getInt(1)
             'Aquí iremos agregando los tipos necesarios
         End Select
         'Nos movemos al siguiente registro
         Consulta.next()
     Next co1    
     CampoAMatriz = mDatos()
 End Function
 'Macro para agregar una matriz a un cuadro de lista
 'el método addItem es muy lento, por ello es mejor
 'agregar la matriz completa
 Sub MatrizACuadroLista( mDatos(), CuadroLista As Object )
     CuadroLista.addItems( mDatos(), 0 )
 End Sub


Empecemos con el código de nuestro botones, por supuesto el primero sera Nuevo, el cual es muy simple pues solo tiene que preparar el cuadro de diálogo para recibir los datos del usuario, vamos a activar y vaciar los cuadros de texto y a establecer el estado de los demás botones de acuerdo a la tabla vista anteriormente.


 Sub Nuevo_Clic()
     'Activamos los cuadros de texto
     Call ActivarCuadrosTexto( True )
     'Los vaciamos
     Call LimpiarCuadrosTexto( True )
     'Botones activados
     cmdGuardar.setEnable( True )
     cmdCancelar.setEnable( True )
     'Botones desactivados
     cmdNuevo.setEnable( False )
     cmdEditar.setEnable( False )
     cmdActualizar.setEnable( False )
     cmdEliminar.setEnable( False )
     cmdSalir.setEnable( False )
     'Desactivamos el cuadro de lista
     lstClientes.setEnable( False )
     'Enviamos el cursor al primer campo
     txtNombre.setFocus()                    
 End Sub


Observa que también desactivamos el cuadro de lista con los nombres de los clientes, esto es importante porque a este control le agregaremos su evento Clic, el cual nos traerá los datos del cliente seleccionado, lo desactivamos para que el usuario no se le ocurra dar clic en el mientras esta agregando uno nuevo o mientras edita otro, ya ves como son los usuarios. El código de las nuevas subrutinas es el siguiente.


 Sub ActivarCuadrosTexto( Activar As Boolean )
     'Recuerda que la clave la establecimos autonúmerico por ello este cuadro nunca lo activamos
     txtNombre.setEditable( Activar )
     txtCalle.setEditable( Activar )
     txtNumero.setEditable( Activar )
     txtColonia.setEditable( Activar )
     txtDelegacion.setEditable( Activar )
     txtCp.setEditable( Activar )
     txtCiudad.setEditable( Activar )
     txtRfc.setEditable( Activar )
 End Sub
 Sub LimpiarCuadrosTexto( Nuevo As Boolean )
     If Nuevo Then
         txtClave.setText( "<Nuevo>" )
     Else
         txtClave.setText( "" )
     End If
     txtNombre.setText( "" )
     txtCalle.setText( "" )
     txtNumero.setText( "" )
     txtColonia.setText( "" )
     txtDelegacion.setText( "" )
     txtCp.setText( "" )
     txtCiudad.setText( "" )
     txtRfc.setText( "" )
 End Sub


El código del botón Cancelar es trivial.


 Sub cmdCancelar_Clic()
 Dim iClientes As Integer
     'Verificamos cuantos clientes hay
     iClientes = lstClientes.getItemCount()
     If iClientes = 0 Then
         'Si no hay clientes limpiamos todo
         Call LimpiarCuadrosTexto( False )
     Else
         'Si hay clientes activamos los controles necesarios
         cmdEditar.setEnable( True )
         cmdEliminar.setEnable( True )
         lstClientes.setEnable( True )
         'Mostramos los datos del cliente seleccionado actualmente
         Call lstClientes_Clic()
     End If
     'Activamos y desactivamos los demás controles necesarios
     cmdNuevo.setEnable( True )
     cmdSalir.setEnable( True )
     cmdGuardar.setEnable( False )
     cmdActualizar.setEnable( False )
     cmdCancelar.setEnable( False )
     Call ActivarCuadrosTexto( False )
 End Sub


Observa que si hay datos, llamamos al evento Clic del control cuadro de lista que aun no codificamos porque primero haremos el código del botón Guardar, el cual es el siguiente.


 Sub cmdGuardar_Clic()
 Dim mDatos() 
 Dim co1 As Integer
 Dim sTmp As String
 Dim sSQL As String
     'Obtenemos los datos de todos los cuadros de texto
     mDatos = Array( txtNombre.getText, txtCalle.getText, txtNumero.getText, txtColonia.getText, txtDelegacion.getText, txtCp.getText, txtCiudad.getText, txtRfc.getText, )
     'Agregamos las comillas simples
     For co1 = LBound(mDatos) To UBound(mDatos)
         mDatos(co1) = "'" & mDatos(co1) & "'"
     Next co1
     'Juntamos y separamos por coma
     sTmp = Join( mDatos(), ",")
     'Construimos la instrucción SQL de inserción
     sSQL = "INSERT INTO ""Clientes"" (""Nombre"",""Calle"",""Numero"",""Colonia"",""Delegacion"",""Cp"",""Ciudad"",""Rfc"") VALUES (" & sTmp & ")"
     'Insertamos los datos
     Call ActualizarDatos( oDatos, sSQL )
     'Activamos y desactivamos los demás controles necesarios        
     cmdEditar.setEnable( True )
     cmdEliminar.setEnable( True )
     lstClientes.setEnable( True )
     cmdNuevo.setEnable( True )
     cmdSalir.setEnable( True )
     cmdGuardar.setEnable( False )
     cmdCancelar.setEnable( False )
     'Desactivamos los cuadros de texto
     Call ActivarCuadrosTexto( False )
 End Sub


¿Que nos falto?, algo en lo que he hecho mucho énfasis a todo lo largo de estas notas, claro, no hemos validado ningún dato, lo cual es un pésimo, si no es que el peor hábito de programación. La instrucción SQL debe estar perfectamente construida, si no es así, el método executeUpdate usado en la macro ActualizarDatos fallará.


 'Ejecuta la consulta de actualización pasada
 Sub ActualizarDatos( BaseDatos As Object, SQL As String) 
 Dim oConexion As Object
 Dim oDeclaracion As Object
 Dim oResultado As Object
     'Creamos una conexion a la base de datos
     oConexion = BaseDatos.getConnection( "","" )
     'Creamos un objeto para las instrucciones SQL
     oDeclaracion = oConexion.createStatement()
     'Ejecutamos la inserción de datos
     oDeclaracion.executeUpdate( SQL )
 End Sub


Modifiquemos la macro para validar los datos, hay muchas formas de hacer esto, todo dependerá de los tipos de datos y de las necesidades de tu proyecto. Reitero, la validación es un tema central en muchos programas, valida a conciencia, siempre.


Usaremos una función para validar todos los datos.


 'Función para validar los datos que capture el usuario
 Function ValidarDatos() As Boolean
 Dim sDato As String
 Dim co1 As Byte
 Dim sLetra As String
     'Obtenemos el valor del cuadro de texto y le quitamos los espacios sobrantes
     sDato = Trim(txtNombre.getText())
     'Validamos que si esta vacio, si es un número o una fecha
     If sDato = "" Or IsNumeric( sDato ) Or IsDate( sDato )Then
         MsgBox "El campo NOMBRE no puede estar vacío, es un número o una fecha", 16, "Error en campo"
         'Vaciamos el contenido del control
         txtNombre.setText( "" )
         'Enviamos el cursor al control
         txtNombre.setFocus()
         'Salimos de la función, ValidarDatos = False
         Exit Function
     Else
         'Si el valor es correcto, lo establecemos en el control para su posterior guardado
         'aquí puedes por ejemplo; convertir en mayusculas
         txtNombre.setText( sDato )
     End If
     'Lo mismo para todos los restantes campos
     sDato = Trim(txtCalle.getText())
     If sDato = "" Or IsNumeric( sDato ) Or IsDate( sDato )Then
         MsgBox "El campo CALLE no puede estar vacío, es un número o una fecha", 16, "Error en campo"
         txtCalle.setText( "" )
         txtCalle.setFocus()
         Exit Function
     Else
         txtCalle.setText( sDato )
     End If
     'Tal vez en este campo quieras aceptar números, yo creo que es mejor que no y obligar a capturar
     'por ejemplo: Nº 7, Mz 43 Lt 7, Edif 8 Depto 6
     sDato = Trim(txtNumero.getText())
     If sDato = "" Or IsNumeric( sDato ) Or IsDate( sDato )Then
         MsgBox "El campo NUMERO no puede estar vacío, es un número o una fecha", 16, "Error en campo"
         txtNumero.setText( "" )
         txtNumero.setFocus()
         Exit Function
     Else
         txtNumero.setText( sDato )
     End If
     sDato = Trim(txtColonia.getText())
     If sDato = "" Or IsNumeric( sDato ) Or IsDate( sDato )Then
         MsgBox "El campo COLONIA no puede estar vacío, es un número o una fecha", 16, "Error en campo"
         txtColonia.setText( "" )
         txtColonia.setFocus()
         Exit Function
     Else
         txtColonia.setText( sDato )
     End If
     sDato = Trim(txtDelegacion.getText())
     If sDato = "" Or IsNumeric( sDato ) Or IsDate( sDato )Then
         MsgBox "El campo DELEGACION no puede estar vacío, es un número o una fecha", 16, "Error en campo"
         txtDelegacion.setText( "" )
         txtDelegacion.setFocus()
         Exit Function
     Else
         txtDelegacion.setText( sDato )
     End If
     'Este es un caso especial, en México el CP esta en formato 00000, validaremos
     'que todos los caracteres capturados sean digitos
     sDato = Trim(txtCp.getText())
     If sDato = "" Or IsDate( sDato )Then
         MsgBox "El campo C.P. no puede estar vacío o una fecha", 16, "Error en campo"
         txtCp.setText( "" )
         txtCp.setFocus()
         Exit Function
     Else
         'Iteramos en cada letra
         For co1 = 1 To Len(sDato)
             'Extraemos cada letra
             sLetra = Mid( sDato, co1, 1 )
             'Verificamos que sea un digito
             If InStr( 1, "0123456789", sLetra ) = 0 Then
                 MsgBox "El campo C.P. solo acepta dígitos", 16, "Error en campo"
                 txtCp.setText( "" )
                 txtCp.setFocus()
                 Exit Function        
             End If
         Next co1
         'Si son puros dígitos, le damos el formato correcto
         sDato = Format( Val(sDato), "00000" )
         txtCp.setText( sDato )
     End If
     sDato = Trim(txtCiudad.getText())
     If sDato = "" Or IsNumeric( sDato ) Or IsDate( sDato )Then
         MsgBox "El campo CIUDAD no puede estar vacío, es un número o una fecha", 16, "Error en campo"
         txtCiudad.setText( "" )
         txtCiudad.setFocus()
         Exit Function
     Else
         txtCiudad.setText( sDato )
     End If
     'Este es otro caso especial, el RFC es una combinación de letras y números que identifica
     'de forma única a cada contribuyente, para personas morales es de 12 caracteres y para las
     'personas físicas es de 13 con el formato [L]LLLDDDDDDAAA
     sDato = Trim(txtRfc.getText())
     If sDato = "" Or IsNumeric( sDato ) Or IsDate( sDato )Then
         MsgBox "El campo RFC no puede estar vacío, es un número o una fecha", 16, "Error en campo"
         txtRfc.setText( "" )
         txtRfc.setFocus()
         Exit Function
     Else
         'Esta es tu tarea, validar que este bien capturado el RFC
         txtRfc.setText( sDato )
     End If
     'Si todos los datos están correctos
     ValidarDatos = True
 End Function


¿Recuerdas que comentamos que muchas veces la validación nos lleva muchas más líneas de código que otros procesos?, aquí una prueba. Entre las propiedades de los controles, por ejemplo el cuadro de texto para el campo CP, lo hemos limitado a solo permitir cinco caracteres, las propiedades de tu tabla y la validación por código, deberías de tener completo control sobre los datos capturados por el usuario, un refuerzo más podría ser un controlador de errores como hemos aprendido ya, queda a tu criterio el establecerlo o no. La validación para el RFC te queda de tarea, en el archivo que acompaña a estos apuntes puedes ver mi propuesta, pero por supuesto, intenta antes hacer la tuya, y claro, no tienen que coincidir plenamente, recuerda que en informática (y en casi todo) hay más de una manera de resolver los problemas. Hay otro caso, dentro de la validación que tienes que considerar, pero este es tan obvio que también te queda de tarea averiguarlo.


Ahora, modifica la macro cmdGuardar_Clic para integrar la validación anterior.


 Sub cmdGuardar_Clic()
 Dim mDatos() 
 Dim co1 As Integer
 Dim sTmp As String
 Dim sSQL As String
     If ValidarDatos() Then
         'Obtenemos los datos de todos los cuadros de texto
         mDatos = Array( txtNombre.getText, txtCalle.getText, txtNumero.getText, txtColonia.getText, txtDelegacion.getText, txtCp.getText, txtCiudad.getText, txtRfc.getText, )
         'Agregamos las comillas simples
         For co1 = LBound(mDatos) To UBound(mDatos)
             mDatos(co1) = "'" & mDatos(co1) & "'"
         Next co1
         'Juntamos y separamos por coma
         sTmp = Join( mDatos(), ",")
         'Construimos la instrucción SQL de inserción
         sSQL = "INSERT INTO ""Clientes"" (""Nombre"",""Calle"",""Numero"",""Colonia"",""Delegacion"",""Cp"",""Ciudad"",""Rfc"") VALUES (" & sTmp & ")"
         'Insertamos los datos
         Call ActualizarDatos( oDatos, sSQL )
 
 
         'Activamos y desactivamos los demás controles necesarios        
         cmdEditar.setEnable( True )
         cmdEliminar.setEnable( True )
         lstClientes.setEnable( True )
         cmdNuevo.setEnable( True )
         cmdSalir.setEnable( True )
         cmdGuardar.setEnable( False )
         cmdCancelar.setEnable( False )
         'Desactivamos los cuadros de texto
         Call ActivarCuadrosTexto( False )
     End If
 End Sub


Ahora si, veamos el código del evento Clic para el cuadro de lista (ListBox) de los clientes, que nos traerá los datos completos del cliente seleccionado por el usuario.


 Sub lstClientes_Clic()
 Dim sCliente As String
 Dim sSQL As String
 Dim oConsulta As Object
     'El cliente seleccionado
     sCliente = lstClientes.getSelectedItem
     'Construimos la consulta SQL
     sSQL = "SELECT * FROM Clientes WHERE Nombre='" & sCliente & "'"
     'Hacemos la consulta
     oConsulta = HacerConsulta( oDatos, sSQL )
     'Mostramos los datos en sus respectivos controles
     With oConsulta
         txtClave.setText( .getInt(1) )
         txtNombre.setText( .getString(2) )
         txtCalle.setText( .getString(3) )
         txtNumero.setText( .getString(4) )
         txtColonia.setText( .getString(5) )
         txtDelegacion.setText( .getString(6) )
         txtCp.setText( .getString(7) )
         txtCiudad.setText( .getString(8) )
         txtRfc.setText( .getString(9) )
     End With
 End Sub


El siguiente botón que vamos a programar es el de Eliminar, que queda así.


 Sub cmdEliminar_Clic()
 Dim sClave As String
 Dim sNombre As String
 Dim sInfo As String
 Dim iRes As Integer
 Dim sSQL As String
 Dim iSel As Integer
 Dim iNum As Integer
     'Posición del cliente seleccionado
     iSel = lstClientes.getSelectedItemPos
     'La clave del cliente
     sClave = txtClave.getText
     'El nombre del cliente
     sNombre = txtNombre.getText
     'Construimos el mensaje a mostrar
     sInfo = "Se eliminará al cliente " & sNombre & " con clave " & sClave & Chr(13) & Chr(13) & "¿Estás seguro de continuar?" & Chr(13) & Chr(13) & "ESTA ACCIÓN NO SE PUEDE DESHACER"
     'Mostramos y preguntamos al usuario
     iRes = MsgBox( sInfo, 36,"Eliminar cliente" )
     'Si el usuario responde Si
     If iRes = 6 Then
         'Construimos la instrucción SQL de borrado
         sSQL = "DELETE FROM ""Clientes"" WHERE ""Id""=" & sClave
         'Actualizamos la base de datos
         Call ActualizarDatos( oDatos, sSQL )
         'Quitamos al cliente de la lista
         lstClientes.removeItems( iSel,1)
         'Obtenemos el número de clientes 
         iNum = lstClientes.getItemCount
         Select Case iNum
             Case 0    'Si ya no hay clientes
                 cmdEditar.setEnable( False )
                 cmdEliminar.setEnable( False )
                 lstClientes.setEnable( False )
                 Call LimpiarCuadrosTexto( False )
                 txtNombre.setFocus()
             Case 1    'Si al menos hay un cliente lo seleccionamos
                 lstClientes.selectItemPos( 0, True )
                 'Traemos sus datos
                 Call lstClientes_Clic()
             Case Else
                 'Solo si se eliminó el ultimo cliente
                 If iNum = iSel Then iSel = iSel - 1    
                 'Seleccionamos el inmediato anterior            
                 lstClientes.selectItemPos( iSel, True )
                 'Traemos sus datos
                 Call lstClientes_Clic()                        
         End Select
     End If
 End Sub


Siempre y perdona el absoluto, siempre pregunta al usuario y trata de informarle lo más claro posible, cuando se esta apunto de realizar una acción que no es posible deshacer, dependiendo del contexto, tal vez hasta dos veces hay que preguntar. En el código anterior nos hace falta una validación antes de eliminar al cliente, ¿cual?.


El botón de Editar tiene un comportamiento muy similar al botón Nuevo, con la diferencia de que no vaciamos los cuadros de texto y activamos otros botones de comando.


 Sub cmdEditar_Clic()
     'Activamos los cuadros de texto
     Call ActivarCuadrosTexto( True )
     'Botones activados
     cmdActualizar.setEnable( True )
     cmdCancelar.setEnable( True )
     'Botones desactivados
     cmdNuevo.setEnable( False )
     cmdEditar.setEnable( False )
     cmdGuardar.setEnable( False )
     cmdEliminar.setEnable( False )
     cmdSalir.setEnable( False )
     'Desactivamos el cuadro de lista
     lstClientes.setEnable( False )
     'Enviamos el cursor al primer campo
     txtNombre.setFocus()
 End Sub


El código del botón Actualizar, es similar al del botón Guardar, pero claro, hay que hacer algunas validaciones un poco diferentes. Te queda de tarea terminarlo, en el archivo anexo de este libro esta mi propuesta, pero de nuevo, no hagas trampa y trata de terminarlo primero, después solo compara recordando que es solo una propuesta.


El siguiente cuadro de diálogo que haremos será el de la facturación, en este ya estarán involucradas dos tablas diferentes, pero su manipulación es muy similar al que acabamos de terminar aunque veras que necesitaremos muchas más líneas de código para poder controlarlo, diseña tu cuadro de dialogo de acuerdo a la siguiente imagen.


ES StarBasic ProyectoPasoAPaso.17.png


En el archivo anexo podrás ver el detalle de los controles usados, así como las principales propiedades usadas en ellos.


En nuestra hoja de calculo de inicio, agrega un segundo botón de comando que abrirá este cuadro de diálogo.


ES StarBasic ProyectoPasoAPaso.16.png


Todo el código de este cuadro de diálogo estará en un nuevo modulo, así que agrega uno al archivo desde el IDE como ya sabes. Lo primero que tenemos que hacer es declarar las variables a nivel de modulo que serán visibles para todas las macros y funciones que desarrollemos en él.


 Option Explicit
 Dim oDatos As Object
 Dim cboClientes As Object
 Dim txtClave As Object
 Dim txtFecha As Object
 Dim txtFactura As Object
 Dim txtCantidad As Object
 Dim txtDescripcion As Object
 Dim txtPu As Object
 Dim txtImporte As Object
 Dim txtSubtotal As Object
 Dim txtImpuesto As Object
 Dim txtTotal As Object
 Dim lstDetalle As Object
 Dim lstLeyendas As Object
 Dim lblImpuesto As Object
 Dim lblEstado As Object
 Dim cmdNuevaFactura As Object
 Dim cmdAgregar As Object
 Dim cmdGuardar As Object
 Dim cmdReservar As Object
 Dim cmdUsarReservada As Object
 Type Articulo
     Cantidad As Double
     Descripcion As String
     Precio As Double
     Importe As Double
 End Type
 Dim EsReservada As Boolean
 Dim dSubtotal As Double
 Dim mDetalle() As New Articulo


La siguiente macro será la macro de inicio y la que se tiene que asociar al botón de comando agregado a la interfaz del usuario. Observa que hacemos uso de varias macros ya vistas en el cuadro de diálogo anterior, como; CargarDialogo, ExistenDatos y TotalRegistros.


 Sub Facturacion()
 Dim oDlg As Object
 Dim lFactura As Long
 Dim sFactura As String
 
 
     'Cargamos el cuadro de diálogo
     oDlg = CargarDialogo( "Standard","dlgFacturacion" )
 
 
     'Nos aseguramos de que sea válido
     If Not IsNull(oDlg) Then    
         'Nos aseguramos de que existan los datos
         oDatos = ExistenDatos()
         If Not IsNull( oDatos ) Then
             'Nos aseguramos de que haya clientes registrados
             If TotalRegistros( oDatos, "Clientes", "Id" ) > 0 Then
                 'Cargamos los controles en memoria
                 Call CargarControles( oDlg )
                 txtDescripcion.setText( "" )
                 cboClientes.setText( "" )
                 lblImpuesto.setText( "IVA " & Format(IVA,"0 %" )
                 'Traemos los nombres de los clientes si lo hay
                 Call TraerClientes( oDatos )
                 'Establecemos la fecha actual
                 txtFecha.Date = Format(Now,"YYYYMMDD")
                 'Consultamos la ultima factura
                 lFactura = UltimaFactura( oDatos )
                 'Si es la primer factura pedimos él número
                 If lFactura = 0 Then
                     Do 
                         sFactura = InputBox( "Es la primer factura que haces, ¿escribe el número de factura?" )
                     Loop Until IsNumeric(sFactura)
                     lFactura = Val( sFactura )
                 Else
                     'Si ya hay solo aumentamos en uno
                     lFactura = lFactura + 1    
                 End If
                 'Si hay al menos una factura reservada activamos el botón de comando
                 If Not ExisteValor( oDatos, "Facturas", "Estado", "Reservada") Then
                     cmdUsarReservada.setEnable( False )
                 End If
 
 
                 'Establecemos el número de la nueva factura
                 txtFactura.setText( lFactura )
                 txtClave.setFocus()
                 'Mostramos el cuadro de diálogo
                 oDlg.execute()
                 'Lo liberamos
                 oDlg.dispose()
             Else
                 MsgBox "No hay clientes registrados, al menos registra uno", 16, "Sin clientes"
             End If
         Else
             MsgBox "No se encontró la base de datos", 16, "Error grave"
         End If
     Else
         MsgBox "No se pudo mostrar el cuadro de diálogo de Clientes"
     End If
 End Sub


La macro CargarControles es igual pero claro, en esta cargamos los controles usado en este cuadro de diálogo, no te confundas, son dos macros con igual nombre pero contenido diferente, al estar en módulos diferentes, el IDE sabe a cual llamar.


 'Para inicializar las variables de todos los controles usados
 Sub CargarControles( Dialogo As Object)
     With Dialogo
         cboClientes = .getControl("cboClientes")
         txtClave = .getControl("txtClave")
         txtFecha = .getControl("txtFecha")
         txtFactura = .getControl("txtFactura")
         txtCantidad = .getControl("txtCantidad")
         txtDescripcion = .getControl("txtDescripcion")
         txtPu = .getControl("txtPu")
         txtImporte = .getControl("txtImporte")
         lstDetalle = .getControl("lstDetalle")
         lstLeyendas = .getControl("lstLeyendas")
         txtSubtotal = .getControl("txtSubtotal")
         txtImpuesto = .getControl("txtImpuesto")
         txtTotal = .getControl("txtTotal")
         lblImpuesto = .getControl("lblImpuesto")
         lblEstado = .getControl("lblEstado")
         cmdNuevaFactura = .getControl("cmdNuevaFactura")
         cmdAgregar = .getControl("cmdAgregar")
         cmdGuardar = .getControl("cmdGuardar")
         cmdReservar = .getControl("cmdReservar")
         cmdUsarReservada = .getControl("cmdUsarReservada")
     End With
 End Sub


Observa que en la siguiente línea.


 lblImpuesto.setText( "IVA " & Format(IVA,"0 %" )


Establecemos el texto de una etiqueta (Label), y usamos el valor de una constante llamada IVA, esta constante la declaramos en el mismo modulo de las otras constantes. Nota las nuevas constantes que usaremos, por supuesto, no son limitativas, agrega todas las que consideres que usaras en tú sistema.


 Option Explicit
 'Cambia aquí el nombre de tu conexión
 Global Const DATOS As String = "datos"
 Global Const AMARILLO_PASTEL As Long = 16777164
 Global Const BLANCO As Long = 16777215
 Global Const IVA As Single = 0.16
 Global Const COPIAS As Byte = 3
 Global Const MAX_COPIAS As Byte = 5


La macro TraerClientes de este modulo, es un poco diferente.


 'Traemos todos los clientes de la base de datos
 Sub TraerClientes( oDatos )
 Dim oConsulta As Object
 Dim sSQL As String
 Dim mClientes()
 Dim lTotalRegistros As Long
     'Obtenemos el total de clientes
     lTotalRegistros = TotalRegistros( oDatos, "Clientes", "Id" )
     'Construimos la consulta para regresar los clientes ordenados por Nombre
     sSQL = "SELECT Nombre FROM Clientes ORDER BY Nombre"
     'Hacemos la consulta
     oConsulta = HacerConsulta( oDatos, sSQL )
     'Movemos los datos a una matriz
     mClientes = CampoAMatriz( oConsulta, lTotalRegistros, 1 )
     'Y los agregamos al cuadro de lista
     Call MatrizACuadroLista( mClientes(), cboClientes )
 End Sub


La función UltimaFactura tiene el siguiente código.


 'Para obtener el número de la ultima factura guardada
 Function UltimaFactura( oDatos ) As Long
 Dim sSQL As String
 Dim oConsulta As Object
     sSQL = "SELECT MAX(Numero) FROM Facturas"
     oConsulta = HacerConsulta( oDatos, sSQL )
     UltimaFactura = oConsulta.getLong( 1 )
 End Function


Observa que obtenemos el máximo del campo Numero, esto no necesariamente tiene que ser así, tal ves la estructura de tú tabla te permita obtener solo el ultimo valor agregado, esto tú lo determinas. La función ExisteValor tiene el siguiente código.


 'Función para saber si un registro ya fue dado de alta
 Function ExisteValor( BaseDatos As Object, Tabla As String, Campo As String, Valor As String) As Boolean
 Dim sSQL As String
 Dim oConsulta As Object
     'Construimos la consulta
     sSQL = "SELECT " & Campo & " FROM " & Tabla & " WHERE " & Campo & " = '" & Valor & "'"
 
 
     'Hacemos la consulta
     oConsulta = HacerConsulta( BaseDatos, sSQL )
     'Verificamos si existe
     If oConsulta.getRow = 1 Then
         ExisteValor = True
     End If
 End Function


El resto del código creo que es autoexplicativo.


El primero control que codificaremos será el cuadro de texto txtClave, usaremos su evento Tecla Pulsada y buscaremos el cliente con dicha clave cuando el usuario presione la tecla  ↵ Enter . Esta información de ayuda, puedes establecerla en la propiedad Texto de ayuda de cada control, si tu sistema no viene (que debería) acompañado de un archivo de ayuda, esta propiedad puede ser muy útil para mostrarle al usuario que se espera que capture o que haga y se muestra en cuanto el cursor entra en el área del control.


ES StarBasic ProyectoPasoAPaso.15.png


Que mostrará en tiempo de ejecución.


ES StarBasic ProyectoPasoAPaso.14.png


El código de este evento es.


 'Cuando el usuario presiona una tecla en el cuadro texto Clave del cliente
 Sub txtClave_TeclaPulsada( Evento )
 Dim lClave As Long
 Dim sCliente As String
     'Si la tecla presiona es la tecla ENTER buscamos al cliente por clave
     If Evento.KeyCode = 1280 Then
         lClave = Val( txtClave.getText )
         txtClave.setText( Format(lClave) )
         sCliente = ExisteCliente( oDatos, Format(lClave), True )
         cboClientes.setText( sCliente )
         If sCliente = "" Then
             MsgBox "El cliente con la clave " & Format(lClave) & " no existe", 16,"Facturacion"
         Else
             txtCantidad.setFocus()
         End If
     End If
 End Sub


El código de la tecla  ↵ Enter  es 1280, tomamos el valor del cuadro de texto, lo convertimos a valor y buscamos esta clave de cliente, el código de la función ExisteCliente es el siguiente. Observa que dependiendo del tercer argumento (Clave) podemos hacer la búsqueda por la clave o por el nombre del cliente, en los dos casos regresamos una cadena con el nombre o con la clave del cliente encontrado o una cadena vacía si no es encontrado.


 'Para saber si un cliente existe, podemos buscar por cliente o por clave 
 Function ExisteCliente( oDatos, Dato As String, Clave As Boolean ) As String
 Dim sSQL As String
 Dim oConsulta As Object
 
 
     'Construimos la consulta correcta
     If Clave Then
         sSQL = "SELECT Nombre FROM Clientes WHERE Id=" & Dato
     Else
         sSQL = "SELECT Id FROM Clientes WHERE Nombre='" & Dato & "'"
     End If
     oConsulta = HacerConsulta( oDatos, sSQL )
     'Si hay una fila al menos se encontró el cliente
     If oConsulta.getRow > 0 Then
         ExisteCliente = oConsulta.getString( 1 )
     End If
 End Function


A parte de la clave, ponemos a disposición del usuario, la posibilidad de seleccionar al cliente desde el control cuadro combinado (ComboBox), usamos un cuadro combinado, por qué este combinado las virtudes de un cuadro de texto para permitir escribir el nombre directamente o seleccionarlo desde la lista desplegable, por esto, tenemos que controlar tanto el evento Estado modificado, que es llamado cuando el usuario selecciona algún elemento de la lista, como el evento Al perder el foco, podemos usar también el evento Texto modificado, pero al ser un campo donde puede haber muchos caracteres implicados nos decantamos por otros eventos, cuyo código es el siguiente.


 'Cuando sale el foco del cuadro combinado de los clientes
 Sub cboClientes_PierdeFoco( Evento )
 Dim sCliente As String
 Dim sClave As String
     sCliente = Trim(Evento.Source.getText)
     If sCliente <> "" Then
         sClave = ExisteCliente( oDatos, sCliente, False )
         If sClave = "" Then
             MsgBox "El cliente " & sCliente & " no esta registrado", 16,"Cliente inexistente"
             txtClave.setText( "" )
         Else
             txtClave.setText( sClave )
         End If
     End If
 End Sub
 
 'Este evento es llamado cuando se usa el raton para cambiar el elemento dentro del cuadro combinado
 Sub cboClientes_Cambia( Evento )
 Dim sCliente As String
 Dim sClave As String
     'Obtenemos el texto y bucamos al cliente por nombre
     sCliente = Evento.Source.getText
     sClave = ExisteCliente( oDatos, sCliente, False )
     If sClave = "" Then
         MsgBox "El cliente " & sCliente & " no esta registrado", 16,"Cliente inexistente"
         txtClave.setText( "" )
     Else
         txtClave.setText( sClave )
         txtCantidad.setFocus()
     End If
 End Sub


Observando el código de los controles txtClave y cboClientes, puedes deducir fácilmente que uno se complementa con el otro, si el usuario escribe una clave correcta el nombre es mostrado, si el usuario escribe un nombre directamente o selecciona uno existente de la lista desplegable, es mostrada su clave correspondiente.


ES StarBasic ProyectoPasoAPaso.13.png


Para el control de la fecha, hemos usado un control fecha (DateField) en donde, al abrir el cuadro de diálogo, mostramos la fecha actual del sistema y las únicas validaciones que hacemos es informarle al usuario si esta usando una fecha anterior o posterior a la actual, en algunos casos tal ves sea necesario evitar que el usuario seleccione una fecha futura, esto, lo dejo a tu criterio, por ahora, lo dejamos a criterio del usuario y solo se lo informamos en la función de validación que implementaremos más adelante.


El botón de comando Reservar, lo usamos para cambiar el estatus de la factura actual a reservada, no se guarda ningún otro dato.


ES StarBasic ProyectoPasoAPaso.12.png


El código de este botón es.


 'Para marcar una factura como reservada para su uso posterior
 Sub cmdReservar_Clic()
 Dim iRes As Integer
 Dim sSQL As String
     iRes = MsgBox( "Esta acción solo marcara esta factura: " & txtFactura.getText() & " como reservada para uso posterior. No se guardará ningún otro dato" & _
                     Chr(10) & Chr(10) & "¿Estás seguro de marcarla como reservada?", 36, "Reservar factura" )
     If iRes= 6 Then
         'Solo nuevas facturas se pueden marcar como reservadas
         sSQL = "INSERT INTO ""Facturas"" (""Numero"",""Estado"") VALUES (" & txtFactura.getText() & ",'Reservada')"
         Call ActualizarDatos( oDatos, sSQL )
         cmdUsarReservada.setEnable( True )
         Call cmdNuevaFactura_Clic()
     End If
 End Sub


Como usaremos el botón de comando Nueva Factura, mostramos su apariencia, recuerda que de forma predeterminada esta desactivado, más adelante te muestro en que caso queda activado.


ES StarBasic ProyectoPasoAPaso.11.png


Y su código.


 'Para preparar el ingreso de una nueva factura
 Sub cmdNuevaFactura_Clic()
 Dim lFactura As Long
     dSubtotal = 0
     EsReservada = False
     Erase mDetalle
     Call ActualizarTotal( 0 )
     lFactura = UltimaFactura( oDatos ) + 1
     txtFactura.setText( Format(lFactura) )
     txtClave.setText( "" )
     cboClientes.setText( "" )
     txtCantidad.setText( "" )
     txtDescripcion.setText( "" )
     txtPu.setText( "" )
     lstDetalle.removeItems( 0, lstDetalle.getItemCount )
     lstLeyendas.removeItems( 0, lstLeyendas.getItemCount )
     cmdNuevaFactura.setEnable( False )
     cmdAgregar.setEnable( True )
     cmdGuardar.setEnable( True )
     cmdReservar.setEnable( True )
     cmdUsarReservada.setEnable( True )
     lblEstado.setText( "Nueva Factura" )
     txtClave.setFocus()
 End Sub


El botón de comando Cancelar, nos sirve para establecer el estatus de la factura actual como cancelada, podemos cancelar tanto una factura nueva como una existente.


ES StarBasic ProyectoPasoAPaso.10.png


El código de este botón es.


 'Botón de comando Cancelar
 Sub cmdCancelar_Clic()
 Dim iRes As Integer
 Dim sSQL As String
     iRes = MsgBox( "Esta acción NO SE PUEDE DESHACER la factura: " & txtFactura.getText() & " se marcará como CANCELADA." & Chr(10) & Chr(10) & "¿Estás seguro de CANCELAR esta factura?", 36, "Cancelar factura" )
     'Nos aseguramos de la respuesta del usuario
     If iRes= 6 Then
         If ExisteValor( oDatos, "Facturas", "Numero", txtFactura.getText() ) Then
             'Si la factura ya existe solo actualizamos su estado
             sSQL = "UPDATE ""Facturas"" SET ""Estado""='Cancelada' WHERE ""Numero""=" & txtFactura.getText()
         Else
             'Si no existe la insertamos como nueva
             sSQL = "INSERT INTO ""Facturas"" (""Numero"",""Estado"") VALUES (" & txtFactura.getText() & ",'Cancelada')"
         End If
         'Hacemos la consulta de actualización
         Call ActualizarDatos( oDatos, sSQL )
         'Preparamos para una nueva factura
         Call cmdNuevaFactura_Clic()
     End If
 End Sub


El botón Usar Reservada, nos servirá para permitirle al usuario seleccionar y usar una factura previamente marcada como reservada.


ES StarBasic ProyectoPasoAPaso.09.png


Y su código.


 'Botón para mostrar las facturas reservadas
 Sub cmdUsarReservada_Clic()
 Dim oDlg As Object
 Dim sSQL As String
 Dim oConsulta As Object
 Dim mReservadas()
     'Verificamos si hay facturas reservadas
     If ExisteValor( oDatos, "Facturas", "Estado", "Reservada") then
         'Cargamos el cuadro de diálogo
         oDlg = CargarDialogo( "Standard","dlgReservadas" )
         'Traemos las facturas reservadas
         sSQL = "SELECT Numero FROM Facturas WHERE Estado='Reservada'"
         oConsulta = HacerConsulta( oDatos, sSQL )
         'Contamos cuantas son
         sSQL = "SELECT COUNT(Estado) FROM Facturas WHERE Estado='Reservada'"
         mReservadas = CampoAMatriz( oConsulta, TotalRegistrosConsulta( oDatos, sSQL ), 3 )        
         Call MatrizACuadroLista( mReservadas(), oDlg.getControl( "lstReservadas" ) )
         'Mostramos el cuadro de diálogo
         oDlg.execute()
         oDlg.dispose()
     Else
         MsgBox "No hay facturas reservadas", 16, "Sin facturas reservadas"
         txtClave.setFocus()
     End If
 End Sub


La macro anterior usa la siguiente macro nueva.


 'Función que devuelve el total de registros de una consulta
 Function TotalRegistrosConsulta( BaseDatos As Object, SQL As String) As Long
 Dim oConsulta As Object
     oConsulta = HacerConsulta( BaseDatos, SQL )
     TotalRegistrosConsulta = oConsulta.getLong(1)
 End Function


Observa que mostramos las facturas en un cuadro de lista de un nuevo cuadro de diálogo que solo tiene dicho cuadro de lista, una etiqueta y un botón de comando para salir.


ES StarBasic ProyectoPasoAPaso.08.png


Con doble clic sobre la factura deseada, la seleccionamos, salimos del cuadro de diálogo y establecemos esta factura como actual en el cuadro de diálogo de facturación. El código de este evento es el siguiente.


 'Evento doble clic para seleccionar una factura reservada
 Sub lstReservadas_Doble_Clic( Evento )
 Dim sFactura As String
 
 
     'Contamos el número de clics y si hay un elemento seleccionado
     If Evento.ClickCount = 2 And ( Evento.Source.getSelectedItemPos > -1 ) Then
         sFactura = Evento.Source.getSelectedItem
         txtFactura.setText( sFactura )
         lblEstado.setText( "Reservada" )
         cmdReservar.setEnable( False )
         cmdNuevaFactura.setEnable( True )
         'Finalizamos el cuadro de diálogo
         Evento.Source.getContext.endExecute()
         EsReservada = True
     End If
 End Sub


Observa que le mostramos al usuario que es una factura reservada, actualizando el texto de la etiqueta lblEstado, también desactivamos el botón Reservar.


ES StarBasic ProyectoPasoAPaso.07.png


Los campos Cantidad y P.U. son controles numéricos (NumericField), por lo que solo permiten la introducción de números, como el cambio de cualquiera de ellos afecta al importe, es decir a su producto, haremos una sola macro para los dos que asignaremos a su evento Texto modificado y cuyo código es el siguiente.


 'Cada vez que cambia la cantidad o el precio
 'actualizamos el importe
 Sub Importe_Modificado( Evento )
 Dim dCantidad As Double
 Dim dPU As Double
 Dim dImporte As Double
     dCantidad = txtCantidad.getValue
     dPU = txtPu.getValue
     dImporte = dCantidad * dPU
     'Nos aseguramos de que el resultado tenga solo dos decimales
     dImporte = FuncionCalc( "ROUND", Array(dImporte,2) )
     txtImporte.setValue( dImporte )
 End Sub


La función FuncionCalc, nos permite llamar a cualquier función integrada de Calc, pasarle los argumentos respectivos y obtener su resultado, esta función ya la hemos visto pero dado que es muy breve la repetimos aquí.


 'Función para llamar a función incorporada de Calc, es importante
 'pasarle los argumentos correctamente en una matriz de datos y
 'usar la función deseada con su nombre en ingles
 Function FuncionCalc( Nombre As String, Datos() ) 
 Dim oSFA As Object
     oSFA = createUnoService( "com.sun.star.sheet.FunctionAccess" )
     FuncionCalc = oSFA.callFunction( Nombre, Datos() )
 End Function


Para mostrar el importe, usamos un control moneda (CurrencyField) por lo que solo establecemos el valor, el formato lo establecemos como propiedad de este control.


ES StarBasic ProyectoPasoAPaso.06.png


El botón de comando Agregar que puedes ver en la imagen anterior, nos sirve para agregar el artículo o producto a la lista de detalle de nuestra factura, su código es el siguiente.


 'Para agregar un artículo al detalle de la factura
 Sub cmdAgregar_Clic()
 Dim dCantidad As Double
 Dim dPU As Double
 Dim dImporte As Double
 Dim sDescripcion As String
 Dim sInfo As String
 
 
     'La cantidad, el precio y el importe
     dCantidad = txtCantidad.getValue
     dPU = txtPu.getValue
     dImporte = txtImporte.getValue
     'La descripcion
     sDescripcion = Trim( txtDescripcion.getText )
     'Como en el cuadro de texto permitimos varias líneas, el usuario puede introducir
     'saltos de líneas innecesarios al inicio y al final de la cadena
     sDescripcion = QuitarSaltosInicio( sDescripcion )
     sDescripcion = QuitarSaltosFin( sDescripcion )
     'Establecemos la cadena ya limpia
     txtDescripcion.setText( sDescripcion )
     'La cantidad y el precio no pueden ser cero
     If dCantidad > 0 Then
         If dPU > 0 Then
             'La descripción no puede estar vacia
             If sDescripcion <> "" Then
                 'Compañero si facturas artículos por más de un millón
                 'que esperas para hacerte mi mecenas 
                 If dImporte < 1000000 Then
                     'Agregamos el artículo y actualizamos el total
                     Call AgregarArticulo( dCantidad, sDescripcion, dPU, dImporte)
                     Call ActualizarTotal( dImporte )
                     'Limpiamos los campos para un nuevo ingreso
                     txtCantidad.setText( "" )
                     txtDescripcion.setText( "" )
                     txtPu.setText( "" )
                     txtCantidad.setFocus()
                 Else
                     MsgBox "El importe es muy alto, verificalo", "16", "Importe"
                     txtPu.setFocus
                 End If
             Else
                 MsgBox "La descripción no puede estar vacía", 16, "Descripción"
                 txtDescripcion.setFocus
             End If
         Else
             MsgBox "El P.U. no puede ser cero", 16, "Precio Unitario"
             txtPu.setFocus
         End If
     Else
         MsgBox "La cantidad no puede ser cero", 16, "Cantidad"
         txtCantidad.setFocus
     End If
 End Sub


Las nuevas funciones y macros usados en este evento son.


 'Función recursiva para quitar los saltos de línea 
 'que estén al inicio de la cadena pasada
 Function QuitarSaltosInicio( Cadena ) As String
 Dim co1 As Integer
     'Si la cadena esta vacia salimos de la función
     If Len(Cadena) = 0 Then
         QuitarSaltosInicio = ""
         Exit Function
     'Si el primer caracter de la izquiera NO es un salto de línea
     'regresamos la cadena y salimos de la función
     ElseIf Left(Cadena,1) <> Chr(10) Then
         QuitarSaltosInicio = Cadena
         Exit Function
     Else
         'Si es un salto de línea, lo omitimos y regresamos el resto de la cadena
         Cadena = Mid( Cadena,2,Len(Cadena) )
         'Volvemos a llamar a la función
         QuitarSaltosInicio = QuitarSaltosInicio(Cadena)
     End If
 End Function
 'Función recursiva igual a la anterior 
 'pero los quita al final de la cadena pasada
 Function QuitarSaltosFin( Cadena ) As String
 Dim co1 As Integer
     If Len(Cadena) = 0 Then
         QuitarSaltosFin = ""
         Exit Function
     ElseIf Right(Cadena,1) <> Chr(10) Then
         QuitarSaltosFin = Cadena
         Exit Function
     Else
         Cadena = Left( Cadena,Len(Cadena)-1 )
         QuitarSaltosFin = QuitarSaltosFin(Cadena)
     End If
 End Function


Ten cuidado con las funciones recursivas, establece siempre como primera o primeras condiciones aquellas con las que salimos de la función. Con un poco de ingenio puedes juntar estas dos funciones en una sola, esa es tu tarea. Las macros restantes son.


 'Para agregar un artículo al cuadro de lista
 Sub AgregarArticulo( Cantidad, Descripcion, Precio, Importe)
 Dim sTmp As String
 Dim iIndice As Integer
 Dim sLinea As String
 Dim mTmp()
     'Obtenemos el nuevo indice para la matriz
     If IsNull( mDetalle ) Then
         iIndice = 0
     Else 
         iIndice = UBound(mDetalle)+1
     End If
     'y redimensionamos con este indice
     Redim Preserve mDetalle( iIndice )
     'Guardamos los datos en el nuevo elemento de la matriz
     With mDetalle( iIndice )
         .Cantidad = Cantidad
         .Descripcion = Descripcion
         .Precio = Precio
         .Importe = Importe
     End With
     'Formateamos la línea de texto para agregarla al cuadro de lista a cada campo
     'le asignamos un espacio concreto y rellenamos con espacio en blanco el resto
     sTmp = Format( Cantidad, "#,##0.00" )
     sLinea = Space(15-Len(sTmp)) & sTmp & " | " 
     'Cuando la descripción es muy larga, la recortamos solo para mostrarla, la cadena
     'completa original queda guardada correctamente en la matriz de detalle
     sTmp = Left(Descripcion,47)
     'Averiguamos si hay saltos de línea
     mTmp = Split( sTmp, Chr(10) )
     If Len(sTmp) >= 47 Then
         'Como los saltos los cuenta pero no se ven, los aumentamos como espacios
         sTmp = sTmp & Space(UBound(mTmp)) & "..." 
     Else
         sTmp = sTmp & Space( 50-Len(sTmp) ) & Space(UBound(mTmp))
     End If
     sLinea = sLinea & sTmp & " | " 
     'Quince espacios para el precio
     sTmp = Format( Precio, "#,##0.00" )
     sLinea = sLinea & Space(15-Len(sTmp)) & sTmp & " | " 
     'Veinte para el importe
     sTmp = Format( Importe, "$ #,##0.00" )
     sLinea = sLinea & Space(20-Len(sTmp)) & sTmp    
     'Agregamos la línea formateada completa
     lstDetalle.addItem( sLinea, lstDetalle.getItemCount )
 End Sub
 
 
 'Para actualizar el total de la factura, hacemos las operaciones necesarios
 'damos el formato correcto y los mostramos en los controles correctos
 Sub ActualizarTotal( Importe )
 Dim dImpuesto As Double
 Dim dTotal As Double
     dSubtotal = dSubtotal + Importe
     txtSubtotal.setText( Format( dSubtotal, "$ #,##0.00" ) )
     'Nos aseguramos de que el resultado tenga solo dos decimales
     dImpuesto = FuncionCalc( "ROUND", Array( dSubtotal*IVA, 2 ) )
     txtImpuesto.setText( Format( dImpuesto, "$ #,##0.00" ) )
     dTotal = dSubtotal + dImpuesto
     txtTotal.setText( Format( dTotal, "$ #,##0.00" ) )
 End Sub


Con el código anterior ya debes de poder agregar artículos. Hay todavía un caso en el que al agregar un artículo, falle el guardado de la factura, tu tarea es encontrarlo, como pista, concéntrate en lo que permites guardar en el campo Descripción.


ES StarBasic ProyectoPasoAPaso.05.png


Por supuesto debemos de permitir al usuario quitarlos, para ello usamos el evento Clic del cuadro de lista, pero tiene que dar doble clic.


 'Para quitar un articulo del detalle
 Sub lstDetalle_Doble_Clic( Evento )
 Dim Pos As Integer
     If Evento.ClickCount = 2 And ( Evento.Source.getSelectedItemPos > -1 ) Then
         Pos = Evento.Source.getSelectedItemPos
         Evento.Source.removeItems( Pos, 1 )
         Call QuitarArticulo( Pos )        
     End If
 End Sub


La macro QuitarArticulo tiene el siguiente código.


'Al quitar un articulo hay que actualizar el total y quitarlo de la matriz
Sub QuitarArticulo( Indice )
Dim co1 As Integer
Dim dImporte As Double
Dim mTmp() As New Articulo
    'Obtenemos el importe y lo ponemos en negativo
    dImporte = mDetalle( Indice ).Importe * -1
    'Actualizamos el total
    Call ActualizarTotal( dImporte )
    'Si hay más de un artículo
    If UBound(mDetalle) > 0 Then
        'Redimencionamos la matriz temporal
        Redim Preserve mTmp( UBound(mDetalle)-1 )
        'Movemos los datos a esta matriz
        For co1 = LBound( mTmp ) To UBound( mTmp )
            'Los que están antes del actual los pasamos tal cual
            If co1 < Indice Then
                mTmp(co1).Cantidad = mDetalle(co1).Cantidad
                mTmp(co1).Descripcion = mDetalle(co1).Descripcion
                mTmp(co1).Precio = mDetalle(co1).Precio
                mTmp(co1).Importe = mDetalle(co1).Importe
            Else
                'Los que están después del actual los movemos del inmediato posterior
                mTmp(co1).Cantidad = mDetalle(co1+1).Cantidad
                mTmp(co1).Descripcion = mDetalle(co1+1).Descripcion
                mTmp(co1).Precio = mDetalle(co1+1).Precio
                mTmp(co1).Importe = mDetalle(co1+1).Importe
            End If
        Next co1
        'Redimenciona la matriz original
        Redim Preserve mDetalle( UBound(mTmp) )
        'Movemos los datos correctos
        For co1 = LBound( mTmp ) To UBound( mTmp )
            mDetalle(co1).Cantidad = mTmp(co1).Cantidad
            mDetalle(co1).Descripcion = mTmp(co1).Descripcion
            mDetalle(co1).Precio = mTmp(co1).Precio
            mDetalle(co1).Importe = mTmp(co1).Importe
        Next co1
    Else
        'Si no quedan elementos borramos la matriz
        Erase mDetalle
    End If
End Sub


Todo ese código para mover los datos entre las matrices es por la limitada forma que tienen los lenguajes Basic en la manipulación de matrices.


Verifica que ya puedas agregar y quitar artículos o productos al detalle de la factura y que el importe total se actualice correctamente.


Nuestra siguiente tarea es permitirle al usuario agregar leyendas a la factura, estas, son útiles para agregar notas de cualquier tipo. El código del botón Leyenda es el siguiente.


 'Para agregar una nueva leyenda a la factura
 Sub cmdLeyenda_Clic()
 Dim sLeyenda As String
     'Cambia aquí el limite de leyendas que quieras permitir, te recomiendo un número pequeño
     'tienes que adaptar el cuadro de lista del formato de impresión para que visualice 
     'correctamente este número de líneas
     If lstLeyendas.getItemCount < 4 Then
         sLeyenda = Trim(InputBox( "Introduce la nueva leyenda para esta factura" ))
         If sLeyenda <> "" Then
             lstLeyendas.addItem( sLeyenda, lstLeyendas.getItemCount )
         End If
     Else
         MsgBox "Solo se permiten cuatro líneas de leyendas", 16,"Leyendas"
     End If
 End Sub


Y por supuesto el código para quitarlas.


'Con doble clic borramos las leyendas del cuadro de lista
Sub lstLeyendas_Doble_Clic( Evento )
Dim Pos As Integer
    If Evento.ClickCount = 2 And ( Evento.Source.getSelectedItemPos > -1 ) Then
        Pos = Evento.Source.getSelectedItemPos
        Evento.Source.removeItems( Pos, 1 )
    End If
End Sub


Solo nos faltan tres botones por codificar, veamos el primero que es Guardar.


ES StarBasic ProyectoPasoAPaso.04.png


Y su código.


 'Para guardar una factura
 Sub cmdGuardar_Clic()
 Dim sFactura As String
 Dim sCliente As String
 Dim sFecha As String
 Dim sSubtotal As String
 Dim sImpuesto As String
 Dim sTotal As String
 Dim sSQL As String
 Dim sTmp As String
 Dim co1 As Integer
 Dim sLeyendas As String
 Dim iRes As Integer
     'Validamos todos los datos
     If ValidarDatos() Then
         iRes = MsgBox( "Todos los datos con correctos, ¿guardar la factura?", 36, "Guardar factura" )
         'si el usuarios responde que si
         If iRes = 6 Then
             'Obtenemos los datos de la factura, la fecha y los valores los convertimos a texto para
             'su uso correcto en la cadena SQL de inserción
             sFactura = txtFactura.getText()
             sCliente = txtClave.getText()
             sFecha = Format(txtFecha.getDate())
             sFecha = "'" & Left(sFecha,4) & "-" & Mid(sFecha,5,2) & "-" & Right(sFecha,2) & "'"
             sSubtotal = Format(dSubtotal)
             sImpuesto = Format( FuncionCalc( "ROUND", Array( dSubtotal*IVA, 2 ) ) )
             sTotal = Format( dSubtotal + Val(sImpuesto) )
             'Si hay leyendas las juntamos en una sola cadena separa por el caracter pipe (|)
             If lstLeyendas.getItemCount > 0 Then
                 sLeyendas = "'" & Join( lstLeyendas.getItems(), "|" ) & "'"
             Else
                 'Si no hay leyendas, se pasa una cadena vacía, observa el par de comillas simples
                 sLeyendas = "''"
             End If
             'Si la factura es reservada ya esta guardada por lo que solo actualizamos los campos restantes
             If EsReservada Then
                 sTmp = Chr(34) & "Cliente" & Chr(34) & "=" & sCliente & ","
                 sTmp = sTmp & Chr(34) & "Fecha" & Chr(34) & "=" & sFecha & ","
                 sTmp = sTmp & Chr(34) & "Subtotal" & Chr(34) & "=" & sSubtotal & ","    
                 sTmp = sTmp & Chr(34) & "Impuesto" & Chr(34) & "=" & sImpuesto & ","
                 sTmp = sTmp & Chr(34) & "Total" & Chr(34) & "=" & sTotal & ","
                 sTmp = sTmp & Chr(34) & "Estado" & Chr(34) & "='Elaborada'," 
                 sTmp = sTmp & Chr(34) & "Leyendas" & Chr(34) & "=" & sLeyendas 
                 sSQL = "UPDATE ""Facturas"" SET " & sTmp & " WHERE ""Numero""=" & sFactura
             Else
                 'Si es nueva usamos todos los campos
                 sTmp = sFactura & "," & sCliente & "," & sFecha & "," & sSubtotal & "," & sImpuesto & "," & sTotal & ",'Elaborada'," & sLeyendas 
                 sSQL = "INSERT INTO ""Facturas"" ( ""Numero"",""Cliente"",""Fecha"",""Subtotal"",""Impuesto"",""Total"",""Estado"",""Leyendas"" ) VALUES (" & sTmp & ")"
             End If
             'Insertamos los datos de la factura
             Call ActualizarDatos( oDatos, sSQL )
             'Insertamos el detalle de la factura
             For co1 = LBound( mDetalle ) To UBound( mDetalle )
                 sTmp = sFactura & "," & mDetalle(co1).Cantidad & ",'" & mDetalle(co1).Descripcion & "'," & mDetalle(co1).Precio & "," & mDetalle(co1).Importe 
                 sSQL = "INSERT INTO ""Detalle"" ( ""Factura"",""Cantidad"",""Descripcion"",""Precio"",""Importe"" ) VALUES (" & sTmp & ")"
                 Call ActualizarDatos( oDatos, sSQL )
             Next
 
 
             'Prepara todo para una nueva factura
             Call cmdNuevaFactura_Clic()
 
 
         End If
     End If
 End Sub


El código de la función ValidarDatos para este cuadro de diálogo.


 'Función para validar todos los datos
 Function ValidarDatos() As Boolean
 Dim sClave As String
 Dim sFecha As String
 Dim dFecha As Date
 Dim iRes As Integer
     'La clave del cliente
     sClave = Format(Val(txtClave.getText()))
     'Como usamos Val y después Format, en ves de vacía, si no hay nada siempre da 0
     'cuidado, tal vez, dependiendo de tu tabla Clientes, tal vez si puedas tener un
     'cliente con la clave 0
     If sClave <> "0" Then
         'Verificamos que exista el cliente
         If ExisteCliente( oDatos, sClave, True ) <> "" Then
             'Verificamos que se hallan agregado productos
             If lstDetalle.getItemCount > 0 Then
                 'Obtenemos la fecha
                 sFecha = Format(txtFecha.getDate)
                 dFecha = DateSerial( Val(Left(sFecha,4)), Val(Mid(sFecha,5,2)), Val(Right(sFecha,2)) )
                 'Si es menor a la fecha actual, se puede usar pero le avisamos al usuario
                 If dFecha < Date() Then
                     iRes = MsgBox( "La fecha " & Format(dFecha,"dd-mmm-yy") & " es una fecha pasada" & Chr(10) & Chr(10) & "¿Estás seguro de usar esta fecha?", 36, "Fecha pasada")
                     If iRes <> 6 Then
                         txtFecha.setFocus()
                         Exit Function
                     End If
                 End If
                 'Si la fecha es mayor, también se puede usar pero avisamos
                 If dFecha > Date() Then
                     iRes = MsgBox( "La fecha " & Format(dFecha,"dd-mmm-yy") & " es una fecha futura" & Chr(10) & Chr(10) & "¿Estás seguro de usar esta fecha?", 36, "Fecha futura")
                     If iRes <> 6 Then
                         txtFecha.setFocus()
                         Exit Function
                     End If
                 End If
                 'Si la factura es reservada avisamos
                 If EsReservada Then
                     iRes = MsgBox( "Esta factura " & txtFactura.getText() & " tiene estatus de RESERVADA" & Chr(10) & Chr(10) & "¿Estás seguro de usarla?", 36, "Guardar factura" )
                     If iRes <> 6 Then
                         Exit Function
                     End If
                 End If
 
 
                 'Si todas las pruebas se pasaron regresamos Verdadero, cualquier validación que necesites
                 'agregala ANTES de esta línea
                 ValidarDatos = True
 
 
             Else
                 txtCantidad.setFocus()
                 MsgBox "No hay artículos a facturar, agrega al menos uno", 16, "Sin artículos"
             End If
         Else
             cboClientes.setText( "" )
             MsgBox "El cliente con la clave " & sClave & " no existe", 16,"Facturacion"
             txtClave.setFocus()
         End If
     Else
         cboClientes.setText( "" )
         MsgBox "Escribe una clave de cliente a buscar", 16,"Facturacion"
         txtClave.setFocus()    
     End If
 End Function


Antes ver el código del botón de comando Imprimir, es importante que ya tengas preparado el formato donde se vaciará la información de la factura a imprimir, tener perfectamente determinadas e identificadas las celdas que usaras para cada dato. En mi propuesta usaremos además, algunos controles de formulario que te muestro en la siguiente imagen.


ES StarBasic ProyectoPasoAPaso.03.png


Un cuadro de lista (1) para las leyendas, un cuadro de texto (2) para la cantidad con letra con su etiqueta respectiva, y otros tres cuadros de texto (3) para los totales. Para evitarnos el manipular directamente estos controles, los relacionaremos con celdas de la hoja de calculo y será en estas donde vaciaremos los datos respectivos. Es importante que todos estos controles los ancles a la página, no a la celda, con esto logramos que, independientemente del número de líneas que ocupe un concepto a facturar, la posición de estos controles no cambie y siempre se impriman en el lugar correcto. Por supuesto tienes que adaptar estas posiciones al formato físico de tu factura. Agrega además, una línea horizontal (1) a la altura de la ultima fila a imprimir, esta línea estará invisible (2), es importante que este anclada a la celda, de este modo, conforme insertemos conceptos, sé desplazará, la moveremos a la distancia que queramos por código y al estar anclada a la celda, sabremos en donde establecer el área de impresión.


ES StarBasic ProyectoPasoAPaso.02.png


Para ubicarla rápidamente por indice, asegúrate de enviarla al fondo de todos los elementos, de este modo sabemos que tendrá como indice el valor cero.


El código del botón de comando Imprimir es el siguiente.


 'Para imprimir la factura actual
 Sub cmdImprimir_Clic()
 Dim iRes As Integer
     'Validamos que todo este bien
     If ValidarDatos() Then
         iRes = MsgBox( "Todos los datos con correctos, esta factura no esta guardada, puedes imprimirla pero NO SE GUARDARÁ ningún dato, ¿deseas imprimirla?", 36, "Imprimir factura" )
         If iRes = 6 Then
             'Imprimimos tomando los datos actuales del cuadro de diálogo
             Call ImprimirFactura( "" )
         End If
     End If
 End Sub


La nueva macro ImprimirFactura tiene el código siguiente.


 'Macro para vaciar los datos al formato de impresion
 Sub ImprimirFactura( NumeroFactura As String )
 Dim sCliente As String
 Dim sFecha As String
 Dim dFecha As Date
 Dim oFecha As Object
 Dim sLinea As String
 Dim sSubtotal As String
 Dim sImpuesto As String
 Dim sTotal As String
 Dim sEstado As String
 Dim oDatosCliente As Object
 Dim oDatosFactura As Object
 Dim oDatosDetalle As Object
 Dim sSQL As String
 Dim mLeyendas()
 Dim HayLeyendas As Boolean
 Dim co1 As Integer
 Dim sCelda As String
 Dim iCopias As Integer
 Dim lTotalDetalle As Integer
     'Borramos las celdas donde vaciamos las leyendas
     Call BorrarDatosRango( ThisComponent, "Impresion", "A51:A54" )
     'Las celdas donde mostramos el detalle
     Call BorrarDatosRango( ThisComponent, "Impresion", "A11:D30" )
     Call BorrarAreasImpresion( ThisComponent, "Impresion" )
     'Copias a imprimir
     iCopias = 1
     'Tomamos los datos del cuadro de diálogo, con esto permitimos hacer pruebas
     'e imprimir las llamadas pre-facturas
     If NumeroFactura = "" Then
         sCliente = txtClave.getText()
         sFecha = Format(txtFecha.getDate())
         dFecha = DateSerial( Val(Left(sFecha,4)), Val(Mid(sFecha,5,2)), Val(Right(sFecha,2)) )
         sSubtotal = txtSubtotal.getText()
         sImpuesto = txtImpuesto.getText()
         sTotal = txtTotal.getText()
         'Si tenemos leyendas
         If lstLeyendas.getItemCount > 0 Then
             mLeyendas = lstLeyendas.getItems()
             HayLeyendas = True
         End If
         'Vaciamos el detalle de la factura
         For co1 = LBound( mDetalle ) To UBound( mDetalle )
             sCelda = "A" & Format(11+co1)
             Call EscribirEnCelda( ThisComponent, "Impresion", sCelda, 2, mDetalle(co1).Cantidad )
             sCelda = "B" & Format(11+co1)
             Call EscribirEnCelda( ThisComponent, "Impresion", sCelda, 1, mDetalle(co1).Descripcion )
             sCelda = "C" & Format(11+co1)
             Call EscribirEnCelda( ThisComponent, "Impresion", sCelda, 2, mDetalle(co1).Precio )
             sCelda = "D" & Format(11+co1)
             Call EscribirEnCelda( ThisComponent, "Impresion", sCelda, 2, mDetalle(co1).Importe )
         Next co1
     Else
         'Tomamos los datos de la base de datos
         sSQL = "SELECT * FROM Facturas WHERE Numero=" & NumeroFactura
         oDatosFactura = HacerConsulta( oDatos, sSQL )
         sCliente = oDatosFactura.getString( 2 )
         'getDate nos devuelve una estructura com.sun.star.util.Date
         oFecha = oDatosFactura.getDate( 3 )
         dFecha = DateSerial( oFecha.Year, oFecha.Month, oFecha.Day )
         sSubtotal = Format( oDatosFactura.getDouble( 4 ), "$ #,##0.00" )
         sImpuesto = Format( oDatosFactura.getDouble( 5 ), "$ #,##0.00" )
         sTotal = Format( oDatosFactura.getDouble( 6 ), "$ #,##0.00" )
         sEstado = oDatosFactura.getString( 7 )
         If sEstado = "Elaborada" Then
             'Cambiamos el número de copias predeterminadas a imprimir, generalmente las
             'facturas se imprimen en juegos
             iCopias = COPIAS
         End If
         'Si hay leyendas
         If oDatosFactura.getString( 8 ) <> "" Then
             'Las obtenemos y llenamos la matriz
             mLeyendas = Split( oDatosFactura.getString( 8 ), "|" )
             HayLeyendas = True
         End If
         'El detalle de la factura        
         sSQL = "SELECT * FROM Detalle WHERE Factura=" & NumeroFactura
         oDatosDetalle = HacerConsulta( oDatos, sSQL )
         sSQL = "SELECT COUNT(Factura) FROM Detalle WHERE Factura=" & NumeroFactura
         lTotalDetalle = TotalRegistrosConsulta( oDatos, sSQL )
         For co1 = 1 To lTotalDetalle
             sCelda = "A" & Format(10+co1)
             Call EscribirEnCelda( ThisComponent, "Impresion", sCelda, 2, oDatosDetalle.getFloat(2) )
             sCelda = "B" & Format(10+co1)
             Call EscribirEnCelda( ThisComponent, "Impresion", sCelda, 1, oDatosDetalle.getString(3) )
             sCelda = "C" & Format(10+co1)
             Call EscribirEnCelda( ThisComponent, "Impresion", sCelda, 2, oDatosDetalle.getFloat(4) )
             sCelda = "D" & Format(10+co1)
             Call EscribirEnCelda( ThisComponent, "Impresion", sCelda, 2, oDatosDetalle.getDouble(5) )
             oDatosDetalle.next()
         Next co1
     End If
 
 
     'Obtenemos y vaciamos los datos del cliente
     sSQL = "SELECT * FROM Clientes WHERE Id=" & sCliente
     oDatosCliente = HacerConsulta( oDatos, sSQL )
     sLinea = oDatosCliente.getString( 2 )
     Call EscribirEnCelda( ThisComponent, "Impresion", "A1", 1, sLinea )
     sLinea = oDatosCliente.getString( 3 ) & " " & oDatosCliente.getString( 4 ) & ", Col. " & oDatosCliente.getString( 5 )
     Call EscribirEnCelda( ThisComponent, "Impresion", "A2", 1, sLinea )
     sLinea = oDatosCliente.getString( 6 ) & ", " & oDatosCliente.getString( 8 ) & ", C.P. " & oDatosCliente.getString( 7 )
     Call EscribirEnCelda( ThisComponent, "Impresion", "A3", 1, sLinea )
     sLinea = "R.F.C.: " & oDatosCliente.getString( 9 )
     Call EscribirEnCelda( ThisComponent, "Impresion", "A4", 1, sLinea )
     'La fecha de la factura
     sLinea = "México, D.F., a " & Format( dFecha, "dd \d\e mmmm \d\e\l yyyy" )
     Call EscribirEnCelda( ThisComponent, "Impresion", "D5", 1, sLinea )
     'Los importes
     Call EscribirEnCelda( ThisComponent, "Impresion", "D50", 1, sSubtotal )
     Call EscribirEnCelda( ThisComponent, "Impresion", "D51", 1, sImpuesto )
     Call EscribirEnCelda( ThisComponent, "Impresion", "D52", 1, sTotal )
     'La cantidad con letra
     sLinea = Numeros_Letras( Val(Mid(sTotal,3,50)), "peso", False, "", "(", "/100 m.n.)", 1)
     Call EscribirEnCelda( ThisComponent, "Impresion", "A50", 1, sLinea )
     'Si hay leyendas
     If HayLeyendas Then
         Call EscribirEnRango( ThisComponent, "Impresion", "A51", 1, mLeyendas() )    
     End If
     'Establecemos el área de impresión e imprimimos
     Call EstablecerAreaImpresion(iCopias, sEstado, NumeroFactura)
 End Sub


Tenemos algunas macros nuevas, para borrar el contenido de un rango.


 'Macro que borra cualquier valor, texto o formula del rango pasado
 Sub BorrarDatosRango( Documento As Object, Hoja As String, Rango As String )
 Dim oRango As Object
     oRango = Documento.getSheets.getByName( Hoja ).getCellRangeByname( Rango )
     oRango.clearContents( 21 )
 End Sub


Para borrar todas las áreas de impresión.


'Borrar todas las áreas de impresión de la hoja
Sub BorrarAreasImpresion( Documento As Object, Hoja As String )
Dim mAI()
    'Borramos todas las áreas de impresión de la hoja 
    Documento.getSheets.getByName( Hoja ).setPrintAreas( mAI() )
End Sub


Para escribir en una celda determinada.


 'Vaciamos el dato pasado en la Celda pasada, hay que establecer el Tipo correcto
 Sub EscribirEnCelda( Documento As Object, Hoja As String, Celda As String, Tipo As Byte, Dato )
 Dim oCelda As Object
     oCelda = Documento.getSheets.getByName( Hoja ).getCellRangeByname( Celda )
     Select Case Tipo
         Case 1 : oCelda.setString( Dato )
         Case 2 : oCelda.setValue( Dato )
         Case 3 : oCelda.setFormula( Dato )
     End Select
 End Sub


Para vaciar una matriz unidimensional en un rango, puedes mejorar esta macro de modo que acepte matrices de dos dimensiones y en ves de usar un ciclo usas el método setDataArray para vaciar toda la matriz.


 'Escribe una matriz en un rango, solo hay que pasarle la celda de inicio
 Sub EscribirEnRango( Documento As Object, Hoja As String, Celda As String, Tipo As Byte, Datos() )
 Dim oCelda As Object
 Dim co1 As Integer
 Dim Fil As Long, Col As Long
     oCelda = Documento.getSheets.getByName( Hoja ).getCellRangeByname( Celda )
     'fila y columna de inicio
     Fil = oCelda.getRangeAddress.StartRow
     Col = oCelda.getRangeAddress.StartColumn
     For co1 = LBound( Datos ) To UBound( Datos )
         oCelda = Documento.getSheets.getByName( Hoja ).getCellByPosition( Col, Fil+co1 )
         Select Case Tipo
             Case 1 : oCelda.setString( Datos(co1) )
             Case 2 : oCelda.setValue( Datos(co1) )
             Case 3 : oCelda.setFormula( Datos(co1) )
         End Select
     Next co1    
 End Sub


La macro para establecer el área de impresión y por fin, imprimir. Asumimos que se envía a la impresora predeterminada, si usas otra impresora, usa las propiedades aprendidas en el capítulo de impresión.


 'Establece el área de impresión e imprime
 Sub EstablecerAreaImpresion( NumCopias As Integer, Estado As String, NumeroFactura As String )
 Dim mAI(0) As New com.sun.star.table.CellRangeAddress
 Dim oDir As New com.sun.star.table.CellAddress
 Dim oDoc As Object
 Dim oHoja As Object
 Dim oPaginaDibujo As Object
 Dim oLinea As Object
 Dim oAncla As object
 Dim oPos As New com.sun.star.awt.Point
 Dim mOpc(0) As New com.sun.star.beans.PropertyValue
 Dim oGlobal As Object
 Dim sSQL As String
     'Servicio global de configuración de Calc
     oGlobal = createUnoService( "com.sun.star.sheet.GlobalSheetSettings" )
     oDoc = ThisComponent
     oHoja = oDoc.getSheets.getByName( "Impresion" )
     oPaginaDibujo = oHoja.getDrawPage()
 
 
     'Esta línea oculta nos auxilia para obtener el área de impresión
     oLinea = oPaginaDibujo.getByINdex( 0 )
     oPos.X = 0
     'La posicionamos a 18 cm de la parte superior que es el área de impresión disponible
     oPos.Y = 18000
     oLinea.setPosition( oPos )
     'Obtenemos la dirección de la celda donde haya quedado
     oDir = oLinea.Anchor.getCellAddress
 
 
     'Construimos el área de impresión correctamente
     'Rango A1:D ?
     mAI(0).Sheet = oDir.Sheet
     mAI(0).StartColumn = 0
     mAI(0).StartRow = 0
     mAI(0).EndColumn = 3
     mAI(0).EndRow = oDir.Row
 
 
     'Agregamos el área de impresión
     oHoja.setPrintAreas( mAI() )
     'Preguntamos cuantas copias se imprimirán
     NumCopias = Val( InputBox( "¿Cuantas copias se imprimirán?" & Chr(10) & Chr(10) & "Asegurate de que la impresora este lista para imprimir", "Imprimir factura", NumCopias ) )
     'Tienes que ser mayor a cero
     If NumCopias > 0 Then
         'El número de copias
         mOpc(0).Name = "CopyCount"
         mOpc(0).Value = NumCopias    
         'Enviamos a imprimir, dado que imprimiremos una hoja diferente a la seleccionada
         'es importante activar esta propiedad, si no, no imprimirá nada
         oGlobal.PrintAllSheets = True
         'Imprimimos
         oDoc.Print( mOpc() )
         'Desactivamos la propiedad
         oGlobal.PrintAllSheets = False
         'Actualizamos el estado de la factura, solo si su estado anterior es Elaborada
         If Estado = "Elaborada" Then
             sSQL = "UPDATE ""Facturas"" SET ""Estado""='Impresa' WHERE ""Numero""=" & NumeroFactura
             Call ActualizarDatos( oDatos, sSQL )
         End If
     Else
         MsgBox "Proceso cancelado, no se imprimió la factura", 0, "Factura Libre"
     End If
 End Sub


Solo nos resta hacer el código del botón de comando Guardar e Imprimir que es el siguiente, esta macro usa las mismas subrutinas anteriores. Como le pasamos el número de factura a la macro ImprimirFactura, toma los datos de la factura de la base de datos.


 'Guarda e imprime la factura
 Sub cmdGuardarImprimir_Clic()
 Dim sFactura As String
 Dim iRes As Integer
     'Guardamos el número de factura actual
     sFactura = txtFactura.getText()
     'Intentamos guardar
     Call cmdGuardar_Clic()
     'Si cambia el número de factura esta se guardó correctamente
     If sFactura <> txtFactura.getText() Then
         iRes = MsgBox( "La factura " & sFactura & " se guardó correctamente, puedes imprimirla ahora, ¿deseas imprimirla?", 36, "Imprimir factura" )
         If iRes = 6 Then
             'Imprimimos la factura guardada
             Call ImprimirFactura( sFactura )
         End If
     End If
 End Sub


Mira que bien esta quedando.


ES StarBasic ProyectoPasoAPaso.01.png


Tal vez pensaras que se me olvido el código del botón Ver Facturas, pero no, no se me olvido si no que es tu tarea escribirlo, la idea de este botón es mostrar en un nuevo cuadro de diálogo, las facturas guardadas, darle la posibilidad al usuario de filtrar por varias formas, de ver el detalle de la factura que quiera y de cambiar el estatus de las mismas, este cuadro de diálogo tiene que ser capaz también, de imprimir o reimprimir la factura que sea, así como de mostrar los reportes necesarios de la facturación. No me decepciones e intenta resolverlo antes de ver mi propuesta en los archivos de código que acompañan a este libro.


Posibles mejoras que te quedan de tarea:


  • Si la base de datos no esta registrada, pero el archivo ODB esta en el mismo directorio del archivo usado como interfaz, podría registrarse automáticamente o preguntarle al usuario si quiere usar este archivo como base de datos fuente.
  • Evitar que el usuario cierre los cuadros de diálogo con el botón de cierre, obligarlo a usar siempre el botón Salir.
  • Puedes cambiar los controles de texto por controles de formato donde se requiera, por ejemplo: el C.P. Y el RFC.
  • Puedes permitir al usuario moverse entre los campos con la tecla  ↵ Enter .
  • ¿Se te ocurre alguna otra?


Solo me resta decirte; para no cometer errores, tienes que convertirte en un experimentado programador, para convertirte en un programador experimentado, tienes que cometer muchos errores...


¡¡Feliz programación!!



ES.Plantillas.Logo foro es.png
Si tienes dudas acerca de lo aquí explicado, tienes algún problema con AOO,
o quieres ampliar la información, no dudes en dirigirte al

Foro Oficial en español de Apache OpenOffice para Macros y API UNO

Personal tools