Files
Complejo_Cap1tan/js/Reportes.js
T
MarcosFlorSalcedo 8ec55e40fc 1ra Versión
2026-07-14 18:24:27 -03:00

586 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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');
});
});