+ Aprendé a usar tu Ecosistema Digital
+Visualizá todas las materias organizadas por día. Usá la barra de búsqueda para filtrar rápido por nombre, aula o código.
+Accedé desde el botón inferior para ver tu plan de estudios.
+En el menú de cada materia (`...`) podés agendar Parciales, Finales o TPs. Verás insignias de colores avisándote la proximidad.
+Tocá el logo arriba a la izquierda para abrir tu credencial. La app subirá el brillo automáticamente para facilitar el escaneo.
+Usa Perfil > Guardar Respaldo para exportar un archivo CSV. Si cambias de teléfono, podés restaurarlo desde el mismo lugar.
+Tus datos son tuyos y de nadie más
+Aulas UNICABA no tiene servidores propios. Cada vez que sincronizás la cartelera o tu credencial, la conexión viaja directo de tu teléfono al servidor de la Universidad.
+ +Tus claves de Moodle o SIU Guaraní se guardan de forma encriptada en el almacenamiento seguro de tu dispositivo. Nunca salen de tu teléfono hacia terceros.
+ +No usamos Google Analytics ni SDKs de seguimiento. No hay publicidad. Cero telemetría invasiva.
+ + +
-
+ Aulas UNICABA es el visor especializado, independiente y offline-first para - la cartelera de horarios, campus virtual y seguimiento de tu progreso académico de la Universidad de la Ciudad. - Desarrollado pensando en - el uso en movimiento, el ahorro de batería y la mala conectividad.
- -Sin Servidores Intermediarios: No dependemos de bases de datos centralizadas, ni recopilamos - credenciales. Cada sincronización viaja directo desde Aulas UNICABA hasta el servidor del SIU/Google localmente. -
- -Tu ecosistema digital universitario, diseñado por y - para estudiantes.
- -Versión 1.2.1
- -Selecciona tu carrera para guardar tu avance y ver el recorrido sugerido, toca materias
diff --git a/src/app/home/home.page.scss b/src/app/home/home.page.scss
index 1b47669..8fcde53 100644
--- a/src/app/home/home.page.scss
+++ b/src/app/home/home.page.scss
@@ -29,20 +29,44 @@
}
// Header styles
-.header-content {
+.header-container-flex {
display: flex;
align-items: center;
- padding: 0 10px;
+ justify-content: space-between;
+ padding: 0 8px;
+ width: 100%;
+}
+.header-logo-section {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ padding: 8px 0;
+
.header-logo {
- height: 36px;
- width: auto;
- background-color: white;
+ height: 38px;
+ background: white;
padding: 4px;
- border-radius: 5px;
- position: absolute;
- left: 10px;
- z-index: 10;
+ border-radius: 8px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ }
+}
+
+.user-avatar-small {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ overflow: hidden;
+ border: 2px solid white;
+ box-shadow: 0 2px 5px rgba(0,0,0,0.2);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
}
}
diff --git a/src/app/home/home.page.ts b/src/app/home/home.page.ts
index fb344db..77e3455 100644
--- a/src/app/home/home.page.ts
+++ b/src/app/home/home.page.ts
@@ -1,5 +1,6 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, OnDestroy } from '@angular/core';
import { ToastController, ModalController, AlertController, ActionSheetController, NavController } from '@ionic/angular';
+import { Subscription } from 'rxjs';
import { DataService, ClaseHorario, EventoAcademico } from '../services/data';
import { Preferences } from '@capacitor/preferences';
@@ -13,8 +14,9 @@ import { LocalStorageService } from '../services/local-storage.service';
import { EventosService } from '../services/eventos.service';
import { EventoModalComponent } from '../components/evento-modal/evento-modal.component';
import { AgendaModalComponent } from '../components/agenda-modal/agenda-modal.component';
+import { InfoModalComponent } from '../components/info-modal/info-modal.component';
import { SiuService } from '../services/siu-guarani.service';
-import { AuthService } from '../services/auth.service';
+import { AuthService, AuthState } from '../services/auth.service';
export interface ProximaClaseInfo {
@@ -44,6 +46,9 @@ export class HomePage implements OnInit {
errorMsg: string = '';
isInfoModalOpen: boolean = false;
+ authState: AuthState | null = null;
+ private authSub: Subscription | null = null;
+
proximaClase: ProximaClaseInfo | null = null;
private countdownTimer: any = null;
@@ -95,6 +100,10 @@ export class HomePage implements OnInit {
async ngOnInit() {
+ this.authSub = this.authService.authState$.subscribe(state => {
+ this.authState = state;
+ });
+
this.route.queryParams.subscribe(params => {
this.highlightedCodigo = params['highlight'] || null;
if (this.highlightedCodigo) {
@@ -118,10 +127,8 @@ export class HomePage implements OnInit {
this.favoritos = userData.favoritos || [];
this.optativasSeleccionadas = userData.optativas || [];
- // Crucial: Recargar los eventos al objeto local
this.eventos = await this.eventosService.loadEventos();
- // Disparamos la lógica de auto-completar para mapear optativas y estados académicos
if (this.siuMaterias.length > 0) {
await this.autoCompletarDesdeSiu();
}
@@ -173,8 +180,6 @@ export class HomePage implements OnInit {
if (favPref.value && JSON.parse(favPref.value).length > 0) {
this.favoritos = JSON.parse(favPref.value);
this.viewMode = 'favoritos';
- } else {
- await this.promptForRestore();
}
this.eventos = await this.eventosService.loadEventos();
}
@@ -191,6 +196,13 @@ export class HomePage implements OnInit {
// 3. Fetch from Server
this.syncData();
+
+ // 4. Mostrar Info Modal automáticamente solo la PRIMERA VEZ (Onboarding)
+ const infoSeen = await Preferences.get({ key: 'info_modal_v1_seen' });
+ if (!infoSeen.value) {
+ setTimeout(() => this.openInfo(), 1000); // Pequeño delay para que la UI se asiente
+ await Preferences.set({ key: 'info_modal_v1_seen', value: 'true' });
+ }
}
syncData(event?: any) {
@@ -245,46 +257,6 @@ export class HomePage implements OnInit {
this.syncData(event);
}
- async promptForRestore() {
- const alert = await this.alertCtrl.create({
- header: '¡Datos no encontrados!',
- message: 'No encontramos tu configuración de favoritos. Si tienes un archivo CSV de respaldo (aulas_unicaba_datos.csv) en Google Drive, iCloud, o en las descargas de tu dispositivo, puedes restaurarlo manualmente.',
- buttons: [
- { text: 'Empezar de cero', role: 'cancel' },
- {
- text: 'Buscar Respaldo',
- handler: async () => {
- const imported = await this.localStorageService.importBackup();
- if (imported && (imported.favoritos.length > 0 || imported.eventos.length > 0 || imported.aprobadas?.length > 0 || imported.optativas?.length > 0 || imported.encurso?.length > 0)) {
- this.favoritos = imported.favoritos;
- this.aprobadas = imported.aprobadas || [];
- this.encurso = imported.encurso || [];
- this.optativasSeleccionadas = imported.optativas || [];
- this.siuMaterias = imported.siuMaterias || [];
- this.siuLastSync = imported.siuLastSync || null;
-
- // Sincronizar eventos cargados desde el CSV con el servicio de eventos
- for (const ev of imported.eventos) {
- await this.eventosService.saveEvento(ev);
- }
- this.eventos = await this.eventosService.loadEventos();
-
- this.viewMode = 'favoritos';
- await Preferences.set({ key: 'favoritos', value: JSON.stringify(this.favoritos) });
- this.filterHorarios();
-
- const toast = await this.toastCtrl.create({ message: 'Respaldo restaurado con éxito.', duration: 2500, color: 'success' });
- toast.present();
- } else {
- const toast = await this.toastCtrl.create({ message: 'No se pudo leer el archivo o está vacío.', duration: 2500, color: 'danger' });
- toast.present();
- }
- }
- }
- ]
- });
- await alert.present();
- }
filterHorarios() {
let baseList = this.horarios;
@@ -581,6 +553,20 @@ export class HomePage implements OnInit {
window.open(url, '_system');
}
+ async openInfo() {
+ const modal = await this.modalCtrl.create({
+ component: InfoModalComponent
+ });
+ await modal.present();
+ }
+
+ async openCredencial() {
+ const modal = await this.modalCtrl.create({
+ component: CredencialModalComponent
+ });
+ await modal.present();
+ }
+
// --- Hitos Académicos (Agenda) ---
@@ -920,97 +906,6 @@ export class HomePage implements OnInit {
});
}
- async forzarRespaldo() {
- this.guardarProgreso();
- const data = {
- favoritos: this.favoritos,
- eventos: this.eventos,
- aprobadas: this.aprobadas,
- optativas: this.optativasSeleccionadas,
- encurso: this.encurso,
- siuMaterias: this.siuMaterias,
- siuLastSync: this.siuLastSync
- };
-
- const actionSheet = await this.actionSheetCtrl.create({
- header: 'Guardar Respaldo',
- subHeader: '¿A dónde querés exportar tus materias guardadas?',
- buttons: [
- {
- text: 'Compartir (Drive, WhatsApp, Mail...)',
- icon: 'share-social-outline',
- handler: async () => {
- await this.ejecutarExportacion(data, false);
- }
- },
- {
- text: 'Descargar Offline (A la carpeta Descargas)',
- icon: 'download-outline',
- handler: async () => {
- await this.ejecutarExportacion(data, true);
- }
- },
- {
- text: 'Cancelar',
- icon: 'close',
- role: 'cancel'
- }
- ]
- });
- await actionSheet.present();
- }
-
- private async ejecutarExportacion(data: any, localMode: boolean) {
- const ok = await this.localStorageService.exportBackup(data, localMode);
- if (ok) {
- const toast = await this.toastCtrl.create({
- message: localMode ? 'Archivo descargado en tu teléfono.' : 'Respaldo emitido correctamente.',
- duration: 3500,
- color: 'success',
- icon: 'save'
- });
- toast.present();
- await Haptics.impact({ style: ImpactStyle.Heavy }).catch(() => { });
- }
- }
-
- async importarRespaldoManual() {
- const imported = await this.localStorageService.importBackup();
- if (imported && (imported.favoritos.length > 0 || imported.eventos.length > 0 || imported.aprobadas?.length > 0 || imported.optativas?.length > 0 || imported.encurso?.length > 0)) {
- this.favoritos = imported.favoritos;
- this.aprobadas = imported.aprobadas || [];
- this.encurso = imported.encurso || [];
- this.optativasSeleccionadas = imported.optativas || [];
- this.siuMaterias = imported.siuMaterias || [];
- this.siuLastSync = imported.siuLastSync || null;
-
- for (const ev of imported.eventos) {
- await this.eventosService.saveEvento(ev);
- }
- this.eventos = await this.eventosService.loadEventos();
-
- await Preferences.set({ key: 'favoritos', value: JSON.stringify(this.favoritos) });
- this.guardarProgreso();
- this.filterHorarios();
-
- const toast = await this.toastCtrl.create({
- message: '¡Respaldo importado correctamente!',
- duration: 3000,
- color: 'success',
- icon: 'checkmark-circle'
- });
- toast.present();
- await Haptics.impact({ style: ImpactStyle.Heavy }).catch(() => { });
- } else {
- const toast = await this.toastCtrl.create({
- message: 'No se procesó ningún archivo válido.',
- duration: 2500,
- color: 'warning',
- icon: 'alert-circle'
- });
- toast.present();
- }
- }
goToPerfil() {
this.navCtrl.navigateForward('/perfil');
diff --git a/src/app/perfil/perfil.page.html b/src/app/perfil/perfil.page.html
index 7e3ec99..03ec65c 100644
--- a/src/app/perfil/perfil.page.html
+++ b/src/app/perfil/perfil.page.html
@@ -4,6 +4,11 @@
Conectá con el SIU para traer tu historial y que la app auto-favoritee tus materias en curso.
+Conectá con el SIU para traer tu historial y que la app auto-favoritee tus materias en + curso.
Vinculá tu cuenta del campus para generar tu credencial digital offline y habilitar alertas de actividades.
+Vinculá tu cuenta del campus para generar tu credencial digital offline y habilitar alertas + de actividades.
Toca para abrir tu credencial. El brillo se ajustará automáticamente.
+Exportá tus materias favoritas y aprobadas para restaurarlas en otro dispositivo.
+ +Tus credenciales se almacenan de forma segura y local en tu dispositivo. No son enviadas a servidores externos ajenos a la universidad.
+Tus credenciales y datos se almacenan de forma segura y local en el almacenamiento persistente de tu teléfono. +