Agrego archivos iniciales
This commit is contained in:
@@ -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.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user