Saltar al contenido
Volver a proyectos

Proyecto de tercer curso · Universidad de Dublín · 2023—2024

Bynle

Una plataforma de venta de entradas para clubes y asociaciones universitarias —eventos, pagos, acceso por QR y una capa social— pensada para que una junta directiva venda la entrada directamente y un estudiante pueda cedérsela a un amigo con seguridad.

  • Django
  • React
  • PostgreSQL
  • Stripe Connect
  • QR ticketing
  • AWS
Periodo
2023 — 2024
Contexto
Proyecto de tercer curso, Grado en Ciencias de la Computación
Institución
Universidad de Dublín (DCU)
Despliegue
Railway, AWS Amplify y S3
134
Pruebas unitarias

Sobre los modelos y las vistas del backend, sin ningún cambio fusionado sin revisión.

10
Entidades del dominio

Usuarios, perfiles, clubes, eventos, entradas, cuentas de Stripe, seguimientos, amistades y solicitudes de transferencia.

3
Destinos de despliegue

Django y PostgreSQL en Railway, React en AWS Amplify y los archivos subidos en un bucket de S3.

2
Tipos de cuenta

Usuarios normales y escáneres de entradas, separados en el token y en el árbol de rutas.

01La idea

De revender entradas a gestionar el evento entero

El proyecto empezó en otro sitio. La idea original era una capa de transferencia segura montada sobre las plataformas de entradas existentes: una persona inicia la cesión, la otra paga dentro de la aplicación y la entrada solo cambia de manos cuando el pago se confirma; un remedio para las estafas de reventa que siguen a cualquier evento agotado.

Lo abandonamos. Trabajando contra las API de esas plataformas, no nos convencía ni la seguridad que ofrecían ni el nivel de verificación que realmente podíamos hacer. Si la verificación tenía que ser fiable, la entrada tenía que ser nuestra.

Así que construimos el sistema de entradas, dirigido al ámbito que mejor conocíamos: los clubes y asociaciones universitarias. Un club crea un evento, vende las entradas directamente y las escanea en la puerta. Y como ahora éramos dueños de la entrada de principio a fin, la transferencia segura que había originado todo pasó a ser algo que sí podíamos garantizar.

El último giro fue social. Una vez que los estudiantes descubrían eventos en la plataforma, tenía poco sentido que el descubrimiento fuera un listado. Seguir clubes, conectar con amigos y ver a qué va la gente convirtió una herramienta de entradas en algo más parecido a un tablón de anuncios del campus.

02El producto

Tres tipos de persona, una plataforma

En Bynle hay tres tipos de persona usando la plataforma, y la interfaz cambia de forma para cada uno.

Estudiantes
Exploran eventos, siguen clubes, envían y aceptan solicitudes de amistad, compran entradas y ceden a un amigo la entrada que no van a usar.
Administradores de club
Crean y gestionan eventos, fijan precios, conectan una cuenta de Stripe, editan la página del club, nombran a otros administradores, crean cuentas de escáner y consultan las estadísticas del club.
Escáneres de entradas
Un tipo de cuenta aparte, creada por un administrador de club, con una única función: iniciar sesión en un móvil en la puerta y escanear códigos QR.
  • Eventos. Los clubes publican eventos con fecha, hora, ubicación, aforo, tipo e imagen de portada, gratuitos o de pago.
  • Entradas. Cada entrada lleva un código único y un código QR generado, ligados a un evento y a un propietario.
  • Transferencias. Una entrada se puede ceder a un amigo, con el pago gestionado dentro de la aplicación cuando no era gratuita.
  • Grafo social. Solicitudes de amistad con estado pendiente, seguimiento de clubes y las conexiones que dos usuarios tienen en común.
  • Estadísticas del club. Un administrador puede ver qué carreras y qué cursos siguen al club, agregados en el backend y representados en gráficas.
  • Archivos. Logos, portadas de club y carteles de evento subidos por los administradores y servidos desde almacenamiento en la nube, no desde el servidor de la aplicación.
Explorando eventos. La barra lateral se adapta a la cuenta: un administrador de club también ve, bajo su propia navegación, los clubes que administra.
Explorando eventos. La barra lateral se adapta a la cuenta: un administrador de club también ve, bajo su propia navegación, los clubes que administra.
Cesión de una entrada a un amigo, según el manual de usuario. Una entrada de pago no puede enviarse hasta que quien la cede tenga una cuenta de Stripe capaz de recibir el importe.
Cesión de una entrada a un amigo, según el manual de usuario. Una entrada de pago no puede enviarse hasta que quien la cede tenga una cuenta de Stripe capaz de recibir el importe.

