EVALUACIÓN TÉCNICA BACKEND + FRONTEND – GESTIÓN DE PERSONAS Y PROYECTOS DE SOFTWARE

Asignatura: Programación y Plataformas Web
Tecnología Backend: Spring Boot | Java | Gradle (Kotlin DSL) | H2 | application.yml
Tecnología Frontend: Angular 18+ | TypeScript | Standalone Components


CONTEXTO

Sistema de gestión de portafolio de desarrolladores y sus proyectos de software. La aplicación permite gestionar información de personas (desarrolladores), sus enlaces de contacto (GitHub, LinkedIn, etc.) y los proyectos de software que han desarrollado. El sistema incluye funcionalidades para listar, filtrar y gestionar el estado activo/inactivo de las personas.

Modelo de relaciones:


PARTE 1: BACKEND – SPRING BOOT

1.1 Configuración de Base de Datos

1.1.1 Dependencias (build.gradle.kts)

dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-validation") runtimeOnly("com.h2database:h2") developmentOnly("org.springframework.boot:spring-boot-devtools") testImplementation("org.springframework.boot:spring-boot-starter-test") }

1.1.2 Configuración (application.yml)

spring: datasource: url: jdbc:h2:mem:portfoliodb driver-class-name: org.h2.Driver username: sa password: jpa: database-platform: org.hibernate.dialect.H2Dialect hibernate: ddl-auto: none show-sql: true properties: hibernate: format_sql: true sql: init: mode: always schema-locations: classpath:schema.sql data-locations: classpath:data.sql server: port: 8080 # CORS para permitir peticiones desde Angular (puerto 4200) spring: web: cors: allowed-origins: "http://localhost:4200" allowed-methods: "*" allowed-headers: "*"

1.1.3 Archivos SQL

Ubicación obligatoria:

Tabla: persons

Campo Tipo Descripción
id BIGINT Identificador único (PK)
first_name VARCHAR(50) Nombre de la persona
last_name VARCHAR(50) Apellido de la persona
email VARCHAR(100) Correo electrónico
bio TEXT Biografía/descripción
profession VARCHAR(100) Profesión (ej: Full-Stack Developer)
location VARCHAR(100) Ubicación geográfica
active CHAR(1) Estado: "S" = activo, "N" = inactivo

Tabla: contact_links

Campo Tipo Descripción
id BIGINT Identificador único (PK)
name VARCHAR(50) Nombre del enlace (GitHub, LinkedIn, etc.)
url VARCHAR(255) URL del enlace
person_id BIGINT FK a persons (ManyToOne)

Tabla: projects

Campo Tipo Descripción
id BIGINT Identificador único (PK)
name VARCHAR(100) Nombre del proyecto
description TEXT Descripción del proyecto
technologies TEXT Lista de tecnologías separadas por comas
status VARCHAR(50) Estado: "Completed", "In Progress", "Planned"
cost DECIMAL Costo del proyecto
start_date DATE Fecha de inicio
end_date DATE Fecha de finalización (puede ser NULL)
person_id BIGINT FK a persons (ManyToOne)
active CHAR(1) Estado: "S" = activo, "N" = inactivo

Tabla: project_links

Campo Tipo Descripción
id BIGINT Identificador único (PK)
name VARCHAR(50) Nombre del enlace (Repository, Demo, Docs)
url VARCHAR(255) URL del enlace
project_id BIGINT FK a projects (OneToOne)

Relaciones:

ALTER TABLE contact_links ADD FOREIGN KEY (person_id) REFERENCES persons(id); ALTER TABLE projects ADD FOREIGN KEY (person_id) REFERENCES persons(id); ALTER TABLE project_links ADD FOREIGN KEY (project_id) REFERENCES projects(id);

Eliminación lógica: Campo active ("S" = activo, "N" = eliminado) en persons y projects


1.2 Endpoints Backend

ENDPOINT 1: Listar todas las personas

Ruta: GET /api/persons

Query Parameter:

DTO Response: Lista de PersonSummaryDto

