diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..384b2cf --- /dev/null +++ b/.gitignore @@ -0,0 +1,71 @@ +## Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +## Publish output +publish/ + +## NuGet +*.nupkg +*.snupkg +.nuget/ +**/[Pp]ackages/* +!**/[Pp]ackages/build/ + +## Visual Studio +.vs/ +*.suo +*.user +*.userosscache +*.sln.docstates +*.userprefs + +## Rider +.idea/ +*.sln.iml + +## VS Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +## macOS +.DS_Store +*.dmg + +## Windows +Thumbs.db +ehthumbs.db +Desktop.ini + +## Linux +*~ + +## SQLite databases +*.db +*.db-shm +*.db-wal + +## Resharper +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +## NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +## BenchmarkDotNet +BenchmarkDotNet.Artifacts/ diff --git a/Asistencia/App.axaml.cs b/Asistencia/App.axaml.cs index b48f28e..8ede636 100644 --- a/Asistencia/App.axaml.cs +++ b/Asistencia/App.axaml.cs @@ -29,6 +29,7 @@ public partial class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); Services = services.BuildServiceProvider(); diff --git a/Asistencia/Asistencia.csproj b/Asistencia/Asistencia.csproj index e31a4fa..ed9599f 100644 --- a/Asistencia/Asistencia.csproj +++ b/Asistencia/Asistencia.csproj @@ -5,6 +5,9 @@ enable enable app.manifest + win-x64;linux-x64;osx-x64;osx-arm64 + true + true diff --git a/Asistencia/Data/AppDbContext.cs b/Asistencia/Data/AppDbContext.cs index 1d5911a..e3b356a 100644 --- a/Asistencia/Data/AppDbContext.cs +++ b/Asistencia/Data/AppDbContext.cs @@ -58,48 +58,48 @@ public class AppDbContext : DbContext modelBuilder.Entity().HasData( new Profesor { - RutPasaporte = "12345678-9", + RutPasaporte = "15668563-1", Nombre = "Juan", - Paterno = "Perez", - Materno = "Gonzalez", + ApPaterno = "Perez", + ApMaterno = "Gonzalez", Email = "juan.perez@norteamericano.cl", Fono = "+56912345678", Direccion = "Av. Providencia 1234, Santiago", Clave = "", - CrearClave = true + CrearClave = true, + Activo = true } ); modelBuilder.Entity().HasData( - new Curso { IdCursos = 1, NombreCurso = "English Basic A1" }, - new Curso { IdCursos = 2, NombreCurso = "English Intermediate B1" }, - new Curso { IdCursos = 3, NombreCurso = "English Advanced C1" } + new Curso { IdCursos = 16451, NombreCurso = "English Basic A1" }, + new Curso { IdCursos = 16493, NombreCurso = "English Intermediate B1" } ); modelBuilder.Entity().HasData( new CursoAbierto { - IdCursoAbierto = 1, - IdCursos = 1, + IdCursoAbierto = 16451, + IdCursos = 16451, Sala = "Sala 101", Sede = "Providencia", HoraInicio = "09:00", HoraFin = "10:30", FechaInicio = new DateTime(2026, 3, 1), - FechaTermino = new DateTime(2026, 7, 31), - ProfesorRun = "12345678-9" + FechaFin = new DateTime(2026, 7, 31), + ProfesorRun = "15668563-1" }, new CursoAbierto { - IdCursoAbierto = 2, - IdCursos = 2, + IdCursoAbierto = 16493, + IdCursos = 16493, Sala = "Sala 202", Sede = "Las Condes", HoraInicio = "14:00", HoraFin = "15:30", FechaInicio = new DateTime(2026, 3, 1), - FechaTermino = new DateTime(2026, 7, 31), - ProfesorRun = "12345678-9" + FechaFin = new DateTime(2026, 7, 31), + ProfesorRun = "15668563-1" } ); @@ -107,32 +107,29 @@ public class AppDbContext : DbContext new DetalleContrato { IdDetalleContrato = 1, - IdCursoAbierto = 1, + IdCursoAbierto = 16451, + IdAlumno = "11111111-1", AP_Paterno = "Garcia", AP_Materno = "Lopez", - Nombres = "Maria", - Email = "maria.garcia@email.com", - Telefono = "+56998765432" + Nombres = "Maria" }, new DetalleContrato { IdDetalleContrato = 2, - IdCursoAbierto = 1, + IdCursoAbierto = 16451, + IdAlumno = "22222222-2", AP_Paterno = "Rodriguez", AP_Materno = "Soto", - Nombres = "Carlos", - Email = "carlos.rodriguez@email.com", - Telefono = "+56987654321" + Nombres = "Carlos" }, new DetalleContrato { IdDetalleContrato = 3, - IdCursoAbierto = 2, + IdCursoAbierto = 16493, + IdAlumno = "33333333-3", AP_Paterno = "Martinez", AP_Materno = "Fernandez", - Nombres = "Ana", - Email = "ana.martinez@email.com", - Telefono = "+56976543210" + Nombres = "Ana" } ); } diff --git a/Asistencia/Data/Repositories/AsistenciaRepository.cs b/Asistencia/Data/Repositories/AsistenciaRepository.cs index 4ba5018..0eaa5de 100644 --- a/Asistencia/Data/Repositories/AsistenciaRepository.cs +++ b/Asistencia/Data/Repositories/AsistenciaRepository.cs @@ -41,7 +41,7 @@ public class AsistenciaRepository var sesion = await _context.AsistenciasProfesor.FindAsync(idSesion); if (sesion == null) return false; - sesion.HoraFin = horaFin; + sesion.HoraSalida = horaFin; sesion.Estado = "Finalizada"; await _context.SaveChangesAsync(); return true; diff --git a/Asistencia/Models/AsistenciaProfesor.cs b/Asistencia/Models/AsistenciaProfesor.cs index 7703ff6..2f35116 100644 --- a/Asistencia/Models/AsistenciaProfesor.cs +++ b/Asistencia/Models/AsistenciaProfesor.cs @@ -5,7 +5,7 @@ namespace Asistencia.Models; public class AsistenciaProfesor { [Key] - public int Id { get; set; } + public int IdAsistenciaProfesor { get; set; } public int IdCursoAbierto { get; set; } @@ -18,12 +18,12 @@ public class AsistenciaProfesor public string HoraInicio { get; set; } = string.Empty; [MaxLength(10)] - public string HoraFin { get; set; } = string.Empty; + public string HoraSalida { get; set; } = string.Empty; [MaxLength(20)] public string Estado { get; set; } = "Activa"; - public bool Temporal { get; set; } + public int Temporal { get; set; } public CursoAbierto? CursoAbierto { get; set; } } diff --git a/Asistencia/Models/CursoAbierto.cs b/Asistencia/Models/CursoAbierto.cs index 185cace..9e0556c 100644 --- a/Asistencia/Models/CursoAbierto.cs +++ b/Asistencia/Models/CursoAbierto.cs @@ -23,7 +23,7 @@ public class CursoAbierto public DateTime FechaInicio { get; set; } - public DateTime FechaTermino { get; set; } + public DateTime FechaFin { get; set; } [MaxLength(20)] public string ProfesorRun { get; set; } = string.Empty; diff --git a/Asistencia/Models/DetalleContrato.cs b/Asistencia/Models/DetalleContrato.cs index 110c5d8..226d637 100644 --- a/Asistencia/Models/DetalleContrato.cs +++ b/Asistencia/Models/DetalleContrato.cs @@ -9,6 +9,9 @@ public class DetalleContrato public int IdCursoAbierto { get; set; } + [MaxLength(30)] + public string IdAlumno { get; set; } = string.Empty; + [MaxLength(100)] public string AP_Paterno { get; set; } = string.Empty; @@ -18,12 +21,6 @@ public class DetalleContrato [MaxLength(100)] public string Nombres { get; set; } = string.Empty; - [MaxLength(200)] - public string Email { get; set; } = string.Empty; - - [MaxLength(20)] - public string Telefono { get; set; } = string.Empty; - public CursoAbierto? CursoAbierto { get; set; } public string NombreCompleto => $"{Nombres} {AP_Paterno} {AP_Materno}".Trim(); diff --git a/Asistencia/Models/Profesor.cs b/Asistencia/Models/Profesor.cs index 09c9b23..8de69a5 100644 --- a/Asistencia/Models/Profesor.cs +++ b/Asistencia/Models/Profesor.cs @@ -12,10 +12,10 @@ public class Profesor public string Nombre { get; set; } = string.Empty; [MaxLength(100)] - public string Paterno { get; set; } = string.Empty; + public string ApPaterno { get; set; } = string.Empty; [MaxLength(100)] - public string Materno { get; set; } = string.Empty; + public string ApMaterno { get; set; } = string.Empty; [MaxLength(200)] public string Email { get; set; } = string.Empty; @@ -31,5 +31,7 @@ public class Profesor public bool CrearClave { get; set; } - public string NombreCompleto => $"{Nombre} {Paterno} {Materno}".Trim(); + public bool Activo { get; set; } = true; + + public string NombreCompleto => $"{Nombre} {ApPaterno} {ApMaterno}".Trim(); } diff --git a/Asistencia/Services/AuthService.cs b/Asistencia/Services/AuthService.cs index 6988582..27f2578 100644 --- a/Asistencia/Services/AuthService.cs +++ b/Asistencia/Services/AuthService.cs @@ -17,21 +17,14 @@ public class AuthService public async Task<(bool Success, string Message)> LoginAsync(string rut, string password) { - if (string.IsNullOrWhiteSpace(rut) || string.IsNullOrWhiteSpace(password)) - return (false, "Ingrese usuario y contraseña"); + if (string.IsNullOrWhiteSpace(rut)) + return (false, "Ingrese su RUT"); var profesor = await _profesorRepo.BuscarPorRutAsync(rut); if (profesor == null) return (false, "Usuario no encontrado"); - if (profesor.CrearClave) - return (false, "Debe crear su contraseña primero"); - - var authenticated = await _profesorRepo.AutenticarAsync(rut, password); - if (authenticated == null) - return (false, "Contraseña incorrecta"); - - _currentUser = authenticated; + _currentUser = profesor; return (true, "Inicio de sesión exitoso"); } diff --git a/Asistencia/Services/ConnectivityService.cs b/Asistencia/Services/ConnectivityService.cs new file mode 100644 index 0000000..d7d02d6 --- /dev/null +++ b/Asistencia/Services/ConnectivityService.cs @@ -0,0 +1,20 @@ +using System.Net.NetworkInformation; + +namespace Asistencia.Services; + +public class ConnectivityService +{ + public async Task HayConexionAsync() + { + try + { + using var ping = new Ping(); + var reply = await ping.SendPingAsync("8.8.8.8", 3000); + return reply.Status == IPStatus.Success; + } + catch + { + return false; + } + } +} diff --git a/Asistencia/ViewLocator.cs b/Asistencia/ViewLocator.cs index f413711..39074dd 100644 --- a/Asistencia/ViewLocator.cs +++ b/Asistencia/ViewLocator.cs @@ -1,14 +1,12 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Linq; using Avalonia.Controls; using Avalonia.Controls.Templates; using Asistencia.ViewModels; namespace Asistencia; -/// -/// Given a view model, returns the corresponding view if possible. -/// [RequiresUnreferencedCode( "Default implementation of ViewLocator involves reflection which may be trimmed away.", Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")] @@ -18,15 +16,17 @@ public class ViewLocator : IDataTemplate { if (param is null) return null; - + var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); - var type = Type.GetType(name); + var type = AppDomain.CurrentDomain.GetAssemblies() + .Select(a => a.GetType(name)) + .FirstOrDefault(t => t != null); if (type != null) { return (Control)Activator.CreateInstance(type)!; } - + return new TextBlock { Text = "Not Found: " + name }; } diff --git a/Asistencia/ViewModels/AsistenciaViewModel.cs b/Asistencia/ViewModels/AsistenciaViewModel.cs index 6cd95d0..3a1015a 100644 --- a/Asistencia/ViewModels/AsistenciaViewModel.cs +++ b/Asistencia/ViewModels/AsistenciaViewModel.cs @@ -6,7 +6,7 @@ using System.Collections.ObjectModel; namespace Asistencia.ViewModels; -public partial class AsistenciaViewModel : ObservableObject +public partial class AsistenciaViewModel : ViewModelBase { private readonly AsistenciaService _asistenciaService; private readonly AuthService _authService; @@ -91,7 +91,7 @@ public partial class AsistenciaViewModel : ObservableObject var sesion = await _asistenciaService.BuscarSesionActivaAsync(_curso.IdCursoAbierto); if (sesion != null) { - await _asistenciaService.FinalizarSesionAsync(sesion.Id); + await _asistenciaService.FinalizarSesionAsync(sesion.IdAsistenciaProfesor); } EnSesion = false; MensajeEstado = "Sesion finalizada"; @@ -121,7 +121,7 @@ public partial class AsistenciaViewModel : ObservableObject } } -public partial class AlumnoAsistenciaViewModel : ObservableObject +public partial class AlumnoAsistenciaViewModel : ViewModelBase { public int IdDetalleContrato { get; } public string NombreCompleto { get; } diff --git a/Asistencia/ViewModels/DashboardViewModel.cs b/Asistencia/ViewModels/DashboardViewModel.cs index 92e5ae2..9ddb8ec 100644 --- a/Asistencia/ViewModels/DashboardViewModel.cs +++ b/Asistencia/ViewModels/DashboardViewModel.cs @@ -6,7 +6,7 @@ using System.Collections.ObjectModel; namespace Asistencia.ViewModels; -public partial class DashboardViewModel : ObservableObject +public partial class DashboardViewModel : ViewModelBase { private readonly AuthService _authService; private readonly AsistenciaService _asistenciaService; diff --git a/Asistencia/ViewModels/LoginViewModel.cs b/Asistencia/ViewModels/LoginViewModel.cs index 9f5092a..37c1ab8 100644 --- a/Asistencia/ViewModels/LoginViewModel.cs +++ b/Asistencia/ViewModels/LoginViewModel.cs @@ -4,7 +4,7 @@ using Asistencia.Services; namespace Asistencia.ViewModels; -public partial class LoginViewModel : ObservableObject +public partial class LoginViewModel : ViewModelBase { private readonly AuthService _authService; private readonly MainWindowViewModel _mainVm; diff --git a/Asistencia/ViewModels/MainWindowViewModel.cs b/Asistencia/ViewModels/MainWindowViewModel.cs index f7ab338..28de353 100644 --- a/Asistencia/ViewModels/MainWindowViewModel.cs +++ b/Asistencia/ViewModels/MainWindowViewModel.cs @@ -8,26 +8,51 @@ using System.Collections.ObjectModel; namespace Asistencia.ViewModels; -public partial class MainWindowViewModel : ObservableObject +public partial class MainWindowViewModel : ViewModelBase { private readonly AuthService _authService; private readonly AsistenciaService _asistenciaService; + private readonly ConnectivityService _connectivityService; private object? _currentView; [ObservableProperty] private string _currentViewTitle = "Login"; + [ObservableProperty] + private bool _conexionDisponible; + + [ObservableProperty] + private string _mensajeConexion = string.Empty; + public object? CurrentView { get => _currentView; set => SetProperty(ref _currentView, value); } - public MainWindowViewModel(AuthService authService, AsistenciaService asistenciaService) + public MainWindowViewModel(AuthService authService, AsistenciaService asistenciaService, ConnectivityService connectivityService) { _authService = authService; _asistenciaService = asistenciaService; - CurrentView = new LoginViewModel(authService, this); + _connectivityService = connectivityService; + _ = VerificarConexionAsync(); + } + + private async Task VerificarConexionAsync() + { + var hayInternet = await _connectivityService.HayConexionAsync(); + ConexionDisponible = hayInternet; + + if (hayInternet) + { + MensajeConexion = "Aplicación no disponible con conexión a internet. Desconéctese para usar el sistema."; + CurrentView = null; + } + else + { + MensajeConexion = "Modo offline — Using local database"; + CurrentView = new LoginViewModel(_authService, this); + } } public void NavigateToLogin() diff --git a/Asistencia/ViewModels/PerfilViewModel.cs b/Asistencia/ViewModels/PerfilViewModel.cs index a9c42db..981cf98 100644 --- a/Asistencia/ViewModels/PerfilViewModel.cs +++ b/Asistencia/ViewModels/PerfilViewModel.cs @@ -4,7 +4,7 @@ using Asistencia.Services; namespace Asistencia.ViewModels; -public partial class PerfilViewModel : ObservableObject +public partial class PerfilViewModel : ViewModelBase { private readonly AuthService _authService; private readonly MainWindowViewModel _mainVm; diff --git a/Asistencia/Views/LoginView.axaml b/Asistencia/Views/LoginView.axaml index 868f6e5..40be6aa 100644 --- a/Asistencia/Views/LoginView.axaml +++ b/Asistencia/Views/LoginView.axaml @@ -20,24 +20,17 @@ Foreground="{DynamicResource PrimaryNavy}" HorizontalAlignment="Center" /> - - - - - -