service worker

This commit is contained in:
2026-02-22 09:06:44 -06:00
parent 46bf68cb21
commit bec656b105
40 changed files with 115887 additions and 229 deletions

View File

@@ -17,10 +17,10 @@ public class MiembroController : Controller
} }
// GET: Miembro // GET: Miembro
public async Task<IActionResult> Index() public async Task<IActionResult> Index(int page = 1, int pageSize = 10, string? search = null)
{ {
var miembros = await _miembroService.GetAllAsync(); var paginatedMembers = await _miembroService.GetPaginatedAsync(page, pageSize, search);
return View(miembros); return View(paginatedMembers);
} }
// GET: Miembro/Details/5 // GET: Miembro/Details/5
@@ -129,4 +129,61 @@ public class MiembroController : Controller
var grupos = await _miembroService.GetGruposTrabajoAsync(); var grupos = await _miembroService.GetGruposTrabajoAsync();
ViewBag.GruposTrabajo = new SelectList(grupos.Select(g => new { g.Id, g.Nombre }), "Id", "Nombre"); ViewBag.GruposTrabajo = new SelectList(grupos.Select(g => new { g.Id, g.Nombre }), "Id", "Nombre");
} }
// GET: Miembro/Importar
public IActionResult Importar()
{
return View();
}
// POST: Miembro/Importar
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Importar(IFormFile? file)
{
if (file == null || file.Length == 0)
{
ModelState.AddModelError("", "Por favor seleccione un archivo CSV.");
return View();
}
if (!file.FileName.EndsWith(".csv", StringComparison.OrdinalIgnoreCase))
{
ModelState.AddModelError("", "El archivo debe ser un CSV.");
return View();
}
try
{
using var stream = file.OpenReadStream();
var createdBy = User.Identity?.Name ?? "Sistema";
var result = await _miembroService.ImportarMiembrosAsync(stream, createdBy);
if (result.SuccessCount > 0)
{
TempData["SuccessMessage"] = $"Se importaron {result.SuccessCount} miembros exitosamente.";
}
if (result.Errors.Any())
{
ViewBag.Errors = result.Errors;
if (result.SuccessCount == 0)
{
ModelState.AddModelError("", "No se pudo importar ningún miembro. Revise los errores.");
}
else
{
TempData["WarningMessage"] = "Se importaron algunos miembros, pero hubo errores en otras filas.";
}
return View(); // Stay on page to show errors
}
return RedirectToAction(nameof(Index));
}
catch (Exception ex)
{
ModelState.AddModelError("", $"Error al procesar el archivo: {ex.Message}");
return View();
}
}
} }

View File

@@ -34,4 +34,21 @@ public interface IMiembroService
/// Gets all active work groups for dropdown /// Gets all active work groups for dropdown
/// </summary> /// </summary>
Task<IEnumerable<(long Id, string Nombre)>> GetGruposTrabajoAsync(); Task<IEnumerable<(long Id, string Nombre)>> GetGruposTrabajoAsync();
/// <summary>
/// Imports members from a CSV stream
/// </summary>
/// <param name="csvStream">The stream of the CSV file</param>
/// <param name="createdBy">The user creating the members</param>
/// <returns>A tuple with success count and a list of error messages</returns>
Task<(int SuccessCount, List<string> Errors)> ImportarMiembrosAsync(Stream csvStream, string createdBy);
/// <summary>
/// Gets paginated members with optional search
/// </summary>
/// <param name="page">Current page number (1-based)</param>
/// <param name="pageSize">Number of items per page</param>
/// <param name="searchQuery">Optional search query to filter by name</param>
/// <returns>Paginated result with members</returns>
Task<PaginatedViewModel<MiembroViewModel>> GetPaginatedAsync(int page, int pageSize, string? searchQuery = null);
} }

View File

