This commit is contained in:
2026-02-22 14:38:53 -06:00
parent bec656b105
commit a73de4a4fa
47 changed files with 4290 additions and 3 deletions

View File

@@ -0,0 +1,48 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Rs_system.Models;
/// <summary>
/// Personas o entidades que pueden recibir salidas de diezmos
/// (pastor, tesorero, organismos externos, etc.)
/// </summary>
[Table("diezmo_beneficiarios")]
public class DiezmoBeneficiario
{
[Key]
[Column("id")]
public long Id { get; set; }
[Column("nombre")]
[Required]
[StringLength(150)]
public string Nombre { get; set; } = string.Empty;
[Column("descripcion")]
[StringLength(300)]
public string? Descripcion { 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("creado_por")]
[StringLength(100)]
public string? CreadoPor { get; set; }
[Column("actualizado_en")]
public DateTime ActualizadoEn { get; set; } = DateTime.UtcNow;
[Column("actualizado_por")]
[StringLength(100)]
public string? ActualizadoPor { get; set; }
// Navegación
public virtual ICollection<DiezmoSalida> Salidas { get; set; } = new List<DiezmoSalida>();
}

View File

@@ -0,0 +1,74 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Rs_system.Models;
/// <summary>
/// Agregado raíz del módulo de diezmos.
/// Representa un período/corte de diezmos (la fecha la elige el operador libremente).
/// Un cierre por fecha (UNIQUE en fecha).
/// </summary>
[Table("diezmo_cierres")]
public class DiezmoCierre
{
[Key]
[Column("id")]
public long Id { get; set; }
/// <summary>Fecha del cierre. UNIQUE — no pueden existir dos cierres para el mismo día.</summary>
[Column("fecha")]
[Required]
public DateOnly Fecha { get; set; }
[Column("cerrado")]
public bool Cerrado { get; set; } = false;
[Column("fecha_cierre")]
public DateTime? FechaCierre { get; set; }
[Column("cerrado_por")]
[StringLength(100)]
public string? CerradoPor { get; set; }
[Column("observaciones")]
[StringLength(500)]
public string? Observaciones { get; set; }
// ── Totales calculados (persistidos para consulta rápida en el listado) ──
[Column("total_recibido", TypeName = "numeric(12,2)")]
public decimal TotalRecibido { get; set; } = 0;
[Column("total_cambio", TypeName = "numeric(12,2)")]
public decimal TotalCambio { get; set; } = 0;
[Column("total_neto", TypeName = "numeric(12,2)")]
public decimal TotalNeto { get; set; } = 0;
[Column("total_salidas", TypeName = "numeric(12,2)")]
public decimal TotalSalidas { get; set; } = 0;
[Column("saldo_final", TypeName = "numeric(12,2)")]
public decimal SaldoFinal { get; set; } = 0;
// ── Auditoría ──
[Column("creado_en")]
public DateTime CreadoEn { get; set; } = DateTime.UtcNow;
[Column("creado_por")]
[StringLength(100)]
public string? CreadoPor { get; set; }
[Column("actualizado_en")]
public DateTime ActualizadoEn { get; set; } = DateTime.UtcNow;
[Column("actualizado_por")]
[StringLength(100)]
public string? ActualizadoPor { get; set; }
[Column("eliminado")]
public bool Eliminado { get; set; } = false;
// ── Navegación ──
public virtual ICollection<DiezmoDetalle> Detalles { get; set; } = new List<DiezmoDetalle>();
public virtual ICollection<DiezmoSalida> Salidas { get; set; } = new List<DiezmoSalida>();
}

View File

