1ra Versión

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