@@ -221,4 +221,265 @@ public class MiembroService : IMiembroService
.Select(g => new ValueTuple<long, string>(g.Id, g.Nombre)) .Select(g => new ValueTuple<long, string>(g.Id, g.Nombre))
.ToListAsync(); .ToListAsync();
} }
public async Task<(int SuccessCount, List<string> Errors)> ImportarMiembrosAsync(Stream csvStream, string createdBy)
{
int successCount = 0;
var errors = new List<string>();
int rowNumber = 1; // 1-based, starting at header
using var reader = new StreamReader(csvStream);
// Read valid groups for validation
var validGroupIds = await _context.GruposTrabajo
.Where(g => g.Activo)
.Select(g => g.Id)
.ToListAsync();
var validGroupIdsSet = new HashSet<long>(validGroupIds);
while (!reader.EndOfStream)
{
var line = await reader.ReadLineAsync();
if (string.IsNullOrWhiteSpace(line)) continue;
rowNumber++;
// Skip header if it looks like one (simple check or just assume first row is header)
// The prompt implies a specific format, we'll assume the first row IS the header based on standard CSV practices,
// but if the user provides a file without header it might be an issue.
// However, usually "loading a csv" implies a header.
// I'll skip the first row (header) in the loop logic by adding a check.
if (rowNumber == 2) continue; // Skip header row (rowNumber started at 1, so first ReadLine is row 1 (header), loop increments to 2)
// Wait, if I increment rowNumber AFTER reading, then:
// Start: rowNumber=1.
// ReadLine (Header). rowNumber becomes 2.
// So if rowNumber == 2, it means we just read the header. Correct.
// Parse CSV line
var values = ParseCsvLine(line);
// Expected columns:
// 0: Nombres
// 1: Apellidos
// 2: Fecha Nacimiento
// 3: Fecha Ingreso Congregacion
// 4: Telefono
// 5: Telefono Emergencia
// 6: Direccion
// 7: Grupo de trabajo (ID)
// 8: Bautizado en El Espiritu Santo (Si/No or True/False)
// 9: Activo (Si/No or True/False)
if (values.Count < 10)
{
errors.Add($"Fila {rowNumber}: Número de columnas insuficiente. Se esperaban 10, se encontraron {values.Count}.");
continue;
}
try
{
// Validation and Parsing
var nombres = values[0].Trim();
var apellidos = values[1].Trim();
if (string.IsNullOrEmpty(nombres) || string.IsNullOrEmpty(apellidos))
{
errors.Add($"Fila {rowNumber}: Nombres y Apellidos son obligatorios.");
continue;
}
DateOnly? fechaNacimiento = ParseDate(values[2]);
DateOnly? fechaIngreso = ParseDate(values[3]);
var telefono = values[4].Trim();
var telefonoEmergencia = values[5].Trim();
var direccion = values[6].Trim();
if (!long.TryParse(values[7], out long grupoId))
{
errors.Add($"Fila {rowNumber}: ID de Grupo de trabajo inválido '{values[7]}'.");
continue;
}
if (!validGroupIdsSet.Contains(grupoId))
{
errors.Add($"Fila {rowNumber}: Grupo de trabajo con ID {grupoId} no existe o no está activo.");
continue;
}
bool bautizado = ParseBool(values[8]);
bool activo = ParseBool(values[9]);
// Create Logic
var strategy = _context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
var persona = new Persona
{
Nombres = nombres,
Apellidos = apellidos,
FechaNacimiento = fechaNacimiento,
Direccion = string.IsNullOrEmpty(direccion) ? null : direccion,
Telefono = string.IsNullOrEmpty(telefono) ? null : telefono,
Activo = activo,
CreadoEn = DateTime.UtcNow,
ActualizadoEn = DateTime.UtcNow
};
_context.Personas.Add(persona);
await _context.SaveChangesAsync();
var miembro = new Miembro
{
PersonaId = persona.Id,
BautizadoEspirituSanto = bautizado,
FechaIngresoCongregacion = fechaIngreso,
TelefonoEmergencia = string.IsNullOrEmpty(telefonoEmergencia) ? null : telefonoEmergencia,
GrupoTrabajoId = grupoId,
Activo = activo,
CreadoPor = createdBy,
CreadoEn = DateTime.UtcNow,
ActualizadoEn = DateTime.UtcNow
};
_context.Miembros.Add(miembro);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
successCount++;
}
catch (Exception ex)
{
errors.Add($"Fila {rowNumber}: Error al guardar en base de datos: {ex.Message}");
// Transaction rolls back automatically on dispose if not committed
}
});
}
catch (Exception ex)
{
errors.Add($"Fila {rowNumber}: Error inesperado: {ex.Message}");
}
}
return (successCount, errors);
}
private List<string> ParseCsvLine(string line)
{
var values = new List<string>();
bool inQuotes = false;
string currentValue = "";
for (int i = 0; i < line.Length; i++)
{
char c = line[i];
if (c == '"')
{
inQuotes = !inQuotes;
}
else if (c == ',' && !inQuotes)
{
values.Add(currentValue);
currentValue = "";
}
else
{
currentValue += c;
}
}
values.Add(currentValue);
// Remove surrounding quotes if present
for (int i = 0; i < values.Count; i++)
{
var val = values[i].Trim();
if (val.StartsWith("\"") && val.EndsWith("\"") && val.Length >= 2)
{
values[i] = val.Substring(1, val.Length - 2).Replace("\"\"", "\"");
}
else
{
values[i] = val;
}
}
return values;
}
private DateOnly? ParseDate(string value)
{
if (string.IsNullOrWhiteSpace(value)) return null;
if (DateOnly.TryParse(value, out var date)) return date;
if (DateTime.TryParse(value, out var dt)) return DateOnly.FromDateTime(dt);
return null; // Or throw depending on strictness, currently lenient
}
private bool ParseBool(string value)
{
if (string.IsNullOrWhiteSpace(value)) return false;
var val = value.Trim().ToLower();
return val == "1" || val == "true" || val == "si" || val == "yes" || val == "s" || val == "verdadero";
}
public async Task<PaginatedViewModel<MiembroViewModel>> GetPaginatedAsync(int page, int pageSize, string? searchQuery = null)
{
// Ensure valid page and pageSize
if (page < 1) page = 1;
if (pageSize < 1) pageSize = 10;
if (pageSize > 100) pageSize = 100; // Max limit
// Start with base query
var query = _context.Miembros
.Include(m => m.Persona)
.Include(m => m.GrupoTrabajo)
.Where(m => !m.Eliminado && m.Activo);
// Apply search filter if provided
if (!string.IsNullOrWhiteSpace(searchQuery))
{
var search = searchQuery.Trim().ToLower();
query = query.Where(m =>
m.Persona.Nombres.ToLower().Contains(search) ||
m.Persona.Apellidos.ToLower().Contains(search) ||
(m.Persona.Nombres + " " + m.Persona.Apellidos).ToLower().Contains(search)
);
}
// Get total count for pagination
var totalItems = await query.CountAsync();
// Get paginated items
var items = await query
.OrderBy(m => m.Persona.Apellidos)
.ThenBy(m => m.Persona.Nombres)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(m => new MiembroViewModel
{
Id = m.Id,
Nombres = m.Persona.Nombres,
Apellidos = m.Persona.Apellidos,
FechaNacimiento = m.Persona.FechaNacimiento,
BautizadoEspirituSanto = m.BautizadoEspirituSanto,
Direccion = m.Persona.Direccion,
FechaIngresoCongregacion = m.FechaIngresoCongregacion,
Telefono = m.Persona.Telefono,
TelefonoEmergencia = m.TelefonoEmergencia,
GrupoTrabajoId = m.GrupoTrabajoId,
GrupoTrabajoNombre = m.GrupoTrabajo != null ? m.GrupoTrabajo.Nombre : null,
Activo = m.Activo,
FotoUrl = m.Persona.FotoUrl
})
.ToListAsync();
return new PaginatedViewModel<MiembroViewModel>
{
Items = items,
CurrentPage = page,
PageSize = pageSize,
TotalItems = totalItems,
SearchQuery = searchQuery
};
}
} }

