diff --git a/routes/console.php b/routes/console.php new file mode 100644 index 0000000..5db5e53 --- /dev/null +++ b/routes/console.php @@ -0,0 +1,37 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote'); + +use Illuminate\Support\Facades\Schedule; + +Schedule::command('app:cleanup-old-events')->daily(); + +// Frecuencia de backup configurable (con fallback seguro si DB no disponible) +try { + $backupFreq = \App\Models\Configuracion::get('backup_frequency', 'daily'); +} catch (\Throwable $e) { + $backupFreq = 'daily'; +} +Schedule::command('backup:run')->{$backupFreq}()->at('01:00'); +Schedule::command('backup:clean')->{$backupFreq}()->at('02:00'); + +// Recordatorio de partidos: diariamente a las 9am +Schedule::command('notificaciones:recordatorio-partido')->dailyAt('09:00'); + +// Reporte semanal: lunes a las 8am +Schedule::command('reportes:semanal')->weeklyOn(1, '08:00'); + +// Purge de hilos de conversación del agente expirados +Schedule::command('agent:purge-threads')->dailyAt('03:00'); + +// Registro de salud del sistema: se ejecuta cada minuto para confirmar que el Cron Job funciona +Schedule::call(function () { + \App\Models\Configuracion::set('last_scheduler_run', now()->toDateTimeString(), 'Última ejecución detectada del programador'); +})->everyMinute(); + + diff --git a/routes/web.php b/routes/web.php new file mode 100644 index 0000000..dc83baf --- /dev/null +++ b/routes/web.php @@ -0,0 +1,220 @@ +name('home'); +Route::get('/offline', fn() => view('offline'))->name('offline'); +Route::get('/eventos', [EventoController::class, 'index'])->name('eventos.index'); + +Route::get('/eventos/{id}', [EventoController::class, 'show'])->name('eventos.show'); +Route::get('/promos', [PromocionController::class, 'index'])->name('promos.index'); +Route::get('/promos/{id}/qr', [PromoQrController::class, 'show'])->name('promo.qr'); +Route::get('/asociate', function () { return view('auth.asociate'); })->name('asociate'); +Route::get('/noticias', [NoticiaController::class, 'index'])->name('noticias.index'); +Route::get('/noticias/{id}', [NoticiaController::class, 'show'])->name('noticias.show'); +Route::get('/torneos/{id}/posiciones', [App\Http\Controllers\TorneoController::class, 'standings'])->name('torneos.standings'); +Route::get('/torneos/{id}/goleadores', [App\Http\Controllers\TorneoController::class, 'topScorers'])->name('torneos.topScorers'); +Route::get('/equipos/{id}', [EquipoController::class, 'publicShow'])->name('equipos.show'); +Route::get('/documentacion', [DocumentacionController::class, 'index'])->name('documentacion.index'); +Route::get('/documentacion/descargar', [DocumentacionController::class, 'download'])->name('documentacion.download'); +Route::get('/panel-usuario', [PanelController::class, 'index'])->name('panel.usuario'); +Route::post('/panel-usuario/actualizar', [PanelController::class, 'actualizarDatos'])->name('panel.actualizar'); +Route::post('/panel-usuario/password', [PanelController::class, 'cambiarPassword'])->name('panel.password'); +Route::post('/panel-usuario/solicitar-qr', [PanelController::class, 'solicitarQr'])->name('panel.solicitar.qr'); +Route::get('/panel-usuario/mis-qrs', [PanelController::class, 'misQrs'])->name('panel.mis.qrs'); +Route::get('/panel-usuario/qr/descargar/{id}', [QrDownloadController::class, 'download'])->name('panel.qr.descargar'); +Route::post('/panel-usuario/generar-promo-qr', [PanelController::class, 'generarPromoQr'])->name('panel.generar.promo.qr'); +Route::get('/panel-usuario/promo-qr/{id}', [PanelController::class, 'verPromoQr'])->name('panel.promo.qr.ver'); + +// // Notificaciones + Route::get('/notificaciones', [NotificacionController::class, 'index'])->name('notificaciones.index'); + Route::get('/notificaciones/count', [NotificacionController::class, 'count'])->name('notificaciones.count'); + Route::get('/notificaciones/latest', [NotificacionController::class, 'latest'])->name('notificaciones.latest'); + Route::post('/notificaciones/subscribe', [NotificacionController::class, 'subscribe'])->name('notificaciones.subscribe'); + Route::post('/notificaciones/{id}/leer', [NotificacionController::class, 'marcarLeida'])->name('notificaciones.leer'); + + Route::post('/notificaciones/leer-todas', [NotificacionController::class, 'marcarTodasLeidas'])->name('notificaciones.leer.todas'); + Route::delete('/notificaciones/{id}', [NotificacionController::class, 'eliminar'])->name('notificaciones.eliminar'); + Route::delete('/notificaciones', [NotificacionController::class, 'eliminarTodas'])->name('notificaciones.eliminar.todas'); + +// ─── Seguimiento de Equipos ─── +Route::post('/seguimiento/equipo/{id}', [SeguimientoController::class, 'toggle'])->name('seguimiento.toggle'); +Route::get('/seguimiento/mis-equipos', [SeguimientoController::class, 'misEquipos'])->name('seguimiento.mis.equipos'); +Route::get('/seguimiento/estado/{id}', [SeguimientoController::class, 'estado'])->name('seguimiento.estado'); + + + +Route::get('/recuperar', function () { return view('auth.recuperar'); })->name('recuperar'); +Route::get('/reset-password/{token}', [AuthController::class, 'resetPasswordForm'])->name('reset.password'); +Route::get('/registro/jugador/buscar', fn() => redirect()->route('asociate'))->name('registro.jugador.buscar.get'); + +// Rate limiting en rutas críticas: 5 intentos por minuto +Route::middleware(['throttle:5,1'])->group(function () { + Route::post('/recuperar', [AuthController::class, 'recuperar']); + Route::post('/reset-password', [AuthController::class, 'resetPassword'])->name('reset.password.post'); + Route::post('/login', [AuthController::class, 'login'])->name('login'); + Route::post('/registro/aficionado', [AuthController::class, 'registroAficionado'])->name('registro.aficionado'); + Route::post('/registro/jugador/buscar', [AuthController::class, 'buscarJugador'])->name('registro.jugador.buscar'); + Route::post('/registro/jugador/completar', [AuthController::class, 'completarRegistroJugador'])->name('registro.jugador.completar'); +}); + +Route::post('/logout', [AuthController::class, 'logout'])->name('logout'); + + +// ═══ ADMIN PANEL ═══ +Route::redirect('/gestionar', '/admin'); +Route::prefix('admin')->group(function () { + Route::get('/', [AdminController::class, 'dashboard'])->name('admin.dashboard'); + + // Clubes + Route::get('/clubes', [AdminController::class, 'clubesIndex'])->name('admin.clubes.index'); + Route::get('/clubes/crear', [AdminController::class, 'clubesCreate'])->name('admin.clubes.create'); + Route::post('/clubes', [AdminController::class, 'clubesStore'])->name('admin.clubes.store'); + Route::get('/clubes/{id}/editar', [AdminController::class, 'clubesEdit'])->name('admin.clubes.edit'); + Route::put('/clubes/{id}', [AdminController::class, 'clubesUpdate'])->name('admin.clubes.update'); + Route::delete('/clubes/{id}', [AdminController::class, 'clubesDestroy'])->name('admin.clubes.destroy'); + + // Equipos + Route::get('/equipos', [AdminController::class, 'equiposIndex'])->name('admin.equipos.index'); + Route::get('/equipos/crear', [AdminController::class, 'equiposCreate'])->name('admin.equipos.create'); + Route::post('/equipos', [AdminController::class, 'equiposStore'])->name('admin.equipos.store'); + Route::get('/equipos/{id}/editar', [AdminController::class, 'equiposEdit'])->name('admin.equipos.edit'); + Route::put('/equipos/{id}', [AdminController::class, 'equiposUpdate'])->name('admin.equipos.update'); + Route::delete('/equipos/{id}', [AdminController::class, 'equiposDestroy'])->name('admin.equipos.destroy'); + Route::get('/equipos/{id}/jugadores', [AdminController::class, 'equipoJugadores'])->name('admin.equipos.jugadores'); + Route::post('/equipos/{id}/jugadores', [AdminController::class, 'equipoAddJugador'])->name('admin.equipos.jugadores.add'); + Route::delete('/equipos/{id}/jugadores/{id_jugador}', [AdminController::class, 'equipoRemoveJugador'])->name('admin.equipos.jugadores.remove'); + Route::get('/jugadores/search-ajax', [AdminController::class, 'jugadoresSearchAjax'])->name('admin.jugadores.search.ajax'); + Route::get('/jugadores/categoria-por-edad', [AdminController::class, 'jugadoresCategoriaPorEdad'])->name('admin.jugadores.categoria.edad'); + + + // Jugadores + Route::get('/jugadores', [AdminController::class, 'jugadoresIndex'])->name('admin.jugadores.index'); + Route::get('/jugadores/crear', [AdminController::class, 'jugadoresCreate'])->name('admin.jugadores.create'); + Route::post('/jugadores', [AdminController::class, 'jugadoresStore'])->name('admin.jugadores.store'); + Route::get('/jugadores/{id}/editar', [AdminController::class, 'jugadoresEdit'])->name('admin.jugadores.edit'); + Route::put('/jugadores/{id}', [AdminController::class, 'jugadoresUpdate'])->name('admin.jugadores.update'); + Route::delete('/jugadores/{id}', [AdminController::class, 'jugadoresDestroy'])->name('admin.jugadores.destroy'); + Route::post('/jugadores/importar', [AdminController::class, 'jugadoresImport'])->name('admin.jugadores.import'); + Route::get('/jugadores/exportar', [AdminController::class, 'jugadoresExport'])->name('admin.jugadores.export'); + + + // Pases + Route::get('/pases', [\App\Http\Controllers\Admin\PaseController::class, 'index'])->name('admin.pases.index'); + Route::get('/pases/crear', [\App\Http\Controllers\Admin\PaseController::class, 'create'])->name('admin.pases.create'); + Route::post('/pases', [\App\Http\Controllers\Admin\PaseController::class, 'store'])->name('admin.pases.store'); + Route::put('/pases/{id}/aprobar', [\App\Http\Controllers\Admin\PaseController::class, 'aprobar'])->name('admin.pases.aprobar'); + Route::put('/pases/{id}/rechazar', [\App\Http\Controllers\Admin\PaseController::class, 'rechazar'])->name('admin.pases.rechazar'); + + // Categorias + Route::get('/categorias', [\App\Http\Controllers\Admin\CategoriaController::class, 'index'])->name('admin.categorias.index'); + Route::get('/categorias/crear', [\App\Http\Controllers\Admin\CategoriaController::class, 'create'])->name('admin.categorias.create'); + Route::post('/categorias', [\App\Http\Controllers\Admin\CategoriaController::class, 'store'])->name('admin.categorias.store'); + Route::get('/categorias/{id}/editar', [\App\Http\Controllers\Admin\CategoriaController::class, 'edit'])->name('admin.categorias.edit'); + Route::put('/categorias/{id}', [\App\Http\Controllers\Admin\CategoriaController::class, 'update'])->name('admin.categorias.update'); + Route::delete('/categorias/{id}', [\App\Http\Controllers\Admin\CategoriaController::class, 'destroy'])->name('admin.categorias.destroy'); + + // Administradores + Route::get('/usuarios', [\App\Http\Controllers\Admin\AdminUserController::class, 'index'])->name('admin.usuarios.index'); + Route::get('/usuarios/crear', [\App\Http\Controllers\Admin\AdminUserController::class, 'create'])->name('admin.usuarios.create'); + Route::post('/usuarios', [\App\Http\Controllers\Admin\AdminUserController::class, 'store'])->name('admin.usuarios.store'); + Route::get('/usuarios/{id}/editar', [\App\Http\Controllers\Admin\AdminUserController::class, 'edit'])->name('admin.usuarios.edit'); + Route::put('/usuarios/{id}', [\App\Http\Controllers\Admin\AdminUserController::class, 'update'])->name('admin.usuarios.update'); + Route::delete('/usuarios/{id}', [\App\Http\Controllers\Admin\AdminUserController::class, 'destroy'])->name('admin.usuarios.destroy'); + + // Eventos + Route::get('/eventos', [AdminController::class, 'eventosIndex'])->name('admin.eventos.index'); + Route::get('/eventos/crear', [AdminController::class, 'eventosCreate'])->name('admin.eventos.create'); + Route::post('/eventos', [AdminController::class, 'eventosStore'])->name('admin.eventos.store'); + Route::get('/eventos/{id}/editar', [AdminController::class, 'eventosEdit'])->name('admin.eventos.edit'); + Route::put('/eventos/{id}', [AdminController::class, 'eventosUpdate'])->name('admin.eventos.update'); + Route::delete('/eventos/{id}', [AdminController::class, 'eventosDestroy'])->name('admin.eventos.destroy'); + Route::get('/eventos/{id}/stats', [AdminController::class, 'eventosStats'])->name('admin.eventos.stats'); + Route::post('/eventos/{id}/stats', [AdminController::class, 'eventosStatsStore'])->name('admin.eventos.stats.store'); + + // Promociones + Route::get('/promociones', [AdminController::class, 'promocionesIndex'])->name('admin.promociones.index'); + Route::get('/promociones/crear', [AdminController::class, 'promocionesCreate'])->name('admin.promociones.create'); + Route::post('/promociones', [AdminController::class, 'promocionesStore'])->name('admin.promociones.store'); + Route::get('/promociones/{id}/editar', [AdminController::class, 'promocionesEdit'])->name('admin.promociones.edit'); + Route::put('/promociones/{id}', [AdminController::class, 'promocionesUpdate'])->name('admin.promociones.update'); + Route::delete('/promociones/{id}', [AdminController::class, 'promocionesDestroy'])->name('admin.promociones.destroy'); + + // Noticias + Route::get('/noticias', [AdminController::class, 'noticiasIndex'])->name('admin.noticias.index'); + Route::get('/noticias/crear', [AdminController::class, 'noticiasCreate'])->name('admin.noticias.create'); + Route::post('/noticias', [AdminController::class, 'noticiasStore'])->name('admin.noticias.store'); + Route::get('/noticias/{id}/editar', [AdminController::class, 'noticiasEdit'])->name('admin.noticias.edit'); + Route::put('/noticias/{id}', [AdminController::class, 'noticiasUpdate'])->name('admin.noticias.update'); + Route::delete('/noticias/{id}', [AdminController::class, 'noticiasDestroy'])->name('admin.noticias.destroy'); + + // Escanear QR + Route::get('/escanear-qr', [AdminController::class, 'escanearQr'])->name('admin.escanear'); + Route::post('/validar-qr', [AdminController::class, 'validarQr'])->name('admin.validar.qr'); + + // Carrusel Inicio + Route::get('/carousel', [\App\Http\Controllers\Admin\CarouselItemController::class, 'index'])->name('admin.carousel.index'); + Route::get('/carousel/crear', [\App\Http\Controllers\Admin\CarouselItemController::class, 'create'])->name('admin.carousel.create'); + Route::post('/carousel', [\App\Http\Controllers\Admin\CarouselItemController::class, 'store'])->name('admin.carousel.store'); + Route::get('/carousel/{carouselItem}/editar', [\App\Http\Controllers\Admin\CarouselItemController::class, 'edit'])->name('admin.carousel.edit'); + Route::put('/carousel/{carouselItem}', [\App\Http\Controllers\Admin\CarouselItemController::class, 'update'])->name('admin.carousel.update'); + Route::delete('/carousel/{carouselItem}', [\App\Http\Controllers\Admin\CarouselItemController::class, 'destroy'])->name('admin.carousel.destroy'); + + // Sponsors + Route::get('/sponsors', [AdminController::class, 'sponsorsIndex'])->name('admin.sponsors.index'); + Route::get('/sponsors/crear', [AdminController::class, 'sponsorsCreate'])->name('admin.sponsors.create'); + Route::post('/sponsors', [AdminController::class, 'sponsorsStore'])->name('admin.sponsors.store'); + Route::get('/sponsors/{id}/editar', [AdminController::class, 'sponsorsEdit'])->name('admin.sponsors.edit'); + Route::put('/sponsors/{id}', [AdminController::class, 'sponsorsUpdate'])->name('admin.sponsors.update'); + Route::delete('/sponsors/{id}', [AdminController::class, 'sponsorsDestroy'])->name('admin.sponsors.destroy'); + + // Configuración General + Route::get('/configuracion', [AdminController::class, 'settingsIndex'])->name('admin.settings.index'); + Route::post('/configuracion', [AdminController::class, 'settingsUpdate'])->name('admin.settings.update'); + Route::post('/configuracion/tarea-manual', [AdminController::class, 'runManualTask'])->name('admin.settings.manual.task'); + + // Torneos + Route::get('/torneos', [AdminController::class, 'torneosIndex'])->name('admin.torneos.index'); + Route::get('/torneos/crear', [AdminController::class, 'torneosCreate'])->name('admin.torneos.create'); + Route::post('/torneos', [AdminController::class, 'torneosStore'])->name('admin.torneos.store'); + Route::get('/torneos/{id}/editar', [AdminController::class, 'torneosEdit'])->name('admin.torneos.edit'); + Route::put('/torneos/{id}', [AdminController::class, 'torneosUpdate'])->name('admin.torneos.update'); + Route::delete('/torneos/{id}', [AdminController::class, 'torneosDestroy'])->name('admin.torneos.destroy'); + Route::post('/torneos/{id}/equipos', [AdminController::class, 'torneoAddEquipo'])->name('admin.torneos.equipos.add'); + Route::delete('/torneos/{id}/equipos/{id_equipo}', [AdminController::class, 'torneoRemoveEquipo'])->name('admin.torneos.equipos.remove'); + + // Fixture Generator + Route::post('/torneos/{id}/generar-fixture', [\App\Http\Controllers\Admin\FixtureController::class, 'preview'])->name('admin.torneos.fixture.preview'); + Route::post('/torneos/{id}/confirmar-fixture', [\App\Http\Controllers\Admin\FixtureController::class, 'confirmar'])->name('admin.torneos.fixture.confirmar'); + + // Importador de resultados históricos + Route::get('/torneos/{id}/importar', [\App\Http\Controllers\Admin\FixtureController::class, 'importForm'])->name('admin.torneos.importar'); + Route::post('/torneos/{id}/importar', [\App\Http\Controllers\Admin\FixtureController::class, 'importStore'])->name('admin.torneos.importar.store'); + + // Playoffs + Route::get('/torneos/{id}/playoffs-manage', [\App\Http\Controllers\Admin\FixtureController::class, 'managePlayoffs'])->name('admin.torneos.playoffs.manage'); + Route::post('/torneos/{id}/generar-playoffs', [\App\Http\Controllers\Admin\FixtureController::class, 'generarPlayoffs'])->name('admin.torneos.playoffs.generar'); + Route::post('/torneos/{id}/playoffs-avanzar', [\App\Http\Controllers\Admin\FixtureController::class, 'avanzarGanador'])->name('admin.torneos.playoffs.avanzar'); +}); + +// Pestaña de Playoffs (Público) +Route::get('/torneos/{id}/playoffs', [App\Http\Controllers\TorneoController::class, 'playoffs'])->name('torneos.playoffs'); + + +// OnAPB Genius Agent +Route::post('/agent/chat', [\App\Http\Controllers\GeniusAgentController::class, 'chat']) + ->middleware('throttle:20,1') + ->name('agent.chat');