feat: catalogo dinamico de tipificaciones de hitos, matriz RBAC y desacoplamiento en modal de eventos

This commit is contained in:
Carlos Tello
2026-09-03 03:48:49 -03:00
parent 0c867e0031
commit 291e537574
13 changed files with 777 additions and 23 deletions
@@ -16,19 +16,10 @@
<ion-list lines="inset" class="compact-list">
<ion-item class="input-item">
<ion-label position="stacked">Tipo e Hito</ion-label>
<ion-select [(ngModel)]="tipo" interface="popover">
<ion-select-option value="Parcial 1">Parcial 1</ion-select-option>
<ion-select-option value="Parcial 2">Parcial 2</ion-select-option>
<ion-select-option value="TP 1">Trabajo Práctico 1</ion-select-option>
<ion-select-option value="TP 2">Trabajo Práctico 2</ion-select-option>
<ion-select-option value="TP 3">Trabajo Práctico 3</ion-select-option>
<ion-select-option value="Recu. P1">Recuperatorio Parcial 1</ion-select-option>
<ion-select-option value="Recu. P2">Recuperatorio Parcial 2</ion-select-option>
<ion-select-option value="Recu. TP 1">Recuperatorio TP 1</ion-select-option>
<ion-select-option value="Recu. TP 2">Recuperatorio TP 2</ion-select-option>
<ion-select-option value="Recu. TP 3">Recuperatorio TP 3</ion-select-option>
<ion-select-option value="Final">Final</ion-select-option>
<ion-select-option value="Otro">Otro</ion-select-option>
<ion-select [(ngModel)]="tipo" interface="popover" placeholder="Seleccionar tipo de hito">
<ion-select-option *ngFor="let opt of milestoneTypes" [value]="opt.name">
{{ opt.name }}
</ion-select-option>
</ion-select>
</ion-item>
@@ -7,6 +7,36 @@ import { EventosService } from '../../services/eventos.service';
import { CapacitorCalendar } from '@ebarooni/capacitor-calendar';
@Component({
selector: 'app-evento-modal',
templateUrl: './evento-modal.component.html',
styleUrls: ['./evento-modal.component.scss'],
standalone: true,
imports: [CommonModule, IonicModule, FormsModule]
})
export interface MilestoneTypeOption {
id?: number;
name: string;
code?: string;
color?: string;
is_active?: boolean;
}
export const DEFAULT_MILESTONE_TYPES: MilestoneTypeOption[] = [
{ name: 'Parcial 1', code: 'parcial_1', color: '#0d6efd' },
{ name: 'Parcial 2', code: 'parcial_2', color: '#0b5ed7' },
{ name: 'Trabajo Práctico 1', code: 'tp_1', color: '#198754' },
{ name: 'Trabajo Práctico 2', code: 'tp_2', color: '#157347' },
{ name: 'Trabajo Práctico 3', code: 'tp_3', color: '#0f5132' },
{ name: 'Recuperatorio Parcial 1', code: 'recu_p1', color: '#fd7e14' },
{ name: 'Recuperatorio Parcial 2', code: 'recu_p2', color: '#e76b08' },
{ name: 'Recuperatorio TP 1', code: 'recu_tp1', color: '#ffc107' },
{ name: 'Recuperatorio TP 2', code: 'recu_tp2', color: '#e0a800' },
{ name: 'Recuperatorio TP 3', code: 'recu_tp3', color: '#c69500' },
{ name: 'Final', code: 'final', color: '#dc3545' },
{ name: 'Otro', code: 'otro', color: '#6c757d' }
];
@Component({
selector: 'app-evento-modal',
templateUrl: './evento-modal.component.html',
@@ -19,7 +49,8 @@ export class EventoModalComponent implements OnInit {
@Input() materiaNombre!: string;
@Input() eventoExistente?: EventoAcademico;
tipo: 'Parcial' | 'TP' | 'Final' | 'Otro' = 'Parcial';
tipo: string = 'Parcial 1';
milestoneTypes: MilestoneTypeOption[] = DEFAULT_MILESTONE_TYPES;
fecha: string = new Date().toISOString();
aula: string = '';
link: string = '';
@@ -34,7 +65,9 @@ export class EventoModalComponent implements OnInit {
private toastCtrl: ToastController
) { }
ngOnInit() {
async ngOnInit() {
await this.cargarTipificaciones();
if (this.eventoExistente) {
this.isEdit = true;
this.tipo = this.eventoExistente.tipo;
@@ -45,6 +78,20 @@ export class EventoModalComponent implements OnInit {
}
}
async cargarTipificaciones() {
try {
const response = await fetch('/api/milestone-types');
if (response.ok) {
const data: MilestoneTypeOption[] = await response.json();
if (Array.isArray(data) && data.length > 0) {
this.milestoneTypes = data.filter(d => d.is_active !== false);
}
}
} catch {
// Mantiene catálogo predeterminado si no hay conexión al backend
}
}
dismiss() {
this.modalCtrl.dismiss();
}
@@ -18,7 +18,7 @@ export interface ClaseHorario {
export interface EventoAcademico {
id: string;
materiaCodigo: string;
tipo: 'Parcial' | 'TP' | 'Final' | 'Otro';
tipo: string; // Dinámico según catálogo MilestoneType
fecha: string; // ISO string
aula?: string;
link?: string;