Skip to main content

Overview

Destinations (POIs) are the core entities of Tesis Rutas. Each destination represents a heritage site or tourist point of interest with rich metadata, geographic coordinates, and multimedia assets.
Each destination can store up to 10 multimedia files (images or videos) via Cloudinary integration.

Data Model

The Destino entity is defined in src/domain/entities/destino.py with the following structure:

Required Fields

class Destino:
    def __init__(
        self,
        nombre: str,
        ubicacion: str,
        importancia: str,
        coordenadas: Coordenadas,
        anio_construccion: list[int],
        arquitecto: Optional[str] = None,
        area_construccion: Optional[float] = None,
        funcion: Optional[str] = None,
        multimedia: Optional[list[dict]] = None,
        id: Optional[str] = None,
        fecha_creacion: Optional[datetime] = None,
        activo: bool = True
    )

Field Descriptions

FieldTypeRequiredDescription
nombrestrYesName of the heritage site
ubicacionstrYesLocation description
importanciastrYesHistorical/cultural importance
coordenadasCoordenadasYesGPS coordinates (validated)
anio_construccionlist[int]Yes1-2 values: [year] or [start_year, end_year]
arquitectostrNoName of the architect
area_construccionfloatNoConstruction area in m²
funcionstrNoOriginal function/purpose
multimedialist[dict]NoArray of multimedia objects (max 10)
activoboolYesActive status (default: true)
  • Coordinates: Automatically validated by the Coordenadas value object
    • Latitude: -90° to 90°
    • Longitude: -180° to 180°
  • Construction Year: Must contain exactly 1 or 2 integers
  • Multimedia: Maximum 10 files per destination
  • Strings: nombre, ubicacion, importancia are automatically stripped of whitespace

API Endpoints

All destination endpoints are defined in src/infrastructure/api/routers/destinos_router.py:
1

Create Destination

POST /destinos/Requires: Admin role
Use Case
use_case = AgregarDestinoUseCase(repo)
destino_id = use_case.ejecutar(data)
Request Body:
{
  "nombre": "Catedral de Lima",
  "ubicacion": "Plaza Mayor, Lima",
  "importancia": "Patrimonio Cultural de la Nación",
  "coordenadas": {
    "latitud": -12.046374,
    "longitud": -77.028236
  },
  "anio_construccion": [1535, 1649],
  "arquitecto": "Francisco Becerra",
  "area_construccion": 2500.0,
  "funcion": "Templo religioso"
}
2

List All Destinations

GET /destinos/Public endpoint - no authentication required
Response
{
  "total": 15,
  "data": [
    {
      "_id": "507f1f77bcf86cd799439011",
      "nombre": "Catedral de Lima",
      "ubicacion": "Plaza Mayor",
      "coordenadas": {"latitud": -12.046374, "longitud": -77.028236},
      "multimedia": [...],
      "activo": true
    }
  ]
}
3

Update Destination

PUT /destinos/{id}Requires: Admin roleUpdates any editable field while preserving existing data.
4

Toggle Active Status

PUT /destinos/estado/{id}Requires: Editor roleSoft delete/restore functionality - toggles the activo boolean.
5

Permanent Delete

DELETE /destinos/{destino_id}/forceRequires: Admin role
Physical deletion from database. Destination must have zero multimedia files before deletion. Automatically removes the Cloudinary folder.
Validation Logic
multimedia_actual = destino.get("multimedia", [])
if multimedia_actual:
    raise HTTPException(
        status_code=400,
        detail="Este destino tiene archivos multimedia. Elimínelos primero."
    )

Creating Destinations

The AgregarDestinoUseCase (src/application/use_cases/agregar_destino.py) handles destination creation:
Use Case Implementation
class AgregarDestinoUseCase:
    def ejecutar(self, data: dict) -> str:
        coordenadas = Coordenadas(
            latitud=data["coordenadas"]["latitud"],
            longitud=data["coordenadas"]["longitud"]
        )

        destino = Destino(
            nombre=data["nombre"],
            ubicacion=data["ubicacion"],
            importancia=data["importancia"],
            coordenadas=coordenadas,
            anio_construccion=data["anio_construccion"],
            arquitecto=data.get("arquitecto"),
            area_construccion=data.get("area_construccion"),
            funcion=data.get("funcion")
        )

        return self.repository.crear_destino(destino)

Database Storage

Destinations are stored in MongoDB with the following structure:
MongoDB Document
def to_dict(self) -> dict:
    return {
        "_id": self.id,
        "nombre": self.nombre,
        "ubicacion": self.ubicacion,
        "importancia": self.importancia,
        "anio_construccion": self.anio_construccion,
        "arquitecto": self.arquitecto,
        "area_construccion": self.area_construccion,
        "funcion": self.funcion,
        "coordenadas": self.coordenadas.to_dict(),
        "multimedia": self.multimedia,
        "fecha_creacion": self.fecha_creacion,
        "activo": self.activo
    }

Best Practices

Coordinate Precision

Use at least 6 decimal places for accurate GPS positioning (±11 cm accuracy)

Construction Years

Use single value [1850] for completed structures, two values [1850, 1865] for construction periods

Soft Deletion

Always use activo flag for deletion to preserve historical data and route references

Multimedia Limits

Plan multimedia usage carefully - 10 file limit per destination enforced at API level

Build docs developers (and LLMs) love