Skip to main content
Every shipment in the system follows a defined lifecycle with five possible states. Understanding these states and their transitions is crucial for managing shipments correctly.

Shipment States

The system defines five states in the EstadoEnvio enum (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/enums/EstadoEnvio.java:3):
public enum EstadoEnvio {
    PENDIENTE,
    EN_TRANSITO,
    DISPONIBLE,
    ENTREGADO,
    CANCELADO
}

State Definitions

Initial state when a shipment is created.Characteristics:
  • Awaiting vehicle assignment
  • Can be canceled
  • No tracking code assigned yet (auto-generated on creation)
  • 4-digit delivery password created
Example:
{
  "id": 1,
  "codigoSeguimiento": "ENV-1710334200000",
  "estado": "PENDIENTE",
  "fechaEnvio": "2026-03-09T10:30:00",
  "placa": null
}
Available Actions:
  • Assign vehicle plate (transitions to EN_TRANSITO)
  • Cancel shipment (transitions to CANCELADO)
Shipment is assigned to a vehicle and in transit to destination.Characteristics:
  • Vehicle plate assigned
  • Cannot be canceled
  • Package is physically moving between branches
Example:
{
  "id": 1,
  "codigoSeguimiento": "ENV-1710334200000",
  "estado": "EN_TRANSITO",
  "placa": "ABC-123",
  "sucursalOrigen": "Cajamarca Centro",
  "sucursalDestino": "Lima Miraflores"
}
Available Actions:
  • Mark as available for pickup (transitions to DISPONIBLE)
Package arrived at destination branch and ready for recipient pickup.Characteristics:
  • Physically at destination branch
  • Recipient can pick up with tracking code + password
  • Waiting for final delivery
Example:
{
  "id": 1,
  "codigoSeguimiento": "ENV-1710334200000",
  "estado": "DISPONIBLE",
  "sucursalDestino": "Lima Miraflores",
  "nombreDestinatario": "María García",
  "dniDestinatario": "87654321"
}
Available Actions:
  • Deliver package (transitions to ENTREGADO)
Package successfully delivered to recipient.Characteristics:
  • Delivery password validated
  • fechaEntrega timestamp recorded
  • Final successful state
  • No further transitions possible
Example:
{
  "id": 1,
  "codigoSeguimiento": "ENV-1710334200000",
  "estado": "ENTREGADO",
  "fechaEnvio": "2026-03-09T10:30:00",
  "fechaEntrega": "2026-03-10T14:20:00"
}
Available Actions:
  • None (terminal state)
Shipment canceled before entering transit.Characteristics:
  • Only possible from PENDIENTE state
  • Cannot be reversed
  • Typically due to sender request or payment issues
Example:
{
  "id": 1,
  "codigoSeguimiento": "ENV-1710334200000",
  "estado": "CANCELADO",
  "fechaEnvio": "2026-03-09T10:30:00"
}
Available Actions:
  • None (terminal state)

State Transition Diagram

Allowed Transitions

The system enforces strict state transition rules in EnvioServiceImpl (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/servicios/Impl/EnvioServiceImpl.java:292):
private void validarTransicionEstado(EstadoEnvio actual, EstadoEnvio nuevo) {
    boolean permitida = switch (actual) {
        case PENDIENTE   -> (nuevo == EstadoEnvio.EN_TRANSITO || nuevo == EstadoEnvio.CANCELADO);
        case EN_TRANSITO -> (nuevo == EstadoEnvio.DISPONIBLE);
        case DISPONIBLE  -> (nuevo == EstadoEnvio.ENTREGADO);
        default          -> false;
    };
    if (!permitida)
        throw new IllegalStateException("Flujo inválido: " + actual + " → " + nuevo);
}

Transition Table

