Skip to main content
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:
  1. Client (Remitente) - The sender
  2. Encomienda (Package) - The physical item being shipped
  3. 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

1

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.
2

Client Validation Rules

FieldTypeRequiredValidation
nombreCompletoStringYes1-100 characters
dniStringYesExactly 8 digits, unique
telefonoStringYesExactly 9 digits
correoStringYesValid email format
3

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

1

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"
  }
]
2

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

FieldRuleError Message
remitenteIdMust exist in database”Remitente no encontrado”
FieldTypeRequiredValidation
descripcionStringYes1-500 characters
pesoDoubleYesMust be > 0
dimensionesStringYesFormat: 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");
FieldTypeRequiredValidation
nombreDestinatarioStringYes1-100 characters
dniDestinatarioStringYesExactly 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");
FieldRuleError Message
sucursalOrigenIdMust exist”Sucursal origen no encontrada”
sucursalDestinoIdMust exist”Sucursal destino no encontrada”
Origin ≠ DestinationMust 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");
FieldTypeRequiredValidation
precioDoubleYesMust be > 0
contrasenaEntregaStringNo4 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

1

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);
}
2

Validation & Entity Creation

EnvioServiceImpl.registrarEnvio() (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/servicios/Impl/EnvioServiceImpl.java:74):
  1. Validates client exists
  2. Validates both branches exist
  3. Validates origin ≠ destination
  4. Validates package weight > 0
  5. Validates recipient DNI format
  6. Generates delivery password if not provided
  7. Creates Envio entity with initial state PENDIENTE
3

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)
4

Save to MySQL

Envio envioGuardado = envioRepository.save(nuevoEnvio);
Creates records in:
  • envios table
  • encomiendas table (via cascade)
5

Semantic Synchronization

integracionSemanticaService.notificarNuevoEnvio(envioGuardado);
Sends HTTP POST to semantic service at http://localhost:8081/api/v1/grafo/sincronizar-envioCreates 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 .
6

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
{
  "status": 400,
  "error": "Bad Request",
  "message": "El DNI debe tener 8 dígitos"
}
Solution: Ensure dniDestinatario is exactly 8 digits:
"dniDestinatario": "87654321"  // ✅ Correct
"dniDestinatario": "8765432"   // ❌ Wrong (7 digits)
{
  "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]
# 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

Frontend Form Validation

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

Build docs developers (and LLMs) love