Sistema de Ventas-PHP
Sistema de Ventas-PHP
Sistema de Ventas-PHP
1 Introducción
2 Antes de comenzar
3 Actualización
3.1.1 Vista previa
3.1.2 Online
3.1.3 Descargar
3.3 Base de datos
3.4 Productos
3.4.1 Nuevo
3.4.2 Listar productos
3.4.3 Editar producto
3.4.4 Guardar cambios
3.4.5 Eliminar producto
3.5 Vender
3.5.4 Terminar venta
3.5.5 Cancelar venta
3.6 Ventas
3.6.1 Listar
3.6.2 Eliminar
3.7 Encabezado y pie
3.8 Conclusión
Hace algunos días hice un ejercicio de un sistema de ventas en PHP. Está
escrito en puro PHP, nada de Javascript. Eso sí, para los estilos utilicé una
variante de Bootstrap.
Antes de comenzar
Te invito a probar Sublime POS 3, un punto de venta evolutivo, gratuito y que
funciona en la nube.
Actualización
Ya he hecho la versión 2 de este sistema. No cambian muchas cosas en
cuanto a su uso, sino a su programación. Lo hice con CodeIgniter usando el
patrón MVC. Ver nueva versión aquí.
Online
Para darte una pequeña idea, puedes visitar este link para probar el sistema.
De igual forma míralo en YouTube:
Descargar
Si gustas descargarlo y probarlo en tu entorno local, aquí abajo lo dejo:
https://github.com/parzibyte/ventas_pdo/archive/master.zip
DROP
DATABASE
IF
EXISTS
ventas;
CREATE DATABASE IF NOT EXISTS ventas;
USE ventas;
CREATE TABLE productos(
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
codigo VARCHAR(255) NOT NULL,
descripcion VARCHAR(255) NOT NULL,
precioVenta DECIMAL(5, 2) NOT NULL,
precioCompra DECIMAL(5, 2) NOT NULL,
existencia DECIMAL(5, 2) NOT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8;
<?php
$contraseña = "1d3ed423r43crt34";
$usuario = "root";
$nombre_base_de_datos = "ventas";
try{
$base_de_datos = new PDO('mysql:host=localhost;dbname=' . $nombre_base_de_datos, $usuario, $contrase
$base_de_datos->query("set names utf8;");
$base_de_datos->setAttribute(PDO::ATTR_EMULATE_PREPARES, FALSE);
$base_de_datos->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$base_de_datos->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
}catch(Exception $e){
echo "Ocurrió algo con la base de datos: " . $e->getMessage();
}
?>
Productos. Nuevo
<?php
include_once
"encabezado.php
" ?>
<div class="col-xs-12">
<h1>Nuevo producto</h1>
<form method="post" action="nuevo.php">
<label for="codigo">Código de barras:</label>
<input class="form-control" name="codigo" required type="text" id="codigo" placeho
<label for="descripcion">Descripción:</label>
<textarea required id="descripcion" name="descripcion" cols="30" rows="5" class="f
<label for="existencia">Existencia:</label>
<input class="form-control" name="existencia" required type="number" id="existenci
existencia">
<br><br><input class="btn btn-info" type="submit" value="Guardar">
</form>
</div>
<?php include_once "pie.php" ?>
include_once "base_de_datos.php";
$codigo = $_POST["codigo"];
$descripcion = $_POST["descripcion"];
$precioVenta = $_POST["precioVenta"];
$precioCompra = $_POST["precioCompra"];
$existencia = $_POST["existencia"];
else echo "Algo salió mal. Por favor verifica que la tabla exista";
?>
<?php include_once "pie.php" ?>
<?php
include_once
"encabezado.php
" ?>
<?php
include_once "base_de_datos.php";
$sentencia = $base_de_datos->query("SELECT * FROM productos;");
$productos = $sentencia->fetchAll(PDO::FETCH_OBJ);
?>
<div class="col-xs-12">
<h1>Productos</h1>
<div>
<a class="btn btn-success" href="./formulario.php">Nuevo <i class="fa fa-pl
</div>
<br>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Código</th>
<th>Descripción</th>
<th>Precio de compra</th>
<th>Precio de venta</th>
<th>Existencia</th>
<th>Editar</th>
<th>Eliminar</th>
</tr>
</thead>
<tbody>
<?php foreach($productos as $producto){ ?>
<tr>
<td><?php echo $producto->id ?></td>
<td><?php echo $producto->codigo ?></td>
<td><?php echo $producto->descripcion ?></td>
<td><?php echo $producto->precioCompra ?></td>
<td><?php echo $producto->precioVenta ?></td>
<td><?php echo $producto->existencia ?></td>
<td><a class="btn btn-warning" href="<?php echo "editar.php?
edit"></i></a></td>
<td><a class="btn btn-danger" href="<?php echo "eliminar.php
trash"></i></a></td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
<?php include_once "pie.php" ?>
Editar producto
Cuando hacemos click en el botón editar, nos redirige a otro archivo. En él,
leemos el id utilizando $_GET. Luego, hacemos una consulta a la base de
datos en donde el id sea el que leímos.
Si el producto no existe, lo indicamos. En caso de que sí, entonces dibujamos
el mismo formulario pero ahora lo llenamos con el atributo value de la
etiqueta input. Para el textarea es diferente, pues el contenido debe ir entre las
etiquetas, no como atributo.
<?php
if(!isset($_GET["id"])) exit();
$id = $_GET["id"];
include_once "base_de_datos.php";
$sentencia = $base_de_datos->prepare("SELECT * FROM productos WHERE id = ?;");
$sentencia->execute([$id]);
$producto = $sentencia->fetch(PDO::FETCH_OBJ);
if($producto === FALSE){
echo "¡No existe algún producto con ese ID!";
exit();
}
?>
<?php include_once "encabezado.php" ?>
<div class="col-xs-12">
<h1>Editar producto con el ID <?php echo $producto->id; ?></h1>
<form method="post" action="guardarDatosEditados.php">
<input type="hidden" name="id" value="<?php echo $producto->id; ?>">
<label for="descripcion">Descripción:</label>
<textarea required id="descripcion" name="descripcion" cols="30" rows="5" class="form
<label for="existencia">Existencia:</label>
<input value="<?php echo $producto->existencia ?>" class="form-control" name="existen
<br><br><input class="btn btn-info" type="submit" value="Guardar">
<a class="btn btn-warning" href="./listar.php">Cancelar</a>
</form>
</div>
<?php include_once "pie.php" ?>
Notar por favor que para saber el id que estamos editando (con el que
haremos el where) lo estoy guardando en un input oculto.
Guardar cambios
Arriba vimos el formulario para editar. Ahora veamos el archivo en donde
realmente guardamos los cambios. Es este:
<?php
#Salir si alguno de los datos no está presente
if(
!isset($_POST["codigo"]) ||
!isset($_POST["descripcion"]) ||
!isset($_POST["precioCompra"]) ||
!isset($_POST["precioVenta"]) ||
!isset($_POST["existencia"]) ||
!isset($_POST["id"])
) exit();
include_once "base_de_datos.php";
$id = $_POST["id"];
$codigo = $_POST["codigo"];
$descripcion = $_POST["descripcion"];
$precioCompra = $_POST["precioCompra"];
$precioVenta = $_POST["precioVenta"];
$existencia = $_POST["existencia"];
Eliminar producto
Finalmente veamos el de eliminar. Notar por favor que no pide confirmación,
así que hay que hacerlo con cuidado.
<?php
if(!isset($_GET["id"])) exit();
$id = $_GET["id"];
include_once "base_de_datos.php";
$sentencia = $base_de_datos->prepare("DELETE FROM productos WHERE id = ?;");
$resultado = $sentencia->execute([$id]);
if($resultado === TRUE){
header("Location: ./listar.php");
exit;
}
else echo "Algo salió mal";
?>
Vender
Esta fue la parte que más me gustó. Sólo se trabaja con arreglos y sesiones,
pero me agradó el resultado.
include_once "encabezado.php";
session_start();
if(!isset($_SESSION["carrito"])) $_SESSION["carrito"] = [];
$granTotal = 0;
?>
<div class="col-xs-12">
<h1>Vender</h1>
<?php
if(isset($_GET["status"])){
if($_GET["status"] === "1"){
?>
<div class="alert alert-success">
<strong>¡Correcto!</strong> Venta realizada correctament
</div>
<?php
}else if($_GET["status"] === "2"){
?>
<div class="alert alert-info">
<strong>Venta cancelada</strong>
</div>
<?php
}else if($_GET["status"] === "3"){
?>
<div class="alert alert-info">
<strong>Ok</strong> Producto quitado de la lista
</div>
<?php
}else if($_GET["status"] === "4"){
?>
<div class="alert alert-warning">
<strong>Error:</strong> El producto que buscas no existe
</div>
<?php
}else if($_GET["status"] === "5"){
?>
<div class="alert alert-danger">
<strong>Error: </strong>El producto está agotado
</div>
<?php
}else{
?>
<div class="alert alert-danger">
<strong>Error:</strong> Algo salió mal mientras se reali
</div>
<?php
}
}
?>
<br>
<form method="post" action="agregarAlCarrito.php">
<label for="codigo">Código de barras:</label>
<input autocomplete="off" autofocus class="form-control" name="codigo" required type=
</form>
<br><br>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Código</th>
<th>Descripción</th>
<th>Precio de venta</th>
<th>Cantidad</th>
<th>Total</th>
<th>Quitar</th>
</tr>
</thead>
<tbody>
<?php foreach($_SESSION["carrito"] as $indice => $producto){
$granTotal += $producto->total;
?>
<tr>
<td><?php echo $producto->id ?></td>
<td><?php echo $producto->codigo ?></td>
<td><?php echo $producto->descripcion ?></td>
<td><?php echo $producto->precioVenta ?></td>
<td><?php echo $producto->cantidad ?></td>
<td><?php echo $producto->total ?></td>
<td><a class="btn btn-danger" href="<?php echo "quitarDelCarrito.php?in
</tr>
<?php } ?>
</tbody>
</table>
Muestra una tabla, que será de los productos que componen la venta.
También muestra el total, que al inicio es 0. Y hasta abajo 2 botones que son
para terminar de vender o para cancelar la venta. Igualmente hay unos ifs que
muestran una alerta como “Producto inexistente” o cosas de esas.
Notemos que tiene un input, eso es para leer el código de barras. En realidad
es un input dentro de un formulario. Dicho formulario, al enviarse, se va al
archivo que veremos a continuación.
Finalmente, si vemos el encabezado, notaremos que declara el índice carrito
en el arreglo superglobal de $_SESSION. Ahí es en donde colocaremos los
productos
<?php
if(!isset($_POST["codigo"])) return;
$codigo = $_POST["codigo"];
include_once "base_de_datos.php";
$sentencia = $base_de_datos->prepare("SELECT * FROM productos WHERE codigo = ? LIMIT 1;");
$sentencia->execute([$codigo]);
$producto = $sentencia->fetch(PDO::FETCH_OBJ);
if($producto){
if($producto->existencia < 1){
header("Location: ./vender.php?status=5");
exit;
}
session_start();
$indice = false;
for ($i=0; $i < count($_SESSION["carrito"]); $i++) {
if($_SESSION["carrito"][$i]->codigo === $codigo){
$indice = $i;
break;
}
}
if($indice === FALSE){
$producto->cantidad = 1;
$producto->total = $producto->precioVenta;
array_push($_SESSION["carrito"], $producto);
}else{
$_SESSION["carrito"][$indice]->cantidad++;
$_SESSION["carrito"][$indice]->total = $_SESSION["carrito"][$indice]->cantidad * $_SESSION[
}
header("Location: ./vender.php");
}else header("Location: ./vender.php?status=4");
?>
Pero suponiendo que todo va bien y que el producto existe, iniciamos sesión y
recorremos el arreglo carrito para ver si ya habíamos agregado ese producto
antes. En caso de que lo hayamos agregado, entonces cambiamos solamente
la cantidad.
if(!isset($_GET["indice"])) return;
$indice = $_GET["indice"];
session_start();
array_splice($_SESSION["carrito"], $indice, 1);
header("Location: ./vender.php?status=3");
?>
Ah, y regresamos a vender.php con el status 3 que creo que dice que fue
quitado correctamente.
Terminar venta
Es un simple archivo que insertará en la base de datos los productos
vendidos, así como la venta, su fecha y su total. Queda así:
<?php
if(!isset($_POST["total"])) exit;
session_start();
$total = $_POST["total"];
include_once "base_de_datos.php";
<?php
session_start();
unset($_SESSION["carrito"]);
$_SESSION["carrito"] = [];
header("Location: ./vender.php?status=2");
?>
Ventas
Para terminar este grandioso tutorial veamos el registro de ventas. Se
compone de dos cosas: listar ventas y poder eliminarlas.
Listar
Por favor no me culpen, pero no sé cómo (y si alguien lo sabe, que me
explique) hacer una consulta que traiga dentro un arreglo. Lo que pasa es que
quería algo para mostrar los productos vendidos por venta. Lo único que se
me ocurrió fue un group_concat. En fin, el código queda así:
<?php
include_once "base_de_datos.php";
$sentencia = $base_de_datos->query("SELECT ventas.total, ventas.fecha, v
productos_vendidos.cantidad SEPARATOR '__') AS productos FROM ventas INN
productos.id = productos_vendidos.id_producto GROUP BY ventas.id ORDER B
$ventas = $sentencia->fetchAll(PDO::FETCH_OBJ);
?>
<div class="col-xs-12">
<h1>Ventas</h1>
<div>
<a class="btn btn-success" href="./vender.php">Nue
</div>
<br>
<table class="table table-bordered">
<thead>
<tr>
<th>Número</th>
<th>Fecha</th>
<th>Productos vendidos</th>
<th>Total</th>
<th>Eliminar</th>
</tr>
</thead>
<tbody>
<?php foreach($ventas as $venta){ ?>
<tr>
<td><?php echo $venta->id ?></td>
<td><?php echo $venta->fecha ?></td>
<td>
<table class="table table-
bordered">
<thead>
<tr>
<th>Código</t
<th>Descripci
<th>Cantidad<
</tr>
</thead>
<tbody>
<?php foreach(explode
$producto = explode("
?>
<tr>
<td><?php ech
<td><?php ech
<td><?php ech
</tr>
<?php } ?>
</tbody>
</table>
</td>
<td><?php echo
$venta->total ?
></td>
<td><a class="btn btn-danger" href="<?php echo "eliminarVent
</tr>
<?php } ?>
</tbody>
</table>
</div>
<?php include_once "pie.php" ?>
<?php
if(!isset($_GET["id"])) exit();
$id = $_GET["id"];
include_once "base_de_datos.php";
$sentencia = $base_de_datos->prepare("DELETE FROM ventas WHERE id = ?;");
$resultado = $sentencia->execute([$id]);
if($resultado === TRUE){
header("Location: ./ventas.php");
exit;
}
else echo "Algo salió mal";
?>
Encabezado y pie
Como lo prometí, aquí el encabezado y pie. Sólo definen el contendor, el menú
de navegación, algunas etiquetas meta y cargan las librerías css.
Encabezado es:
<!DOCTYPE
html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Ventas</title>
</div>
</div>
</body>
</html>
Conclusión
Como lo dije, no tiene estructura. Fue hecho rápidamente, pero funciona y eso
es lo que cuenta. Cuando pienses que algo funciona pero se ve mal o parece
un truco, recuerda que la humanidad hizo que una roca pensara (al inventar
los microprocesadores).
Por cierto, si quieres ver algo parecido a este sistema pero en Laravel, mira
este post.