Sistema de ventas en PHP, MySQL y Bootstrap

En este post te mostraré un pequeño sistema de ventas. Está desarrollado empleando las tecnologías que dice en el título. Además te dejaré los enlaces para que puedas descargarlo y ver una demostración. También te presentaré algunos bloques del código usado.

Es un sistema bastante sencillo, permite administrar productos, usuarios, clientes y obviamente las ventas.

Recuerda que debes de tener instalado PHP y MySQL. Por mi parte uso xampp.

La base de datos

El esquema de la base de datos lo encuentras en el archivo bd.sql

CREATE DATABASE ventas_php;

USE ventas_php;

CREATE TABLE productos(
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    codigo VARCHAR(255) NOT NULL,
    nombre VARCHAR(255) NOT NULL,
    compra DECIMAL(8,2) NOT NULL,
    venta DECIMAL(8,2) NOT NULL,
    existencia INT NOT NULL
);

CREATE TABLE clientes(
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(255) NOT NULL,
    telefono VARCHAR(25) NOT NULL,
    direccion VARCHAR(255) NOT NULL
);

CREATE TABLE usuarios(
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    usuario VARCHAR(50) NOT NULL,
    nombre VARCHAR(255) NOT NULL,
    telefono VARCHAR(25) NOT NULL,
    direccion VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL
);

INSERT INTO usuarios (usuario, nombre, telefono, direccion, password) VALUES ("paco", "PacoHunterDev", "6667771234", "Nowhere", "$2y$10$6zeiv5cq4/HCjWBH5X/Fd.yxKfDaWa5sJaYfW302n./awI/lQcH0i");

CREATE TABLE ventas(
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    fecha DATETIME NOT NULL,
    total DECIMAL(9,2) NOT NULL,
    idUsuario BIGINT NOT NULL,
    idCliente BIGINT
);  

CREATE TABLE productos_ventas(
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    cantidad INT NOT NULL,
    precio DECIMAL(8,2) NOT NULL,
    idProducto BIGINT NOT NULL,
    idVenta BIGINT NOT NULL
);

Ese es el esquema de la base de datos, al crearla se inserta un usuario y contraseña por defecto, en este caso es paco y la PacoHunterDev respectivamente. Para crearla solo copia el esquema en la consola de mysql.

CRUD de productos, clientes y usuarios

Para todas las partes que forman el sistema, el código es prácticamente el mismo. Solo cambian pequeñas cosas como son el nombre de los campos. Pero en esencia es lo mismo. A continuación te presento el HTML y las funciones de php para registrar los productos, como te menciono para las demás partes es similar.

Formulario para registrar producto

El archivo agregar_producto.php contiene el formulario para colocar los datos del producto. En él mismo procesamos los datos para insertarlo en la base de datos.

<?php
include_once "encabezado.php";
include_once "navbar.php";
session_start();

if(empty($_SESSION['usuario'])) header("location: login.php");

?>
<div class="container">
    <h3>Agregar producto</h3>
    <form method="post">
        <div class="mb-3">
            <label for="codigo" class="form-label">Código de barras</label>
            <input type="text" name="codigo" class="form-control" id="codigo" placeholder="Escribe el código de barras del producto">
        </div>
        <div class="mb-3">
            <label for="nombre" class="form-label">Nombre o descripción</label>
            <input type="text" name="nombre" class="form-control" id="nombre" placeholder="Ej. Papas">
        </div>
        <div class="row">
            <div class="col">
                <label for="compra" class="form-label">Precio compra</label>
                <input type="number" name="compra" step="any" id="compra" class="form-control" placeholder="Precio de compra" aria-label="">
            </div>
            <div class="col">
                <label for="venta" class="form-label">Precio venta</label>
                <input type="number" name="venta" step="any" id="venta" class="form-control" placeholder="Precio de venta" aria-label="">
            </div>
            <div class="col">
                <label for="existencia" class="form-label">Existencia</label>
                <input type="number" name="existencia" step="any" id="existencia" class="form-control" placeholder="Existencia" aria-label="">
            </div>
            
        </div>
        <div class="text-center mt-3">
            <input type="submit" name="registrar" value="Registrar" class="btn btn-primary btn-lg">
            
            </input>
            <a class="btn btn-danger btn-lg" href="productos.php">
                <i class="fa fa-times"></i> 
                Cancelar
            </a>
        </div>
    </form>
