Mantenimiento de Miembros

This commit is contained in:
2026-01-13 21:02:34 -06:00
parent 06470a9173
commit 75aac3b273
50 changed files with 1440 additions and 1145 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/RS_system/obj/
/RS_system/obj/Debug/

View File

@@ -1,3 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AActionMethodExecutor_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fdb7395f4add94e6d10e515b3e55373f2821f8323de7dc8e314d78feefacf5584_003FActionMethodExecutor_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAsyncTaskMethodBuilderT_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fe5ace92664429b8b34b68b587316fb211811b58aeb53422b43cecca2ad2bdaf3_003FAsyncTaskMethodBuilderT_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthorizeAttribute_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003F1750dff95ce6347f7797ec095c5c17196efe67de17ff2666b9992265f0b6_003FAuthorizeAttribute_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fbf9021a960b74107a7e141aa06bc9d8a0a53c929178c2fb95b1597be8af8dc_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>

View File

@@ -0,0 +1,132 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Rs_system.Models.ViewModels;
using Rs_system.Services;
namespace Rs_system.Controllers;
[Authorize]
public class MiembroController : Controller
{
private readonly IMiembroService _miembroService;
public MiembroController(IMiembroService miembroService)
{
_miembroService = miembroService;
}
// GET: Miembro
public async Task<IActionResult> Index()
{
var miembros = await _miembroService.GetAllAsync();
return View(miembros);
}
// GET: Miembro/Details/5
public async Task<IActionResult> Details(long? id)
{
if (id == null)
return NotFound();
var miembro = await _miembroService.GetByIdAsync(id.Value);
if (miembro == null)
return NotFound();
return View(miembro);
}
// GET: Miembro/Create
public async Task<IActionResult> Create()
{
await LoadGruposTrabajoAsync();
var viewModel = new MiembroViewModel
{
FechaIngresoCongregacion = DateOnly.FromDateTime(DateTime.Today)
};
return View(viewModel);
}
// POST: Miembro/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(MiembroViewModel viewModel, IFormFile? fotoFile)
{
if (ModelState.IsValid)
{
var createdBy = User.Identity?.Name ?? "Sistema";
var success = await _miembroService.CreateAsync(viewModel, createdBy, fotoFile);
if (success)
{
TempData["SuccessMessage"] = "Miembro creado exitosamente.";
return RedirectToAction(nameof(Index));
}
ModelState.AddModelError("", "Error al crear el miembro. Intente nuevamente.");
}
await LoadGruposTrabajoAsync();
return View(viewModel);
}
// GET: Miembro/Edit/5
public async Task<IActionResult> Edit(long? id)
{
if (id == null)
return NotFound();
var miembro = await _miembroService.GetByIdAsync(id.Value);
if (miembro == null)
return NotFound();
await LoadGruposTrabajoAsync();
return View(miembro);
}
// POST: Miembro/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(long id, MiembroViewModel viewModel, IFormFile? fotoFile)
{
if (id != viewModel.Id)
return NotFound();
if (ModelState.IsValid)
{
var success = await _miembroService.UpdateAsync(id, viewModel, fotoFile);
if (success)
{
TempData["SuccessMessage"] = "Miembro actualizado exitosamente.";
return RedirectToAction(nameof(Index));
}
ModelState.AddModelError("", "Error al actualizar el miembro. Intente nuevamente.");
}
await LoadGruposTrabajoAsync();
return View(viewModel);
}
// POST: Miembro/Delete/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(long id)
{
var success = await _miembroService.DeleteAsync(id);
if (success)
TempData["SuccessMessage"] = "Miembro eliminado exitosamente.";
else
TempData["ErrorMessage"] = "Error al eliminar el miembro.";
return RedirectToAction(nameof(Index));
}
private async Task LoadGruposTrabajoAsync()
{
var grupos = await _miembroService.GetGruposTrabajoAsync();
ViewBag.GruposTrabajo = new SelectList(grupos.Select(g => new { g.Id, g.Nombre }), "Id", "Nombre");
}
}

View File

