Punto de venta con Spring Boot, MySQL y Bootstrap 4

Sistema de ventas con Spring MVC, MySQL y Bootstrap

En este post voy a presentar el código fuente y el JAR de un sistema de ventas o punto de venta open source programado en Java, utilizando el framework web Spring Boot con el paradigma MVC.

Para el diseño he utilizado Bootstrap, y para la persistencia de datos, MySQL.

Punto de venta con Spring Boot, MySQL y Bootstrap 4

A través de este post te explicaré cómo fue programado este sistema de ventas con Spring Boot, además de mostrarte en dónde está el código fuente y cómo ejecutar el sistema; ya que el mismo es open source y gratuito.

Sistema de ventas con Spring Boot

Lo que tiene el sistema es:

  • Control de productos. Registrar producto, eliminar, editar
  • Descuento de existencias por cada venta
  • Interfaz para vender, en donde se escanea el código de barras y se agrega al carrito de compras
  • Registro de ventas, en donde se muestra la fecha y hora de la venta, el total y los productos que se compraron
  • Diseño responsivo. Se adapta a teléfonos, tabletas y computadoras portátiles o de escritorio

Puedes descargar el sistema en la página de releases, o analizar, estudiar y modificar el código fuente en GitHub (recuerda configurar GitHub desktop antes)

Presentación en YouTube

Si quieres saber cómo ejecutarlo o quieres ver un vídeo con las características de este software, míralo a continuación:

Características técnicas

Este sistema de ventas con Spring Boot utiliza el motor de base de datos MySQL, y Bootstrap 4 como framework CSS para el diseño.

También he cargado los iconos de FontAwesome y un poco de JavaScript para el menú responsivo de Bootstrap 4.

El motor de plantillas usado es Thymeleaf.

Dependencias y configuración del sistema de ventas con Spring Boot

Las dependencias o el archivo build.gradle es el siguiente:

El archivo application.properties es el siguiente:

Solo estoy configurando la manera en la que hibernate hace las migraciones, además de indicar que se usará el dialecto MySQL, la contraseña, usuario y cadena de conexión a la base de datos.

En este caso la base de datos se llama ventas_springboot.

Los templates o layouts

Voy a comenzar explicando los layouts, que fue un tema un poco complicado. Lo que requería hacer era incluir o reutilizar plantillas como el encabezado o el pie, modificando únicamente el contenido.

Al final lo logré utilizando alguna dependencia de por ahí. Así que definí la plantilla maestra:

Tengo 4 partes, el encabezado, el menú de navegación, el contenido principal y el pie de página.

El encabezado queda así:

En el mismo cargo el CSS de los estilos y los iconos, así como el framework de Bootstrap.

Después está el menú de navegación:

El navbar solo tiene enlaces hacia cada apartado del sistema. Luego tenemos al pie (ya sé que en orden, ahora iría el contenido, pero el mismo se define en cada sección que veremos más abajo):

Lo único que se hace en el pie es definir los créditos y un script para que el menú sea responsivo. Ese es todo el JavaScript que verás dentro del sistema.

Ahora que he presentado el layout principal veremos cada apartado.

Productos

La gestión de los productos es sencilla. Comenzamos por ver las entidades y repositorios.

Primero definimos la entidad Producto, que tendrá su respectiva tabla en MySQL.

Lo único que defino es un producto (que luego será un producto que se agrega al carrito de ventas o un producto vendido) con algunas validaciones, constructores, getters y setters.

El repositorio de productos es el siguiente:

Estoy extendiendo de CrudRepository, lo único que hago es definir mi interfaz con la entidad y Spring se encarga del resto. He definido también una “consulta” adicional (findFirstByCodigo) que buscará el primer producto por código de barras, esto será útil en otros casos que ya veremos.

Agregar producto

Cuando el usuario quiere agregar un producto se invoca al siguiente método del controlador:

Simplemente regresamos la vista con un Producto vacío, si quieres saber más sobre esto mira mi tutorial sobre el procesamiento de formularios en Spring Boot.

Para agregar un producto se define el siguiente formulario:

Tiene todo lo necesario para mostrar los errores de validación en caso de que existan. Su action es el método del controlador:

En la función recibimos el objeto del formulario a través de @ModelAtribute.

Validamos el formulario y en caso de que haya errores de validación regresamos la vista y detenemos la ejecución del código.

