1668 lines
68 KiB
PHP
1668 lines
68 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Services\ImageOptimizer;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use App\Models\Club;
|
|
use App\Models\Equipo;
|
|
use App\Models\Jugador;
|
|
use App\Models\Evento;
|
|
use App\Models\Promocion;
|
|
use App\Models\Sponsor;
|
|
use App\Models\Noticia;
|
|
use App\Models\QrCode;
|
|
|
|
class AdminController extends Controller
|
|
{
|
|
// ──────── Middleware check ────────
|
|
private function checkSuperAdmin(Request $request)
|
|
{
|
|
if (!session('admin_logged_in') || session('admin_role') != 1) {
|
|
abort(403, 'Acceso denegado. Solo Súper Administradores.');
|
|
}
|
|
}
|
|
|
|
private function checkGeneralAdmin(Request $request)
|
|
{
|
|
if (!session('admin_logged_in') || !in_array(session('admin_role'), [1, 2])) {
|
|
abort(403, 'Acceso denegado');
|
|
}
|
|
}
|
|
|
|
// ══════════════════════════════════
|
|
// DASHBOARD
|
|
// ══════════════════════════════════
|
|
public function dashboard(Request $request)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
|
|
if (session('admin_role') == 1) {
|
|
$stats = [
|
|
'clubes' => Club::count(),
|
|
'equipos' => Equipo::count(),
|
|
'jugadores' => Jugador::count(),
|
|
'eventos' => Evento::count(),
|
|
'promociones' => Promocion::count(),
|
|
'noticias' => Noticia::count(),
|
|
];
|
|
$miClub = null;
|
|
} else {
|
|
$idClub = session('admin_id_club');
|
|
$miClub = Club::find($idClub);
|
|
|
|
$stats = [
|
|
'equipos' => Equipo::where('id_club', $idClub)->count(),
|
|
'jugadores' => Jugador::where('id_club_actual', $idClub)->count(),
|
|
'eventos' => Evento::whereHas('equipoLocal', function($q) use ($idClub) {
|
|
$q->where('id_club', $idClub);
|
|
})->orWhereHas('equipoVisitante', function($q) use ($idClub) {
|
|
$q->where('id_club', $idClub);
|
|
})->count(),
|
|
'promociones' => Promocion::count(), // Promociones son generales
|
|
];
|
|
}
|
|
|
|
return view('admin.dashboard', compact('stats', 'miClub'));
|
|
}
|
|
|
|
// ══════════════════════════════════
|
|
// CLUBES
|
|
// ══════════════════════════════════
|
|
public function clubesIndex(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$search = trim((string) $request->input('q', ''));
|
|
|
|
// Recién creados/editados primero; registros viejos sin timestamps caen al fallback por id.
|
|
$query = Club::withCount('equipos', 'jugadores')
|
|
->orderByRaw("COALESCE(updated_at, '1970-01-01') DESC")
|
|
->orderBy('id_club', 'desc');
|
|
|
|
if ($search !== '') {
|
|
$query->where('nombre', 'like', '%' . $search . '%');
|
|
}
|
|
|
|
$clubes = $query->get();
|
|
return view('admin.clubes.index', compact('clubes', 'search'));
|
|
}
|
|
|
|
public function clubesCreate(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
return view('admin.clubes.form', ['club' => null]);
|
|
}
|
|
|
|
public function clubesStore(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$data = $request->validate([
|
|
'id_club' => 'nullable|integer',
|
|
'nombre' => 'required|string|max:100',
|
|
'es_seleccion' => 'nullable|boolean',
|
|
]);
|
|
$data['es_seleccion'] = $request->boolean('es_seleccion');
|
|
Club::create($data);
|
|
return redirect()->route('admin.clubes.index')->with('admin_msg', 'Club creado correctamente.');
|
|
}
|
|
|
|
public function clubesEdit(Request $request, $id)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
if (session('admin_role') == 2 && session('admin_id_club') != $id) {
|
|
abort(403, 'No tienes permiso para editar este club.');
|
|
}
|
|
$club = Club::findOrFail($id);
|
|
return view('admin.clubes.form', compact('club'));
|
|
}
|
|
|
|
public function clubesUpdate(Request $request, $id)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
if (session('admin_role') == 2 && session('admin_id_club') != $id) {
|
|
abort(403, 'No tienes permiso para editar este club.');
|
|
}
|
|
|
|
$club = Club::findOrFail($id);
|
|
|
|
$rules = [
|
|
'qr_color_texto' => 'nullable|string|max:20',
|
|
'qr_background' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048',
|
|
'logo_club' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:1024'
|
|
];
|
|
|
|
if (session('admin_role') == 1) {
|
|
$rules['nombre'] = 'required|string|max:100';
|
|
$rules['es_seleccion'] = 'nullable|boolean';
|
|
}
|
|
|
|
$data = $request->validate($rules);
|
|
|
|
if (session('admin_role') == 1) {
|
|
$data['es_seleccion'] = $request->boolean('es_seleccion');
|
|
}
|
|
|
|
// Manejo de Logo del Club
|
|
if ($request->hasFile('logo_club')) {
|
|
// Eliminar logo anterior si existe
|
|
if ($club->imagen && file_exists(public_path($club->imagen))) {
|
|
@unlink(public_path($club->imagen));
|
|
}
|
|
$logoPath = app(ImageOptimizer::class)->storeAndOptimize($request->file('logo_club'), 'clubes');
|
|
$data['imagen'] = 'storage/' . $logoPath;
|
|
}
|
|
|
|
// Manejo de Fondo QR
|
|
if ($request->hasFile('qr_background')) {
|
|
// Eliminar fondo anterior si existe
|
|
if ($club->qr_background && file_exists(public_path($club->qr_background))) {
|
|
@unlink(public_path($club->qr_background));
|
|
}
|
|
$path = app(ImageOptimizer::class)->storeAndOptimize($request->file('qr_background'), 'qr');
|
|
$data['qr_background'] = 'storage/' . $path;
|
|
}
|
|
|
|
$club->update($data);
|
|
|
|
if (session('admin_role') == 2) {
|
|
return back()->with('admin_msg', 'Plantilla de QR actualizada correctamente.');
|
|
}
|
|
|
|
return redirect()->route('admin.clubes.index')->with('admin_msg', 'Club actualizado correctamente.');
|
|
}
|
|
|
|
public function clubesDestroy(Request $request, $id)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$club = Club::findOrFail($id);
|
|
$club->delete();
|
|
return redirect()->route('admin.clubes.index')->with('admin_msg', 'Club eliminado correctamente.');
|
|
}
|
|
|
|
// ══════════════════════════════════
|
|
// EQUIPOS
|
|
// ══════════════════════════════════
|
|
public function equiposIndex(Request $request)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$search = trim((string) $request->input('q', ''));
|
|
|
|
$query = Equipo::with('club')->withCount('jugadores')
|
|
->orderByRaw("COALESCE(updated_at, '1970-01-01') DESC")
|
|
->orderBy('id_equipo', 'desc');
|
|
|
|
if (session('admin_role') == 2) {
|
|
$query->where('id_club', session('admin_id_club'));
|
|
}
|
|
|
|
if ($search !== '') {
|
|
$like = '%' . $search . '%';
|
|
$query->where(function ($q) use ($like, $search) {
|
|
$q->where('categoria', 'like', $like)
|
|
->orWhere('division', 'like', $like)
|
|
->orWhereHas('club', function ($c) use ($like) {
|
|
$c->where('nombre', 'like', $like);
|
|
});
|
|
|
|
if (ctype_digit($search)) {
|
|
$q->orWhere('id_equipo', (int) $search);
|
|
}
|
|
});
|
|
}
|
|
|
|
$equipos = $query->get();
|
|
return view('admin.equipos.index', compact('equipos', 'search'));
|
|
}
|
|
|
|
public function equiposCreate(Request $request)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
|
|
if (session('admin_role') == 1) {
|
|
$clubes = Club::orderBy('nombre')->get();
|
|
} else {
|
|
$clubes = Club::where('id_club', session('admin_id_club'))->get();
|
|
}
|
|
|
|
$categorias = \App\Models\Categoria::orderBy('nombre')->get();
|
|
return view('admin.equipos.form', ['equipo' => null, 'clubes' => $clubes, 'categorias' => $categorias]);
|
|
}
|
|
|
|
public function equiposStore(Request $request)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
|
|
$data = $request->validate([
|
|
'id_club' => session('admin_role') == 1 ? 'required|integer|exists:clubes,id_club' : 'nullable',
|
|
'categoria' => 'required|string|max:20',
|
|
'division' => 'required|string|max:5',
|
|
]);
|
|
|
|
if (session('admin_role') == 2) {
|
|
$data['id_club'] = session('admin_id_club');
|
|
}
|
|
|
|
Equipo::create($data);
|
|
return redirect()->route('admin.equipos.index')->with('admin_msg', 'Equipo creado correctamente.');
|
|
}
|
|
|
|
public function equiposEdit(Request $request, $id)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$equipo = Equipo::findOrFail($id);
|
|
|
|
if (session('admin_role') == 2 && $equipo->id_club != session('admin_id_club')) {
|
|
abort(403, 'No tienes permiso para editar este equipo.');
|
|
}
|
|
|
|
if (session('admin_role') == 1) {
|
|
$clubes = Club::orderBy('nombre')->get();
|
|
} else {
|
|
$clubes = Club::where('id_club', session('admin_id_club'))->get();
|
|
}
|
|
|
|
$categorias = \App\Models\Categoria::orderBy('nombre')->get();
|
|
return view('admin.equipos.form', compact('equipo', 'clubes', 'categorias'));
|
|
}
|
|
|
|
public function equiposUpdate(Request $request, $id)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$equipo = Equipo::findOrFail($id);
|
|
|
|
if (session('admin_role') == 2 && $equipo->id_club != session('admin_id_club')) {
|
|
abort(403, 'No tienes permiso para editar este equipo.');
|
|
}
|
|
|
|
$data = $request->validate([
|
|
'id_club' => session('admin_role') == 1 ? 'required|integer|exists:clubes,id_club' : 'nullable',
|
|
'categoria' => 'required|string|max:20',
|
|
'division' => 'required|string|max:5',
|
|
]);
|
|
|
|
if (session('admin_role') == 2) {
|
|
$data['id_club'] = session('admin_id_club');
|
|
}
|
|
|
|
$equipo->update($data);
|
|
return redirect()->route('admin.equipos.index')->with('admin_msg', 'Equipo actualizado correctamente.');
|
|
}
|
|
|
|
public function equiposDestroy(Request $request, $id)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$equipo = Equipo::findOrFail($id);
|
|
|
|
if (session('admin_role') == 2 && $equipo->id_club != session('admin_id_club')) {
|
|
abort(403, 'No tienes permiso para eliminar este equipo.');
|
|
}
|
|
|
|
$equipo->delete();
|
|
return redirect()->route('admin.equipos.index')->with('admin_msg', 'Equipo eliminado correctamente.');
|
|
}
|
|
|
|
public function equipoJugadores($id)
|
|
{
|
|
$this->checkGeneralAdmin(request());
|
|
$equipo = Equipo::with('club', 'jugadores')->findOrFail($id);
|
|
|
|
if (session('admin_role') == 2 && $equipo->id_club != session('admin_id_club')) {
|
|
abort(403, 'No tienes permiso para gestionar jugadores de este equipo.');
|
|
}
|
|
|
|
return view('admin.equipos.jugadores', compact('equipo'));
|
|
}
|
|
|
|
public function equipoAddJugador(Request $request, $id)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$equipo = Equipo::findOrFail($id);
|
|
|
|
if (session('admin_role') == 2 && $equipo->id_club != session('admin_id_club')) {
|
|
abort(403);
|
|
}
|
|
|
|
$data = $request->validate([
|
|
'id_jugador' => 'required|string|exists:jugadores,id_jugador',
|
|
]);
|
|
|
|
// Verificar que el jugador sea del mismo club, salvo que el equipo pertenezca a una selección
|
|
$jugador = Jugador::where('id_jugador', $data['id_jugador'])->firstOrFail();
|
|
$esSeleccion = $equipo->club && $equipo->club->es_seleccion;
|
|
if (!$esSeleccion && $jugador->id_club_actual != $equipo->id_club) {
|
|
return back()->with('admin_error', 'El jugador no pertenece al mismo club que el equipo.');
|
|
}
|
|
|
|
// Evitar duplicados
|
|
if ($equipo->jugadores()->where('jugador_equipo.id_jugador', $data['id_jugador'])->exists()) {
|
|
return back()->with('admin_error', 'El jugador ya está asignado a este equipo.');
|
|
}
|
|
|
|
$equipo->jugadores()->attach($data['id_jugador'], ['fecha_alta' => now()]);
|
|
|
|
return back()->with('admin_msg', 'Jugador asignado correctamente.');
|
|
}
|
|
|
|
public function equipoRemoveJugador($id, $id_jugador)
|
|
{
|
|
$this->checkGeneralAdmin(request());
|
|
$equipo = Equipo::findOrFail($id);
|
|
|
|
if (session('admin_role') == 2 && $equipo->id_club != session('admin_id_club')) {
|
|
abort(403);
|
|
}
|
|
|
|
$equipo->jugadores()->detach($id_jugador);
|
|
|
|
return back()->with('admin_msg', 'Jugador removido del equipo.');
|
|
}
|
|
|
|
public function jugadoresSearchAjax(Request $request)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$q = $request->input('q');
|
|
|
|
$query = Jugador::query();
|
|
|
|
if (session('admin_role') == 2) {
|
|
$query->where('id_club_actual', session('admin_id_club'));
|
|
}
|
|
|
|
if ($q) {
|
|
$query->where(function($sub) use ($q) {
|
|
$sub->where('nombre', 'like', "%$q%")
|
|
->orWhere('apellido', 'like', "%$q%")
|
|
->orWhere('documento', 'like', "%$q%");
|
|
});
|
|
}
|
|
|
|
$jugadores = $query->limit(10)->get(['id_jugador', 'nombre', 'apellido', 'documento']);
|
|
|
|
return response()->json($jugadores);
|
|
}
|
|
|
|
public function jugadoresCategoriaPorEdad(Request $request)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$fecha = $request->input('fecha');
|
|
if (!$fecha) return response()->json(['categoria' => 'Sin categoría']);
|
|
|
|
$anio = \Carbon\Carbon::parse($fecha)->format('Y');
|
|
$edadCategoria = date('Y') - $anio;
|
|
|
|
$categoria = \App\Models\Categoria::where('edad_min', '<=', $edadCategoria)
|
|
->where('edad_max', '>=', $edadCategoria)
|
|
->first();
|
|
|
|
return response()->json(['categoria' => $categoria ? $categoria->nombre : 'Sin categoría']);
|
|
}
|
|
|
|
|
|
// ══════════════════════════════════
|
|
// JUGADORES
|
|
// ══════════════════════════════════
|
|
public function jugadoresIndex(Request $request)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$search = $request->input('q');
|
|
$query = Jugador::with('clubActual')
|
|
->orderByRaw("COALESCE(updated_at, '1970-01-01') DESC")
|
|
->orderByRaw('CAST(id_jugador AS UNSIGNED) DESC');
|
|
|
|
if (session('admin_role') == 2) {
|
|
$query->where('id_club_actual', session('admin_id_club'));
|
|
}
|
|
|
|
if ($search) {
|
|
// Tokenizar por espacios. Cada token debe matchear como INICIO de palabra
|
|
// dentro de "apellido nombre" — así "Man Adriel" encuentra a MAN ADRIEL pero
|
|
// no a ROMAN PEREZ. El DNI sigue aceptando match por substring directo.
|
|
$tokens = array_values(array_filter(
|
|
array_map(
|
|
fn($t) => preg_replace('/[^\p{L}\p{N}]/u', '', $t),
|
|
preg_split('/\s+/u', $search)
|
|
),
|
|
fn($t) => mb_strlen($t) > 0
|
|
));
|
|
|
|
$query->where(function ($q) use ($tokens, $search) {
|
|
$q->where('documento', 'like', '%' . $search . '%');
|
|
|
|
if (!empty($tokens)) {
|
|
$q->orWhere(function ($qq) use ($tokens) {
|
|
foreach ($tokens as $token) {
|
|
$qq->whereRaw(
|
|
"LOWER(CONCAT(apellido, ' ', nombre)) REGEXP ?",
|
|
['[[:<:]]' . mb_strtolower($token)]
|
|
);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
$jugadores = $query->paginate(25);
|
|
$clubes = session('admin_role') == 1 ? Club::orderBy('nombre')->get() : [];
|
|
|
|
return view('admin.jugadores.index', compact('jugadores', 'search', 'clubes'));
|
|
}
|
|
|
|
public function jugadoresCreate(Request $request)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$clubes = Club::orderBy('nombre')->get();
|
|
return view('admin.jugadores.form', ['jugador' => null, 'clubes' => $clubes]);
|
|
}
|
|
|
|
public function jugadoresStore(Request $request)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$data = $request->validate([
|
|
'documento' => 'required|string|max:20',
|
|
'nombre' => 'required|string|max:100',
|
|
'apellido' => 'required|string|max:100',
|
|
'fecha_nacimiento' => 'required|date',
|
|
'id_club_actual' => 'nullable|integer|exists:clubes,id_club',
|
|
'id_club_origen' => 'required|integer|exists:clubes,id_club',
|
|
]);
|
|
|
|
// Verificar si el DNI ya existe. withTrashed: el índice UNIQUE de la BD considera
|
|
// también soft-deleted, así que la validación debe contemplarlos para no caer en error 1062.
|
|
$existente = Jugador::withTrashed()->where('documento', $data['documento'])->first();
|
|
if ($existente) {
|
|
if ($existente->trashed()) {
|
|
return back()->withInput()->withErrors([
|
|
'documento' => "Este DNI pertenece a un jugador que está en la papelera. Restauralo desde allí o contactá al superadmin."
|
|
]);
|
|
}
|
|
$clubNombre = $existente->clubActual ? $existente->clubActual->nombre : 'Sin Club';
|
|
return back()->withInput()->withErrors([
|
|
'documento' => "No se puede registrar al jugador dado que ya pertenece al club $clubNombre."
|
|
]);
|
|
}
|
|
|
|
if (session('admin_role') == 2) {
|
|
$data['id_club_actual'] = session('admin_id_club');
|
|
// Nota: id_club_origen podría ser el mismo o diferente,
|
|
// pero el Id del jugador siempre se basa en el de origen.
|
|
}
|
|
|
|
$data['nombre'] = strtoupper(trim($data['nombre']));
|
|
$data['apellido'] = strtoupper(trim($data['apellido']));
|
|
$data['activo'] = 0; // Se valida luego en /asociate
|
|
if (isset($data['fecha_nacimiento'])) {
|
|
$data['edad'] = \Carbon\Carbon::parse($data['fecha_nacimiento'])->age;
|
|
// $data['categoria'] ya no se asigna, ahora es dinámica
|
|
}
|
|
|
|
$idClub = $data['id_club_origen'];
|
|
$yearFull = \Carbon\Carbon::parse($data['fecha_nacimiento'])->format('Y');
|
|
$data['id_jugador'] = $this->generarIdJugador($idClub, $yearFull);
|
|
|
|
Jugador::create($data);
|
|
return redirect()->route('admin.jugadores.index')->with('admin_msg', 'Jugador creado correctamente. Puede activarse en /asociate.');
|
|
}
|
|
|
|
private function generarIdJugador($idClub, $yearFull)
|
|
{
|
|
$yearShort = \Carbon\Carbon::parse($yearFull . '-01-01')->format('y');
|
|
$prefix = $idClub . $yearShort;
|
|
$secuencia = $this->obtenerSiguienteSecuencia($idClub, $yearFull, $prefix);
|
|
|
|
return sprintf('%s%02d', $prefix, $secuencia);
|
|
}
|
|
|
|
private function obtenerSiguienteSecuencia($idClub, $yearFull, $prefix)
|
|
{
|
|
// withTrashed: incluir jugadores soft-deleted para no reusar IDs y chocar con la PK.
|
|
$ultimoId = (string)Jugador::withTrashed()
|
|
->where('id_jugador', 'LIKE', $prefix . '%')
|
|
->whereRaw("id_jugador REGEXP '^[0-9]+$'")
|
|
->orderByRaw('CAST(id_jugador AS UNSIGNED) DESC')
|
|
->value('id_jugador');
|
|
|
|
$secuencia = 1;
|
|
if ($ultimoId && str_starts_with($ultimoId, $prefix)) {
|
|
$secuenciaStr = substr($ultimoId, strlen($prefix));
|
|
$secuencia = (int)$secuenciaStr + 1;
|
|
}
|
|
return $secuencia;
|
|
}
|
|
|
|
public function jugadoresImport(Request $request)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$request->validate([
|
|
'csv_file' => 'required|file|mimes:csv,txt',
|
|
'id_club' => session('admin_role') == 1 ? 'nullable|integer' : 'nullable'
|
|
]);
|
|
|
|
$file = $request->file('csv_file');
|
|
$handle = fopen($file->getRealPath(), 'r');
|
|
|
|
$successCount = 0;
|
|
$omittedCount = 0;
|
|
$errorCount = 0;
|
|
$teamAssignedCount = 0;
|
|
$errors = [];
|
|
|
|
// Determinar Club Target
|
|
$targetClubId = session('admin_role') == 2 ? session('admin_id_club') : ($request->input('id_club') ?? 99);
|
|
|
|
$currentCategory = null;
|
|
$formatType = 'legacy'; // legacy, cab, internal
|
|
$localSequences = []; // Cache para evitar duplicados en el mismo loop
|
|
|
|
// Leer primeras líneas para detectar formato (el CAB puede empezar con una categoría)
|
|
$detectLines = [];
|
|
for ($i=0; $i<5; $i++) {
|
|
$l = fgets($handle);
|
|
if ($l) $detectLines[] = $l;
|
|
}
|
|
rewind($handle);
|
|
|
|
$fullContentSample = implode("\n", $detectLines);
|
|
if (str_contains($fullContentSample, 'ESTADO LICENCIA') || str_contains($fullContentSample, 'TIPO') || str_contains($fullContentSample, 'JUGADOR')) {
|
|
$formatType = 'cab';
|
|
} elseif (str_contains($fullContentSample, 'DNI;Apellido;Nombre;Fecha Nacimiento')) {
|
|
$formatType = 'internal';
|
|
}
|
|
|
|
while (($row = fgetcsv($handle, 1000, ";")) !== FALSE) {
|
|
if (empty(array_filter($row))) continue;
|
|
|
|
// --- DETECCION DE CATEGORIA (Solo CAB) ---
|
|
// Si la primera columna tiene texto y el resto está vacío, es una categoría (ej: "PREMINI B;;;;;")
|
|
if ($formatType == 'cab' && !empty($row[0]) && empty(array_filter(array_slice($row, 1)))) {
|
|
$currentCategory = trim($row[0]);
|
|
continue;
|
|
}
|
|
|
|
// Saltos de cabecera
|
|
if ($formatType == 'cab' && $row[0] == 'ESTADO LICENCIA') continue;
|
|
if ($formatType == 'internal' && $row[0] == 'DNI') continue;
|
|
|
|
try {
|
|
$dni = ''; $apellido = ''; $nombre = ''; $fechaNac = '';
|
|
$idClubOrigen = $targetClubId;
|
|
$idClubActual = $targetClubId;
|
|
$isJugador = true;
|
|
|
|
if ($formatType == 'cab') {
|
|
// Formato: ESTADO LICENCIA;NIF;NOMBRE;FECHA_ALTA;BAJA;TIPO;FECHA NACIMIENTO;NACIONALIDAD
|
|
if (count($row) < 7) continue;
|
|
$dni = trim($row[1]);
|
|
$fullName = trim($row[2]); // "APELLIDO, NOMBRE"
|
|
$tipo = strtoupper(trim($row[5]));
|
|
$fechaRaw = trim($row[6]); // d/m/Y
|
|
|
|
if ($tipo !== 'JUGADOR') {
|
|
$isJugador = false;
|
|
}
|
|
|
|
if ($isJugador) {
|
|
$parts = explode(',', $fullName);
|
|
$apellido = trim($parts[0]);
|
|
$nombre = isset($parts[1]) ? trim($parts[1]) : '';
|
|
|
|
// Parsear d/m/Y
|
|
$dateParts = explode('/', $fechaRaw);
|
|
if (count($dateParts) == 3) {
|
|
$fechaNac = "{$dateParts[2]}-{$dateParts[1]}-{$dateParts[0]}";
|
|
}
|
|
}
|
|
} elseif ($formatType == 'internal') {
|
|
// Formato: DNI; Apellido; Nombre; Fecha Nacimiento (d/m/Y); ID Club Origen; ID Club Actual; Categoria; Activo
|
|
$dni = trim($row[0]);
|
|
$apellido = trim($row[1]);
|
|
$nombre = trim($row[2]);
|
|
$fechaRaw = trim($row[3]);
|
|
$idClubOrigen = isset($row[4]) ? (int)trim($row[4]) : $targetClubId;
|
|
$idClubActual = isset($row[5]) ? (int)trim($row[5]) : $targetClubId;
|
|
|
|
$dateParts = explode('/', $fechaRaw);
|
|
if (count($dateParts) == 3) {
|
|
$fechaNac = "{$dateParts[2]}-{$dateParts[1]}-{$dateParts[0]}";
|
|
}
|
|
} else {
|
|
// Formato Legado: DNI; Apellido; Nombre; ddmmaaaa; id_club_origen
|
|
if (count($row) < 4) continue;
|
|
$dni = trim($row[0]);
|
|
|
|
// SEGURIDAD: Si el DNI no es numérico en formato legado, probablemente sea una cabecera o ruido
|
|
if (!is_numeric($dni)) continue;
|
|
|
|
$apellido = trim($row[1]);
|
|
$nombre = trim($row[2]);
|
|
$fechaRaw = trim($row[3]); // ddmmaaaa
|
|
$idClubOrigen = isset($row[4]) ? (int)trim($row[4]) : $targetClubId;
|
|
|
|
if (strlen($fechaRaw) == 8) {
|
|
$fechaNac = substr($fechaRaw, 4, 4) . "-" . substr($fechaRaw, 2, 2) . "-" . substr($fechaRaw, 0, 2);
|
|
}
|
|
}
|
|
|
|
if (!$isJugador || !$dni || !$apellido || !$nombre || !$fechaNac) continue;
|
|
|
|
// Verificar existencia
|
|
$jugador = Jugador::where('documento', $dni)->first();
|
|
$anioNac = date('Y', strtotime($fechaNac));
|
|
|
|
$data = [
|
|
'documento' => $dni,
|
|
'apellido' => strtoupper(trim($apellido)),
|
|
'nombre' => strtoupper(trim($nombre)),
|
|
'fecha_nacimiento' => $fechaNac,
|
|
'id_club_origen' => $idClubOrigen,
|
|
'id_club_actual' => $idClubActual,
|
|
'edad' => \Carbon\Carbon::parse($fechaNac)->age,
|
|
];
|
|
|
|
if (!$jugador) {
|
|
// Generar ID con cache local para evitar colisiones en el mismo loop
|
|
$prefix = $idClubOrigen . date('y', strtotime($fechaNac));
|
|
if (!isset($localSequences[$prefix])) {
|
|
// Obtener la base inicial de la BDD
|
|
$localSequences[$prefix] = $this->obtenerSiguienteSecuencia($idClubOrigen, $anioNac, $prefix);
|
|
} else {
|
|
$localSequences[$prefix]++;
|
|
}
|
|
|
|
$data['id_jugador'] = sprintf('%s%02d', $prefix, $localSequences[$prefix]);
|
|
$data['activo'] = 0;
|
|
$jugador = Jugador::create($data);
|
|
$successCount++;
|
|
} else {
|
|
// El usuario prefiere NO pisar datos si el jugador ya existe
|
|
$omittedCount++;
|
|
}
|
|
|
|
// --- MATCHING DE EQUIPO ---
|
|
if ($currentCategory && $jugador) {
|
|
$equipo = Equipo::where('id_club', $idClubActual)
|
|
->where('categoria', 'LIKE', $currentCategory)
|
|
->first();
|
|
if ($equipo) {
|
|
// Evitar duplicados en pivot
|
|
if (!$equipo->jugadores()->where('jugador_equipo.id_jugador', $jugador->id_jugador)->exists()) {
|
|
$equipo->jugadores()->attach($jugador->id_jugador, ['fecha_alta' => now()]);
|
|
$teamAssignedCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
$errorCount++;
|
|
$errors[] = "Error en fila DNI $dni: " . $e->getMessage();
|
|
}
|
|
}
|
|
fclose($handle);
|
|
|
|
$msg = "Importación finalizada ({$formatType}). $successCount nuevos creados, $omittedCount ya registrados (no modificados).";
|
|
if ($teamAssignedCount > 0) $msg .= " $teamAssignedCount asignaciones a equipos realizadas.";
|
|
|
|
if ($errorCount > 0) {
|
|
return redirect()->route('admin.jugadores.index')->with('admin_msg', $msg)->with('admin_error', implode(" | ", array_slice($errors, 0, 5)));
|
|
}
|
|
|
|
return redirect()->route('admin.jugadores.index')->with('admin_msg', $msg);
|
|
}
|
|
|
|
public function jugadoresExport(Request $request)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
|
|
$headers = [
|
|
"Content-type" => "text/csv; charset=UTF-8",
|
|
"Content-Disposition" => "attachment; filename=jugadores_" . date('Ymd_His') . ".csv",
|
|
"Pragma" => "no-cache",
|
|
"Cache-Control" => "must-revalidate, post-check=0, pre-check=0",
|
|
"Expires" => "0"
|
|
];
|
|
|
|
$callback = function() {
|
|
$file = fopen('php://output', 'w');
|
|
// Añadir BOM para Excel (UTF-8)
|
|
fprintf($file, chr(0xEF).chr(0xBB).chr(0xBF));
|
|
|
|
// Cabeceras
|
|
fputcsv($file, [
|
|
'DNI', 'Apellido', 'Nombre', 'Fecha Nacimiento', 'ID Club Origen', 'ID Club Actual', 'Categoria', 'Activo'
|
|
], ";");
|
|
|
|
$query = Jugador::orderBy('apellido');
|
|
|
|
if (session('admin_role') == 2) {
|
|
$query->where('id_club_actual', session('admin_id_club'));
|
|
}
|
|
|
|
$jugadores = $query->get();
|
|
|
|
foreach ($jugadores as $j) {
|
|
fputcsv($file, [
|
|
$j->documento,
|
|
$j->apellido,
|
|
$j->nombre,
|
|
$j->fecha_nacimiento ? $j->fecha_nacimiento->format('d/m/Y') : '',
|
|
$j->id_club_origen ?? 99,
|
|
$j->id_club_actual ?? 99,
|
|
$j->categoria_calculada,
|
|
$j->activo ? 'SI' : 'NO'
|
|
], ";");
|
|
}
|
|
fclose($file);
|
|
};
|
|
|
|
return response()->stream($callback, 200, $headers);
|
|
}
|
|
|
|
public function jugadoresEdit(Request $request, $id)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$jugador = Jugador::findOrFail($id);
|
|
|
|
if (session('admin_role') == 2 && $jugador->id_club_actual != session('admin_id_club')) {
|
|
abort(403, 'No puedes editar un jugador que no pertenece a tu club.');
|
|
}
|
|
|
|
$clubes = session('admin_role') == 1 ? Club::orderBy('nombre')->get() : [];
|
|
return view('admin.jugadores.form', compact('jugador', 'clubes'));
|
|
}
|
|
|
|
public function jugadoresUpdate(Request $request, $id)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$jugador = Jugador::findOrFail($id);
|
|
|
|
if (session('admin_role') == 2 && $jugador->id_club_actual != session('admin_id_club')) {
|
|
abort(403, 'No puedes editar un jugador que no pertenece a tu club.');
|
|
}
|
|
|
|
$data = $request->validate([
|
|
'documento' => 'required|string|max:20|unique:jugadores,documento,' . $id . ',id_jugador',
|
|
'nombre' => 'required|string|max:100',
|
|
'apellido' => 'required|string|max:100',
|
|
'fecha_nacimiento' => 'required|date',
|
|
'id_club_actual' => 'nullable|integer|exists:clubes,id_club',
|
|
'id_club_origen' => 'nullable|integer|exists:clubes,id_club',
|
|
]);
|
|
|
|
if (session('admin_role') == 2) {
|
|
$data['id_club_actual'] = session('admin_id_club'); // Forzamos a no cambiarlo
|
|
}
|
|
|
|
if (isset($data['fecha_nacimiento'])) {
|
|
$data['edad'] = \Carbon\Carbon::parse($data['fecha_nacimiento'])->age;
|
|
}
|
|
|
|
$jugador->update($data);
|
|
return redirect()->route('admin.jugadores.index')->with('admin_msg', 'Jugador actualizado correctamente.');
|
|
}
|
|
|
|
public function jugadoresDestroy(Request $request, $id)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$jugador = Jugador::findOrFail($id);
|
|
|
|
if (session('admin_role') == 2 && $jugador->id_club_actual != session('admin_id_club')) {
|
|
abort(403, 'No tienes permiso para eliminar este jugador.');
|
|
}
|
|
|
|
$jugador->delete();
|
|
return redirect()->route('admin.jugadores.index')->with('admin_msg', 'Jugador eliminado correctamente.');
|
|
}
|
|
|
|
// ══════════════════════════════════
|
|
// EVENTOS
|
|
// ══════════════════════════════════
|
|
public function escanearQr(Request $request)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
|
|
$query = Evento::orderByRaw("
|
|
CAST(SUBSTRING_INDEX(nombre_evento, 'PARTIDO ', -1) AS UNSIGNED) ASC,
|
|
nombre_evento ASC,
|
|
fecha_evento ASC,
|
|
hora_inicio ASC
|
|
");
|
|
|
|
$ahora = \Carbon\Carbon::now();
|
|
$query->where(function($q) use ($ahora) {
|
|
$q->where(function($sub) {
|
|
$sub->whereNull('marcador_local')
|
|
->orWhereNull('marcador_visitante');
|
|
})
|
|
->orWhere('fecha_evento', '>', $ahora->toDateString())
|
|
->orWhere(function($q2) use ($ahora) {
|
|
$q2->where('fecha_evento', '=', $ahora->toDateString())
|
|
->where('hora_fin', '>', $ahora->toTimeString());
|
|
});
|
|
});
|
|
|
|
if (session('admin_role') == 2) {
|
|
$idClub = session('admin_id_club');
|
|
$query->where(function ($q) use ($idClub) {
|
|
$q->whereHas('equipoLocal', function ($q2) use ($idClub) {
|
|
$q2->where('id_club', $idClub);
|
|
})->orWhereHas('equipoVisitante', function ($q2) use ($idClub) {
|
|
$q2->where('id_club', $idClub);
|
|
});
|
|
});
|
|
}
|
|
|
|
$eventos = $query->get();
|
|
return view('admin.escanear_qr', compact('eventos'));
|
|
}
|
|
|
|
public function eventosIndex(Request $request)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
|
|
$estado = $request->get('estado', 'todos');
|
|
$query = Evento::with(['equipoLocal.club', 'equipoVisitante.club'])->orderBy('fecha_evento', 'desc');
|
|
|
|
$tz = 'America/Argentina/Buenos_Aires';
|
|
$ahora = \Carbon\Carbon::now($tz);
|
|
|
|
// Filtro por estado
|
|
if ($estado == 'finalizados') {
|
|
$query->whereNotNull('marcador_local')
|
|
->whereNotNull('marcador_visitante')
|
|
->where(function($q) use ($ahora) {
|
|
$q->where('fecha_evento', '<', $ahora->toDateString())
|
|
->orWhere(function($q2) use ($ahora) {
|
|
$q2->where('fecha_evento', '=', $ahora->toDateString())
|
|
->where('hora_fin', '<=', $ahora->toTimeString());
|
|
});
|
|
});
|
|
} elseif ($estado == 'pendientes') {
|
|
$query->where(function($q) use ($ahora) {
|
|
$q->whereNull('marcador_local')
|
|
->orWhereNull('marcador_visitante')
|
|
->orWhere('fecha_evento', '>', $ahora->toDateString())
|
|
->orWhere(function($q2) use ($ahora) {
|
|
$q2->where('fecha_evento', '=', $ahora->toDateString())
|
|
->where('hora_fin', '>', $ahora->toTimeString());
|
|
});
|
|
});
|
|
}
|
|
|
|
if (session('admin_role') == 2) {
|
|
$idClub = session('admin_id_club');
|
|
$query->where(function($q) use ($idClub) {
|
|
$q->whereHas('equipoLocal', function ($q2) use ($idClub) {
|
|
$q2->where('id_club', $idClub);
|
|
})->orWhereHas('equipoVisitante', function ($q2) use ($idClub) {
|
|
$q2->where('id_club', $idClub);
|
|
});
|
|
});
|
|
}
|
|
|
|
$eventos = $query->orderBy('id_evento', 'desc')->get();
|
|
return view('admin.eventos.index', compact('eventos', 'estado'));
|
|
}
|
|
|
|
public function eventosCreate(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$equipos = Equipo::with('club')->get();
|
|
$torneos = \App\Models\Torneo::orderBy('nombre')->get();
|
|
$torneoEquipos = DB::table('torneo_equipo')->get();
|
|
return view('admin.eventos.form', ['evento' => null, 'equipos' => $equipos, 'torneos' => $torneos, 'torneoEquipos' => $torneoEquipos]);
|
|
}
|
|
|
|
public function eventosStore(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$data = $request->validate([
|
|
'nombre_evento' => 'nullable|string|max:200',
|
|
'id_torneo' => 'nullable|integer|exists:torneos,id',
|
|
'fecha_evento' => 'required|date',
|
|
'hora_inicio' => 'required',
|
|
'hora_fin' => 'required',
|
|
'sede' => 'required|string|max:200',
|
|
'id_equipo_local' => 'required|integer|exists:equipos,id_equipo',
|
|
'id_equipo_visitante' => 'required|integer|exists:equipos,id_equipo',
|
|
'precio' => 'nullable|numeric|min:0',
|
|
'marcador_local' => 'nullable|integer|min:0',
|
|
'marcador_visitante' => 'nullable|integer|min:0',
|
|
]);
|
|
|
|
// Validaciones Deportivas
|
|
$local = Equipo::findOrFail($data['id_equipo_local']);
|
|
$visit = Equipo::findOrFail($data['id_equipo_visitante']);
|
|
|
|
if ($local->categoria != $visit->categoria) {
|
|
return back()->withInput()->withErrors(['id_equipo_visitante' => 'Los equipos deben pertenecer a la misma categoría.']);
|
|
}
|
|
|
|
if (!empty($data['id_torneo'])) {
|
|
$torneo = \App\Models\Torneo::findOrFail($data['id_torneo']);
|
|
$localInTorneo = $torneo->equipos()->where('equipos.id_equipo', $data['id_equipo_local'])->first();
|
|
$visitInTorneo = $torneo->equipos()->where('equipos.id_equipo', $data['id_equipo_visitante'])->first();
|
|
|
|
if (!$localInTorneo || !$visitInTorneo) {
|
|
return back()->withInput()->withErrors(['id_torneo' => 'Uno o ambos equipos no están inscritos en este torneo.']);
|
|
}
|
|
|
|
if ($localInTorneo->pivot->grupo != $visitInTorneo->pivot->grupo) {
|
|
return back()->withInput()->withErrors(['id_equipo_visitante' => 'Los equipos deben pertenecer al mismo grupo dentro del torneo.']);
|
|
}
|
|
}
|
|
|
|
// Autogenerar ID
|
|
$data['id_evento'] = bin2hex(random_bytes(4)); // 8 caracteres
|
|
|
|
if (empty($data['nombre_evento'])) {
|
|
$grupoName = 'General';
|
|
if (!empty($data['id_torneo'])) {
|
|
$rel = DB::table('torneo_equipo')
|
|
->where('id_torneo', $data['id_torneo'])
|
|
->where('id_equipo', $data['id_equipo_local'])
|
|
->first();
|
|
if ($rel) $grupoName = $rel->grupo ?? 'General';
|
|
}
|
|
$data['nombre_evento'] = ($local->club->nombre ?? 'Local') . ' vs ' . ($visit->club->nombre ?? 'Visitante') . " ({$grupoName})";
|
|
}
|
|
|
|
Evento::create($data);
|
|
return redirect()->route('admin.eventos.index')->with('admin_msg', 'Evento creado correctamente.');
|
|
}
|
|
|
|
public function eventosEdit(Request $request, $id)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$evento = Evento::findOrFail($id);
|
|
|
|
if (session('admin_role') == 2) {
|
|
$idClub = session('admin_id_club');
|
|
$isLocal = $evento->equipoLocal && $evento->equipoLocal->id_club == $idClub;
|
|
$isVisitante = $evento->equipoVisitante && $evento->equipoVisitante->id_club == $idClub;
|
|
if (!$isLocal && !$isVisitante) {
|
|
abort(403, 'No tienes permiso para editar este evento.');
|
|
}
|
|
}
|
|
$equipos = Equipo::with('club')->get();
|
|
$torneos = \App\Models\Torneo::orderBy('nombre')->get();
|
|
$torneoEquipos = DB::table('torneo_equipo')->get();
|
|
return view('admin.eventos.form', compact('evento', 'equipos', 'torneos', 'torneoEquipos'));
|
|
}
|
|
|
|
public function eventosUpdate(Request $request, $id)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$evento = Evento::findOrFail($id);
|
|
|
|
if (session('admin_role') == 2) {
|
|
$idClub = session('admin_id_club');
|
|
$isLocal = $evento->equipoLocal && $evento->equipoLocal->id_club == $idClub;
|
|
$isVisitante = $evento->equipoVisitante && $evento->equipoVisitante->id_club == $idClub;
|
|
if (!$isLocal && !$isVisitante) {
|
|
abort(403, 'No tienes permiso para editar este evento.');
|
|
}
|
|
|
|
// Club admins only edit limite_qr_jugador
|
|
$data = $request->validate([
|
|
'limite_qr_jugador' => 'required|integer|min:0',
|
|
]);
|
|
$evento->update(['limite_qr_jugador' => $data['limite_qr_jugador']]);
|
|
return redirect()->route('admin.eventos.index')->with('admin_msg', 'Pase de QRs para el evento actualizado.');
|
|
}
|
|
|
|
// Si es edición, restringimos los campos permitidos según solicitud del usuario
|
|
if ($id) {
|
|
$data = $request->validate([
|
|
'fecha_evento' => 'required|date',
|
|
'hora_inicio' => 'required',
|
|
'hora_fin' => 'required',
|
|
'limite_qr_jugador' => 'nullable|integer|min:0',
|
|
'marcador_local' => 'nullable|integer|min:0',
|
|
'marcador_visitante' => 'nullable|integer|min:0',
|
|
'nombre_evento' => 'nullable|string|max:200',
|
|
]);
|
|
|
|
// Conservar valores que no deberían cambiar para que la validación posterior (deporte) no falle
|
|
$data['id_torneo'] = $evento->id_torneo;
|
|
$data['id_equipo_local'] = $evento->id_equipo_local;
|
|
$data['id_equipo_visitante'] = $evento->id_equipo_visitante;
|
|
|
|
// Si el nombre viene vacío en edición, también lo autogeneramos?
|
|
// El usuario pidió que se setee automáticamente al no poner nada.
|
|
if (empty($data['nombre_evento'])) {
|
|
$local = Equipo::findOrFail($data['id_equipo_local']);
|
|
$visit = Equipo::findOrFail($data['id_equipo_visitante']);
|
|
$grupoName = 'General';
|
|
if (!empty($data['id_torneo'])) {
|
|
$rel = DB::table('torneo_equipo')
|
|
->where('id_torneo', $data['id_torneo'])
|
|
->where('id_equipo', $data['id_equipo_local'])
|
|
->first();
|
|
if ($rel) $grupoName = $rel->grupo ?? 'General';
|
|
}
|
|
$data['nombre_evento'] = ($local->club->nombre ?? 'Local') . ' vs ' . ($visit->club->nombre ?? 'Visitante') . " ({$grupoName})";
|
|
}
|
|
|
|
$data['sede'] = $evento->sede;
|
|
$data['precio'] = $evento->precio;
|
|
} else {
|
|
$data = $request->validate([
|
|
'nombre_evento' => 'nullable|string|max:200',
|
|
'id_torneo' => 'nullable|integer|exists:torneos,id',
|
|
'fecha_evento' => 'required|date',
|
|
'hora_inicio' => 'required',
|
|
'hora_fin' => 'required',
|
|
'sede' => 'nullable|string|max:200',
|
|
'id_equipo_local' => 'nullable|integer|exists:equipos,id_equipo',
|
|
'id_equipo_visitante' => 'nullable|integer|exists:equipos,id_equipo',
|
|
'precio' => 'nullable|numeric|min:0',
|
|
'limite_qr_jugador' => 'nullable|integer|min:0',
|
|
'marcador_local' => 'nullable|integer|min:0',
|
|
'marcador_visitante' => 'nullable|integer|min:0',
|
|
]);
|
|
}
|
|
|
|
// Validaciones Deportivas
|
|
if ($data['id_equipo_local'] && $data['id_equipo_visitante']) {
|
|
$local = Equipo::findOrFail($data['id_equipo_local']);
|
|
$visit = Equipo::findOrFail($data['id_equipo_visitante']);
|
|
|
|
if ($local->categoria != $visit->categoria) {
|
|
return back()->withInput()->withErrors(['id_equipo_visitante' => 'Los equipos deben pertenecer a la misma categoría.']);
|
|
}
|
|
|
|
if (!empty($data['id_torneo'])) {
|
|
$torneo = \App\Models\Torneo::findOrFail($data['id_torneo']);
|
|
$localInTorneo = $torneo->equipos()->where('equipos.id_equipo', $data['id_equipo_local'])->first();
|
|
$visitInTorneo = $torneo->equipos()->where('equipos.id_equipo', $data['id_equipo_visitante'])->first();
|
|
|
|
if (!$localInTorneo || !$visitInTorneo) {
|
|
return back()->withInput()->withErrors(['id_torneo' => 'Uno o ambos equipos no están inscritos en este torneo.']);
|
|
}
|
|
|
|
if ($localInTorneo->pivot->grupo != $visitInTorneo->pivot->grupo) {
|
|
return back()->withInput()->withErrors(['id_equipo_visitante' => 'Los equipos deben pertenecer al mismo grupo dentro del torneo.']);
|
|
}
|
|
}
|
|
|
|
// Autogenerar Nombre si es nulo
|
|
if (empty($data['nombre_evento'])) {
|
|
$data['nombre_evento'] = ($local->club->nombre ?? 'Local') . ' vs ' . ($visit->club->nombre ?? 'Visitante');
|
|
}
|
|
}
|
|
|
|
$evento->update($data);
|
|
return redirect()->route('admin.eventos.index')->with('admin_msg', 'Evento actualizado correctamente.');
|
|
}
|
|
|
|
public function eventosDestroy(Request $request, $id)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$evento = Evento::findOrFail($id);
|
|
|
|
// Al eliminar un evento, también eliminamos sus QRs para que no queden "huérfanos"
|
|
$evento->qrCodes()->delete();
|
|
|
|
$evento->delete();
|
|
return redirect()->route('admin.eventos.index')->with('admin_msg', 'Evento eliminado correctamente.');
|
|
}
|
|
|
|
// ══════════════════════════════════
|
|
// PROMOCIONES / LUGARES
|
|
// ══════════════════════════════════
|
|
public function promocionesIndex(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$promociones = Promocion::withCount('promoQrs')->orderBy('id', 'desc')->get();
|
|
return view('admin.promociones.index', compact('promociones'));
|
|
}
|
|
|
|
public function promocionesCreate(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
return view('admin.promociones.form', ['promocion' => null]);
|
|
}
|
|
|
|
public function promocionesStore(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($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_file' => 'nullable|image|max:2048',
|
|
]);
|
|
|
|
if ($request->hasFile('imagen_file')) {
|
|
$path = app(ImageOptimizer::class)->storeAndOptimize($request->file('imagen_file'), 'promos');
|
|
$data['imagen'] = $path;
|
|
}
|
|
unset($data['imagen_file']);
|
|
|
|
Promocion::create($data);
|
|
return redirect()->route('admin.promociones.index')->with('admin_msg', 'Promoción creada correctamente.');
|
|
}
|
|
|
|
public function promocionesEdit(Request $request, $id)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$promocion = Promocion::findOrFail($id);
|
|
return view('admin.promociones.form', compact('promocion'));
|
|
}
|
|
|
|
public function promocionesUpdate(Request $request, $id)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$promocion = Promocion::findOrFail($id);
|
|
$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_file' => 'nullable|image|max:2048',
|
|
]);
|
|
|
|
if ($request->hasFile('imagen_file')) {
|
|
$path = app(ImageOptimizer::class)->storeAndOptimize($request->file('imagen_file'), 'promos');
|
|
$data['imagen'] = $path;
|
|
}
|
|
unset($data['imagen_file']);
|
|
|
|
$promocion->update($data);
|
|
return redirect()->route('admin.promociones.index')->with('admin_msg', 'Promoción actualizada correctamente.');
|
|
}
|
|
|
|
public function promocionesDestroy(Request $request, $id)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$promocion = Promocion::findOrFail($id);
|
|
$promocion->delete();
|
|
return redirect()->route('admin.promociones.index')->with('admin_msg', 'Promoción eliminada correctamente.');
|
|
}
|
|
|
|
// ══════════════════════════════════
|
|
// NOTICIAS
|
|
// ══════════════════════════════════
|
|
public function noticiasIndex(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$noticias = Noticia::orderBy('fecha', 'desc')->orderBy('id', 'desc')->get();
|
|
return view('admin.noticias.index', compact('noticias'));
|
|
}
|
|
|
|
public function noticiasCreate(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$torneos = \App\Models\Torneo::orderBy('nombre')->get();
|
|
return view('admin.noticias.form', ['noticia' => null, 'torneos' => $torneos]);
|
|
}
|
|
|
|
public function noticiasStore(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$data = $request->validate([
|
|
'titulo' => 'required|string|max:200',
|
|
'contenido' => 'required',
|
|
'imagen_file' => 'nullable|image|max:5120',
|
|
'categoria' => 'nullable|string|max:50',
|
|
'id_torneo' => 'nullable|integer|exists:torneos,id',
|
|
]);
|
|
|
|
if ($request->hasFile('imagen_file')) {
|
|
$path = app(ImageOptimizer::class)->storeAndOptimize($request->file('imagen_file'), 'noticias');
|
|
$data['imagen'] = 'storage/' . $path;
|
|
}
|
|
unset($data['imagen_file']);
|
|
|
|
$data['fecha'] = now();
|
|
Noticia::create($data);
|
|
return redirect()->route('admin.noticias.index')->with('admin_msg', 'Noticia creada correctamente.');
|
|
}
|
|
|
|
public function noticiasEdit(Request $request, $id)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$noticia = Noticia::findOrFail($id);
|
|
$torneos = \App\Models\Torneo::orderBy('nombre')->get();
|
|
return view('admin.noticias.form', compact('noticia', 'torneos'));
|
|
}
|
|
|
|
public function noticiasUpdate(Request $request, $id)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$noticia = Noticia::findOrFail($id);
|
|
$data = $request->validate([
|
|
'titulo' => 'required|string|max:200',
|
|
'contenido' => 'required',
|
|
'imagen_file' => 'nullable|image|max:5120',
|
|
'categoria' => 'nullable|string|max:50',
|
|
'id_torneo' => 'nullable|integer|exists:torneos,id',
|
|
]);
|
|
|
|
if ($request->hasFile('imagen_file')) {
|
|
// Eliminar imagen anterior si existe
|
|
if ($noticia->imagen && !str_starts_with($noticia->imagen, 'http') && file_exists(public_path($noticia->imagen))) {
|
|
@unlink(public_path($noticia->imagen));
|
|
}
|
|
$path = app(ImageOptimizer::class)->storeAndOptimize($request->file('imagen_file'), 'noticias');
|
|
$data['imagen'] = 'storage/' . $path;
|
|
}
|
|
unset($data['imagen_file']);
|
|
|
|
$noticia->update($data);
|
|
return redirect()->route('admin.noticias.index')->with('admin_msg', 'Noticia actualizada correctamente.');
|
|
}
|
|
|
|
public function noticiasDestroy(Request $request, $id)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$noticia = Noticia::findOrFail($id);
|
|
$noticia->delete();
|
|
return redirect()->route('admin.noticias.index')->with('admin_msg', 'Noticia eliminada correctamente.');
|
|
}
|
|
|
|
// ══════════════════════════════════
|
|
// ESCANEAR / VALIDAR QR
|
|
// ══════════════════════════════════
|
|
public function validarQr(Request $request)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
$id_qr = $request->input('id_qr');
|
|
$id_evento = $request->input('id_evento');
|
|
|
|
if (!$id_qr) {
|
|
return response()->json(['valid' => false, 'message' => 'QR inválido.']);
|
|
}
|
|
|
|
$qr = QrCode::with('evento', 'jugador', 'aficionado')->where('id_qr', $id_qr)->first();
|
|
|
|
if (!$qr) {
|
|
return response()->json(['valid' => false, 'message' => 'QR no encontrado.']);
|
|
}
|
|
|
|
// Si se seleccionó un evento, exigir que coincida
|
|
if ($id_evento && (string)$qr->id_evento !== (string)$id_evento) {
|
|
return response()->json(['valid' => false, 'message' => 'Este QR corresponde a otro evento.']);
|
|
}
|
|
|
|
// Verificar vigencia del evento
|
|
if ($qr->evento) {
|
|
$ahora = \Carbon\Carbon::now();
|
|
|
|
$f = ($qr->evento->fecha_evento instanceof \Carbon\Carbon) ? $qr->evento->fecha_evento->format('Y-m-d') : substr($qr->evento->fecha_evento, 0, 10);
|
|
$h1 = ($qr->evento->hora_inicio instanceof \Carbon\Carbon) ? $qr->evento->hora_inicio->format('H:i:s') : $qr->evento->hora_inicio;
|
|
$h2 = ($qr->evento->hora_fin instanceof \Carbon\Carbon) ? $qr->evento->hora_fin->format('H:i:s') : $qr->evento->hora_fin;
|
|
|
|
$inicio = \Carbon\Carbon::parse("$f $h1");
|
|
$fin = \Carbon\Carbon::parse("$f $h2");
|
|
|
|
if ($fin->lessThanOrEqualTo($inicio)) {
|
|
$fin->addDay();
|
|
}
|
|
|
|
if ($ahora < $inicio) {
|
|
return response()->json(['valid' => false, 'message' => '⏳ El evento todavía no comenzó.']);
|
|
}
|
|
if ($ahora > $fin) {
|
|
return response()->json(['valid' => false, 'message' => 'Evento finalizado, QR inválido.']);
|
|
}
|
|
}
|
|
|
|
// Verificar escaneos restantes
|
|
if ((int)$qr->escaneos_restantes <= 0) {
|
|
return response()->json(['valid' => false, 'message' => '❌ QR ya utilizado.']);
|
|
}
|
|
|
|
// Decrementar escaneos (concurrency-safe via DB)
|
|
$affected = \Illuminate\Support\Facades\DB::table('qr_codes')
|
|
->where('id_qr', $id_qr)
|
|
->where('escaneos_restantes', '>', 0)
|
|
->when($id_evento, function ($query) use ($id_evento) {
|
|
return $query->where('id_evento', $id_evento);
|
|
})
|
|
->decrement('escaneos_restantes');
|
|
|
|
if ($affected === 0) {
|
|
return response()->json(['valid' => false, 'message' => '❌ QR ya utilizado.']);
|
|
}
|
|
|
|
// Info del titular según tipo de QR
|
|
$titular = '';
|
|
$categoriaNombre = '';
|
|
|
|
if ($qr->jugador) {
|
|
$categoriaNombre = $qr->jugador->categoria_calculada;
|
|
}
|
|
|
|
if ($qr->tipo_qr === 'invitado' && $qr->jugador) {
|
|
$titular = 'Jugador: ' . $qr->jugador->nombre . ' ' . $qr->jugador->apellido . ' (' . $categoriaNombre . ')';
|
|
} elseif ($qr->tipo_qr === 'publico') {
|
|
if ($qr->aficionado) {
|
|
$titular = 'Aficionado: ' . $qr->aficionado->nombre . ' ' . $qr->aficionado->apellido;
|
|
} else {
|
|
$titular = 'QR de entrada pública (sin referencia)';
|
|
}
|
|
} else {
|
|
$titular = $qr->jugador
|
|
? $qr->jugador->nombre . ' ' . $qr->jugador->apellido . ' (' . $categoriaNombre . ')'
|
|
: ($qr->aficionado ? $qr->aficionado->nombre . ' ' . $qr->aficionado->apellido : 'Desconocido');
|
|
}
|
|
|
|
$mensajeValido = '✅ Acceso válido — Jugador del evento';
|
|
|
|
if ($qr->tipo_qr === 'libre_50') {
|
|
$mensajeValido = '✅ Tenés descuento en la entrada, 50% (Jugador Categoría Libre)';
|
|
}
|
|
|
|
return response()->json([
|
|
'valid' => true,
|
|
'message' => $mensajeValido,
|
|
'data' => [
|
|
'evento' => $qr->evento ? $qr->evento->nombre_evento : $qr->id_evento,
|
|
'titular' => $titular,
|
|
'categoria' => $categoriaNombre,
|
|
'tipo' => $qr->tipo_qr,
|
|
'restantes' => ($qr->escaneos_restantes > 0 ? $qr->escaneos_restantes - 1 : 0),
|
|
],
|
|
]);
|
|
}
|
|
|
|
// ══════════════════════════════════
|
|
// CONFIGURACIÓN GENERAL
|
|
// ══════════════════════════════════
|
|
public function settingsIndex(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$diasExpiracion = \App\Models\Configuracion::get('dias_expiracion_eventos', 30);
|
|
$backupFreq = \App\Models\Configuracion::get('backup_frequency', 'daily');
|
|
$emailReportes = \App\Models\Configuracion::get('email_reportes', 'asociados@onapb.com');
|
|
$lastRun = \App\Models\Configuracion::get('last_scheduler_run', 'Nunca detectado');
|
|
|
|
return view('admin.settings', compact('diasExpiracion', 'backupFreq', 'lastRun', 'emailReportes'));
|
|
}
|
|
|
|
public function settingsUpdate(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$data = $request->validate([
|
|
'dias_expiracion_eventos' => 'required|integer|min:1',
|
|
'backup_frequency' => 'required|string|in:daily,weekly,monthly',
|
|
'email_reportes' => 'required|email',
|
|
]);
|
|
|
|
\App\Models\Configuracion::set('dias_expiracion_eventos', $data['dias_expiracion_eventos'], 'Días de antigüedad para borrar eventos y QRs');
|
|
\App\Models\Configuracion::set('backup_frequency', $data['backup_frequency'], 'Frecuencia de backups automáticos (daily, weekly, monthly)');
|
|
\App\Models\Configuracion::set('email_reportes', $data['email_reportes'], 'Email principal para recibir reportes del sistema');
|
|
|
|
return back()->with('admin_msg', 'Configuración actualizada correctamente.');
|
|
}
|
|
|
|
public function runManualTask(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$command = $request->input('command');
|
|
|
|
try {
|
|
switch ($command) {
|
|
case 'cleanup':
|
|
\Illuminate\Support\Facades\Artisan::call('app:cleanup-old-events');
|
|
$msg = '✅ Tarea de limpieza ejecutada.';
|
|
break;
|
|
case 'backup':
|
|
// Usamos backup:run para forzar un backup completo
|
|
\Illuminate\Support\Facades\Artisan::call('backup:run');
|
|
$msg = '✅ Proceso de backup iniciado.';
|
|
break;
|
|
case 'report':
|
|
\Illuminate\Support\Facades\Artisan::call('reportes:semanal');
|
|
$msg = '✅ Informe semanal enviado.';
|
|
break;
|
|
default:
|
|
throw new \Exception('Comando no reconocido.');
|
|
}
|
|
|
|
return back()->with('admin_msg', $msg . ' (Salida: ' . \Illuminate\Support\Facades\Artisan::output() . ')');
|
|
} catch (\Exception $e) {
|
|
\Illuminate\Support\Facades\Log::error("Error ejecutando comando manual: " . $e->getMessage());
|
|
return back()->with('admin_error', 'Error al ejecutar tarea: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
// ══════════════════════════════════
|
|
// SPONSORS
|
|
// ══════════════════════════════════
|
|
public function sponsorsIndex(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$sponsors = Sponsor::orderBy('orden')->latest()->get();
|
|
return view('admin.sponsors.index', compact('sponsors'));
|
|
}
|
|
|
|
public function sponsorsCreate(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
return view('admin.sponsors.form');
|
|
}
|
|
|
|
public function sponsorsStore(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$data = $request->validate([
|
|
'nombre' => 'required|string|max:100',
|
|
'imagen' => 'required|image|max:5120',
|
|
'url' => 'nullable|url|max:255',
|
|
'activo' => 'nullable|boolean',
|
|
'orden' => 'nullable|integer',
|
|
]);
|
|
|
|
if ($request->hasFile('imagen')) {
|
|
$path = app(ImageOptimizer::class)->storeAndOptimize($request->file('imagen'), 'sponsors');
|
|
$data['imagen'] = 'storage/' . $path;
|
|
}
|
|
|
|
$data['activo'] = $request->has('activo');
|
|
$data['orden'] = $data['orden'] ?? 0;
|
|
|
|
Sponsor::create($data);
|
|
|
|
return redirect()->route('admin.sponsors.index')->with('admin_msg', 'Sponsor creado correctamente.');
|
|
}
|
|
|
|
public function sponsorsEdit(Request $request, $id)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$sponsor = Sponsor::findOrFail($id);
|
|
return view('admin.sponsors.form', compact('sponsor'));
|
|
}
|
|
|
|
public function sponsorsUpdate(Request $request, $id)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$sponsor = Sponsor::findOrFail($id);
|
|
|
|
$data = $request->validate([
|
|
'nombre' => 'required|string|max:100',
|
|
'imagen' => 'nullable|image|max:5120',
|
|
'url' => 'nullable|url|max:255',
|
|
'activo' => 'nullable|boolean',
|
|
'orden' => 'nullable|integer',
|
|
]);
|
|
|
|
if ($request->hasFile('imagen')) {
|
|
// Eliminar imagen anterior si existe
|
|
if ($sponsor->imagen && file_exists(public_path($sponsor->imagen))) {
|
|
@unlink(public_path($sponsor->imagen));
|
|
}
|
|
|
|
$path = app(ImageOptimizer::class)->storeAndOptimize($request->file('imagen'), 'sponsors');
|
|
$data['imagen'] = 'storage/' . $path;
|
|
}
|
|
|
|
$data['activo'] = $request->has('activo');
|
|
$data['orden'] = $data['orden'] ?? 0;
|
|
|
|
$sponsor->update($data);
|
|
|
|
return redirect()->route('admin.sponsors.index')->with('admin_msg', 'Sponsor actualizado correctamente.');
|
|
}
|
|
|
|
public function sponsorsDestroy(Request $request, $id)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$sponsor = Sponsor::findOrFail($id);
|
|
|
|
if ($sponsor->imagen && file_exists(public_path($sponsor->imagen))) {
|
|
@unlink(public_path($sponsor->imagen));
|
|
}
|
|
|
|
$sponsor->delete();
|
|
|
|
return redirect()->route('admin.sponsors.index')->with('admin_msg', 'Sponsor eliminado correctamente.');
|
|
}
|
|
|
|
// ══════════════════════════════════
|
|
// TORNEOS
|
|
// ══════════════════════════════════
|
|
public function torneosIndex(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$torneos = \App\Models\Torneo::withCount('equipos')->latest()->get();
|
|
return view('admin.torneos.index', compact('torneos'));
|
|
}
|
|
|
|
public function torneosCreate(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
return view('admin.torneos.form', ['torneo' => null]);
|
|
}
|
|
|
|
public function torneosStore(Request $request)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$data = $request->validate([
|
|
'nombre' => 'required|string|max:100',
|
|
'fecha_inicio' => 'nullable|date',
|
|
'fecha_fin' => 'nullable|date',
|
|
]);
|
|
\App\Models\Torneo::create($data);
|
|
return redirect()->route('admin.torneos.index')->with('admin_msg', 'Torneo creado correctamente.');
|
|
}
|
|
|
|
public function torneosEdit(Request $request, $id)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$torneo = \App\Models\Torneo::with('equipos.club')->findOrFail($id);
|
|
$clubes = Club::with('equipos')->orderBy('nombre')->get();
|
|
return view('admin.torneos.form', compact('torneo', 'clubes'));
|
|
}
|
|
|
|
public function torneosUpdate(Request $request, $id)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$torneo = \App\Models\Torneo::findOrFail($id);
|
|
$data = $request->validate([
|
|
'nombre' => 'required|string|max:100',
|
|
'fecha_inicio' => 'nullable|date',
|
|
'fecha_fin' => 'nullable|date',
|
|
]);
|
|
$torneo->update($data);
|
|
return redirect()->route('admin.torneos.index')->with('admin_msg', 'Torneo actualizado correctamente.');
|
|
}
|
|
|
|
public function torneosDestroy(Request $request, $id)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$torneo = \App\Models\Torneo::findOrFail($id);
|
|
$torneo->delete();
|
|
return redirect()->route('admin.torneos.index')->with('admin_msg', 'Torneo eliminado correctamente.');
|
|
}
|
|
|
|
public function torneoAddEquipo(Request $request, $id)
|
|
{
|
|
$this->checkSuperAdmin($request);
|
|
$torneo = \App\Models\Torneo::findOrFail($id);
|
|
$data = $request->validate([
|
|
'id_equipo' => 'required|integer|exists:equipos,id_equipo',
|
|
'grupo' => 'nullable|string|max:50',
|
|
]);
|
|
|
|
if ($torneo->equipos()->where('torneo_equipo.id_equipo', $data['id_equipo'])->exists()) {
|
|
return back()->with('admin_error', 'El equipo ya está asignado a este torneo.');
|
|
}
|
|
|
|
$torneo->equipos()->attach($data['id_equipo'], ['grupo' => $data['grupo']]);
|
|
return back()->with('admin_msg', 'Equipo asignado al torneo correctamente.');
|
|
}
|
|
|
|
public function torneoRemoveEquipo($id, $id_equipo)
|
|
{
|
|
$this->checkSuperAdmin(request());
|
|
$torneo = \App\Models\Torneo::findOrFail($id);
|
|
$torneo->equipos()->detach($id_equipo);
|
|
return back()->with('admin_msg', 'Equipo removido del torneo.');
|
|
}
|
|
|
|
public function eventosStats($id)
|
|
{
|
|
$this->checkGeneralAdmin(request());
|
|
$evento = Evento::with(['equipoLocal.jugadores', 'equipoVisitante.jugadores'])->findOrFail($id);
|
|
|
|
// Restricción para Administradores de Club: Solo partidos donde participa su club
|
|
if (session('admin_role') == 2) {
|
|
$idClub = session('admin_id_club');
|
|
if ($evento->equipoLocal->id_club != $idClub && $evento->equipoVisitante->id_club != $idClub) {
|
|
abort(403, 'No tienes permiso para gestionar estadísticas de este partido.');
|
|
}
|
|
}
|
|
|
|
$stats = \App\Models\EventoJugador::where('id_evento', $id)->get()->keyBy('id_jugador');
|
|
return view('admin.eventos.stats', compact('evento', 'stats'));
|
|
}
|
|
|
|
public function eventosStatsStore(Request $request, $id)
|
|
{
|
|
$this->checkGeneralAdmin($request);
|
|
|
|
// Validación de seguridad para Edición
|
|
if (session('admin_role') == 2) {
|
|
$evento = Evento::findOrFail($id);
|
|
$idClub = session('admin_id_club');
|
|
if ($evento->equipoLocal->id_club != $idClub && $evento->equipoVisitante->id_club != $idClub) {
|
|
abort(403, 'No tienes permiso para editar estadísticas de este partido.');
|
|
}
|
|
}
|
|
|
|
$request->validate([
|
|
'stats' => 'required|array',
|
|
'stats.*.puntos' => 'required|integer|min:0',
|
|
'stats.*.faltas' => 'required|integer|min:0|max:5',
|
|
]);
|
|
|
|
foreach ($request->stats as $id_jugador => $vals) {
|
|
// Si es Admin de Club, solo puede guardar sus propios jugadores
|
|
if (session('admin_role') == 2) {
|
|
$jugador = Jugador::find($id_jugador);
|
|
if (!$jugador || $jugador->id_club_actual != session('admin_id_club')) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
\App\Models\EventoJugador::updateOrCreate(
|
|
['id_evento' => $id, 'id_jugador' => $id_jugador],
|
|
['puntos' => $vals['puntos'], 'faltas' => $vals['faltas']]
|
|
);
|
|
}
|
|
|
|
return redirect()->route('admin.eventos.index')->with('admin_msg', 'Estadísticas guardadas correctamente.');
|
|
}
|
|
}
|