Liga de Softbol
Parque Ecológico · VER
🔴 EN VIVO
Boletines
Calendario
Posiciones
Inscripción
⚾ Inscribirse
🔐 Admin
⭐ Temporada Oficial 2025 ⭐
Liga deSoftbol
Parque Ecológico de Veracruz · La liga más competitiva del puerto
🏆 Categoría A 🥈 Categoría B
Inscribe tu Equipo
Ver Posiciones
↓ Desplázate
Actualización en Tiempo Real
Partidos En Vivo
Noticias y Comunicados
Boletines Oficiales
Programa Oficial
Roles de Juego
Todos
Categoría A
Categoría B
Resultados de Temporada
Tabla de Posiciones
# Equipo J G P PCT RA RC
Cargando...
# Equipo J G P PCT RA RC
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 Enviada!
Tu solicitud fue recibida. El equipo administrativo se pondrá en contacto pronto.
Enviar otra solicitud
# Fecha Hora Equipos Cat Sede Marcador Estado
🔴 Marcador En Vivo
Los cambios se publican instantáneamente.
Fecha Equipos Cat Estado Marcador
⏳ Pendientes
✅ Aprobadas
❌ Rechazadas
Todas
# Nombre Usuario Email Rol Equipo Estado
📊
⚾ Jugadores
📅 Partidos
📈 Posiciones
⚙ Equipo
☀️
Salir
+ Registrar Jugador
🏆 Elegibilidad Playoff
# Nombre No. Posición Rol Juegos Playoff Estado
Mis Partidos
📅 Próximos
🔴 En Vivo
✅ Finalizados
Todos
═══════════════════════════════════════════════════ */
/* ─── 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('') : '';
// 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('') : '';
// 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 '';
}
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 '' +
'
Nombre Completo *
' +
'
Número
' +
'
Posición ' +
['Pitcher','Catcher','1B','2B','3B','SS','OF','DH','Manager','Utilidad'].map(function(x) {
return '' + x + ' ';
}).join('') +
'
' +
'
Fecha de Nacimiento
' +
'
Categoría / Rol en el equipo * ' +
'' +
roles.map(function(r) {
return '' + r.label + ' — ' + r.desc + ' ';
}).join('') +
' ' +
'
' +
(p.id ? '' : '') +
'
' +
'Cancelar ' +
'💾 Guardar
';
}
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 + '
' +
'
' +
'
# Equipo J G P PCT RA RC ' +
'' +
(rows.length ? rows.map(function(s, i) {
var isMine = s.team_id === myId;
return '' +
'' + (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 + ' ' +
' ';
}).join('') : '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
' +
'
' +
'
' +
'💾 Guardar Lineup A ' +
'🗑 Limpiar ' +
'
' +
'
' +
// EQUIPO B
'
' +
'
' + nameB + '
' +
'
' +
'
Banca
' +
'
' +
'
' +
'💾 Guardar Lineup B ' +
'🗑 Limpiar ' +
'
' +
'
' +
'
' +
'' +
'
ℹ️ 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 + '
' +
'
' +
'— Seleccionar — ' +
players.map(function(p) {
var eligible = p.playoff_eligible ? ' ✓' : (p.games_played >= 8 ? ' ⚠' : '');
var eligColor = p.playoff_eligible ? '' : (p.games_played >= 8 ? '' : '');
var isSelected = slot && slot.player_id === p.id;
var alreadyUsed = lineup.find(function(l) { return l.player_id === p.id && l.position !== pos; });
return '' +
(p.number ? '#' + p.number + ' ' : '') + p.name + eligible + ' (' + p.games_played + ' J)' +
' ';
}).join('') +
' ' +
(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
' +
'
' +
'
' +
'' +
'# Jugador No. Rol J. Jugados Playoff ' +
'' +
players.map(function(p, i) {
var eligible = p.games_played >= 10;
var near = p.games_played >= 8 && !eligible;
return '' +
'' + (i+1) + ' ' +
'' + p.name + ' ' +
'' + (p.number||'—') + ' ' +
'' + (p.player_role||'jugador') + ' ' +
'' +
'' +
'
' +
'
' + p.games_played + '/10 ' +
'
' +
' ' +
'' + (eligible ?
'✓ Elegible ' :
'⚠ ' + (10-p.games_played) + ' faltan ') +
' ' +
' ';
}).join('') +
'
';
} 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');});
}