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
+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');
});
});