first commit

This commit is contained in:
2026-01-10 23:14:51 -06:00
commit 389715b4b4
503 changed files with 98244 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Rs_system.Models.Enums;
namespace Rs_system.Models.ViewModels;
public class AsistenciaCultoFiltroViewModel
{
[Display(Name = "Fecha Desde")]
[DataType(DataType.Date)]
public DateTime? FechaDesde { get; set; }
[Display(Name = "Fecha Hasta")]
[DataType(DataType.Date)]
public DateTime? FechaHasta { get; set; }
[Display(Name = "Tipo de Culto")]
public TipoCulto? TipoCulto { get; set; }
[Display(Name = "Tipo de Conteo")]
public TipoConteo? TipoConteo { get; set; }
public IEnumerable<AsistenciaCulto>? Resultados { get; set; }
}

View File

@@ -0,0 +1,149 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Rs_system.Models.Enums;
namespace Rs_system.Models.ViewModels;
public class AsistenciaCultoViewModel : IValidatableObject
{
public long? Id { get; set; }
[Required(ErrorMessage = "La fecha y hora de inicio es requerida")]
[Display(Name = "Fecha y Hora de Inicio")]
[DataType(DataType.DateTime)]
public DateTime FechaHoraInicio { get; set; } = DateTime.Now;
[Required(ErrorMessage = "El tipo de culto es requerido")]
[Display(Name = "Tipo de Culto")]
public TipoCulto TipoCulto { get; set; }
[Required(ErrorMessage = "El tipo de conteo es requerido")]
[Display(Name = "Tipo de Conteo")]
public TipoConteo TipoConteo { get; set; }
// Campos para TipoConteo.Detallado
[Display(Name = "Hermanas (Concilio Misionero Femenil)")]
[Range(0, 10000, ErrorMessage = "El valor debe estar entre 0 y 10000")]
public int? HermanasMisioneras { get; set; }
[Display(Name = "Hermanos (Fraternidad de Varones)")]
[Range(0, 10000, ErrorMessage = "El valor debe estar entre 0 y 10000")]
public int? HermanosFraternidad { get; set; }
[Display(Name = "Embajadores de Cristo")]
[Range(0, 10000, ErrorMessage = "El valor debe estar entre 0 y 10000")]
public int? EmbajadoresCristo { get; set; }
[Display(Name = "Niños")]
[Range(0, 10000, ErrorMessage = "El valor debe estar entre 0 y 10000")]
public int? Ninos { get; set; }
[Display(Name = "Visitas")]
[Range(0, 10000, ErrorMessage = "El valor debe estar entre 0 y 10000")]
public int? Visitas { get; set; }
[Display(Name = "Amigos")]
[Range(0, 10000, ErrorMessage = "El valor debe estar entre 0 y 10000")]
public int? Amigos { get; set; }
// Campo para TipoConteo.General
[Display(Name = "Adultos en General")]
[Range(0, 10000, ErrorMessage = "El valor debe estar entre 0 y 10000")]
public int? AdultosGeneral { get; set; }
// Campo para TipoConteo.Total
[Display(Name = "Total Presente")]
[Range(0, 10000, ErrorMessage = "El valor debe estar entre 0 y 10000")]
public int? TotalManual { get; set; }
[Display(Name = "Observaciones")]
[StringLength(500, ErrorMessage = "Las observaciones no pueden exceder 500 caracteres")]
public string? Observaciones { get; set; }
// Propiedades calculadas (solo lectura)
[Display(Name = "Total Calculado")]
[ReadOnly(true)]
public int Total
{
get
{
return TipoConteo switch
{
TipoConteo.Detallado => (HermanasMisioneras ?? 0) +
(HermanosFraternidad ?? 0) +
(EmbajadoresCristo ?? 0) +
(Ninos ?? 0) +
(Visitas ?? 0) +
(Amigos ?? 0),
TipoConteo.General => (AdultosGeneral ?? 0) + (Ninos ?? 0),
TipoConteo.Total => TotalManual ?? 0,
_ => 0
};
}
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
// Validar según TipoConteo
switch (TipoConteo)
{
case TipoConteo.Detallado:
// Todos los campos detallados son requeridos
if (!HermanasMisioneras.HasValue)
results.Add(new ValidationResult("El campo 'Hermanas' es requerido para conteo detallado", new[] { nameof(HermanasMisioneras) }));
else if (HermanasMisioneras.Value < 0)
results.Add(new ValidationResult("El campo 'Hermanas' debe ser mayor o igual a 0", new[] { nameof(HermanasMisioneras) }));
if (!HermanosFraternidad.HasValue)
results.Add(new ValidationResult("El campo 'Hermanos' es requerido para conteo detallado", new[] { nameof(HermanosFraternidad) }));
else if (HermanosFraternidad.Value < 0)
results.Add(new ValidationResult("El campo 'Hermanos' debe ser mayor o igual a 0", new[] { nameof(HermanosFraternidad) }));
if (!EmbajadoresCristo.HasValue)
results.Add(new ValidationResult("El campo 'Embajadores' es requerido para conteo detallado", new[] { nameof(EmbajadoresCristo) }));
else if (EmbajadoresCristo.Value < 0)
results.Add(new ValidationResult("El campo 'Embajadores' debe ser mayor o igual a 0", new[] { nameof(EmbajadoresCristo) }));
if (!Ninos.HasValue)
results.Add(new ValidationResult("El campo 'Niños' es requerido para conteo detallado", new[] { nameof(Ninos) }));
else if (Ninos.Value < 0)
results.Add(new ValidationResult("El campo 'Niños' debe ser mayor o igual a 0", new[] { nameof(Ninos) }));
if (!Visitas.HasValue)
results.Add(new ValidationResult("El campo 'Visitas' es requerido para conteo detallado", new[] { nameof(Visitas) }));
else if (Visitas.Value < 0)
results.Add(new ValidationResult("El campo 'Visitas' debe ser mayor o igual a 0", new[] { nameof(Visitas) }));
if (!Amigos.HasValue)
results.Add(new ValidationResult("El campo 'Amigos' es requerido para conteo detallado", new[] { nameof(Amigos) }));
else if (Amigos.Value < 0)
results.Add(new ValidationResult("El campo 'Amigos' debe ser mayor o igual a 0", new[] { nameof(Amigos) }));
break;
case TipoConteo.General:
// AdultosGeneral y Ninos son requeridos
if (!AdultosGeneral.HasValue)
results.Add(new ValidationResult("El campo 'Adultos en General' es requerido para conteo general", new[] { nameof(AdultosGeneral) }));
else if (AdultosGeneral.Value < 0)
results.Add(new ValidationResult("El campo 'Adultos en General' debe ser mayor o igual a 0", new[] { nameof(AdultosGeneral) }));
if (!Ninos.HasValue)
results.Add(new ValidationResult("El campo 'Niños' es requerido para conteo general", new[] { nameof(Ninos) }));
else if (Ninos.Value < 0)
results.Add(new ValidationResult("El campo 'Niños' debe ser mayor o igual a 0", new[] { nameof(Ninos) }));
break;
case TipoConteo.Total:
// Solo TotalManual es requerido
if (!TotalManual.HasValue)
results.Add(new ValidationResult("El campo 'Total Presente' es requerido para conteo total", new[] { nameof(TotalManual) }));
else if (TotalManual.Value < 0)
results.Add(new ValidationResult("El campo 'Total Presente' debe ser mayor o igual a 0", new[] { nameof(TotalManual) }));
break;
}
return results;
}
}