View File

@@ -261,6 +261,10 @@
</div> </div>
@section Scripts { @section Scripts {
<!-- Offline Support Scripts -->
<script src="~/js/colaboraciones-offline-db.js"></script>
<script src="~/js/colaboraciones-sync.js"></script>
<script> <script>
let timeoutBusqueda = null; let timeoutBusqueda = null;
@@ -414,13 +418,13 @@
if (montoIngresado > 0) { if (montoIngresado > 0) {
if (Math.abs(montoIngresado - montoSugeridoTotal) > 0.01) { if (Math.abs(montoIngresado - montoSugeridoTotal) > 0.01) {
const diferencia = montoIngresado - montoSugeridoTotal; const diferencia = montoSugeridoTotal - montoIngresado; // Corrected calculation for difference
if (diferencia > 0) { if (diferencia > 0) {
mensajeSpan.textContent = `Sobra: $${diferencia.toFixed(2)}`; mensajeSpan.textContent = `Falta: $${diferencia.toFixed(2)}`;
alertaDiv.className = 'alert alert-info py-1 px-2 mb-0 mt-2';
} else {
mensajeSpan.textContent = `Falta: $${Math.abs(diferencia).toFixed(2)}`;
alertaDiv.className = 'alert alert-warning py-1 px-2 mb-0 mt-2'; alertaDiv.className = 'alert alert-warning py-1 px-2 mb-0 mt-2';
} else {
mensajeSpan.textContent = `Sobra: $${Math.abs(diferencia).toFixed(2)}`;
alertaDiv.className = 'alert alert-info py-1 px-2 mb-0 mt-2';
} }
alertaDiv.style.display = 'block'; alertaDiv.style.display = 'block';
} else { } else {
@@ -437,6 +441,93 @@
// Calcular cuando cambia el monto total // Calcular cuando cambia el monto total
document.getElementById('montoTotal')?.addEventListener('input', calcularSugerido); document.getElementById('montoTotal')?.addEventListener('input', calcularSugerido);
// ===== OFFLINE-FIRST FORM SUBMISSION =====
document.getElementById('colaboracionForm')?.addEventListener('submit', async function(e) {
e.preventDefault(); // Prevent default form submission
// Gather form data
const miembroId = document.getElementById('miembroIdHidden').value;
const mesInicial = document.getElementById('mesInicial').value;
const anioInicial = document.getElementById('anioInicial').value;
const mesFinal = document.getElementById('mesFinal').value;
const anioFinal = document.getElementById('anioFinal').value;
const montoTotal = document.getElementById('montoTotal').value;
const observaciones = document.querySelector('[name="Observaciones"]').value;
const tipoPrioritario = document.getElementById('tipoPrioritario').value;
// Get selected tipos
const tiposSeleccionados = Array.from(document.querySelectorAll('.tipo-checkbox:checked'))
.map(cb => cb.value);
// Validate
if (!miembroId) {
toastr.error('Por favor seleccione un miembro');
return;
}
if (tiposSeleccionados.length === 0) {
toastr.error('Por favor seleccione al menos un tipo de colaboración');
return;
}
if (!montoTotal || parseFloat(montoTotal) <= 0) {
toastr.error('Por favor ingrese un monto válido');
return;
}
// Prepare data object
const colaboracionData = {
miembroId: parseInt(miembroId),
mesInicial: parseInt(mesInicial),
anioInicial: parseInt(anioInicial),
mesFinal: parseInt(mesFinal),
anioFinal: parseInt(anioFinal),
montoTotal: parseFloat(montoTotal),
observaciones: observaciones,
tiposSeleccionados: tiposSeleccionados,
tipoPrioritario: tipoPrioritario || null,
registradoPor: '@User.Identity?.Name'
};
// Disable submit button
const submitBtn = this.querySelector('button[type="submit"]');
const originalBtnText = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Guardando...';
try {
// Use sync manager to save (handles online/offline automatically)
const result = await ColaboracionesSyncManager.saveColaboracion(colaboracionData);
if (result.success) {
if (result.offline) {
toastr.warning(result.message);
// Clear form
setTimeout(() => {
window.location.href = '@Url.Action("Index", "Colaboracion")';
}, 2000);
} else {
toastr.success(result.message);
// Redirect to index
setTimeout(() => {
window.location.href = '@Url.Action("Index", "Colaboracion")';
}, 1500);
}
} else {
toastr.error(result.message || 'Error al guardar');
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
}
} catch (error) {
console.error('Form submission error:', error);
toastr.error('Error inesperado: ' + error.message);
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
}
});
// Calcular al cargar la página // Calcular al cargar la página
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
calcularSugerido(); calcularSugerido();

View File

@@ -1,4 +1,4 @@
@model IEnumerable<Rs_system.Models.ViewModels.MiembroViewModel> @model Rs_system.Models.ViewModels.PaginatedViewModel<Rs_system.Models.ViewModels.MiembroViewModel>
@{ @{
ViewData["Title"] = "Miembros de la Iglesia"; ViewData["Title"] = "Miembros de la Iglesia";
} }
@@ -8,29 +8,68 @@
<h4 class="mb-1">Miembros en Propiedad</h4> <h4 class="mb-1">Miembros en Propiedad</h4>
<p class="text-muted mb-0">Gestión de miembros de la congregación</p> <p class="text-muted mb-0">Gestión de miembros de la congregación</p>
</div> </div>
<div>
<a asp-action="Importar" class="btn btn-outline-success me-2">
<i class="bi bi-file-earmark-spreadsheet me-1"></i> Importar CSV
</a>
<a asp-action="Create" class="btn btn-primary-custom"> <a asp-action="Create" class="btn btn-primary-custom">
<i class="bi bi-plus-lg me-1"></i> Nuevo Miembro <i class="bi bi-plus-lg me-1"></i> Nuevo Miembro
</a> </a>
</div> </div>
</div>
<!-- Search and Page Size Controls -->
<div class="card-custom mb-3">
<form method="get" asp-action="Index" id="searchForm" class="row g-3 align-items-end">
<div class="col-md-6">
<label for="search" class="form-label">Buscar Miembro</label>
<input type="text"
class="form-control"
id="search"
name="search"
value="@Model.SearchQuery"
placeholder="Buscar por nombre o apellido...">
</div>
<div class="col-md-3">
<label for="pageSize" class="form-label">Registros por página</label>
<select class="form-select"
id="pageSize"
asp-for="PageSize"
onchange="document.getElementById('searchForm').submit();">
<option value="5">5</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-search me-1"></i> Buscar
</button>
</div>
<input type="hidden" name="page" value="1" />
</form>
</div>
<!-- Summary Cards --> <!-- Summary Cards -->
<div class="row mb-4"> <div class="row mb-4">
<div class="col-md-4"> <div class="col-md-4">
<div class="card-custom text-center"> <div class="card-custom text-center">
<h6 class="text-muted mb-2">Total Miembros</h6> <h6 class="text-muted mb-2">Total Miembros</h6>
<h3 class="text-primary mb-0">@Model.Count()</h3> <h3 class="text-primary mb-0">@Model.TotalItems</h3>
</div> </div>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<div class="card-custom text-center"> <div class="card-custom text-center">
<h6 class="text-muted mb-2">Bautizados en el Espíritu Santo</h6> <h6 class="text-muted mb-2">Bautizados en el Espíritu Santo</h6>
<h3 class="text-success mb-0">@Model.Count(m => m.BautizadoEspirituSanto)</h3> <h3 class="text-success mb-0">@Model.Items.Count(m => m.BautizadoEspirituSanto)</h3>
</div> </div>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<div class="card-custom text-center"> <div class="card-custom text-center">
<h6 class="text-muted mb-2">Grupos de Trabajo</h6> <h6 class="text-muted mb-2">Grupos de Trabajo</h6>
<h3 class="text-info mb-0">@Model.Where(m => m.GrupoTrabajoId.HasValue).GroupBy(m => m.GrupoTrabajoId).Count()</h3> <h3 class="text-info mb-0">@Model.Items.Where(m => m.GrupoTrabajoId.HasValue).GroupBy(m => m.GrupoTrabajoId).Count()</h3>
</div> </div>
</div> </div>
</div> </div>
@@ -52,16 +91,23 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@if (!Model.Any()) @if (!Model.Items.Any())
{ {
<tr> <tr>
<td colspan="8" class="text-center text-muted py-4"> <td colspan="8" class="text-center text-muted py-4">
<i class="bi bi-people fs-1 d-block mb-2"></i> <i class="bi bi-people fs-1 d-block mb-2"></i>
No hay miembros registrados @if (!string.IsNullOrWhiteSpace(Model.SearchQuery))
{
<text>No se encontraron miembros con el criterio de búsqueda "@Model.SearchQuery"</text>
}
else
{
<text>No hay miembros registrados</text>
}
</td> </td>
</tr> </tr>
} }
@foreach (var miembro in Model) @foreach (var miembro in Model.Items)
{ {
<tr> <tr>
<td class="text-center"> <td class="text-center">
@@ -152,6 +198,71 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Pagination Controls -->
@if (Model.TotalPages > 1)
{
<div class="d-flex justify-content-between align-items-center mt-3 px-3 pb-3">
<div class="text-muted">
Mostrando @((Model.CurrentPage - 1) * Model.PageSize + 1) a @(Math.Min(Model.CurrentPage * Model.PageSize, Model.TotalItems)) de @Model.TotalItems registros
</div>
<nav aria-label="Paginación de miembros">
<ul class="pagination mb-0">
<!-- Previous Button -->
<li class="page-item @(!Model.HasPreviousPage ? "disabled" : "")">
<a class="page-link"
href="@Url.Action("Index", new { page = Model.CurrentPage - 1, pageSize = Model.PageSize, search = Model.SearchQuery })"
aria-label="Anterior">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
@{
var startPage = Math.Max(1, Model.CurrentPage - 2);
var endPage = Math.Min(Model.TotalPages, Model.CurrentPage + 2);
if (startPage > 1)
{
<li class="page-item">
<a class="page-link" href="@Url.Action("Index", new { page = 1, pageSize = Model.PageSize, search = Model.SearchQuery })">1</a>
</li>
if (startPage > 2)
{
<li class="page-item disabled"><span class="page-link">...</span></li>
}
}
for (int i = startPage; i <= endPage; i++)
{
<li class="page-item @(i == Model.CurrentPage ? "active" : "")">
<a class="page-link" href="@Url.Action("Index", new { page = i, pageSize = Model.PageSize, search = Model.SearchQuery })">@i</a>
</li>
}
if (endPage < Model.TotalPages)
{
if (endPage < Model.TotalPages - 1)
{
<li class="page-item disabled"><span class="page-link">...</span></li>
}
<li class="page-item">
<a class="page-link" href="@Url.Action("Index", new { page = Model.TotalPages, pageSize = Model.PageSize, search = Model.SearchQuery })">@Model.TotalPages</a>
</li>
}
}
<!-- Next Button -->
<li class="page-item @(!Model.HasNextPage ? "disabled" : "")">
<a class="page-link"
href="@Url.Action("Index", new { page = Model.CurrentPage + 1, pageSize = Model.PageSize, search = Model.SearchQuery })"
aria-label="Siguiente">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
</nav>
</div>
}
</div> </div>
<!-- Delete Form --> <!-- Delete Form -->

View File

@@ -22,6 +22,22 @@
<!--<link rel="stylesheet" href="~/Rs_system.styles.css" asp-append-version="true"/>--> <!--<link rel="stylesheet" href="~/Rs_system.styles.css" asp-append-version="true"/>-->
<link rel="manifest" href="~/manifest.json"> <link rel="manifest" href="~/manifest.json">
<meta name="theme-color" content="#1e293b"> <meta name="theme-color" content="#1e293b">
<!-- Service Worker Registration -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('Service Worker registered successfully:', registration.scope);
})
.catch(error => {
console.error('Service Worker registration failed:', error);
});
});
}
</script>
@RenderSection("Styles", required: false) @RenderSection("Styles", required: false)
</head> </head>
<body> <body>
@@ -53,6 +69,10 @@
<h5 class="mb-0 fw-semibold">@ViewData["Title"]</h5> <h5 class="mb-0 fw-semibold">@ViewData["Title"]</h5>
</div> </div>
<div class="header-right"> <div class="header-right">
<span id="offlineStatus" class="badge bg-success" style="display: none;">
<i class="bi bi-wifi"></i> En línea
</span>
<span id="pendingBadge" class="badge bg-warning ms-2" style="display: none;">0</span>
<partial name="_LoginPartial"/> <partial name="_LoginPartial"/>
</div> </div>
</header> </header>

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -15,7 +15,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("RS_system")] [assembly: System.Reflection.AssemblyCompanyAttribute("RS_system")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+1784131456f11aa7351eef9061c1354519f67545")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+46bf68cb21fcad11c3f8b5ebbeb6ec4b6567d6c9")]
[assembly: System.Reflection.AssemblyProductAttribute("RS_system")] [assembly: System.Reflection.AssemblyProductAttribute("RS_system")]
[assembly: System.Reflection.AssemblyTitleAttribute("RS_system")] [assembly: System.Reflection.AssemblyTitleAttribute("RS_system")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -1 +1 @@
0c9f7ccc95584dd75dafc3cf1c7a4bb9a06235cda706237be23d01c5759bf731 7e2d659fefb50453fa3da00dc55dfdaadf1c304f5a0fbc1389094f2c740f7326

View File

@@ -184,6 +184,10 @@ build_metadata.AdditionalFiles.CssScope =
build_metadata.AdditionalFiles.TargetPath = Vmlld3MvTWllbWJyby9FZGl0LmNzaHRtbA== build_metadata.AdditionalFiles.TargetPath = Vmlld3MvTWllbWJyby9FZGl0LmNzaHRtbA==
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =
[/home/adalberto/RiderProjects/RS_system/RS_system/Views/Miembro/Importar.cshtml]
build_metadata.AdditionalFiles.TargetPath = Vmlld3MvTWllbWJyby9JbXBvcnRhci5jc2h0bWw=
build_metadata.AdditionalFiles.CssScope =
[/home/adalberto/RiderProjects/RS_system/RS_system/Views/Miembro/Index.cshtml] [/home/adalberto/RiderProjects/RS_system/RS_system/Views/Miembro/Index.cshtml]
build_metadata.AdditionalFiles.TargetPath = Vmlld3MvTWllbWJyby9JbmRleC5jc2h0bWw= build_metadata.AdditionalFiles.TargetPath = Vmlld3MvTWllbWJyby9JbmRleC5jc2h0bWw=
build_metadata.AdditionalFiles.CssScope = build_metadata.AdditionalFiles.CssScope =

View File

@@ -1 +1 @@
8269c6b6fd373b3f46013b10aa9c68fa849af0f8b9f46b71c9398c8b327dec83 1e149c33148abe9fa6194941c5133295585bfc66f5204d7905e01b09846bdccc

View File

@@ -266,7 +266,6 @@
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/tlmqwhkg3d-ifse5yxmqk.gz /home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/tlmqwhkg3d-ifse5yxmqk.gz
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/b6c3bvqukf-ifse5yxmqk.gz /home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/b6c3bvqukf-ifse5yxmqk.gz
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/staticwebassets.build.json /home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/staticwebassets.build.json
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/staticwebassets.build.json.cache
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/staticwebassets.development.json /home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/staticwebassets.development.json
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/staticwebassets.build.endpoints.json /home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/staticwebassets.build.endpoints.json
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/RS_system.csproj.Up2Date /home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/RS_system.csproj.Up2Date
@@ -277,3 +276,14 @@
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/ref/RS_system.dll /home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/ref/RS_system.dll
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/lc3k1q6eo4-cr0snyzw1m.gz /home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/lc3k1q6eo4-cr0snyzw1m.gz
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/m7f2490r97-cr0snyzw1m.gz /home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/m7f2490r97-cr0snyzw1m.gz
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/e79wfobnuv-lc8ee02c5q.gz
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/z2cv867s5m-ga728ncyli.gz
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/dpe32h769j-rise9grasc.gz
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/ubjjtv0x1g-4bsvp4jd9h.gz
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/tlvbvx8n5g-pr0jyv6zw7.gz
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/staticwebassets/msbuild.RS_system.Microsoft.AspNetCore.StaticWebAssets.props
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/staticwebassets/msbuild.RS_system.Microsoft.AspNetCore.StaticWebAssetEndpoints.props
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/staticwebassets/msbuild.build.RS_system.props
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/staticwebassets/msbuild.buildMultiTargeting.RS_system.props
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/staticwebassets/msbuild.buildTransitive.RS_system.props
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/staticwebassets.pack.json

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
FIiThG6o4LKnL0aAIanau0zkgrgpEoNVs6Tge42QuR8=

File diff suppressed because one or more lines are too long

View File

@@ -20,7 +20,7 @@
"net9.0" "net9.0"
], ],
"sources": { "sources": {
"/home/adalberto/.dotnet/library-packs": {}, "/usr/lib64/dotnet/library-packs": {},
"https://api.nuget.org/v3/index.json": {} "https://api.nuget.org/v3/index.json": {}
}, },
"frameworks": { "frameworks": {
@@ -39,7 +39,7 @@
"auditLevel": "low", "auditLevel": "low",
"auditMode": "direct" "auditMode": "direct"
}, },
"SdkAnalysisLevel": "9.0.300" "SdkAnalysisLevel": "9.0.100"
}, },
"frameworks": { "frameworks": {
"net9.0": { "net9.0": {
@@ -95,7 +95,7 @@
"privateAssets": "all" "privateAssets": "all"
} }
}, },
"runtimeIdentifierGraphPath": "/home/adalberto/.dotnet/sdk/9.0.300/PortableRuntimeIdentifierGraph.json" "runtimeIdentifierGraphPath": "/usr/lib64/dotnet/sdk/9.0.113/PortableRuntimeIdentifierGraph.json"
} }
} }
} }