[ { "id": 1, "firstName": "Juan", "lastName": "Pérez", "profession": "Full-Stack Developer", "location": "Quito, Ecuador", "active": true, "contactLinks": [ { "id": 1, "name": "GitHub", "url": "https://github.com/juanperez" }, { "id": 2, "name": "LinkedIn", "url": "https://linkedin.com/in/juanperez" } ], "projectCount": 8 }, { "id": 2, "firstName": "María", "lastName": "González", "profession": "Backend Developer", "location": "Guayaquil, Ecuador", "active": true, "contactLinks": [ { "id": 5, "name": "GitHub", "url": "https://github.com/mariagonzalez" } ], "projectCount": 0 } ]

Reglas:

Códigos HTTP:


ENDPOINT 2: Activar/Desactivar persona

Ruta: PATCH /api/persons/{id}/toggle-active

Path Variable:

Response Body: PersonStatusDto

{ "id": 1, "firstName": "Juan", "lastName": "Pérez", "active": false, "message": "Person deactivated successfully" }

Reglas:

Códigos HTTP:

Mensaje de error:

{ "message": "Person not found" }

ENDPOINT 3: Obtener información de una persona

Ruta: GET /api/persons/{id}

Path Variable:

DTO Response: PersonDetailDto

{ "id": 1, "firstName": "Juan", "lastName": "Pérez", "email": "juan.perez@example.com", "bio": "Desarrollador full-stack con 5 años de experiencia...", "profession": "Full-Stack Developer", "location": "Quito, Ecuador", "active": true, "contactLinks": [ { "id": 1, "name": "GitHub", "url": "https://github.com/juanperez" }, { "id": 2, "name": "LinkedIn", "url": "https://linkedin.com/in/juanperez" }, { "id": 3, "name": "Portfolio", "url": "https://juanperez.dev" } ], "projectCount": 8 }

Reglas:

Códigos HTTP:

Mensaje de error:

{ "message": "Person not found" }

ENDPOINT 4: Obtener proyectos de una persona con filtro por costo

Ruta: GET /api/persons/{id}/projects

Path Variable:

Query Parameter:

DTO Response: PersonProjectsDto

{ "personId": 1, "personName": "Juan Pérez", "active": true, "filterApplied": true, "minCost": 5000.00, "projectCount": 3, "projects": [ { "id": 1, "name": "E-Commerce Platform", "description": "Plataforma de comercio electrónico completa...", "technologies": "Spring Boot, Angular, PostgreSQL, AWS", "status": "Completed", "cost": 15000.00, "startDate": "2023-01-15", "endDate": "2023-06-30", "projectLink": { "name": "Repository", "url": "https://github.com/juanperez/ecommerce" } }, { "id": 3, "name": "Mobile Banking App", "description": "Aplicación móvil para gestión bancaria...", "technologies": "React Native, Node.js, MongoDB", "status": "In Progress", "cost": 12000.00, "startDate": "2023-09-01", "endDate": null, "projectLink": { "name": "Demo", "url": "https://demo.banking.com" } } ] }

Reglas:

Códigos HTTP:

Mensaje de error:

{ "message": "Person not found" }

Caso especial - Persona sin proyectos:

{ "personId": 2, "personName": "María González", "active": true, "filterApplied": false, "minCost": null, "projectCount": 0, "projects": [] }

1.3 Modelo de Entidades

Person Entity

@Entity @Table(name = "persons") public class Person { // Agregar annotations e imports necesarios private Long id; private String firstName; private String lastName; private String email; private String bio; private String profession; private String location; private Character active; private List<ContactLink> contactLinks; private List<Project> projects; // Getters, setters, constructors }
@Entity @Table(name = "contact_links") public class ContactLink { // Agregar annotations e imports necesarios private Long id; private String name; private String url; private Person person; // Getters, setters, constructors }

Project Entity

@Entity @Table(name = "projects") public class Project { // Agregar annotations e imports necesarios private Long id; private String name; private String description; private String technologies; private String status; private Double cost; private LocalDate startDate; private LocalDate endDate; private Character active; private Person person; private ProjectLink projectLink; // Getters, setters, constructors }
@Entity @Table(name = "project_links") public class ProjectLink { // Agregar annotations e imports necesarios private Long id; private String name; private String url; private Project project; // Getters, setters, constructors }

1.4 Estructura del Proyecto Backend

Cada módulo con sus paquetes correspondientes:

La estructura debe seguir buenas prácticas de arquitectura en capas.


PARTE 2: FRONTEND – ANGULAR

VERSION DE ANGULAR: 19+ USO DE SEÑALES

2.1 Configuración del Proyecto Angular

Versión: Angular 18+
Arquitectura: Standalone Components
Estilo: CSS/SCSS a elección

Instalación:

ng new portfolio-frontend --standalone --routing cd portfolio-frontend ng serve

2.2 Estructura de Rutas

// app.routes.ts export const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, { path: 'proyectos/:id', component: ProjectsComponent }, { path: 'proyectos', redirectTo: '/home', pathMatch: 'full' }, { path: '**', redirectTo: '/home' } ];

Rutas:


2.3 Página 1: Home (/home)

Componente: HomeComponent

Funcionalidad:

