5.8 KiB
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 |