This commit is contained in:
Laucha1312
2026-06-04 15:24:29 -03:00
parent aa9bc585fd
commit 70d4217109
12 changed files with 16921 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[compose.yaml]
indent_size = 4
+75
View File
@@ -0,0 +1,75 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
# PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
# Destinatario de notificaciones del paquete de backups (Spatie).
# Si se deja vacío, se usa MAIL_FROM_ADDRESS. Dejar en false las notificaciones
# de backup exitoso para no saturar la bandeja ni al proveedor SMTP.
BACKUP_NOTIFICATION_EMAIL=
BACKUP_NOTIFY_SUCCESS=false
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"
# OnAPB Genius Agent
GEMINI_API_KEY=
PRISM_REQUEST_TIMEOUT=60
+1
View File
@@ -0,0 +1 @@
{"version":2,"defects":{"Tests\\Feature\\AuthTest::test_admin_login_success":8,"Tests\\Feature\\AuthTest::test_admin_login_failure":8,"Tests\\Feature\\AuthTest::test_jugador_login_success":8,"Tests\\Feature\\AuthTest::test_aficionado_login_success":8,"Tests\\Feature\\AuthTest::test_logout_clears_session":8,"Tests\\Feature\\AdminTest::test_guest_cannot_access_admin_dashboard":8,"Tests\\Feature\\AdminTest::test_player_cannot_access_admin_dashboard":8,"Tests\\Feature\\AdminTest::test_superadmin_can_access_admin_dashboard":8,"Tests\\Feature\\AdminTest::test_superadmin_can_create_a_club":8,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":7,"Tests\\Feature\\EventoQRTest::test_aficionado_puede_solicitar_qr":7,"Tests\\Feature\\EventoQRTest::test_aficionado_no_puede_solicitar_dos_qrs_para_mismo_evento":7,"Tests\\Feature\\JugadorCreationTest::test_duplicate_dni_shows_club_name_error":8,"Tests\\Feature\\JugadorCreationTest::test_club_admin_can_see_all_clubs_in_origen":8,"Tests\\Feature\\CleanupTest::test_cleanup_only_removes_qrs_but_keeps_event":8,"Tests\\Feature\\PaseTest::test_superadmin_can_approve_a_transfer":8,"Tests\\Feature\\SoftDeleteTest::test_deletion_uses_soft_deletes_on_critical_models":8,"Tests\\Feature\\EventoQRTest::test_jugador_puede_solicitar_qr":7,"Tests\\Feature\\EventoQRTest::test_jugador_no_puede_solicitar_dos_veces_qrs_para_mismo_evento":8,"Tests\\Feature\\TFICorrectionsTest::test_documentation_is_accessible":7,"Tests\\Unit\\AI\\Tools\\ListarEquiposToolTest::test_retorna_todos_los_equipos_sin_filtro":8,"Tests\\Unit\\AI\\Tools\\ListarEquiposToolTest::test_filtra_por_torneo_y_grupo":8},"times":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.414,"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.007,"Tests\\Feature\\AdminTest::test_guest_cannot_access_admin_dashboard":0.103,"Tests\\Feature\\AdminTest::test_player_cannot_access_admin_dashboard":0.005,"Tests\\Feature\\AdminTest::test_superadmin_can_access_admin_dashboard":0.183,"Tests\\Feature\\AdminTest::test_superadmin_can_create_a_club":0.062,"Tests\\Feature\\AuthTest::test_admin_login_success":0.319,"Tests\\Feature\\AuthTest::test_admin_login_failure":0.031,"Tests\\Feature\\AuthTest::test_jugador_login_success":0.037,"Tests\\Feature\\AuthTest::test_aficionado_login_success":0.036,"Tests\\Feature\\AuthTest::test_logout_clears_session":0.005,"Tests\\Feature\\EventoQRTest::test_aficionado_puede_solicitar_qr":0.193,"Tests\\Feature\\EventoQRTest::test_aficionado_no_puede_solicitar_dos_qrs_para_mismo_evento":0.039,"Tests\\Feature\\JugadorCreationTest::test_duplicate_dni_shows_club_name_error":0.098,"Tests\\Feature\\JugadorCreationTest::test_club_admin_can_see_all_clubs_in_origen":0.053,"Tests\\Feature\\CleanupTest::test_cleanup_only_removes_qrs_but_keeps_event":0.204,"Tests\\Feature\\PaseTest::test_superadmin_can_approve_a_transfer":0.595,"Tests\\Feature\\SoftDeleteTest::test_deletion_uses_soft_deletes_on_critical_models":0.525,"Tests\\Feature\\EventoQRTest::test_jugador_puede_solicitar_qr":0.204,"Tests\\Feature\\EventoQRTest::test_jugador_no_puede_solicitar_dos_veces_qrs_para_mismo_evento":0.218,"Tests\\Feature\\TFICorrectionsTest::test_clubes_are_ordered_descending_by_id":0.297,"Tests\\Feature\\TFICorrectionsTest::test_admin_layout_contains_form_validation_script":0.015,"Tests\\Feature\\TFICorrectionsTest::test_qr_pdf_download_route_is_accessible":0.208,"Tests\\Feature\\TFICorrectionsTest::test_documentation_is_accessible":0.142,"Tests\\Unit\\AI\\Tools\\ListarEquiposToolTest::test_retorna_todos_los_equipos_sin_filtro":0.069,"Tests\\Unit\\AI\\Tools\\ListarEquiposToolTest::test_filtra_por_torneo_y_grupo":0.005}}
+241
View File
@@ -0,0 +1,241 @@
# 🏀 OnAPB — Sistema de Gestión de Asociación de Básquet
**OnAPB** es una plataforma web integral para la gestión digital de asociaciones de básquet. Digitaliza la administración de clubes, equipos, jugadores, torneos y el acceso a eventos mediante códigos QR, reemplazando procesos que antes eran 100% manuales o en papel.
> **Sitio en Producción:** [onapb.com](https://onapb.com)
---
## 🛠️ Stack Tecnológico
| Capa | Tecnología |
|---|---|
| **Backend Framework** | Laravel 12.x (PHP 8.2+) |
| **ORM** | Eloquent ORM |
| **Base de Datos** | MySQL / MariaDB |
| **Frontend** | Bootstrap 5 + CSS Vainilla personalizado |
| **Build Tool** | Vite + NPM |
| **Gestión de Paquetes** | Composer |
| **Anti-Bot** | Cloudflare Turnstile |
| **Correo** | Laravel Mailer (SMTP) |
| **QR Generation** | `simplesoftwareio/simple-qrcode` |
| **Backups** | `spatie/laravel-backup` |
| **Testing** | PHPUnit 11 (Feature Tests) |
---
## 🏛️ Arquitectura del Sistema
El sistema sigue el patrón **MVC (Modelo-Vista-Controlador)** provisto por Laravel, con las siguientes capas:
```
┌───────────────────────────────────────────────────────────────┐
│ CLIENTE │
│ Browser (Bootstrap 5 + JavaScript) │
└────────────────────────────┬──────────────────────────────────┘
│ HTTP / HTTPS
┌────────────────────────────▼──────────────────────────────────┐
│ RUTAS (web.php) │
│ Rutas públicas / Panel usuario / Panel admin │
└────────────────────────────┬──────────────────────────────────┘
┌────────────────────────────▼──────────────────────────────────┐
│ CONTROLADORES │
│ AdminController · PanelController · AuthController │
│ TorneoController · FixtureController · PaseController · ... │
└─────────┬───────────────────────────────────────┬─────────────┘
│ │
┌─────────▼─────────┐ ┌───────────▼─────────────┐
│ MODELOS │ │ SERVICIOS │
│ Club / Equipo │ │ NotificacionService │
│ Jugador / Evento │ │ FixtureService │
│ QrCode / Torneo │◄────────────────│ TournamentService │
│ Promocion / ... │ Eloquent ORM └─────────────────────────┘
└─────────┬─────────┘
┌─────────▼─────────┐
│ BASE DE DATOS │
│ MySQL / MariaDB │
└───────────────────┘
```
### Capas Adicionales
- **Observers** — Lógica de negocio automatizada ante eventos de modelos.
- **Console Commands** — Tareas programadas: `CleanupOldEvents`, `RecordatorioPartidos`, `ReporteSemanal`.
- **Mails** — Notificaciones por correo: bienvenida, QRs generados, reset de contraseña.
---
## 🗄️ Esquema de Base de Datos (Entidades Principales)
```
clubes ──────────────────────────────────────────┐
│ id_club, nombre, imagen, qr_background │
│ │
├──► equipos │
│ id_equipo, id_club, categoria, division │
│ │ │
│ └──► jugador_equipo ◄──── jugadores ┘
│ id_jugador, id_equipo id_jugador, documento,
│ fecha_alta nombre, apellido,
│ fecha_nacimiento,
│ id_club_actual, activo
└──► eventos
id_evento (UUID), nombre_evento,
fecha_evento, hora_inicio, hora_fin,
id_equipo_local, id_equipo_visitante,
marcador_local, marcador_visitante,
id_torneo, limite_qr_jugador
└──► qr_codes
id_qr, id_evento,
id_jugador | id_aficionado,
tipo_qr, escaneos_restantes
torneos
id_torneo, nombre, año
└──► torneo_equipo (grupo, puntos, ...)
└──► eventos (fase del torneo)
promociones ──► promo_qrs
aficionados
admin_users (role: 1=SuperAdmin, 2=Admin Club)
```
---
## 👥 Roles del Sistema
El sistema implementa **RBAC (Control de Acceso Basado en Roles)** con 4 perfiles:
| Rol | Descripción | Acceso |
|---|---|---|
| **Visitante** | Sin cuenta | Solo páginas públicas |
| **Jugador** | Deportista federado | Panel usuario (acceso federado) |
| **Aficionado** | Hincha registrado | Panel usuario (acceso general) |
| **Admin de Club** | Gestor de una institución | Panel admin (scope limitado a su club) |
| **Súper Admin** | Personal de OnAPB | Panel admin (acceso total) |
---
## 🌐 Funcionalidades por Rol
### Visitante (sin cuenta)
- Navegar la cartelera de próximos eventos
- Ver el detalle de cada partido (equipos, fecha, sede)
- Ver la sección de Noticias
- Ver Promociones y locales con beneficios
- Ver Posiciones y Tabla de Goleadores de torneos
### Jugador / Aficionado (panel usuario)
- Solicitar y ver sus Códigos QR de acceso a partidos
- Generar QR de beneficio para promociones
- Editar datos de contacto y cambiar contraseña
- Recibir notificaciones internas del sistema
- Seguir equipos favoritos
### Admin de Club (panel admin — scope de su club)
- Ver y gestionar jugadores del club
- Ver y gestionar equipos del club
- Editar logo e identidad visual del club (QR Background)
- Gestionar pases (traspasos) de jugadores
- Escanear y validar QRs en eventos de su club
### Súper Admin (panel admin — acceso total)
- **Todo lo del Admin de Club**, más:
- ABM completo de Clubes, Equipos y Jugadores (globalmente)
- ABM de Eventos / Partidos (calendarización)
- ABM de Torneos + generación de fixtures + bracket de playoffs
- Gestión del carrusel/hero de la página de inicio
- Gestión de Sponsors
- ABM de Noticias y Promociones
- Configuración general del sistema
- Gestión de usuarios administradores
- Importación/Exportación masiva de jugadores (CSV multiformato)
---
## ✅ Suite de Pruebas
El proyecto cuenta con **Feature Tests** ejecutables con PHPUnit:
```bash
php artisan test
```
| Archivo | Casos de Prueba |
|---|---|
| `AuthTest.php` | Login admin · Login jugador · Login aficionado · Cierre de sesión |
| `AdminTest.php` | Acceso denegado a visitantes/jugadores · Acceso SuperAdmin · Creación de club |
| `EventoQRTest.php` | Solicitud de QR por aficionado · Prevención de QR duplicado |
| `JugadorCreationTest.php` | Error por DNI duplicado con nombre de club · Vista de admin de club |
| `CleanupTest.php` | Limpieza de eventos viejos |
---
## 💾 Recuperación ante Fallos
- **SoftDeletes** en modelos críticos: `Club`, `Equipo`, `Jugador`, `Evento` — los registros eliminados no se borran físicamente de la base de datos, son recuperables.
- **Backups automáticos** con `spatie/laravel-backup` — respalda la base de datos y los archivos de almacenamiento en forma programada.
```bash
# Ejecutar backup manual
php artisan backup:run --only-db
```
---
## 🤖 Tareas Automáticas (Scheduler)
| Comando | Descripción | Frecuencia |
|---|---|---|
| `reportes:semanal` | Genera y envía reporte de actividad a admins | Semanal |
| `recordatorio:partidos` | Envía recordatorio de próximos partidos | Diaria |
| `cleanup:old-events` | Limpia QRs de eventos finalizados | Configurable |
---
## ⚙️ Instalación Local
```bash
# 1. Clonar el repositorio
git clone <repo-url>
# 2. Instalar dependencias
composer install
npm install && npm run build
# 3. Configurar entorno
cp .env.example .env
php artisan key:generate
# 4. Configurar .env (DB_DATABASE, DB_USERNAME, DB_PASSWORD, MAIL_*, etc.)
# 5. Migrar y poblar la base de datos
php artisan migrate:fresh --seed
# 6. Enlazar carpeta de almacenamiento
php artisan storage:link
# 7. Iniciar servidor
php artisan serve
```
---
## 🧪 Variables de Entorno Relevantes
| Variable | Descripción |
|---|---|
| `DB_DATABASE` | Nombre de la base de datos |
| `MAIL_HOST` / `MAIL_PORT` | Servidor SMTP para envío de correos |
| `TURNSTILE_SITE_KEY` | Clave pública de Cloudflare Turnstile (anti-bot) |
| `TURNSTILE_SECRET_KEY` | Clave privada de Cloudflare Turnstile |
| `APP_URL` | URL base de la aplicación |
---
*Proyecto desarrollado como Software Integrador para la materia Taller de Integración — FCYT UADER, Oro Verde.*
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env php
<?php
use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true));
// Register the Composer autoloader...
require __DIR__.'/vendor/autoload.php';
// Bootstrap Laravel and handle the command...
/** @var Application $app */
$app = require_once __DIR__.'/bootstrap/app.php';
$status = $app->handleCommand(new ArgvInput);
exit($status);
+91
View File
@@ -0,0 +1,91 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.2",
"barryvdh/laravel-dompdf": "^3.1",
"erusev/parsedown": "*",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1",
"minishlink/web-push": "^10.0",
"prism-php/prism": "^0.100.1",
"spatie/laravel-backup": "^10.2"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/pail": "^1.2.2",
"laravel/pint": "^1.24",
"laravel/sail": "^1.41",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpunit/phpunit": "^11.5.50"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"setup": [
"composer install",
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
"npm install",
"npm run build"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
],
"test": [
"@php artisan config:clear --ansi",
"@php artisan test"
],
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"pre-package-uninstall": [
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}
Generated
+9741
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
{
"$schema": "https://opencode.ai/config.json",
"tools": {
"terminal": true
}
}
+6657
View File
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
{
"$schema": "https://www.schemastore.org/package.json",
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite"
},
"devDependencies": {
"@tailwindcss/vite": "^4.0.0",
"axios": "^1.11.0",
"concurrently": "^9.0.1",
"laravel-vite-plugin": "^2.0.0",
"shadcn": "^4.0.7",
"tailwindcss": "^4.0.0",
"vite": "^7.0.7"
}
}
+37
View File
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
>
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory>app</directory>
</include>
</source>
<php>
<env name="APP_ENV" value="testing"/>
<env name="APP_MAINTENANCE_DRIVER" value="file"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="BROADCAST_CONNECTION" value="null"/>
<env name="CACHE_STORE" value="array"/>
<env name="DB_CONNECTION" value="mysql"/>
<env name="DB_HOST" value="127.0.0.1"/>
<env name="DB_DATABASE" value="onapb_local"/>
<env name="DB_URL" value=""/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="PULSE_ENABLED" value="false"/>
<env name="TELESCOPE_ENABLED" value="false"/>
<env name="NIGHTWATCH_ENABLED" value="false"/>
</php>
</phpunit>
+18
View File
@@ -0,0 +1,18 @@
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
}),
tailwindcss(),
],
server: {
watch: {
ignored: ['**/storage/framework/views/**'],
},
},
});