@@ -22,11 +22,17 @@ public class ApplicationDbContext : DbContext
public DbSet<AsistenciaCulto> AsistenciasCulto { get; set; }
// Offerings module
public DbSet<RegistroCulto> RegistrosCulto { get; set; }
public DbSet<Ofrenda> Ofrendas { get; set; }
public DbSet<DescuentoOfrenda> DescuentosOfrenda { get; set; }
// Church Members module
public DbSet<GrupoTrabajo> GruposTrabajo { get; set; }
public DbSet<Miembro> Miembros { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
@@ -69,6 +75,20 @@ public class ApplicationDbContext : DbContext
.HasOne(u => u.Persona)
.WithMany()
.HasForeignKey(u => u.PersonaId);
// Church Members module relationships
modelBuilder.Entity<Miembro>()
.HasOne(m => m.GrupoTrabajo)
.WithMany(g => g.Miembros)
.HasForeignKey(m => m.GrupoTrabajoId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Miembro>()
.HasOne(m => m.Persona)
.WithMany()
.HasForeignKey(m => m.PersonaId)
.OnDelete(DeleteBehavior.Restrict);
// Global configuration: Convert all dates to UTC when saving
foreach (var entityType in modelBuilder.Model.GetEntityTypes())

View File

@@ -0,0 +1,32 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Rs_system.Models;
[Table("grupos_trabajo")]
public class GrupoTrabajo
{
[Key]
[Column("id")]
public long Id { get; set; }
[Column("nombre")]
[Required]
[StringLength(100)]
public string Nombre { get; set; } = string.Empty;
[Column("descripcion")]
public string? Descripcion { get; set; }
[Column("activo")]
public bool Activo { get; set; } = true;
[Column("creado_en")]
public DateTime CreadoEn { get; set; } = DateTime.UtcNow;
[Column("actualizado_en")]
public DateTime ActualizadoEn { get; set; } = DateTime.UtcNow;
// Navigation properties
public virtual ICollection<Miembro> Miembros { get; set; } = new List<Miembro>();
}

View File

@@ -0,0 +1,51 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Rs_system.Models;
[Table("miembros")]
public class Miembro
{
[Key]
[Column("id")]
public long Id { get; set; }
[Column("persona_id")]
public long PersonaId { get; set; }
[Column("bautizado_espiritu_santo")]
public bool BautizadoEspirituSanto { get; set; } = false;
[Column("fecha_ingreso_congregacion")]
public DateOnly? FechaIngresoCongregacion { get; set; }
[Column("telefono_emergencia")]
[StringLength(20)]
public string? TelefonoEmergencia { get; set; }
[Column("grupo_trabajo_id")]
public long? GrupoTrabajoId { get; set; }
[Column("activo")]
public bool Activo { get; set; } = true;
[Column("eliminado")]
public bool Eliminado { get; set; } = false;
[Column("creado_en")]
public DateTime CreadoEn { get; set; } = DateTime.UtcNow;
[Column("actualizado_en")]
public DateTime ActualizadoEn { get; set; } = DateTime.UtcNow;
[Column("creado_por")]
[StringLength(100)]
public string? CreadoPor { get; set; }
// Navigation properties
[ForeignKey("PersonaId")]
public virtual Persona Persona { get; set; } = null!;
[ForeignKey("GrupoTrabajoId")]
public virtual GrupoTrabajo? GrupoTrabajo { get; set; }
}

View File

@@ -0,0 +1,61 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http;
namespace Rs_system.Models.ViewModels;
public class MiembroViewModel
{
public long Id { get; set; }
[Required(ErrorMessage = "Los nombres son requeridos")]
[StringLength(100, ErrorMessage = "Los nombres no pueden exceder 100 caracteres")]
[Display(Name = "Nombres")]
public string Nombres { get; set; } = string.Empty;
[Required(ErrorMessage = "Los apellidos son requeridos")]
[StringLength(100, ErrorMessage = "Los apellidos no pueden exceder 100 caracteres")]
[Display(Name = "Apellidos")]
public string Apellidos { get; set; } = string.Empty;
[Display(Name = "Fecha de Nacimiento")]
[DataType(DataType.Date)]
public DateOnly? FechaNacimiento { get; set; }
[Display(Name = "Bautizado en el Espíritu Santo")]
public bool BautizadoEspirituSanto { get; set; } = false;
[Display(Name = "Dirección")]
[DataType(DataType.MultilineText)]
public string? Direccion { get; set; }
[Display(Name = "Fecha de Ingreso a la Congregación")]
[DataType(DataType.Date)]
public DateOnly? FechaIngresoCongregacion { get; set; }
[StringLength(20, ErrorMessage = "El teléfono no puede exceder 20 caracteres")]
[Display(Name = "Teléfono")]
[Phone(ErrorMessage = "Formato de teléfono inválido")]
public string? Telefono { get; set; }
[StringLength(20, ErrorMessage = "El teléfono de emergencia no puede exceder 20 caracteres")]
[Display(Name = "Teléfono de Emergencia")]
[Phone(ErrorMessage = "Formato de teléfono inválido")]
public string? TelefonoEmergencia { get; set; }
[Display(Name = "Grupo de Trabajo")]
public long? GrupoTrabajoId { get; set; }
[Display(Name = "Activo")]
public bool Activo { get; set; } = true;
[Display(Name = "Foto del Miembro")]
public string? FotoUrl { get; set; }
[Display(Name = "Subir Foto")]
[DataType(DataType.Upload)]
public IFormFile? FotoFile { get; set; }
// For display purposes
public string? GrupoTrabajoNombre { get; set; }
public string NombreCompleto => $"{Nombres} {Apellidos}";
}

View File

@@ -29,6 +29,8 @@ builder.Services.AddScoped<IAuthService, AuthService>();
builder.Services.AddScoped<IReporteService, ReporteService>();
builder.Services.AddScoped<IConfiguracionService, ConfiguracionService>();
builder.Services.AddScoped<IMenuService, MenuService>();
builder.Services.AddScoped<IMiembroService, MiembroService>();
builder.Services.AddScoped<IFileStorageService, FileStorageService>();
builder.Services.AddSingleton<IQueryCacheService, QueryCacheService>();
builder.Services.AddMemoryCache(options =>
{

View File

@@ -0,0 +1,89 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Hosting;
using System.IO;
using System.Threading.Tasks;
namespace Rs_system.Services;
public interface IFileStorageService
{
Task<string> SaveFileAsync(IFormFile file, string folderName);
Task<bool> DeleteFileAsync(string filePath);
string GetFileUrl(string filePath);
}
public class FileStorageService : IFileStorageService
{
private readonly IHostEnvironment _environment;
private readonly string _uploadsFolder;
public FileStorageService(IHostEnvironment environment)
{
_environment = environment;
_uploadsFolder = Path.Combine(_environment.ContentRootPath, "wwwroot", "uploads");
// Ensure uploads folder exists
if (!Directory.Exists(_uploadsFolder))
{
Directory.CreateDirectory(_uploadsFolder);
}
}
public async Task<string> SaveFileAsync(IFormFile file, string folderName)
{
if (file == null || file.Length == 0)
return string.Empty;
// Create folder if it doesn't exist
var folderPath = Path.Combine(_uploadsFolder, folderName);
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
// Generate unique filename
var fileExtension = Path.GetExtension(file.FileName);
var fileName = $"{Guid.NewGuid()}{fileExtension}";
var filePath = Path.Combine(folderPath, fileName);
// Save file
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
// Return relative path for database storage
return Path.Combine("uploads", folderName, fileName).Replace("\\", "/");
}
public async Task<bool> DeleteFileAsync(string filePath)
{
if (string.IsNullOrEmpty(filePath))
return true;
try
{
var fullPath = Path.Combine(_environment.ContentRootPath, "wwwroot", filePath);
if (File.Exists(fullPath))
{
await Task.Run(() => File.Delete(fullPath));
return true;
}
return true; // File doesn't exist, consider it deleted
}
catch
{
return false;
}
}
public string GetFileUrl(string filePath)
{
if (string.IsNullOrEmpty(filePath))
return string.Empty;
return $"/{filePath}";
}
}

View File

@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Http;
using Rs_system.Models.ViewModels;
namespace Rs_system.Services;
public interface IMiembroService
{
/// <summary>
/// Gets all active members with their work group information
/// </summary>
Task<IEnumerable<MiembroViewModel>> GetAllAsync();
/// <summary>
/// Gets a member by ID
/// </summary>
Task<MiembroViewModel?> GetByIdAsync(long id);
/// <summary>
/// Creates a new member
/// </summary>
Task<bool> CreateAsync(MiembroViewModel viewModel, string createdBy, IFormFile? fotoFile = null);
/// <summary>
/// Updates an existing member
/// </summary>
Task<bool> UpdateAsync(long id, MiembroViewModel viewModel, IFormFile? fotoFile = null);
/// <summary>
/// Soft deletes a member
/// </summary>
Task<bool> DeleteAsync(long id);
/// <summary>
/// Gets all active work groups for dropdown
/// </summary>
Task<IEnumerable<(long Id, string Nombre)>> GetGruposTrabajoAsync();
}

View File

@@ -0,0 +1,224 @@
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Rs_system.Data;
using Rs_system.Models;
using Rs_system.Models.ViewModels;
namespace Rs_system.Services;
public class MiembroService : IMiembroService
{
private readonly ApplicationDbContext _context;
private readonly IFileStorageService _fileStorageService;
public MiembroService(ApplicationDbContext context, IFileStorageService fileStorageService)
{
_context = context;
_fileStorageService = fileStorageService;
}
public async Task<IEnumerable<MiembroViewModel>> GetAllAsync()
{
return await _context.Miembros
.Include(m => m.Persona)
.Include(m => m.GrupoTrabajo)
.Where(m => !m.Eliminado && m.Activo)
.OrderBy(m => m.Persona.Apellidos)
.ThenBy(m => m.Persona.Nombres)
.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();
}
public async Task<MiembroViewModel?> GetByIdAsync(long id)
{
var miembro = await _context.Miembros
.Include(m => m.Persona)
.Include(m => m.GrupoTrabajo)
.FirstOrDefaultAsync(m => m.Id == id && !m.Eliminado);
if (miembro == null)
return null;
return new MiembroViewModel
{
Id = miembro.Id,
Nombres = miembro.Persona.Nombres,
Apellidos = miembro.Persona.Apellidos,
FechaNacimiento = miembro.Persona.FechaNacimiento,
BautizadoEspirituSanto = miembro.BautizadoEspirituSanto,
Direccion = miembro.Persona.Direccion,
FechaIngresoCongregacion = miembro.FechaIngresoCongregacion,
Telefono = miembro.Persona.Telefono,
TelefonoEmergencia = miembro.TelefonoEmergencia,
GrupoTrabajoId = miembro.GrupoTrabajoId,
GrupoTrabajoNombre = miembro.GrupoTrabajo?.Nombre,
Activo = miembro.Activo,
FotoUrl = miembro.Persona.FotoUrl
};
}
public async Task<bool> CreateAsync(MiembroViewModel viewModel, string createdBy, IFormFile? fotoFile = null)
{
var strategy = _context.Database.CreateExecutionStrategy();
try
{
await strategy.ExecuteAsync(async () =>
{
using var transaction = await _context.Database.BeginTransactionAsync();
// Handle photo upload
string? fotoUrl = null;
if (fotoFile != null)
{
fotoUrl = await _fileStorageService.SaveFileAsync(fotoFile, "miembros");
}
// 1. Create Persona
var persona = new Persona
{
Nombres = viewModel.Nombres,
Apellidos = viewModel.Apellidos,
FechaNacimiento = viewModel.FechaNacimiento,
Direccion = viewModel.Direccion,
Telefono = viewModel.Telefono,
FotoUrl = fotoUrl,
Activo = true,
CreadoEn = DateTime.UtcNow,
ActualizadoEn = DateTime.UtcNow
};
_context.Personas.Add(persona);
await _context.SaveChangesAsync();
// 2. Create Miembro linked to Persona
var miembro = new Miembro
{
PersonaId = persona.Id,
BautizadoEspirituSanto = viewModel.BautizadoEspirituSanto,
FechaIngresoCongregacion = viewModel.FechaIngresoCongregacion,
TelefonoEmergencia = viewModel.TelefonoEmergencia,
GrupoTrabajoId = viewModel.GrupoTrabajoId,
Activo = viewModel.Activo,
CreadoPor = createdBy,
CreadoEn = DateTime.UtcNow,
ActualizadoEn = DateTime.UtcNow
};
_context.Miembros.Add(miembro);
await _context.SaveChangesAsync();
await transaction.CommitAsync();
});
return true;
}
catch (Exception ex)
{
// Log exception here if logger is available
Console.WriteLine(ex.Message);
return false;
}
}
public async Task<bool> UpdateAsync(long id, MiembroViewModel viewModel, IFormFile? fotoFile = null)
{
var strategy = _context.Database.CreateExecutionStrategy();
try
{
await strategy.ExecuteAsync(async () =>
{
using var transaction = await _context.Database.BeginTransactionAsync();
var miembro = await _context.Miembros
.Include(m => m.Persona)
.FirstOrDefaultAsync(m => m.Id == id && !m.Eliminado);
if (miembro == null)
throw new InvalidOperationException("Miembro no encontrado");
// Handle photo upload
if (fotoFile != null)
{
// Delete old photo if exists
if (!string.IsNullOrEmpty(miembro.Persona.FotoUrl))
{
await _fileStorageService.DeleteFileAsync(miembro.Persona.FotoUrl);
}
// Save new photo
miembro.Persona.FotoUrl = await _fileStorageService.SaveFileAsync(fotoFile, "miembros");
}
// Update Persona
miembro.Persona.Nombres = viewModel.Nombres;
miembro.Persona.Apellidos = viewModel.Apellidos;
miembro.Persona.FechaNacimiento = viewModel.FechaNacimiento;
miembro.Persona.Direccion = viewModel.Direccion;
miembro.Persona.Telefono = viewModel.Telefono;
miembro.Persona.ActualizadoEn = DateTime.UtcNow;
// Update Miembro
miembro.BautizadoEspirituSanto = viewModel.BautizadoEspirituSanto;
miembro.FechaIngresoCongregacion = viewModel.FechaIngresoCongregacion;
miembro.TelefonoEmergencia = viewModel.TelefonoEmergencia;
miembro.GrupoTrabajoId = viewModel.GrupoTrabajoId;
miembro.Activo = viewModel.Activo;
miembro.ActualizadoEn = DateTime.UtcNow;
await _context.SaveChangesAsync();
await transaction.CommitAsync();
});
return true;
}
catch
{
return false;
}
}
public async Task<bool> DeleteAsync(long id)
{
try
{
var miembro = await _context.Miembros.FindAsync(id);
if (miembro == null || miembro.Eliminado)
return false;
miembro.Eliminado = true;
miembro.ActualizadoEn = DateTime.UtcNow;
await _context.SaveChangesAsync();
return true;
}
catch
{
return false;
}
}
public async Task<IEnumerable<(long Id, string Nombre)>> GetGruposTrabajoAsync()
{
return await _context.GruposTrabajo
.Where(g => g.Activo)
.OrderBy(g => g.Nombre)
.Select(g => new ValueTuple<long, string>(g.Id, g.Nombre))
.ToListAsync();
}
}

View File

@@ -0,0 +1,106 @@
@model Rs_system.Models.ViewModels.MiembroViewModel
@{
ViewData["Title"] = "Nuevo Miembro";
}
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h4 class="mb-1">Nuevo Miembro</h4>
<p class="text-muted mb-0">Registrar un nuevo miembro de la congregación</p>
</div>
<a asp-action="Index" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-1"></i> Volver
</a>
</div>
<div class="card-custom">
<form asp-action="Create" method="post" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>
<div class="row mb-3">
<div class="col-md-6">
<label asp-for="Nombres" class="form-label"></label>
<input asp-for="Nombres" class="form-control" placeholder="Ingrese los nombres" />
<span asp-validation-for="Nombres" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="Apellidos" class="form-label"></label>
<input asp-for="Apellidos" class="form-control" placeholder="Ingrese los apellidos" />
<span asp-validation-for="Apellidos" class="text-danger"></span>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label asp-for="FechaNacimiento" class="form-label"></label>
<input asp-for="FechaNacimiento" type="date" class="form-control" />
<span asp-validation-for="FechaNacimiento" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="FechaIngresoCongregacion" class="form-label"></label>
<input asp-for="FechaIngresoCongregacion" type="date" class="form-control" />
<span asp-validation-for="FechaIngresoCongregacion" class="text-danger"></span>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label asp-for="Telefono" class="form-label"></label>
<input asp-for="Telefono" class="form-control" placeholder="Ej: 7890-1234" />
<span asp-validation-for="Telefono" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="TelefonoEmergencia" class="form-label"></label>
<input asp-for="TelefonoEmergencia" class="form-control" placeholder="Ej: 7890-5678" />
<span asp-validation-for="TelefonoEmergencia" class="text-danger"></span>
</div>
</div>
<div class="mb-3">
<label asp-for="Direccion" class="form-label"></label>
<textarea asp-for="Direccion" class="form-control" rows="3" placeholder="Ingrese la dirección completa"></textarea>
<span asp-validation-for="Direccion" class="text-danger"></span>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label asp-for="GrupoTrabajoId" class="form-label"></label>
<select asp-for="GrupoTrabajoId" class="form-select" asp-items="ViewBag.GruposTrabajo">
<option value="">Seleccione un grupo de trabajo</option>
</select>
<span asp-validation-for="GrupoTrabajoId" class="text-danger"></span>
</div>
<div class="col-md-6">
<label class="form-label d-block">&nbsp;</label>
<div class="form-check">
<input asp-for="BautizadoEspirituSanto" class="form-check-input" type="checkbox" />
<label asp-for="BautizadoEspirituSanto" class="form-check-label"></label>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<label asp-for="FotoFile" class="form-label"></label>
<input asp-for="FotoFile" type="file" class="form-control" accept="image/*" />
<span asp-validation-for="FotoFile" class="text-danger"></span>
<div class="form-text">Formatos permitidos: JPG, PNG, GIF. Tamaño máximo: 5MB</div>
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input asp-for="Activo" class="form-check-input" type="checkbox" checked />
<label asp-for="Activo" class="form-check-label"></label>
</div>
</div>
<div class="d-flex justify-content-end gap-2">
<a asp-action="Index" class="btn btn-secondary">Cancelar</a>
<button type="submit" class="btn btn-primary-custom">
<i class="bi bi-save me-1"></i> Guardar
</button>
</div>
</form>
</div>

View File

@@ -0,0 +1,199 @@
@model Rs_system.Models.ViewModels.MiembroViewModel
@{
ViewData["Title"] = "Detalles del Miembro";
}
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h4 class="mb-1">Detalles del Miembro</h4>
<p class="text-muted mb-0">Información completa del miembro</p>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-outline-primary">
<i class="bi bi-pencil me-1"></i> Editar
</a>
<a asp-action="Index" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-1"></i> Volver
</a>
</div>
</div>
<div class="card-custom">
<div class="row">
<div class="col-md-4 mb-3 text-center">
<h6 class="text-muted mb-2">Foto del Miembro</h6>
@if (!string.IsNullOrEmpty(Model.FotoUrl))
{
<img src="/@Model.FotoUrl" alt="Foto de @Model.NombreCompleto" class="img-fluid rounded" style="max-height: 200px; object-fit: cover;" />
<div class="mt-2">
<a href="/@Model.FotoUrl" target="_blank" class="btn btn-sm btn-outline-info">
<i class="bi bi-eye me-1"></i> Ver foto completa
</a>
</div>
}
else
{
<div class="d-flex flex-column align-items-center">
<i class="bi bi-person-circle text-muted" style="font-size: 6rem;"></i>
<span class="text-muted small">No hay foto registrada</span>
</div>
}
</div>
<div class="col-md-8">
<div class="row">
<div class="col-md-6 mb-3">
<h6 class="text-muted mb-2">Nombres</h6>
<p class="mb-0">@Model.Nombres</p>
</div>
<div class="col-md-6 mb-3">
<h6 class="text-muted mb-2">Apellidos</h6>
<p class="mb-0">@Model.Apellidos</p>
</div>
</div>
</div>
</div>
<hr class="my-3" />
<div class="row">
<div class="col-md-6 mb-3">
<h6 class="text-muted mb-2">Fecha de Nacimiento</h6>
<p class="mb-0">
@if (Model.FechaNacimiento.HasValue)
{
<span>@Model.FechaNacimiento.Value.ToString("dd/MM/yyyy")</span>
<span class="text-muted">
(@{
var edad = DateTime.Today.Year - Model.FechaNacimiento.Value.Year;
if (Model.FechaNacimiento.Value.ToDateTime(TimeOnly.MinValue) > DateTime.Today.AddYears(-edad))
edad--;
}
@edad años)
</span>
}
else
{
<span class="text-muted">No especificado</span>
}
</p>
</div>
<div class="col-md-6 mb-3">
<h6 class="text-muted mb-2">Fecha de Ingreso a la Congregación</h6>
<p class="mb-0">
@if (Model.FechaIngresoCongregacion.HasValue)
{
<span>@Model.FechaIngresoCongregacion.Value.ToString("dd/MM/yyyy")</span>
}
else
{
<span class="text-muted">No especificado</span>
}
</p>
</div>
</div>
<hr class="my-3" />
<div class="row">
<div class="col-md-6 mb-3">
<h6 class="text-muted mb-2">Teléfono</h6>
<p class="mb-0">
@if (!string.IsNullOrEmpty(Model.Telefono))
{
<i class="bi bi-telephone me-1"></i>
<span>@Model.Telefono</span>
}
else
{
<span class="text-muted">No especificado</span>
}
</p>
</div>
<div class="col-md-6 mb-3">
<h6 class="text-muted mb-2">Teléfono de Emergencia</h6>
<p class="mb-0">
@if (!string.IsNullOrEmpty(Model.TelefonoEmergencia))
{
<i class="bi bi-telephone-fill me-1 text-danger"></i>
<span>@Model.TelefonoEmergencia</span>
}
else
{
<span class="text-muted">No especificado</span>
}
</p>
</div>
</div>
<hr class="my-3" />
<div class="mb-3">
<h6 class="text-muted mb-2">Dirección</h6>
<p class="mb-0">
@if (!string.IsNullOrEmpty(Model.Direccion))
{
<i class="bi bi-geo-alt me-1"></i>
<span>@Model.Direccion</span>
}
else
{
<span class="text-muted">No especificado</span>
}
</p>
</div>
<hr class="my-3" />
<div class="row">
<div class="col-md-6 mb-3">
<h6 class="text-muted mb-2">Grupo de Trabajo</h6>
<p class="mb-0">
@if (!string.IsNullOrEmpty(Model.GrupoTrabajoNombre))
{
<span class="badge bg-primary">@Model.GrupoTrabajoNombre</span>
}
else
{
<span class="text-muted">Sin grupo asignado</span>
}
</p>
</div>
<div class="col-md-6 mb-3">
<h6 class="text-muted mb-2">Bautizado en el Espíritu Santo</h6>
<p class="mb-0">
@if (Model.BautizadoEspirituSanto)
{
<span class="badge bg-success">
<i class="bi bi-check-circle me-1"></i> Sí
</span>
}
else
{
<span class="badge bg-secondary">
<i class="bi bi-x-circle me-1"></i> No
</span>
}
</p>
</div>
</div>
<hr class="my-3" />
<div class="mb-3">
<h6 class="text-muted mb-2">Estado</h6>
<p class="mb-0">
@if (Model.Activo)
{
<span class="badge bg-success">
<i class="bi bi-check-circle me-1"></i> Activo
</span>
}
else
{
<span class="badge bg-warning">
<i class="bi bi-pause-circle me-1"></i> Inactivo
</span>
}
</p>
</div>
</div>

View File

@@ -0,0 +1,130 @@
@model Rs_system.Models.ViewModels.MiembroViewModel
@{
ViewData["Title"] = "Editar Miembro";
}
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h4 class="mb-1">Editar Miembro</h4>
<p class="text-muted mb-0">Actualizar información del miembro</p>
</div>
<a asp-action="Index" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-1"></i> Volver
</a>
</div>
<div class="card-custom">
<form asp-action="Edit" method="post" enctype="multipart/form-data">
<input type="hidden" asp-for="Id" />
<div asp-validation-summary="ModelOnly" class="alert alert-danger"></div>
<div class="row mb-3">
<div class="col-md-6">
<label asp-for="Nombres" class="form-label"></label>
<input asp-for="Nombres" class="form-control" placeholder="Ingrese los nombres" />
<span asp-validation-for="Nombres" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="Apellidos" class="form-label"></label>
<input asp-for="Apellidos" class="form-control" placeholder="Ingrese los apellidos" />
<span asp-validation-for="Apellidos" class="text-danger"></span>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label asp-for="FechaNacimiento" class="form-label"></label>
<input asp-for="FechaNacimiento" type="date" class="form-control" />
<span asp-validation-for="FechaNacimiento" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="FechaIngresoCongregacion" class="form-label"></label>
<input asp-for="FechaIngresoCongregacion" type="date" class="form-control" />
<span asp-validation-for="FechaIngresoCongregacion" class="text-danger"></span>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label asp-for="Telefono" class="form-label"></label>
<input asp-for="Telefono" class="form-control" placeholder="Ej: 7890-1234" />
<span asp-validation-for="Telefono" class="text-danger"></span>
</div>
<div class="col-md-6">
<label asp-for="TelefonoEmergencia" class="form-label"></label>
<input asp-for="TelefonoEmergencia" class="form-control" placeholder="Ej: 7890-5678" />
<span asp-validation-for="TelefonoEmergencia" class="text-danger"></span>
</div>
</div>
<div class="mb-3">
<label asp-for="Direccion" class="form-label"></label>
<textarea asp-for="Direccion" class="form-control" rows="3" placeholder="Ingrese la dirección completa"></textarea>
<span asp-validation-for="Direccion" class="text-danger"></span>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label asp-for="GrupoTrabajoId" class="form-label"></label>
<select asp-for="GrupoTrabajoId" class="form-select" asp-items="ViewBag.GruposTrabajo">
<option value="">Seleccione un grupo de trabajo</option>
</select>
<span asp-validation-for="GrupoTrabajoId" class="text-danger"></span>
</div>
<div class="col-md-6">
<label class="form-label d-block">&nbsp;</label>
<div class="form-check">
<input asp-for="BautizadoEspirituSanto" class="form-check-input" type="checkbox" />
<label asp-for="BautizadoEspirituSanto" class="form-check-label"></label>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label asp-for="FotoFile" class="form-label"></label>
<input asp-for="FotoFile" type="file" class="form-control" accept="image/*" />
<span asp-validation-for="FotoFile" class="text-danger"></span>
<div class="form-text">Deje en blanco para mantener la foto actual. Formatos: JPG, PNG, GIF. Máx: 5MB</div>
</div>
<div class="col-md-6">
<label class="form-label">Foto Actual</label>
@if (!string.IsNullOrEmpty(Model.FotoUrl))
{
<div class="d-flex align-items-center gap-3">
<img src="/@Model.FotoUrl" alt="Foto del miembro" class="img-thumbnail" style="max-width: 80px; max-height: 80px; object-fit: cover;" />
<div>
<small class="text-muted d-block">Foto actual</small>
<a href="/@Model.FotoUrl" target="_blank" class="btn btn-sm btn-outline-info">Ver</a>
</div>
</div>
}
else
{
<div class="text-muted">
<i class="bi bi-person-circle" style="font-size: 2rem;"></i>
<p class="mb-0 small">No hay foto registrada</p>
</div>
}
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input asp-for="Activo" class="form-check-input" type="checkbox" />
<label asp-for="Activo" class="form-check-label"></label>
</div>
</div>
<div class="d-flex justify-content-end gap-2">
<a asp-action="Index" class="btn btn-secondary">Cancelar</a>
<button type="submit" class="btn btn-primary-custom">
<i class="bi bi-save me-1"></i> Actualizar
</button>
</div>
</form>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

View File

@@ -0,0 +1,196 @@
@model IEnumerable<Rs_system.Models.ViewModels.MiembroViewModel>
@{
ViewData["Title"] = "Miembros de la Iglesia";
}
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h4 class="mb-1">Miembros en Propiedad</h4>
<p class="text-muted mb-0">Gestión de miembros de la congregación</p>
</div>
<a asp-action="Create" class="btn btn-primary-custom">
<i class="bi bi-plus-lg me-1"></i> Nuevo Miembro
</a>
</div>
<!-- Summary Cards -->
<div class="row mb-4">
<div class="col-md-4">
<div class="card-custom text-center">
<h6 class="text-muted mb-2">Total Miembros</h6>
<h3 class="text-primary mb-0">@Model.Count()</h3>
</div>
</div>
<div class="col-md-4">
<div class="card-custom text-center">
<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>
</div>
</div>
<div class="col-md-4">
<div class="card-custom text-center">
<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>
</div>
</div>
</div>
<!-- Members Table -->
<div class="card-custom">
<div class="table-responsive">
<table class="table-custom">
<thead>
<tr>
<th>Foto</th>
<th>Nombre Completo</th>
<th>Fecha de Nacimiento</th>
<th>Grupo de Trabajo</th>
<th class="text-center">Bautizado E.S.</th>
<th>Teléfono</th>
<th>Fecha Ingreso</th>
<th class="text-center">Acciones</th>
</tr>
</thead>
<tbody>
@if (!Model.Any())
{
<tr>
<td colspan="8" class="text-center text-muted py-4">
<i class="bi bi-people fs-1 d-block mb-2"></i>
No hay miembros registrados
</td>
</tr>
}
@foreach (var miembro in Model)
{
<tr>
<td class="text-center">
@if (!string.IsNullOrEmpty(miembro.FotoUrl))
{
<img src="/@miembro.FotoUrl" alt="@miembro.NombreCompleto" class="img-thumbnail" style="width: 40px; height: 40px; object-fit: cover;" />
}
else
{
<i class="bi bi-person-circle text-muted" style="font-size: 2rem;"></i>
}
</td>
<td>
<strong>@miembro.NombreCompleto</strong>
</td>
<td>
@if (miembro.FechaNacimiento.HasValue)
{
<span>@miembro.FechaNacimiento.Value.ToString("dd/MM/yyyy")</span>
<br>
<small class="text-muted">
@{
var edad = DateTime.Today.Year - miembro.FechaNacimiento.Value.Year;
if (miembro.FechaNacimiento.Value.ToDateTime(TimeOnly.MinValue) > DateTime.Today.AddYears(-edad))
edad--;
}
@edad años
</small>
}
else
{
<span class="text-muted">-</span>
}
</td>
<td>
@if (!string.IsNullOrEmpty(miembro.GrupoTrabajoNombre))
{
<span class="badge bg-primary">@miembro.GrupoTrabajoNombre</span>
}
else
{
<span class="text-muted">Sin grupo</span>
}
</td>
<td class="text-center">
@if (miembro.BautizadoEspirituSanto)
{
<i class="bi bi-check-circle-fill text-success" title="Sí"></i>
}
else
{
<i class="bi bi-x-circle text-muted" title="No"></i>
}
</td>
<td>
@if (!string.IsNullOrEmpty(miembro.Telefono))
{
<span>@miembro.Telefono</span>
}
else
{
<span class="text-muted">-</span>
}
</td>
<td>
@if (miembro.FechaIngresoCongregacion.HasValue)
{
<span>@miembro.FechaIngresoCongregacion.Value.ToString("dd/MM/yyyy")</span>
}
else
{
<span class="text-muted">-</span>
}
</td>
<td class="text-center">
<a asp-action="Details" asp-route-id="@miembro.Id" class="btn btn-sm btn-outline-primary" title="Ver detalles">
<i class="bi bi-eye"></i>
</a>
<a asp-action="Edit" asp-route-id="@miembro.Id" class="btn btn-sm btn-outline-secondary" title="Editar">
<i class="bi bi-pencil"></i>
</a>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="confirmDelete(@miembro.Id)" title="Eliminar">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<!-- Delete Form -->
<form id="deleteForm" asp-action="Delete" method="post" style="display: none;">
<input type="hidden" name="id" id="deleteId" />
</form>
@section Scripts {
<script>
function confirmDelete(id) {
Swal.fire({
title: '¿Eliminar miembro?',
text: 'Esta acción no se puede deshacer.',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#6c757d',
confirmButtonText: 'Sí, eliminar',
cancelButtonText: 'Cancelar'
}).then((result) => {
if (result.isConfirmed) {
document.getElementById('deleteId').value = id;
document.getElementById('deleteForm').submit();
}
});
}
// Show success/error messages
@if (TempData["SuccessMessage"] != null)
{
<text>
toastr.success('@TempData["SuccessMessage"]');
</text>
}
@if (TempData["ErrorMessage"] != null)
{
<text>
toastr.error('@TempData["ErrorMessage"]');
</text>
}
</script>
}

View File

@@ -80,14 +80,5 @@
<script src="~/js/sweetalert.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('Service Worker registrado', reg))
.catch(err => console.log('Error registrando Service Worker', err));
});
}
</script>
</body>
</html>

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 it is too large Load Diff

View File

@@ -1,20 +0,0 @@
{
"runtimeOptions": {
"tfm": "net9.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "9.0.0"
},
{
"name": "Microsoft.AspNetCore.App",
"version": "9.0.0"
}
],
"configProperties": {
"System.GC.Server": true,
"System.Reflection.NullabilityInfoContext.IsSupported": true,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

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.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+06470a917386b93ccdb385de159f794e71e5ff6e")]
[assembly: System.Reflection.AssemblyProductAttribute("RS_system")]
[assembly: System.Reflection.AssemblyTitleAttribute("RS_system")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@@ -1 +1 @@
a82fb9a90c444dc1c29204a553f5c876f9ce9eee0db0545bdc8b02233475e50c
aa92734f95e484f332f4487ee9aaa64971ef9905e9da71408ff6f5605ac4035e

View File

@@ -80,6 +80,22 @@ build_metadata.AdditionalFiles.CssScope =
build_metadata.AdditionalFiles.TargetPath = Vmlld3MvSG9tZS9Qcml2YWN5LmNzaHRtbA==
build_metadata.AdditionalFiles.CssScope =
[/home/adalberto/RiderProjects/RS_system/RS_system/Views/Miembro/Create.cshtml]
build_metadata.AdditionalFiles.TargetPath = Vmlld3MvTWllbWJyby9DcmVhdGUuY3NodG1s
build_metadata.AdditionalFiles.CssScope =
[/home/adalberto/RiderProjects/RS_system/RS_system/Views/Miembro/Details.cshtml]
build_metadata.AdditionalFiles.TargetPath = Vmlld3MvTWllbWJyby9EZXRhaWxzLmNzaHRtbA==
build_metadata.AdditionalFiles.CssScope =
[/home/adalberto/RiderProjects/RS_system/RS_system/Views/Miembro/Edit.cshtml]
build_metadata.AdditionalFiles.TargetPath = Vmlld3MvTWllbWJyby9FZGl0LmNzaHRtbA==
build_metadata.AdditionalFiles.CssScope =
[/home/adalberto/RiderProjects/RS_system/RS_system/Views/Miembro/Index.cshtml]
build_metadata.AdditionalFiles.TargetPath = Vmlld3MvTWllbWJyby9JbmRleC5jc2h0bWw=
build_metadata.AdditionalFiles.CssScope =
[/home/adalberto/RiderProjects/RS_system/RS_system/Views/Modulo/Create.cshtml]
build_metadata.AdditionalFiles.TargetPath = Vmlld3MvTW9kdWxvL0NyZWF0ZS5jc2h0bWw=
build_metadata.AdditionalFiles.CssScope =

View File

@@ -1 +1 @@
c93850685aba3b2472ab5c5100f37c8d8436ca2084dcf489a247c1ecfde9dd53
1d782e947332c33e79de06f0d0ea62084d0dd7211824c580857a222cbbcd615f

View File

@@ -260,7 +260,6 @@
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/zdzqtnkble-87fc7y1x7t.gz
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/lkdeyc53tx-jfsiqqwiad.gz
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/jymdxbokw3-fnygdjjojd.gz
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/iz5enhq87a-svh9ti7drw.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/staticwebassets.build.json
@@ -274,4 +273,4 @@
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/RS_system.genruntimeconfig.cache
/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/rld9et4i1y-hb39ws7tz0.gz
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/7075gedban-7xh0rp53jd.gz
/home/adalberto/RiderProjects/RS_system/RS_system/obj/Debug/net9.0/compressed/7075gedban-bup8j0n9jm.gz

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 +1 @@
++ew/cxKBDtKi6Z9F/joSinKg+cM8PpuNDDNJtmZcrs=
BwKCOSHdnEBO96l3Ju9M2ODTw4cl5dusn66jn2yexBY=

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,52 @@
-- =====================================================
-- Script: Church Members Module Database Schema (Refactored)
-- Description: Creates tables for work groups and members linked to personas
-- =====================================================
-- Table: grupos_trabajo
CREATE TABLE IF NOT EXISTS public.grupos_trabajo
(
id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
nombre character varying(100) NOT NULL,
descripcion text,
activo boolean NOT NULL DEFAULT true,
creado_en timestamp with time zone NOT NULL DEFAULT NOW(),
actualizado_en timestamp with time zone NOT NULL DEFAULT NOW(),
CONSTRAINT grupos_trabajo_pkey PRIMARY KEY (id)
);
-- Table: miembros
-- Drop if exists to handle the schema change during development
DROP TABLE IF EXISTS public.miembros;
CREATE TABLE IF NOT EXISTS public.miembros
(
id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
persona_id bigint NOT NULL,
bautizado_espiritu_santo boolean NOT NULL DEFAULT false,
fecha_ingreso_congregacion date,
telefono_emergencia character varying(20),
grupo_trabajo_id bigint,
activo boolean NOT NULL DEFAULT true,
eliminado boolean NOT NULL DEFAULT false,
creado_en timestamp with time zone NOT NULL DEFAULT NOW(),
actualizado_en timestamp with time zone NOT NULL DEFAULT NOW(),
creado_por character varying(100),
CONSTRAINT miembros_pkey PRIMARY KEY (id),
CONSTRAINT fk_miembros_persona FOREIGN KEY (persona_id)
REFERENCES public.personas (id),
CONSTRAINT fk_miembros_grupo_trabajo FOREIGN KEY (grupo_trabajo_id)
REFERENCES public.grupos_trabajo (id)
);
-- Indexes for performance
CREATE INDEX IF NOT EXISTS idx_miembros_persona ON public.miembros(persona_id);
CREATE INDEX IF NOT EXISTS idx_miembros_grupo_trabajo ON public.miembros(grupo_trabajo_id);
CREATE INDEX IF NOT EXISTS idx_miembros_activo ON public.miembros(activo, eliminado);
-- Initial work groups data
INSERT INTO public.grupos_trabajo (nombre, descripcion) VALUES
('Concilio Misionero Femenil', 'Grupo de trabajo de mujeres misioneras'),
('Fraternidad de Varones', 'Grupo de trabajo de varones de la iglesia'),
('Embajadores de Cristo', 'Grupo de jóvenes embajadores')
ON CONFLICT DO NOTHING;

View File

@@ -2,7 +2,7 @@
--sidebar-width: 260px;
--sidebar-bg: #1e3a2f;
--sidebar-hover: #284c3e;
--sidebar-active: #4CAF50;
--sidebar-active: #4caf50;
--sidebar-text: #f1f5f9;
--sidebar-text-muted: #a8c5b8;
@@ -11,8 +11,8 @@
--content-bg: #f0f7f4;
--primary-color: #4CAF50;
--primary-hover: #388E3C;
--primary-color: #4caf50;
--primary-hover: #388e3c;
--text-main: #1a2e26;
--text-muted: #5c756d;
@@ -259,3 +259,75 @@ h6 {
margin-left: 0;
}
}
/* Responsive Scaling */
@media (max-width: 992px) {
:root {
--sidebar-width: 220px;
--header-height: 50px;
}
body {
font-size: 0.95rem;
}
.page-container {
padding: 1.5rem;
}
.card-custom {
padding: 1.25rem;
}
.nav-link-custom {
padding: 0.6rem 1rem;
font-size: 0.9rem;
}
h1 {
font-size: 1.75rem;
}
h2 {
font-size: 1.5rem;
}
h3 {
font-size: 1.25rem;
}
h4 {
font-size: 1.1rem;
}
}
@media (max-width: 768px) {
:root {
--header-height: 50px; /* Keep compact header */
}
body {
font-size: 0.9rem;
}
.page-container {
padding: 1rem;
}
.card-custom {
padding: 1rem;
margin-bottom: 1rem;
}
.sidebar-brand {
font-size: 1.1rem;
}
.btn-primary-custom {
padding: 0.4rem 0.8rem;
font-size: 0.9rem;
}
.table-custom th,
.table-custom td {
padding: 0.5rem;
font-size: 0.8rem;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB