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,193 +261,284 @@
</div> </div>
@section Scripts { @section Scripts {
<script> <!-- Offline Support Scripts -->
let timeoutBusqueda = null; <script src="~/js/colaboraciones-offline-db.js"></script>
<script src="~/js/colaboraciones-sync.js"></script>
// Búsqueda de miembros <script>
document.getElementById('buscarMiembro').addEventListener('input', function(e) { let timeoutBusqueda = null;
const termino = e.target.value;
const resultadosDiv = document.getElementById('resultadosBusqueda');
clearTimeout(timeoutBusqueda); // Búsqueda de miembros
document.getElementById('buscarMiembro').addEventListener('input', function(e) {
const termino = e.target.value;
const resultadosDiv = document.getElementById('resultadosBusqueda');
if (termino.length < 2) { clearTimeout(timeoutBusqueda);
resultadosDiv.style.display = 'none';
return;
}
timeoutBusqueda = setTimeout(async () => { if (termino.length < 2) {
try { resultadosDiv.style.display = 'none';
const response = await fetch('@Url.Action("BuscarMiembros", "Colaboracion")?termino=' + encodeURIComponent(termino)); return;
const miembros = await response.json(); }
if (miembros.length === 0) { timeoutBusqueda = setTimeout(async () => {
resultadosDiv.innerHTML = '<div class="list-group-item text-muted">No se encontraron resultados</div>'; try {
resultadosDiv.style.display = 'block'; const response = await fetch('@Url.Action("BuscarMiembros", "Colaboracion")?termino=' + encodeURIComponent(termino));
return; const miembros = await response.json();
}
let html = ''; if (miembros.length === 0) {
miembros.forEach(miembro => { resultadosDiv.innerHTML = '<div class="list-group-item text-muted">No se encontraron resultados</div>';
html += ` resultadosDiv.style.display = 'block';
<button type="button" class="list-group-item list-group-item-action" onclick="seleccionarMiembro(${miembro.id}, '${miembro.text}')"> return;
<div class="d-flex justify-content-between align-items-center"> }
<div>
<i class="bi bi-person me-2"></i>
<strong>${miembro.text}</strong>
</div>
${miembro.telefono ? '<small class="text-muted">' + miembro.telefono + '</small>' : ''}
</div>
</button>
`;
});
resultadosDiv.innerHTML = html; let html = '';
resultadosDiv.style.display = 'block'; miembros.forEach(miembro => {
} catch (error) { html += `
console.error('Error al buscar miembros:', error); <button type="button" class="list-group-item list-group-item-action" onclick="seleccionarMiembro(${miembro.id}, '${miembro.text}')">
} <div class="d-flex justify-content-between align-items-center">
}, 300); <div>
}); <i class="bi bi-person me-2"></i>
<strong>${miembro.text}</strong>
</div>
${miembro.telefono ? '<small class="text-muted">' + miembro.telefono + '</small>' : ''}
</div>
</button>
`;
});
// Cerrar resultados cuando se hace clic fuera resultadosDiv.innerHTML = html;
document.addEventListener('click', function(e) { resultadosDiv.style.display = 'block';
const buscarInput = document.getElementById('buscarMiembro'); } catch (error) {
const resultadosDiv = document.getElementById('resultadosBusqueda'); console.error('Error al buscar miembros:', error);
}
}, 300);
});
if (!buscarInput.contains(e.target) && !resultadosDiv.contains(e.target)) { // Cerrar resultados cuando se hace clic fuera
resultadosDiv.style.display = 'none'; document.addEventListener('click', function(e) {
} const buscarInput = document.getElementById('buscarMiembro');
}); const resultadosDiv = document.getElementById('resultadosBusqueda');
function seleccionarMiembro(id, nombre) { if (!buscarInput.contains(e.target) && !resultadosDiv.contains(e.target)) {
document.getElementById('miembroIdHidden').value = id; resultadosDiv.style.display = 'none';
document.getElementById('nombreMiembroSeleccionado').textContent = nombre; }
document.getElementById('miembroSeleccionado').style.display = 'block'; });
document.getElementById('buscarMiembro').value = '';
document.getElementById('buscarMiembro').style.display = 'none';
document.getElementById('resultadosBusqueda').style.display = 'none';
// Cargar historial de pagos function seleccionarMiembro(id, nombre) {
cargarHistorialPagos(id); document.getElementById('miembroIdHidden').value = id;
} document.getElementById('nombreMiembroSeleccionado').textContent = nombre;
document.getElementById('miembroSeleccionado').style.display = 'block';
document.getElementById('buscarMiembro').value = '';
document.getElementById('buscarMiembro').style.display = 'none';
document.getElementById('resultadosBusqueda').style.display = 'none';
function limpiarMiembro() { // Cargar historial de pagos
document.getElementById('miembroIdHidden').value = ''; cargarHistorialPagos(id);
document.getElementById('miembroSeleccionado').style.display = 'none';
document.getElementById('buscarMiembro').style.display = 'block';
document.getElementById('buscarMiembro').focus();
// Ocultar historial
document.getElementById('infoUltimosPagos').style.display = 'none';
document.getElementById('listaUltimosPagos').innerHTML = '';
}
async function cargarHistorialPagos(miembroId) {
const contenedor = document.getElementById('infoUltimosPagos');
const lista = document.getElementById('listaUltimosPagos');
lista.innerHTML = '<div class="spinner-border spinner-border-sm text-info" role="status"></div> Cargando historial...';
contenedor.style.display = 'block';
try {
const response = await fetch('@Url.Action("ObtenerUltimosPagos", "Colaboracion")?miembroId=' + miembroId);
const pagos = await response.json();
if (pagos && pagos.length > 0) {
let html = '';
pagos.forEach(p => {
const colorClass = p.ultimoMes > 0 ? 'bg-white text-info border border-info' : 'bg-secondary text-white';
html += `
<span class="badge ${colorClass} fw-normal p-2">
<strong>${p.nombreTipo}:</strong> ${p.ultimoPeriodoTexto}
</span>
`;
});
lista.innerHTML = html;
} else {
lista.innerHTML = '<span class="text-muted small">No hay historial de pagos registrado.</span>';
}
} catch (error) {
console.error('Error al cargar historial:', error);
lista.innerHTML = '<span class="text-danger small"><i class="bi bi-exclamation-circle"></i> Error al cargar historial</span>';
}
}
function calcularSugerido() {
try {
// Obtener valores
const mesInicial = parseInt(document.getElementById('mesInicial').value);
const anioInicial = parseInt(document.getElementById('anioInicial').value);
const mesFinal = parseInt(document.getElementById('mesFinal').value);
const anioFinal = parseInt(document.getElementById('anioFinal').value);
// Calcular total de meses
const fechaInicial = new Date(anioInicial, mesInicial - 1, 1);
const fechaFinal = new Date(anioFinal, mesFinal - 1, 1);
let totalMeses = 0;
if (fechaFinal >= fechaInicial) {
totalMeses = ((anioFinal - anioInicial) * 12) + (mesFinal - mesInicial) + 1;
}
// Obtener tipos seleccionados y sus montos
const tiposCheckboxes = document.querySelectorAll('.tipo-checkbox:checked');
const totalTipos = tiposCheckboxes.length;
// Calcular monto sugerido total
let montoSugeridoTotal = 0;
tiposCheckboxes.forEach(checkbox => {
const montoPorMes = parseFloat(checkbox.getAttribute('data-monto')) || 0;
montoSugeridoTotal += montoPorMes * totalMeses;
});
// Actualizar UI
document.getElementById('totalMeses').textContent = totalMeses;
document.getElementById('totalTipos').textContent = totalTipos;
document.getElementById('montoSugerido').textContent = montoSugeridoTotal.toFixed(2);
// Comparar con monto ingresado
const montoIngresado = parseFloat(document.getElementById('montoTotal').value) || 0;
const alertaDiv = document.getElementById('alertaDiferencia');
const mensajeSpan = document.getElementById('mensajeDiferencia');
if (montoIngresado > 0) {
if (Math.abs(montoIngresado - montoSugeridoTotal) > 0.01) {
const diferencia = montoIngresado - montoSugeridoTotal;
if (diferencia > 0) {
mensajeSpan.textContent = `Sobra: $${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.style.display = 'block';
} else {
alertaDiv.style.display = 'none';
}
} else {
alertaDiv.style.display = 'none';
}
} catch (error) {
console.error('Error al calcular sugerido:', error);
}
}
// Calcular cuando cambia el monto total
document.getElementById('montoTotal')?.addEventListener('input', calcularSugerido);
// Calcular al cargar la página
document.addEventListener('DOMContentLoaded', function() {
calcularSugerido();
});
// Show error messages
@if (TempData["Error"] != null)
{
<text>
toastr.error('@TempData["Error"]');
</text>
} }
</script>
function limpiarMiembro() {
document.getElementById('miembroIdHidden').value = '';
document.getElementById('miembroSeleccionado').style.display = 'none';
document.getElementById('buscarMiembro').style.display = 'block';
document.getElementById('buscarMiembro').focus();
// Ocultar historial
document.getElementById('infoUltimosPagos').style.display = 'none';
document.getElementById('listaUltimosPagos').innerHTML = '';
}
async function cargarHistorialPagos(miembroId) {
const contenedor = document.getElementById('infoUltimosPagos');
const lista = document.getElementById('listaUltimosPagos');
lista.innerHTML = '<div class="spinner-border spinner-border-sm text-info" role="status"></div> Cargando historial...';
contenedor.style.display = 'block';
try {
const response = await fetch('@Url.Action("ObtenerUltimosPagos", "Colaboracion")?miembroId=' + miembroId);
const pagos = await response.json();
if (pagos && pagos.length > 0) {
let html = '';
pagos.forEach(p => {
const colorClass = p.ultimoMes > 0 ? 'bg-white text-info border border-info' : 'bg-secondary text-white';
html += `
<span class="badge ${colorClass} fw-normal p-2">
<strong>${p.nombreTipo}:</strong> ${p.ultimoPeriodoTexto}
</span>
`;
});
lista.innerHTML = html;
} else {
lista.innerHTML = '<span class="text-muted small">No hay historial de pagos registrado.</span>';
}
} catch (error) {
console.error('Error al cargar historial:', error);
lista.innerHTML = '<span class="text-danger small"><i class="bi bi-exclamation-circle"></i> Error al cargar historial</span>';
}
}
function calcularSugerido() {
try {
// Obtener valores
const mesInicial = parseInt(document.getElementById('mesInicial').value);
const anioInicial = parseInt(document.getElementById('anioInicial').value);
const mesFinal = parseInt(document.getElementById('mesFinal').value);
const anioFinal = parseInt(document.getElementById('anioFinal').value);
// Calcular total de meses
const fechaInicial = new Date(anioInicial, mesInicial - 1, 1);
const fechaFinal = new Date(anioFinal, mesFinal - 1, 1);
let totalMeses = 0;
if (fechaFinal >= fechaInicial) {
totalMeses = ((anioFinal - anioInicial) * 12) + (mesFinal - mesInicial) + 1;
}
// Obtener tipos seleccionados y sus montos
const tiposCheckboxes = document.querySelectorAll('.tipo-checkbox:checked');
const totalTipos = tiposCheckboxes.length;
// Calcular monto sugerido total
let montoSugeridoTotal = 0;
tiposCheckboxes.forEach(checkbox => {
const montoPorMes = parseFloat(checkbox.getAttribute('data-monto')) || 0;
montoSugeridoTotal += montoPorMes * totalMeses;
});
// Actualizar UI
document.getElementById('totalMeses').textContent = totalMeses;
document.getElementById('totalTipos').textContent = totalTipos;
document.getElementById('montoSugerido').textContent = montoSugeridoTotal.toFixed(2);
// Comparar con monto ingresado
const montoIngresado = parseFloat(document.getElementById('montoTotal').value) || 0;
const alertaDiv = document.getElementById('alertaDiferencia');
const mensajeSpan = document.getElementById('mensajeDiferencia');
if (montoIngresado > 0) {
if (Math.abs(montoIngresado - montoSugeridoTotal) > 0.01) {
const diferencia = montoSugeridoTotal - montoIngresado; // Corrected calculation for difference
if (diferencia > 0) {
mensajeSpan.textContent = `Falta: $${diferencia.toFixed(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';
} else {
alertaDiv.style.display = 'none';
}
} else {
alertaDiv.style.display = 'none';
}
} catch (error) {
console.error('Error al calcular sugerido:', error);
}
}
// Calcular cuando cambia el monto total
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
document.addEventListener('DOMContentLoaded', function() {
calcularSugerido();
});
// Show error messages
@if (TempData["Error"] != null)
{
<text>
toastr.error('@TempData["Error"]');
</text>
}
</script>
} }

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,9 +8,48 @@
<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>
<a asp-action="Create" class="btn btn-primary-custom"> <div>
<i class="bi bi-plus-lg me-1"></i> Nuevo Miembro <a asp-action="Importar" class="btn btn-outline-success me-2">
</a> <i class="bi bi-file-earmark-spreadsheet me-1"></i> Importar CSV
</a>
<a asp-action="Create" class="btn btn-primary-custom">
<i class="bi bi-plus-lg me-1"></i> Nuevo Miembro
</a>
</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> </div>
<!-- Summary Cards --> <!-- Summary Cards -->
@@ -18,19 +57,19 @@
<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