Como segundo paso verificamos que no exista un producto con ese código de barras, en caso de que exista ponemos algunos mensajes flash y regresamos.

En caso de que todo vaya bien, guardamos el producto invocando a productosRepository.save(producto) y volvemos al formulario, con un mensaje de éxito.

Mostrar productos

Además de los detalles de los productos tengo 2 botones, o eso parece, ya que en realidad uno es un enlace que lleva a la edición del producto, y otro es el botón submit de un formulario que eliminará el producto como veremos más adelante.

Para mostrar productos se visita la URL /productos/mostrar que invoca al siguiente método:

Devuelve la vista ver_productos.html y le pasa una lista de todos los productos, invocando al método findAll del repositorio de productos.

La vista en cuestión es la siguiente, en donde se dibuja una tabla responsiva de Bootstrap usando th:each propio de Thymeleaf para renderizar arreglos o listas.

Editar producto

Sistema de ventas con Spring - Editar producto
Formulario para editar producto de POS usando Spring y Bootstrap 4

Cuando alguien hace click en Editar se va a una URL como la siguiente: /productos/editar/2 en donde el último valor es el id del producto. Lo que se hace es obtener ese producto por id y regresar la view que va a mostrar el formulario, así que el controlador queda así:

A través del método findById busco el producto usando el id obtenido de @PathVariable. Utilizo orElse para asegurarme de que me regresa un objeto de tipo Producto (y no del tipo Optional) o null.

La vista es la siguiente:

De igual manera es un formulario que mostrará los errores de validación en caso de que existan. Tengo además un input de tipo hidden con el id del producto, aunque bien se podría pasar el id en la ruta.

Cuando se guarda el producto, se llama a este método del controlador:

Ya sé que el parámetro no se debería recuperar por la URL pero lo hago para mostrar la misma vista en caso de que haya errores de validación, y para que la URL no cambie (ya que al procesar la información se usa el método POST pero para mostrar el formulario se usa GET)

En fin, eso no debería tener mayor importancia. Se hace la validación y se comprueba que no se esté usando un código de barras ya existente, aunque se comprueba de igual forma que, si existe un producto con ese código de barras, no sea este mismo que estamos editando.

Eliminar producto

Para eliminar un producto simplemente se envía un formulario con el id dentro del mismo. El método del controlador es el siguiente:

Invocamos al método deleteById pasándole el id del producto obtenido al llamar getId. Después ponemos un mensaje de que la eliminación fue correcta y redirigimos a la lista de productos.

ProductosController dentro del sistema de ventas con Spring Boot

Entonces el controlador de productos queda así:

El controlador tiene todo para manejar este CRUD de productos con Spring

Interfaz para vender en el Sistema de ventas con Spring Boot

Ahora pasemos a la interfaz que se encarga de agregar productos a un carrito para la venta. Los productos los he almacenado en un ArrayList de tipo ProductoParaVender; ya veremos eso más adelante.

Vender - Sistema de ventas con Spring
Interfaz para vender

Primero veamos la nueva clase de ProductoParaVender, que extiende de Producto pero agrega otras propiedades como la cantidad (distinta a la existencia, pues se refiere a la cantidad que se vende):

No es una entidad, pues no será guardado en una base de datos, es simplemente un ayudante para la lista de compras.

Ahora veamos dos métodos que nos ayudan a guardar y a obtener el carrito. Son los siguientes y están dentro del controlador:

Se trata de un ArrayList como lo dije hace un momento, ambos se guardan en la sesión, por eso los métodos necesitan el parámetro de HttpServletRequest.

Ahora tenemos el método que muestra la vista, pasándole la variable del total del carrito:

Renderiza la vista llamada vender.html que es la siguiente:

Para mostrar la tabla con los productos lee session.carrito, pues las vistas de Thymeleaf tienen acceso a la sesión. Fíjate en que también estoy accediendo al iterador para obtener el índice y ponerlo en el formulario que quita un producto de la lista.

Agregar producto al carrito

Tengo un formulario (que es el único input) que agrega el producto a partir del código de barras; vamos a reutilizar el método que programamos anteriormente para obtener un producto por código de barras.

El controlador queda así:

Se ve un poco complejo, pero no lo es. Comenzamos obteniendo el producto por código de barras, después se valida que el producto exista por código de barras y que tenga existencia.

