Learn how to create shipments using the REST API, including all required fields, validation rules, and automatic semantic synchronization.
Overview
Creating a shipment involves three main entities:
Client (Remitente) - The sender
Encomienda (Package) - The physical item being shipped
Envio (Shipment) - The transport operation
Prerequisites
Before creating a shipment, ensure you have:
Sender client created in the system
Origin branch (sucursal) exists
Destination branch (sucursal) exists
Both services running (ports 8080 and 8081)
Step 1: Create a Client
Create Client via API
curl -X POST http://localhost:8080/api/v1/clientes \
-H "Content-Type: application/json" \
-d '{
"nombreCompleto": "Juan Pérez García",
"dni": "12345678",
"telefono": "987654321",
"correo": "[email protected] "
}'
Response: {
"id" : 1 ,
"nombreCompleto" : "Juan Pérez García" ,
"dni" : "12345678" ,
"telefono" : "987654321" ,
"correo" : "[email protected] "
}
Save the id field - you’ll need it as remitenteId when creating shipments.
Client Validation Rules
Field Type Required Validation nombreCompletoString Yes 1-100 characters dniString Yes Exactly 8 digits, unique telefonoString Yes Exactly 9 digits correoString Yes Valid email format
List Existing Clients
curl http://localhost:8080/api/v1/clientes
Search by name/dni/email: curl http://localhost:8080/api/v1/clientes/buscar?keyword=juan
Step 2: Verify Branches Exist
List All Branches
curl http://localhost:8080/api/v1/sucursales
Response: [
{
"id" : 1 ,
"nombre" : "Sucursal Centro Cajamarca" ,
"ciudad" : "Cajamarca" ,
"direccion" : "Jr. Amalia Puga 450" ,
"telefono" : "076345678"
},
{
"id" : 2 ,
"nombre" : "Sucursal Miraflores Lima" ,
"ciudad" : "Lima" ,
"direccion" : "Av. Larco 1234" ,
"telefono" : "014567890"
}
]
Create Branch if Needed
curl -X POST http://localhost:8080/api/v1/sucursales \
-H "Content-Type: application/json" \
-d '{
"nombre": "Sucursal Centro Cusco",
"ciudad": "Cusco",
"direccion": "Av. El Sol 456",
"telefono": "084234567"
}'
Step 3: Create a Shipment
Basic Shipment
curl -X POST http://localhost:8080/api/v1/envios \
-H "Content-Type: application/json" \
-d '{
"remitenteId": 1,
"encomienda": {
"descripcion": "Laptop Dell XPS 15",
"peso": 2.5,
"dimensiones": "40x30x10"
},
"nombreDestinatario": "María García",
"dniDestinatario": "87654321",
"sucursalOrigenId": 1,
"sucursalDestinoId": 2,
"precio": 35.50
}'
Successful Response:
{
"id" : 1 ,
"codigoSeguimiento" : "ENV-1710334200000" ,
"remitente" : {
"id" : 1 ,
"nombreCompleto" : "Juan Pérez García" ,
"dni" : "12345678"
},
"encomienda" : {
"id" : 1 ,
"descripcion" : "Laptop Dell XPS 15" ,
"peso" : 2.5 ,
"dimensiones" : "40x30x10"
},
"nombreDestinatario" : "María García" ,
"dniDestinatario" : "87654321" ,
"sucursalOrigen" : {
"id" : 1 ,
"nombre" : "Sucursal Centro Cajamarca" ,
"ciudad" : "Cajamarca"
},
"sucursalDestino" : {
"id" : 2 ,
"nombre" : "Sucursal Miraflores Lima" ,
"ciudad" : "Lima"
},
"precio" : 35.5 ,
"estado" : "PENDIENTE" ,
"fechaEnvio" : "2026-03-09T10:30:00" ,
"fechaEntrega" : null ,
"placa" : null ,
"contrasenaEntrega" : "4521"
}
Auto-generated fields:
codigoSeguimiento: Unique tracking code (format: ENV-{timestamp})
contrasenaEntrega: 4-digit password for delivery (share with recipient)
estado: Always starts as PENDIENTE
fechaEnvio: Current timestamp (or specified date)
Request DTO Structure
The CrearEnvioRequestDTO (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/dtos/CrearEnvioRequestDTO.java:12) defines the request structure:
public class CrearEnvioRequestDTO {
private Long remitenteId ;
private EncomiendaDTO encomienda ;
private String nombreDestinatario ;
private String dniDestinatario ;
private Long sucursalOrigenId ;
private Long sucursalDestinoId ;
private Double precio ;
private String contrasenaEntrega ; // Optional: auto-generated if null
private LocalDate fechaEnvio ; // Optional: uses current date if null
}
Validation Rules
Field Rule Error Message remitenteIdMust exist in database ”Remitente no encontrado”
Field Type Required Validation descripcionString Yes 1-500 characters pesoDouble Yes Must be > 0 dimensionesString Yes Format: LxWxH (e.g., “40x30x10”)
Code Reference: (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/servicios/Impl/EnvioServiceImpl.java:89)if ( requestDTO . getEncomienda () == null || requestDTO . getEncomienda (). getPeso () <= 0 )
throw new IllegalArgumentException ( "Datos de encomienda inválidos o peso debe ser mayor a 0" );
Field Type Required Validation nombreDestinatarioString Yes 1-100 characters dniDestinatarioString Yes Exactly 8 digits
Code Reference: (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/servicios/Impl/EnvioServiceImpl.java:92)if ( requestDTO . getDniDestinatario () == null || ! requestDTO . getDniDestinatario (). matches ( " \\ d{8}" ))
throw new IllegalArgumentException ( "El DNI debe tener 8 dígitos" );
Field Rule Error Message sucursalOrigenIdMust exist ”Sucursal origen no encontrada” sucursalDestinoIdMust exist ”Sucursal destino no encontrada” Origin ≠ Destination Must be different ”La sucursal de origen y destino no pueden ser iguales”
Code Reference: (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/servicios/Impl/EnvioServiceImpl.java:86)if ( origen . getId (). equals ( destino . getId ()))
throw new IllegalArgumentException ( "La sucursal de origen y destino no pueden ser iguales" );
Field Type Required Validation precioDouble Yes Must be > 0 contrasenaEntregaString No 4 digits (auto-generated if null)
Code Reference: (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/servicios/Impl/EnvioServiceImpl.java:95)String contrasena = ( requestDTO . getContrasenaEntrega () == null || requestDTO . getContrasenaEntrega (). isEmpty ())
? generarContrasena4Digitos ()
: requestDTO . getContrasenaEntrega ();
if ( ! contrasena . matches ( " \\ d{4}" ))
throw new IllegalArgumentException ( "La contraseña debe ser de 4 dígitos numéricos" );
Advanced Examples
Custom Delivery Password
curl -X POST http://localhost:8080/api/v1/envios \
-H "Content-Type: application/json" \
-d '{
"remitenteId": 1,
"encomienda": {
"descripcion": "Documentos legales",
"peso": 0.5,
"dimensiones": "30x20x5"
},
"nombreDestinatario": "Carlos Ruiz",
"dniDestinatario": "11223344",
"sucursalOrigenId": 1,
"sucursalDestinoId": 2,
"precio": 15.00,
"contrasenaEntrega": "9876"
}'
Heavy Package (> 20kg)
curl -X POST http://localhost:8080/api/v1/envios \
-H "Content-Type: application/json" \
-d '{
"remitenteId": 1,
"encomienda": {
"descripcion": "Electrodomésticos - Refrigeradora",
"peso": 45.0,
"dimensiones": "180x70x70"
},
"nombreDestinatario": "Luis Torres",
"dniDestinatario": "55667788",
"sucursalOrigenId": 1,
"sucursalDestinoId": 3,
"precio": 150.00
}'
Future enhancement: Packages > 20kg could be automatically classified as enc:EnvioPesado in the semantic graph using OWL reasoning.
Fragile Items
curl -X POST http://localhost:8080/api/v1/envios \
-H "Content-Type: application/json" \
-d '{
"remitenteId": 2,
"encomienda": {
"descripcion": "Vajilla de cristal - FRÁGIL",
"peso": 8.5,
"dimensiones": "50x40x30"
},
"nombreDestinatario": "Patricia Vega",
"dniDestinatario": "99887766",
"sucursalOrigenId": 2,
"sucursalDestinoId": 1,
"precio": 65.00
}'
Bulk Creation
Create multiple shipments in a script:
#!/bin/bash
for i in { 1..10} ; do
curl -X POST http://localhost:8080/api/v1/envios \
-H "Content-Type: application/json" \
-d "{
\" remitenteId \" : 1,
\" encomienda \" : {
\" descripcion \" : \" Paquete de prueba $i \" ,
\" peso \" : $(( RANDOM % 20 + 1 )),
\" dimensiones \" : \" 30x20x10 \"
},
\" nombreDestinatario \" : \" Cliente $i \" ,
\" dniDestinatario \" : \" 1234567 $i \" ,
\" sucursalOrigenId \" : 1,
\" sucursalDestinoId \" : 2,
\" precio \" : 25.00
}"
sleep 1
done
What Happens Behind the Scenes
Request Received
EnvioController.registrarNuevoEnvio() (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/web/EnvioController.java:41) receives the request:@ PostMapping
@ ResponseStatus ( HttpStatus . CREATED )
public EnvioDTO registrarNuevoEnvio (@ RequestBody CrearEnvioRequestDTO requestDTO)
throws ClienteNotFoundException {
return envioService . registrarEnvio (requestDTO);
}
Validation & Entity Creation
EnvioServiceImpl.registrarEnvio() (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/servicios/Impl/EnvioServiceImpl.java:74):
Validates client exists
Validates both branches exist
Validates origin ≠ destination
Validates package weight > 0
Validates recipient DNI format
Generates delivery password if not provided
Creates Envio entity with initial state PENDIENTE
Auto-generated Fields
// Tracking code: ENV-{timestamp}
@ PrePersist
public void generarCodigoSeguimiento () {
if ( this . codigoSeguimiento == null ) {
this . codigoSeguimiento = "ENV-" + System . currentTimeMillis ();
}
}
From Envio.java (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/entidades/Envio.java:67)
Save to MySQL
Envio envioGuardado = envioRepository . save (nuevoEnvio);
Creates records in:
envios table
encomiendas table (via cascade)
Semantic Synchronization
integracionSemanticaService . notificarNuevoEnvio (envioGuardado);
Sends HTTP POST to semantic service at http://localhost:8081/api/v1/grafo/sincronizar-envio Creates RDF triples: <http://www.encomiendas.com/cliente/12345678> a enc: Cliente ;
enc: tieneNombre "Juan Pérez García" ;
enc: realizaEnvio <http://www.encomiendas.com/envio/ENV-1710334200000> .
<http://www.encomiendas.com/envio/ENV-1710334200000> a enc: Envio ;
enc: codigoSeguimiento "ENV-1710334200000" ;
enc: tieneEstado "PENDIENTE" ;
enc: contienePaquete "Laptop Dell XPS 15" ;
enc: tienePesoKg "2.5" ^^ xsd: decimal .
Return Response
return mapper . deEnvio (envioGuardado);
Converts entity to DTO with all relationships populated.
Error Handling
Common Errors
{
"timestamp" : "2026-03-09T10:30:00" ,
"status" : 404 ,
"error" : "Not Found" ,
"message" : "Remitente no encontrado" ,
"path" : "/api/v1/envios"
}
Solution: Verify client ID exists:curl http://localhost:8080/api/v1/clientes/1
400 - Same Origin and Destination
{
"status" : 400 ,
"message" : "La sucursal de origen y destino no pueden ser iguales"
}
Solution: Use different branch IDs:"sucursalOrigenId" : 1 ,
"sucursalDestinoId" : 2 // Must be different
{
"status" : 400 ,
"message" : "Datos de encomienda inválidos o peso debe ser mayor a 0"
}
Solution: "peso" : 2.5 // ✅ Correct
"peso" : 0 // ❌ Wrong
"peso" : -5 // ❌ Wrong
Query Created Shipments
REST API
# Get all shipments
curl http://localhost:8080/api/v1/envios
# Get specific shipment
curl http://localhost:8080/api/v1/envios/1
# Track by code (public endpoint)
curl http://localhost:8080/api/v1/envios/seguimiento/ENV-1710334200000
# Filter by state
curl http://localhost:8080/api/v1/envios/estado/PENDIENTE?page= 0 & size = 10
# Client's shipment history
curl http://localhost:8080/api/v1/envios/[email protected]
Semantic Search
# Natural language queries
curl -X GET "http://localhost:8081/api/v1/grafo/buscar?texto=envios+pendientes"
curl -X GET "http://localhost:8081/api/v1/grafo/buscar?texto=envios+de+Juan"
curl -X GET "http://localhost:8081/api/v1/grafo/buscar?texto=paquetes+a+Lima"
curl -X GET "http://localhost:8081/api/v1/grafo/buscar?texto=entre+2+y+5+kg"
Integration Tips
const createShipment = async ( formData ) => {
// Client-side validation
if ( ! / ^ \d {8} $ / . test ( formData . dniDestinatario )) {
throw new Error ( 'DNI debe tener 8 dígitos' );
}
if ( formData . sucursalOrigenId === formData . sucursalDestinoId ) {
throw new Error ( 'Origen y destino deben ser diferentes' );
}
if ( formData . encomienda . peso <= 0 ) {
throw new Error ( 'Peso debe ser mayor a 0' );
}
// Send request
const response = await fetch ( 'http://localhost:8080/api/v1/envios' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ( formData )
});
if ( ! response . ok ) {
const error = await response . json ();
throw new Error ( error . message );
}
return await response . json ();
};
Python Integration
import requests
def create_shipment ( sender_id , package , recipient , origin_id , dest_id , price ):
payload = {
"remitenteId" : sender_id,
"encomienda" : package,
"nombreDestinatario" : recipient[ "name" ],
"dniDestinatario" : recipient[ "dni" ],
"sucursalOrigenId" : origin_id,
"sucursalDestinoId" : dest_id,
"precio" : price
}
response = requests.post(
"http://localhost:8080/api/v1/envios" ,
json = payload
)
response.raise_for_status()
return response.json()
# Usage
shipment = create_shipment(
sender_id = 1 ,
package = {
"descripcion" : "Laptop" ,
"peso" : 2.5 ,
"dimensiones" : "40x30x10"
},
recipient = {
"name" : "María García" ,
"dni" : "87654321"
},
origin_id = 1 ,
dest_id = 2 ,
price = 35.50
)
print ( f "Tracking code: { shipment[ 'codigoSeguimiento' ] } " )
print ( f "Delivery password: { shipment[ 'contrasenaEntrega' ] } " )
Next Steps
State Transitions Learn how to update shipment states
Semantic Search Query shipments naturally
SPARQL Queries Write custom queries
Setup Guide Configure the system