This commit is contained in:
2026-02-01 14:28:17 -06:00
parent 700af7ea60
commit 1784131456
109 changed files with 19894 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
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 ArticulosController : Controller
{
private readonly IArticuloService _service;
public ArticulosController(IArticuloService service)
{
_service = service;
}
// GET: Articulos
public async Task<IActionResult> Index(string? search, int? categoriaId, int? ubicacionId, int? estadoId)
{
// Load filter lists
var categorias = await _service.GetCategoriasAsync();
ViewBag.Categorias = new SelectList(categorias.Select(c => new { c.Id, c.Nombre }), "Id", "Nombre", categoriaId);
var ubicaciones = await _service.GetUbicacionesAsync();
ViewBag.Ubicaciones = new SelectList(ubicaciones.Select(u => new { u.Id, u.Nombre }), "Id", "Nombre", ubicacionId);
// Custom Estado SelectList
var estados = await _service.GetEstadosAsync();
ViewBag.Estados = new SelectList(estados.Select(e => new { e.Id, e.Nombre }), "Id", "Nombre", estadoId);
// Keep Search params
ViewBag.CurrentSearch = search ?? "";
ViewBag.CurrentCategoria = categoriaId;
ViewBag.CurrentUbicacion = ubicacionId;
ViewBag.CurrentEstado = estadoId;
var list = await _service.GetAllAsync(search, categoriaId, ubicacionId, estadoId);
return View(list);
}
// GET: Articulos/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null) return NotFound();
var articulo = await _service.GetByIdAsync(id.Value);
if (articulo == null) return NotFound();
return View(articulo);
}
// GET: Articulos/Create
public async Task<IActionResult> Create()
{
await LoadDropdownsAsync();
return View(new ArticuloViewModel());
}
// POST: Articulos/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(ArticuloViewModel viewModel)
{
if (ModelState.IsValid)
{
if (await _service.ExistsCodigoAsync(viewModel.Codigo))
{
ModelState.AddModelError("Codigo", "Ya existe un artículo con este código.");
}
else
{
var createdBy = User.Identity?.Name ?? "Sistema";
var result = await _service.CreateAsync(viewModel, createdBy);
if (result)
{
TempData["SuccessMessage"] = "Artículo registrado exitosamente.";
return RedirectToAction(nameof(Index));
}
ModelState.AddModelError("", "Ocurrió un error al guardar el artículo.");
}
}
await LoadDropdownsAsync();
return View(viewModel);
}
// GET: Articulos/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null) return NotFound();
var articulo = await _service.GetByIdAsync(id.Value);
if (articulo == null) return NotFound();
await LoadDropdownsAsync();
return View(articulo);
}
// POST: Articulos/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, ArticuloViewModel viewModel)
{
if (id != viewModel.Id) return NotFound();
if (ModelState.IsValid)
{
if (await _service.ExistsCodigoAsync(viewModel.Codigo, id))
{
ModelState.AddModelError("Codigo", "Ya existe otro artículo con este código.");
}
else
{
var result = await _service.UpdateAsync(viewModel);
if (result)
{
TempData["SuccessMessage"] = "Artículo actualizado exitosamente.";
return RedirectToAction(nameof(Index));
}
ModelState.AddModelError("", "No se pudo actualizar el artículo.");
}
}
await LoadDropdownsAsync();
return View(viewModel);
}
// POST: Articulos/Delete/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id)
{
var result = await _service.DeleteAsync(id);
if (result)
{
TempData["SuccessMessage"] = "Artículo eliminado exitosamente.";
}
else
{
TempData["ErrorMessage"] = "No se pudo eliminado el artículo.";
}
return RedirectToAction(nameof(Index));
}
private async Task LoadDropdownsAsync()
{
var categorias = await _service.GetCategoriasAsync();
ViewBag.Categorias = new SelectList(categorias.Select(c => new { c.Id, c.Nombre }), "Id", "Nombre");
var ubicaciones = await _service.GetUbicacionesAsync();
ViewBag.Ubicaciones = new SelectList(ubicaciones.Select(u => new { u.Id, u.Nombre }), "Id", "Nombre");
var estados = await _service.GetEstadosAsync();
ViewBag.Estados = new SelectList(estados.Select(e => new { e.Id, e.Nombre }), "Id", "Nombre");
}
}

View File