Más tarde busca dentro del ArrayList y si ya existe, simplemente le aumenta la cantidad. En caso de que no exista, agrega uno nuevo invocando al método add.

Quitar producto de la lista de venta

Es un simple formulario que tiene el índice que ocupa el producto dentro de la lista. El método del controlador que lo maneja es el siguiente:

Se obtiene el carrito y si existe y tiene elementos, se invoca al método remove del ArrayList, que elimina el elemento usando su índice.

Finalmente llamamos al método guardarCarrito y hacemos una redirección a vender.

Cancelar venta

Para cancelar la venta actual solo hay que limpiar el carrito de compras:

Para limpar o eliminar el carrito, guardamos un ArrayList vacío.

Terminar venta

Este método se encarga de terminar la venta, es decir, crear una nueva venta, restar las existencias de los productos vendidos y agregar los nuevos que se venden junto con la venta.

Queda así:

Los comentarios son explicativos. Todavía no hemos visto la clase ProductoVendido ni Venta, así como los repositorios, pero los veremos a continuación.

Así es como queda esta interfaz.

Productos vendidos en este POS

Los productos vendidos son una entidad distinta. No pueden tener una relación con los productos, ya que los mismos pueden cambiar de precio en el futuro y si esto cambia, se afectaría al historial de ventas.

En fin, la entidad (esta sí es una entidad pues va a una base de datos) queda así:

En la línea 12, 13 y 14 establecemos la relación que tiene con la entidad Venta, pues una venta tendrá productos vendidos.

Lleva casi las mismas propiedades que el producto normal. A esta entidad hay que agregarle su respectivo repositorio:

Eso es todo lo que se hace con esta entidad. Ahora veamos la última entidad.

Ventas dentro de sistema de ventas con spring

Historial de ventas con Spring
Listado de ventas con productos, además de la fecha y hora

Esto es fácil, pues no tenemos métodos manuales ni cosas por el estilo. Veamos la entidad de Venta:

Tiene la fecha y hora, un id y un conjunto de productos que son de la clase ProductoVendido. La fecha y hora se obtiene de la clase Utiles, la cual se obtiene y formatea como vimos en otro post.

También existe el método getTotal que recorre todo el conjunto de productos para obtener el total de venta.

Finalmente podemos fijarnos en la anotación @OneToMany de la línea 13, la cual relaciona la venta con los productos vendidos.

Debemos crear un repositorio de las ventas:

No hacemos nada más que definir la interface. Finalmente veamos el controlador:

Tiene un único método que muestra todas las ventas. No tenemos que preocuparnos por hacer relaciones manuales o algo así, pues al invocar a findAll la venta tendrá los productos que se vendieron.

Después de obtener las ventas las enviamos a la vista de ver_ventas.html que se encargan de renderizarlas y de dibujar las tablas:

La view es muy simple, se encarga de dibujar una tabla que tiene otra tabla dentro.

Descargar código del sistema de ventas Spring Boot y compilar

Si quieres puedes clonar el repositorio, he usado gradle para todo así que puedes usar incluso el bloc de notas para programar.

Lo que tienes que hacer es instalar gradle, MySQL y Java; es decir, todo lo necesario para programar con Spring Boot y MySQL.

Después ejecuta el programa con:

gradlew bootRun

Y crea el jar usando:

gradlew build

También puedes importar el proyecto usando IntelliJ IDEA.

Después ejecuta el jar con:

java -jar nombre-del-jar.jar

En ambos casos (ya sea que estés ejecutando el sistema para programar, o ejecutes el jar) visita http://localhost:8080/productos/mostrar.

No olvides que dejo el código fuente en GitHub.

Más información sobre Spring Boot y Gradle aquí.

Tu propio application.properties para mi sistema de ventas con Spring Boot

Si mi contraseña, usuario o configuración no son acorde a tus requisitos recuerda que siempre puedes crear un archivo llamado application.properties en el mismo directorio en donde está el jar, así, será tomado en cuenta ese archivo en lugar del mío.

Conclusión

Te invito a ver otros sistemas de ventas que he programado:

Sublime POS 3 – Este está listo para ser usado

Punto de venta con PHP

Punto de venta con CodeIgniter

Encantado de ayudarte


Estoy disponible para trabajar en tu proyecto, modificar el programa del post o realizar tu tarea pendiente, no dudes en ponerte en contacto conmigo.

No te pierdas ninguno de mis posts

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

