academia/docs/entregables/02-diseno/arquitectura/DI-005-arquitectura-fronten...

5.8 KiB

DI-005: Arquitectura Frontend

Proyecto: Sistema de Registro de Estudiantes Fecha: 2026-01-07


1. Stack Tecnológico

Tecnología Propósito
Angular 21 Framework SPA
Angular Material UI Components
Apollo Angular Cliente GraphQL
Signals Estado reactivo
TypeScript Tipado estático

2. Estructura de Carpetas

src/frontend/src/app/
├── core/                        # Singleton services
│   ├── services/
│   │   ├── student.service.ts
│   │   └── enrollment.service.ts
│   ├── graphql/
│   │   ├── generated/           # Tipos generados
│   │   ├── queries/
│   │   │   ├── students.graphql
│   │   │   └── subjects.graphql
│   │   └── mutations/
│   │       ├── student.graphql
│   │       └── enrollment.graphql
│   └── interceptors/
│       └── error.interceptor.ts
│
├── shared/                      # Reutilizables
│   ├── components/
│   │   ├── confirm-dialog/
│   │   └── loading-spinner/
│   └── pipes/
│       └── credits.pipe.ts
│
└── features/                    # Módulos por funcionalidad
    ├── students/
    │   ├── pages/
    │   │   ├── student-list/
    │   │   └── student-form/
    │   └── components/
    │       └── student-card/
    │
    ├── enrollment/
    │   ├── pages/
    │   │   └── enrollment-page/
    │   └── components/
    │       ├── subject-selector/
    │       └── enrolled-subjects/
    │
    └── classmates/
        └── pages/
            └── classmates-page/

3. Configuración Apollo

// app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(),
    provideApollo(() => ({
      link: httpLink.create({ uri: environment.graphqlUrl }),
      cache: new InMemoryCache(),
      defaultOptions: {
        watchQuery: { fetchPolicy: 'cache-and-network' },
        mutate: { errorPolicy: 'all' }
      }
    }))
  ]
};

4. Gestión de Estado

┌─────────────────────────────────────────────┐
│              Apollo Cache                    │
│  (Estado del servidor: students, subjects)  │
└─────────────────────────────────────────────┘
                    ▲
                    │ watchQuery / mutate
                    ▼
┌─────────────────────────────────────────────┐
│              Services                        │
│  (Encapsulan operaciones GraphQL)           │
└─────────────────────────────────────────────┘
                    ▲
                    │ inject()
                    ▼
┌─────────────────────────────────────────────┐
│              Components                      │
│  (Signals para estado local/UI)             │
└─────────────────────────────────────────────┘

5. Ejemplo de Service

@Injectable({ providedIn: 'root' })
export class StudentService {
  private apollo = inject(Apollo);

  getStudents() {
    return this.apollo.watchQuery<{ students: Student[] }>({
      query: GET_STUDENTS,
      fetchPolicy: 'cache-and-network'
    }).valueChanges;
  }

  createStudent(input: CreateStudentInput) {
    return this.apollo.mutate<{ createStudent: StudentPayload }>({
      mutation: CREATE_STUDENT,
      variables: { input },
      refetchQueries: [{ query: GET_STUDENTS }]
    });
  }
}

6. Ejemplo de Componente (Signals)

@Component({
  selector: 'app-student-list',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    @if (loading()) {
      <mat-spinner />
    } @else {
      <mat-table [dataSource]="students()">
        <!-- columns -->
      </mat-table>
    }
  `
})
export class StudentListComponent {
  private studentService = inject(StudentService);

  students = signal<Student[]>([]);
  loading = signal(true);

  constructor() {
    this.studentService.getStudents().subscribe(({ data, loading }) => {
      this.students.set(data?.students ?? []);
      this.loading.set(loading);
    });
  }
}

7. Rutas (Lazy Loading)

export const routes: Routes = [
  { path: '', redirectTo: 'students', pathMatch: 'full' },
  {
    path: 'students',
    loadComponent: () => import('./features/students/pages/student-list/student-list.component')
  },
  {
    path: 'students/new',
    loadComponent: () => import('./features/students/pages/student-form/student-form.component')
  },
  {
    path: 'enrollment/:studentId',
    loadComponent: () => import('./features/enrollment/pages/enrollment-page/enrollment-page.component')
  },
  {
    path: 'classmates/:studentId',
    loadComponent: () => import('./features/classmates/pages/classmates-page/classmates-page.component')
  }
];

8. Decisiones de Diseño

Decisión Razón
Standalone Components Angular moderno, tree-shakeable
Signals vs RxJS Más simple para estado local, menos boilerplate
Apollo Cache Single source of truth para datos del servidor
OnPush Mejor rendimiento, menos re-renders
Lazy Loading Bundle inicial pequeño