⭐ Temporada Oficial 2025 ⭐

Liga deSoftbol

Parque Ecológico de Veracruz · La liga más competitiva del puerto

🏆 Categoría A🥈 Categoría B
↓ Desplázate
Noticias y Comunicados

Boletines Oficiales

Cargando...
Programa Oficial

Roles de Juego

Cargando...
Resultados de Temporada

Tabla de Posiciones

🏆
Categoría A
#EquipoJGPPCTRARC
Cargando...
🥈
Categoría B
#EquipoJGPPCTRARC
Cargando...
Únete a la Liga

Inscripción de Equipos

¿Listo para competir?
La Liga de Softbol del Parque Ecológico de Veracruz es la competencia más reconocida del puerto. Únete y compite en dos categorías.
1
Llena el Formulario

Datos del equipo y director técnico.

2
Revisión

Revisamos tu solicitud en 48 hrs.

3
Confirmación

Instrucciones de pago e inicio.

4
¡A jugar!

Apareces en el calendario oficial.

📋 Solicitud de Inscripción
🎉

¡Solicitud Enviada!

Tu solicitud fue recibida. El equipo administrativo se pondrá en contacto pronto.

Panel Admin
Liga de Softbol · PEV
Dashboard
🏟
0
Equipos
0
Jugadores
📅
0
Partidos
📢
0
Boletines
📋
0
Solicitudes
Equipos Cat. A
Equipos Cat. B
Solicitudes Pendientes
Partidos Programados
#FechaHoraEquiposCatSedeMarcadorEstado
🔴 Marcador En Vivo
Los cambios se publican instantáneamente.
TRANSMITIENDO
📅

No hay partidos hoy.

