Agrego archivos iniciales
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
<?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.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?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.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?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.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
<?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.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?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.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user