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