API REST en NestJS para registrar envíos, calcular la tarifa según la distancia y consultar el histórico. Construida con arquitectura hexagonal (dominio desacoplado de la infraestructura) y empaquetada con Docker.
Proyecto originalmente realizado como prueba técnica y refactorizado para compartir buenas prácticas.
- Registrar envíos y calcular la tarifa automáticamente según la distancia.
- Consultar el histórico completo de envíos.
- Validación de entrada con
class-validator(víaValidationPipeglobal). - Arquitectura hexagonal: el dominio no depende de NestJS ni de la persistencia.
- Persistencia intercambiable detrás del puerto
ShipmentRepository.
src/
├── domain/
│ ├── entities/shipment.entity.ts # Entidad de dominio
│ └── repositories/shipment.repository.interface.ts # Puerto (interface)
├── usecase/shipment/
│ ├── create-shipment.usecase.ts # Crea y calcula la tarifa
│ └── list-shipments.usecase.ts # Lista los envíos
├── dao/shipment.dao.ts # Adaptador in-memory del puerto
├── view/
│ ├── controller/shipment.controller.ts # Endpoints REST
│ ├── form-request/create-shipment.request.ts # DTO de entrada (validado)
│ └── dtos/shipment.dto.ts # DTO de salida
└── main.ts # Bootstrap + ValidationPipe
La inyección del repositorio se hace por token ('ShipmentRepository' → ShipmentDao),
así los casos de uso dependen de la interfaz, no de la implementación. Para
usar PostgreSQL/Mongo basta con implementar ShipmentRepository y cambiar el
binding en app.module.ts.
| Método | Endpoint | Descripción |
|---|---|---|
POST |
/shipments |
Registrar un nuevo envío |
GET |
/shipments |
Listar todos los envíos |
{
"recipient": "Juan Pérez",
"sender": "Acme Inc.",
"content": "Libro",
"distance": 20
}Respuesta 201
{
"id": "b1f0c2a4-3d6e-4f8a-9c1b-2e5d7a8f0c3e",
"recipient": "Juan Pérez",
"sender": "Acme Inc.",
"content": "Libro",
"shipmentDate": "2024-04-18T16:40:31.160Z",
"distance": 20,
"fee": 15
}Un payload inválido (campo faltante o propiedad no permitida) responde 400.
La tarifa es lineal: una base fija más un costo por kilómetro.
fee = 5 + 0.5 × distancia
| Distancia (km) | Tarifa |
|---|---|
| 0 | 5 |
| 10 | 10 |
| 20 | 15 |
| 100 | 55 |
La lógica vive en
create-shipment.usecase.tsy está cubierta por tests.
docker build -t shipment-system .
docker run -d -p 3000:3000 --name shipment-system shipment-system
curl http://localhost:3000/shipments # []O con Docker Compose:
docker compose up -dnpm install
npm run start:dev # http://localhost:3000npm run test # unit (jest)
npm run test:cov # con cobertura
npm run test:e2e # end-to-end (supertest)Cubren el cálculo de la tarifa, los casos de uso, el DAO y el controller. La
suite corre automáticamente en GitHub Actions (.github/workflows/ci.yml)
junto con el lint y el build.
NestJS 10 · TypeScript 5 · Node 20 · class-validator · Jest ·
Supertest · Docker
MIT — código de propiedad exclusiva de Alejandro Vega.