limpieza
This commit is contained in:
@@ -1,115 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\AdminUser;
|
||||
use App\Models\Club;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class AdminUserController extends Controller
|
||||
{
|
||||
private function checkSuperAdmin(Request $request)
|
||||
{
|
||||
if (!session('admin_logged_in') || session('admin_role') != 1) {
|
||||
abort(403, 'Acceso denegado. Solo Súper Administradores.');
|
||||
}
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->checkSuperAdmin($request);
|
||||
$usuarios = AdminUser::with('club')->orderBy('id', 'desc')->paginate(20);
|
||||
return view('admin.usuarios.index', compact('usuarios'));
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
$this->checkSuperAdmin($request);
|
||||
$usuario = null;
|
||||
$clubes = Club::orderBy('nombre')->get();
|
||||
return view('admin.usuarios.form', compact('usuario', 'clubes'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->checkSuperAdmin($request);
|
||||
|
||||
$data = $request->validate([
|
||||
'username' => 'required|string|max:50|unique:admin_users',
|
||||
'password' => 'required|string|min:6',
|
||||
'role' => 'required|integer|in:1,2',
|
||||
'id_club' => 'nullable|integer|exists:clubes,id_club'
|
||||
]);
|
||||
|
||||
if ($data['role'] == 2 && empty($data['id_club'])) {
|
||||
return back()->withErrors(['id_club' => 'Si el rol es Admin de Club, se requiere un club asociado.'])->withInput();
|
||||
}
|
||||
|
||||
if ($data['role'] == 1) {
|
||||
$data['id_club'] = null; // Superadmins no pertenecen a un club específico en este contexto
|
||||
}
|
||||
|
||||
$data['password'] = Hash::make($data['password']);
|
||||
|
||||
AdminUser::create($data);
|
||||
|
||||
return redirect()->route('admin.usuarios.index')->with('admin_msg', 'Administrador creado exitosamente.');
|
||||
}
|
||||
|
||||
public function edit(Request $request, $id)
|
||||
{
|
||||
$this->checkSuperAdmin($request);
|
||||
$usuario = AdminUser::findOrFail($id);
|
||||
$clubes = Club::orderBy('nombre')->get();
|
||||
return view('admin.usuarios.form', compact('usuario', 'clubes'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->checkSuperAdmin($request);
|
||||
$usuario = AdminUser::findOrFail($id);
|
||||
|
||||
$data = $request->validate([
|
||||
'username' => ['required', 'string', 'max:50', Rule::unique('admin_users')->ignore($usuario->id)],
|
||||
'password' => 'nullable|string|min:6',
|
||||
'role' => 'required|integer|in:1,2',
|
||||
'id_club' => 'nullable|integer|exists:clubes,id_club'
|
||||
]);
|
||||
|
||||
if ($data['role'] == 2 && empty($data['id_club'])) {
|
||||
return back()->withErrors(['id_club' => 'Si el rol es Admin de Club, se requiere un club asociado.'])->withInput();
|
||||
}
|
||||
|
||||
if ($data['role'] == 1) {
|
||||
$data['id_club'] = null;
|
||||
}
|
||||
|
||||
if (!empty($data['password'])) {
|
||||
$data['password'] = Hash::make($data['password']);
|
||||
} else {
|
||||
unset($data['password']);
|
||||
}
|
||||
|
||||
$usuario->update($data);
|
||||
|
||||
return redirect()->route('admin.usuarios.index')->with('admin_msg', 'Administrador actualizado exitosamente.');
|
||||
}
|
||||
|
||||
public function destroy(Request $request, $id)
|
||||
{
|
||||
$this->checkSuperAdmin($request);
|
||||
|
||||
$usuario = AdminUser::findOrFail($id);
|
||||
|
||||
if ($usuario->id == session('admin_id')) {
|
||||
return back()->with('admin_error', 'No puedes eliminar tu propio usuario.');
|
||||
}
|
||||
|
||||
$usuario->delete();
|
||||
|
||||
return redirect()->route('admin.usuarios.index')->with('admin_msg', 'Administrador eliminado.');
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CarouselItem;
|
||||
use App\Services\ImageOptimizer;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class CarouselItemController extends Controller
|
||||
{
|
||||
private function checkGeneralAdmin(Request $request): void
|
||||
{
|
||||
if (!session('admin_logged_in') || !in_array(session('admin_role'), [1, 2])) {
|
||||
abort(403, 'Acceso denegado');
|
||||
}
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->checkGeneralAdmin($request);
|
||||
$items = CarouselItem::orderBy('orden', 'asc')->latest()->get();
|
||||
return view('admin.carousel.index', compact('items'));
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
$this->checkGeneralAdmin($request);
|
||||
return view('admin.carousel.create');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->checkGeneralAdmin($request);
|
||||
$request->validate([
|
||||
'titulo' => 'nullable|string|max:255',
|
||||
'subtitulo' => 'nullable|string|max:255',
|
||||
'boton_texto' => 'nullable|string|max:255',
|
||||
'boton_enlace' => 'nullable|string|max:255',
|
||||
'imagen' => 'required|image|mimes:jpeg,png,jpg,webp|max:5120',
|
||||
'orden' => 'nullable|integer',
|
||||
'activo' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
$data = $request->except('imagen');
|
||||
$data['activo'] = $request->has('activo');
|
||||
|
||||
if ($request->hasFile('imagen')) {
|
||||
$path = app(ImageOptimizer::class)->storeAndOptimize($request->file('imagen'), 'carousel');
|
||||
$data['imagen'] = 'storage/' . $path;
|
||||
}
|
||||
|
||||
CarouselItem::create($data);
|
||||
|
||||
return redirect()->route('admin.carousel.index')->with('admin_msg', 'Slide creado exitosamente.');
|
||||
}
|
||||
|
||||
public function edit(Request $request, CarouselItem $carouselItem)
|
||||
{
|
||||
$this->checkGeneralAdmin($request);
|
||||
return view('admin.carousel.edit', compact('carouselItem'));
|
||||
}
|
||||
|
||||
public function update(Request $request, CarouselItem $carouselItem)
|
||||
{
|
||||
$this->checkGeneralAdmin($request);
|
||||
$request->validate([
|
||||
'titulo' => 'nullable|string|max:255',
|
||||
'subtitulo' => 'nullable|string|max:255',
|
||||
'boton_texto' => 'nullable|string|max:255',
|
||||
'boton_enlace' => 'nullable|string|max:255',
|
||||
'imagen' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:5120',
|
||||
'orden' => 'nullable|integer',
|
||||
'activo' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
$data = $request->except('imagen');
|
||||
$data['activo'] = $request->has('activo');
|
||||
|
||||
if ($request->hasFile('imagen')) {
|
||||
// Eliminar imagen anterior si existe
|
||||
if ($carouselItem->imagen) {
|
||||
$oldPath = public_path($carouselItem->imagen);
|
||||
if (File::exists($oldPath)) {
|
||||
File::delete($oldPath);
|
||||
}
|
||||
}
|
||||
|
||||
$path = app(ImageOptimizer::class)->storeAndOptimize($request->file('imagen'), 'carousel');
|
||||
$data['imagen'] = 'storage/' . $path;
|
||||
}
|
||||
|
||||
$carouselItem->update($data);
|
||||
|
||||
return redirect()->route('admin.carousel.index')->with('admin_msg', 'Slide actualizado exitosamente.');
|
||||
}
|
||||
|
||||
public function destroy(Request $request, CarouselItem $carouselItem)
|
||||
{
|
||||
$this->checkGeneralAdmin($request);
|
||||
if ($carouselItem->imagen) {
|
||||
$imagePath = public_path($carouselItem->imagen);
|
||||
if (File::exists($imagePath)) {
|
||||
File::delete($imagePath);
|
||||
}
|
||||
}
|
||||
|
||||
$carouselItem->delete();
|
||||
|
||||
return redirect()->route('admin.carousel.index')->with('admin_msg', 'Slide eliminado exitosamente.');
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Categoria;
|
||||
|
||||
class CategoriaController extends Controller
|
||||
{
|
||||
private function checkSuperAdmin(Request $request)
|
||||
{
|
||||
if (!session('admin_logged_in') || session('admin_role') != 1) {
|
||||
abort(403, 'Acceso denegado. Solo Súper Administradores.');
|
||||
}
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->checkSuperAdmin($request);
|
||||
$categorias = Categoria::latest()->get();
|
||||
return view('admin.categorias.index', compact('categorias'));
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
$this->checkSuperAdmin($request);
|
||||
return view('admin.categorias.form', ['categoria' => null]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->checkSuperAdmin($request);
|
||||
$data = $request->validate([
|
||||
'nombre' => 'required|string|max:50',
|
||||
'edad_min' => 'required|integer',
|
||||
'edad_max' => 'required|integer|gte:edad_min',
|
||||
'genero' => 'nullable|string|max:20',
|
||||
]);
|
||||
$data['es_libre'] = $request->has('es_libre');
|
||||
Categoria::create($data);
|
||||
return redirect()->route('admin.categorias.index')->with('admin_msg', 'Categoría creada.');
|
||||
}
|
||||
|
||||
public function edit(Request $request, $id)
|
||||
{
|
||||
$this->checkSuperAdmin($request);
|
||||
$categoria = Categoria::findOrFail($id);
|
||||
return view('admin.categorias.form', compact('categoria'));
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$this->checkSuperAdmin($request);
|
||||
$categoria = Categoria::findOrFail($id);
|
||||
$data = $request->validate([
|
||||
'nombre' => 'required|string|max:50',
|
||||
'edad_min' => 'required|integer',
|
||||
'edad_max' => 'required|integer|gte:edad_min',
|
||||
'genero' => 'nullable|string|max:20',
|
||||
]);
|
||||
$data['es_libre'] = $request->has('es_libre');
|
||||
$categoria->update($data);
|
||||
return redirect()->route('admin.categorias.index')->with('admin_msg', 'Categoría actualizada.');
|
||||
}
|
||||
|
||||
public function destroy(Request $request, $id)
|
||||
{
|
||||
$this->checkSuperAdmin($request);
|
||||
$categoria = Categoria::findOrFail($id);
|
||||
$categoria->delete();
|
||||
return redirect()->route('admin.categorias.index')->with('admin_msg', 'Categoría eliminada.');
|
||||
}
|
||||
}
|
||||
@@ -1,380 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Torneo;
|
||||
use App\Services\FixtureService;
|
||||
|
||||
class FixtureController extends Controller
|
||||
{
|
||||
private FixtureService $fixtureService;
|
||||
|
||||
public function __construct(FixtureService $fixtureService)
|
||||
{
|
||||
$this->fixtureService = $fixtureService;
|
||||
}
|
||||
|
||||
private function checkSuperAdmin()
|
||||
{
|
||||
if (!session('admin_logged_in') || session('admin_role') != 1) {
|
||||
abort(403, 'Solo Súper Administradores pueden generar fixtures.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /admin/torneos/{id}/generar-fixture — Preview del fixture
|
||||
*/
|
||||
public function preview(Request $request, int $id)
|
||||
{
|
||||
$this->checkSuperAdmin();
|
||||
|
||||
$torneo = Torneo::with('equipos.club')->findOrFail($id);
|
||||
|
||||
$request->validate([
|
||||
'fecha_inicio' => 'required|date|after_or_equal:today',
|
||||
'dias_entre_jornadas' => 'required|integer|min:1|max:60',
|
||||
'sede_default' => 'nullable|string|max:200',
|
||||
'doble_rueda' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
try {
|
||||
$partidos = $this->fixtureService->generarRoundRobin(
|
||||
torneo: $torneo,
|
||||
fechaInicio: $request->input('fecha_inicio'),
|
||||
diasEntreJornadas: (int) $request->input('dias_entre_jornadas', 7),
|
||||
sedeDefault: $request->input('sede_default', ''),
|
||||
dobleRueda: (bool) $request->input('doble_rueda', false),
|
||||
);
|
||||
|
||||
// Enriquecer con nombres para la vista
|
||||
$partidosEnriquecidos = collect($partidos)->map(function ($p) {
|
||||
$local = \App\Models\Equipo::with('club')->find($p['id_equipo_local']);
|
||||
$visitante = \App\Models\Equipo::with('club')->find($p['id_equipo_visitante']);
|
||||
return array_merge($p, [
|
||||
'nombre_local' => ($local->club->nombre ?? '?') . ' (' . ($local->categoria ?? '') . ')',
|
||||
'nombre_visitante' => ($visitante->club->nombre ?? '?') . ' (' . ($visitante->categoria ?? '') . ')',
|
||||
]);
|
||||
})->toArray();
|
||||
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return back()->with('admin_error', $e->getMessage());
|
||||
}
|
||||
|
||||
$fixtureParams = [
|
||||
'fecha_inicio' => $request->input('fecha_inicio'),
|
||||
'dias_entre_jornadas' => $request->input('dias_entre_jornadas', 7),
|
||||
'sede_default' => $request->input('sede_default', ''),
|
||||
'doble_rueda' => (bool) $request->input('doble_rueda', false),
|
||||
];
|
||||
|
||||
return view('admin.torneos.fixture_preview', compact('torneo', 'partidosEnriquecidos', 'fixtureParams'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /admin/torneos/{id}/confirmar-fixture — Persiste el fixture
|
||||
*/
|
||||
public function confirmar(Request $request, int $id)
|
||||
{
|
||||
$this->checkSuperAdmin();
|
||||
|
||||
$torneo = Torneo::with('equipos.club')->findOrFail($id);
|
||||
|
||||
$request->validate([
|
||||
'fecha_inicio' => 'required|date',
|
||||
'dias_entre_jornadas' => 'required|integer|min:1',
|
||||
'sede_default' => 'nullable|string|max:200',
|
||||
'doble_rueda' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
try {
|
||||
$partidos = $this->fixtureService->generarRoundRobin(
|
||||
torneo: $torneo,
|
||||
fechaInicio: $request->input('fecha_inicio'),
|
||||
diasEntreJornadas: (int) $request->input('dias_entre_jornadas', 7),
|
||||
sedeDefault: $request->input('sede_default', ''),
|
||||
dobleRueda: (bool) $request->input('doble_rueda', false),
|
||||
);
|
||||
|
||||
$creados = $this->fixtureService->persistirFixture($partidos, $torneo);
|
||||
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return back()->with('admin_error', $e->getMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('admin.torneos.edit', $id)
|
||||
->with('admin_msg', "✅ Fixture generado correctamente: {$creados} partidos creados.");
|
||||
}
|
||||
|
||||
public function importForm(int $id)
|
||||
{
|
||||
$this->checkSuperAdmin();
|
||||
$torneo = Torneo::findOrFail($id);
|
||||
return view('admin.torneos.importar', compact('torneo'));
|
||||
}
|
||||
|
||||
public function importStore(Request $request, int $id)
|
||||
{
|
||||
$this->checkSuperAdmin();
|
||||
$torneo = Torneo::with(['equipos.club'])->findOrFail($id);
|
||||
|
||||
$request->validate([
|
||||
'texto_importar' => 'required|string',
|
||||
]);
|
||||
|
||||
$lineas = explode("\n", $request->input('texto_importar'));
|
||||
$creados = 0;
|
||||
$errores = [];
|
||||
|
||||
foreach ($lineas as $index => $linea) {
|
||||
$linea = trim($linea);
|
||||
if (empty($linea)) continue;
|
||||
|
||||
try {
|
||||
// Formato esperado: Fecha, ClubL, CatL, ClubV, CatV, MarcadorL, MarcadorV, Sede, Grupo
|
||||
$partes = str_getcsv($linea);
|
||||
|
||||
if (count($partes) < 7) {
|
||||
throw new \Exception("Formato insuficiente. Se esperan al menos 7 columnas.");
|
||||
}
|
||||
|
||||
$fechaRaw = trim($partes[0]);
|
||||
$fecha = \Carbon\Carbon::parse(str_replace('/', '-', $fechaRaw))->format('Y-m-d');
|
||||
|
||||
$nombreClubL = trim($partes[1]);
|
||||
$catL = trim($partes[2]);
|
||||
$nombreClubV = trim($partes[3]);
|
||||
$catV = trim($partes[4]);
|
||||
$marcadorL = trim($partes[5]);
|
||||
$marcadorV = trim($partes[6]);
|
||||
$sede = $partes[7] ?? null;
|
||||
$grupo = $partes[8] ?? null;
|
||||
|
||||
// Buscar equipos con validación de grupo (prioridad) y categoría
|
||||
$equipoL = $this->buscarEquipo($nombreClubL, $catL, $torneo, $grupo);
|
||||
$equipoV = $this->buscarEquipo($nombreClubV, $catV, $torneo, $grupo);
|
||||
|
||||
if (!$equipoL || !$equipoV) {
|
||||
$missing = !$equipoL ? "'{$nombreClubL} ({$catL})'" : "'{$nombreClubV} ({$catV})'";
|
||||
throw new \Exception("No se encontró el equipo {$missing} en el grupo '" . ($grupo ?? 'N/A') . "' ni por categoría.");
|
||||
}
|
||||
|
||||
\App\Models\Evento::create([
|
||||
'id_evento' => uniqid('ev_imp_'),
|
||||
'id_torneo' => $id,
|
||||
'fase' => \App\Models\Evento::FASE_REGULAR,
|
||||
'fecha_evento' => $fecha,
|
||||
'id_equipo_local' => $equipoL->id_equipo,
|
||||
'id_equipo_visitante' => $equipoV->id_equipo,
|
||||
'marcador_local' => $marcadorL !== '' ? (int)$marcadorL : null,
|
||||
'marcador_visitante' => $marcadorV !== '' ? (int)$marcadorV : null,
|
||||
'nombre_evento' => $equipoL->club->nombre . ' vs ' . $equipoV->club->nombre,
|
||||
'hora_inicio' => '19:00:00',
|
||||
'hora_fin' => '21:00:00',
|
||||
'sede' => $sede,
|
||||
'precio' => 0,
|
||||
]);
|
||||
|
||||
$creados++;
|
||||
} catch (\Exception $e) {
|
||||
$errores[] = "Línea " . ($index + 1) . ": " . $e->getMessage();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$msg = "Se importaron {$creados} partidos exitosamente.";
|
||||
if (count($errores) > 0) {
|
||||
return back()->with('admin_msg', $msg)->with('admin_error', "Se detectaron errores en " . count($errores) . " líneas:<br>" . implode("<br>", $errores));
|
||||
}
|
||||
|
||||
return redirect()->route('admin.torneos.edit', $id)->with('admin_msg', $msg);
|
||||
}
|
||||
|
||||
private function buscarEquipo(string $clubName, string $category, Torneo $torneo, ?string $grupo = null)
|
||||
{
|
||||
$clubNameNorm = $this->normalizeString($clubName);
|
||||
$categoryNorm = $this->normalizeString($category);
|
||||
$grupoNorm = $grupo ? $this->normalizeString($grupo) : null;
|
||||
|
||||
// PASO 1: Prioridad absoluta al GRUPO (vía pivot)
|
||||
if ($grupoNorm) {
|
||||
$match = $torneo->equipos->first(function($e) use ($clubNameNorm, $grupoNorm) {
|
||||
$eClubName = $this->normalizeString($e->club->nombre ?? '');
|
||||
$eGrupo = $this->normalizeString($e->pivot->grupo ?? '');
|
||||
|
||||
return ($eGrupo === $grupoNorm) &&
|
||||
(strpos($eClubName, $clubNameNorm) !== false || strpos($clubNameNorm, $eClubName) !== false);
|
||||
});
|
||||
if ($match) return $match;
|
||||
}
|
||||
|
||||
// PASO 2: Fallback a búsqueda por CATEGORÍA si el grupo no coincide o no se especificó
|
||||
return $torneo->equipos->first(function($e) use ($clubNameNorm, $categoryNorm) {
|
||||
$eClubName = $this->normalizeString($e->club->nombre ?? '');
|
||||
$eCategory = $this->normalizeString($e->categoria ?? '');
|
||||
|
||||
$matchClub = (strpos($eClubName, $clubNameNorm) !== false || strpos($clubNameNorm, $eClubName) !== false);
|
||||
$matchCat = (strpos($eCategory, $categoryNorm) !== false || strpos($categoryNorm, $eCategory) !== false);
|
||||
|
||||
return $matchClub && $matchCat;
|
||||
});
|
||||
}
|
||||
|
||||
private function normalizeString(?string $str): string
|
||||
{
|
||||
if (!$str) return '';
|
||||
$str = mb_strtolower(trim($str), 'UTF-8');
|
||||
$str = str_replace(
|
||||
['á', 'é', 'í', 'ó', 'ú', 'ü', 'ñ'],
|
||||
['a', 'e', 'i', 'o', 'u', 'u', 'n'],
|
||||
$str
|
||||
);
|
||||
// Eliminar caracteres no alfanuméricos para un matching más flexible
|
||||
return preg_replace('/[^a-z0-9]/', '', $str);
|
||||
}
|
||||
|
||||
public function generarPlayoffs(Request $request, int $id)
|
||||
{
|
||||
$this->checkSuperAdmin();
|
||||
$torneo = Torneo::findOrFail($id);
|
||||
$grupo = $request->input('grupo');
|
||||
$formato = (int) $request->input('formato', 1); // 1, 3, 5
|
||||
|
||||
if (!$grupo) {
|
||||
return back()->with('admin_error', 'Debes seleccionar un grupo para generar los playoffs.');
|
||||
}
|
||||
|
||||
$ts = new \App\Services\TournamentService();
|
||||
$standings = $ts->getStandings($id, true);
|
||||
|
||||
if (!isset($standings[$grupo])) {
|
||||
return back()->with('admin_error', "No hay equipos en el grupo {$grupo}.");
|
||||
}
|
||||
|
||||
$top8 = array_slice($standings[$grupo], 0, 8);
|
||||
|
||||
if (count($top8) < 2) {
|
||||
return back()->with('admin_error', "Se necesitan al menos 2 equipos para generar playoffs.");
|
||||
}
|
||||
|
||||
// Determinar emparejamientos (1 vs 8, 4 vs 5, 2 vs 7, 3 vs 6)
|
||||
// Usamos el seeding oficial: 1v8, 4v5, 2v7, 3v6
|
||||
$parejas = [
|
||||
['local' => $top8[0]['id'], 'visit' => $top8[7]['id'] ?? null, 'nro' => 1],
|
||||
['local' => $top8[3]['id'] ?? null, 'visit' => $top8[4]['id'] ?? null, 'nro' => 2],
|
||||
['local' => $top8[1]['id'] ?? null, 'visit' => $top8[6]['id'] ?? null, 'nro' => 3],
|
||||
['local' => $top8[2]['id'] ?? null, 'visit' => $top8[5]['id'] ?? null, 'nro' => 4],
|
||||
];
|
||||
|
||||
$count = 0;
|
||||
foreach ($parejas as $p) {
|
||||
if (!$p['local'] || !$p['visit']) continue;
|
||||
|
||||
$local = \App\Models\Equipo::with('club')->find($p['local']);
|
||||
$visit = \App\Models\Equipo::with('club')->find($p['visit']);
|
||||
|
||||
// Crear N partidos según el formato
|
||||
for ($i = 0; $i < $formato; $i++) {
|
||||
\App\Models\Evento::create([
|
||||
'id_evento' => uniqid('ply_'),
|
||||
'id_torneo' => $id,
|
||||
'fase' => \App\Models\Evento::FASE_CUARTOS,
|
||||
'numero_partido_bracket' => $p['nro'],
|
||||
'id_equipo_local' => $p['local'],
|
||||
'id_equipo_visitante' => $p['visit'],
|
||||
'nombre_evento' => "4tos (J".($i+1)."): " . $local->club->nombre . " vs " . $visit->club->nombre . " ({$grupo})",
|
||||
'fecha_evento' => now()->addDays(7 + ($i * 3))->format('Y-m-d'),
|
||||
'hora_inicio' => '20:00:00',
|
||||
'hora_fin' => '22:00:00',
|
||||
'precio' => 0,
|
||||
]);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('admin.torneos.playoffs.manage', $id)
|
||||
->with('admin_msg', "Playoffs generados: {$count} partidos creados para el grupo {$grupo}.");
|
||||
}
|
||||
|
||||
public function managePlayoffs(int $id)
|
||||
{
|
||||
$this->checkSuperAdmin();
|
||||
$torneo = Torneo::findOrFail($id);
|
||||
|
||||
$ts = new \App\Services\TournamentService();
|
||||
$bracket = $ts->getPlayoffBrackets($id);
|
||||
|
||||
$grupos = \DB::table('torneo_equipo')->where('id_torneo', $id)->distinct()->pluck('grupo')->filter();
|
||||
|
||||
return view('admin.torneos.playoff_manage', compact('torneo', 'bracket', 'grupos'));
|
||||
}
|
||||
|
||||
public function avanzarGanador(Request $request, int $id)
|
||||
{
|
||||
$this->checkSuperAdmin();
|
||||
$faseActual = (int) $request->input('fase');
|
||||
$nroBracket = (int) $request->input('nro_bracket');
|
||||
$idGanador = (int) $request->input('id_ganador');
|
||||
|
||||
$torneo = Torneo::findOrFail($id);
|
||||
$ganador = \App\Models\Equipo::with('club')->findOrFail($idGanador);
|
||||
|
||||
// Mapeo de avance
|
||||
// Cuartos 1 & 2 -> Semis 1
|
||||
// Cuartos 3 & 4 -> Semis 2
|
||||
// Semis 1 & 2 -> Final 1
|
||||
$mapping = [
|
||||
\App\Models\Evento::FASE_CUARTOS => [
|
||||
1 => ['next_fase' => \App\Models\Evento::FASE_SEMIS, 'next_nro' => 1, 'side' => 'local'],
|
||||
2 => ['next_fase' => \App\Models\Evento::FASE_SEMIS, 'next_nro' => 1, 'side' => 'visitante'],
|
||||
3 => ['next_fase' => \App\Models\Evento::FASE_SEMIS, 'next_nro' => 2, 'side' => 'local'],
|
||||
4 => ['next_fase' => \App\Models\Evento::FASE_SEMIS, 'next_nro' => 2, 'side' => 'visitante'],
|
||||
],
|
||||
\App\Models\Evento::FASE_SEMIS => [
|
||||
1 => ['next_fase' => \App\Models\Evento::FASE_FINAL, 'next_nro' => 1, 'side' => 'local'],
|
||||
2 => ['next_fase' => \App\Models\Evento::FASE_FINAL, 'next_nro' => 1, 'side' => 'visitante'],
|
||||
]
|
||||
];
|
||||
|
||||
if (!isset($mapping[$faseActual][$nroBracket])) {
|
||||
return back()->with('admin_error', 'No se puede avanzar desde esta fase.');
|
||||
}
|
||||
|
||||
$next = $mapping[$faseActual][$nroBracket];
|
||||
|
||||
// Buscar si ya existe el partido en la siguiente fase
|
||||
$eventoNext = \App\Models\Evento::where('id_torneo', $id)
|
||||
->where('fase', $next['next_fase'])
|
||||
->where('numero_partido_bracket', $next['next_nro'])
|
||||
->first();
|
||||
|
||||
if ($eventoNext) {
|
||||
if ($next['side'] == 'local') {
|
||||
$eventoNext->id_equipo_local = $idGanador;
|
||||
} else {
|
||||
$eventoNext->id_equipo_visitante = $idGanador;
|
||||
}
|
||||
$eventoNext->nombre_evento = ($eventoNext->equipoLocal->club->nombre ?? 'TBD') . ' vs ' . ($eventoNext->equipoVisitante->club->nombre ?? 'TBD');
|
||||
$eventoNext->save();
|
||||
} else {
|
||||
// Crear el primer partido de la serie (por defecto 1 partido, el admin puede agregar más)
|
||||
\App\Models\Evento::create([
|
||||
'id_evento' => uniqid('ply_adv_'),
|
||||
'id_torneo' => $id,
|
||||
'fase' => $next['next_fase'],
|
||||
'numero_partido_bracket' => $next['next_nro'],
|
||||
'id_equipo_local' => $next['side'] == 'local' ? $idGanador : null,
|
||||
'id_equipo_visitante' => $next['side'] == 'visitante' ? $idGanador : null,
|
||||
'nombre_evento' => $next['side'] == 'local' ? ($ganador->club->nombre . " vs TBD") : ("TBD vs " . $ganador->club->nombre),
|
||||
'fecha_evento' => now()->addDays(14)->format('Y-m-d'),
|
||||
'hora_inicio' => '20:00:00',
|
||||
'hora_fin' => '22:00:00',
|
||||
'precio' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
return back()->with('admin_msg', "✅ Equipo {$ganador->club->nombre} avanzado a la siguiente ronda.");
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Pase;
|
||||
use App\Models\Jugador;
|
||||
use App\Models\Club;
|
||||
|
||||
class PaseController extends Controller
|
||||
{
|
||||
private function checkGeneralAdmin(Request $request)
|
||||
{
|
||||
if (!session('admin_logged_in') || !in_array(session('admin_role'), [1, 2])) {
|
||||
abort(403, 'Acceso denegado');
|
||||
}
|
||||
}
|
||||
|
||||
private function checkSuperAdmin(Request $request)
|
||||
{
|
||||
if (!session('admin_logged_in') || session('admin_role') != 1) {
|
||||
abort(403, 'Acceso denegado. Solo Súper Administradores.');
|
||||
}
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->checkGeneralAdmin($request);
|
||||
|
||||
$query = Pase::with(['jugador', 'clubOrigen', 'clubDestino']);
|
||||
|
||||
if (session('admin_role') == 2) {
|
||||
$idClub = session('admin_id_club');
|
||||
$query->where(function ($q) use ($idClub) {
|
||||
$q->where('id_club_origen', $idClub)
|
||||
->orWhere('id_club_destino', $idClub);
|
||||
});
|
||||
}
|
||||
|
||||
$pases = $query->orderBy('created_at', 'desc')->paginate(20);
|
||||
return view('admin.pases.index', compact('pases'));
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
$this->checkGeneralAdmin($request);
|
||||
return view('admin.pases.create');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->checkGeneralAdmin($request);
|
||||
|
||||
$request->validate([
|
||||
'documento' => 'required|string|exists:jugadores,documento'
|
||||
], [
|
||||
'documento.exists' => 'No se encontró un jugador con ese DNI.'
|
||||
]);
|
||||
|
||||
$jugador = Jugador::where('documento', $request->documento)->first();
|
||||
|
||||
// Si el jugador ya pertenece al club destino, error
|
||||
$idClubDestino = session('admin_role') == 2 ? session('admin_id_club') : $request->input('id_club_destino');
|
||||
|
||||
if (!$idClubDestino) {
|
||||
return back()->withErrors(['id_club_destino' => 'Debe especificar el club destino.']);
|
||||
}
|
||||
|
||||
if ($jugador->id_club_actual == $idClubDestino) {
|
||||
return back()->withErrors(['documento' => 'Este jugador ya pertenece a tu club.']);
|
||||
}
|
||||
|
||||
// Crear la petición de pase
|
||||
Pase::create([
|
||||
'id_jugador' => $jugador->id_jugador,
|
||||
'id_club_origen' => $jugador->id_club_actual,
|
||||
'id_club_destino' => $idClubDestino,
|
||||
'estado' => 'Pendiente'
|
||||
]);
|
||||
|
||||
return redirect()->route('admin.pases.index')->with('admin_msg', 'Solicitud de pase creada correctamente y está pendiente de aprobación.');
|
||||
}
|
||||
|
||||
public function aprobar(Request $request, $id)
|
||||
{
|
||||
$this->checkSuperAdmin($request);
|
||||
$pase = Pase::findOrFail($id);
|
||||
|
||||
if ($pase->estado !== 'Pendiente') {
|
||||
return back()->withErrors(['pase' => 'Este pase ya fue procesado.']);
|
||||
}
|
||||
|
||||
$pase->update(['estado' => 'Aprobado']);
|
||||
|
||||
// Actualizar jugador
|
||||
if ($pase->jugador) {
|
||||
// Desvincular de equipos del club de origen
|
||||
$equiposOldClubIds = $pase->jugador->equipos()
|
||||
->where('id_club', $pase->id_club_origen)
|
||||
->pluck('equipos.id_equipo');
|
||||
|
||||
if ($equiposOldClubIds->count() > 0) {
|
||||
$pase->jugador->equipos()->detach($equiposOldClubIds);
|
||||
}
|
||||
|
||||
$pase->jugador->update([
|
||||
'id_club_origen' => $pase->id_club_origen,
|
||||
'id_club_actual' => $pase->id_club_destino
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.pases.index')->with('admin_msg', 'Pase aprobado correctamente y jugador desvinculado de equipos anteriores.');
|
||||
}
|
||||
|
||||
public function rechazar(Request $request, $id)
|
||||
{
|
||||
$this->checkSuperAdmin($request);
|
||||
$pase = Pase::findOrFail($id);
|
||||
|
||||
if ($pase->estado !== 'Pendiente') {
|
||||
return back()->withErrors(['pase' => 'Este pase ya fue procesado.']);
|
||||
}
|
||||
|
||||
$pase->update(['estado' => 'Rechazado']);
|
||||
|
||||
return redirect()->route('admin.pases.index')->with('admin_msg', 'Pase rechazado.');
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,55 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AdminUser;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AdminUserController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$users = AdminUser::all();
|
||||
return response()->json($users);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$user = AdminUser::findOrFail($id);
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'username' => 'required|string|max:50|unique:admin_users',
|
||||
'password' => 'required|string',
|
||||
'role' => 'nullable|integer',
|
||||
]);
|
||||
$data['password'] = bcrypt($data['password']);
|
||||
$user = AdminUser::create($data);
|
||||
return response()->json($user, 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$user = AdminUser::findOrFail($id);
|
||||
$data = $request->validate([
|
||||
'username' => 'sometimes|string|max:50|unique:admin_users,username,' . $id,
|
||||
'password' => 'sometimes|string',
|
||||
'role' => 'sometimes|integer',
|
||||
]);
|
||||
if (isset($data['password'])) {
|
||||
$data['password'] = bcrypt($data['password']);
|
||||
}
|
||||
$user->update($data);
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$user = AdminUser::findOrFail($id);
|
||||
$user->delete();
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Aficionado;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AficionadoController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$aficionados = Aficionado::all();
|
||||
return response()->json($aficionados);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$aficionado = Aficionado::findOrFail($id);
|
||||
return response()->json($aficionado);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'nombre' => 'required|string|max:100',
|
||||
'apellido' => 'required|string|max:100',
|
||||
'dni' => 'required|string|max:20|unique:aficionados',
|
||||
'fecha_nacimiento' => 'nullable|date',
|
||||
'email' => 'nullable|email|max:150',
|
||||
'telefono' => 'nullable|string|max:50',
|
||||
'localidad' => 'nullable|string|max:100',
|
||||
'password' => 'nullable|string',
|
||||
]);
|
||||
if (isset($data['password'])) {
|
||||
$data['password'] = bcrypt($data['password']);
|
||||
}
|
||||
$aficionado = Aficionado::create($data);
|
||||
return response()->json($aficionado, 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$aficionado = Aficionado::findOrFail($id);
|
||||
$data = $request->validate([
|
||||
'nombre' => 'sometimes|string|max:100',
|
||||
'apellido' => 'sometimes|string|max:100',
|
||||
'dni' => 'sometimes|string|max:20|unique:aficionados,dni,' . $id . ',id_aficionado',
|
||||
'fecha_nacimiento' => 'nullable|date',
|
||||
'email' => 'nullable|email|max:150',
|
||||
'telefono' => 'nullable|string|max:50',
|
||||
'localidad' => 'nullable|string|max:100',
|
||||
'password' => 'nullable|string',
|
||||
]);
|
||||
if (isset($data['password'])) {
|
||||
$data['password'] = bcrypt($data['password']);
|
||||
}
|
||||
$aficionado->update($data);
|
||||
return response()->json($aficionado);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$aficionado = Aficionado::findOrFail($id);
|
||||
$aficionado->delete();
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
@@ -1,411 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use App\Models\Jugador;
|
||||
use App\Models\Aficionado;
|
||||
use App\Models\AdminUser;
|
||||
use App\Models\Club;
|
||||
use App\Mail\WelcomeMail;
|
||||
use App\Mail\ResetPasswordMail;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* Valida el token de Cloudflare Turnstile
|
||||
*/
|
||||
private function verifyTurnstile($token)
|
||||
{
|
||||
if (in_array(config('app.env'), ['local', 'testing']) && $token === '1x00000000000000000000AA') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$token) return false;
|
||||
|
||||
$response = Http::asForm()->post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
|
||||
'secret' => config('services.turnstile.secret_key'),
|
||||
'response' => $token,
|
||||
'remoteip' => request()->ip(),
|
||||
]);
|
||||
|
||||
return $response->successful() && $response->json('success');
|
||||
}
|
||||
|
||||
public function login(Request $request)
|
||||
{
|
||||
$tipo = $request->input('tipo');
|
||||
|
||||
if ($tipo === 'admin') {
|
||||
return $this->loginAdmin($request);
|
||||
}
|
||||
|
||||
return $this->loginPlayer($request);
|
||||
}
|
||||
|
||||
public function loginPlayer(Request $request)
|
||||
{
|
||||
if (!$this->verifyTurnstile($request->input('cf-turnstile-response'))) {
|
||||
return back()->with('login_error', 'Error de verificación de seguridad (Turnstile).')->with('login_tab', 'player');
|
||||
}
|
||||
|
||||
$dni = $request->input('dni');
|
||||
$password = $request->input('password');
|
||||
|
||||
$jugador = Jugador::where('documento', $dni)->where('activo', true)->first();
|
||||
|
||||
if ($jugador && $jugador->password && Hash::check($password, $jugador->password)) {
|
||||
$request->session()->put('user_logged_in', true);
|
||||
$request->session()->put('user_tipo', 'jugador');
|
||||
$request->session()->put('user_id', $jugador->id_jugador);
|
||||
$request->session()->put('user_name', $jugador->nombre . ' ' . $jugador->apellido);
|
||||
$request->session()->put('user_documento', $jugador->documento);
|
||||
$request->session()->put('user_ultimo_acceso', time());
|
||||
|
||||
return redirect()->intended('/');
|
||||
}
|
||||
|
||||
$aficionado = Aficionado::where('dni', $dni)->first();
|
||||
|
||||
if ($aficionado && $aficionado->password && Hash::check($password, $aficionado->password)) {
|
||||
$request->session()->put('user_logged_in', true);
|
||||
$request->session()->put('user_tipo', 'aficionado');
|
||||
$request->session()->put('user_id', $aficionado->id_aficionado);
|
||||
$request->session()->put('user_name', $aficionado->nombre . ' ' . $aficionado->apellido);
|
||||
$request->session()->put('user_documento', $aficionado->dni);
|
||||
$request->session()->put('user_ultimo_acceso', time());
|
||||
|
||||
return redirect()->intended('/');
|
||||
}
|
||||
|
||||
return back()->with('login_error', 'DNI o contraseña incorrectos')->with('login_tab', 'player');
|
||||
}
|
||||
|
||||
public function loginAdmin(Request $request)
|
||||
{
|
||||
if (!$this->verifyTurnstile($request->input('cf-turnstile-response'))) {
|
||||
return back()->with('login_error', 'Error de verificación de seguridad (Turnstile).')->with('login_tab', 'admin');
|
||||
}
|
||||
|
||||
$username = $request->input('username');
|
||||
$password = $request->input('password');
|
||||
|
||||
$admin = AdminUser::whereRaw('BINARY `username` = ?', [$username])->first();
|
||||
|
||||
if ($admin && Hash::check($password, $admin->password)) {
|
||||
$request->session()->put('admin_logged_in', true);
|
||||
$request->session()->put('admin_id', $admin->id);
|
||||
$request->session()->put('admin_username', $admin->username);
|
||||
$request->session()->put('admin_role', $admin->role);
|
||||
$request->session()->put('admin_id_club', $admin->id_club);
|
||||
|
||||
if ($admin->id_club && $admin->club) {
|
||||
$request->session()->put('admin_club_nombre', $admin->club->nombre);
|
||||
}
|
||||
|
||||
$request->session()->put('ultimo_acceso', time());
|
||||
|
||||
return redirect()->intended('/');
|
||||
}
|
||||
|
||||
return back()->with('login_error', 'Usuario o contraseña incorrectos')->with('login_tab', 'admin');
|
||||
}
|
||||
|
||||
public function logout(Request $request)
|
||||
{
|
||||
$isAdmin = $request->session()->get('admin_logged_in');
|
||||
|
||||
if ($isAdmin) {
|
||||
$request->session()->forget(['admin_logged_in', 'admin_id', 'admin_username', 'admin_role', 'ultimo_acceso']);
|
||||
$msg = 'Sesión de administrador cerrada correctamente.';
|
||||
} else {
|
||||
$request->session()->forget(['user_logged_in', 'user_tipo', 'user_id', 'user_name', 'user_documento', 'user_ultimo_acceso']);
|
||||
$msg = 'Sesión cerrada correctamente.';
|
||||
}
|
||||
|
||||
return redirect('/?logout_msg=' . urlencode($msg));
|
||||
}
|
||||
|
||||
public function showLoginForm()
|
||||
{
|
||||
return view('welcome');
|
||||
}
|
||||
|
||||
public function recuperar(Request $request)
|
||||
{
|
||||
if (!$this->verifyTurnstile($request->input('cf-turnstile-response'))) {
|
||||
return back()->with('mensaje', '⚠️ Error de verificación de seguridad (Captcha).');
|
||||
}
|
||||
|
||||
$dni = trim($request->input('dni'));
|
||||
$email = trim($request->input('email'));
|
||||
|
||||
if (empty($dni) || empty($email)) {
|
||||
return back()->with('mensaje', 'Debes ingresar tu DNI y correo electrónico.');
|
||||
}
|
||||
|
||||
$jugador = Jugador::where('documento', $dni)->where('email', $email)->first();
|
||||
$aficionado = Aficionado::where('dni', $dni)->where('email', $email)->first();
|
||||
|
||||
$usuario = $jugador ?: $aficionado;
|
||||
|
||||
if (!$usuario) {
|
||||
return back()->with('mensaje', 'No se encontró un usuario con ese DNI y correo.');
|
||||
}
|
||||
|
||||
$token = bin2hex(random_bytes(16));
|
||||
$expires = now()->addHour();
|
||||
|
||||
if ($jugador) {
|
||||
$jugador->update([
|
||||
'reset_token' => $token,
|
||||
'reset_expira' => $expires
|
||||
]);
|
||||
} else {
|
||||
$aficionado->update([
|
||||
'reset_token' => $token,
|
||||
'reset_expira' => $expires
|
||||
]);
|
||||
}
|
||||
try {
|
||||
Mail::to($usuario->email)->send(new ResetPasswordMail($usuario, $token));
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Error enviando mail de recuperación: " . $e->getMessage());
|
||||
}
|
||||
|
||||
return back()->with('mensaje', '📩 Te enviamos un correo con las instrucciones para recuperar tu contraseña.');
|
||||
}
|
||||
|
||||
public function resetPasswordForm($token)
|
||||
{
|
||||
// Verificar que el token exista y no esté expirado
|
||||
$jugador = Jugador::where('reset_token', $token)->where('reset_expira', '>', now())->first();
|
||||
$aficionado = Aficionado::where('reset_token', $token)->where('reset_expira', '>', now())->first();
|
||||
|
||||
if (!$jugador && !$aficionado) {
|
||||
return redirect()->route('recuperar')->with('mensaje', '❌ El enlace es inválido o ya expiró. Solicitá uno nuevo.');
|
||||
}
|
||||
|
||||
return view('auth.reset_password', compact('token'));
|
||||
}
|
||||
|
||||
public function resetPassword(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'token' => 'required|string',
|
||||
'password' => 'required|confirmed|min:6',
|
||||
]);
|
||||
|
||||
$token = $request->input('token');
|
||||
|
||||
$jugador = Jugador::where('reset_token', $token)->where('reset_expira', '>', now())->first();
|
||||
$aficionado = Aficionado::where('reset_token', $token)->where('reset_expira', '>', now())->first();
|
||||
|
||||
$usuario = $jugador ?: $aficionado;
|
||||
|
||||
if (!$usuario) {
|
||||
return redirect()->route('recuperar')->with('mensaje', '❌ El enlace es inválido o ya expiró. Solicitá uno nuevo.');
|
||||
}
|
||||
|
||||
$usuario->update([
|
||||
'password' => bcrypt($request->input('password')),
|
||||
'reset_token' => null,
|
||||
'reset_expira' => null,
|
||||
]);
|
||||
|
||||
return redirect('/')->with('login_success', '✅ Contraseña cambiada correctamente. Ya podés iniciar sesión.');
|
||||
}
|
||||
|
||||
public function registroAficionado(Request $request)
|
||||
{
|
||||
if (!$this->verifyTurnstile($request->input('cf-turnstile-response'))) {
|
||||
return back()->with('registro_msg', '⚠️ Error de verificación de seguridad (Captcha).')->withInput();
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'nombre' => 'required|string|max:100',
|
||||
'apellido' => 'required|string|max:100',
|
||||
'dni' => 'required|string|unique:aficionados,dni',
|
||||
'email' => 'required|email|unique:aficionados,email',
|
||||
'fecha_nacimiento' => 'nullable|date',
|
||||
'telefono' => 'nullable|string|max:50',
|
||||
'localidad' => 'nullable|string|max:100',
|
||||
'password' => 'required|confirmed|min:6',
|
||||
]);
|
||||
|
||||
$data['password'] = bcrypt($data['password']);
|
||||
$data['fecha_registro'] = now();
|
||||
|
||||
$aficionado = Aficionado::create($data);
|
||||
|
||||
try {
|
||||
Mail::to($aficionado->email)->send(new WelcomeMail($aficionado, 'aficionado'));
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Error enviando mail de bienvenida a Aficionado: " . $e->getMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('asociate')->with('mensaje', '✅ Te registraste correctamente. Ya podés iniciar sesión.');
|
||||
}
|
||||
|
||||
public function buscarJugador(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'nombre' => 'required|string',
|
||||
'apellido' => 'required|string',
|
||||
'dni' => 'required|string',
|
||||
'acepto' => 'required',
|
||||
]);
|
||||
|
||||
$dni = preg_replace('/[^0-9]/', '', $request->input('dni'));
|
||||
$nombre = strtoupper(trim($request->input('nombre')));
|
||||
$apellido = strtoupper(trim($request->input('apellido')));
|
||||
|
||||
// Buscar jugador por DNI
|
||||
$jugador = Jugador::where('documento', $dni)->first();
|
||||
|
||||
if (!$jugador) {
|
||||
// Verificar si ya está registrado como aficionado
|
||||
$aficionado = Aficionado::where('dni', $dni)->first();
|
||||
if ($aficionado) {
|
||||
return redirect()->route('asociate')->with('mensaje', '⚠️ Ya estás registrado como aficionado.');
|
||||
}
|
||||
// No existe - se debe registrar como aficionado
|
||||
return redirect()->route('asociate')->with('mensaje', '⚠️ No encontramos tu registro como jugador. Podés registrarte como aficionado.');
|
||||
}
|
||||
|
||||
// --- Lógica Smart Match ---
|
||||
$nombreEnBD = $this->normalizeString($jugador->nombre ?? '');
|
||||
$apellidoEnBD = $this->normalizeString($jugador->apellido ?? '');
|
||||
$terminosBD = explode(' ', $nombreEnBD . ' ' . $apellidoEnBD);
|
||||
|
||||
$palabrasNombreIn = explode(' ', $this->normalizeString($nombre));
|
||||
$palabrasApellidoIn = explode(' ', $this->normalizeString($apellido));
|
||||
|
||||
$nombreCoincide = false;
|
||||
foreach ($palabrasNombreIn as $p) {
|
||||
if ($this->isApproxMatch($p, $terminosBD)) {
|
||||
$nombreCoincide = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$apellidoCoincide = false;
|
||||
foreach ($palabrasApellidoIn as $p) {
|
||||
if ($this->isApproxMatch($p, $terminosBD)) {
|
||||
$apellidoCoincide = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$nombreCoincide || !$apellidoCoincide) {
|
||||
return redirect()->route('asociate')->with('mensaje', '⚠️ El DNI ingresado no coincide con el Nombre y Apellido proporcionados. Por favor, verifica tus datos.');
|
||||
}
|
||||
// --- Fin Smart Match ---
|
||||
|
||||
if ($jugador->activo) {
|
||||
return redirect()->route('asociate')->with('registro_msg', 'Este jugador ya está registrado en el sistema.');
|
||||
}
|
||||
|
||||
// Jugador encontrado e inactivo - mostrar formulario para completar
|
||||
$club = null;
|
||||
if ($jugador->id_club_actual) {
|
||||
$clubObj = \App\Models\Club::find($jugador->id_club_actual);
|
||||
$club = $clubObj ? $clubObj->nombre : null;
|
||||
}
|
||||
|
||||
$jugador_encontrado = [
|
||||
'documento' => $jugador->documento,
|
||||
'nombre' => $jugador->nombre,
|
||||
'apellido' => $jugador->apellido,
|
||||
'fecha_nacimiento' => $jugador->fecha_nacimiento,
|
||||
'club' => $club,
|
||||
'categoria' => $jugador->categoria,
|
||||
];
|
||||
|
||||
return view('auth.asociate', compact('jugador_encontrado'))->with('tab', 'jugador');
|
||||
}
|
||||
|
||||
public function completarRegistroJugador(Request $request)
|
||||
{
|
||||
if (!$this->verifyTurnstile($request->input('cf-turnstile-response'))) {
|
||||
return back()->with('registro_msg', '⚠️ Error de verificación de seguridad (Captcha).')->withInput();
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'dni' => 'required|string',
|
||||
'email' => 'required|email',
|
||||
'telefono' => 'nullable|string',
|
||||
'password' => 'required|confirmed|min:6',
|
||||
]);
|
||||
|
||||
$dni = preg_replace('/[^0-9]/', '', $request->input('dni'));
|
||||
|
||||
$jugador = Jugador::where('documento', $dni)->first();
|
||||
|
||||
if (!$jugador) {
|
||||
return redirect()->route('asociate')->with('registro_msg', 'Jugador no encontrado.');
|
||||
}
|
||||
|
||||
if ($jugador->activo) {
|
||||
return redirect()->route('asociate')->with('registro_msg', 'Este jugador ya está registrado.');
|
||||
}
|
||||
|
||||
$jugador->update([
|
||||
'email' => $request->input('email'),
|
||||
'telefono' => $request->input('telefono'),
|
||||
'password' => bcrypt($request->input('password')),
|
||||
'activo' => 1,
|
||||
'fecha_registro' => now(),
|
||||
]);
|
||||
|
||||
try {
|
||||
Mail::to($jugador->email)->send(new WelcomeMail($jugador, 'jugador'));
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Error enviando mail de bienvenida a Jugador: " . $e->getMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('asociate')->with('mensaje', '✅ Registro completado exitosamente. Ya podés iniciar sesión.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normaliza un string para comparaciones (mayúsculas, sin acentos, sin espacios extras)
|
||||
*/
|
||||
private function normalizeString($str)
|
||||
{
|
||||
$unwanted_array = ['Š'=>'S', 'š'=>'s', 'Ž'=>'Z', 'ž'=>'z', 'À'=>'A', 'Á'=>'A', 'Â'=>'A', 'Ã'=>'A', 'Ä'=>'A', 'Å'=>'A', 'Æ'=>'A', 'Ç'=>'C', 'È'=>'E', 'É'=>'E',
|
||||
'Ê'=>'E', 'Ë'=>'E', 'Ì'=>'I', 'Í'=>'I', 'Î'=>'I', 'Ï'=>'I', 'Ñ'=>'N', 'Ò'=>'O', 'Ó'=>'O', 'Ô'=>'O', 'Õ'=>'O', 'Ö'=>'O', 'Ø'=>'O', 'Ù'=>'U',
|
||||
'Ú'=>'U', 'Û'=>'U', 'Ü'=>'U', 'Ý'=>'Y', 'Þ'=>'B', 'ß'=>'Ss', 'à'=>'a', 'á'=>'a', 'â'=>'a', 'ã'=>'a', 'ä'=>'a', 'å'=>'a', 'æ'=>'a', 'ç'=>'c',
|
||||
'è'=>'e', 'é'=>'e', 'ê'=>'e', 'ë'=>'e', 'ì'=>'i', 'í'=>'i', 'î'=>'i', 'ï'=>'i', 'ð'=>'o', 'ñ'=>'n', 'ò'=>'o', 'ó'=>'o', 'ô'=>'o', 'õ'=>'o',
|
||||
'ö'=>'o', 'ø'=>'o', 'ù'=>'u', 'ú'=>'u', 'û'=>'u', 'ý'=>'y', 'þ'=>'b', 'ÿ'=>'y'];
|
||||
|
||||
$str = strtr($str, $unwanted_array);
|
||||
return strtoupper(trim(preg_replace('/\s+/', ' ', $str)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Comprueba si una palabra coincide aproximadamente con alguna de la lista
|
||||
*/
|
||||
private function isApproxMatch($word, $list)
|
||||
{
|
||||
if (strlen($word) < 3) return false;
|
||||
|
||||
foreach ($list as $item) {
|
||||
if (strlen($item) < 3) continue;
|
||||
|
||||
// Coincidencia exacta o contenida
|
||||
if ($word === $item || strpos($item, $word) !== false || strpos($word, $item) !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Levenshtein para errores de tipeo (máximo 1 de distancia)
|
||||
if (levenshtein($word, $item) <= 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Club;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ClubController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$clubes = Club::with('equipos')->get();
|
||||
return response()->json($clubes);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$club = Club::with('equipos', 'jugadores')->findOrFail($id);
|
||||
return response()->json($club);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'nombre' => 'required|string|max:100',
|
||||
]);
|
||||
$club = Club::create($data);
|
||||
return response()->json($club, 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$club = Club::findOrFail($id);
|
||||
$data = $request->validate([
|
||||
'nombre' => 'sometimes|string|max:100',
|
||||
]);
|
||||
$club->update($data);
|
||||
return response()->json($club);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$club = Club::findOrFail($id);
|
||||
$club->delete();
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
|
||||
class DocumentacionController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$path = base_path('misc/MANUAL_USUARIO.md');
|
||||
$content = "No se encontró el manual.";
|
||||
|
||||
if (File::exists($path)) {
|
||||
$md = File::get($path);
|
||||
|
||||
// Detectar rol de la sesión
|
||||
$role = $this->getUserRole();
|
||||
|
||||
// Segmentar contenido
|
||||
$filteredMd = $this->segmentMarkdown($md, $role);
|
||||
|
||||
$parsedown = new \Parsedown();
|
||||
$content = $parsedown->text($filteredMd);
|
||||
}
|
||||
|
||||
return view('documentacion.index', compact('content'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detecta el rol del usuario basado en las variables de sesión
|
||||
*/
|
||||
private function getUserRole()
|
||||
{
|
||||
if (session()->get('admin_logged_in')) {
|
||||
$adminRole = session()->get('admin_role');
|
||||
return ($adminRole == 1) ? 'superadmin' : 'admin_club';
|
||||
}
|
||||
|
||||
if (session()->get('user_logged_in')) {
|
||||
return session()->get('user_tipo'); // 'jugador' o 'aficionado'
|
||||
}
|
||||
|
||||
return 'visitante';
|
||||
}
|
||||
|
||||
/**
|
||||
* Segmenta y filtra el Markdown según el rol
|
||||
*/
|
||||
private function segmentMarkdown($md, $role)
|
||||
{
|
||||
$markers = [
|
||||
'cap1' => '<a name="cap1"></a>',
|
||||
'cap2' => '<a name="cap2"></a>',
|
||||
'cap3' => '<a name="cap3"></a>',
|
||||
'cap4' => '<a name="cap4"></a>',
|
||||
'cap5' => '<a name="cap5"></a>',
|
||||
'faq' => '## ❓ Preguntas Frecuentes'
|
||||
];
|
||||
|
||||
// Encontrar posiciones de los marcadores
|
||||
$positions = [];
|
||||
foreach ($markers as $key => $marker) {
|
||||
$pos = strpos($md, $marker);
|
||||
if ($pos !== false) {
|
||||
$positions[$key] = $pos;
|
||||
}
|
||||
}
|
||||
asort($positions);
|
||||
|
||||
$keys = array_keys($positions);
|
||||
$sections = [];
|
||||
|
||||
// Intro (antes del primer capítulo)
|
||||
$sections['intro'] = substr($md, 0, $positions[$keys[0]]);
|
||||
|
||||
// Capítulos y FAQ
|
||||
for ($i = 0; $i < count($keys); $i++) {
|
||||
$start = $positions[$keys[$i]];
|
||||
$end = isset($keys[$i+1]) ? $positions[$keys[$i+1]] : strlen($md);
|
||||
$sections[$keys[$i]] = substr($md, $start, $end - $start);
|
||||
}
|
||||
|
||||
// Determinar qué capítulos mostrar
|
||||
$allowedChapters = [1]; // Todos ven el Cap 1
|
||||
$showSections = ['intro', 'cap1', 'faq'];
|
||||
|
||||
switch ($role) {
|
||||
case 'superadmin':
|
||||
$allowedChapters = [1, 2, 3, 4, 5];
|
||||
$showSections = ['intro', 'cap1', 'cap2', 'cap3', 'cap4', 'cap5', 'faq'];
|
||||
break;
|
||||
case 'admin_club':
|
||||
$allowedChapters = [1, 4];
|
||||
$showSections = ['intro', 'cap1', 'cap4', 'faq'];
|
||||
break;
|
||||
case 'jugador':
|
||||
$allowedChapters = [1, 2];
|
||||
$showSections = ['intro', 'cap1', 'cap2', 'faq'];
|
||||
break;
|
||||
case 'aficionado':
|
||||
$allowedChapters = [1, 3];
|
||||
$showSections = ['intro', 'cap1', 'cap3', 'faq'];
|
||||
break;
|
||||
default: // visitante
|
||||
$allowedChapters = [1];
|
||||
$showSections = ['intro', 'cap1', 'faq'];
|
||||
break;
|
||||
}
|
||||
|
||||
// Filtrar Tabla de Contenidos en la Intro
|
||||
$sections['intro'] = $this->filterTOC($sections['intro'], $allowedChapters);
|
||||
|
||||
// Unir secciones seleccionadas
|
||||
$finalMd = "";
|
||||
foreach ($showSections as $s) {
|
||||
if (isset($sections[$s])) {
|
||||
$finalMd .= $sections[$s] . "\n\n---\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $finalMd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtra la tabla de contenidos para mostrar solo los capítulos permitidos
|
||||
*/
|
||||
private function filterTOC($intro, $allowedChapters)
|
||||
{
|
||||
$lines = explode("\n", $intro);
|
||||
$filteredLines = [];
|
||||
$inTable = false;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if (strpos($line, '| Capítulo | Perfil |') !== false) {
|
||||
$inTable = true;
|
||||
$filteredLines[] = $line;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($inTable) {
|
||||
if (trim($line) === '' || (strpos($line, '|') === false && trim($line) !== '')) {
|
||||
$inTable = false;
|
||||
$filteredLines[] = $line;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strpos($line, '|---|') !== false) {
|
||||
$filteredLines[] = $line;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filtrar fila de la tabla
|
||||
$matched = false;
|
||||
foreach ($allowedChapters as $cap) {
|
||||
if (strpos($line, "[Capítulo $cap]") !== false) {
|
||||
$matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($matched) {
|
||||
$filteredLines[] = $line;
|
||||
}
|
||||
} else {
|
||||
$filteredLines[] = $line;
|
||||
}
|
||||
}
|
||||
return implode("\n", $filteredLines);
|
||||
}
|
||||
|
||||
public function download()
|
||||
{
|
||||
$path = base_path('misc/MANUAL_USUARIO.md');
|
||||
|
||||
if (!File::exists($path)) {
|
||||
abort(404, 'No se pudo generar el manual porque el archivo base no existe.');
|
||||
}
|
||||
|
||||
$md = File::get($path);
|
||||
|
||||
// Detectar rol de la sesión
|
||||
$role = $this->getUserRole();
|
||||
|
||||
// Segmentar contenido
|
||||
$filteredMd = $this->segmentMarkdown($md, $role);
|
||||
|
||||
// Convertir Markdown a HTML
|
||||
$parsedown = new \Parsedown();
|
||||
$htmlContent = $parsedown->text($filteredMd);
|
||||
|
||||
// Preparar Logo en Base64 para el PDF
|
||||
$logoBase64 = null;
|
||||
$logoPath = public_path('logo.png');
|
||||
if (File::exists($logoPath)) {
|
||||
$type = pathinfo($logoPath, PATHINFO_EXTENSION);
|
||||
$data = File::get($logoPath);
|
||||
$logoBase64 = 'data:image/' . $type . ';base64,' . base64_encode($data);
|
||||
}
|
||||
|
||||
// Depuración temporal: Descomenta si querés ver qué rol detecta antes de generar el PDF
|
||||
// dd('Rol detectado: ' . $role);
|
||||
|
||||
// Generar PDF con dompdf
|
||||
$pdf = Pdf::loadView('documentacion.pdf', [
|
||||
'content' => $htmlContent,
|
||||
'logo' => $logoBase64
|
||||
]);
|
||||
|
||||
// Ajustar papel y orientación
|
||||
$pdf->setPaper('A4', 'portrait');
|
||||
|
||||
// Nombre de archivo único para evitar caché de navegador
|
||||
$filename = 'Manual_Segmentado_' . $role . '_' . time() . '.pdf';
|
||||
|
||||
return $pdf->download($filename);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Equipo;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EquipoController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$equipos = Equipo::with('club', 'jugadores')->get();
|
||||
return response()->json($equipos);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$equipo = Equipo::with('club', 'jugadores')->findOrFail($id);
|
||||
return response()->json($equipo);
|
||||
}
|
||||
|
||||
public function publicShow($id)
|
||||
{
|
||||
$equipo = Equipo::with(['club', 'jugadores'])->findOrFail($id);
|
||||
return view('equipos.show', compact('equipo'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'id_club' => 'required|integer|exists:clubes,id_club',
|
||||
'categoria' => 'required|string|max:20',
|
||||
'division' => 'nullable|string|max:5',
|
||||
]);
|
||||
$equipo = Equipo::create($data);
|
||||
return response()->json($equipo, 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$equipo = Equipo::findOrFail($id);
|
||||
$data = $request->validate([
|
||||
'id_club' => 'sometimes|integer|exists:clubes,id_club',
|
||||
'categoria' => 'sometimes|string|max:20',
|
||||
'division' => 'nullable|string|max:5',
|
||||
]);
|
||||
$equipo->update($data);
|
||||
return response()->json($equipo);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$equipo = Equipo::findOrFail($id);
|
||||
$equipo->delete();
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Evento;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EventoController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$fechaStr = $request->get('fecha', now()->toDateString());
|
||||
$fechaSeleccionada = Carbon::parse($fechaStr);
|
||||
|
||||
// Generar rango de fechas (Ayer, Hoy, Mañana + otros)
|
||||
$fechasNav = [];
|
||||
for ($i = -3; $i <= 3; $i++) {
|
||||
$d = now()->addDays($i);
|
||||
$fechasNav[] = [
|
||||
'label' => $this->getFechaLabel($d),
|
||||
'fecha' => $d->toDateString(),
|
||||
'active' => $d->isSameDay($fechaSeleccionada)
|
||||
];
|
||||
}
|
||||
|
||||
$eventos = Evento::with(['equipoLocal.club', 'equipoVisitante.club'])
|
||||
->whereDate('fecha_evento', $fechaSeleccionada->toDateString())
|
||||
->orderBy('hora_inicio', 'asc')
|
||||
->get()
|
||||
->map(function ($e) {
|
||||
$e->estado = $this->calcularEstado($e->fecha_evento, $e->hora_inicio, $e->hora_fin);
|
||||
return $e;
|
||||
});
|
||||
|
||||
return view('eventos.index', compact('eventos', 'fechasNav', 'fechaSeleccionada'));
|
||||
}
|
||||
|
||||
private function getFechaLabel(Carbon $date)
|
||||
{
|
||||
$fecha = $date->format('d/m');
|
||||
if ($date->isToday()) return "HOY $fecha";
|
||||
if ($date->isYesterday()) return "AYER $fecha";
|
||||
if ($date->isTomorrow()) return "MAÑANA $fecha";
|
||||
|
||||
return $date->translatedFormat('D d/m');
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$evento = Evento::with(['equipoLocal.club', 'equipoVisitante.club', 'qrCodes'])
|
||||
->findOrFail($id);
|
||||
|
||||
$evento->estado = $this->calcularEstado($evento->fecha_evento, $evento->hora_inicio, $evento->hora_fin);
|
||||
|
||||
$isAdmin = session()->has('admin_logged_in') && session('admin_logged_in');
|
||||
$isUser = session()->has('user_logged_in') && session('user_logged_in');
|
||||
|
||||
return view('eventos.show', compact('evento', 'isAdmin', 'isUser'));
|
||||
}
|
||||
|
||||
private function calcularEstado($fechaEvento, $horaInicio, $horaFin)
|
||||
{
|
||||
$tz = new \DateTimeZone(config('app.timezone', 'America/Argentina/Buenos_Aires'));
|
||||
|
||||
// Asegurarnos de tener strings limpios (Y-m-d y H:i:s)
|
||||
$f = ($fechaEvento instanceof \Carbon\Carbon) ? $fechaEvento->format('Y-m-d') : substr($fechaEvento, 0, 10);
|
||||
$h1 = ($horaInicio instanceof \Carbon\Carbon) ? $horaInicio->format('H:i:s') : $horaInicio;
|
||||
$h2 = ($horaFin instanceof \Carbon\Carbon) ? $horaFin->format('H:i:s') : $horaFin;
|
||||
|
||||
$inicio = new \DateTime("$f $h1", $tz);
|
||||
$fin = new \DateTime("$f $h2", $tz);
|
||||
|
||||
if ($fin <= $inicio) {
|
||||
$fin->modify('+1 day');
|
||||
}
|
||||
|
||||
$ahora = new \DateTime('now', $tz);
|
||||
|
||||
if ($ahora >= $inicio && $ahora <= $fin) return 'Activo';
|
||||
if ($ahora < $inicio) return 'Próximo';
|
||||
return 'Finalizado';
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AgentThread;
|
||||
use App\Services\GeniusAgentService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class GeniusAgentController extends Controller
|
||||
{
|
||||
public function __construct(private GeniusAgentService $service) {}
|
||||
|
||||
public function chat(Request $request): JsonResponse
|
||||
{
|
||||
set_time_limit(120);
|
||||
|
||||
$request->validate([
|
||||
'message' => ['required', 'string', 'max:1000'],
|
||||
'thread_id' => ['nullable', 'string', 'uuid'],
|
||||
]);
|
||||
|
||||
$message = $request->string('message')->trim()->toString();
|
||||
|
||||
if (session('admin_logged_in')) {
|
||||
return $this->handleAdmin($message, $request->input('thread_id'));
|
||||
}
|
||||
|
||||
return $this->handlePublic($request, $message);
|
||||
}
|
||||
|
||||
private function handleAdmin(string $message, ?string $threadId): JsonResponse
|
||||
{
|
||||
$adminId = (int) session('admin_id');
|
||||
$isSuperadmin = (int) session('admin_role') === 1;
|
||||
$thread = AgentThread::findOrCreateForAdmin($threadId, $adminId);
|
||||
|
||||
$reply = $this->service->chatAdmin($message, $thread, $isSuperadmin);
|
||||
|
||||
return response()->json([
|
||||
'reply' => $reply,
|
||||
'thread_id' => $thread->thread_id,
|
||||
]);
|
||||
}
|
||||
|
||||
private function handlePublic(Request $request, string $message): JsonResponse
|
||||
{
|
||||
$maxMessages = (int) config('services.genius.max_messages_per_session', 20);
|
||||
$windowMin = (int) config('services.genius.session_window_minutes', 60);
|
||||
|
||||
$session = $request->session();
|
||||
$startedAt = $session->get('agent_window_started_at');
|
||||
$count = (int) $session->get('agent_window_count', 0);
|
||||
|
||||
if ($startedAt === null || (time() - (int) $startedAt) > $windowMin * 60) {
|
||||
$startedAt = time();
|
||||
$count = 0;
|
||||
}
|
||||
|
||||
if ($maxMessages > 0 && $count >= $maxMessages) {
|
||||
$remaining = max(1, (int) ceil(($windowMin * 60 - (time() - (int) $startedAt)) / 60));
|
||||
return response()->json([
|
||||
'reply' => "Llegaste al límite de {$maxMessages} consultas por sesión. "
|
||||
. "Volvé a intentar en {$remaining} minuto(s) o contactá directamente a OnAPB.",
|
||||
'limit_reached' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
$history = $session->get('agent_messages', []);
|
||||
$reply = $this->service->chatPublic($message, $history);
|
||||
|
||||
$history[] = ['role' => 'user', 'content' => $message];
|
||||
$history[] = ['role' => 'assistant', 'content' => $reply];
|
||||
|
||||
$session->put('agent_messages', $history);
|
||||
$session->put('agent_window_started_at', $startedAt);
|
||||
$session->put('agent_window_count', $count + 1);
|
||||
|
||||
return response()->json(['reply' => $reply]);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Noticia;
|
||||
use App\Models\Torneo;
|
||||
use App\Models\Evento;
|
||||
use App\Models\Promocion;
|
||||
use App\Models\CarouselItem;
|
||||
use App\Services\TournamentService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$eventos = $this->getEventos();
|
||||
$promociones = Promocion::whereNotNull('descripcion')
|
||||
->where('descripcion', '!=', '')
|
||||
->orderBy('id', 'desc')
|
||||
->get();
|
||||
|
||||
$carouselItems = CarouselItem::where('activo', true)
|
||||
->orderBy('orden', 'asc')
|
||||
->get();
|
||||
|
||||
$noticias = Noticia::orderBy('id', 'desc')
|
||||
->take(3)
|
||||
->get();
|
||||
|
||||
$torneos = Torneo::orderBy('id', 'desc')
|
||||
->get();
|
||||
|
||||
$tournamentService = new TournamentService();
|
||||
$standingsData = [];
|
||||
foreach ($torneos as $t) {
|
||||
$standingsData[$t->id] = $tournamentService->getStandings($t->id);
|
||||
}
|
||||
|
||||
return view('welcome', compact('eventos', 'promociones', 'carouselItems', 'noticias', 'torneos', 'standingsData'));
|
||||
}
|
||||
|
||||
private function getEventos()
|
||||
{
|
||||
$eventos = Evento::with(['equipoLocal.club', 'equipoVisitante.club'])
|
||||
->orderBy('fecha_evento', 'asc')
|
||||
->orderBy('hora_inicio', 'asc')
|
||||
->get()
|
||||
->map(function ($e) {
|
||||
$estado = $this->calcularEstado($e->fecha_evento, $e->hora_inicio, $e->hora_fin);
|
||||
if (in_array($estado, ['Activo', 'Próximo'])) {
|
||||
$e->estado = $estado;
|
||||
return $e;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
->filter()
|
||||
->values();
|
||||
|
||||
return $eventos;
|
||||
}
|
||||
|
||||
private function calcularEstado($fechaEvento, $horaInicio, $horaFin)
|
||||
{
|
||||
$tz = new \DateTimeZone(config('app.timezone', 'America/Argentina/Buenos_Aires'));
|
||||
|
||||
// Asegurarnos de tener strings limpios (Y-m-d y H:i:s)
|
||||
$f = ($fechaEvento instanceof \Carbon\Carbon) ? $fechaEvento->format('Y-m-d') : substr($fechaEvento, 0, 10);
|
||||
$h1 = ($horaInicio instanceof \Carbon\Carbon) ? $horaInicio->format('H:i:s') : $horaInicio;
|
||||
$h2 = ($horaFin instanceof \Carbon\Carbon) ? $horaFin->format('H:i:s') : $horaFin;
|
||||
|
||||
$inicio = new \DateTime("$f $h1", $tz);
|
||||
$fin = new \DateTime("$f $h2", $tz);
|
||||
|
||||
if ($fin <= $inicio) {
|
||||
$fin->modify('+1 day');
|
||||
}
|
||||
|
||||
$ahora = new \DateTime('now', $tz);
|
||||
|
||||
if ($ahora >= $inicio && $ahora <= $fin) return 'Activo';
|
||||
if ($ahora < $inicio) return 'Próximo';
|
||||
return 'Finalizado';
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Jugador;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class JugadorController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$jugadores = Jugador::with('clubActual', 'clubOrigen', 'equipos')->get();
|
||||
return response()->json($jugadores);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$jugador = Jugador::with('clubActual', 'clubOrigen', 'equipos')->findOrFail($id);
|
||||
return response()->json($jugador);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'id_jugador' => 'nullable|string|max:6',
|
||||
'documento' => 'required|string|max:20|unique:jugadores',
|
||||
'nombre' => 'required|string|max:100',
|
||||
'apellido' => 'required|string|max:100',
|
||||
'fecha_nacimiento' => 'nullable|date',
|
||||
'edad' => 'nullable|integer',
|
||||
'categoria' => 'nullable|string|max:20',
|
||||
'id_club_actual' => 'nullable|integer|exists:clubes,id_club',
|
||||
'id_club_origen' => 'nullable|integer|exists:clubes,id_club',
|
||||
'activo' => 'nullable|boolean',
|
||||
'email' => 'nullable|email|max:150',
|
||||
'telefono' => 'nullable|string|max:50',
|
||||
'password' => 'nullable|string',
|
||||
]);
|
||||
if (isset($data['password'])) {
|
||||
$data['password'] = bcrypt($data['password']);
|
||||
}
|
||||
$jugador = Jugador::create($data);
|
||||
return response()->json($jugador, 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$jugador = Jugador::findOrFail($id);
|
||||
$data = $request->validate([
|
||||
'documento' => 'sometimes|string|max:20|unique:jugadores,documento,' . $id . ',id_jugador',
|
||||
'nombre' => 'sometimes|string|max:100',
|
||||
'apellido' => 'sometimes|string|max:100',
|
||||
'fecha_nacimiento' => 'nullable|date',
|
||||
'edad' => 'nullable|integer',
|
||||
'categoria' => 'nullable|string|max:20',
|
||||
'id_club_actual' => 'nullable|integer|exists:clubes,id_club',
|
||||
'id_club_origen' => 'nullable|integer|exists:clubes,id_club',
|
||||
'activo' => 'nullable|boolean',
|
||||
'email' => 'nullable|email|max:150',
|
||||
'telefono' => 'nullable|string|max:50',
|
||||
'password' => 'nullable|string',
|
||||
]);
|
||||
if (isset($data['password'])) {
|
||||
$data['password'] = bcrypt($data['password']);
|
||||
}
|
||||
$jugador->update($data);
|
||||
return response()->json($jugador);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$jugador = Jugador::findOrFail($id);
|
||||
$jugador->delete();
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\JugadorEquipo;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class JugadorEquipoController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$relaciones = JugadorEquipo::with('jugador', 'equipo')->get();
|
||||
return response()->json($relaciones);
|
||||
}
|
||||
|
||||
public function show($idJugador, $idEquipo)
|
||||
{
|
||||
$relacion = JugadorEquipo::where('id_jugador', $idJugador)
|
||||
->where('id_equipo', $idEquipo)
|
||||
->with('jugador', 'equipo')
|
||||
->firstOrFail();
|
||||
return response()->json($relacion);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'id_jugador' => 'required|string|max:6|exists:jugadores,id_jugador',
|
||||
'id_equipo' => 'required|integer|exists:equipos,id_equipo',
|
||||
'fecha_alta' => 'nullable|date',
|
||||
]);
|
||||
$relacion = JugadorEquipo::create($data);
|
||||
return response()->json($relacion, 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, $idJugador, $idEquipo)
|
||||
{
|
||||
$relacion = JugadorEquipo::where('id_jugador', $idJugador)
|
||||
->where('id_equipo', $idEquipo)
|
||||
->firstOrFail();
|
||||
|
||||
$data = $request->validate([
|
||||
'fecha_alta' => 'nullable|date',
|
||||
]);
|
||||
$relacion->update($data);
|
||||
return response()->json($relacion);
|
||||
}
|
||||
|
||||
public function destroy($idJugador, $idEquipo)
|
||||
{
|
||||
$relacion = JugadorEquipo::where('id_jugador', $idJugador)
|
||||
->where('id_equipo', $idEquipo)
|
||||
->firstOrFail();
|
||||
$relacion->delete();
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
|
||||
public function porJugador($idJugador)
|
||||
{
|
||||
$relaciones = JugadorEquipo::where('id_jugador', $idJugador)
|
||||
->with('equipo')
|
||||
->get();
|
||||
return response()->json($relaciones);
|
||||
}
|
||||
|
||||
public function porEquipo($idEquipo)
|
||||
{
|
||||
$relaciones = JugadorEquipo::where('id_equipo', $idEquipo)
|
||||
->with('jugador')
|
||||
->get();
|
||||
return response()->json($relaciones);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Noticia;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class NoticiaController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$noticias = Noticia::orderBy('fecha', 'desc')->get();
|
||||
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json($noticias);
|
||||
}
|
||||
|
||||
return view('noticias.index', compact('noticias'));
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$noticia = Noticia::findOrFail($id);
|
||||
|
||||
if (request()->expectsJson()) {
|
||||
return response()->json($noticia);
|
||||
}
|
||||
|
||||
return view('noticias.show', compact('noticia'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'titulo' => 'required|string|max:200',
|
||||
'contenido' => 'required',
|
||||
'imagen' => 'nullable|string|max:200',
|
||||
]);
|
||||
$noticia = Noticia::create($data);
|
||||
return response()->json($noticia, 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$noticia = Noticia::findOrFail($id);
|
||||
$data = $request->validate([
|
||||
'titulo' => 'sometimes|string|max:200',
|
||||
'contenido' => 'sometimes',
|
||||
'imagen' => 'nullable|string|max:200',
|
||||
]);
|
||||
$noticia->update($data);
|
||||
return response()->json($noticia);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$noticia = Noticia::findOrFail($id);
|
||||
$noticia->delete();
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\NotificacionService;
|
||||
|
||||
class NotificacionController extends Controller
|
||||
{
|
||||
private NotificacionService $service;
|
||||
|
||||
public function __construct(NotificacionService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
// ── Helper: usuario logueado ──
|
||||
private function getUserSession(): ?array
|
||||
{
|
||||
if (!session()->has('user_logged_in')) return null;
|
||||
return [
|
||||
'tipo' => session('user_tipo'),
|
||||
'id' => session('user_id'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /notificaciones — Listado completo paginado
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$u = $this->getUserSession();
|
||||
if (!$u) return redirect('/')->with('panel_error', 'Debés iniciar sesión.');
|
||||
|
||||
$notificaciones = $this->service->obtenerTodas($u['tipo'], $u['id']);
|
||||
$totalNoLeidas = $this->service->contarNoLeidas($u['tipo'], $u['id']);
|
||||
|
||||
return view('notificaciones.index', compact('notificaciones', 'totalNoLeidas'));
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /notificaciones/{id}/leer — Marcar una como leída
|
||||
*/
|
||||
public function marcarLeida(int $id)
|
||||
{
|
||||
$u = $this->getUserSession();
|
||||
if (!$u) return response()->json(['ok' => false], 401);
|
||||
|
||||
$this->service->marcarLeida($id, $u['tipo'], $u['id']);
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /notificaciones/leer-todas — Marcar todas como leídas
|
||||
*/
|
||||
public function marcarTodasLeidas()
|
||||
{
|
||||
$u = $this->getUserSession();
|
||||
if (!$u) return response()->json(['ok' => false], 401);
|
||||
|
||||
$count = $this->service->marcarTodasLeidas($u['tipo'], $u['id']);
|
||||
return response()->json(['ok' => true, 'marcadas' => $count]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /notificaciones/count — Badge AJAX (devuelve JSON {count: N})
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
$u = $this->getUserSession();
|
||||
if (!$u) return response()->json(['count' => 0]);
|
||||
|
||||
return response()->json(['count' => $this->service->contarNoLeidas($u['tipo'], $u['id'])]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /notificaciones/latest — ID de la última no leída
|
||||
*/
|
||||
public function latest()
|
||||
{
|
||||
$u = $this->getUserSession();
|
||||
if (!$u) return response()->json(['id' => 0]);
|
||||
|
||||
$notif = $this->service->obtenerNoLeidas($u['tipo'], $u['id'])->first();
|
||||
return response()->json([
|
||||
'id' => $notif ? $notif->id : 0,
|
||||
'titulo' => $notif ? $notif->titulo : '',
|
||||
'mensaje' => $notif ? $notif->mensaje : ''
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /notificaciones/{id} — Eliminar una notificación
|
||||
*/
|
||||
public function eliminar(int $id)
|
||||
{
|
||||
$u = $this->getUserSession();
|
||||
if (!$u) return response()->json(['ok' => false], 401);
|
||||
|
||||
$this->service->eliminar($id, $u['tipo'], $u['id']);
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /notificaciones — Eliminar todas
|
||||
*/
|
||||
public function eliminarTodas()
|
||||
{
|
||||
$u = $this->getUserSession();
|
||||
if (!$u) return response()->json(['ok' => false], 401);
|
||||
|
||||
$this->service->eliminarTodas($u['tipo'], $u['id']);
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
/**
|
||||
* POST /notificaciones/subscribe — Guardar suscripción para Web Push
|
||||
*/
|
||||
public function subscribe(Request $request)
|
||||
{
|
||||
$u = $this->getUserSession();
|
||||
if (!$u) return response()->json(['ok' => false, 'error' => 'No session'], 401);
|
||||
|
||||
$request->validate([
|
||||
'endpoint' => 'required',
|
||||
'keys.p256dh' => 'required',
|
||||
'keys.auth' => 'required',
|
||||
]);
|
||||
|
||||
\App\Models\PushSubscription::updateOrCreate(
|
||||
[
|
||||
'id_usuario' => (string) $u['id'],
|
||||
'tipo_usuario' => $u['tipo'],
|
||||
'endpoint' => $request->endpoint,
|
||||
],
|
||||
[
|
||||
'p256dh' => $request->keys['p256dh'],
|
||||
'auth' => $request->keys['auth'],
|
||||
]
|
||||
);
|
||||
|
||||
return response()->json(['ok' => true]);
|
||||
}
|
||||
}
|
||||
@@ -1,363 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Models\Jugador;
|
||||
use App\Models\Aficionado;
|
||||
use App\Models\Evento;
|
||||
use App\Models\Equipo;
|
||||
use App\Models\Promocion;
|
||||
use App\Models\QrCode;
|
||||
use App\Models\PromoQr;
|
||||
use App\Mail\QrCodeMail;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PanelController extends Controller
|
||||
{
|
||||
private \App\Services\NotificacionService $notifService;
|
||||
|
||||
public function __construct(\App\Services\NotificacionService $notifService)
|
||||
{
|
||||
$this->notifService = $notifService;
|
||||
}
|
||||
|
||||
// ── Helper: obtener usuario logueado ──
|
||||
private function getUser()
|
||||
{
|
||||
if (!session()->has('user_logged_in')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tipo = session('user_tipo');
|
||||
$id = session('user_id');
|
||||
|
||||
if ($tipo === 'jugador') {
|
||||
return ['user' => Jugador::find($id), 'tipo' => $tipo];
|
||||
}
|
||||
return ['user' => Aficionado::find($id), 'tipo' => $tipo];
|
||||
}
|
||||
|
||||
// ══════════════════════════════════
|
||||
// PANEL PRINCIPAL
|
||||
// ══════════════════════════════════
|
||||
public function index(Request $request)
|
||||
{
|
||||
$data = $this->getUser();
|
||||
if (!$data || !$data['user']) {
|
||||
session()->flush();
|
||||
return redirect('/?logout_msg=' . urlencode('Tu usuario no fue encontrado. Iniciá sesión nuevamente.'));
|
||||
}
|
||||
|
||||
$user = $data['user'];
|
||||
$userTipo = $data['tipo'];
|
||||
$userId = session('user_id');
|
||||
|
||||
// Cargar relaciones de club/equipos para jugadores
|
||||
if ($userTipo === 'jugador') {
|
||||
$user->load(['clubActual', 'equipos.club']);
|
||||
}
|
||||
|
||||
// Obtener QRs del usuario (eventos que NO estén borrados)
|
||||
if ($userTipo === 'jugador') {
|
||||
$qrCodes = QrCode::whereHas('evento')
|
||||
->where('id_jugador', $user->id_jugador)
|
||||
->orderBy('creado', 'desc')
|
||||
->get();
|
||||
} else {
|
||||
$qrCodes = QrCode::whereHas('evento')
|
||||
->where('id_aficionado', $user->id_aficionado)
|
||||
->orderBy('creado', 'desc')
|
||||
->get();
|
||||
}
|
||||
|
||||
// Obtener beneficios de promociones
|
||||
$promoQrs = PromoQr::with('promocion')
|
||||
->where('id_usuario', $userId)
|
||||
->where('tipo_usuario', $userTipo)
|
||||
->orderBy('generado_en', 'desc')
|
||||
->get();
|
||||
|
||||
return view('panel.index', compact('user', 'userTipo', 'qrCodes', 'promoQrs'));
|
||||
}
|
||||
|
||||
// ══════════════════════════════════
|
||||
// ACTUALIZAR DATOS
|
||||
// ══════════════════════════════════
|
||||
public function actualizarDatos(Request $request)
|
||||
{
|
||||
$data = $this->getUser();
|
||||
if (!$data) return redirect('/');
|
||||
|
||||
$request->validate([
|
||||
'email' => 'required|email',
|
||||
'telefono' => 'required|string',
|
||||
'localidad' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$user = $data['user'];
|
||||
|
||||
if ($data['tipo'] === 'jugador') {
|
||||
$user->update([
|
||||
'email' => $request->input('email'),
|
||||
'telefono' => $request->input('telefono'),
|
||||
]);
|
||||
} else {
|
||||
$user->update([
|
||||
'email' => $request->input('email'),
|
||||
'telefono' => $request->input('telefono'),
|
||||
'localidad' => $request->input('localidad'),
|
||||
]);
|
||||
}
|
||||
|
||||
return back()->with('panel_msg', 'Datos actualizados correctamente.');
|
||||
}
|
||||
|
||||
// ══════════════════════════════════
|
||||
// CAMBIAR CONTRASEÑA
|
||||
// ══════════════════════════════════
|
||||
public function cambiarPassword(Request $request)
|
||||
{
|
||||
$data = $this->getUser();
|
||||
if (!$data) return redirect('/');
|
||||
|
||||
$request->validate([
|
||||
'password_actual' => 'required|string',
|
||||
'password_nueva' => 'required|confirmed|min:6',
|
||||
]);
|
||||
|
||||
$user = $data['user'];
|
||||
|
||||
if ($user->password) {
|
||||
if (!Hash::check($request->input('password_actual'), $user->password)) {
|
||||
return back()->with('panel_error', 'La contraseña actual es incorrecta.');
|
||||
}
|
||||
}
|
||||
|
||||
$user->update([
|
||||
'password' => bcrypt($request->input('password_nueva')),
|
||||
]);
|
||||
|
||||
return back()->with('panel_msg', 'Contraseña cambiada correctamente.');
|
||||
}
|
||||
|
||||
// ══════════════════════════════════
|
||||
// SOLICITAR QR PARA EVENTO
|
||||
// ══════════════════════════════════
|
||||
public function solicitarQr(Request $request)
|
||||
{
|
||||
$data = $this->getUser();
|
||||
if (!$data || !$data['user']) return redirect('/');
|
||||
|
||||
$user = $data['user'];
|
||||
$userTipo = $data['tipo'];
|
||||
|
||||
$id_evento = $request->input('id_evento');
|
||||
$evento = Evento::with(['equipoLocal', 'equipoVisitante'])->find($id_evento);
|
||||
|
||||
if (!$evento) {
|
||||
return back()->with('panel_error', 'Evento no encontrado.');
|
||||
}
|
||||
|
||||
$qrs_a_generar = 0;
|
||||
|
||||
if ($userTipo === 'jugador') {
|
||||
// Verificar si ya generó QRs para este evento
|
||||
$yaGenero = QrCode::where('id_evento', $id_evento)
|
||||
->where('id_jugador', $user->id_jugador)
|
||||
->count();
|
||||
|
||||
if ($yaGenero > 0) {
|
||||
return back()->with('panel_error', 'Ya solicitaste QRs para este evento.');
|
||||
}
|
||||
|
||||
// Verificar si el jugador está activo
|
||||
if (!$user->activo) {
|
||||
return back()->with('panel_error', 'Tu registro no está activo. Completá tu registro primero.');
|
||||
}
|
||||
|
||||
// Verificar si pertenece a alguno de los equipos del evento
|
||||
$pertenece = DB::table('jugador_equipo')
|
||||
->where('id_jugador', $user->id_jugador)
|
||||
->whereIn('id_equipo', array_filter([$evento->id_equipo_local, $evento->id_equipo_visitante]))
|
||||
->exists();
|
||||
|
||||
if ($pertenece) {
|
||||
$qrs_a_generar = $evento->limite_qr_jugador ?? 3; // Límite configurable
|
||||
$tipoQr = 'invitado';
|
||||
} else {
|
||||
// Check if category is "libre"
|
||||
$edadCategoria = date('Y') - \Carbon\Carbon::parse($user->fecha_nacimiento)->format('Y');
|
||||
$categoriaDB = \App\Models\Categoria::where('edad_min', '<=', $edadCategoria)
|
||||
->where('edad_max', '>=', $edadCategoria)
|
||||
->first();
|
||||
|
||||
if ($categoriaDB && $categoriaDB->es_libre) {
|
||||
$qrs_a_generar = 1;
|
||||
$tipoQr = 'libre_50'; // Identificador para 50% de descuento
|
||||
} else {
|
||||
return back()->with('panel_error', 'No podés generar QR para este partido ya que no pertenecés a los equipos ni sos categoría Libre. Deberás abonar la totalidad de la entrada en puerta.');
|
||||
}
|
||||
}
|
||||
|
||||
if ($qrs_a_generar === 0) {
|
||||
return back()->with('panel_error', 'No se permiten QRs para este evento.');
|
||||
}
|
||||
|
||||
// Generar QRs
|
||||
for ($i = 0; $i < $qrs_a_generar; $i++) {
|
||||
QrCode::create([
|
||||
'id_qr' => uniqid('qr_'),
|
||||
'id_evento' => $id_evento,
|
||||
'id_jugador' => $user->id_jugador,
|
||||
'tipo_qr' => $tipoQr ?? 'invitado',
|
||||
'escaneos_restantes' => 1,
|
||||
'creado' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Aficionado: no puede solicitar QRs para eventos
|
||||
return back()->with('panel_error', 'Los aficionados no pueden solicitar QRs para eventos. Adquirí tu entrada directamente en el lugar.');
|
||||
}
|
||||
|
||||
// Enviar mail con QRs
|
||||
try {
|
||||
Mail::to($user->email)->send(new QrCodeMail($user, $evento, $qrs_a_generar));
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Error enviando mail de QRs: " . $e->getMessage());
|
||||
}
|
||||
|
||||
// Notificación interna
|
||||
$this->notifService->enviar(
|
||||
$userTipo,
|
||||
$userTipo === 'jugador' ? $user->id_jugador : $user->id_aficionado,
|
||||
'sistema',
|
||||
'🎫 QRs Generados',
|
||||
"Tus {$qrs_a_generar} QR(s) para el evento " . ($evento->nombre_evento ?? 'seleccionado') . " ya están disponibles.",
|
||||
route('panel.mis.qrs', ['evento' => $id_evento])
|
||||
);
|
||||
|
||||
return redirect()->route('panel.mis.qrs', ['evento' => $id_evento])
|
||||
->with('panel_msg', "¡QR(s) generados correctamente! ({$qrs_a_generar})");
|
||||
}
|
||||
|
||||
// ══════════════════════════════════
|
||||
// MIS QRS (per evento)
|
||||
// ══════════════════════════════════
|
||||
public function misQrs(Request $request)
|
||||
{
|
||||
$data = $this->getUser();
|
||||
if (!$data || !$data['user']) return redirect('/');
|
||||
|
||||
$user = $data['user'];
|
||||
$userTipo = $data['tipo'];
|
||||
$id_evento = $request->query('evento');
|
||||
|
||||
// Traer QRs del usuario (solo de eventos activos)
|
||||
$query = QrCode::whereHas('evento')
|
||||
->with(['evento.equipoLocal.club', 'evento.equipoVisitante.club', 'evento.torneo']);
|
||||
|
||||
if ($userTipo === 'jugador') {
|
||||
$query->where('id_jugador', $user->id_jugador);
|
||||
$user->load('clubActual');
|
||||
} else {
|
||||
$query->where('id_aficionado', $user->id_aficionado);
|
||||
}
|
||||
|
||||
if ($id_evento) {
|
||||
$query->where('id_evento', $id_evento);
|
||||
$evento = Evento::find($id_evento);
|
||||
} else {
|
||||
$evento = null;
|
||||
}
|
||||
|
||||
$qrs = $query->orderBy('creado', 'desc')->get();
|
||||
|
||||
// Adjuntar información de grupo si hay torneo
|
||||
foreach ($qrs as $qr) {
|
||||
if ($qr->evento && $qr->evento->id_torneo && $qr->evento->id_equipo_local) {
|
||||
$rel = DB::table('torneo_equipo')
|
||||
->where('id_torneo', $qr->evento->id_torneo)
|
||||
->where('id_equipo', $qr->evento->id_equipo_local)
|
||||
->first();
|
||||
if ($rel) {
|
||||
$qr->evento->grupo_nombre = $rel->grupo ?? 'General';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return view('panel.mis_qrs', compact('user', 'userTipo', 'qrs', 'evento'));
|
||||
}
|
||||
|
||||
// ══════════════════════════════════
|
||||
// GENERAR QR PARA PROMOCIÓN
|
||||
// ══════════════════════════════════
|
||||
public function generarPromoQr(Request $request)
|
||||
{
|
||||
$data = $this->getUser();
|
||||
if (!$data || !$data['user']) return redirect('/');
|
||||
|
||||
$user = $data['user'];
|
||||
$userTipo = $data['tipo'];
|
||||
$userId = session('user_id');
|
||||
|
||||
$id_promo = $request->input('id_promo');
|
||||
$promo = Promocion::find($id_promo);
|
||||
|
||||
if (!$promo) {
|
||||
return back()->with('panel_error', 'Promoción no encontrada.');
|
||||
}
|
||||
|
||||
// Verificar si ya generó QR para esta promo
|
||||
$yaGenero = PromoQr::where('id_promo', $id_promo)
|
||||
->where('id_usuario', $userId)
|
||||
->where('tipo_usuario', $userTipo)
|
||||
->count();
|
||||
|
||||
if ($yaGenero > 0) {
|
||||
return back()->with('panel_error', 'Ya generaste un QR para esta promoción.');
|
||||
}
|
||||
|
||||
$id_qr = bin2hex(random_bytes(8));
|
||||
|
||||
PromoQr::create([
|
||||
'id_qr' => $id_qr,
|
||||
'id_promo' => $id_promo,
|
||||
'id_usuario' => $userId,
|
||||
'tipo_usuario' => $userTipo,
|
||||
'generado_en' => now(),
|
||||
'usado' => false,
|
||||
]);
|
||||
|
||||
return redirect()->route('panel.promo.qr.ver', ['id' => $id_qr])
|
||||
->with('panel_msg', '¡QR de beneficio generado correctamente!');
|
||||
}
|
||||
|
||||
// ══════════════════════════════════
|
||||
// VER QR DE PROMOCIÓN
|
||||
// ══════════════════════════════════
|
||||
public function verPromoQr($id)
|
||||
{
|
||||
$data = $this->getUser();
|
||||
if (!$data || !$data['user']) return redirect('/');
|
||||
|
||||
$user = $data['user'];
|
||||
$userTipo = $data['tipo'];
|
||||
|
||||
if ($userTipo === 'jugador') {
|
||||
$user->load('clubActual');
|
||||
}
|
||||
|
||||
$promoQr = PromoQr::with('promocion')
|
||||
->where('id_qr', $id)
|
||||
->where('id_usuario', session('user_id'))
|
||||
->where('tipo_usuario', $userTipo)
|
||||
->firstOrFail();
|
||||
|
||||
return view('panel.promo_qr', compact('promoQr', 'user', 'userTipo'));
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\PromoQr;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PromoQrController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$promoQrs = PromoQr::with('promocion')->get();
|
||||
return response()->json($promoQrs);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$promoQr = PromoQr::with('promocion', 'usuario')->findOrFail($id);
|
||||
return response()->json($promoQr);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'id_promo' => 'required|integer|exists:promociones,id',
|
||||
'id_usuario' => 'required|integer',
|
||||
'tipo_usuario' => 'required|in:jugador,aficionado',
|
||||
]);
|
||||
$data['id_qr'] = Str::uuid()->toString();
|
||||
$promoQr = PromoQr::create($data);
|
||||
return response()->json($promoQr, 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$promoQr = PromoQr::findOrFail($id);
|
||||
$data = $request->validate([
|
||||
'usado' => 'nullable|boolean',
|
||||
'usado_en' => 'nullable',
|
||||
]);
|
||||
$promoQr->update($data);
|
||||
return response()->json($promoQr);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$promoQr = PromoQr::findOrFail($id);
|
||||
$promoQr->delete();
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Promocion;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PromocionController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$promociones = Promocion::all();
|
||||
|
||||
if ($request->expectsJson()) {
|
||||
return response()->json($promociones);
|
||||
}
|
||||
|
||||
$categorias = $promociones->pluck('categoria')->filter()->unique()->values();
|
||||
|
||||
return view('promos.index', compact('promociones', 'categorias'));
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$promocion = Promocion::with('promoQrs')->findOrFail($id);
|
||||
return response()->json($promocion);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'nombre' => 'required|string|max:100',
|
||||
'direccion' => 'required|string|max:150',
|
||||
'lat' => 'nullable|numeric',
|
||||
'lng' => 'nullable|numeric',
|
||||
'contacto' => 'nullable|string|max:100',
|
||||
'descripcion' => 'nullable',
|
||||
'descripcion_lugar' => 'nullable',
|
||||
'categoria' => 'nullable|string|max:50',
|
||||
'imagen' => 'nullable|string|max:200',
|
||||
]);
|
||||
$promocion = Promocion::create($data);
|
||||
return response()->json($promocion, 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$promocion = Promocion::findOrFail($id);
|
||||
$data = $request->validate([
|
||||
'nombre' => 'sometimes|string|max:100',
|
||||
'direccion' => 'sometimes|string|max:150',
|
||||
'lat' => 'nullable|numeric',
|
||||
'lng' => 'nullable|numeric',
|
||||
'contacto' => 'nullable|string|max:100',
|
||||
'descripcion' => 'nullable',
|
||||
'descripcion_lugar' => 'nullable',
|
||||
'categoria' => 'nullable|string|max:50',
|
||||
'imagen' => 'nullable|string|max:200',
|
||||
]);
|
||||
$promocion->update($data);
|
||||
return response()->json($promocion);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$promocion = Promocion::findOrFail($id);
|
||||
$promocion->delete();
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\QrCode;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class QrCodeController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$qrCodes = QrCode::with('evento', 'jugador', 'aficionado')->get();
|
||||
return response()->json($qrCodes);
|
||||
}
|
||||
|
||||
public function show($id)
|
||||
{
|
||||
$qrCode = QrCode::with('evento', 'jugador', 'aficionado')->findOrFail($id);
|
||||
return response()->json($qrCode);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'id_evento' => 'required|string|max:36',
|
||||
'id_jugador' => 'nullable|string|max:6',
|
||||
'tipo_qr' => 'required|in:invitado,publico',
|
||||
'escaneos_restantes' => 'nullable|integer|min:1',
|
||||
'id_aficionado' => 'nullable|integer',
|
||||
]);
|
||||
$data['id_qr'] = Str::uuid()->toString();
|
||||
$qrCode = QrCode::create($data);
|
||||
return response()->json($qrCode, 201);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$qrCode = QrCode::findOrFail($id);
|
||||
$data = $request->validate([
|
||||
'id_evento' => 'sometimes|string|max:36',
|
||||
'id_jugador' => 'nullable|string|max:6',
|
||||
'tipo_qr' => 'sometimes|in:invitado,publico',
|
||||
'escaneos_restantes' => 'nullable|integer|min:1',
|
||||
'id_aficionado' => 'nullable|integer',
|
||||
]);
|
||||
$qrCode->update($data);
|
||||
return response()->json($qrCode);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$qrCode = QrCode::findOrFail($id);
|
||||
$qrCode->delete();
|
||||
return response()->json(null, 204);
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\QrCode;
|
||||
use Illuminate\Http\Request;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
|
||||
class QrDownloadController extends Controller
|
||||
{
|
||||
public function download($id)
|
||||
{
|
||||
$qr = QrCode::with(['evento.equipoLocal.club', 'evento.equipoVisitante.club', 'jugador.clubActual', 'aficionado'])
|
||||
->where('id_qr', $id)
|
||||
->firstOrFail();
|
||||
|
||||
// Verificar que el usuario tenga permiso para descargar este QR
|
||||
$isUser = session()->has('user_logged_in') && session('user_logged_in');
|
||||
$isAdmin = session()->has('admin_logged_in') && session('admin_logged_in');
|
||||
|
||||
if ($isUser) {
|
||||
$userId = session('user_id');
|
||||
$userTipo = session('user_tipo');
|
||||
if ($userTipo === 'jugador' && $qr->id_jugador != $userId) abort(403);
|
||||
if ($userTipo === 'aficionado' && $qr->id_aficionado != $userId) abort(403);
|
||||
} elseif (!$isAdmin) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$user = $qr->jugador ?: $qr->aficionado;
|
||||
$userTipo = $qr->id_jugador ? 'jugador' : 'aficionado';
|
||||
|
||||
// Convertir imágenes a Base64 para DomPDF (más fiable que URLs remotas/locales)
|
||||
$qrImageBase64 = '';
|
||||
try {
|
||||
$qrUrl = "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=" . urlencode($qr->id_qr);
|
||||
$qrContent = @file_get_contents($qrUrl);
|
||||
if ($qrContent) {
|
||||
$qrImageBase64 = 'data:image/png;base64,' . base64_encode($qrContent);
|
||||
}
|
||||
} catch (\Exception $e) { }
|
||||
|
||||
$club = ($qr->evento && $qr->evento->equipoLocal && $qr->evento->equipoLocal->club) ? $qr->evento->equipoLocal->club : null;
|
||||
|
||||
$backgroundBase64 = null;
|
||||
if ($club && $club->qr_background) {
|
||||
// 1. Intentar en storage/app/public (ruta interna Laravel)
|
||||
$pathInStorage = str_replace(['storage/', 'storage\\'], '', $club->qr_background);
|
||||
$bgPath = storage_path('app/public/' . $pathInStorage);
|
||||
|
||||
// 2. Intentar en public_path (desarrollo local o symlink funcional)
|
||||
if (!file_exists($bgPath)) {
|
||||
$bgPath = public_path($club->qr_background);
|
||||
}
|
||||
|
||||
// 3. Intentar estructura Hostinger (public_html es hermano de la carpeta laravel)
|
||||
if (!file_exists($bgPath)) {
|
||||
$bgPath = base_path('../public_html/' . $club->qr_background);
|
||||
}
|
||||
|
||||
if (file_exists($bgPath)) {
|
||||
$bgContent = @file_get_contents($bgPath);
|
||||
if ($bgContent) {
|
||||
$ext = pathinfo($bgPath, PATHINFO_EXTENSION);
|
||||
$backgroundBase64 = 'data:image/' . $ext . ';base64,' . base64_encode($bgContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$logoBase64 = null;
|
||||
$logoPathFinal = null;
|
||||
|
||||
// Logo del club del jugador (no del local) si corresponde:
|
||||
// jugador → su club actual; aficionado/sin club → club local; fallback → logo OnAPB.
|
||||
$logoClub = null;
|
||||
if ($userTipo === 'jugador' && $qr->jugador && $qr->jugador->clubActual) {
|
||||
$logoClub = $qr->jugador->clubActual;
|
||||
} elseif ($club) {
|
||||
$logoClub = $club;
|
||||
}
|
||||
|
||||
if ($logoClub && $logoClub->imagen) {
|
||||
$logoInStorage = str_replace(['storage/', 'storage\\'], '', $logoClub->imagen);
|
||||
$lPath = storage_path('app/public/' . $logoInStorage);
|
||||
|
||||
if (!file_exists($lPath)) {
|
||||
$lPath = public_path($logoClub->imagen);
|
||||
}
|
||||
|
||||
if (!file_exists($lPath)) {
|
||||
$lPath = base_path('../public_html/' . $logoClub->imagen);
|
||||
}
|
||||
|
||||
if (file_exists($lPath)) {
|
||||
$logoPathFinal = $lPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Si no hay logo, usar logo general
|
||||
if (!$logoPathFinal) {
|
||||
if (file_exists(public_path('logo.png'))) {
|
||||
$logoPathFinal = public_path('logo.png');
|
||||
}
|
||||
}
|
||||
|
||||
if ($logoPathFinal) {
|
||||
$logoContent = @file_get_contents($logoPathFinal);
|
||||
if ($logoContent) {
|
||||
$ext = pathinfo($logoPathFinal, PATHINFO_EXTENSION);
|
||||
$logoBase64 = 'data:image/' . $ext . ';base64,' . base64_encode($logoContent);
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'qr' => $qr,
|
||||
'user' => $user,
|
||||
'userTipo' => $userTipo,
|
||||
'club' => $club,
|
||||
'qrImageBase64' => $qrImageBase64,
|
||||
'backgroundBase64' => $backgroundBase64,
|
||||
'logoBase64' => $logoBase64,
|
||||
];
|
||||
|
||||
$pdf = Pdf::loadView('pdf.qr_ticket', $data)
|
||||
->setPaper([0, 0, 320, 500], 'portrait');
|
||||
|
||||
return $pdf->download("onapb_qr_{$qr->id_qr}.pdf");
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Equipo;
|
||||
use App\Models\EquipoSeguimiento;
|
||||
use App\Models\Evento;
|
||||
use App\Services\NotificacionService;
|
||||
|
||||
class SeguimientoController extends Controller
|
||||
{
|
||||
private NotificacionService $notifService;
|
||||
|
||||
public function __construct(NotificacionService $notifService)
|
||||
{
|
||||
$this->notifService = $notifService;
|
||||
}
|
||||
|
||||
private function getUserSession(): ?array
|
||||
{
|
||||
if (!session()->has('user_logged_in')) return null;
|
||||
return ['tipo' => session('user_tipo'), 'id' => session('user_id')];
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /seguimiento/equipo/{id} — Toggle: seguir o dejar de seguir un equipo
|
||||
*/
|
||||
public function toggle(int $id)
|
||||
{
|
||||
$u = $this->getUserSession();
|
||||
if (!$u) {
|
||||
return response()->json(['ok' => false, 'msg' => 'Debés iniciar sesión para seguir equipos.'], 401);
|
||||
}
|
||||
|
||||
$equipo = Equipo::find($id);
|
||||
if (!$equipo) {
|
||||
return response()->json(['ok' => false, 'msg' => 'Equipo no encontrado.'], 404);
|
||||
}
|
||||
|
||||
$existing = EquipoSeguimiento::where('id_equipo', $id)
|
||||
->where('tipo_usuario', $u['tipo'])
|
||||
->where('id_usuario', (string)$u['id'])
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
$existing->delete();
|
||||
return response()->json(['ok' => true, 'siguiendo' => false, 'msg' => 'Dejaste de seguir este equipo.']);
|
||||
}
|
||||
|
||||
EquipoSeguimiento::create([
|
||||
'id_equipo' => $id,
|
||||
'tipo_usuario'=> $u['tipo'],
|
||||
'id_usuario' => (string)$u['id'],
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
return response()->json(['ok' => true, 'siguiendo' => true, 'msg' => '¡Ahora seguís este equipo!']);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /seguimiento/mis-equipos — Devuelve equipos seguidos y próximos partidos
|
||||
*/
|
||||
public function misEquipos()
|
||||
{
|
||||
$u = $this->getUserSession();
|
||||
if (!$u) return response()->json(['equipos' => []]);
|
||||
|
||||
$seguimientos = EquipoSeguimiento::with(['equipo.club'])
|
||||
->where('tipo_usuario', $u['tipo'])
|
||||
->where('id_usuario', (string)$u['id'])
|
||||
->get();
|
||||
|
||||
$equiposIds = $seguimientos->pluck('id_equipo');
|
||||
|
||||
// Próximos partidos de los equipos seguidos
|
||||
$proximosPartidos = Evento::with(['equipoLocal.club', 'equipoVisitante.club'])
|
||||
->where('fecha_evento', '>=', now()->toDateString())
|
||||
->where(function ($q) use ($equiposIds) {
|
||||
$q->whereIn('id_equipo_local', $equiposIds)
|
||||
->orWhereIn('id_equipo_visitante', $equiposIds);
|
||||
})
|
||||
->orderBy('fecha_evento')
|
||||
->orderBy('hora_inicio')
|
||||
->limit(10)
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'equipos' => $seguimientos->map(fn($s) => [
|
||||
'id' => $s->id_equipo,
|
||||
'nombre' => ($s->equipo->club->nombre ?? 'Equipo') . ' ' . $s->equipo->categoria . ($s->equipo->division ? ' ' . $s->equipo->division : ''),
|
||||
'club' => $s->equipo->club->nombre ?? '—',
|
||||
'categoria'=> $s->equipo->categoria,
|
||||
'division' => $s->equipo->division,
|
||||
]),
|
||||
'proximos_partidos' => $proximosPartidos->map(fn($e) => [
|
||||
'id' => $e->id_evento,
|
||||
'fecha' => $e->fecha_evento->format('d/m/Y'),
|
||||
'hora' => $e->hora_inicio ? $e->hora_inicio->format('H:i') : '',
|
||||
'local' => $e->equipoLocal->club->nombre ?? '?',
|
||||
'visitante'=> $e->equipoVisitante->club->nombre ?? '?',
|
||||
'sede' => $e->sede,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /seguimiento/estado/{id_equipo} — ¿El usuario sigue este equipo?
|
||||
*/
|
||||
public function estado(int $id)
|
||||
{
|
||||
$u = $this->getUserSession();
|
||||
if (!$u) return response()->json(['siguiendo' => false]);
|
||||
|
||||
$siguiendo = EquipoSeguimiento::where('id_equipo', $id)
|
||||
->where('tipo_usuario', $u['tipo'])
|
||||
->where('id_usuario', (string)$u['id'])
|
||||
->exists();
|
||||
|
||||
return response()->json(['siguiendo' => $siguiendo]);
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Torneo;
|
||||
use App\Models\Equipo;
|
||||
use App\Models\Evento;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
|
||||
class TorneoController extends Controller
|
||||
{
|
||||
public function standings(Request $request, $id)
|
||||
{
|
||||
$selectedGroup = $request->query('grupo');
|
||||
$torneo = Torneo::with('equipos.club')->findOrFail($id);
|
||||
|
||||
$tournamentService = new \App\Services\TournamentService();
|
||||
$stats = $tournamentService->getStandings($id, true);
|
||||
|
||||
$grupos = array_keys($stats);
|
||||
|
||||
if ($selectedGroup && isset($stats[$selectedGroup])) {
|
||||
$stats = [$selectedGroup => $stats[$selectedGroup]];
|
||||
}
|
||||
|
||||
$followedTeamIds = [];
|
||||
if (session('user_logged_in') && session('user_id')) {
|
||||
$followedTeamIds = \App\Models\EquipoSeguimiento::where('id_usuario', session('user_id'))
|
||||
->where('tipo_usuario', session('user_tipo'))
|
||||
->pluck('id_equipo')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
return view('torneos.standings', compact('torneo', 'stats', 'grupos', 'selectedGroup', 'followedTeamIds'));
|
||||
}
|
||||
|
||||
public function topScorers(Request $request, $id)
|
||||
{
|
||||
$torneo = Torneo::findOrFail($id);
|
||||
$selectedGroup = $request->query('grupo');
|
||||
|
||||
$query = \App\Models\EventoJugador::with(['jugador.clubActual'])
|
||||
->whereHas('evento', function($q) use ($id) {
|
||||
$q->where('id_torneo', $id)->whereNotNull('marcador_local');
|
||||
});
|
||||
|
||||
if ($selectedGroup) {
|
||||
$query->whereHas('evento.equipoLocal.torneos', function($q) use ($id, $selectedGroup) {
|
||||
$q->where('torneos.id', $id)->where('torneo_equipo.grupo', $selectedGroup);
|
||||
});
|
||||
}
|
||||
|
||||
$scorers = $query->select('id_jugador', DB::raw('SUM(puntos) as total_puntos'), DB::raw('COUNT(id_evento) as partidos_jugados'))
|
||||
->groupBy('id_jugador')
|
||||
->orderByDesc('total_puntos')
|
||||
->take(20)->get();
|
||||
|
||||
$grupos = DB::table('torneo_equipo')->where('id_torneo', $id)->distinct()->pluck('grupo')->filter();
|
||||
|
||||
return view('torneos.scorers', compact('torneo', 'scorers', 'grupos', 'selectedGroup'));
|
||||
}
|
||||
|
||||
public function playoffs(Request $request, $id)
|
||||
{
|
||||
$selectedGroup = $request->query('grupo');
|
||||
$torneo = Torneo::with('equipos.club')->findOrFail($id);
|
||||
|
||||
// Get groups from pivot
|
||||
$grupos = DB::table('torneo_equipo')->where('id_torneo', $id)->distinct()->pluck('grupo')->filter();
|
||||
|
||||
$ts = new \App\Services\TournamentService();
|
||||
$bracket = $ts->getPlayoffBrackets($id);
|
||||
|
||||
// Map to simpler keys if needed by view
|
||||
$bracket = [
|
||||
'cuartos' => collect($bracket[\App\Models\Evento::FASE_CUARTOS] ?? []),
|
||||
'semis' => collect($bracket[\App\Models\Evento::FASE_SEMIS] ?? []),
|
||||
'final' => collect($bracket[\App\Models\Evento::FASE_FINAL] ?? []),
|
||||
];
|
||||
|
||||
return view('torneos.playoffs', compact('torneo', 'bracket', 'grupos', 'selectedGroup'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user