feat(v1.2.1): Integración nativa de Google Calendar UI y exportación dual offline WebView/Share

This commit is contained in:
2026-03-27 16:37:42 -03:00
parent f1da6175ae
commit ca33ed19ce
67 changed files with 995 additions and 285 deletions
+116 -33
View File
@@ -7,6 +7,9 @@
Cartelera de Aulas
</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)="isInfoModalOpen = true">
<ion-icon slot="icon-only" name="information-circle-outline"></ion-icon>
</ion-button>
@@ -19,6 +22,7 @@
<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>
@@ -43,40 +47,58 @@
</ion-header>
<ion-content class="ion-padding privacy-content">
<h2>¿Qué hace esta aplicación?</h2>
<p><strong>Aulas UNICABA</strong> es un visor especializado e independiente para la cartelera de horarios, aulas y
uso de credencial de la Universidad de la Ciudad.</p>
<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>Consulta materias, horarios y aulas rápidamente.</li>
<li>Sigue tus <strong>Favoritos</strong> y recibe <strong>alertas locales</strong> si cambian de aula.</li>
<li><strong>¡NUEVO!</strong> Visualiza tu <strong>Credencial Digital</strong> de estudiante tocando el logo de
la UNICABA en la cabecera.</li>
<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>
<h2>Credencial Digital (Opcional)</h2>
<p>El inicio de sesión para obtener la credencial es <strong>100% opcional</strong> y completamente independiente
de la cartelera de aulas (que puedes seguir usando sin loguearte).</p>
<p>Tus credenciales (DNI y Clave) se comunican <strong>directamente</strong> con el Campus Virtual (Moodle) para
descargar el PDF de tu código QR, sin pasar por ningún servidor intermediario. Luego, la imagen queda guardada
localmente en tu celular para que puedas mostrarla incluso sin conexión a internet.</p>
<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>
<h2>Almacenamiento Seguro y Persistente</h2>
<p>Tus datos son tuyos. Aseguramos que tus materias favoritas se guarden automáticamente en un archivo
<code>.csv</code> seguro dentro de la carpeta "Documentos" de tu dispositivo.
<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>Gracias a esto, toda tu configuración y datos están a salvo y <strong>sobrevivirán a desinstalaciones</strong>
o actualizaciones de la aplicación sin tener que subirlos a la nube.</p>
<h2>Privacidad Absoluta</h2>
<p><strong>Sin Intermediarios:</strong> No recopilamos nombres, contraseñas, ni correos. Todo el procesamiento se
realiza en la memoria de tu teléfono.</p>
<p><strong>Sin Rastreadores:</strong> No incluimos publicidad, telemetría ni analíticas de terceros.</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>
<p class="ion-text-center"><small>Versión 1.1.9 - 26/03/2026</small></p>
</ion-content>
</ng-template>
</ion-modal>
<ion-content [fullscreen]="true" color="light">
<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>
@@ -123,7 +145,7 @@
<p>{{ errorMsg }}</p>
</div>
<ion-list *ngIf="!loading && !errorMsg && horariosFiltrados.length > 0">
<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">
@@ -174,21 +196,82 @@
</ion-card>
</ion-list>
<div class="empty-state" *ngIf="!loading && !errorMsg && horariosFiltrados.length === 0">
<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>
<ion-button fill="clear" size="small" (click)="forzarRespaldo()" style="margin: 0;">
<ion-icon name="save-outline" slot="icon-only" color="primary"></ion-icon>
</ion-button>
</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)">
{{ row.c1.name }}
</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)">
{{ row.c2.name }}
</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)">
{{ row.c3.name }}
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</ion-content>
<ion-footer>
<ion-toolbar color="primary" class="ion-no-padding">
<div class="footer-content"
(click)="openLink('https://www.google.com/maps/place/UdelaCiudad/data=!4m2!3m1!1s0x0:0x6dab35ab5ab98245?sa=X&ved=1t:2428&ictx=111')"
style="cursor: pointer;" title="Mostrar en el mapa">
<div class="footer-text">
<small><ion-icon name="location-outline" style="vertical-align: bottom;"></ion-icon> Tte. Gral. Juan Domingo
Perón 802 (C1038AAR)<br />CABA - Argentina</small>
<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>