1ra Versión
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
cargarConfiguracionCanchas();
|
||||
cargarHorarios();
|
||||
|
||||
// Configurar el guardado para cada formulario
|
||||
const formularios = document.querySelectorAll(
|
||||
".cancha-formulario[data-tipo]"
|
||||
);
|
||||
|
||||
formularios.forEach((form) => {
|
||||
form.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const T = form.dataset.tipo; // Obtiene 'F', 'P' o 'Q'
|
||||
|
||||
// Capturamos los valores dinámicamente usando el prefijo del tipo
|
||||
const datos = {
|
||||
tipo: T,
|
||||
precio: document.getElementById(`precio-${T}`)?.value || 0,
|
||||
duracion: document.getElementById(`duracion-${T}`)?.value || 0,
|
||||
cantidad: document.getElementById(`cantidad-${T}`)?.value || 0,
|
||||
reservas_cupon:
|
||||
document.getElementById(`reservas-cupon-${T}`)?.value || 0,
|
||||
descuento: convertirDescuentoAPorcentaje(
|
||||
document.getElementById(`descuento-${T}`)?.value || 0
|
||||
),
|
||||
faltas: document.getElementById(`faltas-${T}`)?.value || 0,
|
||||
duracion_cupon: document.getElementById(`duracion-cupon-${T}`)?.value || 0,
|
||||
};
|
||||
|
||||
// Caso especial para Quincho que llamaste al input "reservas-Q" en lugar de "reservas-cupon-Q"
|
||||
if (T === "Q") {
|
||||
datos.reservas_cupon =
|
||||
document.getElementById(`reservas-Q`)?.value || 0;
|
||||
}
|
||||
actualizarCancha(datos);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Guardar configuración de Horarios
|
||||
const formHorarios = document.getElementById("form-horarios");
|
||||
if (formHorarios) {
|
||||
formHorarios.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const datosHorarios = [
|
||||
{
|
||||
dia: "Lunes a Viernes",
|
||||
ape: document.getElementById("apertura-l").value,
|
||||
cie: document.getElementById("cierre-l").value,
|
||||
},
|
||||
{
|
||||
dia: "Sábado",
|
||||
ape: document.getElementById("apertura-s").value,
|
||||
cie: document.getElementById("cierre-s").value,
|
||||
},
|
||||
{
|
||||
dia: "Domingo",
|
||||
ape: document.getElementById("apertura-d").value,
|
||||
cie: document.getElementById("cierre-d").value,
|
||||
},
|
||||
];
|
||||
actualizarHorarios(datosHorarios);
|
||||
});
|
||||
}
|
||||
|
||||
function cargarHorarios() {
|
||||
console.log('Cargando horarios...');
|
||||
fetch("../php/Obtener_Horarios.php")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
console.log('Datos de horarios recibidos:', data);
|
||||
data.forEach((h) => {
|
||||
if (h.Dia === "Lunes a Viernes") {
|
||||
setVal("apertura-l", h.Hora_Apertura);
|
||||
setVal("cierre-l", h.Hora_Cierre);
|
||||
} else if (h.Dia === "Sábado") {
|
||||
setVal("apertura-s", h.Hora_Apertura);
|
||||
setVal("cierre-s", h.Hora_Cierre);
|
||||
} else if (h.Dia === "Domingo") {
|
||||
setVal("apertura-d", h.Hora_Apertura);
|
||||
setVal("cierre-d", h.Hora_Cierre);
|
||||
}
|
||||
});
|
||||
console.log('Horarios cargados exitosamente');
|
||||
})
|
||||
.catch((err) => console.error("Error al cargar horarios:", err));
|
||||
}
|
||||
|
||||
window.cargarHorarios = cargarHorarios;
|
||||
|
||||
function normalizarNumero(valor) {
|
||||
if (valor === null || valor === undefined || valor === "") return 0;
|
||||
const texto = String(valor).trim().replace(/\s+/g, "");
|
||||
if (!texto) return 0;
|
||||
const numero = parseFloat(texto.replace(/,/g, "."));
|
||||
return Number.isFinite(numero) ? numero : 0;
|
||||
}
|
||||
|
||||
function convertirDescuentoAPorcentaje(valor) {
|
||||
const numero = normalizarNumero(valor);
|
||||
if (numero === 0) return 0;
|
||||
return numero > 1 ? numero / 100 : numero;
|
||||
}
|
||||
|
||||
function convertirDescuentoAFormulario(valor) {
|
||||
const numero = normalizarNumero(valor);
|
||||
if (numero === 0) return "";
|
||||
return numero > 1 ? numero : numero * 100;
|
||||
}
|
||||
|
||||
function actualizarHorarios(datos) {
|
||||
fetch("../php/Actualizar_Horarios.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(datos),
|
||||
})
|
||||
.then((res) => res.text())
|
||||
.then((msg) => alert(msg))
|
||||
.catch((err) => console.error("Error:", err));
|
||||
}
|
||||
|
||||
function cargarConfiguracionCanchas() {
|
||||
console.log('Cargando configuración de canchas...');
|
||||
fetch("../php/Obtener_Canchas.php")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
console.log('Datos de canchas recibidos:', data);
|
||||
data.forEach((cancha) => {
|
||||
const T = cancha.Tipo; // 'F', 'P', 'Q'
|
||||
|
||||
// Usamos el operador ?. para no romper el código si un input no existe en un tipo
|
||||
setVal(`precio-${T}`, cancha.Precio);
|
||||
setVal(`duracion-${T}`, cancha.Duracion);
|
||||
setVal(`cantidad-${T}`, cancha.Cant_Canchas);
|
||||
setVal(`reservas-cupon-${T}`, cancha.Cant_Reservas_Cupon);
|
||||
setVal(`descuento-${T}`, convertirDescuentoAFormulario(cancha.Descuento_Cupon));
|
||||
setVal(`faltas-${T}`, cancha.Cant_Faltas);
|
||||
setVal(`duracion-cupon-${T}`, cancha.Duracion_Cupon);
|
||||
|
||||
// Ajuste para ID específico de Quincho
|
||||
if (T === "Q") setVal(`reservas-Q`, cancha.Cant_Reservas_Cupon);
|
||||
});
|
||||
console.log('Configuración de canchas cargada exitosamente');
|
||||
})
|
||||
.catch((err) => console.error("Error al cargar configuración:", err));
|
||||
}
|
||||
|
||||
window.cargarConfiguracionCanchas = cargarConfiguracionCanchas;
|
||||
|
||||
// Función auxiliar para asignar valores de forma segura
|
||||
function setVal(id, valor) {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.value = valor;
|
||||
}
|
||||
|
||||
function actualizarCancha(datos) {
|
||||
fetch("../php/Actualizar_Cancha.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(datos),
|
||||
})
|
||||
.then((res) => res.text())
|
||||
.then((msg) => {
|
||||
alert(msg);
|
||||
cargarConfiguracionCanchas(); // Recargar para confirmar cambios
|
||||
})
|
||||
.catch((err) => console.error("Error al actualizar:", err));
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
// Gestion de Usuarios
|
||||
let usuarioEditando = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('DOM cargado - Inicializando Gestion_Usuarios.js');
|
||||
cargarUsuarios();
|
||||
|
||||
// Escuchar el envío del formulario
|
||||
const formulario = document.getElementById('form-registrar-usuario');
|
||||
if (formulario) {
|
||||
formulario.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
console.log('Formulario enviado');
|
||||
if (usuarioEditando) {
|
||||
actualizarUsuario();
|
||||
} else {
|
||||
registrarUsuario();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('No se encontró el formulario con ID form-registrar-usuario');
|
||||
}
|
||||
|
||||
const btnCancelar = document.getElementById('btn-cancelar-edicion');
|
||||
btnCancelar.addEventListener('click', cancelarEdicion);
|
||||
});
|
||||
|
||||
function cargarUsuarios() {
|
||||
console.log('Cargando usuarios...');
|
||||
|
||||
fetch('../php/Obtener_Usuarios.php')
|
||||
.then(response => {
|
||||
console.log('Respuesta status:', response.status);
|
||||
if (!response.ok) {
|
||||
throw new Error('Error HTTP: ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Datos recibidos:', data);
|
||||
|
||||
if (data.success) {
|
||||
const tbody = document.querySelector('#tabla-usuarios tbody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (data.usuarios.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="7" style="text-align: center;">No hay usuarios registrados</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
data.usuarios.forEach(usuario => {
|
||||
const row = document.createElement('tr');
|
||||
row.dataset.user = JSON.stringify(usuario);
|
||||
row.dataset.userId = usuario.ID_Usuario;
|
||||
const esSupremo = usuario.ID_Usuario === 1;
|
||||
const botonEliminar = esSupremo
|
||||
? `<button class="btn-eliminar-usuario" title="El administrador supremo no puede eliminarse" disabled>🔒</button>`
|
||||
: `<button onclick="eliminarUsuario(${usuario.ID_Usuario})" class="btn-eliminar-usuario" title="Eliminar">🗑️</button>`;
|
||||
row.innerHTML = `
|
||||
<td>${usuario.Usuario}</td>
|
||||
<td>${usuario.Nombre} ${usuario.Apellido}</td>
|
||||
<td>${usuario.DNI}</td>
|
||||
<td>${usuario.Rol === 'admin' ? 'Administrador' : usuario.Rol === 'cantina' ? 'Trabajador Cantina' : 'Trabajador Cancha'}</td>
|
||||
<td>${usuario.Telefono || '-'}</td>
|
||||
<td>${usuario.Correo || '-'}</td>
|
||||
<td>
|
||||
<button onclick="editarUsuario(${usuario.ID_Usuario})" class="btn-editar" title="Editar">Editar</button>
|
||||
<button onclick="eliminarUsuario(${usuario.ID_Usuario})" class="btn-eliminar-usuario" title="Eliminar">Eliminar</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
} else {
|
||||
console.error('Error al cargar usuarios:', data.message);
|
||||
const tbody = document.querySelector('#tabla-usuarios tbody');
|
||||
tbody.innerHTML = '<tr><td colspan="7" style="text-align: center; color: red;">Error al cargar usuarios</td></tr>';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error en fetch:', error);
|
||||
const tbody = document.querySelector('#tabla-usuarios tbody');
|
||||
tbody.innerHTML = '<tr><td colspan="7" style="text-align: center; color: red;">Error de conexión</td></tr>';
|
||||
});
|
||||
}
|
||||
|
||||
function registrarUsuario() {
|
||||
const formulario = document.getElementById('form-registrar-usuario');
|
||||
const formData = new FormData(formulario);
|
||||
const contraseña = formData.get('contraseña')?.toString().trim() || '';
|
||||
const confirmarContraseña = formData.get('confirmar_contraseña')?.toString().trim() || '';
|
||||
|
||||
// Validar que el rol esté seleccionado
|
||||
if (!formData.get('rol')) {
|
||||
alert('Por favor selecciona un rol');
|
||||
return;
|
||||
}
|
||||
|
||||
if (contraseña.length < 6) {
|
||||
alert('La contraseña debe tener al menos 6 carácteres');
|
||||
return;
|
||||
}
|
||||
|
||||
if (contraseña !== confirmarContraseña) {
|
||||
alert('Las contraseñas no coinciden');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Registrando usuario:', {
|
||||
usuario: formData.get('usuario'),
|
||||
rol: formData.get('rol'),
|
||||
nombre: formData.get('nombre')
|
||||
});
|
||||
|
||||
fetch('../php/Guardar_Usuario.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
console.log('Respuesta status:', response.status);
|
||||
if (!response.ok) {
|
||||
throw new Error('Error HTTP: ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Respuesta del servidor:', data);
|
||||
|
||||
if (data.success) {
|
||||
alert('✅ Usuario registrado exitosamente');
|
||||
formulario.reset();
|
||||
cargarUsuarios();
|
||||
} else {
|
||||
alert('❌ Error: ' + (data.message || 'No se pudo registrar el usuario'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error en fetch:', error);
|
||||
alert('❌ Error al registrar el usuario: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function editarUsuario(id) {
|
||||
console.log('Editando usuario con ID:', id);
|
||||
|
||||
// Buscar la fila por el ID del usuario de forma más robusta
|
||||
let row = null;
|
||||
const filas = document.querySelectorAll('#tabla-usuarios tbody tr');
|
||||
|
||||
for (let fila of filas) {
|
||||
if (fila.dataset.userId == id) { // Comparación flexible de tipos
|
||||
row = fila;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!row || !row.dataset.user) {
|
||||
console.error('No se encontró el usuario con ID:', id);
|
||||
alert('❌ Error: No se pudo encontrar el usuario. Por favor recarga la página e intenta de nuevo.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const usuario = JSON.parse(row.dataset.user);
|
||||
|
||||
document.getElementById('usuario-id').value = usuario.ID_Usuario;
|
||||
document.getElementById('usuario-username').value = usuario.Usuario;
|
||||
document.getElementById('usuario-password').value = usuario.Contraseña;
|
||||
document.getElementById('usuario-password-confirm').value = usuario.Contraseña;
|
||||
document.querySelector('input[name="nombre"]').value = usuario.Nombre || '';
|
||||
document.querySelector('input[name="apellido"]').value = usuario.Apellido || '';
|
||||
document.querySelector('input[name="dni"]').value = usuario.DNI || '';
|
||||
document.querySelector('input[name="telefono"]').value = usuario.Telefono || '';
|
||||
document.querySelector('input[name="correo"]').value = usuario.Correo || '';
|
||||
document.querySelector('select[name="rol"]').value = usuario.Rol;
|
||||
|
||||
usuarioEditando = id;
|
||||
document.getElementById('btn-registrar-usuario').textContent = 'Guardar Cambios';
|
||||
document.getElementById('btn-cancelar-edicion').style.display = 'inline-block';
|
||||
|
||||
console.log('Usuario cargado para edición:', usuario.Usuario);
|
||||
} catch (error) {
|
||||
console.error('Error al parsear datos del usuario:', error);
|
||||
alert('❌ Error: Los datos del usuario están corruptos. Por favor recarga la página.');
|
||||
}
|
||||
}
|
||||
|
||||
function cancelarEdicion() {
|
||||
usuarioEditando = null;
|
||||
document.getElementById('form-registrar-usuario').reset();
|
||||
document.getElementById('usuario-id').value = '';
|
||||
document.getElementById('btn-registrar-usuario').textContent = 'Registrar Usuario';
|
||||
document.getElementById('btn-cancelar-edicion').style.display = 'none';
|
||||
}
|
||||
|
||||
function actualizarUsuario() {
|
||||
const formulario = document.getElementById('form-registrar-usuario');
|
||||
const formData = new FormData(formulario);
|
||||
const contraseña = formData.get('contraseña')?.toString().trim() || '';
|
||||
const confirmarContraseña = formData.get('confirmar_contraseña')?.toString().trim() || '';
|
||||
|
||||
if (!usuarioEditando) {
|
||||
alert('No hay usuario seleccionado para editar');
|
||||
return;
|
||||
}
|
||||
|
||||
if (contraseña.length < 6) {
|
||||
alert('La contraseña debe tener al menos 6 carácteres');
|
||||
return;
|
||||
}
|
||||
|
||||
if (contraseña !== confirmarContraseña) {
|
||||
alert('Las contraseñas no coinciden');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('../php/Actualizar_Usuario.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Error HTTP: ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('✅ Usuario actualizado exitosamente');
|
||||
cancelarEdicion();
|
||||
cargarUsuarios();
|
||||
} else {
|
||||
alert('❌ Error: ' + (data.message || 'No se pudo actualizar el usuario'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error en fetch:', error);
|
||||
alert('❌ Error al actualizar el usuario: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function eliminarUsuario(id) {
|
||||
if (confirm('¿Estás seguro de que deseas eliminar este usuario? Esta acción no se puede deshacer.')) {
|
||||
console.log('Eliminando usuario con ID:', id);
|
||||
|
||||
fetch('../php/Eliminar_Usuario.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: 'id=' + id
|
||||
})
|
||||
.then(response => {
|
||||
console.log('Respuesta status:', response.status);
|
||||
if (!response.ok) {
|
||||
throw new Error('Error HTTP: ' + response.status);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Respuesta del servidor:', data);
|
||||
|
||||
if (data.success) {
|
||||
alert('✅ Usuario eliminado exitosamente');
|
||||
cargarUsuarios();
|
||||
} else {
|
||||
alert('❌ Error: ' + (data.message || 'No se pudo eliminar el usuario'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error en fetch:', error);
|
||||
alert('❌ Error al eliminar el usuario: ' + error.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
const productosOrden = [];
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const codigoProveedor = document.getElementById("codigo-proveedor");
|
||||
const nombreProveedor = document.getElementById("nombre-proveedor");
|
||||
const telefonoProveedor = document.getElementById("telefono-proveedor");
|
||||
const codigoProducto = document.getElementById("codigo-producto");
|
||||
const cantidadProducto = document.getElementById("cantidad-producto");
|
||||
const btnAgregar = document.getElementById("btn-agregar-producto");
|
||||
const btnGenerar = document.getElementById("btn-generar-orden");
|
||||
const tabla = document.querySelector("#tabla-orden tbody");
|
||||
const tablaOrdenes = document.querySelector("#tabla-ordenes tbody");
|
||||
|
||||
let ordenDetalleActual = null;
|
||||
let modoEdicionDetalle = false;
|
||||
|
||||
if (codigoProveedor) {
|
||||
codigoProveedor.addEventListener("blur", () => {
|
||||
fetch(`../php/Buscar_Proveedor.php?codigo=${codigoProveedor.value.trim()}`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (data && data.Nombre && data.Telefono) {
|
||||
nombreProveedor.value = data.Nombre;
|
||||
telefonoProveedor.value = data.Telefono;
|
||||
} else {
|
||||
nombreProveedor.value = "";
|
||||
telefonoProveedor.value = "";
|
||||
alert("Proveedor no encontrado.");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (btnAgregar) {
|
||||
btnAgregar.addEventListener("click", () => {
|
||||
const codigo = codigoProducto.value.trim();
|
||||
const cantidad = parseInt(cantidadProducto.value, 10);
|
||||
|
||||
if (!codigo || cantidad <= 0) {
|
||||
alert("Código y cantidad válidos requeridos.");
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`../php/Buscar_Producto_Orden.php?codigo=${codigo}`)
|
||||
.then((res) => {
|
||||
if (!res.ok) {
|
||||
throw new Error("Error en la solicitud al servidor.");
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then((producto) => {
|
||||
if (!producto || !producto.ID_Producto) {
|
||||
alert("Producto no encontrado.");
|
||||
return;
|
||||
}
|
||||
|
||||
productosOrden.push({
|
||||
id: producto.ID_Producto,
|
||||
descripcion: producto.Descripcion,
|
||||
cantidad: cantidad,
|
||||
});
|
||||
|
||||
actualizarTabla();
|
||||
codigoProducto.value = "";
|
||||
cantidadProducto.value = "";
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error al obtener el producto:", err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (btnGenerar) {
|
||||
btnGenerar.addEventListener("click", () => {
|
||||
if (!codigoProveedor.value || productosOrden.length === 0) {
|
||||
alert("Proveedor y al menos un producto son necesarios.");
|
||||
return;
|
||||
}
|
||||
|
||||
fetch("../php/Registrar_Orden_Compra.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
proveedor: codigoProveedor.value,
|
||||
productos: productosOrden,
|
||||
}),
|
||||
})
|
||||
.then((res) => res.text())
|
||||
.then((msg) => {
|
||||
alert(msg);
|
||||
productosOrden.length = 0;
|
||||
actualizarTabla();
|
||||
document.getElementById("form-orden").reset();
|
||||
cargarOrdenesCompra();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function actualizarTabla() {
|
||||
if (!tabla) return;
|
||||
tabla.innerHTML = "";
|
||||
productosOrden.forEach((p, i) => {
|
||||
const fila = document.createElement("tr");
|
||||
fila.innerHTML = `
|
||||
<td>${p.id}</td>
|
||||
<td>${p.descripcion}</td>
|
||||
<td>${p.cantidad}</td>
|
||||
<td>
|
||||
<button type="button" class="btn-eliminar" onclick="eliminarProductoOrden(${i})">Eliminar</button>
|
||||
</td>
|
||||
`;
|
||||
tabla.appendChild(fila);
|
||||
});
|
||||
}
|
||||
|
||||
function cargarOrdenesCompra() {
|
||||
if (!tablaOrdenes) return;
|
||||
fetch("../php/Obtener_Ordenes_Compra.php")
|
||||
.then((res) => res.json())
|
||||
.then((ordenes) => {
|
||||
tablaOrdenes.innerHTML = "";
|
||||
ordenes.forEach((orden) => {
|
||||
const fila = document.createElement("tr");
|
||||
const estadoNumerico = parseInt(orden.Estado, 10);
|
||||
const estaProcesada = estadoNumerico !== 0;
|
||||
|
||||
let textoEstado = "Pendiente";
|
||||
if (estadoNumerico === 1) textoEstado = "Aprobada";
|
||||
if (estadoNumerico === 2) textoEstado = "Denegada";
|
||||
if (estadoNumerico === 3) textoEstado = "Confirmada";
|
||||
|
||||
fila.innerHTML = `
|
||||
<td>${orden.ID_Orden}</td>
|
||||
<td>${orden.Fecha}</td>
|
||||
<td>${orden.ID_Proveedor || "N/A"}</td>
|
||||
<td>${orden.Proveedor || "Sin nombre"}</td>
|
||||
<td>${orden.Telefono || "Sin registrar"}</td>
|
||||
<td>${textoEstado}</td>
|
||||
<td>
|
||||
<button type="button" class="btn-ver-orden" data-id="${orden.ID_Orden}" data-estado="${orden.Estado || 0}">Ver Orden</button>
|
||||
<button type="button" class="btn-aprobar"
|
||||
data-id="${orden.ID_Orden}"
|
||||
data-telefono="${orden.Telefono || ''}"
|
||||
data-proveedor="${orden.Proveedor || ''}"
|
||||
${estaProcesada ? "disabled" : ""}>Aprobar</button>
|
||||
<button type="button" class="btn-denegar"
|
||||
data-id="${orden.ID_Orden}"
|
||||
${estaProcesada ? "disabled" : ""}>Denegar</button>
|
||||
</td>
|
||||
`;
|
||||
tablaOrdenes.appendChild(fila);
|
||||
});
|
||||
|
||||
tablaOrdenes.querySelectorAll(".btn-aprobar").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
cambiarEstadoOrden(btn.dataset.id, true, btn.dataset.telefono, btn.dataset.proveedor);
|
||||
});
|
||||
});
|
||||
|
||||
tablaOrdenes.querySelectorAll(".btn-denegar").forEach((btn) => {
|
||||
btn.addEventListener("click", () => cambiarEstadoOrden(btn.dataset.id, false));
|
||||
});
|
||||
|
||||
tablaOrdenes.querySelectorAll(".btn-ver-orden").forEach((btn) => {
|
||||
btn.addEventListener("click", () => verProductosOrden(btn.dataset.id, btn.dataset.estado));
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error al cargar órdenes de compra:", err);
|
||||
});
|
||||
}
|
||||
|
||||
function cambiarEstadoOrden(idOrden, esAprobacion, telefono = "", nombreProv = "") {
|
||||
const nuevoEstado = esAprobacion ? 1 : 2;
|
||||
|
||||
fetch("../php/Actualizar_Orden_Compra.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ id: parseInt(idOrden, 10), estado: nuevoEstado }),
|
||||
})
|
||||
.then(async (res) => {
|
||||
const text = await res.text();
|
||||
if (!res.ok) {
|
||||
throw new Error(text || `HTTP ${res.status}`);
|
||||
}
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (jsonErr) {
|
||||
throw new Error(`Respuesta inválida del servidor: ${text}`);
|
||||
}
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
if (esAprobacion && telefono) {
|
||||
enviarOrdenPorWhatsApp(idOrden, telefono, nombreProv);
|
||||
}
|
||||
cargarOrdenesCompra();
|
||||
} else {
|
||||
alert("Error: " + (data.error || "No se pudo actualizar"));
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error al actualizar la orden:", err);
|
||||
alert("Error al actualizar la orden: " + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
function enviarOrdenPorWhatsApp(idOrden, telefono, nombreProv) {
|
||||
const telefonoLimpio = telefono.replace(/[^0-9]/g, "");
|
||||
|
||||
fetch(`../php/Obtener_Productos_Orden.php?id=${idOrden}`)
|
||||
.then((res) => res.json())
|
||||
.then((productos) => {
|
||||
let mensaje = `*Orden de Compra #${idOrden}*\n`;
|
||||
mensaje += `Proveedor: ${nombreProv}\n`;
|
||||
mensaje += `--------------------------\n`;
|
||||
|
||||
productos.forEach((p) => {
|
||||
mensaje += `- ${p.Cantidad} x ${p.Descripcion}\n`;
|
||||
});
|
||||
|
||||
mensaje += `--------------------------\n`;
|
||||
mensaje += `Favor de confirmar recepción.`;
|
||||
|
||||
const mensajeUrl = encodeURIComponent(mensaje);
|
||||
const urlWhatsApp = `https://web.whatsapp.com/send?phone=${telefonoLimpio}&text=${mensajeUrl}`;
|
||||
|
||||
window.open(urlWhatsApp, "_blank");
|
||||
})
|
||||
.catch((err) => console.error("Error al obtener productos para WhatsApp:", err));
|
||||
}
|
||||
|
||||
function renderDetalleOrden() {
|
||||
const cuerpo = document.getElementById("productos-orden-body");
|
||||
if (!cuerpo || !ordenDetalleActual) {
|
||||
return;
|
||||
}
|
||||
|
||||
cuerpo.innerHTML = "";
|
||||
|
||||
if (ordenDetalleActual.productos.length === 0) {
|
||||
const filaVacia = document.createElement("tr");
|
||||
filaVacia.innerHTML = '<td colspan="2">No hay productos registrados.</td>';
|
||||
cuerpo.appendChild(filaVacia);
|
||||
actualizarBotonesDetalleOrden();
|
||||
return;
|
||||
}
|
||||
|
||||
ordenDetalleActual.productos.forEach((prod) => {
|
||||
const fila = document.createElement("tr");
|
||||
const cantidadActual = prod.Cantidad || 0;
|
||||
const celdaCantidad = modoEdicionDetalle
|
||||
? `<td><input type="number" min="1" value="${cantidadActual}" data-product-id="${prod.ID_Producto}" class="input-cantidad-orden" /></td>`
|
||||
: `<td>${cantidadActual}</td>`;
|
||||
|
||||
fila.innerHTML = `
|
||||
<td>${prod.Descripcion}</td>
|
||||
${celdaCantidad}
|
||||
`;
|
||||
cuerpo.appendChild(fila);
|
||||
});
|
||||
|
||||
actualizarBotonesDetalleOrden();
|
||||
}
|
||||
|
||||
function actualizarBotonesDetalleOrden() {
|
||||
const btnConfirmar = document.getElementById("btn-confirmar-recepcion");
|
||||
const btnEditar = document.getElementById("btn-editar-orden");
|
||||
const btnGuardar = document.getElementById("btn-guardar-orden");
|
||||
const btnCancelar = document.getElementById("btn-cancelar-orden");
|
||||
const btnCerrar = document.getElementById("cerrar-orden");
|
||||
|
||||
if (!btnConfirmar || !btnEditar || !btnGuardar || !btnCancelar || !btnCerrar) {
|
||||
return;
|
||||
}
|
||||
|
||||
const estado = parseInt(ordenDetalleActual?.estado || 0, 10);
|
||||
const esConfirmada = estado === 3;
|
||||
const esBloqueada = estado === 2 || estado === 3;
|
||||
|
||||
if (modoEdicionDetalle) {
|
||||
btnConfirmar.style.display = "none";
|
||||
btnEditar.style.display = "none";
|
||||
btnCerrar.style.display = "none";
|
||||
btnGuardar.style.display = "inline-block";
|
||||
btnCancelar.style.display = "inline-block";
|
||||
} else {
|
||||
btnConfirmar.style.display = "inline-block";
|
||||
btnEditar.style.display = "inline-block";
|
||||
btnCerrar.style.display = "inline-block";
|
||||
btnGuardar.style.display = "none";
|
||||
btnCancelar.style.display = "none";
|
||||
}
|
||||
|
||||
btnConfirmar.disabled = esConfirmada || esBloqueada;
|
||||
btnEditar.disabled = esConfirmada || esBloqueada;
|
||||
}
|
||||
|
||||
function verProductosOrden(idOrden, estadoActual = 0) {
|
||||
fetch(`../php/Obtener_Productos_Orden.php?id=${idOrden}`)
|
||||
.then((res) => res.json())
|
||||
.then((productos) => {
|
||||
ordenDetalleActual = {
|
||||
id: parseInt(idOrden, 10),
|
||||
estado: parseInt(estadoActual, 10),
|
||||
productos: productos || [],
|
||||
};
|
||||
modoEdicionDetalle = false;
|
||||
renderDetalleOrden();
|
||||
|
||||
document.getElementById("tabla-ordenes").style.display = "none";
|
||||
document.getElementById("productos-orden").style.display = "block";
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error al cargar productos de la orden:", err);
|
||||
});
|
||||
}
|
||||
|
||||
async function confirmarRecepcionOrden() {
|
||||
if (!ordenDetalleActual) return;
|
||||
|
||||
try {
|
||||
const res = await fetch("../php/Actualizar_Orden_Compra.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ id: ordenDetalleActual.id, estado: 3 }),
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
ordenDetalleActual.estado = 3;
|
||||
cargarOrdenesCompra();
|
||||
if (window.cargarProductos) {
|
||||
window.cargarProductos();
|
||||
}
|
||||
renderDetalleOrden();
|
||||
} else {
|
||||
alert("Error: " + (data.error || "No se pudo confirmar la recepción"));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error al confirmar recepción:", err);
|
||||
}
|
||||
}
|
||||
|
||||
function activarEdicionDetalleOrden() {
|
||||
modoEdicionDetalle = true;
|
||||
renderDetalleOrden();
|
||||
}
|
||||
|
||||
function cancelarEdicionDetalleOrden() {
|
||||
modoEdicionDetalle = false;
|
||||
renderDetalleOrden();
|
||||
}
|
||||
|
||||
async function guardarEdicionDetalleOrden() {
|
||||
if (!ordenDetalleActual) return;
|
||||
|
||||
const productosActualizados = ordenDetalleActual.productos.map((prod) => {
|
||||
const input = document.querySelector(`input[data-product-id="${prod.ID_Producto}"]`);
|
||||
const cantidad = input ? parseInt(input.value, 10) : parseInt(prod.Cantidad, 10);
|
||||
|
||||
if (!Number.isInteger(cantidad) || cantidad < 1) {
|
||||
throw new Error(`La cantidad para ${prod.Descripcion} debe ser un número mayor a 0.`);
|
||||
}
|
||||
|
||||
return {
|
||||
...prod,
|
||||
Cantidad: cantidad,
|
||||
};
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await fetch("../php/Actualizar_Productos_Orden.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
id: ordenDetalleActual.id,
|
||||
productos: productosActualizados.map((prod) => ({
|
||||
id: prod.ID_Producto,
|
||||
cantidad: prod.Cantidad,
|
||||
})),
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
ordenDetalleActual.productos = productosActualizados;
|
||||
modoEdicionDetalle = false;
|
||||
renderDetalleOrden();
|
||||
} else {
|
||||
alert("Error: " + (data.error || "No se pudieron guardar los cambios"));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error al guardar edición:", err);
|
||||
alert(err.message || "Ocurrió un error al guardar los cambios.");
|
||||
}
|
||||
}
|
||||
|
||||
cargarOrdenesCompra();
|
||||
|
||||
document.getElementById("cerrar-orden").addEventListener("click", () => {
|
||||
document.getElementById("tabla-ordenes").style.display = "table";
|
||||
document.getElementById("productos-orden").style.display = "none";
|
||||
ordenDetalleActual = null;
|
||||
modoEdicionDetalle = false;
|
||||
});
|
||||
|
||||
document.getElementById("btn-confirmar-recepcion").addEventListener("click", confirmarRecepcionOrden);
|
||||
document.getElementById("btn-editar-orden").addEventListener("click", activarEdicionDetalleOrden);
|
||||
document.getElementById("btn-guardar-orden").addEventListener("click", guardarEdicionDetalleOrden);
|
||||
document.getElementById("btn-cancelar-orden").addEventListener("click", cancelarEdicionDetalleOrden);
|
||||
});
|
||||
|
||||
function eliminarProductoOrden(index) {
|
||||
productosOrden.splice(index, 1);
|
||||
document.querySelector("#tabla-orden tbody").innerHTML = "";
|
||||
productosOrden.forEach((p, i) => {
|
||||
const fila = document.createElement("tr");
|
||||
fila.innerHTML = `
|
||||
<td>${p.id}</td>
|
||||
<td>${p.descripcion}</td>
|
||||
<td>${p.cantidad}</td>
|
||||
<td>
|
||||
<button type="button" onclick="eliminarProductoOrden(${i})">Eliminar</button>
|
||||
</td>
|
||||
`;
|
||||
document.querySelector("#tabla-orden tbody").appendChild(fila);
|
||||
});
|
||||
}
|
||||
+585
@@ -0,0 +1,585 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const tipoReporte = document.getElementById('tipo-reporte');
|
||||
const parametros = document.getElementById('parametros');
|
||||
const generarBtn = document.getElementById('generar-reporte');
|
||||
const exportarBtn = document.getElementById('exportar-pdf');
|
||||
const reporteContainer = document.getElementById('reporte-container');
|
||||
const reporteContenido = document.getElementById('reporte-contenido');
|
||||
const spinner = document.getElementById('spinner');
|
||||
const errorMessage = document.getElementById('error-message');
|
||||
|
||||
// Variable global para almacenar el período actual
|
||||
let periodoActual = {
|
||||
tipo: 'diario',
|
||||
fecha: new Date().toISOString().split('T')[0],
|
||||
nombreArchivo: 'reporte'
|
||||
};
|
||||
|
||||
// ========== FUNCIONES AUXILIARES ==========
|
||||
|
||||
function formatoMoneda(valor) {
|
||||
return new Intl.NumberFormat('es-AR', {
|
||||
style: 'currency',
|
||||
currency: 'ARS'
|
||||
}).format(valor);
|
||||
}
|
||||
|
||||
function obtenerSemanaActual() {
|
||||
const hoy = new Date();
|
||||
const diaSemana = hoy.getDay();
|
||||
const lunes = new Date(hoy);
|
||||
const diferenciaDias = diaSemana === 0 ? -6 : 1 - diaSemana;
|
||||
lunes.setDate(hoy.getDate() + diferenciaDias);
|
||||
|
||||
const fechaReferencia = new Date(lunes.getFullYear(), 0, 4);
|
||||
const diaSemanaRef = fechaReferencia.getDay();
|
||||
const lunesRef = new Date(fechaReferencia);
|
||||
lunesRef.setDate(fechaReferencia.getDate() - (diaSemanaRef === 0 ? 6 : diaSemanaRef - 1));
|
||||
|
||||
const diffMs = lunes - lunesRef;
|
||||
const diffDias = Math.floor(diffMs / (24 * 60 * 60 * 1000));
|
||||
const numeroSemana = Math.floor(diffDias / 7) + 1;
|
||||
const ano = lunes.getFullYear();
|
||||
|
||||
return `${ano}-W${String(numeroSemana).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function mostrarSpinner() {
|
||||
spinner.style.display = 'flex';
|
||||
}
|
||||
|
||||
function ocultarSpinner() {
|
||||
spinner.style.display = 'none';
|
||||
}
|
||||
|
||||
function mostrarError(mensaje) {
|
||||
errorMessage.textContent = mensaje;
|
||||
errorMessage.style.display = 'block';
|
||||
}
|
||||
|
||||
function ocultarError() {
|
||||
errorMessage.style.display = 'none';
|
||||
}
|
||||
|
||||
function generarNombresYMetadatos(tipo, data) {
|
||||
let nombreArchivo = 'reporte';
|
||||
|
||||
if (tipo === 'diario') {
|
||||
const fecha = new Date(data.periodo + 'T00:00:00');
|
||||
const fechaCorta = fecha.getDate().toString().padStart(2, '0') + '-' +
|
||||
(fecha.getMonth() + 1).toString().padStart(2, '0') + '-' +
|
||||
fecha.getFullYear().toString().slice(-2);
|
||||
nombreArchivo = 'reporte_' + fechaCorta;
|
||||
} else if (tipo === 'semanal') {
|
||||
const inicio = new Date(data.fecha_inicio + 'T00:00:00');
|
||||
const fin = new Date(data.fecha_fin + 'T00:00:00');
|
||||
const diaInicio = inicio.getDate().toString().padStart(2, '0');
|
||||
const diaFin = fin.getDate().toString().padStart(2, '0');
|
||||
const mesNombre = inicio.toLocaleDateString('es-ES', { month: 'long' });
|
||||
nombreArchivo = `reporte_semana_${diaInicio}-${diaFin}_${mesNombre}`;
|
||||
} else if (tipo === 'mensual') {
|
||||
const mesesNombres = ['', 'enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'];
|
||||
let nombreMes = 'mensual';
|
||||
let anio = new Date().getFullYear();
|
||||
if (data.nombre_mes) {
|
||||
nombreMes = data.nombre_mes;
|
||||
anio = data.nombre_anio || anio;
|
||||
} else if (data.fecha_inicio) {
|
||||
const f = new Date(data.fecha_inicio + 'T00:00:00');
|
||||
if (!isNaN(f.getTime())) {
|
||||
nombreMes = f.toLocaleDateString('es-ES', { month: 'long' });
|
||||
anio = f.getFullYear();
|
||||
}
|
||||
} else if (data.periodo && /^\d{4}-\d{2}$/.test(String(data.periodo))) {
|
||||
const parts = String(data.periodo).split('-');
|
||||
nombreMes = mesesNombres[parseInt(parts[1] || '0')] || nombreMes;
|
||||
anio = parts[0] || anio;
|
||||
}
|
||||
nombreArchivo = 'reporte_' + nombreMes + '_' + anio;
|
||||
} else if (tipo === 'anual') {
|
||||
const anio = data.periodo || data.anio || (data.fecha_inicio ? (new Date(data.fecha_inicio + 'T00:00:00')).getFullYear() : new Date().getFullYear());
|
||||
nombreArchivo = 'reporte_' + anio;
|
||||
}
|
||||
|
||||
periodoActual.tipo = tipo;
|
||||
periodoActual.nombreArchivo = nombreArchivo;
|
||||
}
|
||||
|
||||
function renderizarDiario(data) {
|
||||
const fecha = new Date(data.periodo + 'T00:00:00');
|
||||
const opciones = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
|
||||
const fechaFormato = fecha.toLocaleDateString('es-ES', opciones);
|
||||
const fechaCorta = fecha.getDate().toString().padStart(2, '0') + '-' +
|
||||
(fecha.getMonth() + 1).toString().padStart(2, '0') + '-' +
|
||||
fecha.getFullYear().toString().slice(-2);
|
||||
|
||||
let html = `
|
||||
<div class="reporte-titulo">
|
||||
<h2>📅 Reporte Diario ${fechaCorta} - Cierre de Caja</h2>
|
||||
<div class="reporte-periodo">Reporte ${fechaFormato}</div>
|
||||
</div>
|
||||
|
||||
<table class="reporte-tabla">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Hora</th>
|
||||
<th>Tipo de Operación</th>
|
||||
<th>Descripción / Detalle</th>
|
||||
<th style="text-align: right;">Monto ($)</th>
|
||||
<th>Usuario</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
if (data.transacciones && data.transacciones.length > 0) {
|
||||
data.transacciones.forEach(tx => {
|
||||
const monto = parseFloat(tx.monto);
|
||||
const claseNegativo = monto < 0 ? 'negativo' : '';
|
||||
html += `
|
||||
<tr>
|
||||
<td>${tx.hora || '--:--'}</td>
|
||||
<td><strong>${tx.tipo_operacion}</strong></td>
|
||||
<td>${tx.descripcion || '--'}</td>
|
||||
<td class="monto ${claseNegativo}">${formatoMoneda(monto)}</td>
|
||||
<td>${tx.usuario_registro || 'Sistema'}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
} else {
|
||||
html += `<tr><td colspan="5" style="text-align: center;">Sin transacciones registradas</td></tr>`;
|
||||
}
|
||||
|
||||
html += `
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="resumen-reporte">
|
||||
<h3>Resumen del Día</h3>
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">Ingresos Reservas:</span>
|
||||
<span class="resumen-valor">${formatoMoneda(data.resumen.ingresos_reservas)}</span>
|
||||
</div>
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">Ingresos Cantina:</span>
|
||||
<span class="resumen-valor">${formatoMoneda(data.resumen.ingresos_cantina)}</span>
|
||||
</div>
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">Total Ingresos:</span>
|
||||
<span class="resumen-valor">${formatoMoneda(data.resumen.total_ingresos)}</span>
|
||||
</div>
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">Egresos Totales:</span>
|
||||
<span class="resumen-valor negativo">-${formatoMoneda(data.resumen.egresos)}</span>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 2px solid #2c3e50; margin: 15px 0;">
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">💰 SALDO NETO:</span>
|
||||
<span class="resumen-valor ${data.resumen.saldo_neto < 0 ? 'negativo' : ''}">${formatoMoneda(data.resumen.saldo_neto)}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
reporteContenido.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderizarSemanal(data) {
|
||||
const inicio = new Date(data.fecha_inicio + 'T00:00:00');
|
||||
const fin = new Date(data.fecha_fin + 'T00:00:00');
|
||||
const opciones = { month: 'short', day: 'numeric' };
|
||||
const inicioCorta = inicio.getDate().toString().padStart(2, '0') + '-' +
|
||||
(inicio.getMonth() + 1).toString().padStart(2, '0');
|
||||
const finCorta = fin.getDate().toString().padStart(2, '0') + '-' +
|
||||
(fin.getMonth() + 1).toString().padStart(2, '0') + '-' +
|
||||
fin.getFullYear().toString().slice(-2);
|
||||
|
||||
let html = `
|
||||
<div class="reporte-titulo">
|
||||
<h2>📆 Reporte Semanal ${inicioCorta} al ${finCorta} - Flujo de Caja</h2>
|
||||
<div class="reporte-periodo">Reporte Semana del ${inicio.toLocaleDateString('es-ES', opciones)} al ${fin.toLocaleDateString('es-ES', opciones)}</div>
|
||||
</div>
|
||||
|
||||
<table class="reporte-tabla">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Día de la Semana</th>
|
||||
<th style="text-align: right;">Ingresos Reservas ($)</th>
|
||||
<th style="text-align: right;">Ingresos Cantina ($)</th>
|
||||
<th style="text-align: right;">Costo Mercadería ($)</th>
|
||||
<th style="text-align: right;">Saldo Neto del Día ($)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
if (data.dias && data.dias.length > 0) {
|
||||
data.dias.forEach(dia => {
|
||||
const nombreDia = new Date(dia.dia + 'T00:00:00').toLocaleDateString('es-ES', { weekday: 'long' });
|
||||
const saldoNeto = dia.saldo_neto;
|
||||
const claseNegativo = saldoNeto < 0 ? 'negativo' : '';
|
||||
|
||||
html += `
|
||||
<tr>
|
||||
<td><strong>${nombreDia.charAt(0).toUpperCase() + nombreDia.slice(1)} (${dia.dia})</strong></td>
|
||||
<td class="monto">${formatoMoneda(dia.ingresos_reservas)}</td>
|
||||
<td class="monto">${formatoMoneda(dia.ingresos_cantina)}</td>
|
||||
<td class="monto negativo">${formatoMoneda(dia.costo_mercaderia)}</td>
|
||||
<td class="monto ${claseNegativo}"><strong>${formatoMoneda(saldoNeto)}</strong></td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
html += `
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table class="reporte-tabla">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="5" style="background: #34495e; color: white; text-align: center;">Resumen de la Semana</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="text-align: right;"><strong>Ingresos Reservas Total:</strong></td>
|
||||
<td style="text-align: right;" colspan="4" class="monto encabezado">${formatoMoneda(data.resumen.ingresos_reservas_totales)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;"><strong>Ingresos Cantina Total:</strong></td>
|
||||
<td style="text-align: right;" colspan="4" class="monto encabezado">${formatoMoneda(data.resumen.ingresos_cantina_totales)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;"><strong>Costo Mercadería Total:</strong></td>
|
||||
<td style="text-align: right;" colspan="4" class="monto encabezado negativo">${formatoMoneda(data.resumen.costo_mercaderia_total)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: right;"><strong>💰 Saldo Neto Semana:</strong></td>
|
||||
<td style="text-align: right;" colspan="4" class="monto encabezado ${data.resumen.saldo_neto_semana < 0 ? 'negativo' : ''}">${formatoMoneda(data.resumen.saldo_neto_semana)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="resumen-reporte">
|
||||
<h3>% Ocupación Promedio</h3>
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">Turnos Ocupados:</span>
|
||||
<span class="resumen-valor neutral">${data.resumen.turnos_ocupados} / ${data.resumen.turnos_disponibles}</span>
|
||||
</div>
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">Porcentaje Ocupación:</span>
|
||||
<span class="resumen-valor neutral">${data.resumen.ocupacion_promedio}%</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
reporteContenido.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderizarMensual(data) {
|
||||
const fechaInicio = new Date(data.fecha_inicio + 'T00:00:00');
|
||||
const mesCorta = (fechaInicio.getMonth() + 1).toString().padStart(2, '0') + '-' +
|
||||
fechaInicio.getFullYear().toString().slice(-2);
|
||||
const nombreMes = data.nombre_mes || fechaInicio.toLocaleDateString('es-ES', { month: 'long' });
|
||||
|
||||
let html = `
|
||||
<div class="reporte-titulo">
|
||||
<h2>📊 Reporte Mensual ${mesCorta} - Rentabilidad</h2>
|
||||
<div class="reporte-periodo">Reporte de ${nombreMes.charAt(0).toUpperCase() + nombreMes.slice(1)}</div>
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 30px; color: #2c3e50;">Ingreso por Categoría</h3>
|
||||
<table class="reporte-tabla">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Concepto / Categoría</th>
|
||||
<th style="text-align: right;">Operaciones</th>
|
||||
<th style="text-align: right;">Monto ($)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
if (data.categorias && data.categorias.length > 0) {
|
||||
data.categorias.forEach(cat => {
|
||||
html += `
|
||||
<tr>
|
||||
<td><strong>${cat.categoria}</strong></td>
|
||||
<td style="text-align: right;">${cat.operaciones}</td>
|
||||
<td class="monto">${formatoMoneda(cat.monto)}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
html += `
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 style="margin-top: 30px; color: #2c3e50;">Gastos Fijos</h3>
|
||||
<table class="reporte-tabla">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre del Gasto</th>
|
||||
<th style="text-align: right;">Monto Gasto ($)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
if (data.gastos_fijos && data.gastos_fijos.length > 0) {
|
||||
data.gastos_fijos.forEach(gasto => {
|
||||
html += `
|
||||
<tr>
|
||||
<td>${gasto.descripcion}</td>
|
||||
<td class="monto negativo">${formatoMoneda(gasto.monto)}</td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
} else {
|
||||
html += `
|
||||
<tr>
|
||||
<td colspan="2" style="text-align: center; color: #7f8c8d;">Sin gastos registrados</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
html += `
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="resumen-reporte" style="margin-top: 30px;">
|
||||
<h3>Estado de Resultados</h3>
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">Ingresos Totales:</span>
|
||||
<span class="resumen-valor">${formatoMoneda(data.resumen.ingresos_totales)}</span>
|
||||
</div>
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">Costo de Mercadería:</span>
|
||||
<span class="resumen-valor negativo">-${formatoMoneda(data.resumen.costo_mercaderia)}</span>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 1px solid #2c3e50; margin: 15px 0;">
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">Ganancia Bruta:</span>
|
||||
<span class="resumen-valor">${formatoMoneda(data.resumen.ganancia_bruta)}</span>
|
||||
</div>
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">Gastos Fijos:</span>
|
||||
<span class="resumen-valor negativo">-${formatoMoneda(data.resumen.gastos_fijos_total)}</span>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 2px solid #2c3e50; margin: 15px 0;">
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">💰 GANANCIA NETA:</span>
|
||||
<span class="resumen-valor ${data.resumen.ganancia_neta < 0 ? 'negativo' : ''}" style="font-size: 1.3em;">${formatoMoneda(data.resumen.ganancia_neta)}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
reporteContenido.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderizarAnual(data) {
|
||||
let notaAnoActual = '';
|
||||
if (data.es_ano_actual) {
|
||||
notaAnoActual = `
|
||||
<div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 12px; margin-bottom: 20px; color: #856404;">
|
||||
<strong>ℹ️ Nota:</strong> Este es el año en curso. El reporte muestra datos hasta el ${data.mes_actual}° mes (mes actual).
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
let html = `
|
||||
<div class="reporte-titulo">
|
||||
<h2>📈 Reporte Anual ${data.periodo} - Visión Estratégica</h2>
|
||||
<div class="reporte-periodo">Reporte Año ${data.periodo}</div>
|
||||
</div>
|
||||
${notaAnoActual}
|
||||
|
||||
<table class="reporte-tabla">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mes</th>
|
||||
<th style="text-align: right;">Ingreso Operativo ($)</th>
|
||||
<th style="text-align: right;">Costo Bruto ($)</th>
|
||||
<th style="text-align: right;">Costo Operativo ($)</th>
|
||||
<th style="text-align: right;">Ganancia Bruta ($)</th>
|
||||
<th style="text-align: right;">Ganancia Neta ($)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
`;
|
||||
|
||||
if (data.meses && data.meses.length > 0) {
|
||||
data.meses.forEach(mes => {
|
||||
const gananciaNeta = parseFloat(mes.ganancia_neta);
|
||||
const claseNegativo = gananciaNeta < 0 ? 'negativo' : '';
|
||||
|
||||
html += `
|
||||
<tr>
|
||||
<td><strong>${mes.mes}</strong></td>
|
||||
<td class="monto">${formatoMoneda(mes.ingreso_operativo)}</td>
|
||||
<td class="monto negativo">${formatoMoneda(mes.costo_bruto)}</td>
|
||||
<td class="monto negativo">${formatoMoneda(mes.costo_operativo)}</td>
|
||||
<td class="monto">${formatoMoneda(mes.ganancia_bruta)}</td>
|
||||
<td class="monto ${claseNegativo}"><strong>${formatoMoneda(gananciaNeta)}</strong></td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
html += `
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="resumen-reporte" style="margin-top: 30px;">
|
||||
<h3>Totales Anuales</h3>
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">Ingreso Operativo Total:</span>
|
||||
<span class="resumen-valor">${formatoMoneda(data.totales.ingreso_operativo_total)}</span>
|
||||
</div>
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">Costo Bruto Total:</span>
|
||||
<span class="resumen-valor negativo">-${formatoMoneda(data.totales.costo_bruto_total)}</span>
|
||||
</div>
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">Costo Operativo Total:</span>
|
||||
<span class="resumen-valor negativo">-${formatoMoneda(data.totales.costo_operativo_total)}</span>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 1px solid #2c3e50; margin: 15px 0;">
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">Ganancia Bruta Total:</span>
|
||||
<span class="resumen-valor">${formatoMoneda(data.totales.ganancia_bruta_total)}</span>
|
||||
</div>
|
||||
<hr style="border: none; border-top: 2px solid #2c3e50; margin: 15px 0;">
|
||||
<div class="resumen-fila">
|
||||
<span class="resumen-label">💰 GANANCIA NETA ANUAL:</span>
|
||||
<span class="resumen-valor ${data.totales.ganancia_neta_total < 0 ? 'negativo' : ''}" style="font-size: 1.3em;">${formatoMoneda(data.totales.ganancia_neta_total)}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
reporteContenido.innerHTML = html;
|
||||
}
|
||||
|
||||
// ========== INICIALIZACIÓN ==========
|
||||
|
||||
// Establecer valores por defecto
|
||||
const hoy = new Date();
|
||||
document.getElementById('fecha-diario').valueAsDate = hoy;
|
||||
|
||||
const semanaActual = obtenerSemanaActual();
|
||||
document.getElementById('semana-input').value = semanaActual;
|
||||
|
||||
const mesActual = hoy.toISOString().slice(0, 7);
|
||||
document.getElementById('mes-input').value = mesActual;
|
||||
|
||||
document.getElementById('anio-input').value = hoy.getFullYear();
|
||||
|
||||
// Cambiar parámetros según tipo de reporte
|
||||
tipoReporte.addEventListener('change', () => {
|
||||
document.querySelectorAll('.param-section').forEach(el => el.style.display = 'none');
|
||||
|
||||
const tipo = tipoReporte.value;
|
||||
if (tipo === 'diario') {
|
||||
document.getElementById('param-diario').style.display = 'block';
|
||||
} else if (tipo === 'semanal') {
|
||||
document.getElementById('param-semanal').style.display = 'block';
|
||||
} else if (tipo === 'mensual') {
|
||||
document.getElementById('param-mensual').style.display = 'block';
|
||||
} else if (tipo === 'anual') {
|
||||
document.getElementById('param-anual').style.display = 'block';
|
||||
}
|
||||
});
|
||||
|
||||
// Generar reporte
|
||||
generarBtn.addEventListener('click', async () => {
|
||||
const tipo = tipoReporte.value;
|
||||
let url = '../php/Obtener_Reporte.php?tipo_reporte=' + tipo;
|
||||
|
||||
// Agregar parámetros según el tipo
|
||||
if (tipo === 'diario') {
|
||||
const fecha = document.getElementById('fecha-diario').value;
|
||||
if (!fecha) {
|
||||
mostrarError('Por favor selecciona una fecha');
|
||||
return;
|
||||
}
|
||||
url += '&fecha=' + fecha;
|
||||
} else if (tipo === 'semanal') {
|
||||
const semana = document.getElementById('semana-input').value;
|
||||
if (!semana) {
|
||||
mostrarError('Por favor selecciona una semana');
|
||||
return;
|
||||
}
|
||||
url += '&semana=' + semana;
|
||||
} else if (tipo === 'mensual') {
|
||||
const mes = document.getElementById('mes-input').value;
|
||||
if (!mes) {
|
||||
mostrarError('Por favor selecciona un mes');
|
||||
return;
|
||||
}
|
||||
url += '&mes=' + mes;
|
||||
} else if (tipo === 'anual') {
|
||||
const anio = document.getElementById('anio-input').value;
|
||||
if (!anio) {
|
||||
mostrarError('Por favor selecciona un año');
|
||||
return;
|
||||
}
|
||||
url += '&anio=' + anio;
|
||||
}
|
||||
|
||||
ocultarError();
|
||||
mostrarSpinner();
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
mostrarError(data.error);
|
||||
ocultarSpinner();
|
||||
return;
|
||||
}
|
||||
|
||||
ocultarSpinner();
|
||||
|
||||
// Generar nombres de archivo
|
||||
generarNombresYMetadatos(tipo, data);
|
||||
|
||||
// Renderizar según tipo
|
||||
if (tipo === 'diario') {
|
||||
renderizarDiario(data);
|
||||
} else if (tipo === 'semanal') {
|
||||
renderizarSemanal(data);
|
||||
} else if (tipo === 'mensual') {
|
||||
renderizarMensual(data);
|
||||
} else if (tipo === 'anual') {
|
||||
renderizarAnual(data);
|
||||
}
|
||||
|
||||
reporteContainer.style.display = 'block';
|
||||
exportarBtn.style.display = 'inline-block';
|
||||
} catch (error) {
|
||||
mostrarError('Error al cargar el reporte: ' + error.message);
|
||||
ocultarSpinner();
|
||||
}
|
||||
});
|
||||
|
||||
// Exportar a PDF
|
||||
exportarBtn.addEventListener('click', () => {
|
||||
const elemento = document.getElementById('reporte-contenido');
|
||||
const nombreArchivo = periodoActual.nombreArchivo;
|
||||
|
||||
const opciones = {
|
||||
margin: 10,
|
||||
filename: nombreArchivo + '.pdf',
|
||||
image: { type: 'jpeg', quality: 0.98 },
|
||||
html2canvas: { scale: 2, useCORS: true },
|
||||
jsPDF: { orientation: 'portrait', unit: 'mm', format: 'a4', compress: true }
|
||||
};
|
||||
|
||||
html2pdf()
|
||||
.set(opciones)
|
||||
.from(elemento)
|
||||
.save(nombreArchivo + '.pdf');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,621 @@
|
||||
// Estado global de la reserva
|
||||
let estadoReserva = {
|
||||
tipoCancha: null,
|
||||
fecha: null,
|
||||
horario: null,
|
||||
numeroCancha: null,
|
||||
quincho: false,
|
||||
descuento: 0,
|
||||
cuponeado: false,
|
||||
reservasRestantes: null,
|
||||
cantidadReservas: 0,
|
||||
umbral: 0
|
||||
};
|
||||
|
||||
// Obtener datos de las canchas al cargar
|
||||
let datoscCanchas = { futbol: null, padel: null, quincho: null };
|
||||
|
||||
// Variable global para guardar el código de WhatsApp temporalmente
|
||||
let codigoCorrecto = null;
|
||||
|
||||
// Cargar datos de las canchas desde la base de datos
|
||||
async function cargarDatosCanchas() {
|
||||
try {
|
||||
const response = await fetch('../php/Obtener_Canchas.php');
|
||||
const data = await response.json();
|
||||
console.log('Datos recibidos de Obtener_Canchas.php:', data);
|
||||
|
||||
const canchas = Array.isArray(data)
|
||||
? data
|
||||
: (data && Array.isArray(data.canchas) ? data.canchas : []);
|
||||
|
||||
if (!canchas.length) {
|
||||
console.warn('No se recibieron canchas válidas desde el servidor:', data);
|
||||
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) {
|
||||
console.warn('Tipo de cancha desconocido en la respuesta:', rawTipo, cancha);
|
||||
return;
|
||||
}
|
||||
|
||||
const duracion = parseInt(cancha.Duracion ?? cancha.duracion, 10) || 0;
|
||||
const precio = parseFloat(cancha.Precio ?? cancha.precio_venta ?? cancha.precio) || 0;
|
||||
const apertura = cancha.Hora_Apertura ?? cancha.apertura ?? null;
|
||||
const cierre = cancha.Hora_Cierre ?? cancha.cierre ?? null;
|
||||
const cantidad = parseInt(cancha.Cant_Canchas ?? cancha.cantidad, 10) || 0;
|
||||
const reservas = parseInt(cancha.Cant_Reservas_Cupon ?? cancha.reservas, 10) || 0;
|
||||
const descuento = parseFloat(cancha.Descuento_Cupon ?? cancha.descuento_cupon) || 0;
|
||||
const maxFaltas = parseInt(cancha.Cant_Faltas ?? cancha.cant_faltas, 10) || 0;
|
||||
const duracionCupon = parseInt(cancha.Duracion_Cupon ?? cancha.duracion_cupon, 10) || 0;
|
||||
|
||||
if (tipo === 'F') {
|
||||
datoscCanchas.futbol = {
|
||||
duracion,
|
||||
precio,
|
||||
apertura,
|
||||
cierre,
|
||||
cantidad,
|
||||
reservasParaCupon: reservas,
|
||||
descuentoCupon: descuento,
|
||||
maxFaltas,
|
||||
duracionCupon
|
||||
};
|
||||
} else if (tipo === 'P') {
|
||||
datoscCanchas.padel = {
|
||||
duracion,
|
||||
precio,
|
||||
apertura,
|
||||
cierre,
|
||||
cantidad,
|
||||
reservasParaCupon: reservas,
|
||||
descuentoCupon: descuento,
|
||||
maxFaltas,
|
||||
duracionCupon
|
||||
};
|
||||
} else if (tipo === 'Q') {
|
||||
datoscCanchas.quincho = {
|
||||
precio,
|
||||
reservasMax: reservas
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Datos de canchas cargados:', datoscCanchas);
|
||||
} catch (error) {
|
||||
console.error('Error al cargar datos de canchas desde BD:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializar la interfaz
|
||||
function inicializarReserva() {
|
||||
const botonesCancha = document.querySelectorAll('.btn-cancha-tipo');
|
||||
|
||||
botonesCancha.forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
botonesCancha.forEach(b => b.classList.remove('activo'));
|
||||
btn.classList.add('activo');
|
||||
estadoReserva.tipoCancha = btn.dataset.tipo;
|
||||
estadoReserva.fecha = null;
|
||||
estadoReserva.horario = null;
|
||||
|
||||
document.querySelector('.selector-fecha').style.display = 'block';
|
||||
document.querySelector('.selector-horario').style.display = 'none';
|
||||
document.querySelector('.datos-cliente').style.display = 'none';
|
||||
document.querySelector('.resumen-reserva').style.display = 'none';
|
||||
|
||||
generarDiasDisponibles();
|
||||
});
|
||||
});
|
||||
|
||||
// Botón volver
|
||||
document.querySelector('.btn-volver').addEventListener('click', () => {
|
||||
document.querySelector('.resumen-reserva').style.display = 'none';
|
||||
});
|
||||
|
||||
// --- LÓGICA DE DATOS DEL CLIENTE ---
|
||||
document.querySelector('.btn-verificar').addEventListener('click', async () => {
|
||||
const nombre = document.getElementById('cliente-nombre').value.trim();
|
||||
const telefono = document.getElementById('cliente-telefono').value.trim();
|
||||
const dni = document.getElementById('cliente-dni').value.trim();
|
||||
|
||||
if (!nombre || !telefono || !dni) {
|
||||
alert('Por favor completa todos tus datos antes de continuar');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!estadoReserva.numeroCancha) {
|
||||
alert('Selecciona primero el número de cancha disponible.');
|
||||
return;
|
||||
}
|
||||
|
||||
await obtenerEstadoCupon(dni, estadoReserva.tipoCancha);
|
||||
actualizarResumen();
|
||||
document.querySelector('.resumen-reserva').style.display = 'block';
|
||||
});
|
||||
|
||||
// Botón "Confirmar Reserva" (Pide el código)
|
||||
const btnSolicitar = document.getElementById('btn-solicitar-reserva');
|
||||
if (btnSolicitar) {
|
||||
btnSolicitar.addEventListener('click', async () => {
|
||||
await solicitarCodigoWhatsApp();
|
||||
});
|
||||
}
|
||||
|
||||
// Botón "Validar y Reservar" (Verifica el código)
|
||||
const btnVerificarWA = document.getElementById('btn-verificar-codigo');
|
||||
if (btnVerificarWA) {
|
||||
btnVerificarWA.addEventListener('click', async () => {
|
||||
const codigoIngresado = document.getElementById('codigo-verificacion').value;
|
||||
if (codigoIngresado === codigoCorrecto) {
|
||||
await guardarReservaFinal();
|
||||
} else {
|
||||
alert("Código incorrecto. Por favor, inténtelo de nuevo.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Checkbox quincho
|
||||
document.getElementById('incluir-quincho').addEventListener('change', (e) => {
|
||||
estadoReserva.quincho = e.target.checked;
|
||||
const precioQuincho = datoscCanchas.quincho?.precio || 0;
|
||||
|
||||
if (e.target.checked && precioQuincho > 0) {
|
||||
document.querySelector('.precio-quincho').style.display = 'inline-block';
|
||||
document.getElementById('quincho-reservas').style.display = 'block';
|
||||
document.getElementById('precio-quincho-val').textContent = precioQuincho.toFixed(2);
|
||||
} else {
|
||||
estadoReserva.quincho = false;
|
||||
document.querySelector('.precio-quincho').style.display = 'none';
|
||||
document.getElementById('quincho-reservas').style.display = 'none';
|
||||
e.target.checked = false;
|
||||
}
|
||||
actualizarPrecioTotal();
|
||||
});
|
||||
|
||||
const formComprobante = document.getElementById('form-comprobante');
|
||||
if (formComprobante) {
|
||||
formComprobante.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
await enviarComprobantePago();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Generar días disponibles
|
||||
function generarDiasDisponibles() {
|
||||
const contenedor = document.getElementById('dias-disponibles');
|
||||
contenedor.innerHTML = '';
|
||||
const hoy = new Date();
|
||||
const diasSemana = ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sab'];
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const fecha = new Date(hoy);
|
||||
fecha.setDate(fecha.getDate() + i);
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'btn-dia';
|
||||
|
||||
btn.innerHTML = `<div>${diasSemana[fecha.getDay()]}</div><div>${fecha.getDate()}/${fecha.getMonth() + 1}</div>`;
|
||||
btn.dataset.fecha = fecha.toISOString().split('T')[0];
|
||||
|
||||
btn.addEventListener('click', async () => {
|
||||
document.querySelectorAll('.btn-dia').forEach(b => b.classList.remove('activo'));
|
||||
btn.classList.add('activo');
|
||||
estadoReserva.fecha = btn.dataset.fecha;
|
||||
estadoReserva.horario = null;
|
||||
|
||||
generarHorarios();
|
||||
await actualizarDisponibilidadQuincho(btn.dataset.fecha);
|
||||
document.querySelector('.selector-horario').style.display = 'block';
|
||||
document.querySelector('.datos-cliente').style.display = 'none';
|
||||
document.querySelector('.resumen-reserva').style.display = 'none';
|
||||
});
|
||||
contenedor.appendChild(btn);
|
||||
}
|
||||
}
|
||||
|
||||
// Obtener disponibilidad de quinchos
|
||||
async function obtenerDisponibilidadQuincho(fecha) {
|
||||
try {
|
||||
const response = await fetch(`../php/Obtener_Disponibilidad_Quincho.php?fecha=${fecha}`);
|
||||
const data = await response.json();
|
||||
return data.success ? data.disponibles : 0;
|
||||
} catch (error) { return 0; }
|
||||
}
|
||||
|
||||
// Actualizar disponibilidad de quincho en UI
|
||||
async function actualizarDisponibilidadQuincho(fecha) {
|
||||
const disponibles = await obtenerDisponibilidadQuincho(fecha);
|
||||
const checkboxQuincho = document.getElementById('incluir-quincho');
|
||||
const quinchoInfo = document.getElementById('quincho-reservas');
|
||||
const quinchoAlerta = document.getElementById('quincho-no-disponible');
|
||||
|
||||
document.getElementById('quincho-disp').textContent = disponibles;
|
||||
|
||||
if (disponibles === 0) {
|
||||
checkboxQuincho.disabled = true;
|
||||
checkboxQuincho.checked = false;
|
||||
estadoReserva.quincho = false;
|
||||
quinchoInfo.style.display = 'none';
|
||||
quinchoAlerta.style.display = 'block';
|
||||
document.querySelector('.precio-quincho').style.display = 'none';
|
||||
} else {
|
||||
checkboxQuincho.disabled = false;
|
||||
quinchoAlerta.style.display = 'none';
|
||||
quinchoInfo.style.display = 'block';
|
||||
}
|
||||
actualizarPrecioTotal();
|
||||
}
|
||||
|
||||
// Obtener disponibilidad de cancha
|
||||
async function obtenerDisponibilidadCancha(fecha, horario, duracion, tipo) {
|
||||
try {
|
||||
const response = await fetch(`../php/Obtener_Disponibilidad_Cancha.php?fecha=${fecha}&horario=${horario}&duracion=${duracion}&tipo=${tipo === 'futbol' ? 'F' : 'P'}`);
|
||||
const data = await response.json();
|
||||
return data.success ? data.disponibles : 0;
|
||||
} catch (error) { return 0; }
|
||||
}
|
||||
|
||||
// Obtener números de canchas
|
||||
async function obtenerNumerosCanchas(fecha, horario, duracion, tipo) {
|
||||
try {
|
||||
const response = await fetch(`../php/Obtener_Canchas_Numeros.php?fecha=${fecha}&horario=${horario}&duracion=${duracion}&tipo=${tipo === 'futbol' ? 'F' : 'P'}`);
|
||||
const data = await response.json();
|
||||
return data.success ? data.canchas : [];
|
||||
} catch (error) { return []; }
|
||||
}
|
||||
|
||||
// Generar selección de número de cancha
|
||||
async function generarSeleccionNumeroCancha(horarioInicio) {
|
||||
const contenedor = document.getElementById('botones-canchas-numeros');
|
||||
contenedor.innerHTML = '';
|
||||
const tipo = estadoReserva.tipoCancha;
|
||||
const canchas = await obtenerNumerosCanchas(estadoReserva.fecha, horarioInicio, datoscCanchas[tipo].duracion, tipo);
|
||||
|
||||
canchas.forEach(cancha => {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'btn-numero-cancha';
|
||||
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';
|
||||
} else {
|
||||
btn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.btn-numero-cancha').forEach(b => b.classList.remove('activo'));
|
||||
btn.classList.add('activo');
|
||||
estadoReserva.numeroCancha = cancha.numero;
|
||||
|
||||
document.querySelector('.datos-cliente').style.display = 'block';
|
||||
});
|
||||
}
|
||||
contenedor.appendChild(btn);
|
||||
});
|
||||
}
|
||||
|
||||
// Generar horarios
|
||||
async function generarHorarios() {
|
||||
const contenedor = document.getElementById('grid-horarios');
|
||||
contenedor.innerHTML = '';
|
||||
|
||||
const tipo = estadoReserva.tipoCancha;
|
||||
const datos = datoscCanchas[tipo];
|
||||
|
||||
if (!datos || typeof datos.duracion !== 'number' || !datos.apertura || !datos.cierre) {
|
||||
console.error('No hay datos de configuración válidos para la cancha seleccionada:', tipo, datos);
|
||||
alert('Error al cargar los horarios. Revisa la configuración de la cancha.');
|
||||
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] = estadoReserva.fecha.split('-').map(Number);
|
||||
const fechaSeleccionada = new Date(selYear, selMonth - 1, selDay);
|
||||
const hoy = new Date(ahora.getFullYear(), ahora.getMonth(), ahora.getDate());
|
||||
const fechaSelSoloFecha = new Date(fechaSeleccionada.getFullYear(), fechaSeleccionada.getMonth(), fechaSeleccionada.getDate());
|
||||
const esHoy = fechaSelSoloFecha.getTime() === hoy.getTime();
|
||||
const minutoActual = ahora.getHours() * 60 + ahora.getMinutes();
|
||||
|
||||
let horaActualMin = aperturaTotalMin;
|
||||
const horarios = [];
|
||||
|
||||
while (horaActualMin + duracion <= cierreTotalMin) {
|
||||
const horas = Math.floor(horaActualMin / 60);
|
||||
const minutos = horaActualMin % 60;
|
||||
const horaFin = Math.floor((horaActualMin + duracion) / 60);
|
||||
const minutesFin = (horaActualMin + duracion) % 60;
|
||||
|
||||
horarios.push({
|
||||
inicio: `${String(horas).padStart(2, '0')}:${String(minutos).padStart(2, '0')}`,
|
||||
fin: `${String(horaFin).padStart(2, '0')}:${String(minutesFin).padStart(2, '0')}`,
|
||||
minutos: horaActualMin
|
||||
});
|
||||
horaActualMin += duracion;
|
||||
}
|
||||
|
||||
for (const horario of horarios) {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'horario-slot';
|
||||
btn.textContent = `${horario.inicio}\n-\n${horario.fin}`;
|
||||
|
||||
const inicioArr = horario.inicio.split(':').map(Number);
|
||||
const inicioMin = inicioArr[0] * 60 + inicioArr[1];
|
||||
const esPasado = fechaSelSoloFecha.getTime() < hoy.getTime() || (esHoy && inicioMin < minutoActual);
|
||||
|
||||
let disponibles = 0;
|
||||
if (!esPasado) {
|
||||
disponibles = await obtenerDisponibilidadCancha(estadoReserva.fecha, genericNormalizeTipo(horario.inicio), duracion, tipo);
|
||||
}
|
||||
|
||||
if (esPasado || disponibles === 0) {
|
||||
btn.disabled = true;
|
||||
btn.classList.add('no-disponible');
|
||||
btn.style.opacity = '0.5';
|
||||
btn.style.cursor = 'not-allowed';
|
||||
} else {
|
||||
btn.addEventListener('click', async () => {
|
||||
document.querySelectorAll('.horario-slot').forEach(b => b.classList.remove('activo'));
|
||||
btn.classList.add('activo');
|
||||
estadoReserva.horario = `${horario.inicio} - ${horario.fin}`;
|
||||
estadoReserva.numeroCancha = null;
|
||||
|
||||
document.getElementById('incluir-quincho').checked = false;
|
||||
estadoReserva.quincho = false;
|
||||
document.querySelector('.precio-quincho').style.display = 'none';
|
||||
document.getElementById('quincho-reservas').style.display = 'none';
|
||||
|
||||
await generarSeleccionNumeroCancha(horario.inicio);
|
||||
document.querySelector('.selector-numero-cancha').style.display = 'block';
|
||||
document.querySelector('.datos-cliente').style.display = 'none';
|
||||
document.querySelector('.resumen-reserva').style.display = 'none';
|
||||
});
|
||||
}
|
||||
contenedor.appendChild(btn);
|
||||
}
|
||||
}
|
||||
|
||||
function genericNormalizeTipo(inicio) {
|
||||
return inicio;
|
||||
}
|
||||
|
||||
// Actualizar resumen
|
||||
function actualizarResumen() {
|
||||
const tipo = estadoReserva.tipoCancha;
|
||||
const datos = datoscCanchas[tipo];
|
||||
|
||||
document.getElementById('res-cancha').textContent = `${tipo === 'futbol' ? 'Fútbol' : 'Pádel'} - Cancha ${estadoReserva.numeroCancha}`;
|
||||
document.getElementById('res-fecha').textContent = new Date(estadoReserva.fecha).toLocaleDateString('es-AR', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
|
||||
document.getElementById('res-horario').textContent = estadoReserva.horario;
|
||||
|
||||
const duracionHoras = Math.floor(datos.duracion / 60);
|
||||
const duracionMinutos = datos.duracion % 60;
|
||||
document.getElementById('res-duracion').textContent = duracionHoras > 0 ? (duracionMinutos > 0 ? `${duracionHoras}h ${duracionMinutos}m` : `${duracionHoras}h`) : `${duracionMinutos}m`;
|
||||
|
||||
if (estadoReserva.cuponeado) {
|
||||
document.getElementById('res-cupon').textContent = `Cupón activo: ${(estadoReserva.descuento * 100).toFixed(0)}% aplicado`;
|
||||
} else if (estadoReserva.reservasRestantes !== null) {
|
||||
document.getElementById('res-cupon').textContent = estadoReserva.reservasRestantes > 0 ? `Faltan ${estadoReserva.reservasRestantes} reserva(s) para cupón` : 'Sin cupón activo';
|
||||
} else {
|
||||
document.getElementById('res-cupon').textContent = '-';
|
||||
}
|
||||
|
||||
actualizarPrecioTotal();
|
||||
}
|
||||
|
||||
function actualizarPrecioTotal() {
|
||||
const tipo = estadoReserva.tipoCancha;
|
||||
const datos = datoscCanchas[tipo];
|
||||
|
||||
if (!datos || typeof datos.precio !== 'number') {
|
||||
console.error('No se puede actualizar el precio porque no se cargaron los datos de cancha:', tipo, datos);
|
||||
return;
|
||||
}
|
||||
|
||||
const basePrecio = datos.precio;
|
||||
const descuentoMonto = basePrecio * (estadoReserva.descuento || 0);
|
||||
const precioCancha = basePrecio - descuentoMonto;
|
||||
const totalPrecio = precioCancha + (estadoReserva.quincho ? (datoscCanchas.quincho?.precio || 0) : 0);
|
||||
|
||||
const precioOriginalElement = document.getElementById('precio-original');
|
||||
const precioOriginalValor = document.getElementById('res-precio-original');
|
||||
const labelPrecio = document.getElementById('label-precio');
|
||||
|
||||
if (estadoReserva.descuento > 0) {
|
||||
// Mostrar precio original tachado
|
||||
precioOriginalValor.textContent = `$${basePrecio.toFixed(2)}`;
|
||||
precioOriginalElement.style.display = 'block';
|
||||
labelPrecio.textContent = 'Precio Final:';
|
||||
} else {
|
||||
// Ocultar precio original
|
||||
precioOriginalElement.style.display = 'none';
|
||||
labelPrecio.textContent = 'Precio:';
|
||||
}
|
||||
|
||||
document.getElementById('res-precio').textContent = `$${totalPrecio.toFixed(2)}`;
|
||||
}
|
||||
|
||||
async function obtenerEstadoCupon(dni, tipo) {
|
||||
try {
|
||||
const response = await fetch(`../php/Obtener_Cupon.php?dni=${encodeURIComponent(dni)}&tipo=${encodeURIComponent(tipo)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error(data.message || 'No se pudo obtener el estado del cupón');
|
||||
}
|
||||
|
||||
estadoReserva.cuponeado = data.aplicarDescuento;
|
||||
estadoReserva.descuento = data.aplicarDescuento ? parseFloat(data.descuentoCanchas) : 0;
|
||||
estadoReserva.cantidadReservas = data.cantidadReservas;
|
||||
estadoReserva.umbral = data.umbral;
|
||||
estadoReserva.reservasRestantes = data.reservasRestantes;
|
||||
|
||||
if (!estadoReserva.cuponeado && data.reservasRestantes === 0 && data.umbral === 0) {
|
||||
estadoReserva.reservasRestantes = null;
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error al obtener estado de cupón:', error);
|
||||
alert('No se pudo determinar el estado del cupón. Intenta nuevamente.');
|
||||
estadoReserva.cuponeado = false;
|
||||
estadoReserva.descuento = 0;
|
||||
estadoReserva.reservasRestantes = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// --- FUNCIONES DE WHATSAPP Y GUARDADO ---
|
||||
|
||||
async function solicitarCodigoWhatsApp() {
|
||||
const nombre = document.getElementById('cliente-nombre').value.trim();
|
||||
const telefono = document.getElementById('cliente-telefono').value.trim();
|
||||
|
||||
const btnSolicitar = document.getElementById('btn-solicitar-reserva');
|
||||
btnSolicitar.disabled = true;
|
||||
btnSolicitar.textContent = 'Enviando código...';
|
||||
|
||||
try {
|
||||
const response = await fetch('../php/Enviar_Codigo_WSP.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ telefono: telefono, nombre: nombre })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
codigoCorrecto = data.codigo;
|
||||
|
||||
// Ocultar botones de Confirmar/Volver y mostrar campo de validación
|
||||
document.querySelector('.botones-reserva').style.display = 'none';
|
||||
document.getElementById('lbl-telefono').textContent = telefono;
|
||||
document.getElementById('seccion-verificacion').style.display = 'block';
|
||||
|
||||
console.log('Código enviado a Selenium:', data.codigo);
|
||||
} else {
|
||||
alert('Error al enviar el mensaje de WhatsApp.');
|
||||
btnSolicitar.disabled = false;
|
||||
btnSolicitar.textContent = 'Confirmar Reserva';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('Error de conexión con el servidor.');
|
||||
btnSolicitar.disabled = false;
|
||||
btnSolicitar.textContent = 'Confirmar Reserva';
|
||||
}
|
||||
}
|
||||
|
||||
async function guardarReservaFinal() {
|
||||
const tipo = estadoReserva.tipoCancha;
|
||||
const datos = {
|
||||
nombre: document.getElementById('cliente-nombre').value.trim(),
|
||||
telefono: document.getElementById('cliente-telefono').value.trim(),
|
||||
dni: document.getElementById('cliente-dni').value.trim(),
|
||||
fecha: estadoReserva.fecha,
|
||||
horario: estadoReserva.horario.split(' - ')[0],
|
||||
tipo: tipo === 'futbol' ? 'F' : 'P',
|
||||
numeroCancha: estadoReserva.numeroCancha,
|
||||
monto: datoscCanchas[tipo].precio,
|
||||
incluirQuincho: estadoReserva.quincho,
|
||||
montoQuincho: estadoReserva.quincho ? datoscCanchas.quincho.precio : 0
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('../php/Guardar_Reserva.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(datos)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
alert('¡Reserva confirmada exitosamente!');
|
||||
|
||||
// ADAPTACIÓN: Ocultamos la sección de reserva en vez de refrescar la pantalla
|
||||
document.getElementById('seccion-verificacion').style.display = 'none';
|
||||
document.querySelector('.resumen-reserva').style.display = 'none';
|
||||
|
||||
// Cargamos el ID retornado por tu servidor en el input oculto del formulario
|
||||
// Nota: Tu php/Guardar_Reserva.php debe retornar un campo id_reserva (ej: result.id_reserva)
|
||||
const idNuevaReserva = result.id_reserva || result.id || 0;
|
||||
document.getElementById('comprobante-id-reserva').value = idNuevaReserva;
|
||||
|
||||
// Hacemos visible la ventana de carga para la imagen
|
||||
document.getElementById('seccion-comprobante').style.display = 'block';
|
||||
} else {
|
||||
alert('Error al guardar: ' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('Ocurrió un error crítico al intentar guardar la reserva.');
|
||||
}
|
||||
}
|
||||
|
||||
// --- FUNCIÓN ASÍNCRONA PARA SUBIR EL COMPROBANTE DE PAGO ---
|
||||
async function enviarComprobantePago() {
|
||||
const idReserva = document.getElementById('comprobante-id-reserva').value;
|
||||
const fileInput = document.getElementById('input-file-comprobante');
|
||||
const btnSubir = document.querySelector('#form-comprobante button[type="submit"]');
|
||||
|
||||
if (fileInput.files.length === 0) {
|
||||
alert("Por favor, selecciona una foto de tu comprobante.");
|
||||
return;
|
||||
}
|
||||
|
||||
btnSubir.disabled = true;
|
||||
btnSubir.textContent = "Subiendo archivo...";
|
||||
|
||||
// Construimos el FormData binario
|
||||
const formData = new FormData();
|
||||
formData.append('id_reserva', idReserva);
|
||||
formData.append('comprobante', fileInput.files[0]);
|
||||
|
||||
try {
|
||||
const response = await fetch('../php/Subir_Comprobante.php', {
|
||||
method: 'POST',
|
||||
body: formData // El navegador setea automáticamente el multipart/form-data
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
alert("¡Comprobante enviado con éxito! El administrador verificará su pago en la brevedad.");
|
||||
location.reload(); // Ahora sí, reiniciamos la vista limpia del complejo
|
||||
} else {
|
||||
alert("Error: " + data.error);
|
||||
btnSubir.disabled = false;
|
||||
btnSubir.textContent = "Enviar Comprobante";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error al subir el comprobante:", error);
|
||||
alert("Error de conexión al procesar el archivo.");
|
||||
btnSubir.disabled = false;
|
||||
btnSubir.textContent = "Enviar Comprobante";
|
||||
}
|
||||
}
|
||||
|
||||
// Inicializar cuando el DOM esté listo
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
await cargarDatosCanchas();
|
||||
inicializarReserva();
|
||||
});
|
||||
@@ -0,0 +1,208 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const fechaInput = document.getElementById('fecha-resumen');
|
||||
const totalVerInput = document.getElementById('total-ver');
|
||||
const tablaVentas = document.getElementById('tabla-ver-ventas');
|
||||
const tablaDetalle = document.getElementById('tabla-detalle-venta');
|
||||
const detalleContenedor = document.getElementById('detalle-venta-contenedor');
|
||||
const tablaPrincipal = document.getElementById('tabla-principal-contenedor');
|
||||
|
||||
// --- Estado de la aplicación ---
|
||||
let state = {
|
||||
vistaActual: 'principal', // 'principal' o 'detalle'
|
||||
ventaSeleccionada: null,
|
||||
ventasDelDia: []
|
||||
};
|
||||
|
||||
// --- Funciones de utilidad ---
|
||||
function formatMoney(v) {
|
||||
return '$' + Number(v).toFixed(2);
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('es-AR');
|
||||
}
|
||||
|
||||
// --- Función para cambiar vistas ---
|
||||
function mostrarVistaPrincipal() {
|
||||
state.vistaActual = 'principal';
|
||||
state.ventaSeleccionada = null;
|
||||
if (tablaPrincipal) tablaPrincipal.style.display = 'block';
|
||||
if (detalleContenedor) detalleContenedor.style.display = 'none';
|
||||
}
|
||||
|
||||
function mostrarVistaDetalle(idVenta) {
|
||||
state.vistaActual = 'detalle';
|
||||
state.ventaSeleccionada = idVenta;
|
||||
if (tablaPrincipal) tablaPrincipal.style.display = 'none';
|
||||
if (detalleContenedor) detalleContenedor.style.display = 'block';
|
||||
}
|
||||
|
||||
// --- Carga de Ventas ---
|
||||
function cargarVentasActuales() {
|
||||
const fechaSeleccionada = fechaInput.value;
|
||||
if (fechaSeleccionada) {
|
||||
console.log("Actualizando vista de ventas para:", fechaSeleccionada);
|
||||
fetchVentas({ start: fechaSeleccionada, end: fechaSeleccionada });
|
||||
}
|
||||
}
|
||||
|
||||
function fetchVentas(params) {
|
||||
const query = new URLSearchParams(params).toString();
|
||||
fetch(`../php/Obtener_Ventas.php?${query}`)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
state.ventasDelDia = data || [];
|
||||
renderTablaVentas(data);
|
||||
calcularTotalDia(data);
|
||||
console.log('Ventas cargadas:', data);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error al cargar ventas:', err);
|
||||
if (tablaVentas) {
|
||||
tablaVentas.querySelector('tbody').innerHTML =
|
||||
'<tr><td colspan="4" style="text-align: center; color: red;">Error al cargar ventas</td></tr>';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderTablaVentas(ventas) {
|
||||
if (!tablaVentas) return;
|
||||
|
||||
const tbody = tablaVentas.querySelector('tbody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (!ventas || ventas.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" style="text-align: center;">No hay ventas en el período seleccionado</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
ventas.forEach(venta => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${venta.ID_Venta || '-'}</td>
|
||||
<td>${venta.fecha_hora ? formatDate(venta.fecha_hora) : '-'}</td>
|
||||
<td>${formatMoney(venta.monto_total)}</td>
|
||||
<td>
|
||||
<button class="btn-ver-detalle" onclick="window.verDetalleVenta(${venta.ID_Venta})" title="Ver detalle">Ver Productos</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function calcularTotalDia(ventas) {
|
||||
let total = 0;
|
||||
if (ventas && Array.isArray(ventas) && ventas.length > 0) {
|
||||
total = ventas.reduce((sum, v) => sum + Number(v.monto_total || 0), 0);
|
||||
}
|
||||
if (totalVerInput) totalVerInput.value = formatMoney(total);
|
||||
}
|
||||
|
||||
// --- Funcionalidad de Detalle ---
|
||||
function cargarDetalleVenta(idVenta) {
|
||||
console.log('Cargando detalle para venta:', idVenta);
|
||||
|
||||
fetch(`../php/Obtener_Ventas.php?id_venta=${idVenta}`)
|
||||
.then(res => {
|
||||
console.log('Respuesta status:', res.status);
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP Error: ${res.status}`);
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Detalle cargado:', data);
|
||||
|
||||
// Verificar si hay error en la respuesta
|
||||
if (data.error) {
|
||||
console.error('Error del servidor:', data.error);
|
||||
alert('Error: ' + data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Si la respuesta es un array vacío, mostrar mensaje
|
||||
if (Array.isArray(data) && data.length === 0) {
|
||||
console.warn('No hay productos para esta venta');
|
||||
}
|
||||
|
||||
renderDetalleVenta(data);
|
||||
mostrarVistaDetalle(idVenta);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error completo:', err);
|
||||
alert('❌ Error al cargar el detalle de la venta: ' + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
function renderDetalleVenta(data) {
|
||||
if (!tablaDetalle) return;
|
||||
|
||||
// Asumimos que data contiene los productos de la venta
|
||||
const productos = Array.isArray(data) ? data : (data.productos || []);
|
||||
|
||||
let totalVenta = 0;
|
||||
const tbody = tablaDetalle.querySelector('tbody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (productos.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" style="text-align: center;">No hay productos en esta venta</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
productos.forEach(prod => {
|
||||
const cantidad = Number(prod.cantidad || 0);
|
||||
const precio = Number(prod.precio_unitario || prod.precio || 0);
|
||||
const subtotal = cantidad * precio;
|
||||
totalVenta += subtotal;
|
||||
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${prod.nombre || prod.descripcion || '-'}</td>
|
||||
<td style="text-align: center;">${cantidad}</td>
|
||||
<td style="text-align: right;">${formatMoney(precio)}</td>
|
||||
<td style="text-align: right;">${formatMoney(subtotal)}</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
// Agregar fila de total
|
||||
const rowTotal = document.createElement('tr');
|
||||
rowTotal.style.fontWeight = 'bold';
|
||||
rowTotal.style.borderTop = '2px solid #333';
|
||||
rowTotal.innerHTML = `
|
||||
<td colspan="3" style="text-align: right;">TOTAL GENERAL:</td>
|
||||
<td style="text-align: right;">${formatMoney(totalVenta)}</td>
|
||||
`;
|
||||
tbody.appendChild(rowTotal);
|
||||
}
|
||||
|
||||
// --- Exponemos función global para el onclick ---
|
||||
window.verDetalleVenta = function(idVenta) {
|
||||
cargarDetalleVenta(idVenta);
|
||||
};
|
||||
|
||||
// --- Botón Volver ---
|
||||
const btnVolver = document.getElementById('btn-volver-ventas');
|
||||
if (btnVolver) {
|
||||
btnVolver.addEventListener('click', mostrarVistaPrincipal);
|
||||
}
|
||||
|
||||
// --- Inicialización ---
|
||||
const hoyStr = new Date().toISOString().slice(0, 10);
|
||||
if (fechaInput) {
|
||||
fechaInput.value = hoyStr;
|
||||
cargarVentasActuales();
|
||||
fechaInput.addEventListener('change', () => {
|
||||
mostrarVistaPrincipal();
|
||||
cargarVentasActuales();
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("hashchange", () => {
|
||||
if (window.location.hash === "#ver_ventas") {
|
||||
mostrarVistaPrincipal();
|
||||
cargarVentasActuales();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,291 @@
|
||||
let gastosActuales = {};
|
||||
let columnasGastos = []; // Columnas dinámicas de gastos
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
cargarEstructuraGastos();
|
||||
setupFormListeners();
|
||||
});
|
||||
|
||||
function setupFormListeners() {
|
||||
// Formulario para agregar nuevo tipo de gasto
|
||||
const formAgregarTipo = document.getElementById('form-agregar-tipo-gasto');
|
||||
if (formAgregarTipo) {
|
||||
formAgregarTipo.addEventListener('submit', handleAgregarTipoGasto);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== CARGAR ESTRUCTURA DINÁMICA ==========
|
||||
|
||||
async function cargarEstructuraGastos() {
|
||||
try {
|
||||
const response = await fetch('../php/Obtener_Gastos.php');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
columnasGastos = data.columnas || [];
|
||||
gastosActuales = data.gasto || {};
|
||||
|
||||
construirFormularioDinamico();
|
||||
} else {
|
||||
console.log('No hay gastos registrados:', data.message);
|
||||
columnasGastos = [];
|
||||
construirFormularioDinamico();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error al cargar gastos:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== CONSTRUIR FORMULARIO DINÁMICO ==========
|
||||
|
||||
function construirFormularioDinamico() {
|
||||
// Solo mostrar tabla de gastos
|
||||
mostrarTablaGastos();
|
||||
}
|
||||
|
||||
// ========== AGREGAR NUEVO TIPO DE GASTO ==========
|
||||
|
||||
async function handleAgregarTipoGasto(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const nombre = document.getElementById('nombre-nuevo-gasto').value.trim();
|
||||
const monto = parseFloat(document.getElementById('monto-nuevo-gasto').value);
|
||||
|
||||
if (!nombre || monto < 0) {
|
||||
alert('Por favor completa los datos correctamente');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validar nombre (solo letras y espacios)
|
||||
if (!/^[a-zA-Z\s]+$/.test(nombre)) {
|
||||
alert('El nombre del gasto solo puede contener letras y espacios');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('../php/Registrar_Gastos.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
nuevo_gasto: nombre,
|
||||
monto: monto
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
alert('Nuevo tipo de gasto agregado correctamente');
|
||||
document.getElementById('form-agregar-tipo-gasto').reset();
|
||||
cargarEstructuraGastos();
|
||||
} else {
|
||||
alert('Error al agregar gasto: ' + data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('Error de conexión');
|
||||
}
|
||||
}
|
||||
|
||||
// ========== MOSTRAR TABLA DE GASTOS ==========
|
||||
|
||||
function mostrarTablaGastos() {
|
||||
const tabla = document.getElementById('tabla-gastos');
|
||||
if (!tabla) return;
|
||||
|
||||
const tbody = tabla.querySelector('tbody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (columnasGastos.length === 0) {
|
||||
const tr = document.createElement('tr');
|
||||
const td = document.createElement('td');
|
||||
td.colSpan = 3;
|
||||
td.textContent = 'No hay gastos configurados';
|
||||
td.style.textAlign = 'center';
|
||||
td.style.color = '#999';
|
||||
tr.appendChild(td);
|
||||
tbody.appendChild(tr);
|
||||
return;
|
||||
}
|
||||
|
||||
columnasGastos.forEach(columna => {
|
||||
const tr = document.createElement('tr');
|
||||
const monto = gastosActuales[columna] || 0;
|
||||
const descripcionFormato = columna.charAt(0).toUpperCase() + columna.slice(1);
|
||||
|
||||
// Descripción
|
||||
const tdDesc = document.createElement('td');
|
||||
tdDesc.textContent = descripcionFormato;
|
||||
tr.appendChild(tdDesc);
|
||||
|
||||
// Monto
|
||||
const tdMonto = document.createElement('td');
|
||||
tdMonto.textContent = `$${parseFloat(monto).toFixed(2)}`;
|
||||
tr.appendChild(tdMonto);
|
||||
|
||||
// Acciones
|
||||
const tdAcciones = document.createElement('td');
|
||||
tdAcciones.style.display = 'flex';
|
||||
tdAcciones.style.gap = '10px';
|
||||
|
||||
// Botón Editar
|
||||
const btnEditar = document.createElement('button');
|
||||
btnEditar.type = 'button';
|
||||
btnEditar.className = 'btn-editar-gasto';
|
||||
btnEditar.textContent = 'Editar';
|
||||
btnEditar.addEventListener('click', () => activarEdicionGasto(btnEditar, columna));
|
||||
tdAcciones.appendChild(btnEditar);
|
||||
|
||||
// Botón Eliminar
|
||||
const btnEliminar = document.createElement('button');
|
||||
btnEliminar.type = 'button';
|
||||
btnEliminar.className = 'btn-eliminar-gasto';
|
||||
btnEliminar.textContent = 'Eliminar';
|
||||
btnEliminar.addEventListener('click', () => abrirModalEliminar(columna));
|
||||
tdAcciones.appendChild(btnEliminar);
|
||||
|
||||
tr.appendChild(tdAcciones);
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
|
||||
// ========== EDITAR GASTO ==========
|
||||
|
||||
function activarEdicionGasto(btnEditar, columnaActual) {
|
||||
const fila = btnEditar.closest('tr');
|
||||
const celdas = fila.querySelectorAll('td');
|
||||
const descripcionActual = celdas[0].textContent;
|
||||
const montoActual = parseFloat(celdas[1].textContent.replace('$', ''));
|
||||
|
||||
// Convertir descripción a input
|
||||
celdas[0].innerHTML = `<input type="text" value="${columnaActual}" class="input-descripcion-gasto">`;
|
||||
|
||||
// Convertir monto a input
|
||||
celdas[1].innerHTML = `<input type="number" value="${montoActual}" step="0.01" min="0" class="input-monto-gasto">`;
|
||||
|
||||
// Crear botones Guardar y Cancelar
|
||||
const btnGuardar = document.createElement('button');
|
||||
btnGuardar.type = 'button';
|
||||
btnGuardar.textContent = 'Guardar';
|
||||
btnGuardar.className = 'btn-guardar';
|
||||
btnGuardar.addEventListener('click', () => guardarEdicionGasto(fila, columnaActual, montoActual, descripcionActual));
|
||||
|
||||
const btnCancelar = document.createElement('button');
|
||||
btnCancelar.type = 'button';
|
||||
btnCancelar.textContent = 'Cancelar';
|
||||
btnCancelar.className = 'btn-cancelar';
|
||||
btnCancelar.addEventListener('click', () => cancelarEdicionGasto(fila, columnaActual, montoActual, descripcionActual));
|
||||
|
||||
// Reemplazar botones en la celda de acciones
|
||||
celdas[2].innerHTML = '';
|
||||
celdas[2].appendChild(btnGuardar);
|
||||
celdas[2].appendChild(btnCancelar);
|
||||
}
|
||||
|
||||
function cancelarEdicionGasto(fila, columnaActual, montoActual, descripcionActual) {
|
||||
const celdas = fila.querySelectorAll('td');
|
||||
|
||||
// Restaurar descripción
|
||||
celdas[0].textContent = descripcionActual;
|
||||
|
||||
// Restaurar monto
|
||||
celdas[1].textContent = `$${parseFloat(montoActual).toFixed(2)}`;
|
||||
|
||||
// Restaurar botones Editar y Eliminar
|
||||
const btnEditar = document.createElement('button');
|
||||
btnEditar.type = 'button';
|
||||
btnEditar.className = 'btn-editar-gasto';
|
||||
btnEditar.textContent = 'Editar';
|
||||
btnEditar.addEventListener('click', () => activarEdicionGasto(btnEditar, columnaActual));
|
||||
|
||||
const btnEliminar = document.createElement('button');
|
||||
btnEliminar.type = 'button';
|
||||
btnEliminar.className = 'btn-eliminar-gasto';
|
||||
btnEliminar.textContent = 'Eliminar';
|
||||
btnEliminar.addEventListener('click', () => abrirModalEliminar(columnaActual));
|
||||
|
||||
celdas[2].innerHTML = '';
|
||||
celdas[2].appendChild(btnEditar);
|
||||
celdas[2].appendChild(btnEliminar);
|
||||
}
|
||||
|
||||
async function guardarEdicionGasto(fila, columnaActual, montoActual, descripcionActual) {
|
||||
const nuevoNombre = fila.querySelector('.input-descripcion-gasto').value.trim();
|
||||
const nuevoMonto = parseFloat(fila.querySelector('.input-monto-gasto').value);
|
||||
|
||||
if (!nuevoNombre) {
|
||||
alert('El nombre no puede estar vacío');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN(nuevoMonto) || nuevoMonto < 0) {
|
||||
alert('El monto debe ser un número válido y mayor o igual a 0');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('../php/Editar_Gasto.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
columna_actual: columnaActual,
|
||||
columna_nueva: nuevoNombre,
|
||||
monto: nuevoMonto
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
alert('Gasto editado correctamente');
|
||||
cargarEstructuraGastos();
|
||||
} else {
|
||||
alert('Error al editar gasto: ' + data.message);
|
||||
cancelarEdicionGasto(fila, columnaActual, montoActual, descripcionActual);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('Error de conexión');
|
||||
cancelarEdicionGasto(fila, columnaActual, montoActual, descripcionActual);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== ELIMINAR GASTO ==========
|
||||
|
||||
function abrirModalEliminar(columna) {
|
||||
const confirmar = confirm(`¿Deseas eliminar el gasto "${columna}"? Esta acción no se puede deshacer.`);
|
||||
|
||||
if (confirmar) {
|
||||
eliminarGasto(columna);
|
||||
}
|
||||
}
|
||||
|
||||
async function eliminarGasto(columna) {
|
||||
try {
|
||||
const response = await fetch('../php/Eliminar_Gasto.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
columna: columna
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
alert('Gasto eliminado correctamente');
|
||||
cargarEstructuraGastos();
|
||||
} else {
|
||||
alert('Error al eliminar gasto: ' + data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('Error de conexión');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
let tablaProductosInitDone = false;
|
||||
|
||||
function initTablaProductos() {
|
||||
if (tablaProductosInitDone) return;
|
||||
tablaProductosInitDone = true;
|
||||
|
||||
const form = document.getElementById("form-producto");
|
||||
const tablaBody = document.querySelector("#tabla-productos tbody");
|
||||
|
||||
form.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const datos = new FormData(form);
|
||||
const producto = {
|
||||
descripcion: datos.get("descripcion"),
|
||||
precio_venta: parseFloat(datos.get("precio_venta")),
|
||||
precio_compra: parseFloat(datos.get("precio_compra")),
|
||||
cantidad: parseInt(datos.get("cantidad")),
|
||||
};
|
||||
|
||||
const esEdicion = !!form.dataset.editando;
|
||||
if (esEdicion) {
|
||||
producto.id = form.dataset.editando;
|
||||
}
|
||||
|
||||
const url = esEdicion
|
||||
? "../php/Editar_Producto.php"
|
||||
: "../php/Agregar_Producto.php";
|
||||
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(producto),
|
||||
})
|
||||
.then((res) => res.text())
|
||||
.then((msg) => {
|
||||
alert(msg);
|
||||
form.reset();
|
||||
delete form.dataset.editando;
|
||||
cargarProductos();
|
||||
verificarStockBajoMenu();
|
||||
})
|
||||
.catch((err) => console.error("Error al guardar producto:", err));
|
||||
});
|
||||
|
||||
verificarStockBajoMenu();
|
||||
}
|
||||
|
||||
// Stock limit helpers (stored in localStorage)
|
||||
function getStockLimit() {
|
||||
const val = localStorage.getItem('stock_limit_alert');
|
||||
const n = parseInt(val, 10);
|
||||
return isNaN(n) ? 10 : n;
|
||||
}
|
||||
|
||||
function setStockLimit(limit) {
|
||||
localStorage.setItem('stock_limit_alert', String(parseInt(limit, 10) || 0));
|
||||
verificarStockBajoMenu();
|
||||
if (typeof cargarProductos === 'function') cargarProductos();
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", initTablaProductos);
|
||||
if (document.readyState !== "loading") {
|
||||
initTablaProductos();
|
||||
}
|
||||
|
||||
const lowStockIconHTML = `<i class="fa-solid fa-triangle-exclamation fa-beat low-stock-alert" style="color: rgb(255, 212, 59); margin-left: 0.35rem;" aria-hidden="true" title="Stock bajo"></i>`;
|
||||
|
||||
function actualizarAlertaStockEnMenu(tieneStockBajo) {
|
||||
const linkProductos = document.querySelector('.sidebar__link[href="#productos"]');
|
||||
if (!linkProductos) return;
|
||||
|
||||
const alertaExistente = linkProductos.querySelector('.low-stock-alert');
|
||||
if (tieneStockBajo) {
|
||||
if (!alertaExistente) {
|
||||
linkProductos.insertAdjacentHTML('beforeend', lowStockIconHTML);
|
||||
}
|
||||
} else if (alertaExistente) {
|
||||
alertaExistente.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function verificarStockBajoMenu() {
|
||||
const limit = getStockLimit();
|
||||
fetch("../php/Obtener_Productos.php")
|
||||
.then((res) => res.json())
|
||||
.then((productos) => {
|
||||
const tieneStockBajo = productos.some((p) => parseInt(p.Stock_Disponible, 10) < limit);
|
||||
actualizarAlertaStockEnMenu(tieneStockBajo);
|
||||
})
|
||||
.catch((err) => console.error("Error al consultar stock bajo:", err));
|
||||
}
|
||||
|
||||
function cargarProductos() {
|
||||
fetch("../php/Obtener_Productos.php")
|
||||
.then((res) => res.json())
|
||||
.then((productos) => {
|
||||
const tablaBody = document.querySelector("#tabla-productos tbody");
|
||||
tablaBody.innerHTML = "";
|
||||
const limit = getStockLimit();
|
||||
const tieneStockBajo = productos.some((p) => parseInt(p.Stock_Disponible, 10) < limit);
|
||||
|
||||
productos.forEach((p) => {
|
||||
const stockBajo = parseInt(p.Stock_Disponible, 10) < limit;
|
||||
const fila = document.createElement("tr");
|
||||
fila.innerHTML = `
|
||||
<td>${p.ID_Producto}</td>
|
||||
<td>${p.Descripcion}</td>
|
||||
<td>$${parseFloat(p.Precio_Venta).toFixed(2)}</td>
|
||||
<td>$${parseFloat(p.Precio_Compra).toFixed(2)}</td>
|
||||
<td>${p.Stock_Disponible}${stockBajo ? lowStockIconHTML : ''}</td>
|
||||
<td>
|
||||
<button class="btn-editar" data-id="${
|
||||
p.ID_Producto
|
||||
}">Editar</button>
|
||||
<button class="btn-eliminar" data-id="${
|
||||
p.ID_Producto
|
||||
}">Eliminar</button>
|
||||
</td>
|
||||
`;
|
||||
tablaBody.appendChild(fila);
|
||||
});
|
||||
|
||||
actualizarAlertaStockEnMenu(tieneStockBajo);
|
||||
|
||||
document.querySelectorAll(".btn-editar").forEach((btn) => {
|
||||
btn.addEventListener("click", () => activarEdicionProducto(btn));
|
||||
});
|
||||
|
||||
document.querySelectorAll(".btn-eliminar").forEach((btn) => {
|
||||
btn.addEventListener("click", () => eliminarProducto(btn.dataset.id));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Init stock limit UI
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const input = document.getElementById('stock-limit-input');
|
||||
const btn = document.getElementById('save-stock-limit');
|
||||
if (input) {
|
||||
input.value = getStockLimit();
|
||||
}
|
||||
if (btn) {
|
||||
btn.addEventListener('click', () => {
|
||||
const v = document.getElementById('stock-limit-input').value;
|
||||
setStockLimit(parseInt(v, 10) || 0);
|
||||
alert('Límite de stock guardado: ' + (parseInt(v, 10) || 0));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function eliminarProducto(id) {
|
||||
if (!confirm("¿Estás seguro que querés eliminar este producto?")) return;
|
||||
|
||||
fetch(`../php/Eliminar_Producto.php?id=${id}`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
alert(data.message);
|
||||
if (data.success) {
|
||||
cargarProductos();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error:", err);
|
||||
alert("Error al eliminar el producto");
|
||||
});
|
||||
}
|
||||
|
||||
function activarEdicionProducto(btnEditar) {
|
||||
const fila = btnEditar.closest("tr");
|
||||
const celdas = fila.querySelectorAll("td");
|
||||
const idProducto = btnEditar.dataset.id;
|
||||
|
||||
const descripcionActual = celdas[1].textContent;
|
||||
const precioVentaActual = parseFloat(celdas[2].textContent.replace('$', ''));
|
||||
const precioCompraActual = parseFloat(celdas[3].textContent.replace('$', ''));
|
||||
const stockActual = parseInt(celdas[4].textContent);
|
||||
|
||||
celdas[1].innerHTML = `<input type="text" value="${descripcionActual}" class="input-descripcion">`;
|
||||
celdas[2].innerHTML = `<input type="number" value="${precioVentaActual}" class="input-precio-venta">`;
|
||||
celdas[3].innerHTML = `<input type="number" value="${precioCompraActual}" class="input-precio-compra">`;
|
||||
celdas[4].innerHTML = `<input type="number" value="${stockActual}" class="input-stock">`;
|
||||
|
||||
const btnGuardar = document.createElement("button");
|
||||
btnGuardar.textContent = "Guardar";
|
||||
btnGuardar.classList.add("btn-guardar");
|
||||
btnGuardar.addEventListener("click", () => guardarEdicionProducto(fila, idProducto));
|
||||
|
||||
const btnCancelar = document.createElement("button");
|
||||
btnCancelar.textContent = "Cancelar";
|
||||
btnCancelar.classList.add("btn-cancelar");
|
||||
btnCancelar.addEventListener("click", () =>
|
||||
cancelarEdicionProducto(fila, descripcionActual, precioVentaActual, precioCompraActual, stockActual, idProducto)
|
||||
);
|
||||
|
||||
const celdaBotones = celdas[5];
|
||||
celdaBotones.innerHTML = "";
|
||||
celdaBotones.appendChild(btnGuardar);
|
||||
celdaBotones.appendChild(btnCancelar);
|
||||
}
|
||||
|
||||
function cancelarEdicionProducto(fila, descripcion, venta, compra, stock, id) {
|
||||
const celdas = fila.querySelectorAll("td");
|
||||
celdas[1].textContent = descripcion;
|
||||
celdas[2].textContent = `$${parseFloat(venta).toFixed(2)}`;
|
||||
celdas[3].textContent = `$${parseFloat(compra).toFixed(2)}`;
|
||||
celdas[4].textContent = stock;
|
||||
|
||||
const btnEditar = document.createElement("button");
|
||||
btnEditar.textContent = "Editar";
|
||||
btnEditar.classList.add("btn-editar");
|
||||
btnEditar.dataset.id = id;
|
||||
btnEditar.addEventListener("click", () => activarEdicionProducto(btnEditar));
|
||||
|
||||
const btnEliminar = document.createElement("button");
|
||||
btnEliminar.textContent = "Eliminar";
|
||||
btnEliminar.classList.add("btn-eliminar");
|
||||
btnEliminar.dataset.id = id;
|
||||
btnEliminar.addEventListener("click", () => eliminarProducto(id));
|
||||
|
||||
celdas[5].innerHTML = "";
|
||||
celdas[5].appendChild(btnEditar);
|
||||
celdas[5].appendChild(btnEliminar);
|
||||
}
|
||||
|
||||
function guardarEdicionProducto(fila, idProducto) {
|
||||
const descripcion = fila.querySelector(".input-descripcion").value.trim();
|
||||
const precioVenta = parseFloat(fila.querySelector(".input-precio-venta").value);
|
||||
const precioCompra = parseFloat(fila.querySelector(".input-precio-compra").value);
|
||||
const stock = parseInt(fila.querySelector(".input-stock").value);
|
||||
|
||||
if (!descripcion || isNaN(precioVenta) || isNaN(precioCompra) || isNaN(stock)) {
|
||||
alert("Por favor, complete todos los campos correctamente.");
|
||||
return;
|
||||
}
|
||||
|
||||
fetch("../php/Editar_Producto.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
id: idProducto,
|
||||
descripcion,
|
||||
precio_venta: precioVenta,
|
||||
precio_compra: precioCompra,
|
||||
cantidad: stock
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
cargarProductos();
|
||||
} else {
|
||||
alert("Error al guardar producto: " + data.error);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Error:", err);
|
||||
alert("Ocurrió un error al editar el producto.");
|
||||
});
|
||||
}
|
||||
|
||||
// Actualizar Stock sin recargar la pagina
|
||||
window.addEventListener("hashchange", () => {
|
||||
if (window.location.hash === "#productos") {
|
||||
// Esperar un poco para que el DOM actualice la sección visible
|
||||
setTimeout(() => {
|
||||
const tabla = document.querySelector("#tabla-productos");
|
||||
if (tabla) {
|
||||
cargarProductos();
|
||||
} else {
|
||||
console.warn("Tabla de productos no encontrada.");
|
||||
}
|
||||
}, 100); // pequeño retardo para esperar que el DOM muestre la sección
|
||||
}
|
||||
});
|
||||
|
||||
window.cargarProductos = cargarProductos;
|
||||
@@ -0,0 +1,327 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const form = document.getElementById("form-proveedor");
|
||||
const tablaBody = document.querySelector("#tabla-proveedores tbody");
|
||||
|
||||
form.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const nombre = document.querySelector(".nombre-proveedor").value;
|
||||
const telefono = document.querySelector(".telefono-proveedor").value;
|
||||
|
||||
const proveedor = {
|
||||
id: form.dataset.editando || null,
|
||||
nombre,
|
||||
telefono,
|
||||
};
|
||||
|
||||
const url = proveedor.id
|
||||
? "../php/Editar_Proveedor.php"
|
||||
: "../php/Guardar_Proveedor.php";
|
||||
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(proveedor),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
alert(data.message || "Proveedor guardado");
|
||||
form.reset();
|
||||
delete form.dataset.editando;
|
||||
cargarProveedores();
|
||||
} else {
|
||||
alert("Error: " + (data.error || "Error desconocido"));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
cargarProveedores();
|
||||
|
||||
// Delegación de eventos para la tabla
|
||||
document.addEventListener("click", (e) => {
|
||||
if (e.target.classList.contains("btn-cancelar")) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const fila = e.target.closest("tr");
|
||||
if (fila) {
|
||||
cargarProveedores();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function cargarProveedores() {
|
||||
fetch("../php/Obtener_Proveedores.php")
|
||||
.then((res) => res.json())
|
||||
.then((proveedores) => {
|
||||
const tablaBody = document.querySelector("#tabla-proveedores tbody");
|
||||
tablaBody.innerHTML = "";
|
||||
|
||||
proveedores.forEach((p) => {
|
||||
const fila = document.createElement("tr");
|
||||
fila.setAttribute("data-id", p.ID_Proveedor);
|
||||
fila.innerHTML = `
|
||||
<td>${p.ID_Proveedor}</td>
|
||||
<td>${p.Nombre}</td>
|
||||
<td>${p.Telefono}</td>
|
||||
<td>
|
||||
<button class="btn-ver-productos" data-id="${p.ID_Proveedor}">Productos</button>
|
||||
<button class="btn-editar-proveedor" data-id="${p.ID_Proveedor}">Editar</button>
|
||||
<button class="btn-eliminar-proveedor" data-id="${p.ID_Proveedor}">Eliminar</button>
|
||||
</td>
|
||||
`;
|
||||
tablaBody.appendChild(fila);
|
||||
});
|
||||
|
||||
document.querySelectorAll("#tabla-proveedores .btn-editar-proveedor").forEach((btn) => {
|
||||
btn.addEventListener("click", () => activarModoEdicion(btn));
|
||||
});
|
||||
|
||||
document.querySelectorAll("#tabla-proveedores .btn-eliminar-proveedor").forEach((btn) => {
|
||||
btn.addEventListener("click", () => eliminarProveedor(btn.dataset.id));
|
||||
});
|
||||
|
||||
document.querySelectorAll(".btn-ver-productos").forEach((btn) => {
|
||||
btn.addEventListener("click", () => cargarProductosDelProveedor(btn.dataset.id));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function editarProveedor(id) {
|
||||
fetch(`../php/Buscar_Proveedor_Editar.php?id=${id}`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
console.log("Respuesta del servidor:", data);
|
||||
if (!data || !data.ID_Proveedor) {
|
||||
alert("Proveedor no encontrado");
|
||||
return;
|
||||
}
|
||||
|
||||
const form = document.getElementById("form-proveedor");
|
||||
document.querySelector(".nombre-proveedor").value = data.Nombre;
|
||||
document.querySelector(".telefono-proveedor").value = data.Telefono;
|
||||
form.dataset.editando = data.ID_Proveedor;
|
||||
});
|
||||
}
|
||||
|
||||
function eliminarProveedor(id) {
|
||||
if (!confirm("¿Estás seguro que querés eliminar este proveedor?")) return;
|
||||
|
||||
fetch("../php/Eliminar_Proveedor.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ id: parseInt(id, 10) }),
|
||||
})
|
||||
.then((res) => res.text())
|
||||
.then((text) => {
|
||||
if (!text) {
|
||||
throw new Error("Respuesta vacía del servidor.");
|
||||
}
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch (error) {
|
||||
console.warn("Respuesta de Eliminar_Proveedor no es JSON:", text);
|
||||
throw new Error("Respuesta inválida del servidor.");
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
alert(data.message || "Proveedor eliminado");
|
||||
cargarProveedores();
|
||||
} else {
|
||||
alert("Error: " + (data.error || data.message || "Error desconocido"));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error al eliminar proveedor:", error);
|
||||
alert(error.message || "Error de conexión al eliminar proveedor.");
|
||||
});
|
||||
}
|
||||
|
||||
function cargarProductosDelProveedor(idProveedor) {
|
||||
fetch(`../php/Obtener_Productos_Proveedor.php?id=${idProveedor}`)
|
||||
.then((res) => res.json())
|
||||
.then((productos) => {
|
||||
const contenedor = document.getElementById("productos-proveedor");
|
||||
const cuerpo = document.getElementById("productos-proveedor-body");
|
||||
cuerpo.innerHTML = "";
|
||||
|
||||
productos.forEach((prod) => {
|
||||
const fila = document.createElement("tr");
|
||||
fila.setAttribute("data-id", idProveedor);
|
||||
fila.innerHTML = `
|
||||
<td>${prod.ID_Producto}</td>
|
||||
<td>${prod.Descripcion}</td>
|
||||
<td>
|
||||
<button class="btn-eliminar-producto" data-id-producto="${prod.ID_Producto}" data-id-proveedor="${idProveedor}">Eliminar</button>
|
||||
</td>
|
||||
`;
|
||||
cuerpo.appendChild(fila);
|
||||
});
|
||||
|
||||
contenedor.style.display = "block";
|
||||
|
||||
document.querySelectorAll(".btn-eliminar-producto").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
const idProd = btn.dataset.idProducto;
|
||||
const idProv = btn.dataset.idProveedor;
|
||||
|
||||
fetch(
|
||||
`../php/Eliminar_Producto_Proveedor.php?id_proveedor=${idProv}&id_producto=${idProd}`
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
cargarProductosDelProveedor(idProv);
|
||||
} else {
|
||||
alert("Error: " + data.error);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Agregar producto al proveedor
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const formAgregarProducto = document.getElementById("form-agregar-producto");
|
||||
|
||||
formAgregarProducto.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const idProveedor = document.getElementById("id-proveedor-producto").value;
|
||||
const idProducto = document.getElementById("nuevo-id-producto").value;
|
||||
|
||||
fetch("../php/Agregar_Producto_Proveedor.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
id_proveedor: parseInt(idProveedor),
|
||||
id_producto: parseInt(idProducto),
|
||||
}),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
cargarProductosDelProveedor(idProveedor);
|
||||
document.getElementById("nuevo-id-producto").value = "";
|
||||
} else {
|
||||
alert("Error: " + data.error);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener("click", function (e) {
|
||||
if (e.target.classList.contains("btn-ver-productos")) {
|
||||
const idProveedor = e.target.dataset.id;
|
||||
|
||||
document.getElementById("id-proveedor-producto").value = idProveedor;
|
||||
cargarProductosDelProveedor(idProveedor);
|
||||
|
||||
// Mostrar tabla productos
|
||||
document.getElementById("productos-proveedor").style.display = "block";
|
||||
document.getElementById("tabla-proveedores").style.display = "none";
|
||||
document.getElementById("titulo-proveedores").style.display = "none";
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("cerrar-productos").addEventListener("click", () => {
|
||||
document.getElementById("productos-proveedor").style.display = "none";
|
||||
document.getElementById("tabla-proveedores").style.display = "table";
|
||||
document.getElementById("titulo-proveedores").style.display = "block";
|
||||
});
|
||||
|
||||
function activarModoEdicion(btnEditar) {
|
||||
const fila = btnEditar.closest("tr");
|
||||
const celdas = fila.querySelectorAll("td");
|
||||
|
||||
// Obtener ID desde el botón
|
||||
const idProveedor = btnEditar.getAttribute("data-id");
|
||||
|
||||
if (!idProveedor) {
|
||||
console.error("ID DE PROVEEDOR NO ENCONTRADO");
|
||||
alert("ID de proveedor no encontrado.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Obtener valores actuales ANTES de cambiar a inputs
|
||||
const nombreActual = celdas[1].textContent.trim();
|
||||
const telefonoActual = celdas[2].textContent.trim();
|
||||
|
||||
// Reemplazar con inputs
|
||||
celdas[1].innerHTML = `<input type="text" value="${nombreActual}" class="input-nombre">`;
|
||||
celdas[2].innerHTML = `<input type="text" value="${telefonoActual}" class="input-telefono">`;
|
||||
|
||||
// Botón Guardar
|
||||
const btnGuardar = document.createElement("button");
|
||||
btnGuardar.type = "button";
|
||||
btnGuardar.textContent = "Guardar";
|
||||
btnGuardar.className = "btn-guardar";
|
||||
btnGuardar.setAttribute("data-id", idProveedor);
|
||||
btnGuardar.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
guardarEdicion(fila, idProveedor);
|
||||
});
|
||||
|
||||
// Botón Cancelar
|
||||
const btnCancelar = document.createElement("button");
|
||||
btnCancelar.type = "button";
|
||||
btnCancelar.textContent = "Cancelar";
|
||||
btnCancelar.className = "btn-cancelar";
|
||||
btnCancelar.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Simplemente recargar la tabla cancela todos los cambios
|
||||
cargarProveedores();
|
||||
});
|
||||
|
||||
// Reemplazar celda de botones
|
||||
const celdaBotones = celdas[3];
|
||||
celdaBotones.innerHTML = "";
|
||||
celdaBotones.appendChild(btnGuardar);
|
||||
celdaBotones.appendChild(btnCancelar);
|
||||
}
|
||||
|
||||
function guardarEdicion(fila, idProveedor) {
|
||||
const celdas = fila.querySelectorAll("td");
|
||||
const nuevoNombre = fila.querySelector(".input-nombre").value.trim();
|
||||
const nuevoTelefono = fila.querySelector(".input-telefono").value.trim();
|
||||
|
||||
if (nuevoNombre === "" || nuevoTelefono === "") {
|
||||
alert("Por favor, complete todos los campos.");
|
||||
return;
|
||||
}
|
||||
console.log("Datos enviados:", {
|
||||
id: idProveedor,
|
||||
nombre: nuevoNombre,
|
||||
telefono: nuevoTelefono,
|
||||
});
|
||||
|
||||
// Enviar los nuevos datos al servidor para actualizarlos en la base de datos
|
||||
fetch("../php/Editar_Proveedor.php", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
id: idProveedor,
|
||||
nombre: nuevoNombre,
|
||||
telefono: nuevoTelefono,
|
||||
}),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
alert("Proveedor actualizado correctamente");
|
||||
cargarProveedores();
|
||||
} else {
|
||||
alert("Error al actualizar el proveedor.");
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error al actualizar el proveedor:", error);
|
||||
alert("Ocurrió un error al actualizar el proveedor.");
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
@@ -0,0 +1,208 @@
|
||||
const productos = [];
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const btnMas = document.querySelector(".btn-mas");
|
||||
const btnRegistrar = document.querySelector("#btn-registrar-venta");
|
||||
const codigoInput = document.querySelector(
|
||||
'input[placeholder="Código del Producto"]'
|
||||
);
|
||||
const nombreInput = document.querySelector(
|
||||
'input[placeholder="Nombre del Producto"]'
|
||||
);
|
||||
const cantidadInput = document.querySelector(
|
||||
'input[placeholder="Cantidad"]'
|
||||
);
|
||||
|
||||
const buscarProducto = (terminoBusqueda) => {
|
||||
return fetch(
|
||||
`../php/Buscar_Producto_Ventas.php?codigo=${encodeURIComponent(terminoBusqueda)}`
|
||||
).then((response) => response.json());
|
||||
};
|
||||
|
||||
const completarProductoDesdeInputs = (event) => {
|
||||
// Identificamos cuál de los dos inputs disparó la acción
|
||||
const inputActual = event ? event.target : null;
|
||||
|
||||
// 1. Si el usuario vació el input en el que está trabajando, limpiamos ambos y salimos
|
||||
if (inputActual && inputActual.value.trim() === "") {
|
||||
codigoInput.value = "";
|
||||
nombreInput.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const codigo = codigoInput.value.trim();
|
||||
const nombre = nombreInput.value.trim();
|
||||
|
||||
// 2. Buscamos priorizando el input que el usuario acaba de modificar
|
||||
let terminoBusqueda = "";
|
||||
if (inputActual === codigoInput) {
|
||||
terminoBusqueda = codigo;
|
||||
} else if (inputActual === nombreInput) {
|
||||
terminoBusqueda = nombre;
|
||||
} else {
|
||||
terminoBusqueda = codigo || nombre;
|
||||
}
|
||||
|
||||
// Si por alguna razón ambos están vacíos, aseguramos la limpieza
|
||||
if (!terminoBusqueda) {
|
||||
codigoInput.value = "";
|
||||
nombreInput.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
buscarProducto(terminoBusqueda)
|
||||
.then((data) => {
|
||||
if (!data || !data.ID_Producto) {
|
||||
return;
|
||||
}
|
||||
|
||||
codigoInput.value = data.ID_Producto;
|
||||
nombreInput.value = data.Descripcion;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error al autocompletar el producto:", error);
|
||||
});
|
||||
};
|
||||
|
||||
[codigoInput, nombreInput].forEach((input) => {
|
||||
// Cuando sale del campo (hace clic afuera)
|
||||
input.addEventListener("blur", completarProductoDesdeInputs);
|
||||
|
||||
// Cuando presiona Enter
|
||||
input.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
completarProductoDesdeInputs(event);
|
||||
}
|
||||
});
|
||||
|
||||
// NUEVO: Cuando borra el contenido en tiempo real
|
||||
input.addEventListener("input", (event) => {
|
||||
if (event.target.value.trim() === "") {
|
||||
codigoInput.value = "";
|
||||
nombreInput.value = "";
|
||||
}
|
||||
});
|
||||
});
|
||||
btnMas.addEventListener("click", () => {
|
||||
const codigo = codigoInput.value.trim();
|
||||
const nombre = nombreInput.value.trim();
|
||||
const cantidad = parseInt(cantidadInput.value);
|
||||
|
||||
if (!codigo && !nombre) {
|
||||
alert("Debe ingresar el Código o el Nombre del producto.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN(cantidad) || cantidad <= 0) {
|
||||
alert("Ingrese una cantidad válida mayor a 0.");
|
||||
return;
|
||||
}
|
||||
|
||||
const terminoBusqueda = codigo || nombre;
|
||||
|
||||
buscarProducto(terminoBusqueda)
|
||||
.then((data) => {
|
||||
console.log("Producto obtenido:", data);
|
||||
if (!data || !data.ID_Producto) {
|
||||
alert("Producto no encontrado.");
|
||||
return;
|
||||
}
|
||||
|
||||
const stockDisponible = parseInt(data.Stock_Disponible);
|
||||
|
||||
// Calculamos cuánto de este producto ya tenemos en la lista actual
|
||||
const cantidadEnLista = productos
|
||||
.filter((p) => p.id === data.ID_Producto)
|
||||
.reduce((acc, p) => acc + p.cantidad, 0);
|
||||
|
||||
const cantidadTotalIntentada = cantidad + cantidadEnLista;
|
||||
|
||||
if (cantidadTotalIntentada > stockDisponible) {
|
||||
alert(`Stock insuficiente.
|
||||
Disponible: ${stockDisponible} unidades.
|
||||
En lista: ${cantidadEnLista} unidades.
|
||||
No puedes agregar ${cantidad} más.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const precio = parseFloat(data.Precio_Venta);
|
||||
const subtotal = precio * cantidad;
|
||||
|
||||
productos.push({
|
||||
id: data.ID_Producto,
|
||||
descripcion: data.Descripcion,
|
||||
precio: precio,
|
||||
cantidad: cantidad,
|
||||
subtotal: subtotal,
|
||||
stockMaximo: stockDisponible,
|
||||
});
|
||||
|
||||
actualizarTabla();
|
||||
codigoInput.value = "";
|
||||
nombreInput.value = "";
|
||||
cantidadInput.value = "";
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error al buscar el producto:", error);
|
||||
});
|
||||
});
|
||||
|
||||
btnRegistrar.addEventListener("click", () => {
|
||||
if (productos.length === 0) {
|
||||
alert("No hay productos agregados.");
|
||||
return;
|
||||
}
|
||||
|
||||
fetch("../php/Registrar_Venta.php", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(productos),
|
||||
})
|
||||
.then((res) => res.text())
|
||||
.then((msg) => {
|
||||
alert(msg);
|
||||
productos.length = 0;
|
||||
actualizarTabla();
|
||||
if (typeof window.verificarStockBajoMenu === 'function') {
|
||||
window.verificarStockBajoMenu();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Error al registrar la venta:", err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function actualizarTabla() {
|
||||
const tbody = document.querySelector("#tabla-ventas tbody");
|
||||
tbody.innerHTML = "";
|
||||
|
||||
let total = 0;
|
||||
|
||||
productos.forEach((p, index) => {
|
||||
total += p.subtotal;
|
||||
|
||||
const fila = document.createElement("tr");
|
||||
fila.innerHTML = `
|
||||
<td>${p.id}</td>
|
||||
<td>${p.descripcion}</td>
|
||||
<td>$${p.precio.toFixed(2)}</td>
|
||||
<td>${p.cantidad}</td>
|
||||
<td>$${p.subtotal.toFixed(2)}</td>
|
||||
<td>
|
||||
<button class="btn-eliminar" onclick="eliminarProductoVenta(${index})">Eliminar</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(fila);
|
||||
});
|
||||
|
||||
document.getElementById("total").value = `$${total.toFixed(2)}`;
|
||||
}
|
||||
|
||||
function eliminarProductoVenta(index) {
|
||||
productos.splice(index, 1);
|
||||
actualizarTabla();
|
||||
}
|
||||
Reference in New Issue
Block a user