@@ -0,0 +1,125 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Rs_system.Models;
using Rs_system.Services;
namespace Rs_system.Controllers;
[Authorize]
public class CategoriasController : Controller
{
private readonly ICategoriaService _service;
public CategoriasController(ICategoriaService service)
{
_service = service;
}
// GET: Categorias
public async Task<IActionResult> Index()
{
var list = await _service.GetAllAsync();
return View(list);
}
// GET: Categorias/Create
public IActionResult Create()
{
return View();
}
// POST: Categorias/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Nombre,Descripcion,Activo")] Categoria categoria)
{
if (string.IsNullOrWhiteSpace(categoria.Nombre))
{
ModelState.AddModelError("Nombre", "El nombre es obligatorio.");
}
if (ModelState.IsValid)
{
if (await _service.ExistsAsync(categoria.Nombre))
{
ModelState.AddModelError("Nombre", "Ya existe una categoría con ese nombre.");
return View(categoria);
}
categoria.CreadoPor = User.Identity?.Name ?? "Sistema";
var result = await _service.CreateAsync(categoria);
if (result)
{
TempData["SuccessMessage"] = "Categoría creada exitosamente.";
return RedirectToAction(nameof(Index));
}
ModelState.AddModelError("", "Ocurrió un error al guardar los datos.");
}
return View(categoria);
}
// GET: Categorias/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null) return NotFound();
var categoria = await _service.GetByIdAsync(id.Value);
if (categoria == null) return NotFound();
return View(categoria);
}
// POST: Categorias/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Nombre,Descripcion,Activo")] Categoria categoria)
{
if (id != categoria.Id) return NotFound();
if (string.IsNullOrWhiteSpace(categoria.Nombre))
{
ModelState.AddModelError("Nombre", "El nombre es obligatorio.");
}
if (ModelState.IsValid)
{
if (await _service.ExistsAsync(categoria.Nombre, id))
{
ModelState.AddModelError("Nombre", "Ya existe otra categoría con ese nombre.");
return View(categoria);
}
var result = await _service.UpdateAsync(categoria);
if (result)
{
TempData["SuccessMessage"] = "Categoría actualizada exitosamente.";
return RedirectToAction(nameof(Index));
}
ModelState.AddModelError("", "No se pudo actualizar la categoría o no fue encontrada.");
}
return View(categoria);
}
// POST: Categorias/Delete/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id)
{
var result = await _service.DeleteAsync(id);
if (result)
{
TempData["SuccessMessage"] = "Categoría eliminada exitosamente.";
}
else
{
TempData["ErrorMessage"] = "No se pudo eliminar la categoría.";
}
return RedirectToAction(nameof(Index));
}
}

View File

@@ -0,0 +1,221 @@
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 ColaboracionController : Controller
{
private readonly IColaboracionService _colaboracionService;
private readonly IMiembroService _miembroService;
public ColaboracionController(
IColaboracionService colaboracionService,
IMiembroService miembroService)
{
_colaboracionService = colaboracionService;
_miembroService = miembroService;
}
// GET: Colaboracion
public async Task<IActionResult> Index()
{
try
{
var colaboraciones = await _colaboracionService.GetColaboracionesRecientesAsync();
return View(colaboraciones);
}
catch (Exception ex)
{
TempData["Error"] = $"Error al cargar colaboraciones: {ex.Message}";
return View(new List<Models.Colaboracion>());
}
}
// GET: Colaboracion/Create
public async Task<IActionResult> Create()
{
try
{
var viewModel = new RegistrarColaboracionViewModel
{
MesInicial = DateTime.Now.Month,
AnioInicial = DateTime.Now.Year,
MesFinal = DateTime.Now.Month,
AnioFinal = DateTime.Now.Year,
MontoTotal = 0,
TiposDisponibles = await _colaboracionService.GetTiposActivosAsync()
};
await CargarMiembrosAsync();
return View(viewModel);
}
catch (Exception ex)
{
TempData["Error"] = $"Error al cargar formulario: {ex.Message}";
return RedirectToAction(nameof(Index));
}
}
// POST: Colaboracion/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(RegistrarColaboracionViewModel model)
{
if (ModelState.IsValid)
{
try
{
var registradoPor = User.Identity?.Name ?? "Sistema";
await _colaboracionService.RegistrarColaboracionAsync(model, registradoPor);
TempData["Success"] = "Colaboración registrada exitosamente";
return RedirectToAction(nameof(Index));
}
catch (Exception ex)
{
ModelState.AddModelError("", $"Error al registrar: {ex.Message}");
}
}
// Recargar datos para la vista
model.TiposDisponibles = await _colaboracionService.GetTiposActivosAsync();
await CargarMiembrosAsync();
return View(model);
}
// GET: Colaboracion/Details/5
public async Task<IActionResult> Details(long id)
{
try
{
var colaboracion = await _colaboracionService.GetColaboracionByIdAsync(id);
if (colaboracion == null)
{
TempData["Error"] = "Colaboración no encontrada";
return RedirectToAction(nameof(Index));
}
return View(colaboracion);
}
catch (Exception ex)
{
TempData["Error"] = $"Error al cargar detalle: {ex.Message}";
return RedirectToAction(nameof(Index));
}
}
// GET: Colaboracion/Reportes
public IActionResult Reportes()
{
ViewBag.FechaInicio = DateTime.Now.Date;
ViewBag.FechaFin = DateTime.Now.Date;
return View();
}
// POST: Colaboracion/GenerarReporte
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> GenerarReporte(DateTime fechaInicio, DateTime fechaFin)
{
try
{
// Ajustar fecha fin para incluir todo el día
var fechaFinAjustada = fechaFin.Date.AddDays(1).AddSeconds(-1);
var reporte = await _colaboracionService.GenerarReportePorFechasAsync(
fechaInicio.Date,
fechaFinAjustada);
return View("Reporte", reporte);
}
catch (Exception ex)
{
TempData["Error"] = $"Error al generar reporte: {ex.Message}";
return RedirectToAction(nameof(Reportes));
}
}
// GET: Colaboracion/EstadoCuenta/5
public async Task<IActionResult> EstadoCuenta(long id)
{
try
{
var estado = await _colaboracionService.GenerarEstadoCuentaAsync(id);
return View(estado);
}
catch (Exception ex)
{
TempData["Error"] = $"Error al generar estado de cuenta: {ex.Message}";
return RedirectToAction(nameof(Index));
}
}
// GET: Colaboracion/BuscarMiembros?termino=juan
[HttpGet]
public async Task<IActionResult> BuscarMiembros(string termino)
{
if (string.IsNullOrWhiteSpace(termino) || termino.Length < 2)
{
return Json(new List<object>());
}
try
{
var miembros = await _miembroService.GetAllAsync();
var resultados = miembros
.Where(m =>
m.Nombres.Contains(termino, StringComparison.OrdinalIgnoreCase) ||
m.Apellidos.Contains(termino, StringComparison.OrdinalIgnoreCase) ||
$"{m.Nombres} {m.Apellidos}".Contains(termino, StringComparison.OrdinalIgnoreCase))
.Take(10)
.Select(m => new
{
id = m.Id,
text = $"{m.Nombres} {m.Apellidos}",
telefono = m.Telefono
})
.ToList();
return Json(resultados);
}
catch (Exception ex)
{
return Json(new List<object>());
}
}
// GET: Colaboracion/ObtenerUltimosPagos?miembroId=5
[HttpGet]
public async Task<IActionResult> ObtenerUltimosPagos(long miembroId)
{
try
{
var ultimosPagos = await _colaboracionService.GetUltimosPagosPorMiembroAsync(miembroId);
return Json(ultimosPagos);
}
catch (Exception ex)
{
return Json(new List<object>());
}
}
// Helper methods
private async Task CargarMiembrosAsync()
{
var miembros = await _miembroService.GetAllAsync();
ViewBag.Miembros = new SelectList(
miembros.Select(m => new
{
Id = m.Id,
NombreCompleto = $"{m.Nombres} {m.Apellidos}"
}),
"Id",
"NombreCompleto"
);
}
}

