diff --git a/.classpath b/.classpath
index 8e75d71..b144583 100644
--- a/.classpath
+++ b/.classpath
@@ -26,7 +26,7 @@
-
+
@@ -37,5 +37,6 @@
+
diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..29abf99
--- /dev/null
+++ b/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,6 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8
+encoding//src/test/java=UTF-8
+encoding//src/test/resources=UTF-8
+encoding/=UTF-8
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index 79a4c99..105f116 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -1,8 +1,8 @@
eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
+org.eclipse.jdt.core.compiler.codegen.methodParameters=generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=23
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=17
+org.eclipse.jdt.core.compiler.compliance=23
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
@@ -10,5 +10,5 @@ org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
org.eclipse.jdt.core.compiler.processAnnotations=disabled
-org.eclipse.jdt.core.compiler.release=disabled
-org.eclipse.jdt.core.compiler.source=17
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=23
diff --git a/README.md b/README.md
index 75ebf19..f35a476 100644
--- a/README.md
+++ b/README.md
@@ -1,115 +1,101 @@
-# Maven – Resumen rápido
+# CI/CD con Maven y GitHub Actions
-## ¿Qué es Maven?
+## 1. Estructura del proyecto Maven
-**Apache Maven** es una herramienta de gestión y automatización de proyectos Java.
+Un proyecto Maven bien organizado es la base para que el pipeline de CI funcione sin problemas:
-Permite compilar, probar, empaquetar y gestionar dependencias de forma automática.
-Todo se configura mediante un archivo central llamado `pom.xml`.
-
----
-
-## Conceptos básicos
-
-### POM (Project Object Model)
-
-Es el archivo `pom.xml`, donde se define:
-
-* Información del proyecto (`groupId`, `artifactId`, `version`)
-* Dependencias
-* Plugins
-* Configuración de compilación
-
-Ejemplo básico:
-```xml
-org.ejemplo
-mi-proyecto
-1.0.0
+```
+proyecto/
+├── src/
+│ ├── main/java/ # Código de producción
+│ └── test/java/ # Tests unitarios
+└── pom.xml # Dependencias, plugins y versión de Java
```
+> **Regla clave:** Si `mvn clean install` funciona en local → debe funcionar en CI.
+
---
-## Ciclo de vida de Maven (Build Lifecycle)
+## 2. Dependencias y plugins esenciales
-El ciclo de vida principal está formado por fases que se ejecutan en orden:
+### Testing
+- **`spring-boot-starter-test`** (proyectos Spring Boot): incluye JUnit 5, AssertJ y Mockito.
-| Fase | Descripción |
-| -------- | ------------------------------------------- |
-| validate | Comprueba que el proyecto es correcto |
-| compile | Compila el código fuente |
-| test | Ejecuta los tests |
-| package | Empaqueta la aplicación (JAR/WAR) |
-| verify | Verifica que el paquete es válido |
-| install | Instala el paquete en el repositorio local |
-| deploy | Publica el paquete en un repositorio remoto |
+### Plugins Maven importantes
-Ejemplo de ejecución:
-```bash
-mvn clean install
+| Plugin | Función |
+|--------|---------|
+| `maven-compiler-plugin` | Compila el proyecto |
+| `maven-surefire-plugin` | Ejecuta tests unitarios |
+| `maven-failsafe-plugin` | Ejecuta tests de integración (opcional) |
+
+---
+
+## 3. Tests
+
+Buenas prácticas para que los tests pasen en un entorno limpio de CI:
+
+- Deben ejecutarse **sin depender del IDE**.
+- Evitar dependencias de rutas locales o bases de datos externas → usar **H2 en memoria** si es necesario.
+- Nombres descriptivos con un único escenario por test:
+ ```
+ metodo_cuandoCondicion_retornaResultado
+ ```
+
+---
+
+## 4. Flujo básico con GitHub Actions
+
+Archivo de configuración: `.github/workflows/maven.yml`
+
+```yaml
+name: CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Set up JDK
+ uses: actions/setup-java@v3
+ with:
+ java-version: '23'
+ distribution: 'temurin'
+
+ - name: Cache Maven packages
+ uses: actions/cache@v3
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+
+ - name: Build with Maven
+ run: mvn clean install
```
----
+### ¿Qué hace `mvn clean install`?
+1. **clean** → Elimina artefactos anteriores
+2. **compile** → Compila el código fuente
+3. **test** → Ejecuta los tests unitarios
+4. **package** → Genera el `.jar` o `.war`
-## Repositorios
-
-Maven descarga las dependencias desde repositorios.
-
-Tipos:
-
-* **Local** → en el ordenador (`~/.m2`)
-* **Central** → repositorio público de Maven
-* **Remoto** → repositorios privados (Nexus, Artifactory)
+> Si algún test falla, GitHub Actions marcará el workflow como **Failed** ✗
---
-## Dependencias
+## 5. Buenas prácticas
-Las librerías necesarias para el proyecto se declaran en el `pom.xml`.
-
-Ejemplo:
-```xml
-
- mysql
- mysql-connector-java
- 8.0.33
-
-```
-
-Maven descargará automáticamente la librería.
-
----
-
-## Comandos Maven más usados
-
-| Comando | Función |
-| ------------- | -------------------------------------------- |
-| `mvn compile` | Compila el proyecto |
-| `mvn test` | Ejecuta los tests |
-| `mvn package` | Genera el JAR/WAR |
-| `mvn install` | Instala el artefacto en el repositorio local |
-| `mvn clean` | Elimina la carpeta `target` |
-
----
-
-## Estructura típica de un proyecto Maven
-```
-proyecto
-│
-├─ pom.xml
-└─ src
- ├─ main
- │ ├─ java
- │ └─ resources
- └─ test
- └─ java
-```
-
----
-
-## Idea clave
-
-Maven sigue el principio **"Convention over Configuration"**:
-si respetas su estructura estándar de proyecto, **necesitas muy poca configuración**.
-
----
-Fuentes: [ChatGPT](https://chat.openai.com) + [Claude](https://claude.ai)
\ No newline at end of file
+1. **Mantén el `pom.xml` limpio**: no sobreescribas versiones de dependencias gestionadas por Spring Boot.
+2. **Evita dependencias del entorno local**: sin rutas absolutas ni servicios externos sin mock.
+3. **Usa perfiles Maven** si necesitas configuraciones distintas para CI (p. ej., base de datos H2 para tests).
+4. **Verifica la versión de Java**: el runner de GitHub Actions debe usar la misma que la configurada en el proyecto.
+5. **Habilita la caché de Maven**: reduce significativamente el tiempo de build en cada ejecución.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 89169a8..ab6c06d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,6 +16,7 @@
23
+ 6.0.3
@@ -25,11 +26,20 @@
spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
com.mysql
mysql-connector-j
runtime
+
diff --git a/src/main/java/org/lapaloma/mapamundi/dao/IContinenteDAO.java b/src/main/java/org/lapaloma/mapamundi/dao/IContinenteDAO.java
index 0e9ddf7..228e87a 100644
--- a/src/main/java/org/lapaloma/mapamundi/dao/IContinenteDAO.java
+++ b/src/main/java/org/lapaloma/mapamundi/dao/IContinenteDAO.java
@@ -6,9 +6,9 @@ import org.lapaloma.mapamundi.vo.Continente;
public interface IContinenteDAO {
public Continente obtenerContinentePorClave(String codigo) ;
- public Continente actualizarContinente(Continente coche) ;
- public Continente crearContinente(Continente coche);
- public void borrarContinente(Continente coche);
+ public void actualizarContinente(Continente continente) ;
+ public void crearContinente(Continente continente);
+ public void borrarContinente(Continente continente);
public List obtenerListaContinentes();
public List obtenerContinentePorNombre(String nombre);
}
diff --git a/src/main/java/org/lapaloma/mapamundi/dao/impl/ContinenteDaoJDBC.java b/src/main/java/org/lapaloma/mapamundi/dao/impl/ContinenteDaoJDBC.java
index abbc96e..3e6114b 100644
--- a/src/main/java/org/lapaloma/mapamundi/dao/impl/ContinenteDaoJDBC.java
+++ b/src/main/java/org/lapaloma/mapamundi/dao/impl/ContinenteDaoJDBC.java
@@ -42,7 +42,7 @@ public class ContinenteDaoJDBC implements IContinenteDAO {
}
@Override
- public Continente actualizarContinente(Continente continente) {
+ public void actualizarContinente(Continente continente) {
String sentenciaSQL = """
UPDATE T_CONTINENTE
@@ -64,11 +64,10 @@ public class ContinenteDaoJDBC implements IContinenteDAO {
e.printStackTrace();
}
- return continente;
}
@Override
- public Continente crearContinente(Continente continente) {
+ public void crearContinente(Continente continente) {
String sentenciaSQL = """
INSERT INTO T_CONTINENTE (codigo, nombre_continente)
@@ -88,8 +87,6 @@ public class ContinenteDaoJDBC implements IContinenteDAO {
} catch (Exception e) {
e.printStackTrace();
}
-
- return continente;
}
@Override
diff --git a/src/main/java/org/lapaloma/mapamundi/excepcion/ContinenteNoEncontradoException.java b/src/main/java/org/lapaloma/mapamundi/excepcion/ContinenteNoEncontradoException.java
new file mode 100644
index 0000000..56853fb
--- /dev/null
+++ b/src/main/java/org/lapaloma/mapamundi/excepcion/ContinenteNoEncontradoException.java
@@ -0,0 +1,12 @@
+package org.lapaloma.mapamundi.excepcion;
+
+public class ContinenteNoEncontradoException extends RuntimeException {
+ /**
+ *
+ */
+ private static final long serialVersionUID = -3344627619585104664L;
+
+ public ContinenteNoEncontradoException(String mensaje) {
+ super(mensaje);
+ }
+}
diff --git a/src/main/java/org/lapaloma/mapamundi/service/ContinenteService.java b/src/main/java/org/lapaloma/mapamundi/service/ContinenteService.java
index 19f9961..7305839 100644
--- a/src/main/java/org/lapaloma/mapamundi/service/ContinenteService.java
+++ b/src/main/java/org/lapaloma/mapamundi/service/ContinenteService.java
@@ -7,36 +7,60 @@ import java.util.List;
import org.lapaloma.mapamundi.dao.IContinenteDAO;
import org.lapaloma.mapamundi.dao.impl.ContinenteDaoJDBC;
+import org.lapaloma.mapamundi.excepcion.ContinenteNoEncontradoException;
import org.lapaloma.mapamundi.vo.Continente;
-/**
- * Isidoro Nevares Martín - Virgen de la Paloma Fecha creación: 13 mar 2026
- */
public class ContinenteService {
+
IContinenteDAO continenteDAO = new ContinenteDaoJDBC();
public Continente obtenerContinentePorClave(String codigo) {
- Continente continente = null;
+
+
+ if (codigo == null || codigo.isBlank()) {
+ throw new IllegalArgumentException("Código inválido");
+ }
- continente = continenteDAO.obtenerContinentePorClave(codigo);
+ Continente continente = continenteDAO.obtenerContinentePorClave(codigo);
+ // Esta línea simula un error de negocio, ignorando lo que devuelve el DAO
+ continente = null;
+
+
+ if (continente == null) {
+ throw new ContinenteNoEncontradoException(
+ "No existe el continente con código: " + codigo
+ );
+ }
+
return continente;
}
public List obtenerListaContinentes() {
- List listaContinentes = null;
- listaContinentes = continenteDAO.obtenerListaContinentes();
+ List lista = continenteDAO.obtenerListaContinentes();
- return listaContinentes;
+ if (lista == null || lista.isEmpty()) {
+ throw new RuntimeException("No hay continentes disponibles");
+ }
+
+ return lista;
}
public List obtenerContinentePorNombre(String nombre) {
- List listaContinentes = null;
- listaContinentes = continenteDAO.obtenerContinentePorNombre(nombre);
+ if (nombre == null || nombre.isBlank()) {
+ throw new IllegalArgumentException("Nombre inválido");
+ }
- return listaContinentes;
+ List lista = continenteDAO.obtenerContinentePorNombre(nombre);
+
+ if (lista == null || lista.isEmpty()) {
+ throw new ContinenteNoEncontradoException(
+ "No existen continentes con nombre: " + nombre
+ );
+ }
+
+ return lista;
}
-
-}
+}
\ No newline at end of file
diff --git a/src/test/java/org/lapaloma/mapamundi/service/ContinenteServiceTest.java b/src/test/java/org/lapaloma/mapamundi/service/ContinenteServiceTest.java
new file mode 100644
index 0000000..b6d9ea5
--- /dev/null
+++ b/src/test/java/org/lapaloma/mapamundi/service/ContinenteServiceTest.java
@@ -0,0 +1,166 @@
+package org.lapaloma.mapamundi.service;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.lapaloma.mapamundi.dao.IContinenteDAO;
+import org.lapaloma.mapamundi.excepcion.ContinenteNoEncontradoException;
+import org.lapaloma.mapamundi.vo.Continente;
+
+class ContinenteServiceTest {
+
+ private ContinenteService continenteService;
+ private FakeContinenteDAO fakeDAO;
+
+ @BeforeEach
+ void setUp() {
+ fakeDAO = new FakeContinenteDAO();
+ continenteService = new ContinenteService();
+ continenteService.continenteDAO = fakeDAO;
+ }
+
+ // =========================
+ // obtenerContinentePorClave
+ // =========================
+
+ @Test
+ void obtenerContinentePorClave_cuandoCodigoEsNull_lanzaExcepcion() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ continenteService.obtenerContinentePorClave(null);
+ });
+ }
+
+ @Test
+ void obtenerContinentePorClave_cuandoCodigoEstaVacio_lanzaExcepcion() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ continenteService.obtenerContinentePorClave("");
+ });
+ }
+
+ @Test
+ void obtenerContinentePorClave_cuandoNoExiste_lanzaExcepcion() {
+ assertThrows(ContinenteNoEncontradoException.class, () -> {
+ continenteService.obtenerContinentePorClave("XX");
+ });
+ }
+
+ @Test
+ void obtenerContinentePorClave_cuandoExiste_retornaContinente() {
+ fakeDAO.crearContinente(new Continente("EU", "Europa"));
+
+ Continente resultado = continenteService.obtenerContinentePorClave("EU");
+
+ assertNotNull(resultado);
+ assertEquals("Europa", resultado.getNombre());
+ }
+
+ // =========================
+ // obtenerListaContinentes
+ // =========================
+
+ @Test
+ void obtenerListaContinentes_cuandoListaEstaVacia_lanzaExcepcion() {
+ assertThrows(RuntimeException.class, () -> {
+ continenteService.obtenerListaContinentes();
+ });
+ }
+
+ @Test
+ void obtenerListaContinentes_cuandoHayDatos_retornaLista() {
+ fakeDAO.crearContinente(new Continente("EU", "Europa"));
+
+ List resultado = continenteService.obtenerListaContinentes();
+
+ assertNotNull(resultado);
+ assertEquals(1, resultado.size());
+ }
+
+ // =========================
+ // obtenerContinentePorNombre
+ // =========================
+
+ @Test
+ void obtenerContinentePorNombre_cuandoNombreEsNull_lanzaExcepcion() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ continenteService.obtenerContinentePorNombre(null);
+ });
+ }
+
+ @Test
+ void obtenerContinentePorNombre_cuandoNombreEstaVacio_lanzaExcepcion() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ continenteService.obtenerContinentePorNombre("");
+ });
+ }
+
+ @Test
+ void obtenerContinentePorNombre_cuandoNoExiste_lanzaExcepcion() {
+ assertThrows(ContinenteNoEncontradoException.class, () -> {
+ continenteService.obtenerContinentePorNombre("Inexistente");
+ });
+ }
+
+ @Test
+ void obtenerContinentePorNombre_cuandoExiste_retornaLista() {
+ fakeDAO.crearContinente(new Continente("EU", "Europa"));
+
+ List resultado = continenteService.obtenerContinentePorNombre("Europa");
+
+ assertEquals(1, resultado.size());
+ }
+
+ // =========================
+ // Fake DAO. Se crea el DAO dentro del test para no depender de la conexión a la base de datos, de si hay red, de si accede a un fichero...
+ // En caso de usar el DOA real (ContinenteDaoJDBC) estaríamos hablando de prubeas de integración.
+ // =========================
+
+ static class FakeContinenteDAO implements IContinenteDAO {
+
+ private List data = new ArrayList<>();
+
+ @Override
+ public Continente obtenerContinentePorClave(String codigo) {
+ return data.stream()
+ .filter(c -> c.getCodigo().equals(codigo))
+ .findFirst()
+ .orElse(null);
+ }
+
+ @Override
+ public List obtenerListaContinentes() {
+ return new ArrayList<>(data);
+ }
+
+ @Override
+ public List obtenerContinentePorNombre(String nombre) {
+ List resultado = new ArrayList<>();
+
+ for (Continente c : data) {
+ if (c.getNombre().equals(nombre)) {
+ resultado.add(c);
+ }
+ }
+ return resultado;
+ }
+
+ @Override
+ public void actualizarContinente(Continente continente) {
+ }
+
+ @Override
+ public void crearContinente(Continente continente) {
+ data.add(continente);
+ }
+
+ @Override
+ public void borrarContinente(Continente continente) {
+
+ }
+ }
+}
\ No newline at end of file