From StateTo StateAllowedTriggered ByAPI Endpoint
PENDIENTEEN_TRANSITO✅ YesAssign vehicle platePOST /api/v1/envios/asignar-placa
PENDIENTECANCELADO✅ YesCancel requestPUT /api/v1/envios/{id}/cancelar
EN_TRANSITODISPONIBLE✅ YesArrives at branchPUT /api/v1/envios/{id}/disponible
DISPONIBLEENTREGADO✅ YesRecipient pickupPOST /api/v1/envios/retirar
EN_TRANSITOCANCELADO❌ No--
DISPONIBLECANCELADO❌ No--
ENTREGADO(any)❌ NoTerminal state-
CANCELADO(any)❌ NoTerminal state-
Attempting an invalid transition will throw an IllegalStateException and return HTTP 400.

State Transition APIs

1. Create Shipment (Initial State)

Endpoint: POST /api/v1/envios Initial State: PENDIENTE
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
  }'
Response:
{
  "id": 1,
  "codigoSeguimiento": "ENV-1710334200000",
  "estado": "PENDIENTE",
  "contrasenaEntrega": "1234",
  "fechaEnvio": "2026-03-09T10:30:00"
}
Code Reference: (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/servicios/Impl/EnvioServiceImpl.java:102)
Envio nuevoEnvio = new Envio();
nuevoEnvio.setEstado(EstadoEnvio.PENDIENTE);

2. Assign Vehicle (PENDIENTE → EN_TRANSITO)

Endpoint: POST /api/v1/envios/asignar-placa
curl -X POST http://localhost:8080/api/v1/envios/asignar-placa \
  -H "Content-Type: application/json" \
  -d '{
    "envioId": 1,
    "placa": "ABC-123"
  }'
Response:
{
  "mensaje": "Placa asignada exitosamente",
  "envioId": "1",
  "placa": "ABC-123",
  "nuevoEstado": "EN_TRANSITO"
}
Validation Rules:
  • Shipment must be in PENDIENTE state
  • Plate format: ABC-123 (3-4 alphanumeric characters, hyphen, 3-4 characters)
  • Plate cannot already be assigned
Code Reference: (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/servicios/Impl/EnvioServiceImpl.java:162)
if (envio.getEstado() != EstadoEnvio.PENDIENTE)
    throw new IllegalStateException("Solo se asigna placa a envíos PENDIENTES.");
if (!dto.getPlaca().matches("^[A-Z0-9]{3}-[A-Z0-9]{3,4}$"))
    throw new IllegalArgumentException("Formato de placa inválido (ABC-123)");

envio.setPlaca(dto.getPlaca());
envio.setEstado(EstadoEnvio.EN_TRANSITO);

3. Mark Available (EN_TRANSITO → DISPONIBLE)

Endpoint: PUT /api/v1/envios/{id}/disponible
curl -X PUT http://localhost:8080/api/v1/envios/1/disponible
Response:
{
  "mensaje": "Envío marcado como DISPONIBLE para retiro",
  "envioId": "1",
  "nuevoEstado": "DISPONIBLE"
}
Validation:
  • Must be in EN_TRANSITO state
  • Typically called when package physically arrives at destination branch
Code Reference: (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/servicios/Impl/EnvioServiceImpl.java:180)
validarTransicionEstado(envio.getEstado(), EstadoEnvio.DISPONIBLE);
envio.setEstado(EstadoEnvio.DISPONIBLE);

4. Deliver Package (DISPONIBLE → ENTREGADO)

Endpoint: POST /api/v1/envios/retirar
curl -X POST http://localhost:8080/api/v1/envios/retirar \
  -H "Content-Type: application/json" \
  -d '{
    "codigoSeguimiento": "ENV-1710334200000",
    "contrasenaEntrega": "1234"
  }'
Response:
{
  "mensaje": "Paquete entregado exitosamente",
  "codigoSeguimiento": "ENV-1710334200000"
}
Validation Rules:
  • Must be in DISPONIBLE state
  • Delivery password must match exactly (4 digits)
  • Sets fechaEntrega to current timestamp
Code Reference: (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/servicios/Impl/EnvioServiceImpl.java:191)
if (envio.getEstado() != EstadoEnvio.DISPONIBLE) {
    String msg = switch (envio.getEstado()) {
        case ENTREGADO -> "El paquete ya fue entregado";
        case CANCELADO -> "El envío está cancelado";
        default        -> "El envío aún no está disponible en sucursal";
    };
    throw new IllegalStateException(msg);
}