21 comentarios en “Sistema de ventas con Spring MVC, MySQL y Bootstrap”

  1. Hola! me gusto el articulo y descargué el programa junto con lo demás, hasta vi el video de presentación para ver cómo podia compilarlo y hacer que corriera pero no me deja, y las explicaciones que se muestran no son claras. podrias por favor, colaborarme explicando paso por paso literalmente cómo compilarlo y ejecutarlo de manera clara. es que siguiendo los pasos me causa error y no compila ni puede ver la página web del programa. PSDA: soy nueva en la materia y no tengo experiencia, por eso mi problema al compilarlo. espero puedas ayudar!
    espero tu respuesta lo más pronto posible! por favor

    1. Hola. En el repositorio del software se encuentra la documentación de cómo compilar ya sea desde la terminal o con un IDE; es igual a todos los proyectos de Spring Boot. Igualmente si tiene dudas específicas puede contactarme en https://parzibyte.me
      Saludos 🙂

    1. Hola. Lo tiene que colocar en un servidor con Apache (solo apache, no Apache Tomcat) específicamente en la carpeta pública que en Linux es /var/www/html y en Windows, si usa XAMPP, es C:\xampp\htdocs

  2. Hola que tal, al querer correr el programa me arroja el siguiente error:
    “Error: no se ha encontrado o cargado la clase principal me.parzibyte.sistemaventasspringboot.Application” el proyecto lo descargue de
    “https://github.com/parzibyte/sistema-ventas-spring-boot” tendras idea porque puede dar este error?

  3. Estoy haciendo algo similar pero estoy trabado en poder modificar una factura existente, ya sea las cantidades o agregado de nuevos ítems. Tienes alguna sugerencia o ejemplo hecho?
    Gracias y excelente explicación?

    1. No tengo ejemplo, pero se me ocurre que una factura tendría una relación con una tabla de productos. El producto tendría cantidad. Podría crear un formulario dinámico por cada producto, y así tener una factura modificable.
      Saludos y gracias por sus comentarios 🙂

  4. Buenas! el proyecto esta genial, gracias en serio, estoy aprendiendo respecto al framework y como crear aplicaciones usando este mismo, sabes estoy teniendo el sgte error, creo que es porque no tengo la bd, nose si podrias pasarme un script de la db y asi ver si funciona. A no ser que el error sea otro..
    (ya le cambie el user y el pass del mySQL y aun así veo que me toma tu user)
    Este es el error: Caused by: java.sql.SQLException: Access denied for user ‘parzibyte’@’localhost’ (using password: YES)
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.16.jar!/:8.0.16]
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.16.jar!/:8.0.16]
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.16.jar!/:8.0.16]
    at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:835) ~[mysql-connector-java-8.0.16.jar!/:8.0.16]
    at com.mysql.cj.jdbc.ConnectionImpl.(ConnectionImpl.java:455) ~[mysql-connector-java-8.0.16.jar!/:8.0.16]
    at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:240) ~[mysql-connector-java-8.0.16.jar!/:8.0.16]
    at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:199) ~[mysql-connector-java-8.0.16.jar!/:8.0.16]
    at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:136) ~[HikariCP-3.2.0.jar!/:na]
    at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:369) ~[HikariCP-3.2.0.jar!/:na]
    at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:198) ~[HikariCP-3.2.0.jar!/:na]
    at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:467) ~[HikariCP-3.2.0.jar!/:na]
    at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:541) ~[HikariCP-3.2.0.jar!/:na]
    at com.zaxxer.hikari.pool.HikariPool.(HikariPool.java:115) ~[HikariCP-3.2.0.jar!/:na]
    at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:112) ~[HikariCP-3.2.0.jar!/:na]
    at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122) ~[hibernate-core-5.3.10.Final.jar!/:5.3.10.Final]
    at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess.obtainConnection(JdbcEnvironmentInitiator.java:180) ~[hibernate-core-5.3.10.Final.jar!/:5.3.10.Final]
    at org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl.getIsolatedConnection(DdlTransactionIsolatorNonJtaImpl.java:43) ~[hibernate-core-5.3.10.Final.jar!/:5.3.10.Final]
    … 43 common frames omitted

    1. Saludos. Me parece que puedes cambiar las credenciales en appliation.properties, ahí está definido el usuario y contraseña
      Si el contenido te agrada te invito a seguirme y compartir

Dejar un comentario