Table of Contents
- Migración del Sistema Académico
- De VB.NET WinForms a Angular + .NET API
- Tabla de Contenidos
- Introducción
- Análisis del Sistema Original
- Arquitectura del Nuevo Sistema
- Backend - API .NET
- Estructura del Backend
- 1. Test.Web - API Layer
- 2. Test.Core - Business Logic Layer
- 3. Test.Database - Data Access Layer
- Mapeo de VB.NET a .NET API
- Frontend - Angular
- Estructura del Frontend
- Arquitectura por Capas del Frontend
- 1. Módulo Shared
- 2. Módulo Academic > Maintenance
- 4. Layout Module
- Routing Configuration
- Características UI/UX
- Mapeo de VB.NET WinForms a Angular
- Prompts Utilizados
- 1. Diseño de Arquitectura Backend
- 2. Desarrollo del Mockup del Frontend
- 3. Componentes especificos del frontend
- 4. Integración Frontend-Backend
- Resultados y Capturas
- Conclusiones
- Apéndices
Migración del Sistema Académico
De VB.NET WinForms a Angular + .NET API
Tabla de Contenidos
- Introducción
- Análisis del Sistema Original
- Arquitectura del Nuevo Sistema
- Backend - API .NET
- Frontend - Angular
- Prompts Utilizados
- Resultados
Introducción
Este documento describe el proceso de migración de un sistema académico legacy desarrollado en VB.NET WinForms hacia una arquitectura moderna basada en:
- Frontend: Angular 19+ con Material Design
- Backend: .NET 9 Web API con arquitectura en capas
Objetivos del Proyecto
- Modernizar la interfaz de usuario
- Separar frontend y backend (arquitectura desacoplada)
- Implementar mejores prácticas de desarrollo
- Mejorar la experiencia de usuario (UX)
- Facilitar el mantenimiento y escalabilidad del código
Documentación de Referencia
Para realizar la migración, se utilizaron los siguientes documentos del sistema original:
| Documento | Descripción | Ubicación |
|---|---|---|
Backend.txt |
Código VB.NET del formulario de Familias | /Test/Backend.txt |
Backend2.txt |
Código VB.NET del formulario de Alumnos | /Test/Backend2.txt |
TablesInfo.txt |
Esquemas de tablas principales (KARDEX, DATOS_ALUMNOS, etc.) | /Test/TablesInfo.txt |
TablesInfo2.txt |
Esquemas de tablas complementarias (ALUMEDICA, ALUOTROS, etc.) | /Test/TablesInfo2.txt |
Análisis del Sistema Original
Sistema VB.NET WinForms
El sistema original fue desarrollado en Visual Basic .NET usando Windows Forms, con las siguientes características:
Arquitectura Original
┌─────────────────────────────────────┐
│ Aplicación WinForms (VB.NET) │
│ ┌───────────────────────────────┐ │
│ │ Formularios (UI + Lógica) │ │
│ └───────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────┐ │
│ │ Acceso directo a SQL Server │ │
│ │ (ADO.NET con SqlCommand) │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
↓
┌───────────────┐
│ SQL Server │
└───────────────┘
Problemas Identificados
- Acoplamiento UI-Lógica: Formularios contenían tanto la interfaz como la lógica de negocio
- Queries SQL embebidos: Consultas SQL escritas directamente en el código del formulario
- Validaciones dispersas: Lógica de validación mezclada con código de interfaz
- Sin separación de capas: No había separación clara entre presentación, lógica y datos
- Difícil de mantener: Cambios en la UI afectaban la lógica de negocio
{width=1440 height=810}
Captura del sistema original en VB.NET WinForms
Arquitectura del Nuevo Sistema
Arquitectura Moderna (Desacoplada)
┌──────────────────────────────────────────────────────────┐
│ FRONTEND (Angular) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Components │ │ Services │ │ Models │ │
│ │ (UI) │→ │ (HTTP) │→ │ (TypeScript)│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└──────────────────────────────────────────────────────────┘
↓ HTTP/REST
┌──────────────────────────────────────────────────────────┐
│ BACKEND (.NET API) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Test.Web (Controllers) │ │
│ │ - AuthController, FamiliesController, etc. │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Test.Core (Business Logic) │ │
│ │ - Services, DTOs, Validators, Helpers │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Test.Database (Data Access) │ │
│ │ - Repositories, Models, Connection │ │
│ └─────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
↓
┌─────────────────┐
│ SQL Server │
└─────────────────┘
Ventajas de la Nueva Arquitectura
- Separación de responsabilidades: Cada capa tiene un propósito específico
- Escalabilidad: Frontend y backend pueden escalar independientemente
- Mantenibilidad: Código organizado y fácil de localizar
- Testabilidad: Cada capa puede ser probada de forma aislada
- Reutilización: El backend puede ser consumido por múltiples clientes
Backend - API .NET
Estructura del Backend
El backend sigue una arquitectura en capas (Layered Architecture) con tres proyectos principales:
Test/
├── Test.Web/ # 🌐 Capa de Presentación (API)
├── Test.Core/ # 💼 Capa de Lógica de Negocio
└── Test.Database/ # 💾 Capa de Acceso a Datos
1. Test.Web - API Layer
Responsabilidad: Exponer endpoints HTTP/REST y manejar requests/responses
Estructura
Test.Web/
├── Controllers/
│ ├── FamiliesController.cs # CRUD de Familias
│ ├── StudentsController.cs # CRUD de Alumnos
│ └── LookupController.cs # Catálogos (Colegios, Niveles, etc.)
├── DTOs/
│ └── Common/ # DTOs compartidos
├── Program.cs # Configuración de la aplicación
└── appsettings.json # Configuración (Connection String)
Características Principales
1.1 Controllers
Los controladores siguen el patrón Repository + Service:
[ApiController]
[Route("api/[controller]")]
public class FamiliesController : ControllerBase
{
private readonly IFamilyService _familyService;
private readonly ILogger<FamiliesController> _logger;
public FamiliesController(IFamilyService familyService, ILogger<FamiliesController> logger)
{
_familyService = familyService;
_logger = logger;
}
[HttpGet]
public async Task<ActionResult<ApiResponse<IEnumerable<FamilyDto>>>> GetAll()
{
var families = await _familyService.GetAllAsync();
return Ok(new ApiResponse<IEnumerable<FamilyDto>>(families));
}
}
1.2 CORS Configuration
Configurado para permitir requests desde el frontend Angular:
builder.Services.AddCors(options =>
{
options.AddPolicy("DevelopmentPolicy", policy =>
{
policy.WithOrigins("http://localhost:4200")
.AllowAnyMethod()
.AllowAnyHeader();
});
});
1.3 Inyección de Dependencias
Configuración de servicios en Program.cs:
// Servicios de acceso a datos
builder.Services.AddScoped<IConnection, Connection>();
builder.Services.AddScoped<IFamilyRepository, FamilyRepository>();
builder.Services.AddScoped<IStudentRepository, StudentRepository>();
builder.Services.AddScoped<ILookupRepository, LookupRepository>();
// Servicios de lógica de negocio
builder.Services.AddScoped<IFamilyService, FamilyService>();
builder.Services.AddScoped<IStudentService, StudentService>();
builder.Services.AddScoped<ILookupService, LookupService>();
2. Test.Core - Business Logic Layer
Responsabilidad: Contener la lógica de negocio, validaciones y reglas del dominio
Estructura
Test.Core/
├── Families/
│ ├── FamilyService.cs # Lógica de negocio de Familias
│ ├── IFamilyService.cs
│ ├── LookupService.cs # Servicio de catálogos
│ ├── ILookupService.cs
│ └── DTOs/
│ ├── FamilyDto.cs
│ └── LookupDto.cs
├── Students/
│ ├── StudentService.cs # Lógica de negocio de Alumnos
│ ├── IStudentService.cs
│ └── DTOs/
│ └── StudentDto.cs
└── Common/
├── Constants/
│ └── Messages.cs # Mensajes del sistema
├── Exceptions/
│ ├── BusinessException.cs
│ ├── NotFoundException.cs
│ └── ValidationException.cs
└── Helpers/
├── MappingHelper.cs # Mapeo de entidades
└── ValidationHelper.cs # Validaciones comunes
Patrones Implementados
2.1 Service Pattern
Cada entidad tiene su servicio que encapsula la lógica de negocio:
public class FamilyService : IFamilyService
{
private readonly IFamilyRepository _repository;
private readonly ILogger<FamilyService> _logger;
public async Task<FamilyDto> CreateAsync(CreateFamilyDto dto)
{
// Validaciones
ValidationHelper.ValidateRequired(dto.Apell1P, "Apellido del padre");
// Lógica de negocio
var family = MapToEntity(dto);
var created = await _repository.CreateAsync(family);
return MapToDto(created);
}
}
2.2 DTOs (Data Transfer Objects)
Se usan DTOs para separar los modelos de base de datos de los contratos de la API:
public class FamilyDto
{
public int NumFam { get; set; }
public string Apell1P { get; set; }
public string Nom1P { get; set; }
public string TelCasa { get; set; }
public string Email { get; set; }
// ... más propiedades
}
2.3 Custom Exceptions
Manejo de errores tipificado:
public class BusinessException : Exception
{
public BusinessException(string message) : base(message) { }
}
public class NotFoundException : Exception
{
public NotFoundException(string entity, object key)
: base($"{entity} con ID {key} no encontrado") { }
}
3. Test.Database - Data Access Layer
Responsabilidad: Interacción directa con la base de datos
Estructura
Test.Database/
├── Models/
│ ├── BaseEntity.cs # Clase base para entidades
│ ├── Kardex.cs # Modelo de Familias
│ ├── DatosAlumno.cs # Modelo de Alumnos
│ ├── AlumnoMedico.cs # Datos médicos del alumno
│ ├── AlumnoOtros.cs # Otros datos del alumno
│ ├── Colegio.cs # Catálogo de colegios
│ ├── Nivel.cs # Catálogo de niveles
│ ├── Grado.cs # Catálogo de grados
│ ├── CatalogoColegio.cs # Catálogo de secciones
│ └── Direccion.cs # Datos de dirección
├── Repositories/
│ ├── Families/
│ │ ├── FamilyRepository.cs
│ │ └── IFamilyRepository.cs
│ ├── Students/
│ │ ├── StudentRepository.cs
│ │ └── IStudentRepository.cs
│ └── Lookup/
│ ├── LookupRepository.cs
│ └── ILookupRepository.cs
└── SqlServer/
├── Connection.cs # Gestión de conexión a DB
└── IConnection.cs
Características Principales
3.1 Repository Pattern
Cada repositorio maneja el acceso a datos de una entidad:
public class FamilyRepository : IFamilyRepository
{
private readonly IConnection _connection;
public async Task<IEnumerable<Kardex>> GetAllAsync()
{
const string query = @"
SELECT num_fam, apell1p, nom1p, tel_casa, email
FROM KARDEX
ORDER BY num_fam DESC";
using var connection = _connection.GetConnection();
return await connection.QueryAsync<Kardex>(query);
}
}
3.2 Modelos de Base de Datos
Entidades que representan las tablas de SQL Server:
public class Kardex : BaseEntity
{
public int NumFam { get; set; }
public string Apell1P { get; set; }
public string Nom1P { get; set; }
public string TelCasa { get; set; }
public string Email { get; set; }
// ... más propiedades que mapean a columnas
}
3.3 Connection Management
Gestión centralizada de la conexión a SQL Server:
public class Connection : IConnection
{
private readonly string _connectionString;
public Connection(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("DefaultConnection");
}
public SqlConnection GetConnection()
{
return new SqlConnection(_connectionString);
}
}
Mapeo de VB.NET a .NET API
Ejemplo: Formulario de Familias
| VB.NET Original | Nueva Arquitectura |
|---|---|
frmFamilias.vb (todo en un archivo) |
Controller → FamiliesController.cs |
| Validaciones en el formulario | Service → FamilyService.cs |
SqlCommand directo |
Repository → FamilyRepository.cs |
| Variables locales | DTO → FamilyDto.cs |
| Campos del formulario | Model → Kardex.cs |
Comparación de Código
VB.NET Original (Backend.txt):
cadena = "SELECT * FROM KARDEX WHERE num_fam = " & txtNumFam.Text
Academico.Definiciones.llenaTabla(cadena, tabla)
Nueva API (.NET):
// Repository
public async Task<Kardex> GetByIdAsync(int numFam)
{
const string query = "SELECT * FROM KARDEX WHERE num_fam = @NumFam";
using var connection = _connection.GetConnection();
return await connection.QueryFirstOrDefaultAsync<Kardex>(query, new { NumFam = numFam });
}
// Service
public async Task<FamilyDto> GetByIdAsync(int numFam)
{
var family = await _repository.GetByIdAsync(numFam);
if (family == null)
throw new NotFoundException("Familia", numFam);
return MapToDto(family);
}
// Controller
[HttpGet("{id}")]
public async Task<ActionResult<FamilyDto>> GetById(int id)
{
var family = await _familyService.GetByIdAsync(id);
return Ok(family);
}
Flujo de datos en el backend: Controller → Service → Repository → Database
Frontend - Angular
Estructura del Frontend
El frontend está desarrollado en Angular 19+ con Material Design, siguiendo una arquitectura modular:
Frontend2/academic-system/src/
├── app/
│ ├── core/ # 🔐 Servicios compartidos
│ │ └── services/
│ │ └── api.service.ts
│ ├── shared/ # 🔧 Componentes reutilizables
│ │ ├── components/
│ │ │ ├── confirm-dialog/
│ │ │ └── loading-spinner/
│ │ └── models/
│ ├── modules/
│ │ ├── layout/ # 📐 Layout principal
│ │ │ ├── sidebar/
│ │ │ ├── header/
│ │ │ └── layout.module.ts
│ │ └── academic/ # 📚 Módulo académico
│ │ └── maintenance/ # Mantenimientos
│ │ ├── pages/
│ │ │ ├── families/
│ │ │ │ ├── families-list/
│ │ │ │ └── family-dialog/
│ │ │ └── students/
│ │ │ ├── students-list/
│ │ │ └── student-dialog/
│ │ ├── services/
│ │ │ ├── family-data.service.ts
│ │ │ ├── student-data.service.ts
│ │ │ ├── lookup.service.ts
│ │ │ ├── family-mapper.ts
│ │ │ └── student-mapper.ts
│ │ └── models/
│ │ ├── family-data.ts
│ │ ├── student-data.ts
│ │ └── backend/
│ │ ├── create-family-request.ts
│ │ └── student-dtos.ts
│ ├── environments/
│ │ ├── environment.ts
│ │ └── environment.development.ts
│ └── app.routes.ts
├── assets/
└── styles.scss
Arquitectura por Capas del Frontend
┌─────────────────────────────────────────────────────────┐
│ PRESENTACIÓN │
│ Components (Smart & Presentational) │
│ - families-list, family-dialog, students-list, etc. │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ SERVICIOS │
│ Services (Business Logic del Frontend) │
│ - FamilyDataService, StudentDataService, etc. │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ MAPPERS │
│ Data Transformation │
│ - FamilyMapper, StudentMapper │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ HTTP CLIENT │
│ Comunicación con Backend API │
│ - HttpClient + Interceptors │
└─────────────────────────────────────────────────────────┘
1. Módulo Shared
Responsabilidad: Componentes, pipes y directivas reutilizables
Componentes Compartidos
// confirm-dialog.component.ts
@Component({
selector: 'app-confirm-dialog',
template: `
<h2 mat-dialog-title>{{ data.title }}</h2>
<mat-dialog-content>{{ data.message }}</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="onCancel()">Cancelar</button>
<button mat-raised-button color="warn" (click)="onConfirm()">Confirmar</button>
</mat-dialog-actions>
`
})
export class ConfirmDialogComponent {
constructor(
public dialogRef: MatDialogRef<ConfirmDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { title: string; message: string }
) {}
}
2. Módulo Academic > Maintenance
Responsabilidad: Funcionalidades de mantenimiento académico (Familias, Alumnos)
2.1 Familias (Family Management)
Componente de Lista
// families-list.component.ts
@Component({
selector: 'app-families-list',
imports: [
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatIconModule,
MatButtonModule,
MatFormFieldModule,
MatInputModule,
MatCardModule,
MatDialogModule,
MatSnackBarModule,
DatePipe,
NgIf
],
templateUrl: './families-list.component.html',
styleUrl: './families-list.component.scss'
})
export class FamiliesListComponent implements OnInit, AfterViewInit {
displayedColumns: string[] = [
'apell1',
'padre',
'madre',
'telefono',
'telefonoEmergencia',
'acciones'
];
dataSource = new MatTableDataSource<FamilyData>([]);
isLoading = false;
@ViewChild(MatPaginator) paginator!: MatPaginator;
@ViewChild(MatSort) sort!: MatSort;
constructor(
private familyService: FamilyDataService,
private dialog: MatDialog,
private snackBar: MatSnackBar,
private cdr: ChangeDetectorRef
) {}
ngOnInit(): void {
this.loadFamilies();
}
ngAfterViewInit(): void {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
this.cdr.detectChanges();
}
loadFamilies(): void {
this.isLoading = true;
this.familyService.getAll().subscribe({
next: (families) => {
this.dataSource.data = families;
this.isLoading = false;
},
error: (err) => {
this.showError('Error al cargar familias');
this.isLoading = false;
}
});
}
applyFilter(filterValue: string): void {
this.dataSource.filter = filterValue.trim().toLowerCase();
}
openCreateFamilyDialog(): void {
const dialogRef = this.dialog.open(FamilyDialogComponent, {
width: '800px',
data: null
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.loadFamilies();
}
});
}
openEditFamilyDialog(family: FamilyData): void {
const dialogRef = this.dialog.open(FamilyDialogComponent, {
width: '800px',
data: family
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.loadFamilies();
}
});
}
confirmDeleteFamily(family: FamilyData): void {
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
data: {
title: 'Confirmar eliminación',
message: `¿Está seguro de eliminar la familia ${family.apell1}?`
}
});
dialogRef.afterClosed().subscribe(confirmed => {
if (confirmed && family.id) {
this.familyService.delete(family.id).subscribe({
next: () => {
this.showSuccess('Familia eliminada correctamente');
this.loadFamilies();
},
error: () => this.showError('Error al eliminar familia')
});
}
});
}
private showError(message: string): void {
this.snackBar.open(message, 'Cerrar', { duration: 3000 });
}
private showSuccess(message: string): void {
this.snackBar.open(message, 'Cerrar', { duration: 3000 });
}
}
Template de Lista
<!-- families-list.component.html -->
<div class="page-container">
<!-- Header Section -->
<div class="page-header">
<div class="header-content">
<div class="title-section">
<h1 class="page-title">Mantenimiento de Familias</h1>
<p class="page-description">Gestión de datos familiares del sistema académico</p>
</div>
<button mat-flat-button color="primary" class="create-button" (click)="openCreateFamilyDialog()">
<mat-icon>add</mat-icon>
Nueva Familia
</button>
</div>
</div>
<!-- Filters Card -->
<mat-card class="filters-card">
<mat-card-header>
<mat-card-title>Filtros</mat-card-title>
</mat-card-header>
<mat-card-content>
<mat-form-field appearance="outline" class="filter-field">
<mat-icon matPrefix>search</mat-icon>
<input #search matInput placeholder="Buscar por nombre de encargado, madre o padre..." (keyup)="applyFilter(search.value)" />
</mat-form-field>
</mat-card-content>
</mat-card>
<!-- Families List Card -->
<mat-card class="families-card">
<mat-card-header>
<mat-card-title>Lista de Familias ({{ dataSource.data.length }})</mat-card-title>
</mat-card-header>
<mat-card-content>
<div class="table-wrapper" *ngIf="!isLoading; else loadingTpl">
<table mat-table [dataSource]="dataSource" matSort class="families-table">
<ng-container matColumnDef="apell1">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Apellido</th>
<td mat-cell *matCellDef="let row">{{ row.apell1 || 'N/A' }}</td>
</ng-container>
<ng-container matColumnDef="padre">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Padre</th>
<td mat-cell *matCellDef="let row">{{ row.nombrePadre || 'N/A' }}</td>
</ng-container>
<ng-container matColumnDef="madre">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Madre</th>
<td mat-cell *matCellDef="let row">{{ row.nombreMadre || 'N/A' }}</td>
</ng-container>
<ng-container matColumnDef="telefono">
<th mat-header-cell *matHeaderCellDef>Teléfono</th>
<td mat-cell *matCellDef="let row">{{ row.telefono || 'N/A' }}</td>
</ng-container>
<ng-container matColumnDef="telefonoEmergencia">
<th mat-header-cell *matHeaderCellDef>Tel. Emergencia</th>
<td mat-cell *matCellDef="let row">{{ row.telefonoEmergencia || 'N/A' }}</td>
</ng-container>
<ng-container matColumnDef="acciones">
<th mat-header-cell *matHeaderCellDef>Acciones</th>
<td mat-cell *matCellDef="let row">
<button mat-icon-button color="primary" aria-label="Editar" (click)="openEditFamilyDialog(row)">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button color="warn" aria-label="Eliminar" (click)="confirmDeleteFamily(row)">
<mat-icon>delete</mat-icon>
</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<mat-paginator
[pageSize]="25"
[pageSizeOptions]="[25, 50, 100]"
showFirstLastButtons>
</mat-paginator>
</div>
<ng-template #loadingTpl>
<div class="loading">Cargando...</div>
</ng-template>
</mat-card-content>
</mat-card>
</div>
{width=1077 height=701}
Pantalla de gestión de familias con tabla Material Design
Componente de Diálogo (Crear/Editar)
// family-dialog.component.ts
@Component({
selector: 'app-family-dialog',
templateUrl: './family-dialog.component.html',
styleUrl: './family-dialog.component.scss'
})
export class FamilyDialogComponent implements OnInit {
// Formularios para cada pestaña
encargadoForm!: FormGroup;
madreForm!: FormGroup;
padreForm!: FormGroup;
emergenciaForm!: FormGroup;
// Datos de catálogo
zonas: any[] = [];
departamentos: any[] = [];
municipios: any[] = [];
religiones: any[] = [];
colegios: any[] = [];
nacionalidades: any[] = [];
tiposIdentificacion: any[] = [];
estadosCiviles: any[] = [];
titulosOperables: any[] = [];
titulosMadre: any[] = [];
titulosPadre: any[] = [];
isEditMode = false;
activeTabIndex = 0;
get isFormValid(): boolean {
return this.encargadoForm.valid && this.madreForm.valid &&
this.padreForm.valid && this.emergenciaForm.valid;
}
getDialogTitle(): string {
return this.isEditMode ? 'Editar Familia' : 'Nueva Familia';
}
constructor(
public dialogRef: MatDialogRef<FamilyDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private fb: FormBuilder,
private lookupService: LookupService,
private familyService: FamilyDataService,
private snackBar: MatSnackBar
) {}
ngOnInit(): void {
this.isEditMode = !!this.data;
this.initializeForms();
this.loadCatalogs();
if (this.isEditMode) {
this.loadFamilyData();
}
}
private initializeForms(): void {
this.encargadoForm = this.fb.group({
direccionCompleta: ['', Validators.required],
direccion: ['', Validators.required],
telefono: ['', [Validators.required, Validators.pattern(/^\d{8}$/)]],
encargado: ['', Validators.required],
parentesco: ['', Validators.required],
fechaNacimiento: ['', Validators.required],
noIdentidad: ['', Validators.required],
nombreFactura: ['', Validators.required],
zona: ['', Validators.required],
departamento: ['', Validators.required],
municipio: ['', Validators.required],
religion: ['', Validators.required],
colegio: ['', Validators.required],
sexo: ['', Validators.required],
nacionalidad: ['', Validators.required],
tipoIdentidad: ['', Validators.required]
});
this.madreForm = this.fb.group({
primerApellido: ['', Validators.required],
segundoApellido: ['', Validators.required],
tercerApellido: [''],
primerNombre: ['', Validators.required],
segundoNombre: ['', Validators.required],
tercerNombre: [''],
celular: ['', [Validators.required, Validators.pattern(/^\d{8}$/)]],
telefonoOficina: ['', [Validators.required, Validators.pattern(/^\d{8}$/)]],
correoElectronico: ['', [Validators.email]],
estadoCivil: ['', Validators.required],
nivelAcademico: ['', Validators.required],
tituloAcademico: ['', Validators.required]
});
this.padreForm = this.fb.group({
primerApellido: ['', Validators.required],
segundoApellido: ['', Validators.required],
tercerApellido: [''],
primerNombre: ['', Validators.required],
segundoNombre: ['', Validators.required],
tercerNombre: [''],
celular: ['', [Validators.required, Validators.pattern(/^\d{8}$/)]],
telefonoOficina: ['', [Validators.required, Validators.pattern(/^\d{8}$/)]],
correoElectronico: ['', [Validators.email]],
estadoCivil: ['', Validators.required],
nivelAcademico: ['', Validators.required],
tituloAcademico: ['', Validators.required]
});
this.emergenciaForm = this.fb.group({
nombre: ['', [Validators.required, Validators.minLength(2)]],
telefono: ['', [Validators.required, Validators.pattern(/^\d{8}$/)]],
direccion: ['', [Validators.required, Validators.minLength(5)]],
parentesco: ['', [Validators.required, Validators.minLength(2)]]
});
}
private async loadCatalogs(): Promise<void> {
try {
// Cargar todos los catálogos necesarios
this.zonas = await this.lookupService.getZonas().toPromise() || [];
this.departamentos = await this.lookupService.getDepartamentos().toPromise() || [];
this.religiones = await this.lookupService.getReligiones().toPromise() || [];
this.colegios = await this.lookupService.getColegios().toPromise() || [];
this.nacionalidades = await this.lookupService.getNacionalidades().toPromise() || [];
this.tiposIdentificacion = await this.lookupService.getTiposIdentificacion().toPromise() || [];
this.estadosCiviles = await this.lookupService.getEstadosCiviles().toPromise() || [];
this.titulosOperables = await this.lookupService.getTitulosOperables().toPromise() || [];
this.titulosMadre = await this.lookupService.getTitulosMadre().toPromise() || [];
this.titulosPadre = await this.lookupService.getTitulosPadre().toPromise() || [];
} catch (error) {
this.snackBar.open('Error al cargar catálogos', 'Cerrar', { duration: 3000 });
}
}
private loadFamilyData(): void {
// Cargar datos de la familia para edición
// Aquí se asignarían los valores a los formularios correspondientes
}
onDepartamentoChange(departamentoId: string): void {
// Cargar municipios según el departamento seleccionado
this.lookupService.getMunicipiosPorDepartamento(departamentoId).subscribe({
next: (municipios) => {
this.municipios = municipios;
},
error: () => {
this.snackBar.open('Error al cargar municipios', 'Cerrar', { duration: 3000 });
}
});
}
onCancel(): void {
this.dialogRef.close(false);
}
onCreateFamily(): void {
if (this.isFormValid) {
const familyData = {
encargado: this.encargadoForm.value,
madre: this.madreForm.value,
padre: this.padreForm.value,
emergencia: this.emergenciaForm.value
};
const operation = this.isEditMode
? this.familyService.update(this.data.id, familyData)
: this.familyService.create(familyData);
operation.subscribe({
next: (result) => {
this.snackBar.open(
`Familia ${this.isEditMode ? 'actualizada' : 'creada'} correctamente`,
'Cerrar',
{ duration: 3000 }
);
this.dialogRef.close(true);
},
error: (error) => {
this.snackBar.open(
`Error al ${this.isEditMode ? 'actualizar' : 'crear'} familia`,
'Cerrar',
{ duration: 3000 }
);
}
});
}
}
}
{width=747 height=571}
Formulario completo de creación/edición de familia con pestañas
3.2 Servicios
Family Data Service
// family-data.service.ts
@Injectable({
providedIn: 'root'
})
export class FamilyDataService {
private apiUrl = `${environment.apiUrl}/families`;
constructor(
private http: HttpClient,
private mapper: FamilyMapper
) {}
getAll(): Observable<FamilyData[]> {
return this.http.get<ApiResponse<FamilyDto[]>>(this.apiUrl).pipe(
map(response => response.data.map(dto => this.mapper.toFamilyData(dto)))
);
}
getById(id: number): Observable<FamilyData> {
return this.http.get<ApiResponse<FamilyDto>>(`${this.apiUrl}/${id}`).pipe(
map(response => this.mapper.toFamilyData(response.data))
);
}
create(family: FamilyData): Observable<FamilyData> {
const request = this.mapper.toCreateRequest(family);
return this.http.post<ApiResponse<FamilyDto>>(this.apiUrl, request).pipe(
map(response => this.mapper.toFamilyData(response.data))
);
}
update(id: number, family: FamilyData): Observable<FamilyData> {
const request = this.mapper.toUpdateRequest(family);
return this.http.put<ApiResponse<FamilyDto>>(`${this.apiUrl}/${id}`, request).pipe(
map(response => this.mapper.toFamilyData(response.data))
);
}
delete(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}
}
Mapper Pattern
// family-mapper.ts
@Injectable({
providedIn: 'root'
})
export class FamilyMapper {
toFamilyData(dto: FamilyDto): FamilyData {
return {
numFam: dto.numFam,
apell1P: dto.apell1P,
nom1P: dto.nom1P,
apell1M: dto.apell1M,
nom1M: dto.nom1M,
telCasa: dto.telCasa,
email: dto.email,
direccion: dto.direccion ? {
direccion: dto.direccion.direccion,
zona: dto.direccion.zona,
depto: dto.direccion.depto,
municipio: dto.direccion.municipio
} : undefined
};
}
toCreateRequest(family: FamilyData): CreateFamilyRequest {
return {
apell1P: family.apell1P,
nom1P: family.nom1P,
apell1M: family.apell1M,
nom1M: family.nom1M,
telCasa: family.telCasa,
email: family.email,
direccion: family.direccion
};
}
}
3.3 Modelos (TypeScript Interfaces)
// family-data.ts - Modelo del Frontend
export interface FamilyData {
numFam?: number;
apell1P: string;
apell2P?: string;
nom1P: string;
nom2P?: string;
apell1M: string;
apell2M?: string;
nom1M: string;
nom2M?: string;
telCasa?: string;
email?: string;
direccion?: DireccionData;
}
export interface DireccionData {
direccion?: string;
zona?: string;
depto?: string;
municipio?: string;
}
// backend/create-family-request.ts - Contrato con API
export interface CreateFamilyRequest {
apell1P: string;
nom1P: string;
apell1M: string;
nom1M: string;
telCasa?: string;
email?: string;
direccion?: DireccionData;
}
export interface FamilyDto {
numFam: number;
apell1P: string;
nom1P: string;
apell1M: string;
nom1M: string;
telCasa?: string;
email?: string;
direccion?: DireccionData;
}
4. Layout Module
Responsabilidad: Estructura de navegación de la aplicación
Sidebar Navigation
// sidebar.component.ts
export class SidebarComponent {
menuItems = [
{
title: 'Académico',
icon: 'school',
children: [
{
title: 'Mantenimientos',
icon: 'build',
children: [
{ title: 'Familias', route: '/academic/maintenance/families', icon: 'people' },
{ title: 'Alumnos', route: '/academic/maintenance/students', icon: 'person' },
{ title: 'Asignar Sección', route: '/academic/maintenance/assign-section', icon: 'assignment' }
]
}
]
},
{
title: 'Contabilidad',
icon: 'attach_money',
children: []
}
];
}
Layout completo con sidebar expandible y navegación jerárquica
Routing Configuration
// app.routes.ts
export const routes: Routes = [
{
path: '',
component: LayoutComponent,
children: [
{
path: 'academic',
loadChildren: () => import('./modules/academic/academic.module')
.then(m => m.AcademicModule)
},
{ path: '', redirectTo: 'academic/maintenance/families', pathMatch: 'full' }
]
},
{ path: '**', redirectTo: '' }
];
// maintenance-routing.module.ts
const routes: Routes = [
{
path: 'maintenance',
children: [
{ path: 'families', component: FamiliesListComponent },
{ path: 'students', component: StudentsListComponent },
{ path: '', redirectTo: 'families', pathMatch: 'full' }
]
}
];
Características UI/UX
1. Material Design
- Componentes consistentes: Uso de Angular Material en toda la aplicación
- Tema personalizable: Paleta de colores basada en la identidad corporativa
- Responsive: Adaptable a diferentes tamaños de pantalla
2. Feedback al Usuario
// Ejemplos de feedback
this.snackBar.open('Operación exitosa', 'Cerrar', { duration: 3000 });
this.dialog.open(ConfirmDialogComponent, { data: { title, message } });
3. Estados de Carga
<!-- Loading spinner -->
<mat-spinner *ngIf="isLoading"></mat-spinner>
<!-- Empty state -->
<div *ngIf="!isLoading && dataSource.data.length === 0" class="empty-state">
<mat-icon>inbox</mat-icon>
<p>No hay familias registradas</p>
</div>
4. Validaciones en Tiempo Real
<mat-form-field>
<mat-label>Email</mat-label>
<input matInput formControlName="email" type="email">
<mat-error *ngIf="familyForm.get('email')?.hasError('email')">
Email inválido
</mat-error>
<mat-error *ngIf="familyForm.get('email')?.hasError('required')">
Email es requerido
</mat-error>
</mat-form-field>
Mapeo de VB.NET WinForms a Angular
| Concepto VB.NET | Equivalente Angular |
|---|---|
Form |
Component |
DataGridView |
mat-table |
TextBox |
mat-input |
ComboBox |
mat-select |
Button |
button[mat-button] |
MessageBox |
MatSnackBar / MatDialog |
Form.Load event |
ngOnInit() lifecycle hook |
Button.Click event |
(click) event binding |
| ADO.NET DataTable | Observable<T[]> |
Prompts Utilizados
Esta sección documenta los prompts principales utilizados con IA para generar el código del proyecto.
1. Diseño de Arquitectura Backend
Test.Database
# System Prompt - Test.Database (Capa de Datos)
## CONTEXTO DEL PROYECTO
Migración de sistema VB.NET WinForms → **Angular + C# Web API**
- **Backend**: .NET 8, Clean Architecture, 3 proyectos separados
- **Frontend**: Angular 17+, TypeScript, Angular Material
- **BD**: SQL Server (misma del sistema VB original)
## ARQUITECTURA BACKEND (3 CAPAS)
'''
Test.Web -> Test.Core -> Test.Database
'''
### **Test.Database** - CAPA DE DATOS
**📍 Ubicación**: `Test.Database/`
**🎯 Responsabilidad**: Acceso a datos, conexiones BD, repositories, entidades
**🔗 Conoce**: Solo librerías de acceso a datos (System.Data, SqlClient, EF)
### ESTRUCTURA POR FEATURES:
'''
Test.Database/
├── SqlServer/
│ ├── IConnection.cs # interface IConnection
│ ├── Connection.cs # class Connection : IConnection
│ └── SqlServerContext.cs # DbContext (futuro EF)
├── Models/ # Entidades = tablas de BD
│ ├── Usuario.cs # tabla usuarios
│ ├── BaseEntity.cs # Id, FechaCreacion comunes
│ └── AuditableEntity.cs # + FechaModificacion, etc.
├── Repositories/
│ ├── Users/
│ │ ├── IUserRepository.cs # interface IUserRepository
│ │ └── UserRepository.cs # class UserRepository : IUserRepository
│ └── Base/
│ ├── IRepository.cs # Repository genérico<T>
│ └── Repository.cs # Implementación base
└── Test.Database.csproj # Sin referencias a otros proyectos negocio
'''
## REGLAS DE ARQUITECTURA
- ✅ **Contiene**: Models (entidades), Repositories, conexiones BD
- ✅ **Models = Entidades**: Representan exactamente las tablas de BD
- ✅ **Repositories con parámetros seguros**: Prevenir SQL injection
- ✅ **Patrón Repository**: Separar consultas SQL de lógica negocio
- ✅ **Async/await**: Todas las operaciones BD son asíncronas
- ❌ **NO lógica de negocio**: Solo acceso a datos puro
- ❌ **NO conoce Test.Core/Web**: Es la capa más baja
- ❌ **NO DTOs**: Solo Models que representan tablas
## ESQUEMA BD PRINCIPAL
'''sql
-- tabla usuarios (migrada de VB.NET)
CREATE TABLE usuarios (
login VARCHAR(10) NOT NULL,
nombre VARCHAR(15) NOT NULL,
apellido VARCHAR(15) NULL,
password VARCHAR(15) NOT NULL, -- Texto plano (heredado de VB.NET)
roll SMALLINT NOT NULL, -- 1=Admin, 2=Director, 3=Secretaria, 4=Profesores
rolc SMALLINT NULL, -- Rol complementario/secundario
password2 VARCHAR(15) NULL -- Password secundario/backup
);
'''
## EJEMPLO DE REPOSITORY
'''csharp
// Test.Database/Repositories/Users/UserRepository.cs
public class UserRepository : IUserRepository
{
private readonly IConnection _connection;
public async Task<Usuario?> GetByCredentialsAsync(string login, string password)
{
const string query = @"
SELECT login, nombre, apellido, password, roll, rolc, password2
FROM usuarios
WHERE login = @Login AND password = @Password
AND (roll = 1 OR roll = 2 OR roll = 3)";
using var connection = await _connection.GetConnectionAsync();
using var command = new SqlCommand(query, connection);
// Parámetros seguros
command.Parameters.AddWithValue("@Login", login);
command.Parameters.AddWithValue("@Password", password);
using var reader = await command.ExecuteReaderAsync();
return reader.Read() ? MapToUsuario(reader) : null;
}
private static Usuario MapToUsuario(SqlDataReader reader) => new Usuario
{
Login = reader.GetString("login"),
Nombre = reader.GetString("nombre"),
Apellido = reader.IsDBNull("apellido") ? null : reader.GetString("apellido"),
Password = reader.GetString("password"),
Roll = reader.GetInt16("roll"),
Rolc = reader.IsDBNull("rolc") ? null : reader.GetInt16("rolc"),
Password2 = reader.IsDBNull("password2") ? null : reader.GetString("password2")
};
}
'''
## MODELO USUARIO ACTUALIZADO
'''csharp
// Test.Database/Models/Usuario.cs
public class Usuario
{
public string Login { get; set; } = string.Empty; // VARCHAR(10) NOT NULL
public string Nombre { get; set; } = string.Empty; // VARCHAR(15) NOT NULL
public string? Apellido { get; set; } // VARCHAR(15) NULL
public string Password { get; set; } = string.Empty; // VARCHAR(15) NOT NULL
public short Roll { get; set; } // SMALLINT NOT NULL
public short? Rolc { get; set; } // SMALLINT NULL
public string? Password2 { get; set; } // VARCHAR(15) NULL
}
'''
## FUNCIONALIDADES MIGRADAS
- ✅ **Connection**: Conexión segura a SQL Server con parámetros
- ✅ **UserRepository**: GetByCredentialsAsync para autenticación
- ✅ **Usuario Model**: Entidad que representa tabla usuarios con campos reales
- 🔄 **CRUD User**: Pendiente (Create, Update, Delete, GetAll)
## COMPATIBILIDAD VB.NET ORIGINAL
- ✅ **Password**: Texto plano como en sistema VB original
- ✅ **Misma BD**: Usa las tablas existentes sin modificar estructura
- ✅ **Roles**: Sistema roll principal + rolc complementario
- ✅ **Campos**: Respeta nombres, tipos y longitudes exactas
- ✅ **Campos NULL**: Apellido, rolc, password2 pueden ser nulos
## CONSIDERACIONES IMPORTANTES
- **Login**: Máximo 10 caracteres
- **Nombre**: Máximo 15 caracteres, obligatorio
- **Apellido**: Máximo 15 caracteres, opcional
- **Password**: Texto plano, máximo 15 caracteres (sin hash)
- **Roll**: Rol principal (1-4)
- **Rolc**: Rol complementario opcional
- **Password2**: Password real/ opcional
## AYUDA REQUERIDA
**Implementar Repositories siguiendo:**
1. **Parámetros seguros**: Siempre usar @parameters
2. **Async patterns**: Task<T> para todas las operaciones
3. **Mapeo manual**: SqlDataReader → Models con validación NULL
4. **CRUD operations**: Create, Read, Update, Delete, List
5. **Validación longitudes**: Respetar VARCHAR limits
6. **Transacciones**: Para operaciones complejas
7. **Logging**: Para debugging consultas SQL
**¿Qué repository o consulta necesitas implementar?**
Test.Cores
# System Prompt - Test.Core (Capa de Negocio)
## CONTEXTO DEL PROYECTO
Migración de sistema VB.NET WinForms → **Angular + C# Web API**
- **Backend**: .NET 8, Clean Architecture, 3 proyectos separados
- **Frontend**: Angular 17+, TypeScript, Angular Material
- **BD**: SQL Server (misma del sistema VB original)
## ARQUITECTURA BACKEND (3 CAPAS)
'''
Test.Web -> Test.Core -> Test.Database
'''
### **Test.Core** - CAPA DE NEGOCIO
**📍 Ubicación**: `Test.Core/`
**🎯 Responsabilidad**: Lógica de negocio, validaciones de dominio, orquestación
**🔗 Conoce**: Solo Test.Database (usa sus Models y Repositories)
### ESTRUCTURA POR FEATURES:
'''
Test.Core/
├── Auth/ # Feature: Autenticación
│ ├── IAuthService.cs
│ ├── AuthService.cs
│ └── DTOs/ (AuthResult, etc.)
├── Users/ # Feature: Gestión usuarios
│ ├── IUserService.cs
│ ├── UserService.cs
│ └── DTOs/ (UserOperationResult, etc.)
└── Common/
├── Helpers/ (PasswordHelper, JwtHelper, etc.)
├── Extensions/ (ServiceCollectionExtensions, etc.)
├── Exceptions/ (BusinessException, etc.)
└── Constants/ (Roles, Messages)
'''
## REGLAS DE ARQUITECTURA
- ✅ **Contiene**: Services por feature, DTOs de resultados, helpers, constantes
- ❌ **NO tiene Models propios**: usa directamente `Usuario.cs` de Test.Database
- ❌ **NO accede a BD**: usa repositories inyectados de Test.Database
- ✅ **Patrón**: Un Service por feature con su interface correspondiente
## FUNCIONALIDADES MIGRADAS
- ✅ **AuthService**: Login JWT completo (AuthenticateAsync, ValidateTokenAsync)
- ✅ **PasswordHelper**: Hash con GetHashCode() (compatible con VB original)
- ✅ **JwtHelper**: Generación y validación de tokens JWT
- 🔄 **UserService**: Pendiente (próximo a implementar)
## EJEMPLO DE SERVICE + MAPPING
'''csharp
// Test.Core/Auth/AuthService.cs
public class AuthService : IAuthService
{
private readonly IUserRepository _userRepository;
private readonly IUserMapper _userMapper;
public async Task<AuthResult> AuthenticateAsync(string login, string password)
{
var passwordHash = PasswordHelper.HashPassword(password);
var usuario = await _userRepository.GetByCredentialsAsync(login, passwordHash);
if (usuario == null) return AuthResult.Failed();
var userDto = _userMapper.FromEntity(usuario);
var token = JwtHelper.GenerateToken(usuario);
return AuthResult.Success(userDto, token);
}
}
// Test.Core/Common/Mappers/UserMapper.cs
public class UserMapper : IUserMapper
{
public UserDto FromEntity(Usuario entity) => new UserDto
{
Id = entity.Id,
Login = entity.Login,
Nombre = entity.Nombre,
// ... mapping lógico
};
}
'''
## ROLES BD (tabla usuarios)
- **1**: Administrador del Sistema
- **2**: Director General
- **3**: Secretaria
- **4**: Profesores
## AYUDA REQUERIDA
**Migrar funcionalidades VB → C# Services siguiendo:**
1. **Organización por features** (Auth/, Users/, Reports/)
2. **Inyección de dependencias**
3. **DTOs para resultados de operaciones**
4. **Async/await patterns**
5. **Logging estructurado**
6. **Validaciones de negocio**
**¿En qué feature o service necesitas ayuda?**
Test.Web
# System Prompt - Test.Web (Capa de Presentación API)
## CONTEXTO DEL PROYECTO
Migración de sistema VB.NET WinForms → **Angular + C# Web API**
- **Backend**: .NET 8, Clean Architecture, 3 proyectos separados
- **Frontend**: Angular 17+, TypeScript, Angular Material
- **BD**: SQL Server (misma del sistema VB original)
## ARQUITECTURA BACKEND (3 CAPAS)
'''
Test.Web -> Test.Core -> Test.Database
'''
### **Test.Web** - CAPA DE PRESENTACIÓN API
**📍 Ubicación**: `Test.Web/`
**🎯 Responsabilidad**: Exposición de endpoints REST, validación de entrada, formateo de respuesta
**🔗 Conoce**: Solo Test.Core (usa sus Services y DTOs)
### ESTRUCTURA POR FEATURES:
'''
Test.Web/
├── Controllers/
│ ├── AuthController.cs # POST /api/auth/login, /validate-token
│ ├── UsersController.cs # GET,POST,PUT,DELETE /api/users
│ └── BaseController.cs # Funcionalidad común
├── Program.cs # DI, JWT, CORS, Swagger, configuración
├── appsettings.json # Config: ConnectionStrings, JWT
└── Test.Web.csproj # Referencia: Test.Core únicamente
'''
## REGLAS DE ARQUITECTURA
- ✅ **Contiene**: Controllers, Program.cs, configuración de infraestructura
- ✅ **Controllers ligeros**: Solo orquestan y delegan a Services de Test.Core
- ✅ **Usa DTOs de Core**: NO duplicar DTOs, usar directamente de Test.Core
- ✅ **ApiResponse wrapper**: Formato estándar `{ Success, Message, Data, Errors }`
- ❌ **NO lógica de negocio**: Todo va en Test.Core Services
- ❌ **NO acceso directo a BD**: Solo a través de Test.Core Services
- ❌ **NO Models propios**: Usar entidades de Test.Database vía Test.Core
## CONFIGURACIÓN ACTUAL
- **JWT Authentication** configurado en Program.cs
- **CORS** habilitado para desarrollo (localhost:4200)
- **Swagger** en página principal para documentación
- **Logging** estructurado configurado
- **URL**: `https://localhost:5001`
## EJEMPLO DE CONTROLLER
'''csharp
// Test.Web/Controllers/AuthController.cs
[ApiController]
[Route("api/[controller]")]
public class AuthController : BaseController
{
private readonly IAuthService _authService;
[HttpPost("login")]
public async Task<ActionResult<ApiResponse<AuthResult>>> Login([FromBody] LoginRequest request)
{
// Validación básica
if (string.IsNullOrEmpty(request.Usuario))
return BadRequest(ApiResponse<AuthResult>.ErrorResponse("Usuario requerido"));
// Delegación a Service (Test.Core)
var result = await _authService.AuthenticateAsync(request.Usuario, request.Password);
// Formateo de respuesta
return result.IsSuccess
? Ok(ApiResponse<AuthResult>.SuccessResponse(result))
: Unauthorized(ApiResponse<AuthResult>.ErrorResponse(result.Message));
}
}
'''
## FUNCIONALIDADES MIGRADAS
- ✅ **AuthController**: Login JWT completo
- ✅ **BaseController**: Funcionalidad común para controllers
- ✅ **ApiResponse**: Wrapper estándar para respuestas
- 🔄 **UsersController**: Pendiente (próximo CRUD completo)
## AYUDA REQUERIDA
**Crear Controllers REST siguiendo:**
1. **Endpoints RESTful** estándar (GET, POST, PUT, DELETE)
2. **Validación de entrada** básica antes de delegar
3. **Manejo de errores** con ApiResponse wrapper
4. **Documentación Swagger** con atributos
5. **Async/await patterns**
6. **HTTP Status Codes** apropiados
7. **Paginación** para listados grandes
**¿Qué controller o endpoint necesitas implementar?**
2. Desarrollo del Mockup del Frontend
Al crear el ui se uso la herramienta de lovable para tener un mockup de lo que seria el frontend
Quiero hacer un ui con espacio para apis, en donde sera una aplicacion academica seria. Este tendra un login, en donde pedira un usuario y contraseña, de ahi, entra segun el rol, a una pantalla basica, sin nada se podria dewcir. Tendra un menu hamburguesa al lado izquierdo, y segun el rol, este podra ver sus opciones en el menu hamburgues.
Entre las opciones se encuentran
Contabilidad
|-Academico
| |- Mantenimientos
| | |-Mantenimiento datos de Familia
| | |-Mantenimiento datos de Alumno
| |- ...
| - ...
Secretaria
|- ...
Habra mas opciones, eso indica el ...
Como puedes ver el de mantenimiento es el que mas me importa ahorita, quiero que en esas pantallas, hayan una tabla basica, que muestre datos de la familia, lo mismo cuando se entre al del estudiante, quiero que muestre una tabla de datos del estudainte
Para ambas, tiene que haber un filtro, una opcion para crear una nueva familia
Al crear una nueva familia, ocupa cargar 4 tipos de formularios
Datos encargado
Datos Madre
Datos Padre
Datos emergencia.
Puedes ponerlos en un diferente tab o como se mas intuitivo
Tambien se podra editar los datos de la familia, esto seria en la tabla, e igual que borrar.
Para el crear de el alumno, este necesita 4 tipos de formularios
Datos Personales
Datos Academicos
Psicologia
Datos Medicos
Y como ultimo, es necesario que se le asigne una familia, este puede estar encima de todos los formularios como una opcion para buscar una failia y asignarsela
Puedes ponerlos en un diferente tab o como se mas intuitivo
Tambien tendra la opcion de editar y borrar en la tabla y un filtro
Una vez creado el Mockup, le envie una imagen a cursor de como queria que se viera y se trabajo desde ahi
Quiero que generes un frontend bien estructurado, con angular material. En un folder dentro de donde estamos Quiero que se mire similar a la iamgen. En donde tiene un menu en el lado izqueirdo, la tabla de mantenimiento de familias y asi. Quiero que sea ese ui pero bien estructurado.
Deja espacio para las apis, y por el momento hazlo con datos de muckup. Quiero ese menu expandible de hamburguesa, esa tabla, esas opciones. Cualquier duda antes de comenzar hazmela saber, estariamos trabajando en la ultima version de angular.
No pongas explicacion de lo que haces
3. Componentes especificos del frontend
Para los componentes específicos del frontend, con las imágenes del mockup le indicarle que hacer
Ahora hagamos lo que esta dentro de el boton "Datos de Familia" es en si,
Todas las pantallas deben de tener el Sistema de Gestion de Familia en la parte superior, ahora segun lo que tenemos, se mira feo lo que esta dentro, mantenimiento de familia esta pegado al lado izquierdo, el boton de crear esta mal puesto y el filtro esta feo, igual, todo lo que eesta dentro, essta pegado a los lados. En si quiero que sea como la imagen, en donde esta el titulo de Mantenimineot de Familais, con la descripcioon breve abajo y el boton de crear a su derecha
Y lo demas dentro de como cards. HAz los cambios sin darme explicacion
4. Integración Frontend-Backend
<!-- PROMPT 5: Servicios HTTP -->
En la carpeta de Test, tengo el backend de el programa. En la carpeta de frontend2 Tengo el frontend del progrograma quiero que unas el backend con el frontend.
Ahorita lo que quiero ver es la parte de crear familia, solo que al llenar campos y apretaar el boton de crear, que cree la familia. Claro, con una buena estructura en el frontend, ya sea con una carpeta llamada Models, o Services para que hagan sus cosas.
Resultados y Capturas
Pantallas Completadas
1. Dashboard Principal
{width=1400 height=558}
Dashboard principal con navegación lateral
2. Gestión de Familias
{width=1398 height=708}
Lista de familias con filtros y paginación
{width=1399 height=708}
Formulario de creación de familia
{width=1401 height=708}
Formulario de edición de familia
3. Gestión de Alumnos
{width=1400 height=707}
Lista de alumnos con información detallada
{width=1401 height=707}
Formulario de creación de alumno (múltiples tabs)
{width=1399 height=706}
Tab de datos académicos del alumno
Comparación Visual: VB.NET vs Angular
| VB.NET WinForms | Angular + Material |
|---|---|
{width=936 height=478} |
{width=1097 height=645} |
| Interfaz desktop tradicional | Interfaz web moderna y responsive |
API Endpoints Implementados
Families
GET /api/families- Listar todas las familiasGET /api/families/{id}- Obtener familia por IDPOST /api/families- Crear familiaPUT /api/families/{id}- Actualizar familiaDELETE /api/families/{id}- Eliminar familia
Students
GET /api/students- Listar todos los alumnosGET /api/students/{id}- Obtener alumno por IDPOST /api/students- Crear alumnoPUT /api/students/{id}- Actualizar alumnoDELETE /api/students/{id}- Eliminar alumno
Lookup
GET /api/lookup/colegios- Catálogo de colegiosGET /api/lookup/niveles- Catálogo de nivelesGET /api/lookup/grados- Catálogo de gradosGET /api/lookup/secciones- Catálogo de secciones
Conclusiones
Logros Alcanzados
- Migración exitosa del sistema legacy a arquitectura moderna
- Separación completa de frontend y backend
- Implementación de mejores prácticas (Clean Architecture, SOLID)
- Interfaz moderna y responsive con Material Design
- API RESTful documentada con Swagger
- Código mantenible y escalable
Apéndices
A. Tecnologías Utilizadas
Backend
- .NET 9
- ASP.NET Core Web API
- Dapper (micro-ORM)
- SQL Server
- Swagger/OpenAPI
Frontend
- Angular 19.2.15
- Angular Material
- RxJS
- TypeScript
- SCSS
B. Estructura de Base de Datos
Tablas Principales
KARDEX- Datos de familiasDATOS_ALUMNOS- Datos generales de alumnosALUMEDICA- Datos médicos de alumnosALUOTROS- Otros datos de alumnosCOLEGIOS- Catálogo de colegiosNIVELES- Catálogo de nivelesGRADOS- Catálogo de gradosCATALOGOCOLEGIO- Catálogo de seccionesUSUARIOS- Usuarios del sistema
C. Variables de Entorno
Backend (appsettings.json)
{
"ConnectionStrings": {
"DefaultConnection": "Server=...;Database=...;User Id=...;Password=..."
}
}
Frontend (environment.ts)
export const environment = {
production: false,
apiUrl: 'https://localhost:7025/api'
};


