Architectural Overview
InvestGo is built using a layered architecture pattern with Spring Boot, ensuring separation of concerns and maintainability. The application follows Domain-Driven Design (DDD) principles with clearly defined layers.
Layered Architecture
┌──────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ ┌────────────────────────────────────────────────────┐ │
│ │ REST Controllers (@RestController) │ │
│ │ - AuthenticationController │ │
│ │ - OportunidadInversionController │ │
│ │ - UsuarioController, EmpresaController, etc. │ │
│ └────────────────────────────────────────────────────┘ │
└───────────────────────┬──────────────────────────────────┘
│
┌───────────────────────▼──────────────────────────────────┐
│ Security Layer │
│ ┌────────────────────────────────────────────────────┐ │
│ │ JWT Authentication Filter │ │
│ │ Spring Security Configuration │ │
│ │ BCrypt Password Encoder │ │
│ └────────────────────────────────────────────────────┘ │
└───────────────────────┬──────────────────────────────────┘
│
┌───────────────────────▼──────────────────────────────────┐
│ Business Layer │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Service Interfaces & Implementations (@Service) │ │
│ │ - OportunidadInversionService │ │
│ │ - UsuarioService, CarteraService, etc. │ │
│ │ Business logic, validation, calculations │ │
│ └────────────────────────────────────────────────────┘ │
└───────────────────────┬──────────────────────────────────┘
│
┌───────────────────────▼──────────────────────────────────┐
│ Data Access Layer │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Repositories (@Repository) │ │
│ │ - Spring Data JPA Repositories │ │
│ │ - Custom query methods │ │
│ └────────────────────────────────────────────────────┘ │
└───────────────────────┬──────────────────────────────────┘
│
┌───────────────────────▼──────────────────────────────────┐
│ Domain Layer │
│ ┌────────────────────────────────────────────────────┐ │
│ │ JPA Entities (@Entity) │ │
│ │ - Usuario, Empresa, Factura │ │
│ │ - OportunidadInversion, Cartera, etc. │ │
│ └────────────────────────────────────────────────────┘ │
└───────────────────────┬──────────────────────────────────┘
│
┌───────────────────────▼──────────────────────────────────┐
│ Persistence Layer │
│ MySQL Database │
│ (InvestGo schema) │
└──────────────────────────────────────────────────────────┘
Package Structure
The application follows Spring Boot conventions with organized packages:
com.proyecto.integrador/
├── SistemaFactoringBackendApplication.java # Main class with CommandLineRunner
├── controladores/ # REST Controllers
│ ├── AuthenticationController.java
│ ├── OportunidadInversionController.java
│ ├── UsuarioController.java
│ ├── EmpresaController.java
│ ├── FacturaController.java
│ ├── CarteraController.java
│ ├── TransaccionController.java
│ ├── CuentaBancariaController.java
│ ├── BancoController.java
│ └── MonedaController.java
├── servicios/ # Service layer
│ ├── impl/ # Service implementations
│ │ └── UserDetailsServiceImpl.java
│ ├── OportunidadInversionService.java
│ ├── UsuarioService.java
│ ├── CarteraService.java
│ ├── FacturaService.java
│ └── ...
├── repositorios/ # JPA Repositories
│ ├── OportunidadInversionRepository.java
│ ├── UsuarioRepository.java
│ └── ...
├── entidades/ # JPA Entities
│ ├── Usuario.java
│ ├── Empresa.java
│ ├── Factura.java
│ ├── OportunidadInversion.java
│ ├── OportunidadFactura.java
│ ├── OportunidadUsuario.java
│ ├── Cartera.java
│ ├── Transacciones.java
│ ├── CuentaBancaria.java
│ ├── Rol.java
│ ├── Riesgo.java
│ ├── Bancos.java
│ ├── Monedas.java
│ ├── TipoTransaccion.java
│ └── Authority.java
├── configuraciones/ # Configuration classes
│ ├── MySecurityConfig.java # Spring Security config
│ ├── JwtAuthenticationFilter.java # JWT filter
│ ├── JwtAuthenticationEntryPoint.java # Auth entry point
│ └── JwtUtils.java # JWT utilities
├── dto/ # Data Transfer Objects
│ ├── JwtRequest.java
│ └── JwtResponse.java
└── utils/ # Utilities
└── AppSettings.java # Application settings
Core Domain Model
Entity Relationship Diagram
InvestGo’s domain model revolves around users, invoices, and investment opportunities:
┌─────────────┐
│ Rol │
├─────────────┤
│ id (PK) │
│ tipo │
└──────┬──────┘
│
│ 1
│
│ N
┌─────────────────────▼──────────────────────┐
│ Usuario │
├────────────────────────────────────────────┤
│ id (PK) │
│ nombre, apellidoPa, apellidoMa │
│ username, password │
│ correo, telefono, dni │
│ foto, fecha, enable │
│ idTipoUsu (FK → Rol) │
└────┬─────────────────────────────────┬────┘
│ 1 │ 1
│ │
│ 1 │ N
┌─────────▼──────────┐ ┌──────────▼─────────────┐
│ Cartera │ │ OportunidadInversion │
├────────────────────┤ ├────────────────────────┤
│ idCartera (PK) │ │ idOportunidad (PK) │
│ saldo │ │ rendimiento, tir │
│ idUsu (FK) │ │ monto, montoRecaudado │
└────────────────────┘ │ fechaCaducidad │
│ fechaRegistro │
│ fechaPago, enable │
│ idEmpresa (FK) │
│ idUsu (FK) │
└──────────┬─────────────┘
│ N
│
│ 1
┌──────────────────────────────────────▼────────────┐
│ Empresa │
├───────────────────────────────────────────────────┤
│ idEmpresa (PK) │
│ representanteLegal, nomEmpresa │
│ ruc, razonSocial │
│ fechaDeInicioActv, direccion │
│ telefono, correo, sector │
│ fechRegistro, enable │
│ idRiesgo (FK → Riesgo) │
└───────┬───────────────────────────────────────────┘
│ 1 │ 1
│ │
│ N │ N
┌──────────▼──────────┐ ┌─────────▼────────┐
│ Factura │ │ Riesgo │
├─────────────────────┤ ├──────────────────┤
│ idFactura (PK) │ │ idRiesgo (PK) │
│ codFactura │ │ rango (A/B/C) │
│ monto │ │ descripcion │
│ fechaEmision │ └──────────────────┘
│ fechaPago │
│ enable, descripcion │
│ idEmpresa (FK) │
└──────┬──────────────┘
│ N
│
│ N (through OportunidadFactura)
│
┌──────▼─────────────────┐
│ OportunidadFactura │
├────────────────────────┤
│ idOpoFactura (PK) │
│ idOportunidad (FK) │
│ idFactura (FK) │
└────────────────────────┘
┌────────────────────────┐
│ OportunidadUsuario │ (Investors in opportunities)
├────────────────────────┤
│ idOpoUsu (PK) │
│ idOportunidad (FK) │
│ idUsuario (FK) │
│ monto, fechaInversion │
└────────────────────────┘
┌────────────────────────┐ ┌──────────────────┐
│ CuentaBancaria │─────N────│ Bancos │
├────────────────────────┤ 1 ├──────────────────┤
│ idCuentaBancaria (PK) │ │ idBancos (PK) │
│ nroCuenta │ │ nomBancos │
│ idUsuario (FK) │ └──────────────────┘
│ idBanco (FK) │
│ idMoneda (FK) │ ┌──────────────────┐
└────────┬───────────────┘ │ Monedas │
│ 1 ├──────────────────┤
│ │ idMonedas (PK) │
│ N │ nomMonedas │
┌────────▼───────────────┐ │ valorMoneda │
│ Transacciones │──────N───┤ (PEN/USD) │
├────────────────────────┤ 1 └──────────────────┘
│ idTransaccion (PK) │
│ monto, fecha │ ┌──────────────────┐
│ idCuentaBancaria (FK) │──────N───│ TipoTransaccion │
│ idTipoTransaccion (FK) │ 1 ├──────────────────┤
└────────────────────────┘ │ id (PK) │
│ tipo │
│ (Deposito/Retiro)│
└──────────────────┘
Key Entity Details
Usuario (User)
Table: usuarios
Implements: UserDetails (Spring Security)
@ Entity
@ Table ( name = "usuarios" )
public class Usuario implements UserDetails {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private long id ;
private String nombre ;
private String apellidoPa ;
private String apellidoMa ;
private String telefono ;
private String correo ;
private String username ;
private String password ; // BCrypt encrypted
private String foto ;
@ Temporal ( TemporalType . DATE )
@ JsonFormat ( pattern = "yyyy-MM-dd" , timezone = "America/Lima" )
private Date fecha ;
private String dni ;
private String enable ; // "Activo" or "No Activo"
@ ManyToOne
@ JoinColumn ( name = "idTipoUsu" , insertable = false , updatable = false )
private Rol tiporol ;
private long idTipoUsu ; // 1 = INVERSIONISTA, 2 = ADMIN
}
Key Features:
Integrates with Spring Security through UserDetails
Password encrypted with BCrypt
One-to-one relationship with Cartera (wallet)
Many-to-one relationship with Rol
OportunidadInversion (Investment Opportunity)
Table: OportunidadInversion
@ Entity
@ Table ( name = "OportunidadInversion" )
public class OportunidadInversion {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private int idOportunidad ;
private double rendimiento ; // Yield percentage
private double tir ; // Internal Rate of Return
private Double monto ; // Total amount needed
private Double montoRecaudado ; // Amount raised so far
@ Temporal ( TemporalType . DATE )
@ JsonFormat ( pattern = "yyyy-MM-dd" , timezone = "America/Lima" )
private Date fechaCaducidad ; // Maturity date
@ Temporal ( TemporalType . DATE )
private Date fechaRegistro ; // Registration date
@ Temporal ( TemporalType . DATE )
private Date fechaPago ; // Payment date (calculated)
private String enable ; // "Activo" or "No Activo"
@ ManyToOne
@ JoinColumn ( name = "idEmpresa" )
private Empresa empresa ;
private int idEmpresa ;
@ ManyToOne
@ JoinColumn ( name = "idUsu" )
private Usuario usuario ; // Admin who created it
private long idUsu ;
}
Business Logic:
fechaPago is automatically calculated as fechaCaducidad - 2 months
montoRecaudado tracks investment progress
enable status determines visibility to investors
Factura (Invoice)
Table: factura
@ Entity
@ Table ( name = "factura" )
public class Factura {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private int idFactura ;
private String codFactura ;
private double monto ;
@ Temporal ( TemporalType . DATE )
@ JsonFormat ( pattern = "yyyy-MM-dd" , timezone = "America/Lima" )
private Date fechaEmision ;
@ Temporal ( TemporalType . DATE )
private Date fechaPago ;
private String enable ; // "Activo" = available, "No Activo" = used
private String descripcion ;
@ ManyToOne ( fetch = FetchType . LAZY )
@ JoinColumn ( name = "idEmpresa" )
@ JsonIgnoreProperties ({ "hibernateLazyInitializer" , "handler" })
private Empresa empresa ;
}
Key Features:
Lazy loading for Empresa relationship
When added to OportunidadInversion, status changes to “No Activo”
Multiple invoices can be bundled into one opportunity via OportunidadFactura
Empresa (Company)
Table: empresas
@ Entity
@ Table ( name = "empresas" )
public class Empresa {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private int idEmpresa ;
private String representanteLegal ;
private String nomEmpresa ;
private String ruc ; // Tax ID
private String razonSocial ;
@ Temporal ( TemporalType . DATE )
private Date fechaDeInicioActv ;
private String direccion ;
private String telefono ;
private String correo ;
private String sector ;
@ Temporal ( TemporalType . DATE )
private Date fechRegistro ;
private String enable ;
@ ManyToOne
@ JoinColumn ( name = "idRiesgo" )
private Riesgo riesgo ;
private int idRiesgo ;
}
Cartera (Wallet)
Table: Carteras
@ Entity
@ Table ( name = "Carteras" )
public class Cartera {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private int idCartera ;
private double saldo ; // Current balance
@ ManyToOne
@ JoinColumn ( name = "idUsu" )
private Usuario usuario ;
private long idUsu ;
}
Business Logic:
Created automatically when a user is registered
Default admin wallet starts with balance of 10,000,000
Updated during investments and transactions
Transacciones (Transactions)
Table: transacciones
@ Entity
@ Table ( name = "transacciones" )
public class Transacciones {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private long idTransaccion ;
private double monto ;
@ ManyToOne
@ JoinColumn ( name = "idCuentaBancaria" )
private CuentaBancaria cuentaBancaria ;
private int idCuentaBancaria ;
@ ManyToOne
@ JoinColumn ( name = "idTipoTransaccion" )
private TipoTransaccion tipoTransaccion ;
private long idTipoTransaccion ; // 1 = Deposito, 2 = Retiro
@ Temporal ( TemporalType . DATE )
@ JsonFormat ( pattern = "yyyy-MM-dd" , timezone = "America/Lima" )
private Date fecha ;
}
Security Architecture
JWT Authentication Flow
┌────────┐ ┌────────────────┐
│ Client │ │ API Server │
└───┬────┘ └───────┬────────┘
│ │
│ 1. POST /generate-token │
│ { username, password } │
│────────────────────────────────────────────────>│
│ │
│ 2. Authenticate │
│ (BCrypt verify) │
│ │
│ 3. Return JWT token │
│<────────────────────────────────────────────────│
│ { token: "eyJ..." } │
│ │
│ 4. GET /api/user/listarOportunidadInversion │
│ Authorization: Bearer eyJ... │
│────────────────────────────────────────────────>│
│ │
│ 5. JwtAuthenticationFilter
│ - Extract token │
│ - Validate signature │
│ - Load UserDetails │
│ - Set Authentication │
│ │
│ 6. Return data │
│<────────────────────────────────────────────────│
│ │
Security Components
MySecurityConfig
File: configuraciones/MySecurityConfig.java
@ EnableWebSecurity
@ Configuration
@ EnableGlobalMethodSecurity ( prePostEnabled = true )
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@ Override
protected void configure ( HttpSecurity http ) throws Exception {
http
. csrf (). disable ()
. cors (). disable ()
. authorizeRequests ()
. antMatchers ( "/generate-token" , "/api/**" ). permitAll ()
. antMatchers ( HttpMethod . OPTIONS ). permitAll ()
. anyRequest (). authenticated ()
. and ()
. exceptionHandling ()
. authenticationEntryPoint (unauthorizedHandler)
. and ()
. sessionManagement ()
. sessionCreationPolicy ( SessionCreationPolicy . STATELESS );
http . addFilterBefore (jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter . class );
}
}
Key Configurations:
CSRF disabled (stateless API)
/generate-token and /api/** are public
All other endpoints require authentication
Stateless session management (no session cookies)
JWT filter runs before Spring’s authentication filter
JwtAuthenticationFilter
File: configuraciones/JwtAuthenticationFilter.java
@ Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@ Override
protected void doFilterInternal ( HttpServletRequest request ,
HttpServletResponse response ,
FilterChain filterChain ) {
String requestTokenHeader = request . getHeader ( "Authorization" );
String username = null ;
String jwtToken = null ;
// Extract token from "Bearer <token>" header
if (requestTokenHeader != null &&
requestTokenHeader . startsWith ( "Bearer " )) {
jwtToken = requestTokenHeader . substring ( 7 );
username = this . jwtUtils . extractUsername (jwtToken);
}
// Validate and set authentication
if (username != null &&
SecurityContextHolder . getContext (). getAuthentication () == null ) {
UserDetails userDetails =
this . userDetailsService . loadUserByUsername (username);
if ( this . jwtUtils . validateToken (jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken (
userDetails, null , userDetails . getAuthorities ());
SecurityContextHolder . getContext (). setAuthentication (auth);
}
}
filterChain . doFilter (request, response);
}
}
Filter Responsibilities:
Extract JWT from Authorization header
Parse and validate token
Load user details from database
Set authentication in Spring Security context
Design Patterns Used
1. Layered Architecture
Clear separation: Controllers → Services → Repositories → Entities
Each layer has specific responsibilities
Promotes testability and maintainability
2. Dependency Injection
Extensive use of @Autowired for loose coupling
Constructor injection in newer components
Spring manages bean lifecycle
3. Repository Pattern
Spring Data JPA repositories abstract data access
Custom query methods defined in repository interfaces
Automatic CRUD operations
4. Data Transfer Objects (DTO)
JwtRequest and JwtResponse for authentication
Separates API contracts from domain entities
5. Filter Chain Pattern
JwtAuthenticationFilter in security filter chain
Processes requests before reaching controllers
6. Command Pattern
CommandLineRunner in main application class
Initializes database with seed data on startup
7. Factory Pattern
BCryptPasswordEncoder bean creation
AuthenticationManager bean configuration
Database Schema
Key Tables
Table Primary Key Foreign Keys Description usuariosididTipoUsu → rol.idUser accounts rolid- User roles (Admin/Investor) empresasidEmpresaidRiesgo → riesgo.idRiesgoCompanies facturaidFacturaidEmpresa → empresas.idEmpresaInvoices OportunidadInversionidOportunidadidEmpresa, idUsuInvestment opportunities OportunidadFacturaidOpoFacturaidOportunidad, idFacturaInvoice-opportunity junction OportunidadUsuarioidOpoUsuidOportunidad, idUsuarioUser investments CarterasidCarteraidUsu → usuarios.idUser wallets CuentaBancariaidCuentaBancariaidUsuario, idBanco, idMonedaBank accounts transaccionesidTransaccionidCuentaBancaria, idTipoTransaccionWallet transactions riesgoidRiesgo- Risk levels (A/B/C) BancosidBancos- Supported banks MonedasidMonedas- Currencies (PEN/USD) TipoTransaccionid- Transaction types
Seed Data
On first startup, CommandLineRunner populates:
Roles:
INVERSIONISTA (id: 1)
ADMIN (id: 2)
Default Admin User:
Username: jamie
Password: Admin12345 (BCrypt hashed)
Role: ADMIN
Wallet balance: 10,000,000
Transaction Types:
Deposito (id: 1)
Retiro (id: 2)
Banks:
Banco continental BBVA
Banco de credito BCP
Scotiabank
Interbank
Dinners Club
Banbif
American Express
Currencies:
Risk Levels:
A: “El riesgo de inversion es nulo!”
B: “El riesgo de inversion es CASI nulo!”
C: “El riesgo de inversion algo elevado”
API Design Principles
RESTful Conventions
GET - Retrieve resources
POST - Create new resources
PUT - Update existing resources
DELETE - Soft delete (set enable = "No Activo")
Response Structure
Success Response:
{
"mensaje" : "La oportunidad de inversion se registro exitosamente!" ,
"OportunidadInversion" : { ... }
}
Error Response:
{
"mensaje" : "No existe oportunidad con Id: 123" ,
"error" : "Database access error: ..."
}
HTTP Status Codes
200 OK - Successful operation
400 BAD_REQUEST - Invalid input or business rule violation
401 UNAUTHORIZED - Missing or invalid JWT token
404 NOT_FOUND - Resource not found
409 CONFLICT - Duplicate entry or state conflict
500 INTERNAL_SERVER_ERROR - Database or server error
Endpoints like /api/user/listarOportunidadInversion/page/{page} use Spring Data’s Pageable:
Pageable pageable = PageRequest . of (page, 8 );
return oportunidadInversionService . listarOportunidadInversionesActivasPage (
"No Activo" , pageable);
Returns 8 items per page.
Transaction Workflow Example
Creating an Investment Opportunity
Admin adds invoices to temporary list:
POST /api/addFactura
{ "idFactura": 1 }
Admin creates opportunity:
POST /api/insertaOportunidadInversion
{
"rendimiento": 12.5,
"tir": 15.3,
"monto": 50000,
"fechaCaducidad": "2024-06-30",
"idEmpresa": 1
}
System automatically:
Sets montoRecaudado = 0.0
Sets enable = "Activo"
Calculates fechaPago = fechaCaducidad - 2 months
Creates OportunidadFactura entries for each invoice
Sets invoices to enable = "No Activo"
Clears temporary invoice list
Investors view opportunity:
GET /api/user/listarOportunidadInversion
Investor makes investment (separate workflow via OportunidadUsuario)
Lazy Loading
@ ManyToOne ( fetch = FetchType . LAZY )
@ JoinColumn ( name = "idEmpresa" )
@ JsonIgnoreProperties ({ "hibernateLazyInitializer" , "handler" })
private Empresa empresa ;
Prevents N+1 query problems
@JsonIgnoreProperties avoids serialization errors
Query Optimization
Custom repository methods for filtered queries
Pagination for large datasets
spring.jpa.show-sql=true for query analysis
Database Indexing
Recommended indexes (not in code, add manually):
usuarios(username) - Frequent lookups in authentication
OportunidadInversion(enable) - Filtering active opportunities
factura(enable, idEmpresa) - Finding available invoices
Configuration Management
Application Properties
Location: src/main/resources/application.properties
# Server
server.port =8091
# Database
spring.datasource.url =jdbc:mysql://localhost:3306/InvestGo
spring.datasource.username =root
spring.datasource.password =1234
# JPA/Hibernate
spring.jpa.hibernate.ddl-auto =update # Creates/updates schema
spring.jpa.show-sql =true # Logs SQL queries
spring.jpa.properties.hibernate.format_sql =true
Environment-Specific Configuration
For production, override properties:
java -jar app.jar \
--spring.datasource.url=jdbc:mysql://prod-db:3306/InvestGo \
--spring.datasource.password=${ DB_PASSWORD } \
--spring.jpa.hibernate.ddl-auto=validate
Never use ddl-auto=update in production! Use validate or none and manage schema with migration tools like Flyway or Liquibase.
Next Steps
API Reference Detailed endpoint documentation with examples
Quick Start Set up your development environment