@@ -0,0 +1,67 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Rs_system.Models;
/// <summary>
/// Diezmo individual aportado por un miembro dentro de un cierre.
/// MontoNeto = MontoEntregado - CambioEntregado (calculado por el sistema).
/// </summary>
[Table("diezmo_detalles")]
public class DiezmoDetalle
{
[Key]
[Column("id")]
public long Id { get; set; }
[Column("diezmo_cierre_id")]
public long DiezmoCierreId { get; set; }
[Column("miembro_id")]
public long MiembroId { get; set; }
/// <summary>Monto físico que el miembro entregó (puede incluir cambio).</summary>
[Column("monto_entregado", TypeName = "numeric(12,2)")]
[Required]
public decimal MontoEntregado { get; set; }
/// <summary>Cambio devuelto al miembro.</summary>
[Column("cambio_entregado", TypeName = "numeric(12,2)")]
public decimal CambioEntregado { get; set; } = 0;
/// <summary>Diezmo neto real = MontoEntregado - CambioEntregado. Calculado por el sistema.</summary>
[Column("monto_neto", TypeName = "numeric(12,2)")]
public decimal MontoNeto { get; set; }
[Column("observaciones")]
[StringLength(300)]
public string? Observaciones { get; set; }
[Column("fecha")]
public DateTime Fecha { get; set; } = DateTime.UtcNow;
// ── Auditoría ──
[Column("creado_por")]
[StringLength(100)]
public string? CreadoPor { get; set; }
[Column("creado_en")]
public DateTime CreadoEn { get; set; } = DateTime.UtcNow;
[Column("actualizado_en")]
public DateTime ActualizadoEn { get; set; } = DateTime.UtcNow;
[Column("actualizado_por")]
[StringLength(100)]
public string? ActualizadoPor { get; set; }
[Column("eliminado")]
public bool Eliminado { get; set; } = false;
// ── Navegación ──
[ForeignKey("DiezmoCierreId")]
public virtual DiezmoCierre DiezmoCierre { get; set; } = null!;
[ForeignKey("MiembroId")]
public virtual Miembro Miembro { get; set; } = null!;
}

View File

@@ -0,0 +1,66 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Rs_system.Models;
/// <summary>
/// Salida de fondos registrada contra un cierre de diezmos.
/// Incluye entregas al pastor, gastos administrativos, misiones, etc.
/// </summary>
[Table("diezmo_salidas")]
public class DiezmoSalida
{
[Key]
[Column("id")]
public long Id { get; set; }
[Column("diezmo_cierre_id")]
public long DiezmoCierreId { get; set; }
[Column("tipo_salida_id")]
public long TipoSalidaId { get; set; }
[Column("beneficiario_id")]
public long? BeneficiarioId { get; set; }
[Column("monto", TypeName = "numeric(12,2)")]
[Required]
public decimal Monto { get; set; }
[Column("concepto")]
[Required]
[StringLength(300)]
public string Concepto { get; set; } = string.Empty;
/// <summary>Correlativo de recibo asignado al momento de generar el comprobante.</summary>
[Column("numero_recibo")]
[StringLength(30)]
public string? NumeroRecibo { get; set; }
[Column("fecha")]
public DateTime Fecha { get; set; } = DateTime.UtcNow;
// ── Auditoría ──
[Column("creado_por")]
[StringLength(100)]
public string? CreadoPor { get; set; }
[Column("creado_en")]
public DateTime CreadoEn { get; set; } = DateTime.UtcNow;
[Column("actualizado_en")]
public DateTime ActualizadoEn { get; set; } = DateTime.UtcNow;
[Column("eliminado")]
public bool Eliminado { get; set; } = false;
// ── Navegación ──
[ForeignKey("DiezmoCierreId")]
public virtual DiezmoCierre DiezmoCierre { get; set; } = null!;
[ForeignKey("TipoSalidaId")]
public virtual DiezmoTipoSalida TipoSalida { get; set; } = null!;
[ForeignKey("BeneficiarioId")]
public virtual DiezmoBeneficiario? Beneficiario { get; set; }
}

View File

