Hoy vengo a presentar un sistema gratuito y open source para el control de pagos y cooperaciones creado con PHP, MySQL, Twig, Bootstrap y Vue
Este sistema de pagos está creado con PHP para el lenguaje del servidor, utiliza Twig para renderizar las plantillas, mismas que tienen un diseño usando Bootstrap. La persistencia de datos es gracias a MySQL.
Del lado del cliente se utiliza Vue para consumir la API y traer los datos.
Entre sus características encontramos:
- Software para el control de pagos de personas
- Registro de personas
- Posibilidad de registrar usuarios para iniciar sesión
- Impresión de comprobantes de pago en impresora térmica
- Reporte de totales abonados por personas
- Responsivo y accesible desde teléfono, tableta o computadora
Con algunas modificaciones, este software podría controlar el sistema de abonos de ventas de productos, cooperaciones de personas, pagos, etcétera.
Puede servir para estudiar un proyecto de PHP o para usarlo directamente en producción (de hecho yo lo estoy usando)
A continuación voy a explicar cómo funciona, además de mostrar el código fuente y otras cosas más.
Código fuente y descargas
El código fuente está en GitHub, eres libre de clonarlo y modificarlo.
Si quieres puedes descargarlo; comienza instalando XAMPP en Windows o LAMP en Linux y más tarde instala las dependencias instalando Composer en Windows o Linux y ejecutando:
composer install
En caso de que no uses composer, existe un release que ya tiene todas las dependencias. Simplemente debes descargarlo y extraerlo en la carpeta pública.
Personas y pagos
Básicamente el software registra personas y pagos. Una persona puede tener múltiples pagos.
El pago se puede editar y eliminar; registra la fecha, el monto y la persona.
Además, se genera un hash único que en teoría sirve para que los tickets no se puedan falsificar, pues aunque alguien pueda imprimir un ticket, necesitaría calcular un hash, ponerlo en nuestro servidor y ponerlo en el ticket.
Comprobantes de pagos
Los comprobantes son una forma de comprobar que las personas han hecho los pagos.
Un comprobante lleva la fecha, el nombre de la persona, el hash que mencioné anteriormente y algunos totales.
Lleva el total de lo que han cooperado todos, y el total que ha dado la persona:
Este comprobante puede ser impreso las veces que sea necesario.
Usuarios
El sistema tiene usuarios con cosas básicas. Permite el registro de los mismos:
Y el cambio de contraseña:
Las contraseñas son hasheadas con bcrypt.
El index y las rutas
Todo se encuentra en el archivo index.php; que define las rutas de las vistas y las rutas de la API.
He usado Phroute pues es ligero y cumple su función. Dentro del archivo de rutas encontramos un filtro que simplemente verifica que el usuario esté logueado.
El manejador de sesiones es uno propio con MySQL.
<?php
require __DIR__ . '/vendor/autoload.php'; #Cargar todas las dependencias
use Parzibyte\Servicios\Comun;
use Parzibyte\Servicios\SesionService;
use Parzibyte\Servicios\Twig;
use Phroute\Phroute\Dispatcher;
use Phroute\Phroute\Exception\HttpMethodNotAllowedException;
use Phroute\Phroute\Exception\HttpRouteNotFoundException;
use Phroute\Phroute\RouteCollector;
define("DIRECTORIO_RAIZ", __DIR__);
define("DIRECTORIO_APLICACION", DIRECTORIO_RAIZ . "/app");
define("RUTA_LOGS", __DIR__ . DIRECTORY_SEPARATOR . "logs");
define("URL_RAIZ", Comun::env("URL_RAIZ"));
define("URL_DIRECTORIO_PUBLICO", URL_RAIZ . "/public");
define("RUTA_API", URL_RAIZ . "/api");
define("NOMBRE_APLICACION", "Sistema de pagos");
define("AUTOR", "Luis Cabrera Benito a.k.a <a href='https://parzibyte.me'>parzibyte</a>");
ini_set('display_errors', 0);
ini_set("log_errors", 1);
ini_set("error_log", __DIR__ . "/logs/" . date("Y-m-d") . ".log");
if (!file_exists(RUTA_LOGS)) {
mkdir(RUTA_LOGS);
}
function view($nombre)
{
echo Twig::obtener()->render("$nombre.twig");
return;
}
function json($datos)
{
header("Content-type: application/json");
echo json_encode($datos);
return;
}
function redirect($ruta)
{
header("Location: " . URL_RAIZ . $ruta);
exit;
}
$enrutador = new RouteCollector();
$enrutador->filter("logueado", function () {
if (empty(SesionService::leer("correoUsuario"))) {
return redirect("/login");
}
});
$enrutador
->group(["before" => "logueado"], function ($enrutadorVistasPrivadas) {
$enrutadorVistasPrivadas
->get("/ajustes", ["Parzibyte\Controladores\ControladorAjustes", "index"])
->get("/usuarios", ["Parzibyte\Controladores\ControladorUsuarios", "index"])
->get("/personas", ["Parzibyte\Controladores\ControladorPersonas", "index"])
->get("/pagos", ["Parzibyte\Controladores\ControladorPagos", "index"])
->get("/logout", ["Parzibyte\Controladores\ControladorLogin", "logout"]);
});
$enrutador->get("/login", ["Parzibyte\Controladores\ControladorLogin", "index"]);
$enrutador
->group(
["prefix" => "api"],
function ($enrutadorApi) {
$enrutadorApi
->put("/usuario/login", ["Parzibyte\Controladores\ControladorLogin", "login"]);
$enrutadorApi->group(
["before" => "logueado"],
function ($enrutadorApiLogueado) {
$enrutadorApiLogueado
->get("/ajustes", ["Parzibyte\Controladores\ControladorAjustes", "obtenerTodos"])
->post("/ajustes", ["Parzibyte\Controladores\ControladorAjustes", "guardarMuchos"])
->get("/usuarios", ["Parzibyte\Controladores\ControladorUsuarios", "obtener"])
->post("/usuario", ["Parzibyte\Controladores\ControladorUsuarios", "agregar"])
->put("/usuario", ["Parzibyte\Controladores\ControladorUsuarios", "actualizarPalabraSecreta"])
->delete("/usuario/{idUsuario}", ["Parzibyte\Controladores\ControladorUsuarios", "eliminar"])
->get("/personas", ["Parzibyte\Controladores\ControladorPersonas", "obtener"])
->post("/persona", ["Parzibyte\Controladores\ControladorPersonas", "agregar"])
->put("/persona", ["Parzibyte\Controladores\ControladorPersonas", "actualizar"])
->delete("/persona/{idPersona}", ["Parzibyte\Controladores\ControladorPersonas", "eliminar"])
->get("/pagos", ["Parzibyte\Controladores\ControladorPagos", "obtener"])
->post("/pago", ["Parzibyte\Controladores\ControladorPagos", "agregar"])
->put("/pago", ["Parzibyte\Controladores\ControladorPagos", "actualizar"])
->delete("/pago/{idPago}", ["Parzibyte\Controladores\ControladorPagos", "eliminar"])
->get("/ticket/{idPago}", ["Parzibyte\Controladores\ControladorTickets", "imprimirTicketDePago"]);
});
});
$despachador = new Dispatcher($enrutador->getData());
$rutaCompleta = $_SERVER["REQUEST_URI"];
$metodo = $_SERVER['REQUEST_METHOD'];
$rutaLimpia = parsearUrl($rutaCompleta);
try {
$despachador->dispatch($metodo, $rutaLimpia);
} catch (HttpRouteNotFoundException $e) {
echo "Error: Ruta [ $rutaLimpia ] no encontrada";
} catch (HttpMethodNotAllowedException $e) {
echo "Error: Ruta [ $rutaLimpia ] encontrada pero método [ $metodo ] no permitido";
}
function parsearUrl($uri)
{
return implode('/',
array_slice(
explode('/', $uri), Comun::env("OFFSET_RUTAS", 2)));
}
Las vistas
Como lo dije, las vistas son archivos de Twig. No tienen mucho que hacer, pues el renderizado de los datos se hace con Vue.
La impresión del ticket
Utilizo la misma librería que presenté hace algún tiempo. El controlador del ticket se ve así:
<?php
namespace Parzibyte\Controladores;
use Exception;
use Mike42\Escpos\PrintConnectors\WindowsPrintConnector;
use Mike42\Escpos\Printer;
use Parzibyte\Modelos\ModeloPagos;
use Parzibyte\Servicios\Comun;
class ControladorTickets
{
public static function imprimirTicketDePago($idPago)
{
try {
$nombreImpresora = Comun::env("NOMBRE_IMPRESORA");
} catch (Exception $e) {
return false;
}
$connector = new WindowsPrintConnector($nombreImpresora);
$printer = new Printer($connector);
$pago = ModeloPagos::obtenerPorId($idPago);
if (!$pago) {
return false;
}
$repeticiones = 20;
$printer->setTextSize(2, 2);
$printer->text("Comprobante");
$printer->feed();
$printer->setTextSize(1, 1);
$printer->text(str_repeat("=", $repeticiones));
$printer->feed();
$printer->text("Nombre: " . $pago->persona);
$printer->feed();
$printer->text("Monto: $" . number_format($pago->monto, 2, ".", ","));
$printer->feed();
$printer->text("Fecha: " . $pago->fecha);
$printer->feed();
$printer->text("Hash: " . $pago->hash);
$printer->feed();
$printer->text(str_repeat("=", $repeticiones));
$printer->feed();
$printer->feed();
$printer->text("Total abonado por ti: $" . number_format(ModeloPagos::totalDePersona($pago->idPersona), 2, ".", ","));
$printer->feed();
$printer->text("Total abonado por todos: $" . number_format(ModeloPagos::total(), 2, ".", ","));
$printer->feed(2);
$printer->text("(Calculados en la fecha de impreso este ticket)");
$printer->feed(5);
$printer->cut();
$printer->pulse();
$printer->close();
return true;
}
}
Para deshabilitar la impresora simplemente hay que evitar poner la línea que indica su nombre.
El archivo env y htaccess
Las credenciales y algunos ajustes están dentro del archivo env.php; solo hay que tomar de ejemplo el env.ejemplo.php
En este archivo también se puede deshabilitar la creación, eliminación y edición de usuarios; útil por ejemplo en ambientes de prueba o demostración en donde los usuarios no deben modificar, valga la redundancia, a los usuarios.
Utilizo el archivo .htaccess para rescribir las URLs y hacer que las mismas se vean más limpias.
Instalación
Si tienes dudas de cómo instalarlo, por favor mira la guía de instalación.
Conclusión
Como lo dije, a partir de este sistema se pueden crear otros; o tomarlo de ejemplo para crear uno mejor.
La plantilla ya está creada y solo hay que añadir más código; se utiliza un paradigma similar a MVC y es como tener una caja de herramientas en lugar de un framework complejo.
Si te gusta este sistema, probablemente también te guste uno de cotizaciones.
buenas tardes, al momento de abrir me aparece este mensaje: Error fatal : Clase ‘Parzibyte\Servicios\Comun’ no encontrada en C:\xampp\htdocs\app\index.php en la línea 15
que no estoy haciendo
Hola. Tal vez no está siguiendo bien el tutorial o no ha generado el autoload
hola , estaba probando tu proyecto en github y me figura este error Error: Ruta [ ] no encontrada , llevo verificandolo todo el día y hasta ahora no puedo encontrar el error.
Quizá puedas darme una mano , gracias
Hola. Claro, si desea ayuda personalizada envíeme un mensaje en https://parzibyte.me/#contacto
Tendría algun costo por solucionar ese problema ?
Al ser un trabajo que requiere mi tiempo, sí
Hola buen dia modifique el archivo env.php y me da el mismo error Error: Ruta [ ] no ssssncontrada no importa que ingrese. Desde ya gracias por el esfuerzo y compartir.
Hola, buen día. Por favor verifique la BASE_URL y el OFFSET. Si quiere puede enviarme su archivo env por privado para que lo revise
Saludos
Hola amigo buenas tardes, felicitaciones por tu sitio y ejemplos que tienes en tu blog, estuve revisando y se ve interesante yo baje los ejemplos pero imposible ponerlo a funcionar yo soy nuevo en este mundo de php y todo eso, el caso es que yo solo tengo xampp y mysql no se usar composer nada de eso, cuando lo ejecuto en mi localhost me sale este mensaje.
Error: Ruta [ ] no ssssncontrada
No se cual es el motivo yo solo modifique según lo que tu dices en el ejemplo el archivo
env.php
Gracias y nuevamente mis felicitaciones
Hola, buen día. Debe modificar en el archivo env.php basándose en el ejemplo provisto. Los comentarios indican qué hacer.
En este caso supongo que no ha configurado la ruta local en donde está el programa, o que no ha calculado el offset de las rutas.
Saludos 🙂