</div>
<?php
if(isset($_POST['registrar'])){
    $codigo = $_POST['codigo'];
    $nombre = $_POST['nombre'];
    $compra = $_POST['compra'];
    $venta = $_POST['venta'];
    $existencia = $_POST['existencia'];
    if(empty($codigo) 
    || empty($nombre) 
    || empty($compra) 
    || empty($venta)
    || empty($existencia)){
        echo'
        <div class="alert alert-danger mt-3" role="alert">
            Debes completar todos los datos.
        </div>';
        return;
    } 
    
    include_once "funciones.php";
    $resultado = registrarProducto($codigo, $nombre, $compra, $venta, $existencia);
    if($resultado){
        echo'
        <div class="alert alert-success mt-3" role="alert">
            Producto registrado con éxito.
        </div>';
    }
    
}
?>

Primero se incluyen los archivos de encabezado.php (que es donde colocamos los encabezados para los archivos de css y esas cosas) y navbar.php (el menú). Al final del archivo incluimos el archivo de funciones.php, en este archivo se encuentran todas las funciones que interactúan con la base de datos. En este caso empleamos la función de registrarProducto() que recibe el código, nombre, precio de compra, precio de venta y la existencia del producto. La función queda así:

function registrarProducto($codigo, $nombre, $compra, $venta, $existencia){
    $sentencia = "INSERT INTO productos(codigo, nombre, compra, venta, existencia) VALUES (?,?,?,?,?)";
    $parametros = [$codigo, $nombre, $compra, $venta, $existencia];
    return insertar($sentencia, $parametros);
}

Dentro de las funciones del CRUD usamos otras funciones para editar, eliminar, insertar y seleccionar los datos. Estas funciones reciben la sentencia a ejecutar y los parámetros que se deben ejecutar, son las siguientes:

function select($sentencia, $parametros = []){
    $bd = conectarBaseDatos();
    $respuesta = $bd->prepare($sentencia);
    $respuesta->execute($parametros);
    return $respuesta->fetchAll();
}

function insertar($sentencia, $parametros ){
    $bd = conectarBaseDatos();
    $respuesta = $bd->prepare($sentencia);
    return $respuesta->execute($parametros);
}

function eliminar($sentencia, $id ){
    $bd = conectarBaseDatos();
    $respuesta = $bd->prepare($sentencia);
    return $respuesta->execute([$id]);
}

function editar($sentencia, $parametros ){
    $bd = conectarBaseDatos();
    $respuesta = $bd->prepare($sentencia);
    return $respuesta->execute($parametros);
}

Y la conexión a la base de datos es la siguiente:

function conectarBaseDatos() {
	$host = "localhost";
	$db   = "ventas_php";
	$user = "root";
	$pass = "";
	$charset = 'utf8mb4';

	$options = [
	    \PDO::ATTR_ERRMODE            => \PDO::ERRMODE_EXCEPTION,
	    \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_OBJ,
	    \PDO::ATTR_EMULATE_PREPARES   => false,
	];
	$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
	try {
	     $pdo = new \PDO($dsn, $user, $pass, $options);
	     return $pdo;
	} catch (\PDOException $e) {
	     throw new \PDOException($e->getMessage(), (int)$e->getCode());
	}
}

Mostrar productos (select)

La tabla y las cartas para mostrar los productos se encuentran en el archivo de productos.php. Las partes de usuarios y clientes es lo mismo.

Las cartas que se muestran al inicio también se emplean en el dashboard, se importa el archivo cartas_totales.php.

<?php
include_once "encabezado.php";
?>
	<div class="card-deck row">
		<?php foreach($cartas as $carta){?>
		<div class="col-xs-12 col-sm-6 col-md-3" style="color: <?=  $carta['color']?> !important">
			<div class="card text-center">
				<div class="card-body">
					<h4 class="card-title" >
						<i class="fa <?= $carta['icono']?>"></i>
						<?= $carta['titulo']?>
					</h4>
					<h2><?= $carta['total']?></h2>

				</div>

			</div>
		</div>
		<?php }?>
	</div>

Y se crear un arreglo colocando el nombre de la carta, el icono, el total que se va a mostrar y el color, algo así:

