Trabajo Práctico · Full-Stack

Sistema de Gestión
de Socios

Plataforma web para administrar socios, cuotas, pagos y reportes del Burzaco Sportclub — construida con arquitectura hexagonal, desplegada en la nube.

2
servicios (API + Frontend)
5
módulos funcionales
100%
contenedorizado
CI/CD
deploy automático

scrolleá para recorrer el proyecto
El problema

Gestionar un club a mano no escala

El Burzaco Sportclub administraba socios, cuotas y deudas con planillas dispersas. Sin un sistema central, era difícil saber quién pagó, quién debe, y generar reportes confiables para la toma de decisiones.

Datos dispersos

Información de socios repartida en planillas sin una fuente única de verdad.

Cuotas sin control

Difícil rastrear pagos, deudas y períodos por socio.

Sin métricas

Imposible ver recaudación, morosidad o crecimiento de un vistazo.

La solución

Una plataforma web, centralizada y en la nube

Un backend REST que concentra toda la lógica de negocio y una base única, con un panel de administración limpio para operar el día a día del club.

Backend — API REST

Flask + arquitectura hexagonal. Concentra reglas de negocio, autenticación y persistencia. Corre en el puerto 5001.

Frontend — Panel admin

Server-side rendering con Jinja2. Login, gestión de socios, registro de pagos y reportes. Puerto 5000.

Arquitectura

Hexagonal — Puertos y Adaptadores

El dominio no depende de nada externo. La lógica de negocio vive aislada; bases de datos y frameworks son detalles intercambiables conectados por interfaces.

Frontend (ClubAdministracion)
Jinja2 + FlaskVistas y panel admin
DataServiceCliente del backend (dual-mode)
↓ HTTP / JSON · Authorization: Bearer ↓
Backend — Capas
InfrastructureRutas REST, repos SQLite/Supabase
ApplicationServicios + Puertos (ABC)
DomainSocio, Usuario, Pago + reglas
↓ Puerto: SocioRepository (abstracto) ↓
Persistencia (adaptadores intercambiables)
SQLiteDesarrollo local
Supabase / PostgreSQLProducción

El dominio (centro) no sabe si los datos vienen de SQLite o Supabase. Eso se decide afuera y se inyecta — el corazón del patrón.

Tecnologías

Stack

Backend

Python 3.14Flask 3.0PyJWTbcrypt

Datos

SQLiteSupabasePostgreSQL

Frontend

Jinja2HTML/CSSJavaScript

Contenedores

DockerDocker Compose

Nube

GCP Cloud RunArtifact Registry

CI/CD

GitHub ActionsDeploy automático
Funcionalidades

Qué hace el sistema

01

Gestión de socios

Alta, baja, edición y búsqueda con filtros (actividad, categoría, estado, cuota). Paginación incluida.

02

Autenticación con JWT

Login por DNI y contraseña. El backend firma un token JWT (válido 8 h); el frontend lo guarda y lo manda en cada llamada protegida. Contraseñas hasheadas con bcrypt.

03

Registro de pagos

Registrar cuotas por socio (monto, concepto, período), ver historial completo y resumen: total pagado, montos y períodos cubiertos.

04

Reportes

Tarjetas de total de socios, morosos y recaudación; distribución por actividad y cobranzas por período.

05

Sincronización en vivo

Botón "Sincronizar" que actualiza las estadísticas con datos reales del backend en un click.

Seguridad

Cómo funciona el login

1. Ingreso

El operador entra DNI + contraseña en el panel.

2. Verificación

El backend busca el usuario y compara la contraseña con bcrypt contra el hash guardado.

3. Token

Si es válido, firma un JWT (HS256) con DNI, nombre y rol. Expira en 8 horas.

4. Sesión

El frontend guarda el token y lo manda como Authorization: Bearer en cada pedido protegido.

Endpoint

# POST /api/auth/login
{
  "dni": "00000000",
  "password": "••••••••"
}
# 200 OK
{ "token": "<JWT>" }
bcrypt (work factor 12) JWT HS256 SECRET_KEY por entorno expiración 8 h
Diseño orientado a objetos

POO y SOLID en acción

No usamos clases por usar: cada pilar resuelve un problema concreto de mantenibilidad.

Encapsulamiento

La entidad Socio guarda su estado y sus reglas juntas: pagar_cuota() valida antes de cambiar el estado.

Herencia

Las excepciones heredan de Exception; los repositorios concretos heredan del puerto abstracto.

Abstracción

Puertos como SocioRepository (ABC) definen el contrato sin implementarlo.

Polimorfismo + Inyección

Un mismo servicio recibe SQLite o Supabase indistintamente. No sabe ni le importa cuál es.

El patrón Repository

# application/ports.py — la abstracción
class SocioRepository(ABC):
    @abstractmethod
    def get_by_dni(self, dni): ...

# app.py — polimorfismo + inyección
if supabase_url and supabase_key:
    repo = SupabaseSocioRepository(...)
else:
    repo = SQLiteSocioRepository(...)

service = SocioService(repo)  # DIP (la D de SOLID)

Separamos el QUÉ del CÓMO: el servicio trabaja contra la abstracción, no contra la base de datos concreta.

DevOps

Del commit a producción, solo

Cada push a main dispara un pipeline que prueba, construye la imagen y la despliega — sin pasos manuales.

1 · Push a main

Un cambio entra al repositorio.

2 · CI — Pruebas

GitHub Actions corre los tests y verifica que la app arranca antes de seguir.

3 · Build

Se construye la imagen Docker del backend y del frontend.

4 · Deploy

Las imágenes se publican y se despliegan en GCP Cloud Run. Escala solo según la demanda.

docker compose up --build GitHub Actions Cloud Run (us-central1) Variables de entorno seguras