This commit is contained in:
Laucha1312
2026-06-04 15:20:26 -03:00
parent fdd0fef3f0
commit cc049c6cb6
64 changed files with 8914 additions and 0 deletions
@@ -0,0 +1,88 @@
@extends('admin.layout')
@section('title', 'Preview Fixture — ' . $torneo->nombre)
@section('content')
<div class="page-header">
<h2><i class="bi bi-calendar3-range-fill"></i> Preview del Fixture</h2>
<a href="{{ route('admin.torneos.edit', $torneo->id) }}" class="btn-admin-outline">
<i class="bi bi-arrow-left"></i> Volver al torneo
</a>
</div>
<div class="admin-card mb-4">
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
<div>
<h5 class="mb-0 fw-bold">{{ $torneo->nombre }}</h5>
<span class="text-muted small">{{ count($partidosEnriquecidos) }} partidos a generar</span>
</div>
<span class="badge bg-warning text-dark fs-6 px-3 py-2">
<i class="bi bi-eye me-1"></i> Solo vista previa aún no se guardó nada
</span>
</div>
<div class="card-body">
{{-- Agrupar por jornada --}}
@php
$jornadas = collect($partidosEnriquecidos)->groupBy('jornada');
@endphp
@foreach($jornadas as $numJornada => $partidos)
<div class="mb-4">
<h6 class="text-uppercase fw-bold text-muted border-bottom pb-2 mb-3">
<i class="bi bi-calendar-event me-1"></i> Jornada {{ $numJornada }}
<span class="text-primary ms-2">{{ \Carbon\Carbon::parse($partidos->first()['fecha_evento'])->format('d/m/Y') }}</span>
</h6>
<div class="table-responsive">
<table class="table table-sm table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>Local</th>
<th class="text-center">vs</th>
<th>Visitante</th>
<th>Fecha</th>
<th>Hora</th>
<th>Sede</th>
</tr>
</thead>
<tbody>
@foreach($partidos as $p)
<tr>
<td class="fw-bold">{{ $p['nombre_local'] }}</td>
<td class="text-center text-muted">🆚</td>
<td class="fw-bold">{{ $p['nombre_visitante'] }}</td>
<td>{{ \Carbon\Carbon::parse($p['fecha_evento'])->format('d/m/Y') }}</td>
<td>{{ substr($p['hora_inicio'], 0, 5) }}</td>
<td class="text-muted">{{ $p['sede'] ?: '—' }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endforeach
{{-- Botón de confirmación --}}
<div class="border-top pt-4 mt-2 d-flex gap-3 justify-content-end">
<a href="{{ route('admin.torneos.edit', $torneo->id) }}" class="btn btn-outline-secondary">
<i class="bi bi-x-lg me-1"></i> Cancelar
</a>
<form method="POST" action="{{ route('admin.torneos.fixture.confirmar', $torneo->id) }}" class="confirm-submit"
data-confirm-text="Se crearán {{ count($partidosEnriquecidos) }} partidos en la base de datos. Esta acción no se puede deshacer fácilmente."
data-confirm-button="Sí, generar fixture"
data-confirm-icon="warning">
@csrf
<input type="hidden" name="fecha_inicio" value="{{ $fixtureParams['fecha_inicio'] }}">
<input type="hidden" name="dias_entre_jornadas" value="{{ $fixtureParams['dias_entre_jornadas'] }}">
<input type="hidden" name="sede_default" value="{{ $fixtureParams['sede_default'] }}">
<input type="hidden" name="doble_rueda" value="{{ $fixtureParams['doble_rueda'] ? 1 : 0 }}">
<button type="submit" class="btn-admin px-5 py-3">
<i class="bi bi-check2-circle me-2"></i> Confirmar y Generar Fixture
</button>
</form>
</div>
</div>
</div>
@endsection
@@ -0,0 +1,237 @@
@extends('admin.layout')
@section('title', ($torneo ? 'Editar' : 'Nuevo') . ' Torneo - Admin OnAPB')
@section('content')
<div class="page-header">
<h2><i class="bi bi-trophy-fill"></i> {{ $torneo ? 'Editar Torneo' : 'Nuevo Torneo' }}</h2>
<a href="{{ route('admin.torneos.index') }}" class="btn-admin-outline">
<i class="bi bi-arrow-left"></i> Volver
</a>
</div>
<div class="row">
<div class="{{ $torneo ? 'col-lg-5' : 'col-12' }}">
<div class="admin-card mb-4">
<div class="card-header bg-white py-3">
<h5 class="mb-0 fw-bold">Información General</h5>
</div>
<div class="card-body">
<form method="POST" action="{{ $torneo ? route('admin.torneos.update', $torneo->id) : route('admin.torneos.store') }}" class="admin-form">
@csrf
@if($torneo) @method('PUT') @endif
<div class="mb-3">
<label class="form-label">Nombre del Torneo *</label>
<input type="text" name="nombre" class="form-control" value="{{ old('nombre', $torneo->nombre ?? '') }}" required placeholder="Ej: Torneo Apertura 2026">
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Fecha de Inicio</label>
<input type="date" name="fecha_inicio" class="form-control" value="{{ old('fecha_inicio', ($torneo && $torneo->fecha_inicio) ? $torneo->fecha_inicio->format('Y-m-d') : '') }}">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Fecha de Fin</label>
<input type="date" name="fecha_fin" class="form-control" value="{{ old('fecha_fin', ($torneo && $torneo->fecha_fin) ? $torneo->fecha_fin->format('Y-m-d') : '') }}">
</div>
</div>
</div>
<button type="submit" class="btn-admin w-100 py-3 mt-3">
<i class="bi bi-check-lg"></i> {{ $torneo ? 'Actualizar Torneo' : 'Crear Torneo' }}
</button>
</form>
</div>
</div>
</div>
@if($torneo)
<div class="col-lg-7">
<!-- Assign Teams -->
<div class="admin-card mb-4">
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold">Equipos Participantes</h5>
<span class="badge bg-primary px-3 rounded-pill">{{ $torneo->equipos->count() }}</span>
</div>
<div class="card-body">
<form action="{{ route('admin.torneos.equipos.add', $torneo->id) }}" method="POST" class="row g-2 mb-4 align-items-end">
@csrf
<div class="col-md-6">
<label class="form-label small text-muted">Asignar Equipo al Torneo</label>
<select name="id_equipo" class="form-select select2-basic" required>
<option value="">Buscar equipo por club / categoría...</option>
@foreach($clubes as $club)
<optgroup label="{{ $club->nombre }}">
@foreach($club->equipos as $eq)
<option value="{{ $eq->id_equipo }}">
{{ $eq->categoria }} {{ $eq->division ? '('.$eq->division.')' : '' }}
</option>
@endforeach
</optgroup>
@endforeach
</select>
</div>
<div class="col-md-3">
<label class="form-label small text-muted">Grupo / División (Opcional)</label>
<input type="text" name="grupo" class="form-control" placeholder="Ej: Zona B">
</div>
<div class="col-md-3">
<button type="submit" class="btn-admin w-100">
<i class="bi bi-plus-lg"></i> Agregar
</button>
</div>
</form>
<div class="table-responsive" style="max-height: 400px; overflow-y: auto;">
<table class="table table-sm align-middle mb-0">
<thead class="bg-light sticky-top">
<tr>
<th>Club</th>
<th>Categoría Base</th>
<th>Grupo en Torneo</th>
<th class="text-end">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($torneo->equipos as $te)
<tr>
<td>
<strong>{{ $te->club->nombre }}</strong>
</td>
<td>
<span class="badge border text-dark">{{ $te->categoria }} {{ $te->division }}</span>
</td>
<td>
@if($te->pivot->grupo)
<span class="badge bg-info text-white">{{ $te->pivot->grupo }}</span>
@else
<span class="text-muted small italic">Usar Base</span>
@endif
</td>
<td class="text-end">
<form action="{{ route('admin.torneos.equipos.remove', [$torneo->id, $te->id_equipo]) }}" method="POST" class="d-inline">
@csrf @method('DELETE')
<button type="submit" class="btn btn-link text-danger p-0 border-0" onclick="return confirm('¿Remover este equipo del torneo?')">
<i class="bi bi-x-circle-fill fs-5"></i>
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="3" class="text-center py-4 text-muted">No hay equipos asignados a este torneo.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
@endif
</div>
@if($torneo && $torneo->equipos->count() >= 2)
<!-- Generar Fixture -->
<div class="admin-card mt-4">
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
<div>
<h5 class="mb-0 fw-bold"><i class="bi bi-calendar3-range-fill text-primary me-2"></i>Generar Fixture Regular</h5>
<span class="text-muted small">Round-Robin automático para los {{ $torneo->equipos->count() }} equipos del torneo</span>
</div>
<a href="{{ route('admin.torneos.importar', $torneo->id) }}" class="btn btn-sm btn-outline-success">
<i class="bi bi-upload"></i> Importar Masivo (CSV/Texto)
</a>
</div>
<div class="card-body">
<form method="POST" action="{{ route('admin.torneos.fixture.preview', $torneo->id) }}">
@csrf
<div class="row g-3 align-items-end">
<div class="col-md-3">
<label class="form-label small fw-bold text-muted text-uppercase">Fecha de inicio *</label>
<input type="date" name="fecha_inicio" class="form-control" required
min="{{ date('Y-m-d') }}" value="{{ date('Y-m-d', strtotime('+7 days')) }}">
</div>
<div class="col-md-2">
<label class="form-label small fw-bold text-muted text-uppercase">Días entre jornadas</label>
<input type="number" name="dias_entre_jornadas" class="form-control" value="7" min="1" max="60">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold text-muted text-uppercase">Sede por defecto</label>
<input type="text" name="sede_default" class="form-control" placeholder="Ej: Estadio Municipal">
</div>
<div class="col-md-2 d-flex align-items-center gap-2 pt-3">
<input type="checkbox" name="doble_rueda" value="1" class="form-check-input" id="doble_rueda" style="width:20px;height:20px;">
<label for="doble_rueda" class="form-check-label small fw-bold">Ida y vuelta</label>
</div>
<div class="col-md-1">
<button type="submit" class="btn-admin w-100 py-3" title="Ver preview">
<i class="bi bi-eye"></i> Preview
</button>
</div>
</div>
</form>
</div>
</div>
<!-- Generar Playoffs -->
<div class="admin-card mt-4 border-primary">
<div class="card-header bg-light py-3">
<h5 class="mb-0 fw-bold text-primary"><i class="bi bi-diagram-3-fill me-2"></i>Fase Eliminatoria (Playoffs)</h5>
<span class="text-muted small">Generar cruces 1º vs 8º basado en la tabla actual</span>
</div>
<div class="card-body">
<form method="POST" action="{{ route('admin.torneos.playoffs.generar', $torneo->id) }}">
@csrf
<div class="row g-3 align-items-end">
<div class="col-md-3">
<label class="form-label small fw-bold text-muted text-uppercase">Grupo / Categoría</label>
<select name="grupo" class="form-select" required>
<option value="">Seleccione grupo...</option>
@php
$grupos = \DB::table('torneo_equipo')->where('id_torneo', $torneo->id)->distinct()->pluck('grupo')->filter();
@endphp
@foreach($grupos as $g)
<option value="{{ $g }}">{{ $g }}</option>
@endforeach
</select>
</div>
<div class="col-md-2">
<label class="form-label small fw-bold text-muted text-uppercase">Formato de Serie</label>
<select name="formato" class="form-select" required>
<option value="1">Partido Único</option>
<option value="3">Mejor de 3</option>
<option value="5">Mejor de 5</option>
</select>
</div>
<div class="col-md-4">
<div class="alert alert-info py-2 mb-0 small">
<i class="bi bi-info-circle"></i> Los 8 mejores del grupo iniciarán en Cuartos.
</div>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-primary w-100 py-3" onclick="return confirm('¿Generar Cuartos de Final con este formato?')">
<i class="bi bi-lightning-fill"></i> Generar Cuartos
</button>
<a href="{{ route('admin.torneos.playoffs.manage', $torneo->id) }}" class="btn btn-outline-primary w-100 mt-2">
<i class="bi bi-diagram-3"></i> Gestionar LLaves Existentes
</a>
</div>
</div>
</form>
</div>
</div>
@elseif($torneo)
<div class="alert alert-warning mt-4 d-flex align-items-center gap-2">
<i class="bi bi-exclamation-triangle-fill fs-5"></i>
<span>Necesitás al menos <strong>2 equipos</strong> asignados al torneo para generar el fixture.</span>
</div>
@endif
@endsection
@@ -0,0 +1,46 @@
@extends('layouts.app')
@section('content')
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Importar Fixture Histórico</h1>
<a href="{{ route('admin.torneos.edit', $torneo->id) }}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> Volver
</a>
</div>
<div class="card shadow-sm border-0 mb-4">
<div class="card-body">
<h5 class="card-title">Instrucciones</h5>
<p class="text-muted">
Pega aquí los partidos ya jugados para cargarlos masivamente.
El formato debe ser CSV con las siguientes <strong>9 columnas</strong>:
</p>
<div class="bg-light p-3 rounded mb-3">
<code>Fecha(AAAA-MM-DD), Club Local, Cat Local, Club Visitante, Cat Visitante, Marcador L, Marcador V, Sede(opc), Grupo(opc)</code>
</div>
<p class="small text-info">
<i class="fas fa-info-circle"></i>
<strong>Manejo de Equipos:</strong> El sistema busca por Club Y Categoría. Esto permite diferenciar, por ejemplo, los equipos de "San Martin" en "Primera A" de los de "Primera B" en el mismo torneo.
</p>
<form action="{{ route('admin.torneos.importar.store', $torneo->id) }}" method="POST">
@csrf
<div class="mb-3">
<label for="texto_importar" class="form-label">Datos a importar (CSV)</label>
<textarea class="form-control" id="texto_importar" name="texto_importar" rows="10" placeholder="2026-03-14, Recreativo, Primera B, Talleres, Primera B, 64, 61, , Primera B"></textarea>
</div>
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle"></i>
Esta acción creará nuevos registros de eventos. Evita duplicar partidos que ya existan en el sistema.
</div>
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-upload"></i> Procesar e Importar
</button>
</form>
</div>
</div>
</div>
@endsection
@@ -0,0 +1,57 @@
@extends('admin.layout')
@section('title', 'Gestión de Torneos - Admin OnAPB')
@section('content')
<div class="page-header">
<h2><i class="bi bi-trophy-fill"></i> Torneos</h2>
<a href="{{ route('admin.torneos.create') }}" class="btn-admin-primary">
<i class="bi bi-plus-lg me-2"></i> NUEVO TORNEO
</a>
</div>
<div class="admin-card">
<div class="table-responsive">
<table class="admin-table">
<thead>
<tr>
<th>Nombre</th>
<th>Inicio</th>
<th>Fin</th>
<th class="text-center">Equipos</th>
<th class="text-end">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($torneos as $torneo)
<tr>
<td class="fw-bold text-dark">{{ $torneo->nombre }}</td>
<td>{{ $torneo->fecha_inicio ? $torneo->fecha_inicio->format('d/m/Y') : '—' }}</td>
<td>{{ $torneo->fecha_fin ? $torneo->fecha_fin->format('d/m/Y') : '—' }}</td>
<td class="text-center">
<span class="badge bg-light text-dark border">{{ $torneo->equipos_count }}</span>
</td>
<td class="text-end">
<div class="d-flex justify-content-end gap-2">
<a href="{{ route('admin.torneos.edit', $torneo->id) }}" class="btn-admin-icon" title="Editar / Gestionar Equipos">
<i class="bi bi-pencil-square"></i>
</a>
<form action="{{ route('admin.torneos.destroy', $torneo->id) }}" method="POST" onsubmit="return confirm('¿Eliminar este torneo? Esto no borrará los equipos ni los eventos vinculados.')">
@csrf @method('DELETE')
<button type="submit" class="btn-admin-icon text-danger">
<i class="bi bi-trash"></i>
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center py-5 text-muted">No hay torneos creados.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
@endsection
@@ -0,0 +1,142 @@
@extends('admin.layout')
@section('title', 'Gestión de Playoffs - ' . $torneo->nombre)
@section('content')
<div class="page-header">
<div class="d-flex align-items-center gap-3">
<a href="{{ route('admin.torneos.edit', $torneo->id) }}" class="btn-admin-outline p-2">
<i class="bi bi-arrow-left"></i>
</a>
<h2 class="mb-0"><i class="bi bi-diagram-3-fill text-primary"></i> Gestión de Playoffs</h2>
</div>
</div>
<div class="admin-card mb-4 border-primary">
<div class="card-body">
<div class="row align-items-center">
<div class="col-md-8">
<h4 class="fw-bold mb-1">{{ $torneo->nombre }}</h4>
<p class="text-muted mb-0">Control de llaves y avance manual de ganadores por serie.</p>
</div>
<div class="col-md-4 text-end">
<button class="btn btn-admin" onclick="window.location.reload()">
<i class="bi bi-arrow-clockwise"></i> Actualizar Estado
</button>
</div>
</div>
</div>
</div>
@php
$fases = [
\App\Models\Evento::FASE_CUARTOS => 'Cuartos de Final',
\App\Models\Evento::FASE_SEMIS => 'Semifinales',
\App\Models\Evento::FASE_FINAL => 'Gran Final'
];
@endphp
@foreach($fases as $faseId => $faseNombre)
<div class="mb-5">
<h3 class="h4 fw-bold text-uppercase border-bottom pb-2 mb-4">
<span class="badge bg-primary me-2">{{ $faseNombre }}</span>
</h3>
<div class="row g-4">
@php $hayPartidos = isset($bracket[$faseId]) && count($bracket[$faseId]) > 0; @endphp
@if($hayPartidos)
@foreach($bracket[$faseId] as $nroBracket => $serie)
<div class="col-lg-6 col-xl-3">
<div class="admin-card h-100 border-{{ ($serie['wins_local'] >= 2 || $serie['wins_visitante'] >= 2) ? 'success' : 'light' }}">
<div class="card-header bg-white d-flex justify-content-between align-items-center py-2">
<span class="small fw-bold text-muted">LLAVE #{{ $nroBracket }}</span>
<span class="badge bg-light text-dark border small">Serie: {{ $serie['wins_local'] }} - {{ $serie['wins_visitante'] }}</span>
</div>
<div class="card-body">
<div class="team-row d-flex justify-content-between align-items-center mb-3">
<div class="d-flex align-items-center gap-2">
@if($serie['equipo_local'])
<img src="{{ $serie['equipo_local']->club->imagen ? asset('storage/'.$serie['equipo_local']->club->imagen) : asset('static/escudo_default.png') }}" width="24" class="rounded-circle border">
<span class="fw-bold {{ $serie['wins_local'] > $serie['wins_visitante'] ? 'text-success' : '' }}">
{{ $serie['equipo_local']->club->nombre }}
</span>
@else
<span class="text-muted italic">Por definir</span>
@endif
</div>
<span class="badge {{ $serie['wins_local'] > $serie['wins_visitante'] ? 'bg-success' : 'bg-secondary' }}">{{ $serie['wins_local'] }}</span>
</div>
<div class="team-row d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-center gap-2">
@if($serie['equipo_visitante'])
<img src="{{ $serie['equipo_visitante']->club->imagen ? asset('storage/'.$serie['equipo_visitante']->club->imagen) : asset('static/escudo_default.png') }}" width="24" class="rounded-circle border">
<span class="fw-bold {{ $serie['wins_visitante'] > $serie['wins_local'] ? 'text-success' : '' }}">
{{ $serie['equipo_visitante']->club->nombre }}
</span>
@else
<span class="text-muted fst-italic">Por definir</span>
@endif
</div>
<span class="badge {{ $serie['wins_visitante'] > $serie['wins_local'] ? 'bg-success' : 'bg-secondary' }}">{{ $serie['wins_visitante'] }}</span>
</div>
<hr>
<div class="d-grid gap-2">
<!-- Botón de Avance -->
@php
$needed = floor($serie['total_partidos'] / 2) + 1;
$readyToAdvance = ($serie['wins_local'] >= $needed || $serie['wins_visitante'] >= $needed);
$winnerId = $serie['wins_local'] > $serie['wins_visitante'] ? ($serie['equipo_local']->id_equipo ?? null) : ($serie['equipo_visitante']->id_equipo ?? null);
@endphp
@if($readyToAdvance && $faseId < \App\Models\Evento::FASE_FINAL)
<form action="{{ route('admin.torneos.playoffs.avanzar', $torneo->id) }}" method="POST">
@csrf
<input type="hidden" name="fase" value="{{ $faseId }}">
<input type="hidden" name="nro_bracket" value="{{ $nroBracket }}">
<input type="hidden" name="id_ganador" value="{{ $winnerId }}">
<button type="submit" class="btn btn-success btn-sm w-100 fw-bold">
<i class="bi bi-chevron-double-right"></i> PROMOCIONAR GANADOR
</button>
</form>
@endif
<button class="btn btn-outline-primary btn-sm" type="button" data-bs-toggle="collapse" data-bs-target="#matches-{{ $faseId }}-{{ $nroBracket }}">
Ver Partidos ({{ $serie['total_partidos'] }})
</button>
</div>
<div class="collapse mt-3" id="matches-{{ $faseId }}-{{ $nroBracket }}">
<ul class="list-group list-group-flush small">
@foreach($serie['matches'] as $idx => $m)
<li class="list-group-item d-flex justify-content-between align-items-center px-0">
<span>G{{ $idx+1 }} - {{ $m->fecha_evento->format('d/m') }}</span>
<div class="d-flex align-items-center gap-2">
<span class="fw-bold">{{ $m->marcador_local ?? '-' }} : {{ $m->marcador_visitante ?? '-' }}</span>
<a href="{{ route('admin.eventos.edit', $m->id_evento) }}" class="btn btn-sm p-0 text-primary">
<i class="bi bi-pencil-square"></i>
</a>
</div>
</li>
@endforeach
</ul>
</div>
</div>
</div>
</div>
@endforeach
@else
<div class="col-12">
<div class="alert alert-light border fst-italic py-4 text-center">
No hay llaves generadas para esta fase.
</div>
</div>
@endif
</div>
</div>
@endforeach
@endsection