03Arquitectura

Cinco capas, tres servidores

El sistema se divide en un cliente React, una capa de aplicación en Django, una capa de datos en PostgreSQL, una capa de integración para pagos y archivos, y una capa de despliegue que coloca cada pieza donde corresponde.

El backend es una API REST. Las vistas de Django se agrupan en manejadores por dominio —autenticación, clubes, estadísticas, eventos, amistades, escáneres, pagos, entradas, transferencias y datos de usuario— con serializadores que validan todo lo que entra y convierten las instancias de los modelos a JSON al salir.

Funciona repartido en tres servicios. La aplicación Django y un contenedor de PostgreSQL conviven en Railway, que descarga la última versión de una rama fija en cada push y ejecuta un script de arranque que migra, recopila los estáticos y lanza Gunicorn. El frontend en React se aloja en AWS Amplify. Los archivos subidos —logos, portadas y carteles— viven en un bucket de S3 y no en el servidor de la aplicación.

Cliente
React, con React Router protegiendo las rutas privadas y un árbol de rutas aparte para las cuentas de escáner.
Aplicación
Vistas REST de Django agrupadas en diez manejadores de dominio, con serializadores para la validación y la conversión a JSON.
Datos
PostgreSQL para los datos estructurados, ejecutado desde una imagen de Docker; un bucket de S3 para los archivos subidos.
Integración
Stripe para los pagos y el alta en Connect, con una comprobación de estado que decide si un club o un usuario puede cobrar.
Despliegue
Railway para el backend y la base de datos, AWS Amplify para el frontend y control de versiones con Git para ambos.
El modelo operacional del manual técnico: dónde se ejecuta cada componente y qué habla con qué.
El modelo operacional del manual técnico: dónde se ejecuta cada componente y qué habla con qué.
El modelo de datos. Diez entidades, con las tablas de unión —Follow, Friend y TransferRequest— cargando estado propio en lugar de ser meros enlaces.
El modelo de datos. Diez entidades, con las tablas de unión —Follow, Friend y TransferRequest— cargando estado propio en lugar de ser meros enlaces.

04Entradas

Vender una entrada es fácil; moverla con seguridad no

Una entrada solo vale algo si exactamente una persona puede usarla, exactamente una vez. Esa restricción condiciona las tres partes más difíciles del sistema: cómo llega el dinero al club, cómo cambia de manos una entrada y qué ocurre en la puerta.

La transferencia es la parte que hicimos mal primero. Al principio, una entrada en mitad de una cesión seguía siendo una entrada válida, así que podía escanearse mientras la transferencia estaba en curso y alguien podía entrar a un evento con una entrada que estaba regalando. La solución fue darle a las entradas un estado de transferencia propio.

  • Stripe Connect. Los clubes cobran a través de su propia cuenta de Stripe Connect, no de la nuestra. El sistema comprueba si esa cuenta está completa y no permite cobrar por un evento hasta que lo esté.
  • Estado de transferencia. Una entrada en proceso de cesión queda en estado de transferencia. Escanea como inválida y no pertenece a ninguna de las dos partes hasta que el destinatario acepta o el remitente cancela.
  • Cambio de propietario. Al aceptar, la entrada antigua se elimina y se crea una nueva para el destinatario, de modo que una entrada cedida es un registro nuevo y no uno editado.
  • Cesiones de pago. Cuando la entrada costó dinero, el destinatario paga dentro de la aplicación y la transferencia solo se cierra una vez procesado el pago.
  • QR en la puerta. Cada entrada lleva un código QR generado que apunta a un endpoint de validación al que solo pueden acceder las cuentas de tipo escáner.
  • Cuentas de escáner. Los administradores crean cuentas de escáner para un evento. Tienen credenciales propias, su propio árbol de rutas y no pueden hacer nada más que escanear.