$cartas = [
    ["titulo" => "No. Productos", "icono" => "fa fa-box", "total" => count($productos), "color" => "#3578FE"],
    ["titulo" => "Total productos", "icono" => "fa fa-shopping-cart", "total" => obtenerNumeroProductos(), "color" => "#4F7DAF"],
    ["titulo" => "Total inventario", "icono" => "fa fa-money-bill", "total" => "$". obtenerTotalInventario(), "color" => "#1FB824"],
    ["titulo" => "Ganancia", "icono" => "fa fa-wallet", "total" => "$". calcularGananciaProductos(), "color" => "#D55929"],
];

La tabla para los productos luce así:

<table class="table">
        <thead>
            <tr>
                <th>Código</th>
                <th>Nombre</th>
                <th>Precio compra</th>
                <th>Precio venta</th>
                <th>Ganancia</th>
                <th>Existencia</th>
                <th>Editar</th>
                <th>Eliminar</th>
            </tr>
        </thead>
        <tbody>
            <?php
            foreach($productos as $producto){
            ?>
                <tr>
                    <td><?= $producto->codigo; ?></td>
                    <td><?= $producto->nombre; ?></td>
                    <td><?= '$'.$producto->compra; ?></td>
                    <td><?= '$'.$producto->venta; ?></td>
                    <td><?= '$'. floatval($producto->venta - $producto->compra); ?></td>
                    <td><?= $producto->existencia; ?></td>
                    <td>
                        <a class="btn btn-info" href="editar_producto.php?id=<?= $producto->id; ?>">
                            <i class="fa fa-edit"></i>
                            Editar
                        </a>
                    </td>
                    <td>
                        <a class="btn btn-danger" href="eliminar_producto.php?id=<?= $producto->id; ?>">
                            <i class="fa fa-trash"></i>
                            Eliminar
                        </a>
                    </td>
                </tr>
            <?php } ?>
        </tbody>
    </table>

Y la función para obtener los productos es la siguiente:

function obtenerProductos($busqueda = null){
    $parametros = [];
    $sentencia = "SELECT * FROM productos ";
    if(isset($busqueda)){
        $sentencia .= " WHERE nombre LIKE ? OR codigo LIKE ?";
        array_push($parametros, "%".$busqueda."%", "%".$busqueda."%"); 
    } 
    return select($sentencia, $parametros);
}

Editar producto

En la tabla de productos, se redirecciona en el botón de editar, este lleva al formulario en donde se colocarán los nuevos datos del producto.

<td>
  <a class="btn btn-info" href="editar_producto.php?id=<?= $producto->id; ?>">
     <i class="fa fa-edit"></i>
       Editar
  </a>
</td>

Y la función para editar la función es:

function editarProducto($codigo, $nombre, $compra, $venta, $existencia, $id){
    $sentencia = "UPDATE productos SET codigo = ?, nombre = ?, compra = ?, venta = ?, existencia = ? WHERE id = ?";
    $parametros = [$codigo, $nombre, $compra, $venta, $existencia, $id];
    return editar($sentencia, $parametros);
}

Eliminar producto

Para eliminar un producto es prácticamente lo mismo, en la tabla de productos al presionar sobre el botón, redirecciona al archivo de eliminar_producto.php.

<?php
$id = $_GET['id'];
if (!$id) {
    echo 'No se ha seleccionado el producto';
    exit;
}
include_once "funciones.php";

$resultado = eliminarProducto($id);
if(!resultado){
    echo "Error al eliminar";
    return;
}

header("Location: productos.php");
?>

La función es la siguiente:

function eliminarProducto($id){
    $sentencia = "DELETE FROM productos WHERE id = ?";
    return eliminar($sentencia, $id);
}

Capturas del sistema

Inicio de sesión

El inicio de sesión solo pide el nombre de usuario y la contraseña, si los datos son correctos redirecciona a la página principal.

Realizar venta

Para realizar la venta, se escribe el código de barras del producto que se va a vender. Los productos de la lista se pueden eliminar. Puedes seleccionar el cliente que desees. Si no se selecciona la venta se marca como mostrador.

Reporte de ventas

En el reporte de ventas, al inicio, solo se muestran las ventas realizadas en el día, pero se pueden filtrar por fecha, cliente o usuario.

En esta parte se emplean las cartas explicadas en la parte de arriba.

Descargar código

Si deseas usar y probar este sistema puedes descargarlo del siguiente enlace: https://github.com/pacohunterdev/ventas-php además si deseas ver la demostración puedes verla en el siguiente vídeo https://www.youtube.com/watch?v=X-3vPQnA_2M&t=43s.