feat: actualización a v1.1.9 con Agenda interactiva y correcciones visuales

This commit is contained in:
Carlos Tello
2026-03-27 07:43:41 -03:00
parent d3243c0166
commit f1da6175ae
20 changed files with 1525 additions and 404 deletions
+113
View File
@@ -0,0 +1,113 @@
import { Injectable } from '@angular/core';
import { Preferences } from '@capacitor/preferences';
import { LocalNotifications, LocalNotificationSchema } from '@capacitor/local-notifications';
import { Haptics, ImpactStyle } from '@capacitor/haptics';
import { EventoAcademico } from './data';
@Injectable({
providedIn: 'root'
})
export class EventosService {
private readonly PREF_EVENTOS = 'eventos_academicos';
private eventos: EventoAcademico[] = [];
constructor() {
this.loadEventos();
}
async loadEventos(): Promise<EventoAcademico[]> {
const { value } = await Preferences.get({ key: this.PREF_EVENTOS });
if (value) {
this.eventos = JSON.parse(value);
}
return this.eventos;
}
getEventosByMateria(codigo: string): EventoAcademico[] {
return this.eventos.filter(e => e.materiaCodigo === codigo);
}
getAllEventos(): EventoAcademico[] {
return this.eventos;
}
async saveEvento(evento: EventoAcademico) {
const index = this.eventos.findIndex(e => e.id === evento.id);
if (index > -1) {
this.eventos[index] = evento;
} else {
this.eventos.push(evento);
}
await this.syncStorage();
await this.scheduleNotifications(evento);
await Haptics.impact({ style: ImpactStyle.Medium }).catch(() => {});
}
async deleteEvento(id: string) {
this.eventos = this.eventos.filter(e => e.id !== id);
await this.syncStorage();
await this.cancelNotifications(id);
await Haptics.impact({ style: ImpactStyle.Light }).catch(() => {});
}
private async syncStorage() {
await Preferences.set({
key: this.PREF_EVENTOS,
value: JSON.stringify(this.eventos)
});
}
/**
* Programa notificaciones escalonadas para un hito académico: 7 días, 1 día y 2 horas antes.
*/
async scheduleNotifications(evento: EventoAcademico) {
const now = new Date();
const eventDate = new Date(evento.fecha);
if (eventDate <= now) return;
// Cancelamos notificaciones previas de este ID
await this.cancelNotifications(evento.id);
const notifs: LocalNotificationSchema[] = [];
const baseId = this.stringToNumericId(evento.id);
const milestones = [
{ label: 'en 7 días', offset: 7 * 24 * 60 * 60 * 1000, suffix: '7d' },
{ label: 'mañana', offset: 24 * 60 * 60 * 1000, suffix: '1d' },
{ label: 'en 2 horas', offset: 2 * 60 * 60 * 1000, suffix: '2h' }
];
milestones.forEach((m, i) => {
const scheduleAt = new Date(eventDate.getTime() - m.offset);
if (scheduleAt > now) {
notifs.push({
id: baseId + i,
title: `📌 Recordatorio: ${evento.tipo}`,
body: `${evento.materiaCodigo}: ${m.label} es el examen/entrega.`,
schedule: { at: scheduleAt },
extra: { eventoId: evento.id, codigo: evento.materiaCodigo }
});
}
});
if (notifs.length > 0) {
await LocalNotifications.schedule({ notifications: notifs });
}
}
async cancelNotifications(eventoId: string) {
const baseId = this.stringToNumericId(eventoId);
const ids = [baseId, baseId + 1, baseId + 2];
await LocalNotifications.cancel({ notifications: ids.map(id => ({ id })) }).catch(() => {});
}
private stringToNumericId(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0;
}
return Math.abs(hash % 100000) + 200000;
}
}