@@ -0,0 +1,51 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Rs_system.Models;
/// <summary>
/// Catálogo de tipos de salida del módulo de diezmos
/// (Entrega al Pastor, Gastos Administrativos, Misiones, etc.)
/// </summary>
[Table("diezmo_tipos_salida")]
public class DiezmoTipoSalida
{
[Key]
[Column("id")]
public long Id { get; set; }
[Column("nombre")]
[Required]
[StringLength(100)]
public string Nombre { get; set; } = string.Empty;
[Column("descripcion")]
[StringLength(300)]
public string? Descripcion { get; set; }
/// <summary>
/// Marca este tipo como la entrega oficial al pastor.
/// Permite sugerirlo/forzarlo automáticamente al cerrar con saldo pendiente.
/// </summary>
[Column("es_entrega_pastor")]
public bool EsEntregaPastor { get; set; } = false;
[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("creado_por")]
[StringLength(100)]
public string? CreadoPor { get; set; }
[Column("actualizado_en")]
public DateTime ActualizadoEn { get; set; } = DateTime.UtcNow;
// Navegación
public virtual ICollection<DiezmoSalida> Salidas { get; set; } = new List<DiezmoSalida>();
}

View File

@@ -0,0 +1,30 @@
using System.ComponentModel.DataAnnotations;
namespace Rs_system.Models.ViewModels.Catalogos;
public class TipoSalidaViewModel
{
public long Id { get; set; }
[Required(ErrorMessage = "El nombre es obligatorio")]
[StringLength(100)]
public string Nombre { get; set; } = string.Empty;
[StringLength(300)]
public string? Descripcion { get; set; }
[Display(Name = "Es Entrega Directa a Pastor")]
public bool EsEntregaPastor { get; set; }
}
public class BeneficiarioViewModel
{
public long Id { get; set; }
[Required(ErrorMessage = "El nombre es obligatorio")]
[StringLength(150)]
public string Nombre { get; set; } = string.Empty;
[StringLength(300)]
public string? Descripcion { get; set; }
}

View File

@@ -0,0 +1,84 @@
using System.ComponentModel.DataAnnotations;
namespace Rs_system.Models.ViewModels;
// ─────────────────────────────────────────────────────────────────────────────
// Formulario — Nuevo cierre
// ─────────────────────────────────────────────────────────────────────────────
public class DiezmoCierreCreateViewModel
{
[Required(ErrorMessage = "La fecha es obligatoria.")]
[Display(Name = "Fecha del cierre")]
public DateOnly Fecha { get; set; } = DateOnly.FromDateTime(DateTime.Today);
[Display(Name = "Observaciones")]
[StringLength(500)]
public string? Observaciones { get; set; }
}
// ─────────────────────────────────────────────────────────────────────────────
// Pantalla operativa de detalle del cierre
// ─────────────────────────────────────────────────────────────────────────────
public class DiezmoCierreDetalleViewModel
{
public long Id { get; set; }
public DateOnly Fecha { get; set; }
public bool Cerrado { get; set; }
public string? Observaciones { get; set; }
public string? CerradoPor { get; set; }
public DateTime? FechaCierre { get; set; }
// Totales
public decimal TotalRecibido { get; set; }
public decimal TotalCambio { get; set; }
public decimal TotalNeto { get; set; }
public decimal TotalSalidas { get; set; }
public decimal SaldoFinal { get; set; }
// Datos de detalles
public List<DiezmoDetalleRowViewModel> Detalles { get; set; } = new();
// Datos de salidas
public List<DiezmoSalidaRowViewModel> Salidas { get; set; } = new();
// Formularios embebidos para modales
public DiezmoDetalleFormViewModel FormDetalle { get; set; } = new();
public DiezmoSalidaFormViewModel FormSalida { get; set; } = new();
// Datos de selectores para los modales
public List<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem> MiembrosSelect { get; set; } = new();
public List<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem> TiposSalidaSelect { get; set; } = new();
public List<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem> BeneficiariosSelect { get; set; } = new();
public string EstadoBadge => Cerrado ? "badge bg-secondary" : "badge bg-success";
public string EstadoTexto => Cerrado ? "Cerrado" : "Abierto";
}
// ─────────────────────────────────────────────────────────────────────────────
// Fila de un detalle en la tabla
// ─────────────────────────────────────────────────────────────────────────────
public class DiezmoDetalleRowViewModel
{
public long Id { get; set; }
public long MiembroId { get; set; }
public string NombreMiembro { get; set; } = string.Empty;
public decimal MontoEntregado { get; set; }
public decimal CambioEntregado { get; set; }
public decimal MontoNeto { get; set; }
public string? Observaciones { get; set; }
public DateTime Fecha { get; set; }
}
// ─────────────────────────────────────────────────────────────────────────────
// Fila de una salida en la tabla
// ─────────────────────────────────────────────────────────────────────────────
public class DiezmoSalidaRowViewModel
{
public long Id { get; set; }
public string TipoSalidaNombre { get; set; } = string.Empty;
public string? BeneficiarioNombre { get; set; }
public decimal Monto { get; set; }
public string Concepto { get; set; } = string.Empty;
public string? NumeroRecibo { get; set; }
public DateTime Fecha { get; set; }
}

