Alinear BD local con esquema de produccion, login offline y publicacion portable
- Modelos renombrados para coincidir con columnas de MariaDB (ApPaterno/ApMaterno, HoraSalida, FechaFin, IdAsistenciaProfesor) - Agregado campo IdAlumno a DetalleContrato - Seed data con IDs reales de produccion (16451, 16493, profesor 15668563-1) - Login offline: solo valida RUT contra BD local, sin contraseña - ConnectivityService: checkea internet al inicio, bloquea si hay conexion - ViewLocator corregido: busca tipos en todos los ensamblados - Todos los ViewModels heredan de ViewModelBase - Publicacion self-contained para win-x64, linux-x64, osx-x64, osx-arm64 - Agregado .gitignore, removidos bin/obj/publish del tracking
This commit is contained in:
1 parent
9068122b6b
commit
fb275508ee
19 files changed
+196
-77
No files matched your search
+71
@@ -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/
|
||||
@@ -29,6 +29,7 @@ public partial class App : Application
|
||||
services.AddSingleton<AsistenciaRepository>();
|
||||
services.AddSingleton<AuthService>();
|
||||
services.AddSingleton<AsistenciaService>();
|
||||
services.AddSingleton<ConnectivityService>();
|
||||
services.AddSingleton<MainWindowViewModel>();
|
||||
|
||||
Services = services.BuildServiceProvider();
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
<SelfContained>true</SelfContained>
|
||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -58,48 +58,48 @@ public class AppDbContext : DbContext
|
||||
modelBuilder.Entity<Profesor>().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<Curso>().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<CursoAbierto>().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"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace Asistencia.Services;
|
||||
|
||||
public class ConnectivityService
|
||||
{
|
||||
public async Task<bool> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Given a view model, returns the corresponding view if possible.
|
||||
/// </summary>
|
||||
[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 };
|
||||
}
|
||||
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -20,24 +20,17 @@
|
||||
Foreground="{DynamicResource PrimaryNavy}"
|
||||
HorizontalAlignment="Center" />
|
||||
|
||||
<TextBlock Text="Inicie sesion con su cuenta"
|
||||
<TextBlock Text="Modo offline — Ingrese su RUT"
|
||||
FontSize="14" Foreground="{DynamicResource TextSecondary}"
|
||||
HorizontalAlignment="Center" Margin="0,0,0,24" />
|
||||
|
||||
<TextBlock Text="RUT" FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBox Watermark="Ej: 12345678-9"
|
||||
<TextBox PlaceholderText="Ej: 12345678-9"
|
||||
Text="{Binding Rut}"
|
||||
FontSize="14" Margin="0,0,0,8" />
|
||||
|
||||
<TextBlock Text="Contrasena" FontSize="13" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource TextPrimary}" />
|
||||
<TextBox Watermark="Ingrese su contrasena"
|
||||
Text="{Binding Password}"
|
||||
PasswordChar="*"
|
||||
FontSize="14" Margin="0,0,0,16" />
|
||||
|
||||
<Button Content="Iniciar Sesion"
|
||||
<Button Content="Ingresar"
|
||||
Command="{Binding LoginCommand}"
|
||||
Classes="primary"
|
||||
HorizontalAlignment="Stretch"
|
||||
|
||||
@@ -17,7 +17,24 @@
|
||||
<vm:MainWindowViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<Panel>
|
||||
<Panel Background="{DynamicResource BackgroundSecondary}">
|
||||
<ContentControl Content="{Binding CurrentView}" />
|
||||
|
||||
<Border IsVisible="{Binding ConexionDisponible}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
CornerRadius="16" Padding="48" Width="420"
|
||||
Background="#FFF3CD" BorderBrush="#FFC107" BorderThickness="1">
|
||||
<StackPanel Spacing="16" HorizontalAlignment="Center">
|
||||
<TextBlock Text="Sin acceso al sistema"
|
||||
FontSize="24" FontWeight="Bold"
|
||||
Foreground="#856404"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock Text="{Binding MensajeConexion}"
|
||||
FontSize="14" TextWrapping="Wrap"
|
||||
Foreground="#856404"
|
||||
HorizontalAlignment="Center"
|
||||
TextAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Panel>
|
||||
</Window>
|
||||
Reference in new issue
Block a user