Cesión de una entrada de pago, según el manual técnico. La rama de pago solo se ejecuta cuando el precio es mayor que cero, y la propiedad cambia después de confirmarse el pago.
Cesión de una entrada de pago, según el manual técnico. La rama de pago solo se ejecuta cuando el precio es mayor que cero, y la propiedad cambia después de confirmarse el pago.
Una entrada escaneada en la puerta. El endpoint devuelve el nombre y el número de estudiante del asistente, y registra cuándo se escaneó la entrada.
Una entrada escaneada en la puerta. El endpoint devuelve el nombre y el número de estudiante del asistente, y registra cuándo se escaneó la entrada.

05Calidad

Qué probamos y qué se rompió

Las pruebas fueron por tres vías: revisión de código en cada merge request, pruebas unitarias del backend y sesiones de prueba con estudiantes reales.

El backend terminó con 134 pruebas unitarias entre modelos y vistas: integridad de datos y reglas de validación por un lado, gestión de peticiones y lógica de negocio por otro, incluidas comprobaciones de permisos como confirmar que solo una cuenta de escáner puede acceder al endpoint de validación de entradas. Ningún cambio llegó a la rama principal sin que lo revisara el otro.

Las pruebas con usuarios cambiaron el producto. Dos peticiones se repitieron con claridad suficiente como para construirlas:

  • Una campana de notificaciones. Los usuarios querían llegar a las solicitudes de amistad y a las transferencias entrantes desde cualquier punto, y no navegando hasta la página que las contenía. Añadimos una campana en la barra superior, con un contador.
  • Borrar una cesión enviada. Los participantes querían retirar una cesión enviada por error o tras un cambio de planes. Las transferencias enviadas pasaron a poder eliminarse.
La batería del backend: 134 pruebas, todas superadas.
La batería del backend: 134 pruebas, todas superadas.

Qué se rompió

  1. 01

    Los administradores de club, modelados como clase propia

    Problema

    Los administradores de club empezaron siendo una clase de modelo dedicada. Era la forma equivocada: la relación entre usuarios y clubes es de muchos a muchos, y forzarla a través de una clase propia hacía incómoda la administración.

    Solución

    Se refactorizó a una relación de muchos a muchos, que permitió que varios usuarios administraran varios clubes sin ceremonias y simplificó todo el proceso.

  2. 02

    Dos personas, migraciones borradas y una base de datos rota

    Problema

    Al principio trabajábamos en ramas separadas y ambos generábamos migraciones al cambiar los modelos. Ninguno entendía las migraciones de Django lo suficiente, y los dos habíamos borrado algunas. Al hacer rebase para fusionar con main, la base de datos se rompió: ya no coincidía con los modelos, y tardamos mucho en descubrir por qué.

    Solución

    Acordamos no volver a borrar una migración salvo con la base de datos respaldada y un borrado completo realmente justificado. Un script que repoblaba la base de datos desde cero nos permitió reconstruir un conjunto de datos funcional mientras lo resolvíamos.

  3. 03

    Las entradas seguían siendo válidas durante la cesión

    Problema

    Mientras una transferencia estaba en curso, la entrada seguía activa, con el riesgo de un acceso no autorizado con una entrada que ya se estaba cediendo.

    Solución

    Las entradas mantienen un estado de transferencia hasta que el destinatario acepta o el remitente cancela. En ese estado no pueden escanearse y cuentan como inválidas en la puerta.

  4. 04

    Desplegar tres piezas a la vez

    Problema

    Poner en producción el backend de Django, el frontend de React y la base de datos a la vez costó días de documentación y una larga racha de intentos fallidos en el servidor del backend.

    Solución

    Railway, que se integra con el repositorio y descarga la última versión de una rama fija, levantó el backend y la base de datos en una instancia compartida; el frontend se fue a AWS Amplify.

Tecnologías

Construido con

Frontend

  • React
  • React Router
  • JavaScript
  • Axios

Backend

  • Django
  • Python
  • Serializadores
  • JWT
  • Gunicorn

Datos

  • PostgreSQL
  • Docker
  • AWS S3

Plataforma

  • Railway
  • AWS Amplify
  • Stripe Connect
  • Pexels API
  • GitLab CI

Créditos

Equipo y documentación

Realizado como proyecto de tercer curso entre dos personas para el Grado en Ciencias de la Computación de la Universidad de Dublín: la misma pareja que el Cortafuegos de SMS para 5G un año después.

Junto a
Jack Keenan
Institución
Universidad de Dublín (DCU), 2023—2024
Pagos
Stripe Connect
Documentación
Manual técnico y manual de usuario, publicados con el código.
Volver a proyectos