Nueva mejoras Y estabilidad
This commit is contained in:
@@ -1,41 +1,30 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MieSystem.Data.Interfaces;
|
||||
using MieSystem.Models;
|
||||
using MieSystem.Models.ViewModels;
|
||||
using MieSystem.Services;
|
||||
|
||||
namespace MieSystem.Controllers
|
||||
{
|
||||
public class AsistenciaController : Controller
|
||||
{
|
||||
private readonly IExpedienteRepository _expedienteRepository;
|
||||
private readonly IAsistenciaRepository _asistenciaRepository;
|
||||
|
||||
public AsistenciaController(
|
||||
IExpedienteRepository expedienteRepository,
|
||||
IAsistenciaRepository asistenciaRepository)
|
||||
private readonly ExpedienteService expedienteService;
|
||||
private readonly AsistenciaService asistenciaService;
|
||||
public AsistenciaController(ExpedienteService expedienteService, AsistenciaService asistenciaService)
|
||||
{
|
||||
_expedienteRepository = expedienteRepository;
|
||||
_asistenciaRepository = asistenciaRepository;
|
||||
this.expedienteService = expedienteService;
|
||||
this.asistenciaService = asistenciaService;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index(int? año, int? mes, string diasSemana = null)
|
||||
{
|
||||
// Valores por defecto: mes actual
|
||||
var fechaActual = DateTime.Now;
|
||||
var añoSeleccionado = año ?? fechaActual.Year;
|
||||
var mesSeleccionado = mes ?? fechaActual.Month;
|
||||
|
||||
// Obtener todos los niños activos
|
||||
var expedientes = await _expedienteRepository.GetActivosAsync();
|
||||
var expedientes = await expedienteService.GetActivosAsync();
|
||||
|
||||
// Obtener días del mes seleccionado
|
||||
var diasDelMes = GetDiasDelMes(añoSeleccionado, mesSeleccionado);
|
||||
|
||||
// Filtrar por días de semana si se especifica
|
||||
if (!string.IsNullOrEmpty(diasSemana))
|
||||
{
|
||||
var diasFiltro = diasSemana.Split(',')
|
||||
@@ -45,7 +34,7 @@ namespace MieSystem.Controllers
|
||||
}
|
||||
|
||||
// Obtener asistencias para el mes
|
||||
var asistencias = await _asistenciaRepository.GetAsistenciasPorMesAsync(
|
||||
var asistencias = await asistenciaService.GetAsistenciasPorMesAsync(
|
||||
añoSeleccionado, mesSeleccionado);
|
||||
|
||||
// Crear diccionario para acceso rápido
|
||||
@@ -53,7 +42,7 @@ namespace MieSystem.Controllers
|
||||
foreach (var asistencia in asistencias)
|
||||
{
|
||||
var key = $"{asistencia.ExpedienteId}_{asistencia.Fecha:yyyy-MM-dd}";
|
||||
dictAsistencias[key] = asistencia.Estado;
|
||||
dictAsistencias[key] = asistencia.Estado.ToString();
|
||||
}
|
||||
|
||||
// Crear modelo de vista
|
||||
@@ -89,11 +78,11 @@ namespace MieSystem.Controllers
|
||||
{
|
||||
ExpedienteId = expedienteId,
|
||||
Fecha = fechaDate,
|
||||
Estado = estado,
|
||||
Estado = estado[0],
|
||||
UsuarioRegistro = User.Identity?.Name ?? "Sistema"
|
||||
};
|
||||
|
||||
var resultado = await _asistenciaRepository.GuardarAsistenciaAsync(asistencia);
|
||||
var resultado = await asistenciaService.SaveAsync(asistencia);
|
||||
|
||||
return Json(new { success = resultado, message = "Asistencia guardada" });
|
||||
}
|
||||
@@ -116,13 +105,13 @@ namespace MieSystem.Controllers
|
||||
{
|
||||
ExpedienteId = asistenciaDto.ExpedienteId,
|
||||
Fecha = asistenciaDto.Fecha,
|
||||
Estado = asistenciaDto.Estado,
|
||||
Estado = asistenciaDto.Estado[0],
|
||||
UsuarioRegistro = User.Identity?.Name ?? "Sistema"
|
||||
};
|
||||
asistenciasModel.Add(asistencia);
|
||||
}
|
||||
|
||||
var resultado = await _asistenciaRepository.GuardarAsistenciasMasivasAsync(asistenciasModel);
|
||||
var resultado = asistenciaService.GuardarAsistenciasMasivasAsync(asistenciasModel);
|
||||
|
||||
return Json(new
|
||||
{
|
||||
@@ -141,7 +130,7 @@ namespace MieSystem.Controllers
|
||||
{
|
||||
try
|
||||
{
|
||||
var estadisticas = await _asistenciaRepository.GetEstadisticasMesAsync(año, mes);
|
||||
var estadisticas = await asistenciaService.GetEstadisticasMesAsync(año, mes);
|
||||
return Json(estadisticas);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MieSystem.Data.Interfaces;
|
||||
using MieSystem.Models;
|
||||
using MieSystem.Models.ViewModels;
|
||||
using MieSystem.Services;
|
||||
|
||||
namespace MieSystem.Controllers
|
||||
{
|
||||
public class ExpedientesController : Controller
|
||||
{
|
||||
private static List<Expediente> _expedientes = new List<Expediente>();
|
||||
private static int _idCounter = 1;
|
||||
//private static int _idCounter = 1;
|
||||
private readonly IWebHostEnvironment _hostingEnvironment;
|
||||
|
||||
private readonly IExpedienteRepository _expedienteRepository;
|
||||
public ExpedientesController(IExpedienteRepository expedienteRepository, IWebHostEnvironment hostingEnvironment)
|
||||
private readonly ExpedienteService expedienteService;
|
||||
|
||||
public ExpedientesController(ExpedienteService expedienteService, IWebHostEnvironment hostingEnvironment)
|
||||
{
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
|
||||
_expedienteRepository = expedienteRepository;
|
||||
this.expedienteService = expedienteService;
|
||||
}
|
||||
|
||||
// GET: Expedientes
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var today = DateTime.Today;
|
||||
var lst = await _expedienteRepository.GetAllAsync();
|
||||
var lst = await expedienteService.GetAllAsync();
|
||||
_expedientes = [.. lst];
|
||||
return View();
|
||||
}
|
||||
@@ -83,7 +81,7 @@ namespace MieSystem.Controllers
|
||||
FechaNacimiento = e.FechaNacimiento,
|
||||
Sexo = e.Sexo,
|
||||
NombreResponsable = e.NombreResponsable,
|
||||
FotoUrl = e.FotoUrl
|
||||
FotoUrl = ExisteFoto(e.FotoUrl)
|
||||
})
|
||||
.ToList();
|
||||
|
||||
@@ -140,7 +138,6 @@ namespace MieSystem.Controllers
|
||||
// Crear nuevo expediente
|
||||
var expediente = new Expediente
|
||||
{
|
||||
Id = _idCounter++,
|
||||
Nombre = model.Nombre,
|
||||
Apellidos = model.Apellidos,
|
||||
FechaNacimiento = model.FechaNacimiento,
|
||||
@@ -160,7 +157,8 @@ namespace MieSystem.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
await _expedienteRepository.CreateAsync(expediente);
|
||||
//await _expedienteRepository.CreateAsync(expediente);
|
||||
await expedienteService.SaveAsync(expediente);
|
||||
return Json(new { success = true, message = "Expediente creado exitosamente" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -217,7 +215,8 @@ namespace MieSystem.Controllers
|
||||
ModelState.Remove("Observaciones");
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
var expediente = await _expedienteRepository.GetByIdAsync(id);
|
||||
//var expediente = await _expedienteRepository.GetByIdAsync(id);
|
||||
var expediente = await expedienteService.GetByIdAsync(id);
|
||||
//var expediente = _expedientes.FirstOrDefault(e => e.Id == id);
|
||||
if (expediente == null)
|
||||
{
|
||||
@@ -278,7 +277,8 @@ namespace MieSystem.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
await _expedienteRepository.UpdateAsync(expediente);
|
||||
//await _expedienteRepository.UpdateAsync(expediente);
|
||||
await expedienteService.SaveAsync(expediente);
|
||||
return Json(new { success = true, message = "Expediente actualizado exitosamente" });
|
||||
|
||||
}
|
||||
@@ -292,7 +292,7 @@ namespace MieSystem.Controllers
|
||||
}
|
||||
|
||||
// DELETE: Expedientes/Delete/5
|
||||
[HttpDelete]
|
||||
/*[HttpDelete]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
var expediente = _expedientes.FirstOrDefault(e => e.Id == id);
|
||||
@@ -318,7 +318,7 @@ namespace MieSystem.Controllers
|
||||
|
||||
return Json(new { success = true, message = "Expediente eliminado exitosamente" });
|
||||
}
|
||||
|
||||
*/
|
||||
// GET: Expedientes/Details/5 (Opcional)
|
||||
public IActionResult Details(int id)
|
||||
{
|
||||
@@ -410,6 +410,23 @@ namespace MieSystem.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
public string ExisteFoto(string url)
|
||||
{
|
||||
if(string.IsNullOrEmpty(url))
|
||||
return Path.Combine("images", "default-avatar.png");
|
||||
|
||||
var uploadsFolder = Path.Combine(_hostingEnvironment.WebRootPath, "uploads", "fotos");
|
||||
string[] parts = url.Split('/');
|
||||
string name = parts[^1];
|
||||
|
||||
string fullpath = Path.Combine(uploadsFolder, name);
|
||||
|
||||
if (System.IO.File.Exists(fullpath))
|
||||
return url;
|
||||
|
||||
return Path.Combine("images", "default-avatar.png");
|
||||
}
|
||||
|
||||
// GET: Expedientes/DeleteImage
|
||||
[HttpDelete]
|
||||
public IActionResult DeleteImage(string imageUrl)
|
||||
|
||||
21
MieSystem/Data/NpgsqlConnectionFactory.cs
Normal file
21
MieSystem/Data/NpgsqlConnectionFactory.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Data;
|
||||
using Npgsql;
|
||||
|
||||
namespace MieSystem.Data;
|
||||
|
||||
public class NpgsqlConnectionFactory
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
|
||||
public NpgsqlConnectionFactory(IConfiguration configuration)
|
||||
{
|
||||
_connectionString = configuration.GetConnectionString("PostgreSQL")
|
||||
?? throw new InvalidOperationException("ConnectionString PostgreSQL no configurada");
|
||||
}
|
||||
|
||||
public Task<IDbConnection> CreateAsync()
|
||||
{
|
||||
IDbConnection conn = new NpgsqlConnection(_connectionString);
|
||||
return Task.FromResult(conn);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="Dapper.Contrib" Version="2.0.78" />
|
||||
<PackageReference Include="Dapper.SqlBuilder" Version="2.1.66" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
|
||||
@@ -25,4 +26,8 @@
|
||||
<Folder Include="wwwroot\uploads\fotos\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MicroORM\MicroORM.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,33 +1,64 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace MieSystem.Models
|
||||
{
|
||||
[Table("asistencia", Schema = "public")]
|
||||
public class Asistencia
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
[Column("id")]
|
||||
public int Id { get; set; }
|
||||
public int ExpedienteId { get; set; }
|
||||
public DateTime Fecha { get; set; }
|
||||
public string Estado { get; set; } // P, T, F
|
||||
public TimeSpan? HoraEntrada { get; set; }
|
||||
public TimeSpan? HoraSalida { get; set; }
|
||||
public string Observaciones { get; set; }
|
||||
public DateTime FechaRegistro { get; set; }
|
||||
public string UsuarioRegistro { get; set; }
|
||||
|
||||
// Propiedades calculadas
|
||||
[Required]
|
||||
[Column("expediente_id")]
|
||||
public int ExpedienteId { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("fecha")]
|
||||
public DateTime Fecha { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("estado")]
|
||||
public char Estado { get; set; } // P, T, F
|
||||
|
||||
[Column("hora_entrada")]
|
||||
public TimeSpan? HoraEntrada { get; set; }
|
||||
|
||||
[Column("hora_salida")]
|
||||
public TimeSpan? HoraSalida { get; set; }
|
||||
|
||||
[Column("observaciones")]
|
||||
public string? Observaciones { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("fecha_registro")]
|
||||
public DateTime FechaRegistro { get; set; }
|
||||
|
||||
[StringLength(100)]
|
||||
[Column("usuario_registro")]
|
||||
public string? UsuarioRegistro { get; set; }
|
||||
|
||||
// ==========================
|
||||
// PROPIEDADES CALCULADAS
|
||||
// ==========================
|
||||
|
||||
[NotMapped]
|
||||
public string EstadoTexto => Estado switch
|
||||
{
|
||||
"P" => "Presente",
|
||||
"T" => "Tarde",
|
||||
"F" => "Falto",
|
||||
'P' => "Presente",
|
||||
'T' => "Tarde",
|
||||
'F' => "Faltó",
|
||||
_ => "Desconocido"
|
||||
};
|
||||
|
||||
[NotMapped]
|
||||
public string ColorEstado => Estado switch
|
||||
{
|
||||
"P" => "success",
|
||||
"T" => "warning",
|
||||
"F" => "danger",
|
||||
'P' => "success",
|
||||
'T' => "warning",
|
||||
'F' => "danger",
|
||||
_ => "secondary"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,62 +1,85 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace MieSystem.Models
|
||||
{
|
||||
[Table("expedientes")]
|
||||
public class Expediente
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
[Column("id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
|
||||
[Required]
|
||||
[StringLength(100)]
|
||||
[Column("nombre")]
|
||||
public string Nombre { get; set; }
|
||||
|
||||
public string Nombre { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[StringLength(150)]
|
||||
[Column("apellidos")]
|
||||
public string Apellidos { get; set; }
|
||||
|
||||
public string Apellidos { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[Column("fecha_nacimiento")]
|
||||
public DateTime FechaNacimiento { get; set; }
|
||||
|
||||
[Column("nombre_padre")]
|
||||
public string NombrePadre { get; set; }
|
||||
|
||||
[Column("nombre_madre")]
|
||||
public string NombreMadre { get; set; }
|
||||
|
||||
[Column("nombre_responsable")]
|
||||
public string NombreResponsable { get; set; }
|
||||
|
||||
[Column("parentesco_responsable")]
|
||||
public string ParentescoResponsable { get; set; }
|
||||
|
||||
|
||||
[Required]
|
||||
[StringLength(1)]
|
||||
[Column("sexo")]
|
||||
public string Sexo { get; set; }
|
||||
|
||||
public string Sexo { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(150)]
|
||||
[Column("nombre_padre")]
|
||||
public string? NombrePadre { get; set; }
|
||||
|
||||
[StringLength(150)]
|
||||
[Column("nombre_madre")]
|
||||
public string? NombreMadre { get; set; }
|
||||
|
||||
[StringLength(150)]
|
||||
[Column("nombre_responsable")]
|
||||
public string? NombreResponsable { get; set; }
|
||||
|
||||
[StringLength(50)]
|
||||
[Column("parentesco_responsable")]
|
||||
public string? ParentescoResponsable { get; set; }
|
||||
|
||||
[Column("direccion")]
|
||||
public string Direccion { get; set; }
|
||||
|
||||
public string? Direccion { get; set; }
|
||||
|
||||
[StringLength(20)]
|
||||
[Column("telefono")]
|
||||
public string Telefono { get; set; }
|
||||
|
||||
public string? Telefono { get; set; }
|
||||
|
||||
[Column("observaciones")]
|
||||
public string Observaciones { get; set; }
|
||||
|
||||
public string? Observaciones { get; set; }
|
||||
|
||||
[StringLength(500)]
|
||||
[Column("foto_url")]
|
||||
public string FotoUrl { get; set; }
|
||||
|
||||
public string? FotoUrl { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("fecha_creacion")]
|
||||
public DateTime FechaCreacion { get; set; }
|
||||
|
||||
|
||||
[Required]
|
||||
[Column("fecha_actualizacion")]
|
||||
public DateTime FechaActualizacion { get; set; }
|
||||
|
||||
|
||||
[Required]
|
||||
[Column("activo")]
|
||||
public bool Activo { get; set; }
|
||||
|
||||
// ============================
|
||||
// 🔹 Propiedades calculadas
|
||||
// ============================
|
||||
|
||||
|
||||
// Propiedades calculadas (solo lectura)
|
||||
[NotMapped]
|
||||
public string NombreCompleto => $"{Nombre} {Apellidos}".Trim();
|
||||
|
||||
[NotMapped]
|
||||
public int Edad
|
||||
{
|
||||
get
|
||||
@@ -64,17 +87,14 @@ namespace MieSystem.Models
|
||||
var today = DateTime.Today;
|
||||
var age = today.Year - FechaNacimiento.Year;
|
||||
|
||||
// Si aún no ha cumplido años este año, restar 1
|
||||
if (FechaNacimiento.Date > today.AddYears(-age))
|
||||
{
|
||||
age--;
|
||||
}
|
||||
|
||||
return age;
|
||||
}
|
||||
}
|
||||
|
||||
// Otra propiedad útil para mostrar
|
||||
[NotMapped]
|
||||
public string EdadConMeses
|
||||
{
|
||||
get
|
||||
@@ -84,9 +104,7 @@ namespace MieSystem.Models
|
||||
var months = today.Month - FechaNacimiento.Month;
|
||||
|
||||
if (today.Day < FechaNacimiento.Day)
|
||||
{
|
||||
months--;
|
||||
}
|
||||
|
||||
if (months < 0)
|
||||
{
|
||||
@@ -98,10 +116,10 @@ namespace MieSystem.Models
|
||||
}
|
||||
}
|
||||
|
||||
// Para mostrar en selectores
|
||||
[NotMapped]
|
||||
public string NombreConEdad => $"{NombreCompleto} ({Edad} años)";
|
||||
|
||||
// Para mostrar en listas
|
||||
[NotMapped]
|
||||
public string InformacionBasica => $"{NombreCompleto} | {Edad} años | {Sexo}";
|
||||
}
|
||||
}
|
||||
|
||||
43
MieSystem/Models/Persona.cs
Normal file
43
MieSystem/Models/Persona.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace MieSystem.Models;
|
||||
|
||||
[Table("Personas", Schema = "public")]
|
||||
public class Persona
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
[Column("Id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(250)]
|
||||
[Column("Nombres")]
|
||||
public string Nombres { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[StringLength(250)]
|
||||
[Column("Apellidos")]
|
||||
public string Apellidos { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(50)]
|
||||
[Column("DUI")]
|
||||
public string? Dui { get; set; }
|
||||
|
||||
[StringLength(1000)]
|
||||
[Column("Direccion")]
|
||||
public string? Direccion { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("sexo")]
|
||||
public char Sexo { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("FechaNacimiento")]
|
||||
public DateTime FechaNacimiento { get; set; }
|
||||
|
||||
[StringLength(1000)]
|
||||
[Column("FotoUrl")]
|
||||
public string? FotoUrl { get; set; }
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
using System.Data;
|
||||
using MicroORM;
|
||||
using MicroORM.Interfaces;
|
||||
using MieSystem.Data;
|
||||
using MieSystem.Data.Interfaces;
|
||||
using MieSystem.Data.Repositories;
|
||||
using MieSystem.Services;
|
||||
|
||||
namespace MieSystem
|
||||
{
|
||||
@@ -10,11 +15,27 @@ namespace MieSystem
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddControllersWithViews();
|
||||
builder.Services.AddControllersWithViews().AddRazorRuntimeCompilation();
|
||||
|
||||
builder.Services.AddSingleton<IDatabaseConnectionFactory, DatabaseConnectionFactory>();
|
||||
|
||||
/* Esto es lo nuevo */
|
||||
builder.Services.AddSingleton<NpgsqlConnectionFactory>();
|
||||
|
||||
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
|
||||
|
||||
builder.Services.AddScoped<IExpedienteRepository, ExpedienteRepository>();
|
||||
// Delegate que consume el Repository<T>
|
||||
builder.Services.AddScoped<Func<Task<IDbConnection>>>(sp =>
|
||||
{
|
||||
var factory = sp.GetRequiredService<NpgsqlConnectionFactory>();
|
||||
return factory.CreateAsync;
|
||||
});
|
||||
|
||||
builder.Services.AddScoped<ExpedienteService>();
|
||||
builder.Services.AddScoped<AsistenciaService>();
|
||||
/***********/
|
||||
|
||||
//builder.Services.AddScoped<IExpedienteRepository, ExpedienteRepository>();
|
||||
builder.Services.AddScoped<IAsistenciaRepository, AsistenciaRepository>();
|
||||
var app = builder.Build();
|
||||
app.UseStaticFiles();
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "http://localhost:5037"
|
||||
"applicationUrl": "http://localhost:8090"
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
@@ -16,7 +16,7 @@
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "https://localhost:7037;http://localhost:5037"
|
||||
"applicationUrl": "https://localhost:7037;http://localhost:8090"
|
||||
},
|
||||
"Container (Dockerfile)": {
|
||||
"commandName": "Docker",
|
||||
|
||||
93
MieSystem/Services/AsistenciaService.cs
Normal file
93
MieSystem/Services/AsistenciaService.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System.Diagnostics.Tracing;
|
||||
using MicroORM.Interfaces;
|
||||
using MieSystem.Models;
|
||||
|
||||
namespace MieSystem.Services;
|
||||
|
||||
public class AsistenciaService
|
||||
{
|
||||
private readonly IRepository<Asistencia> _repo;
|
||||
|
||||
public AsistenciaService(IRepository<Asistencia> expe)
|
||||
{
|
||||
_repo = expe;
|
||||
}
|
||||
public Task<int> SaveAsync(Asistencia expediente)
|
||||
{
|
||||
return _repo.SaveAsync(expediente);
|
||||
}
|
||||
|
||||
public Task<Asistencia?> GetByIdAsync(object id)
|
||||
{
|
||||
return _repo.GetByIdAsync(id);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<Asistencia>> GetAllAsync()
|
||||
{
|
||||
return _repo.GetAllAsync();
|
||||
}
|
||||
|
||||
public Task<IEnumerable<Asistencia>> GetAsistenciasPorMesAsync(int añoSeleccionado, int mesSeleccionado)
|
||||
{
|
||||
var sql = @"
|
||||
SELECT
|
||||
id,
|
||||
expediente_id as ExpedienteId,
|
||||
fecha,
|
||||
estado,
|
||||
hora_entrada as HoraEntrada,
|
||||
hora_salida as HoraSalida,
|
||||
observaciones,
|
||||
fecha_registro as FechaRegistro,
|
||||
usuario_registro as UsuarioRegistro
|
||||
FROM asistencia
|
||||
WHERE EXTRACT(YEAR FROM fecha) = @Anio
|
||||
AND EXTRACT(MONTH FROM fecha) = @Mes
|
||||
ORDER BY fecha, expediente_id";
|
||||
|
||||
return _repo.QueryAsync<Asistencia>(sql, new {Anio = añoSeleccionado, Mes = mesSeleccionado});
|
||||
}
|
||||
|
||||
public Task<EstadisticasMes?> GetEstadisticasMesAsync(int año, int mes)
|
||||
{
|
||||
var sql = @"
|
||||
SELECT
|
||||
COALESCE(COUNT(*), 0) as Total,
|
||||
COALESCE(COUNT(CASE WHEN estado = 'P' THEN 1 END), 0) as Presentes,
|
||||
COALESCE(COUNT(CASE WHEN estado = 'T' THEN 1 END), 0) as Tardes,
|
||||
COALESCE(COUNT(CASE WHEN estado = 'F' THEN 1 END), 0) as Faltas,
|
||||
COALESCE(ROUND(
|
||||
COUNT(CASE WHEN estado = 'P' THEN 1 END) * 100.0 /
|
||||
NULLIF(COUNT(*), 0),
|
||||
2
|
||||
), 0) as PorcentajePresentes,
|
||||
COALESCE(ROUND(
|
||||
COUNT(CASE WHEN estado = 'T' THEN 1 END) * 100.0 /
|
||||
NULLIF(COUNT(*), 0),
|
||||
2
|
||||
), 0) as PorcentajeTardes,
|
||||
COALESCE(ROUND(
|
||||
COUNT(CASE WHEN estado = 'F' THEN 1 END) * 100.0 /
|
||||
NULLIF(COUNT(*), 0),
|
||||
2
|
||||
), 0) as PorcentajeFaltas
|
||||
FROM asistencia
|
||||
WHERE EXTRACT(YEAR FROM fecha) = @Anio
|
||||
AND EXTRACT(MONTH FROM fecha) = @Mes";
|
||||
|
||||
return _repo.QuerySingleAsync<EstadisticasMes?>(sql, new { Anio = año, Mes = mes });
|
||||
}
|
||||
public bool GuardarAsistenciasMasivasAsync(List<Asistencia> lst)
|
||||
{
|
||||
try
|
||||
{
|
||||
_repo.SaveAsyncList(lst);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
MieSystem/Services/ExpedienteService.cs
Normal file
35
MieSystem/Services/ExpedienteService.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using MicroORM.Interfaces;
|
||||
using MieSystem.Models;
|
||||
|
||||
namespace MieSystem.Services;
|
||||
|
||||
public class ExpedienteService
|
||||
{
|
||||
private readonly IRepository<Expediente> _repo;
|
||||
|
||||
public ExpedienteService(IRepository<Expediente> expe)
|
||||
{
|
||||
_repo = expe;
|
||||
}
|
||||
|
||||
public Task<int> SaveAsync(Expediente expediente)
|
||||
{
|
||||
return _repo.SaveAsync(expediente);
|
||||
}
|
||||
|
||||
public Task<Expediente?> GetByIdAsync(object id)
|
||||
{
|
||||
return _repo.GetByIdAsync(id);
|
||||
}
|
||||
|
||||
public Task<IEnumerable<Expediente>> GetAllAsync()
|
||||
{
|
||||
return _repo.GetAllAsync();
|
||||
}
|
||||
|
||||
public Task<IEnumerable<Expediente>> GetActivosAsync()
|
||||
{
|
||||
return _repo.GetAllAsync(e=> e.Activo == true);
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,147 +1,139 @@
|
||||
@using MieSystem.Models;
|
||||
@using MieSystem.Models
|
||||
@using MieSystem.Models.ViewModels
|
||||
|
||||
@model ExpedienteViewModel
|
||||
|
||||
<form id="@(ViewData["IsEdit"] != null && (bool)ViewData["IsEdit"] ? "editForm" : "createForm")"
|
||||
method="post"
|
||||
enctype="multipart/form-data"
|
||||
data-id="@(Model?.Id ?? 0)">
|
||||
@{
|
||||
bool isEdit = ViewData["IsEdit"] != null && (bool)ViewData["IsEdit"];
|
||||
}
|
||||
|
||||
@if (ViewData["IsEdit"] != null && (bool)ViewData["IsEdit"])
|
||||
<form id="@(isEdit ? "editForm" : "createForm")"
|
||||
method="post"
|
||||
enctype="multipart/form-data">
|
||||
|
||||
@* SOLO EN EDIT SE ENVÍA EL ID *@
|
||||
@if (isEdit)
|
||||
{
|
||||
<input type="hidden" asp-for="Id" />
|
||||
}
|
||||
|
||||
<!-- Campo oculto para almacenar la URL de la imagen subida -->
|
||||
<input type="hidden" asp-for="FotoUrl" />
|
||||
@* Campo oculto para la URL de la imagen *@
|
||||
<input type="hidden" asp-for="FotoUrl" value="@(Model?.FotoUrl ?? "/images/default-avatar.png")" />
|
||||
|
||||
<div class="row">
|
||||
<!-- Columna izquierda - Datos personales -->
|
||||
<!-- ================= COLUMNA IZQUIERDA ================= -->
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-light">
|
||||
<h6 class="mb-0">Datos Personales</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Foto -->
|
||||
|
||||
<!-- FOTO -->
|
||||
<div class="mb-3 text-center">
|
||||
<div class="mb-2">
|
||||
<img id="@(ViewData["IsEdit"] != null && (bool)ViewData["IsEdit"] ? "fotoPreviewEdit" : "fotoPreview")"
|
||||
src="@(Model?.FotoUrl ?? "/images/default-avatar.png")"
|
||||
alt="Foto del niño"
|
||||
class="rounded-circle border"
|
||||
style="width: 120px; height: 120px; object-fit: cover;">
|
||||
</div>
|
||||
<div class="d-flex justify-content-center align-items-center gap-2">
|
||||
<img id="@(isEdit ? "fotoPreviewEdit" : "fotoPreview")"
|
||||
src="@(Model?.FotoUrl ?? "/images/default-avatar.png")"
|
||||
class="rounded-circle border mb-2"
|
||||
style="width:120px;height:120px;object-fit:cover;" />
|
||||
|
||||
<div class="d-flex justify-content-center gap-2">
|
||||
<label class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-camera me-1"></i> Seleccionar Foto
|
||||
<input type="file"
|
||||
asp-for="Foto"
|
||||
id="@(ViewData["IsEdit"] != null && (bool)ViewData["IsEdit"] ? "FotoEdit" : "Foto")"
|
||||
id="@(isEdit ? "FotoEdit" : "Foto")"
|
||||
class="d-none"
|
||||
accept="image/*">
|
||||
accept="image/*" />
|
||||
</label>
|
||||
|
||||
<!-- Botón para eliminar imagen -->
|
||||
<button type="button"
|
||||
id="@(isEdit ? "deleteFotoEdit" : "deleteFoto")"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
id="@(ViewData["IsEdit"] != null && (bool)ViewData["IsEdit"] ? "deleteFotoEdit" : "deleteFoto")"
|
||||
style="display: @((Model?.FotoUrl != null && Model.FotoUrl != "/images/default-avatar.png") ? "block" : "none");">
|
||||
style="display:@(Model?.FotoUrl != null && Model.FotoUrl != "/images/default-avatar.png" ? "block" : "none")">
|
||||
<i class="bi bi-trash me-1"></i> Eliminar
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">Formatos permitidos: JPG, PNG, GIF, BMP. Tamaño máximo: 5MB</small>
|
||||
</div>
|
||||
|
||||
<span asp-validation-for="Foto" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<!-- Nombre -->
|
||||
<!-- NOMBRE -->
|
||||
<div class="mb-3">
|
||||
<label asp-for="Nombre" class="form-label">Nombre *</label>
|
||||
<input asp-for="Nombre" class="form-control" placeholder="Ingrese el nombre">
|
||||
<input asp-for="Nombre" class="form-control" />
|
||||
<span asp-validation-for="Nombre" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<!-- Apellidos -->
|
||||
<!-- APELLIDOS -->
|
||||
<div class="mb-3">
|
||||
<label asp-for="Apellidos" class="form-label">Apellidos *</label>
|
||||
<input asp-for="Apellidos" class="form-control" placeholder="Ingrese los apellidos">
|
||||
<input asp-for="Apellidos" class="form-control" />
|
||||
<span asp-validation-for="Apellidos" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<!-- Fecha de Nacimiento -->
|
||||
<!-- FECHA NACIMIENTO -->
|
||||
<div class="mb-3">
|
||||
<label asp-for="FechaNacimiento" class="form-label">Fecha de Nacimiento *</label>
|
||||
<input asp-for="FechaNacimiento" type="date" class="form-control">
|
||||
<input asp-for="FechaNacimiento" type="date" class="form-control" />
|
||||
<span asp-validation-for="FechaNacimiento" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<!-- Sexo -->
|
||||
<!-- SEXO -->
|
||||
<div class="mb-3">
|
||||
<label asp-for="Sexo" class="form-label">Sexo *</label>
|
||||
<div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" asp-for="Sexo" value="M" id="sexoM">
|
||||
<label class="form-check-label" for="sexoM">Masculino</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" asp-for="Sexo" value="F" id="sexoF">
|
||||
<label class="form-check-label" for="sexoF">Femenino</label>
|
||||
</div>
|
||||
<label class="form-label">Sexo *</label><br />
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" asp-for="Sexo" value="M" />
|
||||
<label class="form-check-label">Masculino</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline">
|
||||
<input class="form-check-input" type="radio" asp-for="Sexo" value="F" />
|
||||
<label class="form-check-label">Femenino</label>
|
||||
</div>
|
||||
<span asp-validation-for="Sexo" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Columna derecha - Datos familiares y dirección -->
|
||||
<!-- ================= COLUMNA DERECHA ================= -->
|
||||
<div class="col-md-6">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-light">
|
||||
<h6 class="mb-0">Datos Familiares</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Nombre del Padre -->
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="NombrePadre" class="form-label">Nombre del Padre</label>
|
||||
<input asp-for="NombrePadre" class="form-control" placeholder="Ingrese nombre del padre">
|
||||
<span asp-validation-for="NombrePadre" class="text-danger"></span>
|
||||
<input asp-for="NombrePadre" class="form-control" />
|
||||
</div>
|
||||
|
||||
<!-- Nombre de la Madre -->
|
||||
<div class="mb-3">
|
||||
<label asp-for="NombreMadre" class="form-label">Nombre de la Madre</label>
|
||||
<input asp-for="NombreMadre" class="form-control" placeholder="Ingrese nombre de la madre">
|
||||
<span asp-validation-for="NombreMadre" class="text-danger"></span>
|
||||
<input asp-for="NombreMadre" class="form-control" />
|
||||
</div>
|
||||
|
||||
<!-- Nombre del Responsable -->
|
||||
<div class="mb-3">
|
||||
<label asp-for="NombreResponsable" class="form-label">Nombre del Responsable *</label>
|
||||
<input asp-for="NombreResponsable" class="form-control" placeholder="Ingrese nombre del responsable">
|
||||
<input asp-for="NombreResponsable" class="form-control" />
|
||||
<span asp-validation-for="NombreResponsable" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<!-- Parentesco del Responsable -->
|
||||
<div class="mb-3">
|
||||
<label asp-for="ParentescoResponsable" class="form-label">Parentesco del Responsable</label>
|
||||
<label asp-for="ParentescoResponsable" class="form-label">Parentesco</label>
|
||||
<select asp-for="ParentescoResponsable" class="form-select">
|
||||
<option value="">Seleccione parentesco</option>
|
||||
<option value="Padre">Padre</option>
|
||||
<option value="Madre">Madre</option>
|
||||
<option value="Abuelo">Abuelo</option>
|
||||
<option value="Abuela">Abuela</option>
|
||||
<option value="Tío">Tío</option>
|
||||
<option value="Tía">Tía</option>
|
||||
<option value="Hermano">Hermano</option>
|
||||
<option value="Hermana">Hermana</option>
|
||||
<option value="Otro">Otro</option>
|
||||
<option value="">Seleccione</option>
|
||||
<option>Padre</option>
|
||||
<option>Madre</option>
|
||||
<option>Abuelo</option>
|
||||
<option>Abuela</option>
|
||||
<option>Tío</option>
|
||||
<option>Tía</option>
|
||||
<option>Otro</option>
|
||||
</select>
|
||||
<span asp-validation-for="ParentescoResponsable" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -150,26 +142,23 @@
|
||||
<h6 class="mb-0">Dirección y Contacto</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Dirección -->
|
||||
|
||||
<div class="mb-3">
|
||||
<label asp-for="Direccion" class="form-label">Dirección *</label>
|
||||
<textarea asp-for="Direccion" class="form-control" rows="3" placeholder="Ingrese dirección completa"></textarea>
|
||||
<textarea asp-for="Direccion" class="form-control"></textarea>
|
||||
<span asp-validation-for="Direccion" class="text-danger"></span>
|
||||
</div>
|
||||
|
||||
<!-- Teléfono -->
|
||||
<div class="mb-3">
|
||||
<label asp-for="Telefono" class="form-label">Teléfono de Contacto</label>
|
||||
<input asp-for="Telefono" class="form-control" placeholder="Ingrese número telefónico">
|
||||
<span asp-validation-for="Telefono" class="text-danger"></span>
|
||||
<label asp-for="Telefono" class="form-label">Teléfono</label>
|
||||
<input asp-for="Telefono" class="form-control" />
|
||||
</div>
|
||||
|
||||
<!-- Observaciones -->
|
||||
<div class="mb-3">
|
||||
<label asp-for="Observaciones" class="form-label">Observaciones</label>
|
||||
<textarea asp-for="Observaciones" class="form-control" rows="2" placeholder="Observaciones adicionales"></textarea>
|
||||
<span asp-validation-for="Observaciones" class="text-danger"></span>
|
||||
<textarea asp-for="Observaciones" class="form-control"></textarea>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user