Todos los Partidos
FechaEquiposCatEstadoMarcador
Categoría A
#EquipoJGPPCTRARC
Generar Credencial
#NombreUsuarioEmailRolEquipoEstado
═══════════════════════════════════════════════════ */ /* ─── Estado del manager ─── */ let MGR_TEAM = null; let MGR_USER = null; let mgrGameTab = 'upcoming'; /* ─── Detectar si es manager al hacer login ─── */ function isManager(user) { // Es manager si tiene team_id y no es super admin (role_id > 2) return user && user.role_id > 2 && user.team_id; } async function loadManagerPanel() { try { const data = await api('GET', '/manager/me'); MGR_USER = data.user; MGR_TEAM = data.team; if (!MGR_TEAM) { toast('⚠ Tu cuenta no tiene equipo asignado. Contacta al administrador.'); doLogout(); return; } document.getElementById('mgr-team-name').textContent = MGR_TEAM.name; document.getElementById('mgr-user-name').textContent = MGR_USER.name + ' · Cat. ' + MGR_TEAM.category; document.getElementById('landing').style.display = 'none'; document.getElementById('adminPanel').style.display = 'none'; document.getElementById('managerPanel').style.display = 'block'; mgrNav('dashboard'); } catch (e) { toast('Error cargando panel: ' + e.message); doLogout(); } } function mgrNav(section) { ['dashboard','players','games','standings','settings'].forEach(function(s) { document.getElementById('mgr-' + s).style.display = s === section ? 'block' : 'none'; var btn = document.getElementById('mgr-btn-' + s); if (btn) btn.classList.toggle('btn-gd', s === section); if (btn) btn.classList.toggle('btn-ot', s !== section); }); var renders = { dashboard: loadMgrDashboard, players: loadMgrPlayers, games: function() { loadMgrGames(mgrGameTab); }, standings: loadMgrStandings, settings: loadMgrSettings }; if (renders[section]) renders[section](); } /* ─── DASHBOARD ─── */ async function loadMgrDashboard() { try { var tid = MGR_TEAM.id; var results = await Promise.all([ api('GET', '/manager/team/' + tid + '/players'), api('GET', '/manager/team/' + tid + '/games'), api('GET', '/manager/team/' + tid + '/standings') ]); var players = results[0], games = results[1], standData = results[2]; // Stats var today = new Date().toISOString().slice(0,10); var upcoming = games.filter(function(g) { return g.game_date >= today && g.status !== 'finished'; }); var finished = games.filter(function(g) { return g.status === 'finished'; }); var wins = finished.filter(function(g) { var myId = parseInt(tid); return (g.team_a_id === myId && g.score_a > g.score_b) || (g.team_b_id === myId && g.score_b > g.score_a); }).length; document.getElementById('mgr-stats-grid').innerHTML = mgrStatCard('⚾', players.length + '/22', 'Jugadores') + mgrStatCard('🏆', wins, 'Victorias') + mgrStatCard('📅', upcoming.length, 'Próx. Partidos') + mgrStatCard('📊', standData.myPosition || '–', 'Posición en Liga'); // Próximos partidos var upEl = document.getElementById('mgr-upcoming'); var upList = upcoming.slice(0, 3); upEl.innerHTML = upList.length ? upList.map(function(g) { return mgrGameRow(g, tid); }).join('') : '
📅

Sin partidos próximos

'; // Resultados recientes var resEl = document.getElementById('mgr-results'); var resList = finished.slice(0, 3); resEl.innerHTML = resList.length ? resList.map(function(g) { return mgrGameRow(g, tid); }).join('') : '

Sin resultados aún

'; // Mi posición var myS = standData.myStanding; var pos = standData.myPosition; document.getElementById('mgr-my-standing').innerHTML = myS ? ( '
' + '
' + pos + '
' + '
' + '
' + MGR_TEAM.name + '
' + '
' + mgrStatMini('J', myS.played) + mgrStatMini('G', myS.wins) + mgrStatMini('P', myS.losses) + mgrStatMini('PCT', pct(myS.wins, myS.losses)) + mgrStatMini('RA', myS.runs_for) + mgrStatMini('RC', myS.runs_against) + '
' + '
' + '
Cat. ' + standData.category + '
' + '
' ) : '

Sin datos de posición aún

'; } catch (e) { toast('Error: ' + e.message); } } function mgrStatCard(ico, val, lbl) { return '
' + ico + '
' + val + '
' + lbl + '
'; } function mgrStatMini(lbl, val) { return '
' + lbl + '
' + val + '
'; } function mgrGameRow(g, myTeamId) { var myId = parseInt(myTeamId); var isHome = g.team_a_id === myId; var rival = isHome ? g.team_b_name : g.team_a_name; var myScore = isHome ? g.score_a : g.score_b; var rivalScore = isHome ? g.score_b : g.score_a; var statusMap = {scheduled:'Programado', live:'EN VIVO', finished:'Finalizado'}; var tagMap = {scheduled:'tag-a', live:'tag-live', finished:'tag-ok'}; var result = ''; if (g.status === 'finished' && myScore !== null) { result = myScore > rivalScore ? 'V ' + myScore + '–' + rivalScore : myScore < rivalScore ? 'D ' + myScore + '–' + rivalScore : 'E ' + myScore + '–' + rivalScore; } return '
' + '
' + '
' + (isHome ? 'vs ' : '@ ') + rival + '
' + '
' + fmtDate(g.game_date) + ' · ' + (g.game_time||'').slice(0,5) + ' · ' + g.location + '
' + '
' + '
' + (result ? '' + result + '' : '') + '' + statusMap[g.status] + '' + '
' + '
'; } /* ─── JUGADORES ─── */ async function loadMgrPlayers() { try { var players = await api('GET', '/manager/team/' + MGR_TEAM.id + '/players'); var total = players.length; var byRole = { manager: players.filter(function(p) { return p.player_role === 'manager'; }), 'sub-manager': players.filter(function(p) { return p.player_role === 'sub-manager'; }), jugador: players.filter(function(p) { return p.player_role === 'jugador'; }), refuerzo: players.filter(function(p) { return p.player_role === 'refuerzo'; }) }; document.getElementById('mgr-player-count').textContent = total + '/22 jugadores registrados' + (total >= 22 ? ' — LÍMITE ALCANZADO' : ' (' + (22 - total) + ' disponibles)'); if (total >= 22) document.getElementById('btn-add-player').disabled = true; // Resumen de roles var roleColors = { manager:'var(--gold)', 'sub-manager':'#88aaff', jugador:'#88e8aa', refuerzo:'#e8aa88' }; var roleIcons = { manager:'⭐', 'sub-manager':'🔹', jugador:'⚾', refuerzo:'🔄' }; document.getElementById('mgr-roles-summary').innerHTML = ['manager','sub-manager','jugador','refuerzo'].map(function(role) { var count = byRole[role].length; var max = (role === 'manager' || role === 'sub-manager') ? 1 : '∞'; return '
' + '
' + roleIcons[role] + '
' + '
' + count + '/' + max + '
' + '
' + role + '
' + '
'; }).join(''); // Tabla var roleTagStyle = { manager: 'background:rgba(201,160,64,.2);color:var(--gold-lt);border:1px solid rgba(201,160,64,.3)', 'sub-manager':'background:rgba(80,130,220,.2);color:#88aaff;border:1px solid rgba(80,130,220,.3)', jugador: 'background:rgba(50,180,100,.15);color:#88e8aa;border:1px solid rgba(50,180,100,.3)', refuerzo: 'background:rgba(220,160,30,.15);color:#f0c060;border:1px solid rgba(220,160,30,.3)' }; document.getElementById('mgr-players-body').innerHTML = players.length ? players.map(function(p, i) { return '' + '' + (i+1) + '' + '' + p.name + '' + '' + (p.number||'—') + '' + '' + (p.position||'—') + '' + '' + (p.category||'—') + '' + '' + (p.player_role||'jugador') + '' + '' + (p.active ? 'Activo' : 'Inactivo') + '' + ' ' + ''; }).join('') : '
Sin jugadores registrados. Haz clic en "Registrar Jugador" para comenzar.'; } catch (e) { toast('Error: ' + e.message); } } function openAddMgrPlayer() { openModal('Registrar Jugador', mgrPlayerForm({})); } function mgrPlayerForm(p) { var roles = [ { value: 'jugador', label: '⚾ Jugador', desc: 'Jugador regular del equipo' }, { value: 'manager', label: '⭐ Manager', desc: 'Director técnico (máx. 1)' }, { value: 'sub-manager', label: '🔹 Sub-Manager', desc: 'Asistente técnico (máx. 1)' }, { value: 'refuerzo', label: '🔄 Refuerzo', desc: 'Jugador de refuerzo' } ]; return '
' + '
' + '
' + '
' + '
' + '
' + '' + '
' + (p.id ? '' : '') + '
' + '
' + '
'; } async function saveMgrPlayer(id) { var d = { name: document.getElementById('mp-n').value, number: parseInt(document.getElementById('mp-nu').value)||null, position: document.getElementById('mp-po').value, date_of_birth:document.getElementById('mp-d').value||null, player_role: document.getElementById('mp-role').value }; if (!d.name) { toast('⚠ Nombre requerido'); return; } try { if (id) { await api('PUT', '/manager/team/' + MGR_TEAM.id + '/players/' + id, d); } else { await api('POST', '/manager/team/' + MGR_TEAM.id + '/players', d); } closeModal(); loadMgrPlayers(); toast('✅ Jugador guardado'); } catch (e) { toast('⚠ ' + e.message); } } async function openEditMgrPlayer(id) { try { var players = await api('GET', '/manager/team/' + MGR_TEAM.id + '/players'); var p = players.find(function(x) { return x.id === id; }); if (p) openModal('Editar Jugador', mgrPlayerForm(p)); } catch (e) { toast('Error'); } } async function delMgrPlayer(id) { if (!confirm('¿Eliminar este jugador?')) return; try { await api('DELETE', '/manager/team/' + MGR_TEAM.id + '/players/' + id); loadMgrPlayers(); toast('🗑 Jugador eliminado'); } catch (e) { toast('Error: ' + e.message); } } /* ─── PARTIDOS ─── */ function setMgrGameTab(tab, btn) { mgrGameTab = tab; document.querySelectorAll('#mgr-games .tb').forEach(function(b) { b.classList.remove('active'); }); btn.classList.add('active'); loadMgrGames(tab); } async function loadMgrGames(tab) { try { var games = await api('GET', '/manager/team/' + MGR_TEAM.id + '/games'); var today = new Date().toISOString().slice(0,10); var filtered = games; if (tab === 'upcoming') filtered = games.filter(function(g) { return g.game_date >= today && g.status === 'scheduled'; }); if (tab === 'live') filtered = games.filter(function(g) { return g.status === 'live'; }); if (tab === 'finished') filtered = games.filter(function(g) { return g.status === 'finished'; }); var el = document.getElementById('mgr-games-list'); var myId = MGR_TEAM.id; if (!filtered.length) { el.innerHTML = '
📅

Sin partidos en esta categoría

'; return; } var statusMap = {scheduled:'Programado', live:'EN VIVO', finished:'Finalizado'}; var tagMap = {scheduled:'tag-a', live:'tag-live', finished:'tag-ok'}; el.innerHTML = filtered.map(function(g) { var isHome = g.team_a_id === myId; var myScore = isHome ? g.score_a : g.score_b; var rvScore = isHome ? g.score_b : g.score_a; var won = g.status === 'finished' && myScore !== null && myScore > rvScore; var lost = g.status === 'finished' && myScore !== null && myScore < rvScore; var cardBorder = won ? 'rgba(50,180,100,.4)' : lost ? 'rgba(180,40,40,.4)' : 'var(--border)'; return '
' + '
' + '📅 ' + fmtDate(g.game_date) + ' · ' + (g.game_time||'').slice(0,5) + '' + '' + statusMap[g.status] + '' + '
' + '
' + '
' + '
' + '
' + g.team_a_name + '
' + '
' + '
' + (g.status==='finished'&&myScore!==null ? '
' + g.score_a + ' ' + g.score_b + '
' : '
VS
') + '
' + '
' + '
' + '
' + g.team_b_name + '
' + '
' + '
' + (won ? '
🏆 VICTORIA
' : '') + (lost ? '
DERROTA
' : '') + '
📍 ' + g.location + 'Cat. ' + g.category + '
' + '
'; }).join(''); } catch (e) { toast('Error cargando partidos'); } } /* ─── POSICIONES ─── */ async function loadMgrStandings() { try { var data = await api('GET', '/manager/team/' + MGR_TEAM.id + '/standings'); var rows = data.standings; var myId = MGR_TEAM.id; var pcFn = function(i) { return i===0?'g1':i===1?'g2':i===2?'g3':''; }; document.getElementById('mgr-standings-table').innerHTML = '
' + '
' + (data.category==='A'?'🏆':'🥈') + '' + '
Categoría ' + data.category + '
' + '
' + '' + '' + (rows.length ? rows.map(function(s, i) { var isMine = s.team_id === myId; return '' + '' + '' + '' + '' + '' + '' + '' + '' + ''; }).join('') : '') + '
#EquipoJGPPCTRARC
' + (i+1) + '
' + '' + (isMine ? '' + s.team_name + ' ★' : s.team_name) + '
' + s.played + '' + s.wins + '' + s.losses + '' + pct(s.wins, s.losses) + '' + s.runs_for + '' + s.runs_against + '
Sin datos
'; } catch (e) { toast('Error'); } } /* ─── SETTINGS ─── */ async function loadMgrSettings() { // Info del equipo var t = MGR_TEAM; document.getElementById('mgr-team-info').innerHTML = '
Nombre: ' + t.name + '
' + '
Categoría: Cat. ' + t.category + '
' + '
Director Técnico: ' + (t.coach||'—') + '
' + '
Fundado: ' + (t.founded||'—') + '
' + '
Estado: ' + (t.active ? '✅ Activo' : '❌ Inactivo') + '
' + '
Para modificar información del equipo contacta al administrador.
'; // Preview logo actual if (t.logo_url) { document.getElementById('mgr-logo-preview').innerHTML = ''; } } function previewLogoFile(input) { if (!input.files || !input.files[0]) return; var file = input.files[0]; if (file.size > 3 * 1024 * 1024) { toast('⚠ La imagen no debe superar 3MB'); input.value = ''; return; } var reader = new FileReader(); reader.onload = function(e) { document.getElementById('mgr-logo-preview').innerHTML = ''; document.getElementById('btn-upload-logo').disabled = false; }; reader.readAsDataURL(file); } async function uploadLogo() { var input = document.getElementById('logo-file'); if (!input.files || !input.files[0]) return; var btn = document.getElementById('btn-upload-logo'); btn.disabled = true; btn.textContent = '⬆ Subiendo...'; try { var formData = new FormData(); formData.append('logo', input.files[0]); var r = await fetch('/api/manager/team/' + MGR_TEAM.id + '/logo', { method: 'POST', headers: { 'Authorization': 'Bearer ' + TOKEN }, body: formData }); var data = await r.json(); if (!r.ok) throw new Error(data.error); MGR_TEAM = data; toast('✅ Logo actualizado'); btn.textContent = '✅ Logo subido'; } catch (e) { toast('Error: ' + e.message); btn.disabled = false; btn.textContent = '⬆ Subir Logo'; } } /* ═══ LINEUP + MODO DIA ═══ */ /* ═══════════════════════════════════════════════════════════════ LINEUP MANAGER + MODO DÍA/NOCHE Agregar al index.html antes del cierre ═══════════════════════════════════════════════════════════════ */ /* ─── MODO DÍA/NOCHE ──────────────────────────────────────── */ (function initTheme() { var saved = localStorage.getItem('liga_theme') || 'dark'; document.documentElement.setAttribute('data-theme', saved); })(); function toggleTheme() { var current = document.documentElement.getAttribute('data-theme'); var next = current === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', next); localStorage.setItem('liga_theme', next); var btn = document.getElementById('theme-toggle'); if (btn) btn.textContent = next === 'dark' ? '☀️' : '🌙'; } /* ─── CSS MODO DÍA (inyectar en ) ──────────────────── */ var themeCSS = ` [data-theme="light"] { --burg: #8B1A2E; --burg-dk: #6B1020; --burg-lt: #A02040; --gold: #9A7820; --gold-lt: #B8920A; --gold-dk: #7A6010; --cream: #2A1A1F; --cream-lt: #1A0A0F; --dark: #F5F0EC; --dark2: #EDE5DC; --dim: rgba(40,20,25,.7); --border: rgba(100,40,60,.2); --border-lt: rgba(100,40,60,.1); --shadow: 0 12px 48px rgba(0,0,0,.15); } [data-theme="light"] body { background:#F5F0EC; color:#2A1A1F; } [data-theme="light"] .navbar { background:linear-gradient(180deg,rgba(245,240,236,.98),rgba(245,240,236,.92)); border-bottom-color:rgba(139,26,46,.15); } [data-theme="light"] .nav-link { color:rgba(42,26,31,.6); } [data-theme="light"] .nav-link:hover { color:#2A1A1F; background:rgba(139,26,46,.07); } [data-theme="light"] .hero { background:radial-gradient(ellipse 80% 90% at 50% 60%, #e8c0cc 0%, #F5F0EC 65%); } [data-theme="light"] .hero-pattern { background-image: linear-gradient(45deg,rgba(139,26,46,.04) 25%,transparent 25%), linear-gradient(-45deg,rgba(139,26,46,.04) 25%,transparent 25%), linear-gradient(45deg,transparent 75%,rgba(139,26,46,.04) 75%), linear-gradient(-45deg,transparent 75%,rgba(139,26,46,.04) 75%); } [data-theme="light"] .hero-glow { background:radial-gradient(circle,rgba(139,26,46,.25),transparent 70%); } [data-theme="light"] .hero-title { color:#2A1A1F; } [data-theme="light"] .cat-pill { background:rgba(139,26,46,.08); } [data-theme="light"] .bul-card { background:linear-gradient(145deg,rgba(220,180,190,.3),rgba(245,240,236,.95)); } [data-theme="light"] .game-card { background:linear-gradient(135deg,rgba(220,180,190,.25),rgba(245,240,236,.95)); } [data-theme="light"] .stands-card { background:linear-gradient(145deg,rgba(220,180,190,.2),rgba(245,240,236,.98)); } [data-theme="light"] .stands-hdr { background:linear-gradient(135deg,var(--burg),var(--burg-dk)); } [data-theme="light"] .sttbl td { color:rgba(42,26,31,.75); } [data-theme="light"] .sttbl tbody tr:hover { background:rgba(139,26,46,.04); } [data-theme="light"] .sec-sched { background:linear-gradient(180deg,#EAD8DE 0%,#F5F0EC 100%); } [data-theme="light"] .sec-live { background:linear-gradient(180deg,#F5F0EC,#EDE5DC); } [data-theme="light"] .live-card { background:linear-gradient(135deg,rgba(220,40,40,.08),rgba(245,240,236,.98)); } [data-theme="light"] .ins-form { background:rgba(245,240,236,.9); } [data-theme="light"] .f input, [data-theme="light"] .f select, [data-theme="light"] .f textarea { background:rgba(139,26,46,.04); color:#2A1A1F; border-color:rgba(139,26,46,.2); } [data-theme="light"] footer { background:#EDE5DC; } [data-theme="light"] #loginOv { background:rgba(220,200,210,.85); } [data-theme="light"] .lc { background:linear-gradient(145deg,rgba(220,180,190,.6),rgba(245,240,236,.97)); } [data-theme="light"] .lc-f input { background:rgba(139,26,46,.05); color:#2A1A1F; } [data-theme="light"] .sidebar { background:linear-gradient(180deg,#EDE5DC,#E5D5DC); } [data-theme="light"] .sb-it { color:rgba(42,26,31,.6); } [data-theme="light"] .sb-it:hover { background:rgba(139,26,46,.08); } [data-theme="light"] .sb-it.active { background:rgba(139,26,46,.12); color:var(--gold-lt); } [data-theme="light"] .adm-main { background:#F5F0EC; } [data-theme="light"] .topbar { background:rgba(245,240,236,.9); border-bottom-color:rgba(139,26,46,.15); } [data-theme="light"] .card { background:linear-gradient(145deg,rgba(220,180,190,.15),rgba(245,240,236,.95)); } [data-theme="light"] .sc { background:linear-gradient(135deg,rgba(220,180,190,.2),rgba(245,240,236,.95)); } [data-theme="light"] table.dt td { color:rgba(42,26,31,.75); } [data-theme="light"] table.dt tbody tr:hover { background:rgba(139,26,46,.04); } [data-theme="light"] .modal { background:linear-gradient(145deg,#EDE5DC,#F5F0EC); } [data-theme="light"] .fd input, [data-theme="light"] .fd select, [data-theme="light"] .fd textarea { background:rgba(139,26,46,.04); color:#2A1A1F; border-color:rgba(139,26,46,.2); } [data-theme="light"] .fd input:focus, [data-theme="light"] .fd select:focus { background:rgba(139,26,46,.07); } [data-theme="light"] .twrap { border-color:rgba(139,26,46,.15); } [data-theme="light"] .lineup-field { background:rgba(50,120,60,.08); border-color:rgba(50,120,60,.2); } [data-theme="light"] .lineup-slot { background:rgba(245,240,236,.9); border-color:rgba(139,26,46,.2); } [data-theme="light"] .lineup-slot.filled { background:rgba(139,26,46,.08); border-color:rgba(139,26,46,.4); } [data-theme="light"] #managerPanel { background:#F5F0EC; } [data-theme="light"] .score-input { background:rgba(139,26,46,.08); color:#2A1A1F; border-color:rgba(139,26,46,.3); } [data-theme="light"] .inning-input { background:rgba(139,26,46,.05); color:#2A1A1F; } `; var styleEl = document.createElement('style'); styleEl.textContent = themeCSS; document.head.appendChild(styleEl); /* ─── LINEUP STATE ──────────────────────────────────────────── */ var currentLineup = { gameId: null, teamAId: null, teamBId: null, teamA: [], // jugadores disponibles equipo A teamB: [], // jugadores disponibles equipo B lineupA: [], // alineación equipo A lineupB: [] // alineación equipo B }; var FIELD_POSITIONS = ['P','C','1B','2B','3B','SS','LF','CF','RF','DH']; var BENCH_LABEL = 'BN'; /* ─── ABRIR MODAL LINEUP ─────────────────────────────────────── */ async function openLineupModal(gameId, teamAId, teamBId, teamAName, teamBName) { currentLineup.gameId = gameId; currentLineup.teamAId = teamAId; currentLineup.teamBId = teamBId; openModal('⚾ Alineaciones — ' + teamAName + ' vs ' + teamBName, '
Cargando jugadores...
' ); try { var results = await Promise.all([ api('GET', '/lineups/' + gameId + '/team/' + teamAId + '/available'), api('GET', '/lineups/' + gameId + '/team/' + teamBId + '/available'), api('GET', '/lineups/' + gameId) ]); currentLineup.teamA = results[0]; currentLineup.teamB = results[1]; var existing = results[2]; currentLineup.lineupA = existing.filter(function(l) { return l.team_id === teamAId; }); currentLineup.lineupB = existing.filter(function(l) { return l.team_id === teamBId; }); document.getElementById('mBd').innerHTML = renderLineupUI(teamAName, teamBName); renderLineupSlots('A'); renderLineupSlots('B'); renderBench('A'); renderBench('B'); } catch(e) { document.getElementById('mBd').innerHTML = '
Error cargando jugadores: ' + e.message + '
'; } } function renderLineupUI(nameA, nameB) { return '
' + // EQUIPO A '
' + '
' + nameA + '
' + '
' + '
Banca
' + '
' + '
' + '' + '' + '
' + '
' + // EQUIPO B '
' + '
' + nameB + '
' + '
' + '
Banca
' + '
' + '
' + '' + '' + '
' + '
' + '
' + '
' + '
ℹ️ ELEGIBILIDAD PLAYOFF (mín. 10 juegos)
' + '
Los jugadores con cumplen el mínimo. Los marcados con no son elegibles para playoffs.
' + '
'; } function renderLineupSlots(team) { var container = document.getElementById('lineup-slots-' + team); var lineup = team === 'A' ? currentLineup.lineupA : currentLineup.lineupB; var players = team === 'A' ? currentLineup.teamA : currentLineup.teamB; container.innerHTML = FIELD_POSITIONS.map(function(pos, idx) { var slot = lineup.find(function(l) { return l.position === pos; }); var player = slot ? players.find(function(p) { return p.id === slot.player_id; }) : null; var posLabel = pos; var posColor = pos === 'P' ? '#ff8888' : pos === 'C' ? '#88aaff' : pos === 'DH' ? '#f0c060' : '#88e8aa'; return '
' + '
' + posLabel + '
' + '' + (slot ? '' + (slot.games_played>=10?'✓':'⚠') + '' : '') + '
'; }).join(''); } function renderBench(team) { var container = document.getElementById('bench-' + team); var lineup = team === 'A' ? currentLineup.lineupA : currentLineup.lineupB; var players = team === 'A' ? currentLineup.teamA : currentLineup.teamB; // Jugadores NO en ninguna posición var benchPlayers = players.filter(function(p) { return !lineup.find(function(l) { return l.player_id === p.id && FIELD_POSITIONS.includes(l.position); }); }); if (!benchPlayers.length) { container.innerHTML = '
Sin jugadores en banca
'; return; } container.innerHTML = benchPlayers.map(function(p) { var inBench = lineup.find(function(l) { return l.player_id === p.id && l.position === 'BN'; }); return '
' + '
' + '' + (p.number?'#'+p.number+' ':'') + p.name + '' + '' + (p.playoff_eligible?'✓ ':'⚠ ') + p.games_played + 'J' + '' + '
'; }).join(''); } function assignPosition(team, pos, playerId) { var lineup = team === 'A' ? currentLineup.lineupA : currentLineup.lineupB; // Quitar si ya estaba en otra posición var linupRef = lineup; var idx = linupRef.findIndex(function(l) { return l.position === pos; }); if (idx !== -1) linupRef.splice(idx, 1); if (playerId) { // Quitar de donde estaba antes var prev = linupRef.findIndex(function(l) { return l.player_id === parseInt(playerId); }); if (prev !== -1) linupRef.splice(prev, 1); linupRef.push({ player_id: parseInt(playerId), position: pos, batting_order: FIELD_POSITIONS.indexOf(pos) + 1, games_played: 0 }); } if (team === 'A') currentLineup.lineupA = linupRef; else currentLineup.lineupB = linupRef; renderLineupSlots(team); renderBench(team); } function toggleBench(team, playerId) { var lineup = team === 'A' ? currentLineup.lineupA : currentLineup.lineupB; var inBench = lineup.findIndex(function(l) { return l.player_id === playerId && l.position === 'BN'; }); if (inBench !== -1) { lineup.splice(inBench, 1); } else { lineup.push({ player_id: playerId, position: 'BN', batting_order: 99 }); } renderBench(team); } function clearLineup(team) { if (team === 'A') currentLineup.lineupA = []; else currentLineup.lineupB = []; renderLineupSlots(team); renderBench(team); } async function saveLineup(team) { var teamId = team === 'A' ? currentLineup.teamAId : currentLineup.teamBId; var lineup = team === 'A' ? currentLineup.lineupA : currentLineup.lineupB; try { await api('PUT', '/lineups/' + currentLineup.gameId, { team_id: teamId, lineup: lineup }); toast('✅ Lineup ' + team + ' guardado — ' + lineup.filter(function(l){return l.position!=='BN';}).length + ' titulares'); } catch(e) { toast('Error: ' + e.message); } } /* ─── ELEGIBILIDAD PLAYOFF ──────────────────────────────────── */ async function openPlayoffEligibility(teamId, teamName) { openModal('🏆 Elegibilidad Playoff — ' + teamName, '
Cargando...
' ); try { var players = await api('GET', '/lineups/players/eligibility/' + teamId); var eligible = players.filter(function(p) { return p.playoff_eligible; }); var notEligible= players.filter(function(p) { return !p.playoff_eligible; }); document.getElementById('mBd').innerHTML = '
' + '
' + '
' + eligible.length + '
' + '
Elegibles
' + '
' + '
' + '
' + notEligible.length + '
' + '
No Elegibles
' + '
' + '
' + '
' + players.length + '
' + '
Total
' + '
' + '
' + '
' + '' + '' + players.map(function(p, i) { var eligible = p.games_played >= 10; var near = p.games_played >= 8 && !eligible; return '' + '' + '' + '' + '' + '' + '' + ''; }).join('') + '
#JugadorNo.RolJ. JugadosPlayoff
' + (i+1) + '' + p.name + '' + (p.number||'—') + '' + (p.player_role||'jugador') + '' + '
' + '
' + '
' + '
' + '' + p.games_played + '/10' + '
' + '
' + (eligible ? '✓ Elegible' : '⚠ ' + (10-p.games_played) + ' faltan') + '
'; } catch(e) { document.getElementById('mBd').innerHTML = '
Error: ' + e.message + '
'; } } /* BOOT */ loadLanding(); if(TOKEN){ apiFetch('GET','/auth/me').then(function(u){ME=u;}).catch(function(){TOKEN=null;localStorage.removeItem('liga_token');}); }