API REST desarrollada con Java 17 + Spring Boot 3 para procesar dos archivos de texto de formato fijo, cruzar su información y retornar al estudiante o estudiantes con la nota más alta.
El proyecto sigue una arquitectura en capas con separación estricta de responsabilidades:
com.alessandragodoy.studentms/
│
├── 🌐 controller/
│ └── StudentController → Adaptador HTTP. Implementa StudentApi (contract-first via OpenAPI)
│
├── ⚙️ service/
│ ├── IStudentService → Contrato del servicio
│ └── impl/
│ └── StudentServiceImpl → Orquesta parsing y nota máxima
│
├── 📄 parser/
│ ├── IFileParser<T> → Abstracción genérica (permite nuevos formatos sin modificar código existente)
│ └── impl/
│ ├── E1FileParser → Parsea archivo de notas
│ └── E2FileParser → Parsea archivo de sexo
│
├── 📦 dto/
│ ├── E1Record → Record de entrada (nota)
│ ├── E2Record → Record de entrada (sexo)
│ ├── HighestGradeResultDTO → DTO de respuesta al cliente
│ └── CustomErrorResponse → Estructura uniforme de errores
│
├── ⚠️ exception/
│ ├── EmptyFileException → Archivo vacío
│ ├── FileParsingException → Error estructural / I/O
│ ├── FileValidationException → Dato inválido dentro del archivo
│ └── GlobalExceptionHandler → Manejo centralizado con @ControllerAdvice
│
├── 🛠️ util/
│ └── ValidationUtil → Validaciones reutilizables
│
├── ⚙️ config/
│ └── OpenApiConfig → Configuración de Swagger UI
│
└── 🗄️ model/
└── Student → Entidad JPA (persistencia futura)
Decisiones clave:
- Contract-first: el contrato REST (
StudentApi) se generó desde el spec OpenAPI antes de implementar el controller, permitiendo desarrollo desacoplado y tests independientes de la implementación. - TDD: los parsers y el service fueron desarrollados partiendo de los tests — los casos de prueba definen el comportamiento esperado antes que el código.
- Parsers genéricos con OCP:
IFileParser<T>permite agregar nuevos formatos de archivo sin modificar código existente. - Modelo de persistencia preparado: la entidad
Studenty el repositorio JPA/H2 están incluidas como base lista para evolucionar hacia persistencia real sin refactoring mayor.
| Herramienta | Versión mínima |
|---|---|
| Java | 17 |
| Maven | 3.9+ |
No se requiere instalar base de datos ni ninguna dependencia externa. La aplicación levanta directamente con mvn spring-boot:run.
# Clonar el repositorio
git clone https://github.com/abengl/StudentMS.git
cd studentms
# Compilar y ejecutar
mvn spring-boot:runLa aplicación levanta en http://localhost:8080
# Ejecutar toda la suite
mvn testCobertura por capa:
E1FileParserTest— Unit tests del parser de notas: formato fijo, largo de línea, validaciones de tipo de doc, número de doc, rango de nota, caracteres no numéricos, líneas en blanco.E2FileParserTest— Unit tests del parser de sexo: mismas validaciones estructurales + género válido (M/F), case-sensitive.StudentServiceImplTest— Unit tests del servicio: cruce de archivos, nota máxima, empates, registros sin match, duplicados en E1 y E2.StudentControllerTest— Tests de integración de capa web (@WebMvcTest): archivos vacíos, faltantes, Content-Type incorrecto, errores de validación, estructura del error response.
Procesa los archivos E1 y E2 y retorna al estudiante o estudiantes con la nota más alta. En caso de empate, se retornan todos los estudiantes con esa nota.
Request — multipart/form-data
| Campo | Tipo | Descripción |
|---|---|---|
e1 |
file | Archivo de notas (formato fijo: tipo_doc[1] + num_doc[8] + nota[2]) — 11 caracteres por línea |
e2 |
file | Archivo de sexo (formato fijo: tipo_doc[1] + num_doc[8] + sexo[1]) — 10 caracteres por línea |
Response — 200 OK
[
{
"documentType": "1",
"documentNumber": "12345678",
"gender": "F",
"grade": 20
}
]Errores
| Status | Causa |
|---|---|
| 400 | Archivo vacío, campo faltante, o dato inválido (tipo de doc, número de doc, nota fuera de rango, género inválido) |
| 415 | Content-Type incorrecto (se espera multipart/form-data) |
| 422 | Error de formato estructural: largo de línea incorrecto, nota no numérica, o error de I/O |
Los errores retornan la siguiente estructura:
{
"timestamp": "2026-05-11T08:00:00",
"message": "Line 2 has invalid length: expected 11, got 10",
"path": "/api/v1/students/highest-grade"
}Swagger UI disponible en: http://localhost:8080/swagger-ui.html
[tipo_doc: 1 char][num_doc: 8 chars][nota: 2 chars]
Valores válidos:
tipo_doc:1(DNI),2(CE),3(Pasaporte)num_doc: exactamente 8 dígitosnota: número entero entre00y20
Ejemplo:
11234567818
21111111120
[tipo_doc: 1 char][num_doc: 8 chars][sexo: 1 char]
Valores válidos:
sexo:F(femenino) oM(masculino) — case-sensitive
Ejemplo:
112345678F
211111111M
Las líneas en blanco son ignoradas en ambos archivos.
El servicio cruza E1 y E2 usando la combinación tipo_doc + num_doc como clave:
- Se construye un mapa de sexo desde E2 (
tipo_doc + num_doc → gender). En caso de duplicados, se conserva el primer registro. - Se filtran los registros de E1 que tienen match en ese mapa.
- Se calcula la nota máxima entre los registros cruzados.
- Se retornan todos los registros con esa nota máxima (soporte de empates).
Si ningún registro de E1 tiene match en E2, se lanza NoSuchElementException.
La IA fue utilizada como herramienta de apoyo en etapas específicas, con revisión y criterio propio en cada decisión:
| Etapa | Uso |
|---|---|
| Análisis del problema | Descomposición del scope e identificación de edge cases (líneas malformadas, registros sin match, empates, duplicados) que pueden omitirse en una primera lectura |
| Diseño de arquitectura | Evaluación de dependencias y tecnologías para justificar decisiones con criterio propio |
| Generación de casos de prueba | Sugerencia de escenarios de test para los parsers y el servicio |
| Boilerplate y configuración | Scaffolding inicial de application.yml y configuración de Spring revisado manualmente |
| Code review asíncrono | Revisión de implementaciones antes de commit para detectar inconsistencias en nomenclatura y manejo de errores |
Alessandra Godoy
- Email: dev@alessandragodoy.com
- LinkedIn: linkedin.com/in/alessandragodoy-engineer