1ra Versión

This commit is contained in:
MarcosFlorSalcedo
2026-07-14 18:24:27 -03:00
commit 8ec55e40fc
86 changed files with 15839 additions and 0 deletions
+673
View File
@@ -0,0 +1,673 @@
// Variables globales para mantener el auto-refresh
let filtrosActuales = {};
let intervaloActualizacion = null;
// Datos de las canchas (similar a Reservar_Turno.js)
let datosCanchas = { futbol: null, padel: null, quincho: null };
// Cargar datos de las canchas
async function cargarDatosCanchastabla() {
try {
const response = await fetch('../php/Obtener_Canchas.php');
const data = await response.json();
const canchas = Array.isArray(data)
? data
: (data && Array.isArray(data.canchas) ? data.canchas : []);
if (!canchas.length) return;
canchas.forEach(cancha => {
const rawTipo = String(cancha.Tipo ?? cancha.tipo ?? '').trim().toUpperCase();
const tipo = rawTipo === 'F' || rawTipo === 'FUTBOL' ? 'F'
: rawTipo === 'P' || rawTipo === 'PADEL' ? 'P'
: rawTipo === 'Q' || rawTipo === 'QUINCHO' ? 'Q'
: null;
if (!tipo) return;
const duracion = parseInt(cancha.Duracion ?? cancha.duracion, 10) || 0;
const apertura = cancha.Hora_Apertura ?? cancha.apertura ?? null;
const cierre = cancha.Hora_Cierre ?? cancha.cierre ?? null;
if (tipo === 'F') {
datosCanchas.futbol = { duracion, apertura, cierre };
} else if (tipo === 'P') {
datosCanchas.padel = { duracion, apertura, cierre };
}
});
} catch (error) {
console.error('Error al cargar datos de canchas:', error);
}
}
document.addEventListener("DOMContentLoaded", async () => {
// Cargar datos de canchas
await cargarDatosCanchastabla();
// Establecer la fecha de hoy por defecto
const hoy = new Date().toISOString().split('T')[0];
const inputFecha = document.querySelector(".filtro-formulario input[name='fecha']");
if (inputFecha) {
inputFecha.value = hoy;
}
cargarReservas({ fecha: hoy }); // Cargar reservas del día actual
iniciarActualizacionAutomatica(); // Inicia el auto-refresh
const formFiltro = document.querySelector(".filtro-formulario");
const btnReestablecer = document.getElementById("btn-reestablecer-filtros");
// Asegurar estilos para reprogramar (inyección en head si el css no está presente)
ensureReprogramStyles();
// Agregar event listeners para búsqueda dinámica
if (formFiltro) {
const inputs = formFiltro.querySelectorAll("input, select");
inputs.forEach(input => {
input.addEventListener("change", () => {
aplicarFiltrosDinamicos();
});
input.addEventListener("input", () => {
aplicarFiltrosDinamicos();
});
});
}
// Event listener para botón reestablecer
if (btnReestablecer) {
btnReestablecer.addEventListener("click", (e) => {
e.preventDefault();
reestablecerFiltros();
});
}
});
function aplicarFiltrosDinamicos() {
const formFiltro = document.querySelector(".filtro-formulario");
const formData = new FormData(formFiltro);
const filtros = {
fecha: formData.get("fecha"),
nombre: formData.get("nombre"),
dni: formData.get("dni"),
tipo: formData.get("tipo_cancha")
};
cargarReservas(filtros);
}
function reestablecerFiltros() {
const formFiltro = document.querySelector(".filtro-formulario");
// Limpiar todos los inputs
const inputs = formFiltro.querySelectorAll("input, select");
inputs.forEach(input => {
if (input.name === "fecha") {
// Establecer fecha de hoy
input.value = new Date().toISOString().split('T')[0];
} else {
input.value = "";
}
});
// Cargar reservas del día actual
const hoy = new Date().toISOString().split('T')[0];
cargarReservas({ fecha: hoy });
}
function cargarReservas(filtros = {}) {
// Guardar los filtros actuales para el auto-refresh (solo los que tienen valor)
const filtrosConValor = {};
for (const [key, value] of Object.entries(filtros)) {
if (value) {
filtrosConValor[key] = value;
}
}
if (Object.keys(filtrosConValor).length > 0) {
filtrosActuales = filtrosConValor;
}
let url = "../php/Obtener_Reservas.php?";
const params = new URLSearchParams();
// Usar los filtros guardados si no se proporcionan nuevos
const filtrosAUsar = Object.keys(filtrosConValor).length > 0 ? filtrosConValor : filtrosActuales;
// Solo agregamos parámetros si tienen valor
if (filtrosAUsar.fecha) params.append("fecha", filtrosAUsar.fecha);
if (filtrosAUsar.nombre) params.append("nombre", filtrosAUsar.nombre);
if (filtrosAUsar.dni) params.append("dni", filtrosAUsar.dni);
if (filtrosAUsar.tipo) params.append("tipo", filtrosAUsar.tipo);
fetch(url + params.toString())
.then(res => res.json())
.then(reservas => {
const tablaBody = document.querySelector("#tabla-reservas tbody");
tablaBody.innerHTML = "";
if (reservas.length === 0) {
tablaBody.innerHTML = "<tr><td colspan='9'>No se encontraron resultados.</td></tr>";
return;
}
reservas.forEach(r => {
const tipoTexto = r.Tipo === 'F' ? 'Fútbol' : (r.Tipo === 'P' ? 'Pádel' : (r.Tipo === 'Q' ? 'Quincho' : 'Otro'));
// Convertir estado numérico a texto
let estadoTexto = '';
let estadoValor = parseInt(r.Estado);
if (estadoValor === 0) {
estadoTexto = 'No Pagado';
} else if (estadoValor === 1) {
estadoTexto = 'Pagado';
} else if (estadoValor === 2) {
estadoTexto = 'Cancelado';
}
// Verificar si la reserva es del pasado
const fechaReserva = new Date(r.Fecha_Hora);
const hoy = new Date();
hoy.setHours(0, 0, 0, 0);
fechaReserva.setHours(0, 0, 0, 0);
const esDelPasado = fechaReserva < hoy;
const disabledAttr = esDelPasado ? 'disabled' : '';
const disabledClass = esDelPasado ? 'disabled' : '';
const fila = document.createElement("tr");
fila.setAttribute("data-id-reserva", r.ID_Reserva);
fila.setAttribute("data-tipo-cancha", r.Tipo);
fila.setAttribute("data-numero-cancha", r.Numero);
fila.setAttribute("data-fecha-reserva", r.Fecha_Hora);
fila.innerHTML = `
<td>${r.Nombre}</td>
<td>${r.DNI}</td>
<td>${r.Telefono}</td>
<td>${new Date(r.Fecha_Hora).toLocaleString()}</td>
<td>${tipoTexto}</td>
<td>${r.Numero}</td>
<td>${parseFloat(r.Descuento_Cupon) > 0 ? (parseFloat(r.Descuento_Cupon) * 100).toFixed(0) + '%' : '0%'}</td>
<td>$${parseFloat(r.Monto).toFixed(2)}</td>
<td>
<div class="estado-dropdown">
<button class="estado-btn ${disabledClass}" data-estado="${estadoValor}" ${disabledAttr} title="${esDelPasado ? 'No se pueden modificar reservas pasadas' : ''}">
<span class="estado-texto">${estadoTexto}</span>
<span class="estado-flecha">▼</span>
</button>
<div class="estado-opciones" style="display: none;">
<div class="estado-opcion" data-valor="0">No Pagado</div>
<div class="estado-opcion" data-valor="1">Pagado</div>
<div class="estado-opcion" data-valor="2">Cancelado</div>
</div>
</div>
<button class="reprogramar-btn ${disabledClass}" ${disabledAttr} title="Reprogramar reserva">Reprogramar</button>
<div class="reprogramar-container" style="display:none;"></div>
</td>
`;
tablaBody.appendChild(fila);
});
// Agregar event listeners al dropdown
agregarListenersDropdown();
agregarListenersReprogramar();
})
.catch(err => console.error("Error en fetch:", err));
}
function agregarListenersDropdown() {
const botonesEstado = document.querySelectorAll(".estado-btn");
botonesEstado.forEach(btn => {
// Marcar la opción activa al inicio
const estadoActual = btn.getAttribute("data-estado");
const dropdown = btn.closest(".estado-dropdown");
const opciones = dropdown.querySelectorAll(".estado-opcion");
opciones.forEach(op => {
if (op.getAttribute("data-valor") === estadoActual) {
op.classList.add("activo");
} else {
op.classList.remove("activo");
}
});
btn.addEventListener("click", (e) => {
e.stopPropagation();
// Si el botón está deshabilitado, no hacer nada
if (btn.hasAttribute("disabled") || btn.classList.contains("disabled")) {
return;
}
const opcionesDiv = dropdown.querySelector(".estado-opciones");
// Cerrar otros dropdowns abiertos
document.querySelectorAll(".estado-opciones").forEach(op => {
if (op !== opcionesDiv) op.style.display = "none";
});
// Toggle del dropdown actual
const estaAbierto = opcionesDiv.style.display === "block";
opcionesDiv.style.display = estaAbierto ? "none" : "block";
btn.setAttribute("aria-expanded", !estaAbierto);
});
});
// Click en opciones
const opcionesEstado = document.querySelectorAll(".estado-opcion");
opcionesEstado.forEach(opcion => {
opcion.addEventListener("click", (e) => {
e.stopPropagation();
const dropdown = opcion.closest(".estado-dropdown");
const btn = dropdown.querySelector(".estado-btn");
const textoSpan = btn.querySelector(".estado-texto");
const opcionesDiv = dropdown.querySelector(".estado-opciones");
const nuevoValor = opcion.getAttribute("data-valor");
const textoOpcion = opcion.textContent;
// Remover clase activo de todas las opciones
dropdown.querySelectorAll(".estado-opcion").forEach(op => {
op.classList.remove("activo");
});
// Agregar clase activo a la opción seleccionada
opcion.classList.add("activo");
// Actualizar el botón
textoSpan.textContent = textoOpcion;
btn.setAttribute("data-estado", nuevoValor);
// Ocultar dropdown
opcionesDiv.style.display = "none";
btn.setAttribute("aria-expanded", false);
// Guardar cambio en BD
const fila = opcion.closest("tr");
const idReserva = fila.getAttribute("data-id-reserva");
actualizarEstadoReserva(idReserva, parseInt(nuevoValor));
});
});
// Cerrar dropdown si se hace clic fuera
document.addEventListener("click", (e) => {
if (!e.target.closest(".estado-dropdown")) {
document.querySelectorAll(".estado-opciones").forEach(op => {
op.style.display = "none";
});
document.querySelectorAll(".estado-btn").forEach(btn => {
btn.setAttribute("aria-expanded", false);
});
}
});
}
function actualizarEstadoReserva(idReserva, nuevoEstado) {
fetch("../php/Actualizar_Estado_Reserva.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id_reserva: idReserva, estado: nuevoEstado })
})
.then(res => res.json())
.then(data => {
if (data.success) {
console.log("Estado actualizado correctamente");
} else {
alert("Error al actualizar: " + data.error);
// Recargar para mostrar el estado anterior
cargarReservas();
}
})
.catch(err => {
console.error("Error:", err);
alert("Error al actualizar el estado");
cargarReservas();
});
}
// Función para iniciar la actualización automática
function iniciarActualizacionAutomatica() {
// Actualizar cada 5 segundos (5000 milisegundos)
intervaloActualizacion = setInterval(() => {
cargarReservas();
}, 5000);
}
// Función para detener la actualización automática (opcional, si es necesario)
function detenerActualizacionAutomatica() {
if (intervaloActualizacion) {
clearInterval(intervaloActualizacion);
intervaloActualizacion = null;
}
}
// Listeners y modal para reprogramar
function agregarListenersReprogramar() {
const botones = document.querySelectorAll('.reprogramar-btn');
botones.forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
if (btn.hasAttribute('disabled') || btn.classList.contains('disabled')) return;
const fila = btn.closest('tr');
const idReserva = fila.getAttribute('data-id-reserva');
crearModalReprogramar(idReserva, fila);
});
});
}
function crearModalReprogramar(idReserva, fila) {
// Obtener datos de la reserva
const tipoCancha = fila.getAttribute('data-tipo-cancha'); // 'F' o 'P'
const fechaActualReserva = fila.getAttribute('data-fecha-reserva');
// Crear overlay
const overlay = document.createElement('div');
overlay.className = 'reprogramar-overlay';
const modal = document.createElement('div');
modal.className = 'reprogramar-modal';
modal.innerHTML = `
<h3 class="modal-title">Elige un día</h3>
<div class="dias-grid"></div>
<h3 class="modal-title">Selecciona horario</h3>
<div class="horarios-grid"></div>
<h3 class="modal-title">Selecciona cancha</h3>
<div class="canchas-grid"></div>
<div class="reprogramar-actions">
<button class="btn-cancelar">Cancelar</button>
<button class="btn-confirmar" disabled>Confirmar</button>
</div>
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
// Generar los próximos 7 días
const diasGrid = modal.querySelector('.dias-grid');
const hoy = new Date();
for (let i = 0; i < 7; i++) {
const dia = new Date(hoy);
dia.setDate(hoy.getDate() + i);
const btn = document.createElement('button');
btn.className = 'dia-btn';
const options = { weekday: 'short', day: 'numeric', month: 'numeric' };
btn.textContent = `${dia.toLocaleDateString(undefined, { weekday: 'short' })}\n${dia.getDate()}/${dia.getMonth()+1}`;
btn.dataset.iso = dia.toISOString().split('T')[0];
diasGrid.appendChild(btn);
}
let diaSeleccionado = null;
let horaSeleccionada = null;
let canchaSeleccionada = null;
// Handlers selección de día
modal.querySelectorAll('.dia-btn').forEach(d => {
d.addEventListener('click', async () => {
modal.querySelectorAll('.dia-btn').forEach(x => x.classList.remove('selected'));
d.classList.add('selected');
diaSeleccionado = d.dataset.iso;
// Generar horarios cuando se selecciona un día
await generarHorariosReprogramar(modal, tipoCancha, diaSeleccionado);
// Limpiar selección de horario y cancha
horaSeleccionada = null;
canchaSeleccionada = null;
actualizarEstadoBotonConfirm(modal, diaSeleccionado, horaSeleccionada, canchaSeleccionada);
});
});
// Delegation para horarios (se generan dinámicamente)
modal.addEventListener('click', async (e) => {
if (e.target.classList.contains('horario-btn') && !e.target.disabled) {
modal.querySelectorAll('.horario-btn').forEach(x => x.classList.remove('selected'));
e.target.classList.add('selected');
horaSeleccionada = e.target.dataset.hora;
canchaSeleccionada = null;
// Generar selección de canchas
await generarSeleccionCanchasReprogramar(modal, tipoCancha, diaSeleccionado, horaSeleccionada);
actualizarEstadoBotonConfirm(modal, diaSeleccionado, horaSeleccionada, canchaSeleccionada);
}
});
// Delegation para canchas
modal.addEventListener('click', (e) => {
if (e.target.classList.contains('cancha-btn') && !e.target.disabled) {
modal.querySelectorAll('.cancha-btn').forEach(x => x.classList.remove('selected'));
e.target.classList.add('selected');
canchaSeleccionada = e.target.dataset.numero;
actualizarEstadoBotonConfirm(modal, diaSeleccionado, horaSeleccionada, canchaSeleccionada);
}
});
// Cancelar
modal.querySelector('.btn-cancelar').addEventListener('click', () => {
closeModalReprogramar(overlay);
});
// Confirmar
modal.querySelector('.btn-confirmar').addEventListener('click', () => {
if (!diaSeleccionado || !horaSeleccionada || !canchaSeleccionada) return;
const fechaHora = diaSeleccionado + ' ' + horaSeleccionada;
reprogramarReserva(idReserva, fechaHora, overlay);
});
// Cerrar al hacer click fuera del modal
overlay.addEventListener('click', (e) => {
if (e.target === overlay) closeModalReprogramar(overlay);
});
}
// Generar horarios para reprogramación
async function generarHorariosReprogramar(modal, tipoCancha, fechaSeleccionada) {
const horariosGrid = modal.querySelector('.horarios-grid');
horariosGrid.innerHTML = '';
const tipoCode = tipoCancha === 'F' ? 'futbol' : 'padel';
const datos = datosCanchas[tipoCode];
if (!datos || !datos.duracion || !datos.apertura || !datos.cierre) {
horariosGrid.innerHTML = '<p>Error: No se pudieron cargar los datos de las canchas</p>';
return;
}
const duracion = datos.duracion;
const [aperturaH, aperturaM] = datos.apertura.split(':').map(Number);
const [cierreH, cierreM] = datos.cierre.split(':').map(Number);
const aperturaTotalMin = aperturaH * 60 + aperturaM;
const cierreTotalMin = cierreH * 60 + cierreM;
const ahora = new Date();
const [selYear, selMonth, selDay] = fechaSeleccionada.split('-').map(Number);
const fechaSelDate = new Date(selYear, selMonth - 1, selDay);
const hoy = new Date(ahora.getFullYear(), ahora.getMonth(), ahora.getDate());
const esHoy = fechaSelDate.getTime() === hoy.getTime();
const minutoActual = ahora.getHours() * 60 + ahora.getMinutes();
let horaActualMin = aperturaTotalMin;
while (horaActualMin + duracion <= cierreTotalMin) {
const horas = Math.floor(horaActualMin / 60);
const minutos = horaActualMin % 60;
const horaFin = Math.floor((horaActualMin + duracion) / 60);
const minutosFin = (horaActualMin + duracion) % 60;
const inicio = `${String(horas).padStart(2, '0')}:${String(minutos).padStart(2, '0')}`;
const fin = `${String(horaFin).padStart(2, '0')}:${String(minutosFin).padStart(2, '0')}`;
const inicioMin = horas * 60 + minutos;
const esPasado = fechaSelDate < hoy || (esHoy && inicioMin < minutoActual);
const btn = document.createElement('button');
btn.className = 'horario-btn';
btn.textContent = `${inicio}\n-\n${fin}`;
btn.dataset.hora = `${String(horas).padStart(2, '0')}:${String(minutos).padStart(2, '0')}:00`;
// Verificar disponibilidad
let disponibles = 0;
if (!esPasado) {
disponibles = await obtenerDisponibilidadCanchaReprogramar(
fechaSeleccionada,
inicio,
duracion,
tipoCancha
);
}
// Desactivar si es pasado o no hay canchas disponibles
if (esPasado || disponibles === 0) {
btn.disabled = true;
btn.classList.add('no-disponible');
btn.style.opacity = '0.5';
btn.style.cursor = 'not-allowed';
}
horariosGrid.appendChild(btn);
horaActualMin += duracion;
}
// Limpiar grid de canchas
modal.querySelector('.canchas-grid').innerHTML = '';
}
// Obtener disponibilidad de cancha para reprogramación
async function obtenerDisponibilidadCanchaReprogramar(fecha, horario, duracion, tipo) {
try {
const response = await fetch(
`../php/Obtener_Disponibilidad_Cancha.php?fecha=${fecha}&horario=${horario}&duracion=${duracion}&tipo=${tipo}`
);
const data = await response.json();
return data.success ? data.disponibles : 0;
} catch (error) {
console.error('Error obteniendo disponibilidad:', error);
return 0;
}
}
// Generar selección de canchas disponibles
async function generarSeleccionCanchasReprogramar(modal, tipoCancha, fecha, horario) {
const canchasGrid = modal.querySelector('.canchas-grid');
canchasGrid.innerHTML = '';
const tipoCode = tipoCancha === 'F' ? 'futbol' : 'padel';
const datos = datosCanchas[tipoCode];
if (!datos || !datos.duracion) {
canchasGrid.innerHTML = '<p>Error: No se pudieron cargar los datos</p>';
return;
}
try {
const response = await fetch(
`../php/Obtener_Canchas_Numeros.php?fecha=${fecha}&horario=${horario}&duracion=${datos.duracion}&tipo=${tipoCancha}`
);
const data = await response.json();
if (data.success && data.canchas) {
data.canchas.forEach(cancha => {
const btn = document.createElement('button');
btn.className = 'cancha-btn';
btn.textContent = `Cancha ${cancha.numero}`;
btn.dataset.numero = cancha.numero;
if (!cancha.disponible) {
btn.disabled = true;
btn.classList.add('no-disponible');
btn.style.opacity = '0.5';
btn.style.cursor = 'not-allowed';
}
canchasGrid.appendChild(btn);
});
}
} catch (error) {
console.error('Error generando selección de canchas:', error);
canchasGrid.innerHTML = '<p>Error al cargar canchas disponibles</p>';
}
}
function actualizarEstadoBotonConfirm(modal, dia, hora, cancha) {
const btn = modal.querySelector('.btn-confirmar');
if (dia && hora && cancha) {
btn.disabled = false;
} else {
btn.disabled = true;
}
}
function closeModalReprogramar(overlay) {
if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay);
}
function reprogramarReserva(idReserva, fechaHora, overlay) {
fetch('../php/Actualizar_Horario_Reserva.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id_reserva: idReserva, fecha_hora: fechaHora })
})
.then(async res => {
if (!res.ok) throw new Error('HTTP ' + res.status);
const text = await res.text();
try {
return JSON.parse(text);
} catch (error) {
throw new Error('Respuesta no JSON: ' + text.trim().replace(/\s+/g, ' '));
}
})
.then(data => {
if (data.success) {
closeModalReprogramar(overlay);
cargarReservas();
alert('Reserva reprogramada correctamente');
} else {
alert('Error al reprogramar: ' + (data.error || 'Error desconocido'));
}
})
.catch(err => {
console.error('Error reprogramando:', err);
alert('Error al reprogramar la reserva: ' + err.message);
});
}
// Inyecta estilos mínimos si no están presentes en el CSS cargado
function ensureReprogramStyles() {
if (document.getElementById('reprogramar-styles')) return;
const css = `
.reprogramar-btn{margin-left:8px;padding:6px 8px;border-radius:6px;background:rgba(30,125,30,0.12);border:1px solid rgba(30,125,30,0.2);color:#d9f2d9;cursor:pointer;font-weight:600}
.reprogramar-overlay{position:fixed;inset:0;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;z-index:9999;padding:20px}
.reprogramar-modal{width:min(980px,96%);max-height:90vh;overflow:auto;background:#111;border-radius:12px;padding:18px;border:1px solid rgba(30,125,30,0.18)}
.dias-grid{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:14px}.dia-btn{width:110px;height:70px;border-radius:10px;padding:8px;background:rgba(255,255,255,0.03);border:2px solid rgba(255,255,255,0.08);color:#fff;font-weight:700;cursor:pointer;white-space:pre-line;display:flex;flex-direction:column;align-items:center;justify-content:center}
.dia-btn.selected{background:linear-gradient(135deg,#1e7d1e,#145214);border-color:#1e7d1e}
.horarios-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:12px;margin-bottom:14px}.horario-btn{padding:12px;border-radius:10px;background:rgba(255,255,255,0.03);border:2px solid rgba(255,255,255,0.08);color:#fff;font-weight:700;cursor:pointer;white-space:pre-line}
.horario-btn.selected{background:linear-gradient(135deg,#1e7d1e,#145214);border-color:#1e7d1e}
.horario-btn.no-disponible{opacity:0.4 !important;cursor:not-allowed !important}
.canchas-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:12px;margin-bottom:14px}.cancha-btn{padding:12px;border-radius:10px;background:rgba(255,255,255,0.03);border:2px solid rgba(255,255,255,0.08);color:#fff;font-weight:700;cursor:pointer}
.cancha-btn.selected{background:linear-gradient(135deg,#1e7d1e,#145214);border-color:#1e7d1e}
.cancha-btn.no-disponible{opacity:0.4 !important;cursor:not-allowed !important}
.reprogramar-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.reprogramar-actions .btn-cancelar,.reprogramar-actions .btn-confirmar{padding:10px 14px;border-radius:8px;font-weight:700;border:2px solid rgba(255,255,255,0.08);background:rgba(255,255,255,0.03);color:#fff}
.reprogramar-actions .btn-confirmar:disabled{opacity:0.5;cursor:not-allowed}
`;
const style = document.createElement('style');
style.id = 'reprogramar-styles';
style.appendChild(document.createTextNode(css));
document.head.appendChild(style);
}
// Delegación: por si los listeners directos no se añadieron (tabla re-renderizada)
document.addEventListener('click', (e) => {
const btn = e.target.closest && e.target.closest('.reprogramar-btn');
if (!btn) return;
e.stopPropagation();
if (btn.hasAttribute('disabled') || btn.classList.contains('disabled')) return;
// evitar crear múltiples overlays
if (document.querySelector('.reprogramar-overlay')) return;
const fila = btn.closest('tr');
const idReserva = fila ? fila.getAttribute('data-id-reserva') : null;
if (idReserva) crearModalReprogramar(idReserva, fila);
});