296 lines
14 KiB
HTML
296 lines
14 KiB
HTML
<ion-header [translucent]="true">
|
|
<ion-toolbar color="secondary">
|
|
<div class="header-content">
|
|
<img src="assets/logo_unicaba_mini.png" class="header-logo" alt="UNICABA Logo">
|
|
<ion-title class="ion-text-center">
|
|
UNICABA
|
|
</ion-title>
|
|
<ion-buttons slot="end">
|
|
<ion-button *ngIf="viewMode === 'favoritos'" (click)="importarRespaldoManual()" title="Importar Respaldo">
|
|
<ion-icon slot="icon-only" name="cloud-download-outline"></ion-icon>
|
|
</ion-button>
|
|
<ion-button (click)="goToPerfil()" title="Mi Perfil">
|
|
<ion-icon slot="icon-only" name="person-circle-outline"></ion-icon>
|
|
</ion-button>
|
|
<ion-button (click)="isInfoModalOpen = true">
|
|
<ion-icon slot="icon-only" name="information-circle-outline"></ion-icon>
|
|
</ion-button>
|
|
</ion-buttons>
|
|
</div>
|
|
</ion-toolbar>
|
|
|
|
<ion-toolbar color="light" class="ion-no-padding">
|
|
<div class="view-segment">
|
|
<ion-segment [(ngModel)]="viewMode" (ionChange)="onViewModeChange()" color="primary">
|
|
<ion-segment-button value="favoritos">Favoritos</ion-segment-button>
|
|
<ion-segment-button value="todas">Cartelera</ion-segment-button>
|
|
<ion-segment-button value="plan">Mi Carrera</ion-segment-button>
|
|
</ion-segment>
|
|
</div>
|
|
</ion-toolbar>
|
|
|
|
<ion-toolbar color="light" class="ion-no-padding" *ngIf="viewMode === 'todas'">
|
|
<div class="filter-section">
|
|
<ion-searchbar placeholder="Buscar materia, aula o código..." (ionInput)="onSearchChange($event)"
|
|
[debounce]="250"></ion-searchbar>
|
|
</div>
|
|
</ion-toolbar>
|
|
</ion-header>
|
|
|
|
<ion-modal [isOpen]="isInfoModalOpen" (didDismiss)="isInfoModalOpen = false">
|
|
<ng-template>
|
|
<ion-header>
|
|
<ion-toolbar color="primary">
|
|
<ion-title>Información y Privacidad</ion-title>
|
|
<ion-buttons slot="end">
|
|
<ion-button (click)="isInfoModalOpen = false">Cerrar</ion-button>
|
|
</ion-buttons>
|
|
</ion-toolbar>
|
|
</ion-header>
|
|
<ion-content class="ion-padding privacy-content">
|
|
<h2>¿Qué hace esta aplicación?</h2>
|
|
<p><strong>Aulas UNICABA</strong> es el visor especializado, independiente y <strong>offline-first</strong> 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.</p>
|
|
|
|
<h3>Nuevas Características y Funciones</h3>
|
|
<ul>
|
|
<li><strong>Cartelera Rápida y Offline:</strong> Consulta de materias organizada por días y cacheada al
|
|
instante. Muestra datos rápido aunque estés bajo tierra sin red.</li>
|
|
<li><strong>Mi Carrera y Trackeo:</strong> Ingresá a tu Plan de Estudios interactivo. Usá los Action Sheets
|
|
nativos para marcar materias como <strong>'Aprobadas'</strong> (Fucsia) o <strong>'En Curso'</strong>
|
|
(Violeta), y gestioná tus <strong>Materias Optativas</strong> desde el catálogo
|
|
directamente con un clic.</li>
|
|
<li><strong>Próxima Clase Inteligente:</strong> Un banner destacado superior en Favoritos te avisa cuándo es y
|
|
cuánto falta
|
|
para tu próxima clase, con un reloj regresivo dinámico en vivo.</li>
|
|
<li><strong>Alertas Automáticas:</strong> Si la universidad cambia repentinamente el número de aula o el link de
|
|
Meet/Zoom de tus materias, serás notificado. También recibís pre-alertas de 30 minutos antes de cursar.</li>
|
|
</ul>
|
|
|
|
<h3>Agenda de Hitos Académicos</h3>
|
|
<ul>
|
|
<li>Accedé rápidamente para agendar <strong>Parciales / Trabajos Prácticos / Finales</strong> desde cualquier
|
|
materia para llevar un control estricto.</li>
|
|
<li>El robusto sistema de calendario nativo "Wheels" evitará borrados accidentales de tus fechas importantes.
|
|
</li>
|
|
</ul>
|
|
|
|
<h3>Credencial Inmediata y Exportación</h3>
|
|
<ul>
|
|
<li><strong>Credencial Digital:</strong> Tocando el logo superior izquierdo obtenés tu QR offline y se sube el
|
|
brillo de tu pantalla al máximo de forma automática para facilitar la lectura.</li>
|
|
<li><strong>Respaldo Portable Seguro:</strong> Apretando la "Nubecita" o "Disquete" podés emitir un archivo de
|
|
backup (JSON seguro) hacia la memoria o nube de tu teléfono, para que restaures de inmediato toda la
|
|
configuración si mudás a otro equipo.</li>
|
|
</ul>
|
|
|
|
<h3>Privacidad Zero-Trust</h3>
|
|
<p><strong>Sin Servidores Intermediarios:</strong> 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.
|
|
</p>
|
|
|
|
<p class="ion-text-center" style="margin-top: 30px;"><small>Tu ecosistema digital universitario, diseñado por y
|
|
para estudiantes.</small></p>
|
|
|
|
<p class="ion-text-center" style="margin-top: 30px;"><small><strong>Versión 1.2.1</strong></small></p>
|
|
|
|
</ion-content>
|
|
</ng-template>
|
|
</ion-modal>
|
|
|
|
<ion-content [fullscreen]="true" color="light" (swipe)="onSwipe($event)">
|
|
<ion-refresher slot="fixed" (ionRefresh)="doRefresh($event)">
|
|
<ion-refresher-content pullingText="Desliza para actualizar" refreshingSpinner="crescent"
|
|
refreshingText="Actualizando..."></ion-refresher-content>
|
|
</ion-refresher>
|
|
|
|
<!-- Banner: Próxima Clase -->
|
|
<div class="proxima-clase-container" *ngIf="viewMode === 'favoritos' && proximaClase && !loading">
|
|
<ion-card class="proxima-clase-card" [class.urgente]="proximaClase.diffMs > 0 && proximaClase.diffMs <= 1800000">
|
|
<ion-card-content (click)="verAgendaMateria(proximaClase.item)">
|
|
<div class="proxima-header">
|
|
<div class="proxima-label">
|
|
<ion-icon name="time-outline"></ion-icon>
|
|
<span>{{ proximaClase.label }}</span>
|
|
</div>
|
|
<ion-chip [color]="countdownColor(proximaClase.diffMs)" class="countdown-chip"
|
|
[class.pulse-chip]="proximaClase.diffMs > 0 && proximaClase.diffMs <= 1800000">
|
|
<ion-icon name="alarm-outline"></ion-icon>
|
|
<ion-label>{{ formatCountdown(proximaClase.diffMs) }}</ion-label>
|
|
</ion-chip>
|
|
</div>
|
|
|
|
<p class="proxima-asignatura">{{ proximaClase.item.asignatura }}</p>
|
|
<div class="proxima-detalles">
|
|
<span class="proxima-aula">
|
|
<ion-icon name="location-outline"></ion-icon>
|
|
Aula {{ proximaClase.item.aula }}
|
|
</span>
|
|
<span class="proxima-hora">
|
|
<ion-icon name="calendar-outline"></ion-icon>
|
|
{{ proximaClase.item.dia }} {{ proximaClase.item.horario }}
|
|
</span>
|
|
</div>
|
|
|
|
</ion-card-content>
|
|
</ion-card>
|
|
</div>
|
|
|
|
<div class="loading-container" *ngIf="loading">
|
|
<ion-spinner name="crescent" color="primary"></ion-spinner>
|
|
<p>Cargando cartelera...</p>
|
|
</div>
|
|
|
|
<div class="error-state" *ngIf="!loading && errorMsg">
|
|
<p>{{ errorMsg }}</p>
|
|
</div>
|
|
|
|
<ion-list *ngIf="!loading && !errorMsg && horariosFiltrados.length > 0 && viewMode !== 'plan'">
|
|
<ion-card *ngFor="let item of horariosFiltrados" [class.highlight-card]="item.codigo === highlightedCodigo"
|
|
class="subject-card">
|
|
|
|
<ion-card-header>
|
|
<div class="card-header-flex">
|
|
<div class="header-titles">
|
|
<ion-card-subtitle color="primary">{{ item.carrera }}</ion-card-subtitle>
|
|
<ion-card-title>{{ item.asignatura }}</ion-card-title>
|
|
</div>
|
|
<div class="card-actions">
|
|
<!-- Compartir siempre visible -->
|
|
<ion-icon class="share-icon" name="share-social-outline" color="medium"
|
|
(click)="shareItem(item)"></ion-icon>
|
|
|
|
<!-- Icono condicional: Hamburguesa en favoritos, Estrella en cartelera -->
|
|
<ion-icon class="menu-icon"
|
|
[name]="viewMode === 'favoritos' ? 'menu-outline' : (isFavorito(item.codigo) ? 'star' : 'star-outline')"
|
|
[color]="(viewMode === 'todas' && isFavorito(item.codigo)) ? 'warning' : 'medium'"
|
|
(click)="openMateriaActions(item)"></ion-icon>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Indicador de Próximo Evento (Agenda) -->
|
|
<div class="evento-badge-container" *ngIf="getProximoEvento(item.codigo) as proximo">
|
|
<ion-badge [color]="isEventoUrgente(proximo.fecha) ? 'danger' : 'secondary'" class="evento-badge">
|
|
<ion-icon [name]="proximo.tipo === 'TP' ? 'document-text-outline' : 'calendar-outline'"></ion-icon>
|
|
{{ proximo.tipo }}: {{ formatFechaEvento(proximo.fecha) }}
|
|
</ion-badge>
|
|
</div>
|
|
|
|
</ion-card-header>
|
|
<ion-card-content (click)="verAgendaMateria(item)">
|
|
<p><strong>Código:</strong> {{ item.codigo }}</p>
|
|
<div class="aula-container">
|
|
<span class="aula-tag">{{ item.aula }}</span>
|
|
</div>
|
|
<p class="schedule-text">{{ item.dia }} - {{ item.horario }}</p>
|
|
<div *ngIf="item.link" class="link-container">
|
|
<ion-button fill="outline" color="primary" size="small" expand="block"
|
|
(click)="openLink(item.link!); $event.stopPropagation()">
|
|
<ion-icon slot="start" name="videocam-outline"></ion-icon>
|
|
Ingresar a Clase Virtual
|
|
</ion-button>
|
|
</div>
|
|
</ion-card-content>
|
|
|
|
</ion-card>
|
|
</ion-list>
|
|
|
|
<div class="empty-state" *ngIf="!loading && !errorMsg && horariosFiltrados.length === 0 && viewMode !== 'plan'">
|
|
<p>No se encontraron horarios para la selección actual.</p>
|
|
</div>
|
|
|
|
<!-- Vista: Mi Carrera (Plan de Estudios) -->
|
|
<div *ngIf="viewMode === 'plan'" class="ion-padding content-plan">
|
|
<ion-card class="carrera-selector-card">
|
|
<ion-card-content>
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
<span class="selector-title" style="margin: 0;">Plan de Estudios / Recorrido</span>
|
|
<div style="display: flex; gap: 8px;">
|
|
<ion-button fill="outline" color="primary" size="small" (click)="forzarRespaldo()" style="margin: 0;"
|
|
title="Guardar Manualmente" class="header-btn">
|
|
<ion-icon name="save-outline" slot="icon-only"></ion-icon>
|
|
</ion-button>
|
|
</div>
|
|
</div>
|
|
<p class="selector-desc">Selecciona tu carrera para guardar tu avance y ver el recorrido sugerido, toca materias
|
|
para marcarlas como aprobadas o en curso.</p>
|
|
<ion-item lines="none" class="carrera-item">
|
|
<ion-icon name="school-outline" slot="start" color="primary"></ion-icon>
|
|
<ion-select placeholder="Elegí una carrera" [(ngModel)]="carreraSeleccionada" interface="popover">
|
|
<ion-select-option value="tecnologias">Lic. en Tecnologías Digitales</ion-select-option>
|
|
<ion-select-option value="datos">Lic. en Ciencia de Datos</ion-select-option>
|
|
<ion-select-option value="videojuegos">Lic. en Des. de Videojuegos</ion-select-option>
|
|
</ion-select>
|
|
</ion-item>
|
|
</ion-card-content>
|
|
</ion-card>
|
|
|
|
<div *ngIf="carreraSeleccionada" class="tabla-recorrido-container fade-in">
|
|
<table class="recorrido-table">
|
|
<thead>
|
|
<tr>
|
|
<th>1° Cuat.</th>
|
|
<th>2° Cuat.</th>
|
|
<th>3° Cuat.</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr *ngFor="let row of getRecorridoData()">
|
|
<td>
|
|
<div class="materia-cell" [class.aprobada]="isAprobada(row.c1.name)"
|
|
[class.encurso]="isEnCurso(row.c1.name)" [class.optativa-cell]="row.c1.isOptativa"
|
|
(click)="handleCellClick(row.c1)">
|
|
<span class="m-name">{{ abreviarMateria(row.c1.name) }}</span>
|
|
<ion-badge *ngIf="getSiuMateriaParaCelda(row.c1.name) as siuMat"
|
|
[ngClass]="'custom-badge-' + getNotaColor(siuMat.nota)" class="siu-cell-badge">
|
|
{{ getSiuBadgeText(siuMat) }}
|
|
</ion-badge>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="materia-cell" [class.aprobada]="isAprobada(row.c2.name)"
|
|
[class.encurso]="isEnCurso(row.c2.name)" [class.optativa-cell]="row.c2.isOptativa"
|
|
(click)="handleCellClick(row.c2)">
|
|
<span class="m-name">{{ abreviarMateria(row.c2.name) }}</span>
|
|
<ion-badge *ngIf="getSiuMateriaParaCelda(row.c2.name) as siuMat"
|
|
[ngClass]="'custom-badge-' + getNotaColor(siuMat.nota)" class="siu-cell-badge">
|
|
{{ getSiuBadgeText(siuMat) }}
|
|
</ion-badge>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="materia-cell" [class.aprobada]="isAprobada(row.c3.name)"
|
|
[class.encurso]="isEnCurso(row.c3.name)" [class.optativa-cell]="row.c3.isOptativa"
|
|
(click)="handleCellClick(row.c3)">
|
|
<span class="m-name">{{ abreviarMateria(row.c3.name) }}</span>
|
|
<ion-badge *ngIf="getSiuMateriaParaCelda(row.c3.name) as siuMat"
|
|
[ngClass]="'custom-badge-' + getNotaColor(siuMat.nota)" class="siu-cell-badge">
|
|
{{ getSiuBadgeText(siuMat) }}
|
|
</ion-badge>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<!-- The SIU Guaraní list at the bottom was removed per user request -->
|
|
</div>
|
|
|
|
|
|
</ion-content>
|
|
|
|
<ion-footer class="ion-no-border">
|
|
<ion-toolbar class="footer-toolbar">
|
|
<div class="modern-footer-btn" (click)="openMap()" title="Abrir en Mapas">
|
|
<ion-icon name="location" class="location-icon"></ion-icon>
|
|
<div class="footer-text-column">
|
|
<span class="footer-title">U de la Ciudad</span>
|
|
<span class="footer-address">Tte. Gral. J.D. Perón 802 • CABA</span>
|
|
</div>
|
|
<ion-icon name="navigate-circle" class="nav-icon"></ion-icon>
|
|
</div>
|
|
</ion-toolbar>
|
|
</ion-footer> |