From 1bfa6a6ade0278a3c55b7ab55a3c06cb97df25b7 Mon Sep 17 00:00:00 2001
From: elmer-20 <80175046+elmer-20@users.noreply.github.com>
Date: Mon, 9 Mar 2026 16:49:46 -0500
Subject: [PATCH] excel_upload
---
.env | 2 +
Dockerfile.dev | 18 ++
docker-compose.yml | 39 ++--
docker-compose_prod.yml | 27 +++
pom.xml | 25 ++-
.../controller/ExcelController.java | 32 +++
.../ingresantes/entity/Asignatura.java | 32 +++
.../ingresantes/entity/CarpetaExamen.java | 29 +++
.../repository/AsignaturaRepository.java | 11 +
.../repository/CarpetaExamenRepository.java | 11 +
.../repository/InscripcionRepository.java | 6 +-
.../ingresantes/service/ExcelService.java | 190 ++++++++++++++++++
src/main/resources/application.properties | 24 ++-
13 files changed, 412 insertions(+), 34 deletions(-)
create mode 100644 .env
create mode 100644 Dockerfile.dev
create mode 100644 docker-compose_prod.yml
create mode 100644 src/main/java/com/service/ingresantes/controller/ExcelController.java
create mode 100644 src/main/java/com/service/ingresantes/entity/Asignatura.java
create mode 100644 src/main/java/com/service/ingresantes/entity/CarpetaExamen.java
create mode 100644 src/main/java/com/service/ingresantes/repository/AsignaturaRepository.java
create mode 100644 src/main/java/com/service/ingresantes/repository/CarpetaExamenRepository.java
create mode 100644 src/main/java/com/service/ingresantes/service/ExcelService.java
diff --git a/.env b/.env
new file mode 100644
index 0000000..82e1f39
--- /dev/null
+++ b/.env
@@ -0,0 +1,2 @@
+MYSQL_DATABASE=data_admision
+MYSQL_ALLOW_EMPTY_PASSWORD=yes
\ No newline at end of file
diff --git a/Dockerfile.dev b/Dockerfile.dev
new file mode 100644
index 0000000..2c615ed
--- /dev/null
+++ b/Dockerfile.dev
@@ -0,0 +1,18 @@
+# Usa Maven y JDK para compilar y correr código
+FROM maven:3.9.2-eclipse-temurin-17
+
+WORKDIR /app
+
+# Copia solo pom.xml primero para cache
+COPY pom.xml .
+
+# Pre-descarga dependencias
+RUN mvn dependency:go-offline
+
+# Copia el código fuente
+COPY src ./src
+
+EXPOSE 8080
+
+# Ejecuta Spring Boot directamente (hot reload)
+CMD ["mvn", "spring-boot:run"]
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index 336184f..ac06adf 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,23 +1,34 @@
services:
-
- postgres:
- image: postgres:15
- container_name: postgres_admision
+ mysql:
+ image: mysql:8
+ container_name: mysql_admision_dev
environment:
- POSTGRES_DB: data_admision
- POSTGRES_USER: postgres
- POSTGRES_PASSWORD: 1234
+ MYSQL_DATABASE: admision_db
+ MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
ports:
- - "5432:5432"
+ - "3306:3306"
+ volumes:
+ - mysql_data:/var/lib/mysql
+ restart: unless-stopped
backend:
- build: .
- container_name: spring_admision
+ build:
+ context: .
+ dockerfile: Dockerfile.dev # Dockerfile que corre mvn spring-boot:run
+ container_name: spring_admision_dev
depends_on:
- - postgres
+ - mysql
ports:
- "8080:8080"
+ volumes:
+ - .:/app # Monta tu código para ver cambios sin rebuild
+ - ~/.m2:/root/.m2 # Cache de Maven para no bajar deps siempre
environment:
- SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/data_admision
- SPRING_DATASOURCE_USERNAME: postgres
- SPRING_DATASOURCE_PASSWORD: 1234
\ No newline at end of file
+ SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/admision_db?useSSL=false&serverTimezone=UTC
+ SPRING_DATASOURCE_USERNAME: root
+ SPRING_DATASOURCE_PASSWORD: ""
+ command: mvn spring-boot:run # Ejecuta Spring directamente
+ restart: unless-stopped
+
+volumes:
+ mysql_data:
\ No newline at end of file
diff --git a/docker-compose_prod.yml b/docker-compose_prod.yml
new file mode 100644
index 0000000..7790322
--- /dev/null
+++ b/docker-compose_prod.yml
@@ -0,0 +1,27 @@
+version: "3.9"
+
+services:
+ mysql:
+ image: mysql:8
+ container_name: mysql_admision
+ environment:
+ MYSQL_DATABASE: ${MYSQL_DATABASE}
+ MYSQL_ALLOW_EMPTY_PASSWORD: ${MYSQL_ALLOW_EMPTY_PASSWORD}
+ ports:
+ - "3306:3306"
+ restart: unless-stopped
+
+ backend:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ container_name: spring_admision
+ depends_on:
+ - mysql
+ ports:
+ - "8080:8080"
+ environment:
+ SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/${MYSQL_DATABASE}?useSSL=false&serverTimezone=UTC
+ SPRING_DATASOURCE_USERNAME: root
+ SPRING_DATASOURCE_PASSWORD: ""
+ restart: unless-stopped
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 656de31..4d0c690 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,10 +34,10 @@
org.springframework.boot
spring-boot-starter-data-jpa
-
+
org.springframework.boot
spring-boot-starter-webmvc
@@ -50,9 +50,19 @@
true
- org.postgresql
- postgresql
- runtime
+ com.mysql
+ mysql-connector-j
+ 8.1.0
+
+
+ org.apache.poi
+ poi
+ 5.2.3
+
+
+ org.apache.poi
+ poi-ooxml
+ 5.2.3
org.projectlombok
@@ -64,17 +74,18 @@
spring-boot-starter-data-jpa-test
test
-
+
org.springframework.boot
spring-boot-starter-webmvc-test
test
+
diff --git a/src/main/java/com/service/ingresantes/controller/ExcelController.java b/src/main/java/com/service/ingresantes/controller/ExcelController.java
new file mode 100644
index 0000000..65a7030
--- /dev/null
+++ b/src/main/java/com/service/ingresantes/controller/ExcelController.java
@@ -0,0 +1,32 @@
+package com.service.ingresantes.controller;
+
+import com.service.ingresantes.service.ExcelService;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+@RestController
+@RequestMapping("/api/excel")
+public class ExcelController {
+
+ private final ExcelService excelService;
+
+ public ExcelController(ExcelService excelService) {
+ this.excelService = excelService;
+ }
+
+ @PostMapping("/upload")
+ public ResponseEntity uploadExcel(@RequestParam("file") MultipartFile file) {
+ try {
+ excelService.importarExcel(file);
+ return ResponseEntity.ok("Archivo subido y procesado correctamente!");
+ } catch (Exception e) {
+ return ResponseEntity.badRequest().body("Error al procesar el archivo: " + e.getMessage());
+ }
+ }
+
+ @GetMapping("/ping")
+ public String ping() {
+ return "API funcionando123456!";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/service/ingresantes/entity/Asignatura.java b/src/main/java/com/service/ingresantes/entity/Asignatura.java
new file mode 100644
index 0000000..67aa945
--- /dev/null
+++ b/src/main/java/com/service/ingresantes/entity/Asignatura.java
@@ -0,0 +1,32 @@
+package com.service.ingresantes.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity
+@Table(name = "asignaturas")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Asignatura {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private Integer codigo;
+
+ private String nombre;
+
+ private Integer cantidadPreguntas;
+
+ private Double ponderacion;
+
+ // Nuevo campo: puntaje por pregunta
+ private Double puntajePorPregunta;
+
+ @ManyToOne
+ @JoinColumn(name = "area_id")
+ private Area area;
+}
\ No newline at end of file
diff --git a/src/main/java/com/service/ingresantes/entity/CarpetaExamen.java b/src/main/java/com/service/ingresantes/entity/CarpetaExamen.java
new file mode 100644
index 0000000..cea235a
--- /dev/null
+++ b/src/main/java/com/service/ingresantes/entity/CarpetaExamen.java
@@ -0,0 +1,29 @@
+package com.service.ingresantes.entity;
+
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity
+@Table(name = "carpeta_examen")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class CarpetaExamen {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String tipo; // claves, ids, respuestas
+
+ private String ruta;
+
+ @ManyToOne
+ @JoinColumn(name = "proceso_id")
+ private Proceso proceso;
+
+ @ManyToOne
+ @JoinColumn(name = "area_id")
+ private Area area;
+}
\ No newline at end of file
diff --git a/src/main/java/com/service/ingresantes/repository/AsignaturaRepository.java b/src/main/java/com/service/ingresantes/repository/AsignaturaRepository.java
new file mode 100644
index 0000000..c921dd8
--- /dev/null
+++ b/src/main/java/com/service/ingresantes/repository/AsignaturaRepository.java
@@ -0,0 +1,11 @@
+package com.service.ingresantes.repository;
+
+import com.service.ingresantes.entity.Asignatura;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface AsignaturaRepository extends JpaRepository {
+ // Aquí puedes agregar consultas personalizadas si lo necesitas
+ // Ejemplo: List findByNombre(String nombre);
+}
\ No newline at end of file
diff --git a/src/main/java/com/service/ingresantes/repository/CarpetaExamenRepository.java b/src/main/java/com/service/ingresantes/repository/CarpetaExamenRepository.java
new file mode 100644
index 0000000..3648c7b
--- /dev/null
+++ b/src/main/java/com/service/ingresantes/repository/CarpetaExamenRepository.java
@@ -0,0 +1,11 @@
+package com.service.ingresantes.repository;
+
+import com.service.ingresantes.entity.CarpetaExamen;
+import org.springframework.data.jpa.repository.JpaRepository;
+import java.util.List;
+
+public interface CarpetaExamenRepository extends JpaRepository {
+
+ List findByProcesoIdAndAreaId(Long procesoId, Long areaId);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/service/ingresantes/repository/InscripcionRepository.java b/src/main/java/com/service/ingresantes/repository/InscripcionRepository.java
index 3832ae2..1e5201b 100644
--- a/src/main/java/com/service/ingresantes/repository/InscripcionRepository.java
+++ b/src/main/java/com/service/ingresantes/repository/InscripcionRepository.java
@@ -1,11 +1,13 @@
package com.service.ingresantes.repository;
import com.service.ingresantes.entity.Inscripcion;
+import com.service.ingresantes.entity.Postulante;
+import com.service.ingresantes.entity.Proceso;
import org.springframework.data.jpa.repository.JpaRepository;
-import java.util.List;
public interface InscripcionRepository extends JpaRepository {
- List findByProcesoId(Long procesoId);
+ // Método para verificar si existe una inscripción de un postulante a un proceso
+ boolean existsByPostulanteAndProceso(Postulante postulante, Proceso proceso);
}
\ No newline at end of file
diff --git a/src/main/java/com/service/ingresantes/service/ExcelService.java b/src/main/java/com/service/ingresantes/service/ExcelService.java
new file mode 100644
index 0000000..dcfcc7d
--- /dev/null
+++ b/src/main/java/com/service/ingresantes/service/ExcelService.java
@@ -0,0 +1,190 @@
+package com.service.ingresantes.service;
+
+import com.service.ingresantes.entity.*;
+import com.service.ingresantes.repository.*;
+import org.apache.poi.ss.usermodel.*;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+import jakarta.transaction.Transactional;
+
+import java.io.InputStream;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Optional;
+
+@Service
+public class ExcelService {
+
+ private final PostulanteRepository postulanteRepo;
+ private final ProcesoRepository procesoRepo;
+ private final ProgramaRepository programaRepo;
+ private final ModalidadRepository modalidadRepo;
+ private final InscripcionRepository inscripcionRepo;
+
+ public ExcelService(PostulanteRepository postulanteRepo,
+ ProcesoRepository procesoRepo,
+ ProgramaRepository programaRepo,
+ ModalidadRepository modalidadRepo,
+ InscripcionRepository inscripcionRepo) {
+ this.postulanteRepo = postulanteRepo;
+ this.procesoRepo = procesoRepo;
+ this.programaRepo = programaRepo;
+ this.modalidadRepo = modalidadRepo;
+ this.inscripcionRepo = inscripcionRepo;
+ }
+
+ // --- Método auxiliar para leer cualquier celda como String ---
+ private String getCellStringValue(Cell cell) {
+ if (cell == null) return "";
+ switch (cell.getCellType()) {
+ case STRING:
+ return cell.getStringCellValue().trim();
+ case NUMERIC:
+ if (DateUtil.isCellDateFormatted(cell)) {
+ return cell.getLocalDateTimeCellValue().toLocalDate().toString();
+ } else {
+ return String.valueOf((long) cell.getNumericCellValue());
+ }
+ case BOOLEAN:
+ return String.valueOf(cell.getBooleanCellValue());
+ case FORMULA:
+ return cell.getCellFormula();
+ default:
+ return "";
+ }
+ }
+
+ @Transactional
+ public void importarExcel(MultipartFile file) throws Exception {
+ InputStream is = file.getInputStream();
+ Workbook workbook = WorkbookFactory.create(is);
+ Sheet sheet = workbook.getSheetAt(0);
+
+ DateTimeFormatter fechaHoraFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+ for (Row row : sheet) {
+ if (row.getRowNum() == 0) continue; // Saltar encabezado
+
+ String dni = getCellStringValue(row.getCell(0));
+ if (dni.isEmpty()) {
+ System.out.println("Fila " + row.getRowNum() + ": DNI vacío, se omite");
+ continue;
+ }
+
+ Postulante postulante = postulanteRepo.findById(dni).orElseGet(() -> {
+ try {
+ Postulante p = new Postulante();
+ p.setDni(dni);
+ p.setPaterno(getCellStringValue(row.getCell(1)));
+ p.setMaterno(getCellStringValue(row.getCell(2)));
+ p.setNombres(getCellStringValue(row.getCell(3)));
+ p.setSexo(getCellStringValue(row.getCell(4)));
+
+ String fechaNacStr = getCellStringValue(row.getCell(5));
+ if (!fechaNacStr.isEmpty()) {
+ try { p.setFechaNacimiento(LocalDate.parse(fechaNacStr)); } catch (Exception e) {}
+ }
+
+ String edadStr = getCellStringValue(row.getCell(6));
+ if (!edadStr.isEmpty()) {
+ try { p.setEdad(Integer.parseInt(edadStr)); } catch (NumberFormatException ignored) {}
+ }
+
+ p.setUbigeoResidencia(getCellStringValue(row.getCell(7)));
+ p.setDepartamentoResidencia(getCellStringValue(row.getCell(8)));
+ p.setProvinciaResidencia(getCellStringValue(row.getCell(9)));
+ p.setDistritoResidencia(getCellStringValue(row.getCell(10)));
+
+ String egresoStr = getCellStringValue(row.getCell(11));
+ if (!egresoStr.isEmpty()) {
+ try { p.setEgreso(Integer.parseInt(egresoStr)); } catch (NumberFormatException ignored) {}
+ }
+
+ p.setCodModular(getCellStringValue(row.getCell(12)));
+ p.setUbigeoColegio(getCellStringValue(row.getCell(13)));
+ p.setDepartamentoColegio(getCellStringValue(row.getCell(14)));
+ p.setProvinciaColegio(getCellStringValue(row.getCell(15)));
+ p.setDistritoColegio(getCellStringValue(row.getCell(16)));
+
+ System.out.println("Fila " + row.getRowNum() + ": Postulante creado -> " + dni);
+ return postulanteRepo.save(p);
+ } catch (Exception e) {
+ System.out.println("Fila " + row.getRowNum() + ": Error guardando postulante " + dni + " -> " + e.getMessage());
+ return null;
+ }
+ });
+
+ if (postulante == null) continue;
+
+ String fechaInsStr = getCellStringValue(row.getCell(17));
+ LocalDateTime fechaInscripcion;
+ if (!fechaInsStr.isEmpty()) {
+ try {
+ fechaInscripcion = LocalDateTime.parse(fechaInsStr, fechaHoraFormatter);
+ } catch (Exception e) {
+ try { fechaInscripcion = LocalDate.parse(fechaInsStr).atStartOfDay(); }
+ catch (Exception ex) { fechaInscripcion = LocalDateTime.now(); }
+ }
+ } else {
+ fechaInscripcion = LocalDateTime.now();
+ }
+
+ String procesoStr = getCellStringValue(row.getCell(18));
+ if (procesoStr.isEmpty()) {
+ System.out.println("Fila " + row.getRowNum() + ": Proceso vacío, se omite");
+ continue;
+ }
+ Long procesoId;
+ try { procesoId = Long.parseLong(procesoStr); }
+ catch (NumberFormatException e) {
+ System.out.println("Fila " + row.getRowNum() + ": Proceso inválido -> " + procesoStr);
+ continue;
+ }
+ Optional procesoOpt = procesoRepo.findById(procesoId);
+ if (procesoOpt.isEmpty()) {
+ System.out.println("Fila " + row.getRowNum() + ": Proceso no encontrado -> " + procesoId);
+ continue;
+ }
+ Proceso proceso = procesoOpt.get();
+
+ String programaStr = getCellStringValue(row.getCell(19));
+ Optional programaOpt = programaRepo.findById(Long.parseLong(programaStr));
+ if (programaOpt.isEmpty()) {
+ System.out.println("Fila " + row.getRowNum() + ": Programa no encontrado -> " + programaStr);
+ continue;
+ }
+ ProgramaEstudio programa = programaOpt.get();
+
+ String modalidadStr = getCellStringValue(row.getCell(20));
+ Optional modalidadOpt = modalidadRepo.findById(Long.parseLong(modalidadStr));
+ if (modalidadOpt.isEmpty()) {
+ System.out.println("Fila " + row.getRowNum() + ": Modalidad no encontrada -> " + modalidadStr);
+ continue;
+ }
+ Modalidad modalidad = modalidadOpt.get();
+
+ boolean existe = inscripcionRepo.existsByPostulanteAndProceso(postulante, proceso);
+ if (existe) {
+ System.out.println("Fila " + row.getRowNum() + ": La inscripción ya existe para el postulante " + dni);
+ continue;
+ }
+
+ try {
+ Inscripcion inscripcion = new Inscripcion();
+ inscripcion.setFechaInscripcion(fechaInscripcion);
+ inscripcion.setPostulante(postulante);
+ inscripcion.setProceso(proceso);
+ inscripcion.setPrograma(programa);
+ inscripcion.setModalidad(modalidad);
+ inscripcionRepo.save(inscripcion);
+ System.out.println("Fila " + row.getRowNum() + ": Inscripción guardada -> " + dni);
+ } catch (Exception e) {
+ System.out.println("Fila " + row.getRowNum() + ": Error guardando inscripción -> " + e.getMessage());
+ }
+ }
+
+ workbook.close();
+ is.close();
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 891fb56..2f0f540 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -14,19 +14,21 @@
# con docker
-
spring.application.name=ingresantes
-# CONEXION POSTGRESQL (VARIABLES DE ENTORNO)
-spring.datasource.url=${SPRING_DATASOURCE_URL}
-spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
-spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
-spring.datasource.driver-class-name=org.postgresql.Driver
+spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/data_admision?useSSL=false&serverTimezone=UTC}
+spring.datasource.username=${SPRING_DATASOURCE_USERNAME:root}
+spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:}
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
-# JPA / HIBERNATE
-spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
+spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.ddl-auto=update
-
-# MOSTRAR SQL
spring.jpa.show-sql=true
-spring.jpa.properties.hibernate.format_sql=true
\ No newline at end of file
+spring.jpa.properties.hibernate.format_sql=true
+
+# Habilitar subida de archivos
+spring.servlet.multipart.enabled=true
+
+# Tamaño máximo de archivo y request
+spring.servlet.multipart.max-file-size=10MB
+spring.servlet.multipart.max-request-size=10MB
\ No newline at end of file