View File

@@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
namespace Rs_system.Models.ViewModels;
public class LoginViewModel
{
[Required(ErrorMessage = "El nombre de usuario es requerido")]
[Display(Name = "Usuario")]
public string NombreUsuario { get; set; } = string.Empty;
[Required(ErrorMessage = "La contraseña es requerida")]
[DataType(DataType.Password)]
[Display(Name = "Contraseña")]
public string Contrasena { get; set; } = string.Empty;
[Display(Name = "Recordarme")]
public bool RecordarMe { get; set; }
}

View File

@@ -0,0 +1,20 @@
using System.Collections.Generic;
namespace Rs_system.Models.ViewModels
{
public class MenuViewModel
{
public List<MenuItem> Items { get; set; } = new List<MenuItem>();
}
public class MenuItem
{
public string Title { get; set; } = string.Empty;
public string? Icon { get; set; }
public string? Url { get; set; }
public bool IsActive { get; set; }
public bool IsGroup { get; set; } // True if it's a module with children
public List<MenuItem> Children { get; set; } = new List<MenuItem>();
public int Order { get; set; }
}
}

View File

@@ -0,0 +1,39 @@
using System.ComponentModel.DataAnnotations;
namespace Rs_system.Models.ViewModels;
public class RegisterViewModel
{
[Required(ErrorMessage = "Los nombres son requeridos")]
[StringLength(100, ErrorMessage = "Máximo 100 caracteres")]
[Display(Name = "Nombres")]
public string Nombres { get; set; } = string.Empty;
[Required(ErrorMessage = "Los apellidos son requeridos")]
[StringLength(100, ErrorMessage = "Máximo 100 caracteres")]
[Display(Name = "Apellidos")]
public string Apellidos { get; set; } = string.Empty;
[Required(ErrorMessage = "El nombre de usuario es requerido")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "El usuario debe tener entre 3 y 50 caracteres")]
[RegularExpression(@"^[a-zA-Z0-9_]+$", ErrorMessage = "Solo letras, números y guiones bajos")]
[Display(Name = "Nombre de Usuario")]
public string NombreUsuario { get; set; } = string.Empty;
[Required(ErrorMessage = "El correo electrónico es requerido")]
[EmailAddress(ErrorMessage = "Correo electrónico inválido")]
[Display(Name = "Correo Electrónico")]
public string Email { get; set; } = string.Empty;
[Required(ErrorMessage = "La contraseña es requerida")]
[StringLength(100, MinimumLength = 6, ErrorMessage = "La contraseña debe tener al menos 6 caracteres")]
[DataType(DataType.Password)]
[Display(Name = "Contraseña")]
public string Contrasena { get; set; } = string.Empty;
[Required(ErrorMessage = "Debe confirmar la contraseña")]
[Compare("Contrasena", ErrorMessage = "Las contraseñas no coinciden")]
[DataType(DataType.Password)]
[Display(Name = "Confirmar Contraseña")]
public string ConfirmarContrasena { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,71 @@
using System.ComponentModel.DataAnnotations;
namespace Rs_system.Models.ViewModels;
/// <summary>
/// ViewModel for creating/editing offering records
/// </summary>
public class RegistroCultoViewModel
{
public long? Id { get; set; }
[Required(ErrorMessage = "La fecha es requerida")]
[Display(Name = "Fecha del Culto")]
[DataType(DataType.Date)]
public DateOnly Fecha { get; set; } = DateOnly.FromDateTime(DateTime.Today);
[Display(Name = "Observaciones")]
[StringLength(500, ErrorMessage = "Las observaciones no pueden exceder 500 caracteres")]
public string? Observaciones { get; set; }
public List<OfrendaItemViewModel> Ofrendas { get; set; } = new();
// Calculated properties for display
public decimal TotalOfrendas => Ofrendas?.Sum(o => o.Monto) ?? 0;
public decimal TotalDescuentos => Ofrendas?.Sum(o => o.TotalDescuentos) ?? 0;
public decimal MontoNeto => TotalOfrendas - TotalDescuentos;
}
/// <summary>
/// ViewModel for an individual offering
/// </summary>
public class OfrendaItemViewModel
{
public long? Id { get; set; }
[Required(ErrorMessage = "El monto es requerido")]
[Display(Name = "Monto")]
[Range(0.01, 999999.99, ErrorMessage = "El monto debe ser mayor a 0")]
[DataType(DataType.Currency)]
public decimal Monto { get; set; }
[Required(ErrorMessage = "El concepto es requerido")]
[Display(Name = "Concepto")]
[StringLength(200, ErrorMessage = "El concepto no puede exceder 200 caracteres")]
public string Concepto { get; set; } = string.Empty;
public List<DescuentoItemViewModel> Descuentos { get; set; } = new();
// Calculated properties
public decimal TotalDescuentos => Descuentos?.Sum(d => d.Monto) ?? 0;
public decimal MontoNeto => Monto - TotalDescuentos;
}
/// <summary>
/// ViewModel for a deduction from an offering
/// </summary>
public class DescuentoItemViewModel
{
public long? Id { get; set; }
[Required(ErrorMessage = "El monto es requerido")]
[Display(Name = "Monto")]
[Range(0.01, 999999.99, ErrorMessage = "El monto debe ser mayor a 0")]
[DataType(DataType.Currency)]
public decimal Monto { get; set; }
[Required(ErrorMessage = "El concepto es requerido")]
[Display(Name = "Concepto")]
[StringLength(200, ErrorMessage = "El concepto no puede exceder 200 caracteres")]
public string Concepto { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,45 @@
using System.ComponentModel.DataAnnotations;
namespace Rs_system.Models.ViewModels;
public class UsuarioViewModel
{
public long? Id { get; set; }
[Required(ErrorMessage = "Los nombres son requeridos")]
[Display(Name = "Nombres")]
public string Nombres { get; set; } = string.Empty;
[Required(ErrorMessage = "Los apellidos son requeridos")]
[Display(Name = "Apellidos")]
public string Apellidos { get; set; } = string.Empty;
[Required(ErrorMessage = "El nombre de usuario es requerido")]
[Display(Name = "Nombre de Usuario")]
[StringLength(50)]
public string NombreUsuario { get; set; } = string.Empty;
[Required(ErrorMessage = "El correo electrónico es requerido")]
[EmailAddress(ErrorMessage = "Correo electrónico inválido")]
[Display(Name = "Email")]
public string Email { get; set; } = string.Empty;
[Display(Name = "Contraseña")]
[DataType(DataType.Password)]
[StringLength(100, MinimumLength = 6, ErrorMessage = "La contraseña debe tener al menos 6 caracteres")]
public string? Contrasena { get; set; }
[Display(Name = "Confirmar Contraseña")]
[DataType(DataType.Password)]
[Compare("Contrasena", ErrorMessage = "Las contraseñas no coinciden")]
public string? ConfirmarContrasena { get; set; }
[Display(Name = "Estado")]
public bool Activo { get; set; } = true;
[Display(Name = "Teléfono")]
public string? Telefono { get; set; }
[Display(Name = "Roles Asignados")]
public List<int> SelectedRoles { get; set; } = new List<int>();
}