View File

@@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
namespace Rs_system.Models.ViewModels;
/// <summary>Fila del listado de cierres de diezmos.</summary>
public class DiezmoCierreListViewModel
{
public long Id { get; set; }
public DateOnly Fecha { get; set; }
public bool Cerrado { get; set; }
public decimal TotalRecibido { get; set; }
public decimal TotalNeto { get; set; }
public decimal TotalSalidas { get; set; }
public decimal SaldoFinal { get; set; }
public int NumeroDetalles { get; set; }
public int NumeroSalidas { get; set; }
public string EstadoBadge => Cerrado ? "badge bg-secondary" : "badge bg-success";
public string EstadoTexto => Cerrado ? "Cerrado" : "Abierto";
}

View File

@@ -0,0 +1,28 @@
using System.ComponentModel.DataAnnotations;
namespace Rs_system.Models.ViewModels;
/// <summary>Formulario modal para agregar un diezmo de un miembro.</summary>
public class DiezmoDetalleFormViewModel
{
[Required(ErrorMessage = "Seleccione un miembro.")]
[Display(Name = "Miembro")]
public long MiembroId { get; set; }
[Required(ErrorMessage = "El monto entregado es obligatorio.")]
[Range(0.01, 999999.99, ErrorMessage = "El monto debe ser mayor a 0.")]
[Display(Name = "Monto entregado")]
public decimal MontoEntregado { get; set; }
[Required(ErrorMessage = "El monto del diezmo (neto) es obligatorio.")]
[Range(0.01, 999999.99, ErrorMessage = "El diezmo debe ser mayor a 0.")]
[Display(Name = "Diezmo (Neto)")]
public decimal MontoNeto { get; set; }
// Este campo ahora vendrá como solo-lectura desde el formulario
public decimal CambioEntregado { get; set; } = 0;
[Display(Name = "Observaciones")]
[StringLength(300)]
public string? Observaciones { get; set; }
}

View File

@@ -0,0 +1,24 @@
using System.ComponentModel.DataAnnotations;
namespace Rs_system.Models.ViewModels;
/// <summary>Formulario modal para registrar una salida/entrega de fondos.</summary>
public class DiezmoSalidaFormViewModel
{
[Required(ErrorMessage = "Seleccione el tipo de salida.")]
[Display(Name = "Tipo de salida")]
public long TipoSalidaId { get; set; }
[Display(Name = "Beneficiario")]
public long? BeneficiarioId { get; set; }
[Required(ErrorMessage = "El monto es obligatorio.")]
[Range(0.01, 999999.99, ErrorMessage = "El monto debe ser mayor a 0.")]
[Display(Name = "Monto")]
public decimal Monto { get; set; }
[Required(ErrorMessage = "El concepto es obligatorio.")]
[StringLength(300)]
[Display(Name = "Concepto")]
public string Concepto { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,14 @@
namespace Rs_system.Models.ViewModels;
public class PaginatedViewModel<T>
{
public List<T> Items { get; set; } = new();
public int CurrentPage { get; set; }
public int PageSize { get; set; }
public int TotalItems { get; set; }
public int TotalPages => (int)Math.Ceiling((double)TotalItems / PageSize);
public string? SearchQuery { get; set; }
public bool HasPreviousPage => CurrentPage > 1;
public bool HasNextPage => CurrentPage < TotalPages;
}