  1. Listado de personas:

  2. Filtro de personas activas:

  3. Íconos de redes sociales:

  4. Botón Activar/Desactivar:

  5. Botón Ver Proyectos:

Diseño sugerido:

Endpoint utilizado:


2.4 Página 2: Proyectos de una Persona (/proyectos/:id)

Componente: ProjectsComponent

Funcionalidad:

  1. Encabezado con información de la persona:

  2. Filtro de proyectos por costo:

  3. Listado de proyectos:

  4. Manejo de casos especiales:

  5. Navegación:

Diseño sugerido:

Endpoints utilizados:


2.6 Interfaces TypeScript

Crear interfaces que coincidan con los DTOs del backend:

// person.models.ts export interface PersonSummary { id: number; firstName: string; lastName: string; profession: string; location: string; active: boolean; contactLinks: ContactLink[]; projectCount: number; } export interface PersonDetail { id: number; firstName: string; lastName: string; email: string; bio: string; profession: string; location: string; active: boolean; contactLinks: ContactLink[]; projectCount: number; } export interface ContactLink { id: number; name: string; url: string; } export interface PersonStatus { id: number; firstName: string; lastName: string; active: boolean; message: string; } export interface PersonProjects { personId: number; personName: string; active: boolean; filterApplied: boolean; minCost: number | null; projectCount: number; projects: Project[]; } export interface Project { id: number; name: string; description: string; technologies: string; status: string; cost: number; startDate: string; endDate: string | null; projectLink: ProjectLink; } export interface ProjectLink { name: string; url: string; }


2.8 Componentes Sugeridos

Estructura de componentes:

src/app/ ├── components/ │ ├── home/ │ │ └── home.component.ts │ ├── projects/ │ │ └── projects.component.ts │ ├── person-card/ (opcional: componente reutilizable) │ │ └── person-card.component.ts │ └── project-card/ (opcional: componente reutilizable) │ └── project-card.component.ts ├── services/ │ └── person.service.ts ├── models/ │ └── person.models.ts └── app.routes.ts

PARTE 3: CRITERIOS DE EVALUACIÓN

3.1 Backend (60%)

Criterio Puntos
ENDPOINT 1 – GET /api/persons (con filtro activeOnly) 6.0
ENDPOINT 2 – PATCH /api/persons/{id}/toggle-active 6.0
ENDPOINT 3 – GET /api/persons/{id} 6.0
ENDPOINT 4 – GET /api/persons/{id}/projects (con filtro minCost) 6.5
Relaciones JPA correctas (OneToMany, ManyToOne, OneToOne) 2.0
Estructura del proyecto y arquitectura 4.0
SUBTOTAL BACKEND 30.0

3.2 Frontend (40%)

Criterio Puntos
Página Home: Listado de personas con cards 6.0
Página Home: Filtro activos y botón activar/desactivar 2.0
Página Home: Íconos de redes sociales y navegación 1.5
Página Proyectos: Información de persona y listado de proyectos 6.0
Página Proyectos: Filtro por costo 2.0
Manejo de casos especiales (sin proyectos, persona inexistente) 2.0
Validaciones de rutas (/proyectos sin ID, ID inválido) 1.5
SUBTOTAL FRONTEND 20.0

PARTE 4: NOTAS IMPORTANTES

Backend:

Frontend:

Datos de prueba:

Penalización total (0 puntos) si:


PARTE 5: ENTREGA

Backend:

Frontend:

Formato de entrega: