Backend API REST para gestionar personas, categorias, productos y consultas. Plataforma de intercambio de productos. Proyecto academico de Escalab, actualizado a Spring Boot 3.
- Que necesitas antes de empezar
- Clonar el proyecto
- Configurar la base de datos PostgreSQL
- Configurar application.properties
- Levantar el proyecto
- Verificar que funciona
- Autenticacion - Como obtener un token JWT
- Endpoints de la API
- Ejemplos con cURL
- Swagger / OpenAPI
- Correr los tests
- Estructura del proyecto
- Modelo de datos
- Stack tecnologico
- Problemas comunes
Antes de tocar cualquier cosa, asegurate de tener instalado:
| Herramienta | Version | Como verificar |
|---|---|---|
| JDK | 17 o superior | java -version |
| Git | cualquiera | git --version |
| PostgreSQL | 12 o superior | psql --version |
NO necesitas instalar Maven. El proyecto incluye Maven Wrapper (
mvnw), que descarga Maven automaticamente.
Windows:
- Descarga desde https://adoptium.net/ (Eclipse Temurin 17)
- Ejecuta el instalador
- Marca la opcion "Set JAVA_HOME variable"
- Reinicia la terminal
Mac:
brew install openjdk@17Linux (Ubuntu/Debian):
sudo apt update
sudo apt install openjdk-17-jdkVerifica:
java -version
# Debe mostrar algo como: openjdk version "17.x.x"git clone https://github.com/fjrock/springboot-escalab-backend.git
cd springboot-escalab-backend- Abre una terminal de PostgreSQL (
psql):
psql -U postgres- Crea la base de datos:
CREATE DATABASE escalab_db;- (Opcional) Crea un usuario dedicado:
CREATE USER escalab_user WITH PASSWORD 'tu_password_aqui';
GRANT ALL PRIVILEGES ON DATABASE escalab_db TO escalab_user;- Sal de psql:
\q- Ve a https://neon.tech y crea una cuenta
- Crea un nuevo proyecto
- Copia el connection string que te dan (se ve asi):
postgresql://usuario:password@ep-xxxx.us-east-2.aws.neon.tech/neondb?sslmode=require - Usaras esa URL en el siguiente paso
Una vez que la app levante por primera vez (Hibernate crea las tablas automaticamente), necesitas insertar datos en las tablas usuario, rol y usuario_rol manualmente para poder autenticarte.
Conectate a tu base de datos y ejecuta:
-- Crear roles
INSERT INTO rol (id_rol, nombre, descripcion) VALUES (1, 'USER', 'Rol de usuario basico');
INSERT INTO rol (id_rol, nombre, descripcion) VALUES (2, 'ADMIN', 'Rol de administrador');
-- Crear un usuario (password: 123456 encriptada con BCrypt)
INSERT INTO usuario (id_usuario, nombre, clave, estado)
VALUES (1, 'admin', '$2a$10$VZjGwkPOJC18TJk0MrKfg.FMKfbalNWwxMrvPBRiiUraKnxIXaQYy', true);
-- Asignar rol USER al usuario
INSERT INTO usuario_rol (id_usuario, id_rol) VALUES (1, 1);
-- (Opcional) Asignar tambien rol ADMIN
INSERT INTO usuario_rol (id_usuario, id_rol) VALUES (1, 2);La clave
$2a$10$VZjGwkPOJC18TJk0MrKfg.FMKfbalNWwxMrvPBRiiUraKnxIXaQYyes "123456" encriptada con BCrypt. Puedes generar otra en https://bcrypt-generator.com/
Abre el archivo src/main/resources/application.properties y configura tu conexion:
spring.datasource.url=jdbc:postgresql://localhost:5432/escalab_db
spring.datasource.username=postgres
spring.datasource.password=tu_password_aquispring.datasource.url=jdbc:postgresql://ep-xxxx.us-east-2.aws.neon.tech/neondb?sslmode=require
spring.datasource.username=tu_usuario_neon
spring.datasource.password=tu_password_neonsecurity.jwt.client-id=ofreceloapp
security.jwt.client-secret=ofrecelo2020
app.security.jwt-secret=ofrecelo-jwt-secret-key-change-this-please-2026
app.security.jwt-expiration-seconds=3600./mvnw spring-boot:run.\mvnw.cmd spring-boot:runmvnw.cmd spring-boot:runSi todo esta bien, veras algo como:
Started SpringbootEscalabBackendApplication in X.XXX seconds
La app corre en http://localhost:8080
./mvnw spring-boot:run -Dspring-boot.run.arguments=--server.port=8081Abre tu navegador y ve a:
http://localhost:8080/swagger-ui/index.html
Si ves la interfaz de Swagger, el proyecto esta corriendo correctamente.
Todos los endpoints (excepto /oauth/token y Swagger) requieren autenticacion JWT.
curl -X POST "http://localhost:8080/oauth/token?grant_type=password&username=admin&password=123456" \
-H "Authorization: Basic b2ZyZWNlbG9hcHA6b2ZyZWNlbG8yMDIw"
b2ZyZWNlbG9hcHA6b2ZyZWNlbG8yMDIwesofreceloapp:ofrecelo2020codificado en Base64.
Respuesta:
{
"access_token": "eyJhbGciOiJIUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write"
}Copia el valor de access_token y usalo asi en todas las peticiones:
curl -H "Authorization: Bearer TU_TOKEN_AQUI" http://localhost:8080/persona/1El header Authorization: Basic ... se construye asi:
- Concatena:
clientId:clientSecret->ofreceloapp:ofrecelo2020 - Codifica en Base64:
b2ZyZWNlbG9hcHA6b2ZyZWNlbG8yMDIw - Header final:
Authorization: Basic b2ZyZWNlbG9hcHA6b2ZyZWNlbG8yMDIw
En Linux/Mac puedes generarlo asi:
echo -n "ofreceloapp:ofrecelo2020" | base64Todos los endpoints requieren Authorization: Bearer <token> excepto los marcados como publicos.
| Metodo | Ruta | Descripcion |
|---|---|---|
| POST | /oauth/token |
Obtener token JWT |
| Metodo | Ruta | Descripcion |
|---|---|---|
| GET | /persona/{id} |
Obtener persona por ID |
| POST | /persona |
Crear persona |
| PUT | /persona |
Actualizar persona |
| DELETE | /persona/{id} |
Eliminar persona |
| Metodo | Ruta | Descripcion |
|---|---|---|
| GET | /categoria/{id} |
Obtener categoria por ID |
| POST | /categoria |
Crear categoria |
| PUT | /categoria |
Actualizar categoria |
| DELETE | /categoria/{id} |
Eliminar categoria |
| Metodo | Ruta | Descripcion |
|---|---|---|
| GET | /producto/{id} |
Obtener producto por ID |
| POST | /producto |
Crear producto |
| PUT | /producto |
Actualizar producto |
| DELETE | /producto/{id} |
Eliminar producto |
| Metodo | Ruta | Descripcion |
|---|---|---|
| POST | /consulta/buscartodoporrun |
Buscar consultas por RUN de persona |
| POST | /consulta/buscartodoporcategoria |
Buscar consultas por categoria |
| POST | /consulta/buscartodoporproducto |
Buscar consultas por producto |
| POST | /consulta/registrarconsulta |
Registrar nueva consulta |
| Metodo | Ruta | Descripcion |
|---|---|---|
| GET | /consultapersona/{idPersona} |
Listar consultas de una persona |
| POST | /consultapersona/registrar |
Registrar relacion consulta-persona |
| Metodo | Ruta | Descripcion |
|---|---|---|
| GET | /consultacategoria/{idCategoria} |
Listar consultas de una categoria |
| POST | /consultacategoria/registrar |
Registrar relacion consulta-categoria |
| Metodo | Ruta | Descripcion |
|---|---|---|
| GET | /consultaproducto/{idProducto} |
Listar consultas de un producto |
| POST | /consultaproducto/registrar |
Registrar relacion consulta-producto |
| Metodo | Ruta | Descripcion |
|---|---|---|
| GET | /tokens/anular/{tokenId} |
Anular token (informativo, JWT es stateless) |
curl -X POST "http://localhost:8080/oauth/token?grant_type=password&username=admin&password=123456" \
-H "Authorization: Basic b2ZyZWNlbG9hcHA6b2ZyZWNlbG8yMDIw"curl -X POST http://localhost:8080/persona \
-H "Authorization: Bearer TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"nombre": "Juan",
"apellidoPaterno": "Perez",
"apellidoMaterno": "Lopez",
"run": "12345678",
"dv": "9",
"telefono": "912345678",
"tipoPersona": "NATURAL",
"email": "juan@email.com",
"banned": false
}'curl -H "Authorization: Bearer TU_TOKEN" http://localhost:8080/persona/1curl -X POST http://localhost:8080/categoria \
-H "Authorization: Bearer TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"nombre": "Electronica"
}'curl -X POST http://localhost:8080/producto \
-H "Authorization: Bearer TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"nombre": "Laptop HP",
"stock": 5,
"tipoIntercambio": "TRUEQUE",
"lugarEntrega": "Santiago Centro"
}'curl -X POST http://localhost:8080/consulta/registrarconsulta \
-H "Authorization: Bearer TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"idPersona": 1,
"idCategoria": 1,
"idProducto": 1
}'curl -X POST http://localhost:8080/consulta/buscartodoporrun \
-H "Authorization: Bearer TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"run": "12345678"
}'Con la app corriendo, puedes explorar y probar TODOS los endpoints desde el navegador:
| Recurso | URL |
|---|---|
| Swagger UI | http://localhost:8080/swagger-ui/index.html |
| OpenAPI JSON | http://localhost:8080/v3/api-docs |
- Primero obtene un token usando el endpoint
/oauth/tokendesde Swagger o cURL - Copia el
access_tokende la respuesta - Haz clic en el boton "Authorize" (candado verde arriba a la derecha)
- Escribe:
Bearer TU_TOKEN_AQUI - Haz clic en "Authorize"
- Ahora puedes probar todos los endpoints protegidos
./mvnw clean test.\mvnw.cmd clean testLos tests usan una base de datos H2 en memoria (no necesitas PostgreSQL para los tests).
./mvnw clean verifyEl reporte se genera en: target/site/jacoco/index.html
El proyecto requiere 100% de cobertura de lineas y branches para pasar el build.
./mvnw clean package -DskipTests./mvnw clean packageEl JAR se genera en: target/springboot-escalab-backend-0.0.1-SNAPSHOT.jar
java -jar target/springboot-escalab-backend-0.0.1-SNAPSHOT.jarsrc/
├── main/
│ ├── java/com/escalab/
│ │ ├── SpringbootEscalabBackendApplication.java # Clase principal
│ │ ├── SecurityConfig.java # Configuracion Spring Security + JWT
│ │ ├── SwaggerConfig.java # Configuracion OpenAPI/Swagger
│ │ ├── AuthException.java # Manejo de errores de autenticacion
│ │ │
│ │ ├── controller/ # Controladores REST
│ │ │ ├── OAuthTokenController.java # POST /oauth/token
│ │ │ ├── PersonaController.java # CRUD /persona
│ │ │ ├── CategoriaController.java # CRUD /categoria
│ │ │ ├── ProductoController.java # CRUD /producto
│ │ │ ├── ConsultaController.java # Busquedas /consulta
│ │ │ ├── ConsultaPersonaController.java # /consultapersona
│ │ │ ├── ConsultaCategoriaController.java # /consultacategoria
│ │ │ ├── ConsultaProductoController.java # /consultaproducto
│ │ │ └── TokenController.java # /tokens (admin)
│ │ │
│ │ ├── model/ # Entidades JPA
│ │ │ ├── Persona.java # Personas (nombre, run, email...)
│ │ │ ├── Categoria.java # Categorias de productos
│ │ │ ├── Producto.java # Productos (nombre, stock, lugar...)
│ │ │ ├── Consulta.java # Consultas (persona+categoria+producto)
│ │ │ ├── ConsultaPersona.java # Relacion consulta-persona
│ │ │ ├── ConsultaCategoria.java # Relacion consulta-categoria
│ │ │ ├── ConsultaProducto.java # Relacion consulta-producto
│ │ │ ├── ConsultaPersonaPK.java # Clave compuesta
│ │ │ ├── ConsultaCategoriaPK.java # Clave compuesta
│ │ │ ├── ConsultaProductoPK.java # Clave compuesta
│ │ │ ├── Usuario.java # Usuarios del sistema
│ │ │ ├── Rol.java # Roles (USER, ADMIN)
│ │ │ └── ResetToken.java # Tokens de reset de password
│ │ │
│ │ ├── dto/ # Objetos de transferencia
│ │ │ └── FiltroConsultaDTO.java # Filtro para busquedas
│ │ │
│ │ ├── repo/ # Repositorios JPA
│ │ │ ├── IPersonaRepo.java
│ │ │ ├── ICategoriaRepo.java
│ │ │ ├── IProductoRepo.java
│ │ │ ├── IConsultaRepo.java
│ │ │ ├── IConsultaPersonaRepo.java
│ │ │ ├── IConsultaCategoriaRepo.java
│ │ │ ├── IConsultaProductoRepo.java
│ │ │ ├── IGuardaConsultaRepo.java
│ │ │ ├── IUsuarioRepo.java
│ │ │ └── IResetTokenRepo.java
│ │ │
│ │ ├── service/ # Interfaces de servicio
│ │ │ ├── ICRUD.java # Interfaz base CRUD
│ │ │ ├── IPersonaService.java
│ │ │ ├── ICategoriaService.java
│ │ │ ├── IProductoService.java
│ │ │ ├── IConsultaService.java
│ │ │ ├── IConsultaPersonaService.java
│ │ │ ├── IConsultaCategoriaService.java
│ │ │ ├── IConsultaProductoService.java
│ │ │ └── IResetTokenService.java
│ │ │
│ │ ├── service/impl/ # Implementaciones
│ │ │ ├── PersonaServiceImpl.java
│ │ │ ├── CategoriaServiceImpl.java
│ │ │ ├── ProductoServiceImpl.java
│ │ │ ├── ConsultaServiceImpl.java
│ │ │ ├── ConsultaPersonaServiceImpl.java
│ │ │ ├── ConsultaCategoriaServiceImpl.java
│ │ │ ├── ConsultaProductoServiceImpl.java
│ │ │ ├── ResetTokenServiceImpl.java
│ │ │ └── UsuarioServiceImpl.java # UserDetailsService (login)
│ │ │
│ │ ├── exception/ # Manejo de errores
│ │ │ ├── ResponseExceptionHandler.java # Handler global
│ │ │ ├── ModeloNotFoundException.java # Error 404
│ │ │ └── ExceptionResponse.java # DTO de error
│ │ │
│ │ └── util/
│ │ └── CORS.java # Filtro CORS (permite todos los origenes)
│ │
│ └── resources/
│ └── application.properties # Configuracion principal
│
└── test/ # Tests unitarios e integracion
├── java/com/escalab/ # 28 archivos de test
└── resources/
└── application.properties # Config de test (H2 en memoria)
┌──────────┐ ┌───────────┐ ┌───────────┐
│ Persona │ │ Categoria │ │ Producto │
│──────────│ │───────────│ │───────────│
│ id │ │ id │ │ id │
│ nombre │ │ nombre │ │ nombre │
│ apellidos│ └─────┬─────┘ │ stock │
│ run / dv │ │ │ fechas │
│ telefono │ │ │ tipo │
│ email │ │ │ lugar │
│ tipo │ │ └─────┬─────┘
│ banned │ │ │
└────┬─────┘ │ │
│ │ │
│ ┌────────┴─────────┐ │
└────────┤ Consulta ├───────┘
│──────────────────│
│ id │
│ id_persona (FK) │
│ id_categoria(FK) │
│ id_producto (FK) │
└────────┬─────────┘
│
┌────────────┼────────────┐
│ │ │
┌───────┴───────┐ ┌─┴──────────┐ ┌┴──────────────┐
│ConsultaPersona│ │ConsultaCat.│ │ConsultaProduct.│
│ id_persona │ │ id_categ. │ │ id_producto │
│ id_consulta │ │ id_consulta│ │ id_consulta │
└───────────────┘ └────────────┘ └────────────────┘
┌──────────┐ ┌──────────┐
│ Usuario │──M:N─│ Rol │
│──────────│ │──────────│
│ id │ │ id │
│ username │ │ nombre │
│ password │ │ descrip. │
│ enabled │ └──────────┘
└────┬─────┘
│ 1:1
┌────┴──────┐
│ResetToken │
│ token │
│ expiracion│
└───────────┘
Persona:
| Campo | Tipo | Requerido | Descripcion |
|---|---|---|---|
| idPersona | Integer | Auto | ID autoincremental |
| nombre | String(100) | Si | Nombre |
| apellidoPaterno | String(100) | Si | Apellido paterno |
| apellidoMaterno | String(100) | Si | Apellido materno |
| run | String(8) | Si | RUN sin digito verificador |
| dv | String(1) | Si | Digito verificador |
| telefono | String(12) | Si | Telefono |
| tipoPersona | String(100) | Si | Tipo (ej: NATURAL, JURIDICA) |
| String(100) | Si | Email (validado con @Email) | |
| banned | boolean | Si | Si esta baneado (default: false) |
Categoria:
| Campo | Tipo | Requerido | Descripcion |
|---|---|---|---|
| idCategoria | Integer | Auto | ID autoincremental |
| nombre | String(50) | Si | Nombre de la categoria |
Producto:
| Campo | Tipo | Requerido | Descripcion |
|---|---|---|---|
| idProducto | Integer | Auto | ID autoincremental |
| nombre | String(50) | Si | Nombre del producto |
| stock | Integer | Si | Cantidad disponible |
| fechaCreacion | LocalDateTime | No | Fecha de creacion |
| fechaActualizacion | LocalDateTime | No | Fecha de actualizacion |
| tipoIntercambio | String | No | Tipo de intercambio |
| lugarEntrega | String(50) | No | Lugar de entrega |
Consulta:
| Campo | Tipo | Requerido | Descripcion |
|---|---|---|---|
| idConsulta | Integer | Auto | ID autoincremental |
| persona | Persona (FK) | Si | Persona asociada |
| categoria | Categoria (FK) | Si | Categoria asociada |
| producto | Producto (FK) | Si | Producto asociado |
FiltroConsultaDTO (para busquedas):
| Campo | Tipo | Descripcion |
|---|---|---|
| run | String | RUN para filtrar |
| nombre | String | Nombre para filtrar |
| idConsulta | Integer | ID de consulta |
| idCategoria | Integer | ID de categoria |
| idPersona | Integer | ID de persona |
| idProducto | Integer | ID de producto |
| Tecnologia | Version | Para que se usa |
|---|---|---|
| Java | 17 | Lenguaje principal |
| Spring Boot | 3.4.4 | Framework base |
| Spring Security 6 | - | Autenticacion y autorizacion |
| Spring OAuth2 Resource Server | - | Validacion de tokens JWT |
| Spring Data JPA | - | Acceso a datos (Hibernate 6) |
| Spring Validation | - | Validacion de campos (@Valid, @Email) |
| Spring HATEOAS | - | Links en respuestas REST |
| Spring Mail | - | Soporte para envio de correos |
| Thymeleaf | - | Motor de templates |
| PostgreSQL | 12+ | Base de datos principal |
| H2 Database | - | Base de datos para tests |
| springdoc-openapi | 2.8.5 | Documentacion Swagger/OpenAPI |
| NimbusDS JOSE | - | Firma y verificacion de JWT (HS256) |
| BCrypt | - | Encriptacion de passwords |
| JaCoCo | 0.8.12 | Cobertura de codigo (100%) |
| Maven Wrapper | - | Build sin instalar Maven |
| JUnit 5 + Mockito | - | Testing |
Otro proceso usa el puerto 8080. Opciones:
# Opcion 1: Usar otro puerto
./mvnw spring-boot:run -Dspring-boot.run.arguments=--server.port=8081
# Opcion 2: Matar el proceso que usa el puerto (Linux/Mac)
lsof -i :8080
kill -9 <PID>
# Opcion 2: Matar el proceso (Windows)
netstat -ano | findstr :8080
taskkill /PID <PID> /F- Verifica que PostgreSQL esta corriendo:
pg_isready - Verifica que el usuario y password son correctos en
application.properties - Verifica que la base de datos existe:
psql -U postgres -l
- Verifica que estas enviando el header
Authorization: Bearer <token> - El token expira en 1 hora (3600 segundos). Si expiro, obtene uno nuevo
- Verifica que el usuario existe en la tabla
usuarioy tiene roles asignados
- Verifica que el header Basic Auth es correcto
- El valor debe ser
ofreceloapp:ofrecelo2020codificado en Base64 - Valor correcto:
Basic b2ZyZWNlbG9hcHA6b2ZyZWNlbG8yMDIw
- Asegurate de enviar
grant_type=passwordcomo parametro
- El proyecto exige 100% de cobertura. Si agregaste codigo nuevo, necesitas agregar tests
- Revisa el reporte en
target/site/jacoco/index.htmlpara ver que falta cubrir
chmod +x mvnwjava -version
# Si no muestra 17, configura JAVA_HOME:
export JAVA_HOME=/path/to/jdk-17# 1. Clonar
git clone https://github.com/fjrock/springboot-escalab-backend.git
cd springboot-escalab-backend
# 2. Configurar DB en src/main/resources/application.properties
# 3. Levantar
./mvnw spring-boot:run
# 4. Insertar datos iniciales (usuario, roles) en PostgreSQL
# 5. Obtener token
curl -X POST "http://localhost:8080/oauth/token?grant_type=password&username=admin&password=123456" \
-H "Authorization: Basic b2ZyZWNlbG9hcHA6b2ZyZWNlbG8yMDIw"
# 6. Usar la API
curl -H "Authorization: Bearer <token>" http://localhost:8080/persona/1
# 7. Ver Swagger
# http://localhost:8080/swagger-ui/index.html