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 { 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; } }