if (!envio.getContrasenaEntrega().equals(retiroDTO.getContrasenaEntrega()))
    throw new IllegalArgumentException("Contraseña de entrega incorrecta");

envio.setEstado(EstadoEnvio.ENTREGADO);
envio.setFechaEntrega(LocalDateTime.now());

5. Cancel Shipment (PENDIENTE → CANCELADO)

Endpoint: PUT /api/v1/envios/{id}/cancelar
curl -X PUT http://localhost:8080/api/v1/envios/1/cancelar
Response:
{
  "mensaje": "Envío cancelado exitosamente",
  "envioId": "1",
  "nuevoEstado": "CANCELADO"
}
Validation:
  • Only allowed from PENDIENTE state
  • Cannot cancel once vehicle is assigned
Code Reference: (svc-envio-encomienda/src/main/java/org/jchilon3mas/springcloud/svc/envio/encomienda/servicios/Impl/EnvioServiceImpl.java:148)
if (envio.getEstado() != EstadoEnvio.PENDIENTE)
    throw new IllegalStateException(
        "Solo se pueden cancelar envíos PENDIENTES. Estado: " + envio.getEstado());

envio.setEstado(EstadoEnvio.CANCELADO);

Semantic Synchronization

Every state change is automatically synchronized to the semantic graph:
// After every state change
integracionSemanticaService.notificarNuevoEnvio(envio);
This updates the RDF triple:
<http://www.encomiendas.com/envio/ENV-1710334200000>
    enc:tieneEstado "EN_TRANSITO" .
Semantic queries reflect the updated state immediately:
curl -X GET "http://localhost:8081/api/v1/grafo/buscar?texto=envios+en+transito"

Common State Scenarios

Happy Path

Early Cancellation

Invalid Transition Attempts

curl -X PUT http://localhost:8080/api/v1/envios/1/cancelar
# Response: 400 Bad Request
# "Solo se pueden cancelar envíos PENDIENTES. Estado: EN_TRANSITO"

Tracking State History

Currently, the system only stores the current state. To track history, you could:

Option 1: Query Semantic Graph by Date

Search for all shipments that changed state on a specific date:
curl -X GET "http://localhost:8081/api/v1/grafo/buscar?texto=envios+hoy"

Option 2: Add State History Entity

@Entity
public class EstadoHistorial {
    @Id
    @GeneratedValue
    private Long id;
    
    @ManyToOne
    private Envio envio;
    
    private EstadoEnvio estadoAnterior;
    private EstadoEnvio estadoNuevo;
    private LocalDateTime fechaCambio;
    private String comentario;
}

Querying by State

REST API

# Get all shipments by state
curl -X GET "http://localhost:8080/api/v1/envios/estado/PENDIENTE?page=0&size=10"
# Natural language
curl -X GET "http://localhost:8081/api/v1/grafo/buscar?texto=envios+pendientes"

# All in transit
curl -X GET "http://localhost:8081/api/v1/grafo/buscar?texto=en+transito"

# Delivered today
curl -X GET "http://localhost:8081/api/v1/grafo/buscar?texto=entregados+hoy"

SPARQL Query

PREFIX enc: <http://www.encomiendas.com/ontologia#>

SELECT ?codigo ?estado ?fechaEnvio
WHERE {
    ?envioURI enc:codigoSeguimiento ?codigo .
    ?envioURI enc:tieneEstado ?estado .
    ?envioURI enc:fechaEnvio ?fechaEnvio .
    FILTER (?estado = "DISPONIBLE")
}

Best Practices

Always validate state

Check current state before attempting transitions

Handle transition errors

Catch IllegalStateException and inform users clearly

Log state changes

Include timestamps and user info for audit trails

Notify stakeholders

Send emails/SMS on key transitions (DISPONIBLE, ENTREGADO)

Next Steps

Creating Shipments

Learn to create shipments via API

Semantic Search

Query shipments by state naturally

Setup Guide

Configure the system

Ontology Reference

How states are represented in RDF

Build docs developers (and LLMs) love