Files
MIESYSTEM/MieSystem/Views/Asistencia/Index.cshtml
2025-12-26 22:27:20 -06:00

1150 lines
43 KiB
Plaintext
Raw Permalink 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.

@model MieSystem.Models.ViewModels.AsistenciaViewModel
@{
ViewData["Title"] = "Control de Asistencia";
var diasSeleccionadosList = new List<string>();
if (!string.IsNullOrEmpty(Model.DiasSemanaSeleccionados))
{
diasSeleccionadosList = Model.DiasSemanaSeleccionados
.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(d => d.Trim())
.ToList();
}
var diasAMostrar = Model.DiasDelMes
.Where(d => diasSeleccionadosList.Count == 0 ||
diasSeleccionadosList.Contains(((int)d.DayOfWeek).ToString()))
.ToList();
}
<div class="container-fluid">
<div class="card mb-4">
<div class="card-header text-white" style="background: linear-gradient(180deg, #2c3e50 0%, #1a2530 100%);">
<h5 class="mb-0"><i class="fas fa-filter"></i> Filtros</h5>
</div>
<div class="card-body">
<form method="get" id="filtroForm" class="row g-3">
<div class="col-md-3">
<label class="form-label">Año</label>
<select name="año" class="form-select" id="selectAnio">
@foreach (var año in ViewBag.Años)
{
<option value="@año.Value" selected="@año.Selected">
@año.Text
</option>
}
</select>
</div>
<div class="col-md-3">
<label class="form-label">Mes</label>
<select name="mes" class="form-select" id="selectMes">
@foreach (var mes in ViewBag.Meses)
{
<option value="@mes.Value" selected="@(mes.Value == Model.Mes.ToString())">
@mes.Text
</option>
}
</select>
</div>
<div class="col-md-4">
<label class="form-label">Días de la semana</label>
<div class="dias-semana-checkboxes">
@foreach (var dia in ViewBag.DiasSemana)
{
var isChecked = diasSeleccionadosList.Contains(dia.Value);
<div class="form-check form-check-inline">
<input class="form-check-input dia-checkbox"
type="checkbox"
value="@dia.Value"
id="dia@(dia.Value)"
@(isChecked ? "checked" : "")>
<label class="form-check-label" for="dia@(dia.Value)">
@dia.Text
</label>
</div>
}
<input type="hidden" name="diasSemana" id="diasSemanaInput"
value="@Model.DiasSemanaSeleccionados">
</div>
</div>
<div class="col-md-2 d-flex align-items-end">
<button type="submit" class="btn w-100"
style="background: linear-gradient(180deg, #2c3e50 0%, #1a2530 100%); color: white;">
<i class="fas fa-search text-white"></i> Filtrar
</button>
</div>
</form>
</div>
</div>
<div class="row mb-4" id="estadisticasContainer">
</div>
<div class="card">
<div class="card-header bg-success text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-calendar-check"></i>
Asistencia - @Model.NombreMes @Model.Año
<span class="badge bg-light text-dark ms-2">@Model.Expedientes.Count niños</span>
</h5>
<div>
<button class="btn btn-light btn-sm" id="btnGuardarTodo">
<i class="fas fa-save"></i> Guardar todo
</button>
<button class="btn btn-light btn-sm" id="btnExportar">
<i class="fas fa-file-excel"></i> Exportar
</button>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive" style="max-height: 70vh; overflow-y: auto;">
<table class="table table-bordered table-hover table-sm" id="tablaAsistencia">
<thead class="table-dark sticky-top">
<tr>
<th class="sticky-left bg-dark text-white" style="min-width: 200px;">
<div>Nombre</div>
<div class="small">Edad</div>
</th>
@foreach (var dia in Model.DiasDelMes)
{
var diaSemana = ((int)dia.DayOfWeek).ToString();
var isChecked = Model.DiasSemanaSeleccionados?.Contains(diaSemana) ?? true;
if (!string.IsNullOrEmpty(Model.DiasSemanaSeleccionados) && !isChecked)
{
continue;
}
var nombreDia = dia.ToString("ddd", new System.Globalization.CultureInfo("es-ES"));
var esFinDeSemana = dia.DayOfWeek == DayOfWeek.Saturday || dia.DayOfWeek == DayOfWeek.Sunday;
<th class="text-center @(esFinDeSemana ? "" : "")" style="min-width: 50px;">
<div>@dia.Day</div>
<div class="small">@nombreDia</div>
</th>
}
</tr>
</thead>
<tbody>
@foreach (var expediente in Model.Expedientes)
{
<!-- Calcular propiedades aquí mismo -->
var nombreCompleto = $"{expediente.Nombre} {expediente.Apellidos}".Trim();
var edad = 0;
if (expediente.FechaNacimiento != DateTime.MinValue)
{
var today = DateTime.Today;
edad = today.Year - expediente.FechaNacimiento.Year;
if (expediente.FechaNacimiento.Date > today.AddYears(-edad))
{
edad--;
}
}
<tr data-expediente-id="@expediente.Id">
<td class="sticky-left bg-white" style="min-width: 200px;">
<div class="fw-bold">@nombreCompleto</div>
<div class="small text-muted">Edad: @edad años</div>
</td>
@foreach (var dia in Model.DiasDelMes)
{
var diaSemana = ((int)dia.DayOfWeek).ToString();
var isChecked = Model.DiasSemanaSeleccionados?.Contains(diaSemana) ?? true;
if (!string.IsNullOrEmpty(Model.DiasSemanaSeleccionados) && !isChecked)
{
continue;
}
var key = $"{expediente.Id}_{dia:yyyy-MM-dd}";
var estadoActual = Model.Asistencias.ContainsKey(key)
? Model.Asistencias[key]
: "";
// Determinar clase CSS según estado
var claseEstado = estadoActual switch
{
"P" => "celda-presente",
"T" => "celda-tarde",
"F" => "celda-falta",
_ => ""
};
var esFinDeSemana = dia.DayOfWeek == DayOfWeek.Saturday || dia.DayOfWeek == DayOfWeek.Sunday;
<td class="text-center p-0 celda-asistencia @claseEstado @(esFinDeSemana ? "bg-light" : "")"
data-expediente="@expediente.Id"
data-fecha="@dia.ToString("yyyy-MM-dd")"
style="min-width: 50px;">
<select class="form-select form-select-sm estado-select border-0 h-100 w-100"
data-initial="@estadoActual">
<option value=""></option>
@if (estadoActual == "P")
{
<option value="P" selected class="bg-success text-white">P</option>
}
else
{
<option value="P" class="bg-success text-white">P</option>
}
@if (estadoActual == "T")
{
<option value="T" selected class="bg-warning">T</option>
}
else
{
<option value="T" class="bg-warning">T</option>
}
@if (estadoActual == "F")
{
<option value="F" selected class="bg-danger text-white">F</option>
}
else
{
<option value="F" class="bg-danger text-white">F</option>
}
</select>
</td>
}
</tr>
}
</tbody>
<tfoot class="table-dark">
<tr>
<td class="sticky-left bg-dark text-white fw-bold">
<div>TOTALES POR DÍA</div>
<div class="small text-light">P/T/F</div>
</td>
@foreach (var dia in Model.DiasDelMes)
{
var diaSemana = ((int)dia.DayOfWeek).ToString();
var isChecked = Model.DiasSemanaSeleccionados?.Contains(diaSemana) ?? true;
if (!string.IsNullOrEmpty(Model.DiasSemanaSeleccionados) && !isChecked)
{
continue;
}
var esFinDeSemana = dia.DayOfWeek == DayOfWeek.Saturday || dia.DayOfWeek == DayOfWeek.Sunday;
// Calcular totales para este día
var totalPresente = 0;
var totalTarde = 0;
var totalFalta = 0;
foreach (var expediente in Model.Expedientes)
{
var key = $"{expediente.Id}_{dia:yyyy-MM-dd}";
if (Model.Asistencias.ContainsKey(key))
{
var estado = Model.Asistencias[key];
switch (estado)
{
case "P": totalPresente++; break;
case "T": totalTarde++; break;
case "F": totalFalta++; break;
}
}
}
var totalDia = totalPresente + totalTarde + totalFalta;
<td class="text-center p-1 @(esFinDeSemana ? "bg-secondary" : "bg-dark") text-white"
style="min-width: 65px; font-size: 0.85rem;">
<div class="fw-bold mb-1">@totalDia</div>
<div class="d-flex justify-content-center gap-1">
<span class="badge bg-success" title="Presentes">@totalPresente</span>
<span class="badge bg-warning text-dark" title="Tardes">@totalTarde</span>
<span class="badge bg-danger" title="Faltas">@totalFalta</span>
</div>
</td>
}
</tr>
</tfoot>
</table>
</div>
</div>
<div class="card-footer">
<div class="row">
<div class="col-md-6">
<div class="leyenda">
<span class="badge bg-success me-2">P = Presente</span>
<span class="badge bg-warning me-2">T = Tarde</span>
<span class="badge bg-danger me-2">F = Falto</span>
<span class="text-muted ms-3">(Vacío = No registrado)</span>
</div>
</div>
<div class="col-md-6 text-end">
<small class="text-muted">
Total: @Model.Expedientes.Count niños × @diasAMostrar.Count días =
@(Model.Expedientes.Count * diasAMostrar.Count) registros posibles
</small>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="modalCarga" tabindex="-1">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-body text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Cargando...</span>
</div>
<p class="mt-2">Guardando cambios...</p>
</div>
</div>
</div>
</div>
@section Styles {
<style>
/* Fijar altura del contenedor de estadísticas */
#estadisticasContainer {
min-height: 90px; /* ← mitad */
transition: min-height 0.3s ease;
}
/* Estilos para las tarjetas de estadísticas */
#estadisticasContainer .card {
height: 100%;
min-height: 90px; /* ← mitad */
display: flex;
flex-direction: column;
}
#estadisticasContainer .card-body {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
/* Spinner centrado */
#estadisticasContainer .spinner-border {
width: 1.75rem;
height: 1.75rem;
}
/* Alert centrado */
#estadisticasContainer .alert {
margin: 0;
display: flex;
align-items: center;
justify-content: center;
min-height: 90px;
}
/* Reemplaza los estilos del tfoot con estos: */
tfoot.table-dark td {
vertical-align: middle !important;
border-color: #495057 !important;
}
tfoot .badge {
font-size: 0.7rem;
padding: 0.25em 0.5em;
min-width: 24px;
}
tfoot tr:first-child td {
border-top: 2px solid #6c757d;
}
tfoot tr.bg-success td {
border-top: 2px solid #198754;
}
/* Para la celda combinada de totales generales */
tfoot td[colspan] {
background: linear-gradient(135deg, #198754 0%, #146c43 100%) !important;
color: white !important;
}
tfoot td[colspan] .badge {
font-size: 0.8rem;
padding: 0.4em 0.8em;
}
tfoot td[colspan] .text-light {
color: rgba(255, 255, 255, 0.9) !important;
}
/* Estilos específicos para el footer oscuro */
tfoot.table-dark .sticky-left {
background: #343a40 !important;
color: white !important;
}
tfoot.table-dark td.bg-dark {
background-color: #343a40 !important;
}
tfoot.table-dark td.bg-secondary {
background-color: #6c757d !important;
}
/* Asegurar que el texto sea visible en fondo oscuro */
tfoot.table-dark .text-white,
tfoot.table-dark .text-light {
color: white !important;
}
tfoot.table-dark .text-muted {
color: rgba(255, 255, 255, 0.7) !important;
}
/* Estilos generales de la tabla */
.table th, .table td {
vertical-align: middle;
}
/* Colores para las celdas según estado */
.celda-presente {
background-color: #d1e7dd !important; /* Verde claro */
}
.celda-tarde {
background-color: #fff3cd !important; /* Amarillo claro */
}
.celda-falta {
background-color: #f8d7da !important; /* Rojo claro */
}
/* Estilos para el select */
.estado-select {
cursor: pointer;
transition: all 0.2s;
border: 1px solid transparent !important;
text-align: center;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
}
/* Ocultar flecha del select */
.estado-select::-ms-expand {
display: none;
}
.estado-select:focus {
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
outline: none;
}
/* Colores para el select según estado */
.estado-select.bg-success {
background-color: #198754 !important;
color: white !important;
border-color: #198754 !important;
}
.estado-select.bg-warning {
background-color: #ffc107 !important;
color: black !important;
border-color: #ffc107 !important;
}
.estado-select.bg-danger {
background-color: #dc3545 !important;
color: white !important;
border-color: #dc3545 !important;
}
/* Select vacío */
.estado-select:not([value]):not([value=""]) {
background-color: white !important;
color: black !important;
}
/* Celdas cambiadas (no guardadas) */
.celda-asistencia.changed {
outline: 2px solid #0d6efd;
outline-offset: -2px;
}
/* Posición fija para la primera columna */
.sticky-left {
position: sticky;
left: 0;
background: white;
z-index: 5;
}
/* Estilos para checkboxes de días */
.dias-semana-checkboxes {
background: #f8f9fa;
padding: 10px;
border-radius: 5px;
border: 1px solid #dee2e6;
}
/* Colores para días de semana en encabezados */
.bg-lunes {
background-color: #e3f2fd !important;
}
.bg-martes {
background-color: #f3e5f5 !important;
}
.bg-miercoles {
background-color: #e8f5e8 !important;
}
.bg-jueves {
background-color: #fff3e0 !important;
}
.bg-viernes {
background-color: #fce4ec !important;
}
.bg-sabado {
background-color: #f1f8e9 !important;
}
.bg-domingo {
background-color: #fff8e1 !important;
}
/* Estilos para encabezados fijos */
.table-dark.sticky-top {
z-index: 10;
}
/* Estilos para tarjetas de estadísticas */
.card .display-6 {
font-size: 2rem;
font-weight: bold;
}
/* Progress bars en estadísticas */
.progress {
background-color: rgba(255, 255, 255, 0.3);
}
/* Hover effects para celdas */
.celda-asistencia:hover {
background-color: #f8f9fa !important;
}
.celda-presente:hover {
background-color: #c3e6cb !important;
}
.celda-tarde:hover {
background-color: #ffeaa7 !important;
}
.celda-falta:hover {
background-color: #f5c6cb !important;
}
/* Responsive adjustments */
@@media (max-width: 768px) {
.table-responsive {
font-size: 0.85rem;
}
.estado-select {
padding: 0.25rem;
font-size: 0.8rem;
}
.dias-semana-checkboxes .form-check-inline {
display: block;
margin-right: 0;
margin-bottom: 5px;
}
}
/* Animación suave para cambios de color */
.celda-asistencia, .estado-select {
transition: background-color 0.3s ease, color 0.3s ease;
}
/* Estilos para fin de semana */
.bg-light {
background-color: #f8f9fa !important;
}
/* Leyenda de colores */
.leyenda .badge {
font-size: 0.8rem;
padding: 0.4em 0.8em;
}
/* Cambia de 50px a 65px */
.celda-asistencia {
min-width: 65px !important; /* ← Cambiado de 50px */
}
.table th.text-center {
min-width: 65px !important; /* ← Cambiado de 50px */
}
/* Cambia de 200px a 240px */
.sticky-left {
min-width: 240px !important; /* ← Cambiado de 200px */
}
/* Aumenta el padding del select */
.estado-select {
padding: 10px 4px !important; /* ← Más padding */
font-size: 14px !important; /* ← Texto más grande */
}
</style>
}
@section Scripts {
<script>
$(document).ready(function () {
// Actualizar estadísticas al cargar
cargarEstadisticas();
// Manejar checkboxes de días de semana
$('.dia-checkbox').change(function () {
var diasSeleccionados = $('.dia-checkbox:checked').map(function () {
return $(this).val();
}).get().join(',');
$('#diasSemanaInput').val(diasSeleccionados);
});
// Inicializar colores al cargar
inicializarColores();
// Guardar todas las asistencias cambiadas
$('#btnGuardarTodo').click(function () {
var cambios = [];
var celdasCambiadas = $('.celda-asistencia.changed');
if (celdasCambiadas.length === 0) {
toastr.info('No hay cambios para guardar');
return;
}
if (!confirm(`¿Guardar ${celdasCambiadas.length} cambios?`)) {
return;
}
$('#modalCarga').modal('show');
celdasCambiadas.each(function () {
var celda = $(this);
var select = celda.find('.estado-select');
var expedienteId = celda.data('expediente');
var fecha = celda.data('fecha');
var estado = select.val();
cambios.push({
expedienteId: expedienteId,
fecha: fecha,
estado: estado
});
});
// Enviar cambios masivos
$.ajax({
url: '@Url.Action("GuardarAsistenciasMasivas", "Asistencia")',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(cambios),
success: function (response) {
$('#modalCarga').modal('hide');
if (response.success) {
// Quitar marca de cambiado y actualizar estado inicial
$('.celda-asistencia').removeClass('changed');
$('.estado-select').each(function () {
$(this).data('initial', $(this).val());
});
toastr.success(response.message);
cargarEstadisticas();
} else {
toastr.error(response.message);
}
},
error: function () {
$('#modalCarga').modal('hide');
toastr.error('Error al guardar los cambios');
}
});
});
// Exportar a Excel
$('#btnExportar').click(function () {
var año = $('#selectAnio').val();
var mes = $('#selectMes').val();
var diasSemana = $('#diasSemanaInput').val();
var url = '@Url.Action("ExportarExcel", "Asistencia")' +
'?año=' + año +
'&mes=' + mes +
'&diasSemana=' + diasSemana;
window.open(url, '_blank');
});
// Cambiar mes/año con selects
$('#selectAnio, #selectMes').change(function () {
$('#filtroForm').submit();
});
// Aplicar colores a días de semana
aplicarColoresDias();
});
// Función para aplicar color a una celda según el estado
function aplicarColorCelda(celda, estado) {
// Remover todas las clases de color
celda.removeClass('celda-presente celda-tarde celda-falta');
// Aplicar nueva clase según estado
switch (estado) {
case 'P':
celda.addClass('celda-presente');
break;
case 'T':
celda.addClass('celda-tarde');
break;
case 'F':
celda.addClass('celda-falta');
break;
default:
// Vacío - no aplicar color
break;
}
}
// Función para aplicar color al select según estado
function aplicarColorSelect(select, estado) {
// Remover todas las clases de color
select.removeClass('bg-success bg-warning bg-danger text-white');
// Aplicar nueva clase según estado
switch (estado) {
case 'P':
select.addClass('bg-success text-white');
break;
case 'T':
select.addClass('bg-warning');
break;
case 'F':
select.addClass('bg-danger text-white');
break;
default:
// Vacío - color por defecto
break;
}
}
// Función para inicializar colores al cargar la página
function inicializarColores() {
$('.estado-select').each(function () {
var estado = $(this).val();
var celda = $(this).closest('.celda-asistencia');
aplicarColorSelect($(this), estado);
aplicarColorCelda(celda, estado);
});
}
// ============================================
// FUNCIONES PARA GUARDAR ASISTENCIA
// ===========================================
function guardarAsistencia(expedienteId, fecha, estado, celda) {
$.ajax({
url: '@Url.Action("GuardarAsistencia", "Asistencia")',
type: 'POST',
data: {
expedienteId: expedienteId,
fecha: fecha,
estado: estado
},
success: function (response) {
if (response.success) {
celda.removeClass('changed');
celda.find('.estado-select').data('initial', estado);
toastr.success('Guardado correctamente');
// Actualizar estadísticas y footer
cargarEstadisticas();
actualizarFooterTabla();
} else {
toastr.error(response.message);
// Revertir color si hay error
var select = celda.find('.estado-select');
var initial = select.data('initial');
aplicarColorSelect(select, initial);
aplicarColorCelda(celda, initial);
select.val(initial);
}
},
error: function () {
toastr.error('Error de conexión');
// Revertir color
var select = celda.find('.estado-select');
var initial = select.data('initial');
aplicarColorSelect(select, initial);
aplicarColorCelda(celda, initial);
select.val(initial);
}
});
}
// ============================================
// FUNCIONES PARA ESTADÍSTICAS
// ============================================
function cargarEstadisticas() {
var año = $('#selectAnio').val();
var mes = $('#selectMes').val();
// Mostrar indicador de carga SIN cambiar el HTML completo
var container = $('#estadisticasContainer');
var currentContent = container.html();
container.html(`
<div class="col-md-12 d-flex align-items-center justify-content-center" style="min-height: 180px;">
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Cargando...</span>
</div>
<p class="mt-2">Cargando estadísticas...</p>
</div>
</div>
`);
$.ajax({
url: '@Url.Action("ObtenerEstadisticas", "Asistencia")',
type: 'GET',
data: {año: año, mes: mes},
success: function (data) {
// Verificar si data es null o undefined
if (!data) {
mostrarEstadisticasVacias();
return;
}
// Verificar si hay error
if (data.error) {
console.error('Error del servidor:', data.error);
mostrarEstadisticasVacias('Error: ' + data.error);
return;
}
// Extraer valores con manejo seguro
var total = extraerNumero(data, 'total');
var presentes = extraerNumero(data, 'presentes');
var tardes = extraerNumero(data, 'tardes');
var faltas = extraerNumero(data, 'faltas');
// Verificar consistencia
var suma = presentes + tardes + faltas;
if (total > 0 && suma !== total) {
console.warn('Inconsistencia en datos: total=' + total + ', suma=' + suma);
total = suma;
}
// Calcular porcentajes
var porcentajePresentes = calcularPorcentaje(presentes, total);
var porcentajeTardes = calcularPorcentaje(tardes, total);
var porcentajeFaltas = calcularPorcentaje(faltas, total);
// Mostrar estadísticas
mostrarEstadisticasHTML(total, presentes, tardes, faltas,
porcentajePresentes, porcentajeTardes, porcentajeFaltas);
// Actualizar el footer de la tabla
actualizarFooterTabla();
},
error: function (xhr, status, error) {
console.error('Error AJAX:', status, error);
mostrarEstadisticasVacias('Error al cargar estadísticas');
}
});
}
function actualizarFooterTabla() {
// Recalcular totales por día basados en los datos actuales
$('tfoot tr td').each(function (index) {
if (index === 0) return; // Saltar la primera celda (TOTALES POR DÍA)
var td = $(this);
var colIndex = td.index();
// Solo procesar celdas que contienen totales por día
if (td.hasClass('text-center')) {
var totalPresente = 0;
var totalTarde = 0;
var totalFalta = 0;
// Recorrer todas las filas del tbody para esta columna
$('tbody tr').each(function () {
var celda = $(this).find('td').eq(colIndex);
if (celda.length) {
var select = celda.find('.estado-select');
var estado = select.val();
switch (estado) {
case 'P':
totalPresente++;
break;
case 'T':
totalTarde++;
break;
case 'F':
totalFalta++;
break;
}
}
});
var totalDia = totalPresente + totalTarde + totalFalta;
// Actualizar la celda del footer
td.html(`
<div class="fw-bold mb-1">${totalDia}</div>
<div class="d-flex justify-content-center gap-1">
<span class="badge bg-success" title="Presentes">${totalPresente}</span>
<span class="badge bg-warning text-dark" title="Tardes">${totalTarde}</span>
<span class="badge bg-danger" title="Faltas">${totalFalta}</span>
</div>
`);
}
});
}
// Función auxiliar para extraer números de forma segura
function extraerNumero(obj, propiedad) {
if (!obj || obj[propiedad] === null || obj[propiedad] === undefined) {
return 0;
}
var valor = obj[propiedad];
// Convertir a número si es string
if (typeof valor === 'string') {
valor = parseFloat(valor);
}
// Verificar que sea un número válido
if (isNaN(valor) || !isFinite(valor)) {
return 0;
}
// Redondear a entero si es decimal
return Math.round(valor);
}
// Función para calcular porcentajes
function calcularPorcentaje(parcial, total) {
if (!total || total <= 0) {
return 0;
}
var porcentaje = (parcial / total) * 100;
// Redondear a 1 decimal
return Math.round(porcentaje * 10) / 10;
}
// Función para mostrar estadísticas vacías o con error
function mostrarEstadisticasVacias(mensaje) {
var mensajeMostrar = mensaje || 'No hay datos de asistencia para este período';
$('#estadisticasContainer').html(`
<div class="col-md-12 d-flex align-items-center justify-content-center" style="min-height: 180px;">
<div class="alert alert-warning m-0">
<i class="fas fa-exclamation-triangle"></i> ${mensajeMostrar}
</div>
</div>
`);
}
// Función para mostrar estadísticas en HTML
function mostrarEstadisticasHTML(total, presentes, tardes, faltas,
porcentajePresentes, porcentajeTardes, porcentajeFaltas) {
// Calcular porcentaje de asistencia general
var asistenciaGeneral = total > 0 ?
((presentes + tardes) / total * 100).toFixed(1) : 0;
$('#estadisticasContainer').html(`
<div class="col-md-3">
<div class="card bg-success text-white h-100">
<div class="card-body text-center d-flex flex-column justify-content-center">
<h6 class="card-title">
<i class="fas fa-check-circle"></i> Presentes
</h6>
<h2 class="display-6 mb-2">${presentes.toLocaleString()}</h2>
<div class="mt-auto">
<small>${porcentajePresentes}%</small>
<div class="progress mt-1" style="height: 5px;">
<div class="progress-bar bg-light"
style="width: ${porcentajePresentes}%"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning h-100">
<div class="card-body text-center d-flex flex-column justify-content-center">
<h6 class="card-title">
<i class="fas fa-clock"></i> Tardes
</h6>
<h2 class="display-6 mb-2">${tardes.toLocaleString()}</h2>
<div class="mt-auto">
<small>${porcentajeTardes}%</small>
<div class="progress mt-1" style="height: 5px;">
<div class="progress-bar bg-dark"
style="width: ${porcentajeTardes}%"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-danger text-white h-100">
<div class="card-body text-center d-flex flex-column justify-content-center">
<h6 class="card-title">
<i class="fas fa-times-circle"></i> Faltas
</h6>
<h2 class="display-6 mb-2">${faltas.toLocaleString()}</h2>
<div class="mt-auto">
<small>${porcentajeFaltas}%</small>
<div class="progress mt-1" style="height: 5px;">
<div class="progress-bar bg-light"
style="width: ${porcentajeFaltas}%"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white h-100">
<div class="card-body text-center d-flex flex-column justify-content-center">
<h6 class="card-title">
<i class="fas fa-chart-bar"></i> Resumen
</h6>
<h2 class="display-6 mb-2">${total.toLocaleString()}</h2>
<div class="mt-auto">
<small>Asistencia: ${asistenciaGeneral}%</small>
<div class="progress mt-1" style="height: 5px;">
<div class="progress-bar bg-light"
style="width: ${asistenciaGeneral}%"></div>
</div>
<div class="small mt-2">
<div>Presentes: ${presentes}</div>
<div>Tardes: ${tardes}</div>
<div>Faltas: ${faltas}</div>
</div>
</div>
</div>
</div>
</div>
`);
}
// ============================================
// FUNCIONES ADICIONALES
// ============================================
function aplicarColoresDias() {
$('th.text-center').each(function () {
var textoDia = $(this).find('.small').text().trim();
var claseColor = '';
switch (textoDia) {
case 'Lun':
claseColor = 'bg-lunes';
break;
case 'Mar':
claseColor = 'bg-martes';
break;
case 'Mié':
claseColor = 'bg-miercoles';
break;
case 'Jue':
claseColor = 'bg-jueves';
break;
case 'Vie':
claseColor = 'bg-viernes';
break;
case 'Sáb':
claseColor = 'bg-sabado';
break;
case 'Dom':
claseColor = 'bg-domingo';
break;
}
if (claseColor) {
$(this).addClass(claseColor);
}
});
}
// También actualizar el footer cuando se cambia un select individualmente
$('.estado-select').change(function () {
var estado = $(this).val();
var celda = $(this).closest('.celda-asistencia');
var initial = $(this).data('initial');
// Actualizar colores
aplicarColorSelect($(this), estado);
aplicarColorCelda(celda, estado);
// Marcar como cambiado si es diferente
if (initial !== estado) {
celda.addClass('changed');
} else {
celda.removeClass('changed');
}
// Actualizar el footer inmediatamente (sin esperar guardar)
actualizarFooterTabla();
// Guardar automáticamente
var expedienteId = celda.data('expediente');
var fecha = celda.data('fecha');
guardarAsistencia(expedienteId, fecha, estado, celda);
});
</script>
}