Software y sistemas

Sistema web de pagos y cooperaciones open source con PHP

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.

Registro de pagos de personas

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:

Comprobante de pago

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.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

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.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Ver comentarios

  • 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 , 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 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 :)

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

1 semana hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

2 semanas hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

2 semanas hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

2 semanas hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

2 semanas hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

2 semanas hace

Esta web usa cookies.