Domain Model Overview
Sistema de Ventas implements a rich domain model with clear entity relationships and business logic. The data model supports the complete sales workflow from product catalog to purchase, order, and payment processing.
Entity Relationship Diagram
Note on Relationships : Foreign key relationships across microservices (shown with dashed lines in some diagrams) are logical references only, not database-enforced foreign keys. They are maintained through application-level validation via Feign client calls.
Authentication & Authorization Domain
Usuario (User Profile)
@ Entity
public class Usuario {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private Long id ;
private String nombres ;
private String apellidoPaterno ;
private String apellidoMaterno ;
private String dni ; // National ID
private String direccion ;
private String telefono ;
private Boolean estado ;
@ OneToOne ( cascade = CascadeType . ALL , fetch = FetchType . LAZY )
@ JoinColumn ( name = "auth_user_id" )
private AuthUser authUser ;
}
Business Rules :
One Usuario has exactly one AuthUser (authentication credentials)
Usuario can be active or inactive (estado)
Stores personal information separately from credentials
AuthUser (Authentication Credentials)
@ Entity
public class AuthUser {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private Long id ;
private String username ; // Unique
private String password ; // BCrypt encrypted
private String email ; // Unique
private Boolean activo ;
}
Security Features :
Password stored with BCrypt hashing
Unique username and email constraints
Active status for account management
Rol (Role)
@ Entity
public class Rol {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private Long id ;
private String nombre ; // Role name
private String descripcion ;
@ ManyToMany
private Set < Acceso > accesos ;
}
Acceso (Permission)
@ Entity
public class Acceso {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private Long id ;
private String nombre ; // Permission name
private String recurso ; // Resource/endpoint
private String accion ; // Action (GET, POST, etc.)
}
RBAC Model
Product Catalog Domain
Categoria (Category)
@ Entity
public class Categoria {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private Long id ;
@ NotBlank ( message = "El nombre es obligatorio" )
@ Column ( nullable = false , unique = true )
private String nombre ;
private String descripcion ;
private boolean estado = true ;
@ CreationTimestamp
private LocalDateTime fechaCreacion ;
}
Validation :
Name is required and unique
Active by default
Automatic timestamp on creation
Producto (Product)
@ Entity
public class Producto {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private Long id ;
@ Column ( nullable = false , unique = true )
private String codigo ; // Product code
@ Column ( nullable = false , unique = true )
private String nombre ; // Product name
private String descripcion ;
private Integer cantidad ; // Stock quantity
private Double precioVenta ; // Sale price
private Double costoCompra ; // Purchase cost
private boolean estado = true ;
private LocalDateTime fechaCreacion ;
private LocalDateTime fechaActualizacion ;
private String imagen ; // Image path
@ ManyToOne ( fetch = FetchType . LAZY )
@ JoinColumn ( name = "categoria_id" )
private Categoria categoria ;
@ PrePersist
protected void onCreate () {
fechaCreacion = LocalDateTime . now ();
fechaActualizacion = fechaCreacion;
}
@ PreUpdate
protected void onUpdate () {
fechaActualizacion = LocalDateTime . now ();
}
}
Business Logic :
Unique product code and name
Default quantity: 0
Automatic timestamp management
Soft delete via estado flag
Price margin: precioVenta - costoCompra
Category-Product Relationship
Customer Domain
Cliente (Customer)
@ Entity
@ Table ( name = "cliente" , uniqueConstraints = {
@ UniqueConstraint ( columnNames = { "dni" })
})
public class Cliente {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private Long id ;
@ Column ( nullable = false , unique = true )
private String dni ; // National ID
@ Column ( nullable = false )
private String nombre ;
private String apellido ;
private String direccion ;
private String telefono ;
@ Column ( nullable = false , unique = true )
private String email ;
private LocalDateTime fechaRegistro ;
private Boolean activo = true ;
@ PrePersist
public void prePersist () {
this . fechaRegistro = LocalDateTime . now ();
if (activo == null ) {
activo = true ;
}
}
}
Unique Constraints :
DNI (national ID)
Email address
Default Values :
Active status: true
Registration date: current timestamp
Supplier Domain
Proveedor (Supplier)
@ Entity
public class Proveedor {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private Long id ;
private String ruc ; // Tax ID
private String nombre ; // Company name
private String telefono ;
private String direccion ;
private String correo ; // Email
private Boolean estado ; // Active status
@ PrePersist
public void prePersist () {
this . estado = true ;
}
}
Business Rules :
RUC serves as unique business identifier
Active by default
Stores company contact information
Transaction Domains
Venta (Sale)
@ Entity
public class Venta {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private Integer id ;
@ Column ( nullable = false , unique = true )
private String serie ; // 3-letter code (auto-generated)
@ Column ( nullable = false , unique = true )
private String numero ; // 6-digit number (auto-generated)
private String descripcion ;
@ Column ( name = "cliente_id" )
private Long clienteId ;
@ Transient
private Cliente cliente ; // Fetched from Cliente Service
@ OneToMany ( fetch = FetchType . LAZY , cascade = CascadeType . ALL )
@ JoinColumn ( name = "venta_id" )
private List < VentaDetalle > detalle ;
private LocalDateTime fechaVenta ;
private Double baseImponible ; // Taxable base
private Double igv ; // 18% tax (Peru)
private Double total ;
@ Column ( name = "formapago_id" )
private Long formapagoId ;
@ Transient
private FormaPago formaPago ; // Fetched from Pagos Service
@ PrePersist
public void prePersist () {
calcularTotales ();
generarSerieYNumero ();
}
@ PreUpdate
public void preUpdate () {
calcularTotales ();
}
private void calcularTotales () {
this . baseImponible = 0.0 ;
this . igv = 0.0 ;
this . total = 0.0 ;
if (detalle != null ) {
for ( VentaDetalle d : detalle) {
d . calcularMontos ();
this . baseImponible += d . getBaseImponible ();
this . igv += d . getIgv ();
this . total += d . getTotal ();
}
}
}
}
VentaDetalle (Sale Line Item)
@ Entity
public class VentaDetalle {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private Integer id ;
private Double cantidad ;
private Double precio ;
private Double baseImponible ;
private Double igv ;
private Double total ;
@ Column ( name = "producto_id" )
private Long productoId ;
@ Transient
private Producto producto ; // Fetched from Catalogo Service
@ PrePersist
@ PreUpdate
public void calcularMontos () {
if (precio != null && cantidad != null ) {
double baseUnitario = precio / 1.18 ; // Peru: 18% IGV
double igvUnitario = precio - baseUnitario;
this . baseImponible = baseUnitario * cantidad;
this . igv = igvUnitario * cantidad;
this . total = precio * cantidad;
}
}
}
Tax Calculation Logic
Peru Tax System (IGV) The system implements Peru’s 18% IGV (Impuesto General a las Ventas) tax:
Price includes tax : precio is the final price with tax
Base calculation : baseImponible = precio / 1.18
Tax calculation : igv = precio - baseImponible
Total : total = precio × cantidad
Example:
Product price: S/. 100.00 (includes tax)
Base: S/. 84.75
IGV (18%): S/. 15.25
Total for 2 units: S/. 200.00
Compra (Purchase)
@ Entity
public class Compra {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private Long id ;
@ Column ( nullable = false , unique = true )
private String serie ;
@ Column ( nullable = false , unique = true )
private String numero ;
private String descripcion ;
@ Column ( name = "proveedor_id" )
private Long proveedorId ;
@ Transient
private Proveedor proveedor ; // Fetched from Proveedor Service
@ OneToMany ( fetch = FetchType . LAZY , cascade = CascadeType . ALL )
@ JoinColumn ( name = "compra_id" )
private List < CompraDetalle > detalle ;
private LocalDateTime fechaCompra ;
private Double baseImponible ;
private Double igv ;
private Double total ;
@ Column ( name = "formapago_id" )
private Long formapagoId ;
@ Transient
private FormaPago formaPago ;
// Similar calculation logic as Venta
}
CompraDetalle (Purchase Line Item)
Similar structure to VentaDetalle:
Quantity, price, tax calculations
Reference to Producto (via productoId)
Automatic calculation on persist/update
Pedido (Order)
@ Entity
public class Pedido {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private Integer id ;
@ Column ( nullable = false , unique = true )
private String codigo ;
@ Column ( nullable = false , unique = true )
private String serie ;
private String descripcion ;
private String estado ; // Order status
@ Column ( name = "cliente_id" )
private Long clienteId ;
@ Transient
private Cliente cliente ;
@ OneToMany ( fetch = FetchType . LAZY , cascade = CascadeType . ALL )
@ JoinColumn ( name = "pedido_id" )
private List < PedidoDetalle > detalle ;
private LocalDateTime fechaPedido ;
private LocalDate fechaEntrega ; // Delivery date
private Double baseImponible ;
private Double igv ;
private Double total ;
@ Column ( name = "formapago_id" )
private Long formapagoId ;
@ Transient
private FormaPago formaPago ;
public Pedido () {
this . fechaPedido = LocalDateTime . now ();
this . fechaEntrega = this . fechaPedido . toLocalDate (). plusWeeks ( 1 );
this . estado = "PENDIENTE" ;
}
}
Order Status Values :
PENDIENTE: Pending
EN_PROCESO: In process
COMPLETADO: Completed
CANCELADO: Cancelled
Default Behavior :
Status: PENDIENTE
Delivery date: +7 days from order date
Automatic timestamp on creation
Payment Domain
@ Entity
public class FormaPago {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private Long id ;
private String nombre ; // Payment method name
}
Standard Payment Methods :
Efectivo (Cash)
Transferencia Bancaria (Bank Transfer)
Cheque (Check)
Tarjeta de Crédito (Credit Card)
Tarjeta de Débito (Debit Card)
Depósito Bancario (Bank Deposit)
Letra de Cambio (Bill of Exchange)
Pagaré (Promissory Note)
Transaction Pattern
All transaction entities (Venta, Compra, Pedido) follow a common pattern:
Shared Characteristics
Header Features
Unique series and number
Date tracking
Tax calculation
Total aggregation
Payment method reference
Detail Features
Line item tracking
Quantity and price
Tax breakdown
Product reference
Automatic calculations
Cross-Service Data Flow
Creating a Sale (Venta)
Data Validation Flow
Data Integrity Patterns
Application-Level Foreign Keys
Since database foreign keys don’t span microservices:
// In VentaService
public Venta createVenta ( VentaRequest request) {
// Validate cliente exists
Cliente cliente = clienteFeign . listarcliente ( request . getClienteId ())
. orElseThrow (() -> new ClienteNotFoundException ());
// Validate each producto exists and has stock
for ( VentaDetalleRequest detalle : request . getDetalle ()) {
Producto producto = productoFeign . listarProducto ( detalle . getProductoId ())
. orElseThrow (() -> new ProductoNotFoundException ());
if ( producto . getCantidad () < detalle . getCantidad ()) {
throw new InsufficientStockException ();
}
}
// Validate payment method exists
FormaPago formaPago = formaPagoFeign . buscarFormaPago ( request . getFormapagoId ())
. orElseThrow (() -> new FormaPagoNotFoundException ());
// All validations passed, create venta
return ventaRepository . save (venta);
}
Eventual Consistency
Some operations may have eventual consistency:
Sale Created → Venta saved to database
Inventory Update → Async call to Catalogo service
Update Confirmation → Eventual consistency achieved
If inventory update fails:
Circuit breaker triggers fallback
Retry mechanism kicks in
Compensation transaction may be needed
Common Data Transfer Objects (DTOs)
Product DTO (used in Venta, Compra, Pedido services)
public class Producto {
private Long id ;
private String codigo ;
private String nombre ;
private String descripcion ;
private Integer cantidad ;
private Double precioVenta ;
private Double costoCompra ;
private boolean estado ;
private String imagen ;
private Categoria categoria ;
}
Cliente DTO (used in Venta, Pedido services)
public class Cliente {
private Long id ;
private String dni ;
private String nombre ;
private String apellido ;
private String direccion ;
private String telefono ;
private String email ;
private Boolean activo ;
}
Best Practices
Entity Design
Use @Transient for Cross-Service Data Store IDs in database, fetch full objects via Feign when needed
Implement Lifecycle Callbacks Use @PrePersist, @PreUpdate for automatic calculations and timestamps
Validate at Multiple Levels
Bean validation (@NotNull, @NotBlank)
Service-level business rules
Cross-service validation via Feign
Use Soft Deletes Implement estado or activo flags instead of hard deletes
Use FetchType.LAZY for collections and relationships
Implement pagination for large result sets
Cache reference data (categories, payment methods)
Use DTOs to avoid lazy initialization exceptions
Data Consistency
Validate foreign IDs before saving
Implement compensation logic for failed operations
Use circuit breakers for resilience
Log all cross-service validation failures
Next Steps
Architecture Overview Return to architecture overview
Microservices Explore microservice implementations