Compare commits

..
1 Commits
15 changed files with 339 additions and 359 deletions

No files matched your search

+1
View File
@@ -30,6 +30,7 @@ public partial class App : Application
services.AddSingleton<AuthService>(); services.AddSingleton<AuthService>();
services.AddSingleton<AsistenciaService>(); services.AddSingleton<AsistenciaService>();
services.AddSingleton<ConnectivityService>(); services.AddSingleton<ConnectivityService>();
services.AddSingleton<SyncService>();
services.AddSingleton<MainWindowViewModel>(); services.AddSingleton<MainWindowViewModel>();
Services = services.BuildServiceProvider(); Services = services.BuildServiceProvider();
-77
View File
@@ -55,82 +55,5 @@ public class AppDbContext : DbContext
private static void SeedData(ModelBuilder modelBuilder) private static void SeedData(ModelBuilder modelBuilder)
{ {
modelBuilder.Entity<Profesor>().HasData(
new Profesor
{
RutPasaporte = "15668563-1",
Nombre = "Juan",
ApPaterno = "Perez",
ApMaterno = "Gonzalez",
Email = "juan.perez@norteamericano.cl",
Fono = "+56912345678",
Direccion = "Av. Providencia 1234, Santiago",
Clave = "",
CrearClave = true,
Activo = true
}
);
modelBuilder.Entity<Curso>().HasData(
new Curso { IdCursos = 16451, NombreCurso = "English Basic A1" },
new Curso { IdCursos = 16493, NombreCurso = "English Intermediate B1" }
);
modelBuilder.Entity<CursoAbierto>().HasData(
new CursoAbierto
{
IdCursoAbierto = 16451,
IdCursos = 16451,
Sala = "Sala 101",
Sede = "Providencia",
HoraInicio = "09:00",
HoraFin = "10:30",
FechaInicio = new DateTime(2026, 3, 1),
FechaFin = new DateTime(2026, 7, 31),
ProfesorRun = "15668563-1"
},
new CursoAbierto
{
IdCursoAbierto = 16493,
IdCursos = 16493,
Sala = "Sala 202",
Sede = "Las Condes",
HoraInicio = "14:00",
HoraFin = "15:30",
FechaInicio = new DateTime(2026, 3, 1),
FechaFin = new DateTime(2026, 7, 31),
ProfesorRun = "15668563-1"
}
);
modelBuilder.Entity<DetalleContrato>().HasData(
new DetalleContrato
{
IdDetalleContrato = 1,
IdCursoAbierto = 16451,
IdAlumno = "11111111-1",
AP_Paterno = "Garcia",
AP_Materno = "Lopez",
Nombres = "Maria"
},
new DetalleContrato
{
IdDetalleContrato = 2,
IdCursoAbierto = 16451,
IdAlumno = "22222222-2",
AP_Paterno = "Rodriguez",
AP_Materno = "Soto",
Nombres = "Carlos"
},
new DetalleContrato
{
IdDetalleContrato = 3,
IdCursoAbierto = 16493,
IdAlumno = "33333333-3",
AP_Paterno = "Martinez",
AP_Materno = "Fernandez",
Nombres = "Ana"
}
);
} }
} }
@@ -21,7 +21,7 @@ public class AsistenciaRepository
a.Estado == "Activa"); a.Estado == "Activa");
} }
public async Task<AsistenciaProfesor> IniciarSesionAsync(int idCursoAbierto, string rut, DateTime fecha, string horaInicio) public async Task<AsistenciaProfesor> IniciarSesionAsync(int idCursoAbierto, string rut, DateTime fecha, string horaInicio, int tipoAsignacion)
{ {
var sesion = new AsistenciaProfesor var sesion = new AsistenciaProfesor
{ {
@@ -29,7 +29,8 @@ public class AsistenciaRepository
RutPasaporte = rut, RutPasaporte = rut,
FechaAsistencia = fecha, FechaAsistencia = fecha,
HoraInicio = horaInicio, HoraInicio = horaInicio,
Estado = "Activa" Estado = "Activa",
Temporal = tipoAsignacion == 4 ? 1 : 0
}; };
_context.AsistenciasProfesor.Add(sesion); _context.AsistenciasProfesor.Add(sesion);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
@@ -33,4 +33,11 @@ public class CursoRepository
.Where(d => d.IdCursoAbierto == idCursoAbierto) .Where(d => d.IdCursoAbierto == idCursoAbierto)
.ToListAsync(); .ToListAsync();
} }
public async Task<DetalleContrato?> BuscarAlumnoPorRutAsync(int idCursoAbierto, string idAlumno)
{
return await _context.DetallesContrato
.FirstOrDefaultAsync(d => d.IdCursoAbierto == idCursoAbierto
&& d.IdAlumno == idAlumno);
}
} }
+5 -2
View File
@@ -23,13 +23,16 @@ public class AsistenciaService
public Task<List<DetalleContrato>> ObtenerAlumnosAsync(int idCursoAbierto) public Task<List<DetalleContrato>> ObtenerAlumnosAsync(int idCursoAbierto)
=> _cursoRepo.BuscarAlumnosPorCursoAsync(idCursoAbierto); => _cursoRepo.BuscarAlumnosPorCursoAsync(idCursoAbierto);
public Task<DetalleContrato?> BuscarAlumnoPorRutAsync(int idCursoAbierto, string idAlumno)
=> _cursoRepo.BuscarAlumnoPorRutAsync(idCursoAbierto, idAlumno);
public Task<AsistenciaProfesor?> BuscarSesionActivaAsync(int idCursoAbierto) public Task<AsistenciaProfesor?> BuscarSesionActivaAsync(int idCursoAbierto)
=> _asistenciaRepo.BuscarSesionActivaAsync(idCursoAbierto, DateTime.Today); => _asistenciaRepo.BuscarSesionActivaAsync(idCursoAbierto, DateTime.Today);
public async Task<AsistenciaProfesor> IniciarSesionAsync(int idCursoAbierto, string rut) public async Task<AsistenciaProfesor> IniciarSesionAsync(int idCursoAbierto, string rut, int tipoAsignacion)
{ {
var now = DateTime.Now; var now = DateTime.Now;
return await _asistenciaRepo.IniciarSesionAsync(idCursoAbierto, rut, now.Date, now.ToString("HH:mm")); return await _asistenciaRepo.IniciarSesionAsync(idCursoAbierto, rut, now.Date, now.ToString("HH:mm"), tipoAsignacion);
} }
public async Task<bool> FinalizarSesionAsync(int idSesion) public async Task<bool> FinalizarSesionAsync(int idSesion)
+4 -1
View File
@@ -15,7 +15,9 @@ public class AuthService
public Profesor? CurrentUser => _currentUser; public Profesor? CurrentUser => _currentUser;
public async Task<(bool Success, string Message)> LoginAsync(string rut, string password) public int TipoAsignacionActual { get; private set; }
public async Task<(bool Success, string Message)> LoginAsync(string rut, int tipoAsignacion)
{ {
if (string.IsNullOrWhiteSpace(rut)) if (string.IsNullOrWhiteSpace(rut))
return (false, "Ingrese su RUT"); return (false, "Ingrese su RUT");
@@ -25,6 +27,7 @@ public class AuthService
return (false, "Usuario no encontrado"); return (false, "Usuario no encontrado");
_currentUser = profesor; _currentUser = profesor;
TipoAsignacionActual = tipoAsignacion;
return (true, "Inicio de sesión exitoso"); return (true, "Inicio de sesión exitoso");
} }
+68
View File
@@ -0,0 +1,68 @@
using Microsoft.EntityFrameworkCore;
using Asistencia.Data;
using Asistencia.Models;
namespace Asistencia.Services;
public class SyncService
{
private readonly AppDbContext _db;
private readonly ConnectivityService _connectivity;
public SyncService(AppDbContext db, ConnectivityService connectivity)
{
_db = db;
_connectivity = connectivity;
}
public async Task<int> ContarPendientesAsync()
{
var sesiones = await _db.AsistenciasProfesor
.Where(a => a.Estado == "Finalizada")
.CountAsync();
var asistencias = await _db.AsistenciasAlumno.CountAsync();
return sesiones + asistencias;
}
public async Task<(bool Success, string Message)> SincronizarAsync(string apiUrl)
{
if (!await _connectivity.HayConexionAsync())
return (false, "Sin conexion a internet");
var pendientes = await ContarPendientesAsync();
if (pendientes == 0)
return (true, "No hay datos pendientes");
try
{
var sesiones = await _db.AsistenciasProfesor
.Where(a => a.Estado == "Finalizada")
.ToListAsync();
foreach (var sesion in sesiones)
{
// TODO: POST to apiUrl + "/api/asistencia-profesor"
// await HttpClient.PostAsJsonAsync(apiUrl + "/api/asistencia-profesor", sesion);
}
var asistencias = await _db.AsistenciasAlumno.ToListAsync();
foreach (var asistencia in asistencias)
{
// TODO: POST to apiUrl + "/api/asistencia-alumno"
// await HttpClient.PostAsJsonAsync(apiUrl + "/api/asistencia-alumno", asistencia);
}
_db.AsistenciasProfesor.RemoveRange(sesiones);
_db.AsistenciasAlumno.RemoveRange(asistencias);
await _db.SaveChangesAsync();
return (true, $"Sincronizados {sesiones.Count} sesiones y {asistencias.Count} asistencias");
}
catch (Exception ex)
{
return (false, $"Error al sincronizar: {ex.Message}");
}
}
}
+69 -103
View File
@@ -11,100 +11,112 @@ public partial class AsistenciaViewModel : ViewModelBase
private readonly AsistenciaService _asistenciaService; private readonly AsistenciaService _asistenciaService;
private readonly AuthService _authService; private readonly AuthService _authService;
private readonly MainWindowViewModel _mainVm; private readonly MainWindowViewModel _mainVm;
private readonly CursoAbierto _curso;
[ObservableProperty] [ObservableProperty]
private string _nombreCurso = string.Empty; private string _inputCursoAbierto = string.Empty;
[ObservableProperty]
private string _infoCurso = string.Empty;
[ObservableProperty] [ObservableProperty]
private bool _enSesion; private bool _enSesion;
[ObservableProperty] [ObservableProperty]
private int _totalAlumnos; private string _inputRutAlumno = string.Empty;
[ObservableProperty]
private int _presentes;
[ObservableProperty]
private int _tardanzas;
[ObservableProperty]
private int _ausentes;
[ObservableProperty] [ObservableProperty]
private string _mensajeEstado = string.Empty; private string _mensajeEstado = string.Empty;
public ObservableCollection<AlumnoAsistenciaViewModel> Alumnos { get; } = new(); [ObservableProperty]
private string _mensajeError = string.Empty;
public AsistenciaViewModel(AsistenciaService asistenciaService, AuthService authService, CursoAbierto curso, MainWindowViewModel mainVm) [ObservableProperty]
private bool _hasError;
public ObservableCollection<AlumnoPresenteViewModel> AlumnosPresentes { get; } = new();
private int _idCursoAbierto;
public AsistenciaViewModel(AsistenciaService asistenciaService, AuthService authService, MainWindowViewModel mainVm)
{ {
_asistenciaService = asistenciaService; _asistenciaService = asistenciaService;
_authService = authService; _authService = authService;
_mainVm = mainVm; _mainVm = mainVm;
_curso = curso;
NombreCurso = curso.Curso?.NombreCurso ?? "Curso";
InfoCurso = $"{curso.Sala} | {curso.Sede} | {curso.HoraInicio} - {curso.HoraFin}";
_ = InitAsync();
}
private async Task InitAsync()
{
var sesion = await _asistenciaService.BuscarSesionActivaAsync(_curso.IdCursoAbierto);
EnSesion = sesion != null;
var alumnos = await _asistenciaService.ObtenerAlumnosAsync(_curso.IdCursoAbierto);
var asistencias = await _asistenciaService.ObtenerAsistenciaAlumnosAsync(_curso.IdCursoAbierto);
Alumnos.Clear();
foreach (var alumno in alumnos)
{
var tipo = asistencias.TryGetValue(alumno.IdDetalleContrato, out var t) ? t : TipoAsistencia.Ausente;
Alumnos.Add(new AlumnoAsistenciaViewModel(alumno, tipo));
}
TotalAlumnos = Alumnos.Count;
ActualizarContadores();
MensajeEstado = EnSesion
? "Sesion en curso - Puede registrar asistencia"
: "Inicie la sesion para registrar asistencia";
} }
[RelayCommand] [RelayCommand]
private async Task IniciarSesionAsync() private async Task IniciarSesionAsync()
{ {
HasError = false;
if (!int.TryParse(InputCursoAbierto, out var idCurso))
{
MensajeError = "Ingrese un ID de curso valido";
HasError = true;
return;
}
if (_authService.CurrentUser == null) return; if (_authService.CurrentUser == null) return;
await _asistenciaService.IniciarSesionAsync(_curso.IdCursoAbierto, _authService.CurrentUser.RutPasaporte); _idCursoAbierto = idCurso;
await _asistenciaService.IniciarSesionAsync(idCurso, _authService.CurrentUser.RutPasaporte, _authService.TipoAsignacionActual);
EnSesion = true; EnSesion = true;
MensajeEstado = "Sesion iniciada - Registre la asistencia de los alumnos"; MensajeEstado = $"Sesion curso {idCurso} — Ingrese RUT de alumnos presentes";
} }
[RelayCommand] [RelayCommand]
private async Task FinalizarSesionAsync() private async Task FinalizarSesionAsync()
{ {
var sesion = await _asistenciaService.BuscarSesionActivaAsync(_curso.IdCursoAbierto); var sesion = await _asistenciaService.BuscarSesionActivaAsync(_idCursoAbierto);
if (sesion != null) if (sesion != null)
{ {
await _asistenciaService.FinalizarSesionAsync(sesion.IdAsistenciaProfesor); await _asistenciaService.FinalizarSesionAsync(sesion.IdAsistenciaProfesor);
} }
EnSesion = false; EnSesion = false;
AlumnosPresentes.Clear();
InputCursoAbierto = string.Empty;
InputRutAlumno = string.Empty;
MensajeEstado = "Sesion finalizada"; MensajeEstado = "Sesion finalizada";
} }
[RelayCommand] [RelayCommand]
private async Task MarcarAlumnoAsync(AlumnoAsistenciaViewModel? alumno) private async Task AgregarAlumnoAsync()
{ {
if (alumno == null || !EnSesion) return; if (!EnSesion) return;
alumno.CiclarTipoAsistencia(); HasError = false;
await _asistenciaService.GuardarAsistenciaAlumnoAsync(alumno.IdDetalleContrato, alumno.TipoAsistencia); var rut = InputRutAlumno.Trim();
ActualizarContadores();
if (string.IsNullOrWhiteSpace(rut))
{
MensajeError = "Ingrese el RUT del alumno";
HasError = true;
return;
}
if (AlumnosPresentes.Any(a => a.IdAlumno == rut))
{
MensajeError = "Este RUT ya fue registrado";
HasError = true;
InputRutAlumno = string.Empty;
return;
}
var alumno = await _asistenciaService.BuscarAlumnoPorRutAsync(_idCursoAbierto, rut);
if (alumno == null)
{
MensajeError = $"RUT {rut} no encontrado en el curso";
HasError = true;
InputRutAlumno = string.Empty;
return;
}
await _asistenciaService.GuardarAsistenciaAlumnoAsync(alumno.IdDetalleContrato, TipoAsistencia.Presente);
AlumnosPresentes.Add(new AlumnoPresenteViewModel
{
IdAlumno = rut,
NombreCompleto = alumno.NombreCompleto
});
InputRutAlumno = string.Empty;
} }
[RelayCommand] [RelayCommand]
@@ -112,56 +124,10 @@ public partial class AsistenciaViewModel : ViewModelBase
{ {
_mainVm.NavigateToDashboard(); _mainVm.NavigateToDashboard();
} }
private void ActualizarContadores()
{
Presentes = Alumnos.Count(a => a.TipoAsistencia == Models.TipoAsistencia.Presente);
Tardanzas = Alumnos.Count(a => a.TipoAsistencia == Models.TipoAsistencia.Tardanza);
Ausentes = Alumnos.Count(a => a.TipoAsistencia == Models.TipoAsistencia.Ausente);
}
} }
public partial class AlumnoAsistenciaViewModel : ViewModelBase public class AlumnoPresenteViewModel
{ {
public int IdDetalleContrato { get; } public string IdAlumno { get; set; } = string.Empty;
public string NombreCompleto { get; } public string NombreCompleto { get; set; } = string.Empty;
[ObservableProperty]
private int _tipoAsistencia;
[ObservableProperty]
private string _tipoAsistenciaTexto;
[ObservableProperty]
private string _tipoAsistenciaColor;
public AlumnoAsistenciaViewModel(DetalleContrato alumno, int tipoInicial)
{
IdDetalleContrato = alumno.IdDetalleContrato;
NombreCompleto = alumno.NombreCompleto;
TipoAsistencia = tipoInicial;
ActualizarDisplay();
}
public void CiclarTipoAsistencia()
{
TipoAsistencia = TipoAsistencia switch
{
Models.TipoAsistencia.Presente => Models.TipoAsistencia.Tardanza,
Models.TipoAsistencia.Tardanza => Models.TipoAsistencia.Ausente,
_ => Models.TipoAsistencia.Presente
};
ActualizarDisplay();
}
private void ActualizarDisplay()
{
TipoAsistenciaTexto = Models.TipoAsistencia.Nombre(TipoAsistencia);
TipoAsistenciaColor = TipoAsistencia switch
{
Models.TipoAsistencia.Presente => "#28A745",
Models.TipoAsistencia.Tardanza => "#FFC107",
_ => "#DC3545"
};
}
} }
+29 -15
View File
@@ -10,40 +10,54 @@ public partial class DashboardViewModel : ViewModelBase
{ {
private readonly AuthService _authService; private readonly AuthService _authService;
private readonly AsistenciaService _asistenciaService; private readonly AsistenciaService _asistenciaService;
private readonly SyncService _syncService;
private readonly MainWindowViewModel _mainVm; private readonly MainWindowViewModel _mainVm;
[ObservableProperty] [ObservableProperty]
private string _nombreProfesor = string.Empty; private string _nombreProfesor = string.Empty;
[ObservableProperty] [ObservableProperty]
private int _totalCursos; private string _tipoAsignacion = string.Empty;
public ObservableCollection<CursoAbierto> Cursos { get; } = new(); [ObservableProperty]
private int _pendientes;
public DashboardViewModel(AuthService authService, AsistenciaService asistenciaService, MainWindowViewModel mainVm) [ObservableProperty]
private string _mensajeSync = string.Empty;
[ObservableProperty]
private bool _hasMensajeSync;
public DashboardViewModel(AuthService authService, AsistenciaService asistenciaService, SyncService syncService, MainWindowViewModel mainVm)
{ {
_authService = authService; _authService = authService;
_asistenciaService = asistenciaService; _asistenciaService = asistenciaService;
_syncService = syncService;
_mainVm = mainVm; _mainVm = mainVm;
NombreProfesor = authService.CurrentUser?.Nombre ?? ""; NombreProfesor = authService.CurrentUser?.NombreCompleto ?? "";
_ = LoadCursosAsync(); TipoAsignacion = authService.TipoAsignacionActual == 1 ? "Titular" : "Reemplazo";
_ = CargarPendientesAsync();
} }
private async Task LoadCursosAsync() private async Task CargarPendientesAsync()
{ {
if (_authService.CurrentUser == null) return; Pendientes = await _syncService.ContarPendientesAsync();
var cursos = await _asistenciaService.ObtenerCursosProfesorAsync(_authService.CurrentUser.RutPasaporte);
Cursos.Clear();
foreach (var c in cursos) Cursos.Add(c);
TotalCursos = Cursos.Count;
} }
[RelayCommand] [RelayCommand]
private void AbrirAsistencia(CursoAbierto? curso) private void AbrirAsistencia()
{ {
if (curso != null) _mainVm.NavigateToAsistencia();
_mainVm.NavigateToAsistencia(curso); }
[RelayCommand]
private async Task SincronizarAsync()
{
HasMensajeSync = false;
var (success, message) = await _syncService.SincronizarAsync("http://localhost:5000");
MensajeSync = message;
HasMensajeSync = true;
await CargarPendientesAsync();
} }
[RelayCommand] [RelayCommand]
+16 -2
View File
@@ -13,7 +13,10 @@ public partial class LoginViewModel : ViewModelBase
private string _rut = string.Empty; private string _rut = string.Empty;
[ObservableProperty] [ObservableProperty]
private string _password = string.Empty; private bool _esTitular = true;
[ObservableProperty]
private bool _esReemplazo;
[ObservableProperty] [ObservableProperty]
private string _errorMessage = string.Empty; private string _errorMessage = string.Empty;
@@ -30,13 +33,24 @@ public partial class LoginViewModel : ViewModelBase
_mainVm = mainVm; _mainVm = mainVm;
} }
partial void OnEsTitularChanged(bool value)
{
if (value) EsReemplazo = false;
}
partial void OnEsReemplazoChanged(bool value)
{
if (value) EsTitular = false;
}
[RelayCommand] [RelayCommand]
private async Task LoginAsync() private async Task LoginAsync()
{ {
IsLoading = true; IsLoading = true;
HasError = false; HasError = false;
var (success, message) = await _authService.LoginAsync(Rut, Password); var tipoAsignacion = EsTitular ? 1 : 4;
var (success, message) = await _authService.LoginAsync(Rut, tipoAsignacion);
if (success) if (success)
{ {
+6 -4
View File
@@ -13,6 +13,7 @@ public partial class MainWindowViewModel : ViewModelBase
private readonly AuthService _authService; private readonly AuthService _authService;
private readonly AsistenciaService _asistenciaService; private readonly AsistenciaService _asistenciaService;
private readonly ConnectivityService _connectivityService; private readonly ConnectivityService _connectivityService;
private readonly SyncService _syncService;
private object? _currentView; private object? _currentView;
[ObservableProperty] [ObservableProperty]
@@ -30,11 +31,12 @@ public partial class MainWindowViewModel : ViewModelBase
set => SetProperty(ref _currentView, value); set => SetProperty(ref _currentView, value);
} }
public MainWindowViewModel(AuthService authService, AsistenciaService asistenciaService, ConnectivityService connectivityService) public MainWindowViewModel(AuthService authService, AsistenciaService asistenciaService, ConnectivityService connectivityService, SyncService syncService)
{ {
_authService = authService; _authService = authService;
_asistenciaService = asistenciaService; _asistenciaService = asistenciaService;
_connectivityService = connectivityService; _connectivityService = connectivityService;
_syncService = syncService;
_ = VerificarConexionAsync(); _ = VerificarConexionAsync();
} }
@@ -64,13 +66,13 @@ public partial class MainWindowViewModel : ViewModelBase
public void NavigateToDashboard() public void NavigateToDashboard()
{ {
CurrentViewTitle = "Panel Principal"; CurrentViewTitle = "Panel Principal";
CurrentView = new DashboardViewModel(_authService, _asistenciaService, this); CurrentView = new DashboardViewModel(_authService, _asistenciaService, _syncService, this);
} }
public void NavigateToAsistencia(CursoAbierto curso) public void NavigateToAsistencia()
{ {
CurrentViewTitle = "Asistencia"; CurrentViewTitle = "Asistencia";
CurrentView = new AsistenciaViewModel(_asistenciaService, _authService, curso, this); CurrentView = new AsistenciaViewModel(_asistenciaService, _authService, this);
} }
public void NavigateToPerfil() public void NavigateToPerfil()
+72 -93
View File
@@ -4,130 +4,109 @@
x:Class="Asistencia.Views.AsistenciaView" x:Class="Asistencia.Views.AsistenciaView"
x:DataType="vm:AsistenciaViewModel"> x:DataType="vm:AsistenciaViewModel">
<Design.DataContext>
<vm:AsistenciaViewModel />
</Design.DataContext>
<ScrollViewer> <ScrollViewer>
<StackPanel Spacing="0" Margin="32,24"> <StackPanel Spacing="0" Margin="32,24" MaxWidth="500">
<Button Content="Volver" Classes="secondary" <Button Content="Volver" Classes="secondary"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Command="{Binding VolverCommand}" Command="{Binding VolverCommand}"
Margin="0,0,0,16" /> Margin="0,0,0,16" />
<TextBlock Text="{Binding NombreCurso}" Classes="page-header" /> <TextBlock Text="Tomar Asistencia" Classes="page-header" />
<Border Classes="card" Margin="0,0,0,20"> <Border Classes="card" Margin="0,0,0,16">
<StackPanel Spacing="8"> <StackPanel Spacing="12">
<TextBlock Text="{Binding InfoCurso}" <TextBlock Text="ID Curso Abierto" FontSize="13" FontWeight="SemiBold"
FontSize="14" Foreground="{DynamicResource TextSecondary}" /> Foreground="{DynamicResource TextPrimary}" />
<TextBox PlaceholderText="Ej: 16451"
<TextBlock Text="{Binding MensajeEstado}" Text="{Binding InputCursoAbierto}"
FontSize="13" FontWeight="SemiBold" FontSize="14" />
Foreground="{DynamicResource PrimaryNavy}" /> <StackPanel Orientation="Horizontal" Spacing="8">
<Button Content="Iniciar Sesion" Classes="success"
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,8,0,0">
<Button Content="Iniciar Sesion"
Classes="success"
Command="{Binding IniciarSesionCommand}" Command="{Binding IniciarSesionCommand}"
IsVisible="{Binding !EnSesion}" /> IsVisible="{Binding !EnSesion}" />
<Button Content="Finalizar Sesion" <Button Content="Finalizar Sesion" Classes="danger"
Classes="danger"
Command="{Binding FinalizarSesionCommand}" Command="{Binding FinalizarSesionCommand}"
IsVisible="{Binding EnSesion}" /> IsVisible="{Binding EnSesion}" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Border> </Border>
<Grid ColumnDefinitions="*,Auto" Margin="0,0,0,16"> <Border Classes="card" Margin="0,0,0,16"
<TextBlock Grid.Column="0" IsVisible="{Binding EnSesion}">
Classes="section-title" <StackPanel Spacing="12">
Text="Alumnos" <TextBlock Text="RUT Alumno" FontSize="13" FontWeight="SemiBold"
VerticalAlignment="Center" /> Foreground="{DynamicResource TextPrimary}" />
<Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="16"> <TextBox Grid.Column="0"
<StackPanel Orientation="Horizontal" Spacing="4"> PlaceholderText="Ej: 11111111-1"
<Border Background="{DynamicResource SuccessColor}" Text="{Binding InputRutAlumno}"
CornerRadius="4" Width="12" Height="12" /> FontSize="14"
<TextBlock Text="{Binding Presentes}" FontSize="13" KeyDown="OnRutAlumnoKeyDown" />
Foreground="{DynamicResource TextSecondary}" /> <Button Grid.Column="1"
</StackPanel> Content="+"
<StackPanel Orientation="Horizontal" Spacing="4"> Classes="primary"
<Border Background="{DynamicResource WarningColor}" Command="{Binding AgregarAlumnoCommand}"
CornerRadius="4" Width="12" Height="12" /> Margin="8,0,0,0"
<TextBlock Text="{Binding Tardanzas}" FontSize="13" Width="40" Height="40"
Foreground="{DynamicResource TextSecondary}" /> FontSize="18" FontWeight="Bold" />
</StackPanel> </Grid>
<StackPanel Orientation="Horizontal" Spacing="4">
<Border Background="{DynamicResource DangerColor}"
CornerRadius="4" Width="12" Height="12" />
<TextBlock Text="{Binding Ausentes}" FontSize="13"
Foreground="{DynamicResource TextSecondary}" />
</StackPanel>
</StackPanel> </StackPanel>
</Grid> </Border>
<Border Classes="card" Padding="0"> <TextBlock Text="{Binding MensajeEstado}"
<Grid RowDefinitions="Auto,*"> FontSize="13" FontWeight="SemiBold"
Foreground="{DynamicResource PrimaryNavy}"
Margin="0,0,0,12"
IsVisible="{Binding !HasError}" />
<Border Grid.Row="0" Background="{DynamicResource BackgroundSecondary}" <TextBlock Text="{Binding MensajeError}"
FontSize="13"
Foreground="{DynamicResource DangerColor}"
Margin="0,0,0,12"
IsVisible="{Binding HasError}" />
<Border Classes="card" Padding="0"
IsVisible="{Binding EnSesion}">
<StackPanel>
<Border Background="{DynamicResource BackgroundSecondary}"
CornerRadius="12,12,0,0" Padding="16,12"> CornerRadius="12,12,0,0" Padding="16,12">
<Grid ColumnDefinitions="*,120"> <TextBlock Text="Alumnos Presentes"
<TextBlock Grid.Column="0" Text="Alumno" FontSize="13" FontWeight="SemiBold"
FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource TextSecondary}" />
Foreground="{DynamicResource TextSecondary}" />
<TextBlock Grid.Column="1" Text="Estado"
FontSize="13" FontWeight="SemiBold"
Foreground="{DynamicResource TextSecondary}"
HorizontalAlignment="Center" />
</Grid>
</Border> </Border>
<ListBox ItemsSource="{Binding AlumnosPresentes}"
<ListBox Grid.Row="1"
ItemsSource="{Binding Alumnos}"
Background="Transparent" Background="Transparent"
BorderThickness="0" BorderThickness="0"
Padding="0"> Padding="0"
MaxHeight="300">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:AlumnoAsistenciaViewModel"> <DataTemplate x:DataType="vm:AlumnoPresenteViewModel">
<Border Padding="16,12" <Border Padding="16,10"
BorderBrush="{DynamicResource BorderColor}" BorderBrush="{DynamicResource BorderColor}"
BorderThickness="0,0,0,1"> BorderThickness="0,0,0,1">
<Grid ColumnDefinitions="*,120"> <Grid ColumnDefinitions="Auto,*">
<TextBlock Grid.Column="0" <Border Grid.Column="0"
Text="{Binding NombreCompleto}" Background="{DynamicResource SuccessColor}"
FontSize="14" CornerRadius="12" Padding="10,4"
VerticalAlignment="Center" /> VerticalAlignment="Center">
<TextBlock Text="P" Foreground="White"
<Button Grid.Column="1" FontSize="12" FontWeight="Bold" />
HorizontalAlignment="Center" </Border>
Command="{Binding $parent[ListBox].((vm:AsistenciaViewModel)DataContext).MarcarAlumnoCommand}" <StackPanel Grid.Column="1" Margin="12,0,0,0"
CommandParameter="{Binding}" VerticalAlignment="Center">
Background="{Binding TipoAsistenciaColor}" <TextBlock Text="{Binding NombreCompleto}"
CornerRadius="20" FontSize="14" FontWeight="SemiBold" />
Padding="16,6" <TextBlock Text="{Binding IdAlumno}"
FontSize="13" FontSize="12"
FontWeight="SemiBold" Foreground="{DynamicResource TextSecondary}" />
Foreground="White" </StackPanel>
BorderThickness="0"
MinWidth="100">
<TextBlock Text="{Binding TipoAsistenciaTexto}"
Foreground="White" />
</Button>
</Grid> </Grid>
</Border> </Border>
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>
</ListBox> </ListBox>
</Grid> </StackPanel>
</Border>
<Border Classes="card" Margin="0,16,0,0"
IsVisible="{Binding !Alumnos.Count}"
Background="#FFF3CD">
<TextBlock Text="No hay alumnos en este curso."
FontSize="14" Foreground="#856404" />
</Border> </Border>
</StackPanel> </StackPanel>
+10 -1
View File
@@ -1,5 +1,6 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Input;
using Asistencia.ViewModels;
namespace Asistencia.Views; namespace Asistencia.Views;
@@ -9,4 +10,12 @@ public partial class AsistenciaView : UserControl
{ {
InitializeComponent(); InitializeComponent();
} }
private void OnRutAlumnoKeyDown(object? sender, KeyEventArgs e)
{
if (e.Key == Key.Return && DataContext is AsistenciaViewModel vm)
{
vm.AgregarAlumnoCommand.Execute(null);
}
}
} }
+35 -52
View File
@@ -1,7 +1,6 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Asistencia.ViewModels" xmlns:vm="using:Asistencia.ViewModels"
xmlns:models="using:Asistencia.Models"
x:Class="Asistencia.Views.DashboardView" x:Class="Asistencia.Views.DashboardView"
x:DataType="vm:DashboardViewModel"> x:DataType="vm:DashboardViewModel">
@@ -10,18 +9,19 @@
</Design.DataContext> </Design.DataContext>
<ScrollViewer> <ScrollViewer>
<StackPanel Spacing="0" Margin="32,24"> <StackPanel Spacing="0" Margin="32,24" MaxWidth="500">
<TextBlock Text="Panel Principal" Classes="page-header" /> <TextBlock Text="Panel Principal" Classes="page-header" />
<Border Classes="card" Margin="0,0,0,24"> <Border Classes="card" Margin="0,0,0,24">
<Grid ColumnDefinitions="*,Auto"> <Grid ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0" Spacing="4"> <StackPanel Grid.Column="0" Spacing="4">
<TextBlock Text="Bienvenido"
FontSize="14" Foreground="{DynamicResource TextSecondary}" />
<TextBlock Text="{Binding NombreProfesor}" <TextBlock Text="{Binding NombreProfesor}"
FontSize="24" FontWeight="Bold" FontSize="22" FontWeight="Bold"
Foreground="{DynamicResource TextPrimary}" /> Foreground="{DynamicResource TextPrimary}" />
<TextBlock Text="{Binding TipoAsignacion}"
FontSize="13"
Foreground="{DynamicResource TextSecondary}" />
</StackPanel> </StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8"> <StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="8">
@@ -35,55 +35,38 @@
</Grid> </Grid>
</Border> </Border>
<TextBlock Text="Mis Cursos" Classes="section-title" Margin="0,0,0,12" /> <Button Content="Tomar Asistencia"
Classes="primary"
Command="{Binding AbrirAsistenciaCommand}"
HorizontalAlignment="Stretch"
Height="50"
FontSize="16" FontWeight="SemiBold"
Margin="0,0,0,16" />
<ItemsControl ItemsSource="{Binding Cursos}"> <Border Classes="card" Margin="0,0,0,16">
<ItemsControl.ItemTemplate> <StackPanel Spacing="12">
<DataTemplate x:DataType="models:CursoAbierto"> <Grid ColumnDefinitions="*,Auto">
<Border Classes="card" Margin="0,0,0,12"> <TextBlock Grid.Column="0"
<Grid ColumnDefinitions="*,Auto" RowDefinitions="Auto,Auto,Auto"> FontSize="14" FontWeight="SemiBold"
Foreground="{DynamicResource TextPrimary}">
<TextBlock Grid.Row="0" Grid.Column="0" <TextBlock.Text>
Text="{Binding Curso.NombreCurso}" <MultiBinding StringFormat="Pendientes: {0}">
FontSize="18" FontWeight="Bold" <Binding Path="Pendientes" />
Foreground="{DynamicResource TextPrimary}" /> </MultiBinding>
</TextBlock.Text>
<TextBlock Grid.Row="0" Grid.Column="1" </TextBlock>
Text="{Binding Sala}" <Button Grid.Column="1"
FontSize="13" Content="Sincronizar"
Foreground="{DynamicResource TextSecondary}" /> Classes="success"
Command="{Binding SincronizarCommand}" />
<TextBlock Grid.Row="1" Grid.Column="0" </Grid>
FontSize="13" <TextBlock Text="{Binding MensajeSync}"
Foreground="{DynamicResource TextSecondary}" FontSize="12"
Margin="0,4,0,0"> Foreground="{DynamicResource SuccessColor}"
<TextBlock.Text> IsVisible="{Binding HasMensajeSync}" />
<MultiBinding StringFormat="{}{0} | {1}"> </StackPanel>
<Binding Path="Sede" />
<Binding Path="HoraInicio" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
Content="Tomar Asistencia"
Classes="primary"
HorizontalAlignment="Left"
Margin="0,12,0,0"
Command="{Binding $parent[ItemsControl].((vm:DashboardViewModel)DataContext).AbrirAsistenciaCommand}"
CommandParameter="{Binding}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Border Classes="card" Margin="0,24,0,0"
IsVisible="{Binding !Cursos.Count}"
Background="#FFF3CD">
<TextBlock Text="No tiene cursos asignados actualmente."
FontSize="14" Foreground="#856404" />
</Border> </Border>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>
</UserControl> </UserControl>
+14 -7
View File
@@ -4,10 +4,6 @@
x:Class="Asistencia.Views.LoginView" x:Class="Asistencia.Views.LoginView"
x:DataType="vm:LoginViewModel"> x:DataType="vm:LoginViewModel">
<Design.DataContext>
<vm:LoginViewModel />
</Design.DataContext>
<Panel Background="{DynamicResource BackgroundSecondary}"> <Panel Background="{DynamicResource BackgroundSecondary}">
<Border HorizontalAlignment="Center" VerticalAlignment="Center" <Border HorizontalAlignment="Center" VerticalAlignment="Center"
CornerRadius="16" Padding="48" Width="420" CornerRadius="16" Padding="48" Width="420"
@@ -20,15 +16,26 @@
Foreground="{DynamicResource PrimaryNavy}" Foreground="{DynamicResource PrimaryNavy}"
HorizontalAlignment="Center" /> HorizontalAlignment="Center" />
<TextBlock Text="Modo offline — Ingrese su RUT" <TextBlock Text="Modo offline"
FontSize="14" Foreground="{DynamicResource TextSecondary}" FontSize="14" Foreground="{DynamicResource TextSecondary}"
HorizontalAlignment="Center" Margin="0,0,0,24" /> HorizontalAlignment="Center" Margin="0,0,0,24" />
<TextBlock Text="RUT" FontSize="13" FontWeight="SemiBold" <TextBlock Text="RUT" FontSize="13" FontWeight="SemiBold"
Foreground="{DynamicResource TextPrimary}" /> Foreground="{DynamicResource TextPrimary}" />
<TextBox PlaceholderText="Ej: 12345678-9" <TextBox PlaceholderText="Ej: 15668563-1"
Text="{Binding Rut}" Text="{Binding Rut}"
FontSize="14" Margin="0,0,0,16" /> FontSize="14" Margin="0,0,0,12" />
<TextBlock Text="Tipo de asignacion" FontSize="13" FontWeight="SemiBold"
Foreground="{DynamicResource TextPrimary}" Margin="0,0,0,4" />
<RadioButton Content="Titular"
IsChecked="{Binding EsTitular}"
GroupName="TipoAsignacion"
FontSize="14" />
<RadioButton Content="Reemplazo"
IsChecked="{Binding EsReemplazo}"
GroupName="TipoAsignacion"
FontSize="14" Margin="0,0,0,16" />
<Button Content="Ingresar" <Button Content="Ingresar"
Command="{Binding LoginCommand}" Command="{Binding LoginCommand}"