View File

@@ -0,0 +1,127 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Rs_system.Models;
using Rs_system.Services;
using Rs_system.Data;
using Microsoft.EntityFrameworkCore;
namespace Rs_system.Controllers;
[Authorize]
public class ContabilidadController : Controller
{
private readonly IContabilidadService _contabilidadService;
private readonly IMiembroService _miembroService;
private readonly ApplicationDbContext _context;
public ContabilidadController(IContabilidadService contabilidadService, IMiembroService miembroService, ApplicationDbContext context)
{
_contabilidadService = contabilidadService;
_miembroService = miembroService;
_context = context;
}
[HttpGet]
public async Task<IActionResult> Index(long? grupoId)
{
var grupos = await _miembroService.GetGruposTrabajoAsync();
ViewBag.Grupos = new SelectList(grupos.Select(g => new { g.Id, g.Nombre }), "Id", "Nombre", grupoId);
List<ReporteMensualContable> reportes = new();
if (grupoId.HasValue)
{
reportes = await _contabilidadService.ListarReportesPorGrupoAsync(grupoId.Value);
}
ViewBag.GrupoId = grupoId;
return View(reportes);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AbrirMes(long grupoId, int mes, int anio)
{
try
{
var reporte = await _contabilidadService.ObtenerOCrearReporteMensualAsync(grupoId, mes, anio);
TempData["Success"] = $"Reporte de {reporte.NombreMes} {anio} abierto correctamente.";
return RedirectToAction(nameof(RegistroMensual), new { id = reporte.Id });
}
catch (Exception ex)
{
TempData["Error"] = "Error al abrir el mes: " + ex.Message;
return RedirectToAction(nameof(Index), new { grupoId });
}
}
[HttpGet]
public async Task<IActionResult> RegistroMensual(long id)
{
var reporte = await _context.ReportesMensualesContables
.Include(r => r.GrupoTrabajo)
.Include(r => r.Registros)
.FirstOrDefaultAsync(r => r.Id == id);
if (reporte == null) return NotFound();
ViewBag.SaldoActual = await _contabilidadService.CalcularSaldoActualAsync(id);
return View(reporte);
}
[HttpPost]
public async Task<IActionResult> GuardarBulk([FromBody] BulkSaveRequest request)
{
if (request == null || request.ReporteId <= 0) return BadRequest("Solicitud inválida.");
var registros = request.Registros.Select(r => new ContabilidadRegistro
{
Id = r.Id,
Tipo = r.Tipo,
Monto = r.Monto,
Fecha = DateTime.SpecifyKind(r.Fecha, DateTimeKind.Utc),
Descripcion = r.Descripcion ?? ""
}).ToList();
var success = await _contabilidadService.GuardarRegistrosBulkAsync(request.ReporteId, registros);
if (success)
{
var nuevoSaldo = await _contabilidadService.CalcularSaldoActualAsync(request.ReporteId);
return Json(new { success = true, saldo = nuevoSaldo });
}
return Json(new { success = false, message = "Error al guardar los registros. Verifique que el mes no esté cerrado." });
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CerrarMes(long id)
{
var success = await _contabilidadService.CerrarReporteAsync(id);
if (success)
{
TempData["Success"] = "El reporte ha sido cerrado. Ya no se pueden realizar cambios.";
}
else
{
TempData["Error"] = "No se pudo cerrar el reporte.";
}
return RedirectToAction(nameof(RegistroMensual), new { id });
}
// Helper classes for AJAX
public class BulkSaveRequest
{
public long ReporteId { get; set; }
public List<RegistroInput> Registros { get; set; } = new();
}
public class RegistroInput
{
public long Id { get; set; }
public TipoMovimientoContable Tipo { get; set; }
public decimal Monto { get; set; }
public DateTime Fecha { get; set; }
public string? Descripcion { get; set; }
}
}

View File

@@ -0,0 +1,319 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Rs_system.Models;
using Rs_system.Services;
using Rs_system.Data;
using Microsoft.EntityFrameworkCore;
namespace Rs_system.Controllers;
[Authorize]
public class ContabilidadGeneralController : Controller
{
private readonly IContabilidadGeneralService _contabilidadService;
private readonly ApplicationDbContext _context;
private readonly IFileStorageService _fileStorageService;
public ContabilidadGeneralController(IContabilidadGeneralService contabilidadService, ApplicationDbContext context, IFileStorageService fileStorageService)
{
_contabilidadService = contabilidadService;
_context = context;
_fileStorageService = fileStorageService;
}
// ==================== Vista Principal ====================
[HttpGet]
public async Task<IActionResult> Index(int? anio)
{
var anioActual = anio ?? DateTime.Now.Year;
ViewBag.Anio = anioActual;
// Generar lista de años disponibles
var anios = Enumerable.Range(DateTime.Now.Year - 5, 10).Reverse();
ViewBag.Anios = new SelectList(anios);
var reportes = await _contabilidadService.ListarReportesAsync(anioActual);
return View(reportes);
}
// ==================== Abrir/Crear Reporte Mensual ====================
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AbrirMes(int mes, int anio)
{
try
{
var reporte = await _contabilidadService.ObtenerOCrearReporteMensualAsync(mes, anio);
TempData["Success"] = $"Reporte de {reporte.NombreMes} {anio} abierto correctamente.";
return RedirectToAction(nameof(RegistroMensual), new { id = reporte.Id });
}
catch (Exception ex)
{
TempData["Error"] = "Error al abrir el mes: " + ex.Message;
return RedirectToAction(nameof(Index));
}
}
// ==================== Registro Mensual (Excel-like) ====================
[HttpGet]
public async Task<IActionResult> RegistroMensual(long id)
{
var reporte = await _context.ReportesMensualesGenerales
.Include(r => r.Movimientos)
.ThenInclude(m => m.CategoriaIngreso)
.Include(r => r.Movimientos)
.ThenInclude(m => m.CategoriaEgreso)
.FirstOrDefaultAsync(r => r.Id == id);
if (reporte == null) return NotFound();
ViewBag.SaldoActual = await _contabilidadService.CalcularSaldoActualAsync(id);
ViewBag.CategoriasIngreso = await _contabilidadService.ObtenerCategoriasIngresoAsync();
ViewBag.CategoriasEgreso = await _contabilidadService.ObtenerCategoriasEgresoAsync();
return View(reporte);
}
// ==================== Guardar Movimientos Bulk (AJAX) ====================
[HttpPost]
public async Task<IActionResult> GuardarBulk([FromBody] BulkSaveRequest request)
{
if (request == null || request.ReporteId <= 0)
return BadRequest("Solicitud inválida.");
var movimientos = request.Movimientos.Select(m => new MovimientoGeneral
{
Id = m.Id,
Tipo = m.Tipo,
CategoriaIngresoId = m.CategoriaIngresoId,
CategoriaEgresoId = m.CategoriaEgresoId,
Monto = m.Monto,
Fecha = DateTime.SpecifyKind(m.Fecha, DateTimeKind.Utc),
Descripcion = m.Descripcion ?? "",
NumeroComprobante = m.NumeroComprobante
}).ToList();
var success = await _contabilidadService.GuardarMovimientosBulkAsync(request.ReporteId, movimientos);
if (success)
{
var nuevoSaldo = await _contabilidadService.CalcularSaldoActualAsync(request.ReporteId);
return Json(new { success = true, saldo = nuevoSaldo });
}
return Json(new { success = false, message = "Error al guardar los movimientos. Verifique que el mes no esté cerrado." });
}
// ==================== Cerrar Mes ====================
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CerrarMes(long id)
{
var success = await _contabilidadService.CerrarReporteAsync(id);
if (success)
{
TempData["Success"] = "El reporte ha sido cerrado. Ya no se pueden realizar cambios.";
}
else
{
TempData["Error"] = "No se pudo cerrar el reporte.";
}
return RedirectToAction(nameof(RegistroMensual), new { id });
}
// ==================== Consolidado Mensual ====================
[HttpGet]
public async Task<IActionResult> Consolidado(long id)
{
var reporte = await _context.ReportesMensualesGenerales
.FirstOrDefaultAsync(r => r.Id == id);
if (reporte == null) return NotFound();
ViewBag.ConsolidadoIngresos = await _contabilidadService.ObtenerConsolidadoIngresosAsync(id);
ViewBag.ConsolidadoEgresos = await _contabilidadService.ObtenerConsolidadoEgresosAsync(id);
ViewBag.SaldoActual = await _contabilidadService.CalcularSaldoActualAsync(id);
return View(reporte);
}
// ==================== Gestión de Categorías ====================
[HttpGet]
public async Task<IActionResult> GestionCategorias()
{
var categoriasIngreso = await _context.CategoriasIngreso
.OrderBy(c => c.Nombre)
.ToListAsync();
var categoriasEgreso = await _context.CategoriasEgreso
.OrderBy(c => c.Nombre)
.ToListAsync();
ViewBag.CategoriasIngreso = categoriasIngreso;
ViewBag.CategoriasEgreso = categoriasEgreso;
return View();
}
// ==================== CRUD Categorías Ingreso ====================
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CrearCategoriaIngreso(CategoriaIngreso categoria)
{
if (ModelState.IsValid)
{
await _contabilidadService.CrearCategoriaIngresoAsync(categoria);
TempData["Success"] = "Categoría de ingreso creada exitosamente.";
}
else
{
TempData["Error"] = "Error al crear la categoría.";
}
return RedirectToAction(nameof(GestionCategorias));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditarCategoriaIngreso(CategoriaIngreso categoria)
{
if (ModelState.IsValid)
{
var success = await _contabilidadService.ActualizarCategoriaIngresoAsync(categoria);
TempData[success ? "Success" : "Error"] = success
? "Categoría actualizada exitosamente."
: "Error al actualizar la categoría.";
}
return RedirectToAction(nameof(GestionCategorias));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EliminarCategoriaIngreso(long id)
{
var success = await _contabilidadService.EliminarCategoriaIngresoAsync(id);
TempData[success ? "Success" : "Error"] = success
? "Categoría eliminada exitosamente."
: "Error al eliminar la categoría.";
return RedirectToAction(nameof(GestionCategorias));
}
// ==================== CRUD Categorías Egreso ====================
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CrearCategoriaEgreso(CategoriaEgreso categoria)
{
if (ModelState.IsValid)
{
await _contabilidadService.CrearCategoriaEgresoAsync(categoria);
TempData["Success"] = "Categoría de egreso creada exitosamente.";
}
else
{
TempData["Error"] = "Error al crear la categoría.";
}
return RedirectToAction(nameof(GestionCategorias));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditarCategoriaEgreso(CategoriaEgreso categoria)
{
if (ModelState.IsValid)
{
var success = await _contabilidadService.ActualizarCategoriaEgresoAsync(categoria);
TempData[success ? "Success" : "Error"] = success
? "Categoría actualizada exitosamente."
: "Error al actualizar la categoría.";
}
return RedirectToAction(nameof(GestionCategorias));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EliminarCategoriaEgreso(long id)
{
var success = await _contabilidadService.EliminarCategoriaEgresoAsync(id);
TempData[success ? "Success" : "Error"] = success
? "Categoría eliminada exitosamente."
: "Error al eliminar la categoría.";
return RedirectToAction(nameof(GestionCategorias));
}
// ==================== Helper Classes for AJAX ====================
public class BulkSaveRequest
{
public long ReporteId { get; set; }
public List<MovimientoInput> Movimientos { get; set; } = new();
}
public class MovimientoInput
{
public long Id { get; set; }
public int Tipo { get; set; }
public long? CategoriaIngresoId { get; set; }
public long? CategoriaEgresoId { get; set; }
public decimal Monto { get; set; }
public DateTime Fecha { get; set; }
public string? Descripcion { get; set; }
public string? NumeroComprobante { get; set; }
}
// ==================== Adjuntos ====================
[HttpGet]
public async Task<IActionResult> ObtenerAdjuntos(long movimientoId)
{
var adjuntos = await _contabilidadService.ObtenerAdjuntosMovimientoAsync(movimientoId);
return Json(adjuntos.Select(a => new {
id = a.Id,
nombre = a.NombreArchivo,
url = _fileStorageService.GetFileUrl(a.RutaArchivo),
tipo = a.TipoContenido,
fecha = a.FechaSubida.ToLocalTime().ToString("g")
}));
}
[HttpPost]
public async Task<IActionResult> SubirAdjunto(long movimientoId, List<IFormFile> archivos)
{
if (movimientoId <= 0 || archivos == null || !archivos.Any())
return BadRequest("Datos inválidos.");
int count = 0;
foreach (var archivo in archivos)
{
if (archivo.Length > 0)
{
// El usuario solicitó guardar en uploads/miembros
var ruta = await _fileStorageService.SaveFileAsync(archivo, "miembros");
if (!string.IsNullOrEmpty(ruta))
{
await _contabilidadService.CrearAdjuntoAsync(movimientoId, archivo.FileName, ruta, archivo.ContentType);
count++;
}
}
}
return Json(new { success = true, count = count, message = $"{count} archivos subidos correctamente." });
}
[HttpPost]
public async Task<IActionResult> EliminarAdjunto(long id)
{
// Primero obtener para borrar el archivo físico si es necesario (opcional, aquí solo borramos registro BD)
// O idealmente el servicio se encarga. Por ahora solo borramos BD.
var success = await _contabilidadService.EliminarAdjuntoAsync(id);
return Json(new { success = success });
}
}

View File

@@ -0,0 +1,125 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Rs_system.Models;
using Rs_system.Services;
namespace Rs_system.Controllers;
[Authorize]
public class EstadosController : Controller
{
private readonly IEstadoArticuloService _service;
public EstadosController(IEstadoArticuloService service)
{
_service = service;
}
// GET: Estados
public async Task<IActionResult> Index()
{
var list = await _service.GetAllAsync();
return View(list);
}
// GET: Estados/Create
public IActionResult Create()
{
return View();
}
// POST: Estados/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Nombre,Descripcion,Color,Activo")] EstadoArticulo estado)
{
if (string.IsNullOrWhiteSpace(estado.Nombre))
{
ModelState.AddModelError("Nombre", "El nombre es obligatorio.");
}
if (ModelState.IsValid)
{
if (await _service.ExistsAsync(estado.Nombre))
{
ModelState.AddModelError("Nombre", "Ya existe un estado con ese nombre.");
return View(estado);
}
estado.CreadoPor = User.Identity?.Name ?? "Sistema";
var result = await _service.CreateAsync(estado);
if (result)
{
TempData["SuccessMessage"] = "Estado creado exitosamente.";
return RedirectToAction(nameof(Index));
}
ModelState.AddModelError("", "Ocurrió un error al guardar los datos.");
}
return View(estado);
}
// GET: Estados/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null) return NotFound();
var estado = await _service.GetByIdAsync(id.Value);
if (estado == null) return NotFound();
return View(estado);
}
// POST: Estados/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Nombre,Descripcion,Color,Activo")] EstadoArticulo estado)
{
if (id != estado.Id) return NotFound();
if (string.IsNullOrWhiteSpace(estado.Nombre))
{
ModelState.AddModelError("Nombre", "El nombre es obligatorio.");
}
if (ModelState.IsValid)
{
if (await _service.ExistsAsync(estado.Nombre, id))
{
ModelState.AddModelError("Nombre", "Ya existe otro estado con ese nombre.");
return View(estado);
}
var result = await _service.UpdateAsync(estado);
if (result)
{
TempData["SuccessMessage"] = "Estado actualizado exitosamente.";
return RedirectToAction(nameof(Index));
}
ModelState.AddModelError("", "No se pudo actualizar el estado o no fue encontrado.");
}
return View(estado);
}
// POST: Estados/Delete/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id)
{
var result = await _service.DeleteAsync(id);
if (result)
{
TempData["SuccessMessage"] = "Estado eliminado exitosamente.";
}
else
{
TempData["ErrorMessage"] = "No se pudo eliminar el estado.";
}
return RedirectToAction(nameof(Index));
}
}

View File

@@ -0,0 +1,190 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Rs_system.Models;
using Rs_system.Services;
namespace Rs_system.Controllers;
[Authorize]
public class MovimientosInventarioController : Controller
{
private readonly IMovimientoService _movimientoService;
private readonly IArticuloService _articuloService;
private readonly IUbicacionService _ubicacionService;
private readonly IEstadoArticuloService _estadoService;
private readonly IPrestamoService _prestamoService;
public MovimientosInventarioController(
IMovimientoService movimientoService,
IArticuloService articuloService,
IUbicacionService ubicacionService,
IEstadoArticuloService estadoService,
IPrestamoService prestamoService)
{
_movimientoService = movimientoService;
_articuloService = articuloService;
_ubicacionService = ubicacionService;
_estadoService = estadoService;
_prestamoService = prestamoService;
}
// GET: MovimientosInventario
public async Task<IActionResult> Index()
{
var historial = await _movimientoService.GetHistorialGeneralAsync(50); // Limit 50 for performance
return View(historial);
}
// GET: MovimientosInventario/Create
// This is the "Wizard" or "Action Selector"
public async Task<IActionResult> Create(int? articuloId)
{
if (articuloId.HasValue)
{
var articulo = await _articuloService.GetByIdAsync(articuloId.Value);
if (articulo == null) return NotFound();
ViewBag.ArticuloId = articulo.Id;
ViewBag.ArticuloNombre = $"{articulo.Codigo} - {articulo.Nombre}";
ViewBag.UbicacionActual = articulo.UbicacionNombre;
ViewBag.EstadoActual = articulo.EstadoNombre;
ViewBag.TipoControl = articulo.TipoControl; // "UNITARIO" or "LOTE"
ViewBag.CantidadGlobal = articulo.CantidadGlobal; // For LOTE validation?
}
ViewBag.Articulos = new SelectList((await _articuloService.GetAllAsync()).Select(x => new { x.Id, Nombre = $"{x.Codigo} - {x.Nombre}" }), "Id", "Nombre", articuloId);
ViewBag.Ubicaciones = new SelectList(await _ubicacionService.GetAllAsync(), "Id", "Nombre");
ViewBag.Estados = new SelectList(await _estadoService.GetAllAsync(), "Id", "Nombre");
return View();
}
// POST: MovimientosInventario/RegistrarTraslado
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RegistrarTraslado(int articuloId, int nuevaUbicacionId, string observacion, int cantidad = 1)
{
var usuario = User.Identity?.Name ?? "Sistema";
// Use the new Quantity-Aware method
var result = await _movimientoService.RegistrarTrasladoCantidadAsync(articuloId, nuevaUbicacionId, cantidad, observacion, usuario);
if (result)
{
TempData["SuccessMessage"] = "Traslado registrado correctamente.";
return RedirectToAction(nameof(Index));
}
TempData["ErrorMessage"] = "Error al registrar el traslado. Verifique stock o campos.";
return RedirectToAction(nameof(Create), new { articuloId });
}
// POST: MovimientosInventario/RegistrarBaja
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RegistrarBaja(int articuloId, string motivo, int cantidad = 1)
{
if (string.IsNullOrWhiteSpace(motivo))
{
TempData["ErrorMessage"] = "Debe especificar un motivo para la baja.";
return RedirectToAction(nameof(Create), new { articuloId });
}
var usuario = User.Identity?.Name ?? "Sistema";
var result = await _movimientoService.RegistrarBajaCantidadAsync(articuloId, cantidad, motivo, usuario);
if (result)
{
TempData["SuccessMessage"] = "Baja registrada correctamente.";
return RedirectToAction(nameof(Index));
}
TempData["ErrorMessage"] = "Error al registrar la baja.";
return RedirectToAction(nameof(Create), new { articuloId });
}
// POST: MovimientosInventario/RegistrarCambioEstado
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RegistrarCambioEstado(int articuloId, int nuevoEstadoId, string observacion)
{
var usuario = User.Identity?.Name ?? "Sistema";
var result = await _movimientoService.RegistrarCambioEstadoAsync(articuloId, nuevoEstadoId, observacion, usuario);
if (result)
{
TempData["SuccessMessage"] = "Cambio de estado registrado correctamento.";
return RedirectToAction(nameof(Index));
}
TempData["ErrorMessage"] = "Error al registrar el cambio de estado. Verifique que el estado sea diferente al actual.";
return RedirectToAction(nameof(Create), new { articuloId });
}
// POST: MovimientosInventario/RegistrarPrestamo
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RegistrarPrestamo(int articuloId, int cantidad, string personaNombre, string personaIdentificacion, DateTime? fechaDevolucionEstimada, string observacion)
{
if (string.IsNullOrWhiteSpace(personaNombre))
{
TempData["ErrorMessage"] = "Debe especificar el nombre de la persona a quien se presta el artículo.";
return RedirectToAction(nameof(Create), new { articuloId });
}
var usuario = User.Identity?.Name ?? "Sistema";
var result = await _prestamoService.RegistrarPrestamoAsync(articuloId, cantidad, personaNombre, personaIdentificacion, fechaDevolucionEstimada, observacion, usuario);
if (result)
{
TempData["SuccessMessage"] = "Préstamo registrado correctamente.";
return RedirectToAction(nameof(Index));
}
TempData["ErrorMessage"] = "Error al registrar el préstamo. Verifique stock disponible.";
return RedirectToAction(nameof(Create), new { articuloId });
}
// GET: MovimientosInventario/PrestamosActivos
public async Task<IActionResult> PrestamosActivos()
{
var prestamosActivos = await _prestamoService.GetPrestamosActivosAsync();
return View(prestamosActivos);
}
// POST: MovimientosInventario/RegistrarDevolucion
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RegistrarDevolucion(long prestamoId, string observacion)
{
var usuario = User.Identity?.Name ?? "Sistema";
var result = await _prestamoService.RegistrarDevolucionAsync(prestamoId, observacion, usuario);
if (result)
{
TempData["SuccessMessage"] = "Devolución registrada correctamente.";
return RedirectToAction(nameof(PrestamosActivos));
}
TempData["ErrorMessage"] = "Error al registrar la devolución.";
return RedirectToAction(nameof(PrestamosActivos));
}
// POST: MovimientosInventario/RegistrarEntrada
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RegistrarEntrada(int articuloId, int cantidad, string observacion)
{
var usuario = User.Identity?.Name ?? "Sistema";
var result = await _movimientoService.RegistrarEntradaCantidadAsync(articuloId, cantidad, observacion, usuario);
if (result)
{
TempData["SuccessMessage"] = "Entrada de inventario registrada correctamente.";
return RedirectToAction(nameof(Index));
}
TempData["ErrorMessage"] = "Error al registrar la entrada de inventario.";
return RedirectToAction(nameof(Create), new { articuloId });
}
}

View File

@@ -0,0 +1,168 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Rs_system.Data;
using Rs_system.Models;
using Rs_system.Services;
namespace Rs_system.Controllers;
[Authorize]
public class TipoColaboracionController : Controller
{
private readonly IColaboracionService _colaboracionService;
private readonly ApplicationDbContext _context;
public TipoColaboracionController(IColaboracionService colaboracionService, ApplicationDbContext context)
{
_colaboracionService = colaboracionService;
_context = context;
}
// GET: TipoColaboracion
public async Task<IActionResult> Index()
{
try
{
var tipos = await _context.TiposColaboracion
.OrderBy(t => t.Orden)
.ToListAsync();
return View(tipos);
}
catch (Exception ex)
{
TempData["Error"] = $"Error al cargar tipos: {ex.Message}";
return View(new List<TipoColaboracion>());
}
}
// GET: TipoColaboracion/Create
public IActionResult Create()
{
var model = new TipoColaboracion
{
MontoSugerido = 1.00m,
Activo = true
};
return View(model);
}
// POST: TipoColaboracion/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(TipoColaboracion model)
{
if (ModelState.IsValid)
{
try
{
model.CreadoEn = DateTime.UtcNow;
model.ActualizadoEn = DateTime.UtcNow;
_context.TiposColaboracion.Add(model);
await _context.SaveChangesAsync();
TempData["Success"] = "Tipo de colaboración creado exitosamente";
return RedirectToAction(nameof(Index));
}
catch (Exception ex)
{
ModelState.AddModelError("", $"Error al crear: {ex.Message}");
}
}
return View(model);
}
// GET: TipoColaboracion/Edit/5
public async Task<IActionResult> Edit(long id)
{
try
{
var tipo = await _context.TiposColaboracion.FindAsync(id);
if (tipo == null)
{
TempData["Error"] = "Tipo de colaboración no encontrado";
return RedirectToAction(nameof(Index));
}
return View(tipo);
}
catch (Exception ex)
{
TempData["Error"] = $"Error al cargar tipo: {ex.Message}";
return RedirectToAction(nameof(Index));
}
}
// POST: TipoColaboracion/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(long id, TipoColaboracion model)
{
if (id != model.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
var tipo = await _context.TiposColaboracion.FindAsync(id);
if (tipo == null)
{
TempData["Error"] = "Tipo de colaboración no encontrado";
return RedirectToAction(nameof(Index));
}
tipo.Nombre = model.Nombre;
tipo.Descripcion = model.Descripcion;
tipo.MontoSugerido = model.MontoSugerido;
tipo.Activo = model.Activo;
tipo.Orden = model.Orden;
tipo.ActualizadoEn = DateTime.UtcNow;
_context.TiposColaboracion.Update(tipo);
await _context.SaveChangesAsync();
TempData["Success"] = "Tipo de colaboración actualizado exitosamente";
return RedirectToAction(nameof(Index));
}
catch (Exception ex)
{
ModelState.AddModelError("", $"Error al actualizar: {ex.Message}");
}
}
return View(model);
}
// POST: TipoColaboracion/Delete/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(long id)
{
try
{
var tipo = await _context.TiposColaboracion.FindAsync(id);
if (tipo == null)
{
TempData["Error"] = "Tipo de colaboración no encontrado";
return RedirectToAction(nameof(Index));
}
// Soft delete - just deactivate
tipo.Activo = false;
tipo.ActualizadoEn = DateTime.UtcNow;
await _context.SaveChangesAsync();
TempData["Success"] = "Tipo de colaboración desactivado exitosamente";
}
catch (Exception ex)
{
TempData["Error"] = $"Error al desactivar: {ex.Message}";
}
return RedirectToAction(nameof(Index));
}
}

View File

@@ -0,0 +1,125 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Rs_system.Models;
using Rs_system.Services;
namespace Rs_system.Controllers;
[Authorize]
public class UbicacionesController : Controller
{
private readonly IUbicacionService _service;
public UbicacionesController(IUbicacionService service)
{
_service = service;
}
// GET: Ubicaciones
public async Task<IActionResult> Index()
{
var list = await _service.GetAllAsync();
return View(list);
}
// GET: Ubicaciones/Create
public IActionResult Create()
{
return View();
}
// POST: Ubicaciones/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Nombre,Descripcion,Responsable,Activo")] Ubicacion ubicacion)
{
if (string.IsNullOrWhiteSpace(ubicacion.Nombre))
{
ModelState.AddModelError("Nombre", "El nombre es obligatorio.");
}
if (ModelState.IsValid)
{
if (await _service.ExistsAsync(ubicacion.Nombre))
{
ModelState.AddModelError("Nombre", "Ya existe una ubicación con ese nombre.");
return View(ubicacion);
}
ubicacion.CreadoPor = User.Identity?.Name ?? "Sistema";
var result = await _service.CreateAsync(ubicacion);
if (result)
{
TempData["SuccessMessage"] = "Ubicación creada exitosamente.";
return RedirectToAction(nameof(Index));
}
ModelState.AddModelError("", "Ocurrió un error al guardar los datos.");
}
return View(ubicacion);
}
// GET: Ubicaciones/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null) return NotFound();
var ubicacion = await _service.GetByIdAsync(id.Value);
if (ubicacion == null) return NotFound();
return View(ubicacion);
}
// POST: Ubicaciones/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Nombre,Descripcion,Responsable,Activo")] Ubicacion ubicacion)
{
if (id != ubicacion.Id) return NotFound();
if (string.IsNullOrWhiteSpace(ubicacion.Nombre))
{
ModelState.AddModelError("Nombre", "El nombre es obligatorio.");
}
if (ModelState.IsValid)
{
if (await _service.ExistsAsync(ubicacion.Nombre, id))
{
ModelState.AddModelError("Nombre", "Ya existe otra ubicación con ese nombre.");
return View(ubicacion);
}
var result = await _service.UpdateAsync(ubicacion);
if (result)
{
TempData["SuccessMessage"] = "Ubicación actualizada exitosamente.";
return RedirectToAction(nameof(Index));
}
ModelState.AddModelError("", "No se pudo actualizar la ubicación o no fue encontrada.");
}
return View(ubicacion);
}
// POST: Ubicaciones/Delete/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id)
{
var result = await _service.DeleteAsync(id);
if (result)
{
TempData["SuccessMessage"] = "Ubicación eliminada exitosamente.";
}
else
{
TempData["ErrorMessage"] = "No se pudo eliminar la ubicación.";
}
return RedirectToAction(nameof(Index));
}
}