View File

@@ -7,7 +7,7 @@
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/adalberto/.nuget/packages/</NuGetPackageRoot> <NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/adalberto/.nuget/packages/</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/adalberto/.nuget/packages/</NuGetPackageFolders> <NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/adalberto/.nuget/packages/</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle> <NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.13.2</NuGetToolVersion> <NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' "> <ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="/home/adalberto/.nuget/packages/" /> <SourceRoot Include="/home/adalberto/.nuget/packages/" />

View File

@@ -3332,7 +3332,7 @@
"net9.0" "net9.0"
], ],
"sources": { "sources": {
"/home/adalberto/.dotnet/library-packs": {}, "/usr/lib64/dotnet/library-packs": {},
"https://api.nuget.org/v3/index.json": {} "https://api.nuget.org/v3/index.json": {}
}, },
"frameworks": { "frameworks": {
@@ -3351,7 +3351,7 @@
"auditLevel": "low", "auditLevel": "low",
"auditMode": "direct" "auditMode": "direct"
}, },
"SdkAnalysisLevel": "9.0.300" "SdkAnalysisLevel": "9.0.100"
}, },
"frameworks": { "frameworks": {
"net9.0": { "net9.0": {
@@ -3407,7 +3407,7 @@
"privateAssets": "all" "privateAssets": "all"
} }
}, },
"runtimeIdentifierGraphPath": "/home/adalberto/.dotnet/sdk/9.0.300/PortableRuntimeIdentifierGraph.json" "runtimeIdentifierGraphPath": "/usr/lib64/dotnet/sdk/9.0.113/PortableRuntimeIdentifierGraph.json"
} }
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"version": 2, "version": 2,
"dgSpecHash": "mE5kunE1L6A=", "dgSpecHash": "3JUQhcRdx7k=",
"success": true, "success": true,
"projectFilePath": "/home/adalberto/RiderProjects/RS_system/RS_system/RS_system.csproj", "projectFilePath": "/home/adalberto/RiderProjects/RS_system/RS_system/RS_system.csproj",
"expectedPackageFiles": [ "expectedPackageFiles": [

View File

@@ -1 +1 @@
"restore":{"projectUniqueName":"/home/adalberto/RiderProjects/RS_system/RS_system/RS_system.csproj","projectName":"RS_system","projectPath":"/home/adalberto/RiderProjects/RS_system/RS_system/RS_system.csproj","outputPath":"/home/adalberto/RiderProjects/RS_system/RS_system/obj/","projectStyle":"PackageReference","originalTargetFrameworks":["net9.0"],"sources":{"/home/adalberto/.dotnet/library-packs":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net9.0":{"targetAlias":"net9.0","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"9.0.300"}"frameworks":{"net9.0":{"targetAlias":"net9.0","dependencies":{"BCrypt.Net-Next":{"target":"Package","version":"[4.0.3, )"},"Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore":{"target":"Package","version":"[9.0.5, )"},"Microsoft.AspNetCore.Identity.EntityFrameworkCore":{"target":"Package","version":"[9.0.5, )"},"Microsoft.AspNetCore.Identity.UI":{"target":"Package","version":"[9.0.5, )"},"Microsoft.EntityFrameworkCore.Design":{"include":"Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive","suppressParent":"All","target":"Package","version":"[9.0.5, )"},"Microsoft.EntityFrameworkCore.Tools":{"target":"Package","version":"[9.0.5, )"},"Npgsql.EntityFrameworkCore.PostgreSQL":{"target":"Package","version":"[9.0.3, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.AspNetCore.App":{"privateAssets":"none"},"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"/home/adalberto/.dotnet/sdk/9.0.300/PortableRuntimeIdentifierGraph.json"}} "restore":{"projectUniqueName":"/home/adalberto/RiderProjects/RS_system/RS_system/RS_system.csproj","projectName":"RS_system","projectPath":"/home/adalberto/RiderProjects/RS_system/RS_system/RS_system.csproj","outputPath":"/home/adalberto/RiderProjects/RS_system/RS_system/obj/","projectStyle":"PackageReference","originalTargetFrameworks":["net9.0"],"sources":{"/usr/lib64/dotnet/library-packs":{},"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net9.0":{"targetAlias":"net9.0","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"9.0.100"}"frameworks":{"net9.0":{"targetAlias":"net9.0","dependencies":{"BCrypt.Net-Next":{"target":"Package","version":"[4.0.3, )"},"Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore":{"target":"Package","version":"[9.0.5, )"},"Microsoft.AspNetCore.Identity.EntityFrameworkCore":{"target":"Package","version":"[9.0.5, )"},"Microsoft.AspNetCore.Identity.UI":{"target":"Package","version":"[9.0.5, )"},"Microsoft.EntityFrameworkCore.Design":{"include":"Runtime, Build, Native, ContentFiles, Analyzers, BuildTransitive","suppressParent":"All","target":"Package","version":"[9.0.5, )"},"Microsoft.EntityFrameworkCore.Tools":{"target":"Package","version":"[9.0.5, )"},"Npgsql.EntityFrameworkCore.PostgreSQL":{"target":"Package","version":"[9.0.3, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"frameworkReferences":{"Microsoft.AspNetCore.App":{"privateAssets":"none"},"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"/usr/lib64/dotnet/sdk/9.0.113/PortableRuntimeIdentifierGraph.json"}}

View File

@@ -1 +1 @@
17677521665296480 17717199317703256

View File

@@ -1 +1 @@
17677521665296480 17717199317703256