Saltar a contenido

← Volver al índice | App Móvil | Web SPA

Especificación — API REST y WebSocket

Tipo: Especificación Funcional — Contrato de API
Audiencia: Equipo de desarrollo backend y frontend, integradores
Fecha: 20 de marzo de 2026
Relacionado con: Arquitectura del Sistema | Modelo de Datos


1. Información General

Parámetro Valor
Base URL http://localhost:8080/api/v1
Formato JSON (application/json)
Autenticación Bearer Token (JWT via Entra ID)
Versionado Path-based (/api/v1/, /api/v2/)
Documentación OpenAPI 3.0 en /api/docs
Rate limiting 100 req/min por usuario (configurable)

2. Autenticación y Seguridad

2.1 Flujo de Autenticación

sequenceDiagram
    participant APP as App / Web
    participant ENTRA as Microsoft Entra ID
    participant API6 as Backend API

    APP->>ENTRA: Authorization Code + PKCE
    ENTRA-->>APP: ID Token + Access Token
    APP->>API6: GET /api/v1/... + Bearer Token
    API6->>ENTRA: Valida token (JWKS)
    ENTRA-->>API6: Token válido + claims
    API6-->>APP: Respuesta 200

2.2 Headers Requeridos

Authorization: Bearer <jwt-token>
Content-Type: application/json
Accept: application/json
X-Request-Id: <uuid>

2.3 Roles y Permisos

Rol Descripción Permisos
INVESTIGADOR Investigador de campo CRUD sobre sus muestras, consultas IA
RESPONSABLE Responsable de departamento Todo del investigador + ver muestras del dpto.
ADMIN Administrador del sistema Acceso completo
API_CLIENT Cliente externo (F3) Solo lectura via API pública

3. Endpoints REST

3.1 Muestras (/samples)

POST /api/v1/samples — Crear muestra con identificación IA

Request (multipart/form-data):

Campo Tipo Obligatorio Descripción
image File Imagen de la muestra (JPEG/PNG)
codigo_interno String Código IEO (ej: IEOMA-CFM-0042)
latitud Float Coordenada GPS
longitud Float Coordenada GPS
fecha_captura Date No Por defecto: hoy
talla_cm Float No Talla en centímetros
peso_g Float No Peso en gramos
sexo String No M, F, INDETERMINADO
metodo_captura String No ARRASTRE, PALANGRE, NASAS, OTRO
campana_id UUID No Campaña de origen
notas String No Notas del investigador

Response (201 Created):

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "codigo_interno": "IEOMA-CFM-0042",
  "imagen_url": "https://minio:9000/ieo-images/550e8400.jpg",
  "identificación": {
    "especie_sugerida": {
      "nombre_cientifico": "Mullus surmuletus",
      "nombre_comun": "Salmonete de roca",
      "confianza": 0.92
    },
    "edad_estimada": 3,
    "sexo_estimado": "F",
    "top_k_similares": [
      { "muestra_id": "uuid-1", "score": 0.95, "thumbnail": "url" },
      { "muestra_id": "uuid-2", "score": 0.91, "thumbnail": "url" }
    ],
    "modelo_utilizado": "qwen25-vl-7b",
    "confirmada": false
  },
  "created_at": "2026-03-20T10:30:00Z"
}

GET /api/v1/samples — Listar muestras

Query Parameters:

Parámetro Tipo Descripción
page Int Página (default: 0)
size Int Tamaño de página (default: 20, max: 100)
especie_id UUID Filtrar por especie
campana_id UUID Filtrar por campaña
coleccion_id UUID Filtrar por colección
fecha_desde Date Fecha mínima
fecha_hasta Date Fecha máxima
confirmada Boolean Solo identificaciones confirmadas/pendientes
sort String Campo de ordenación (default: created_at,desc)

Response (200 OK): Paginado estándar Spring.

GET /api/v1/samples/{id} — Detalle de muestra

Response (200 OK): Objeto muestra completo con parámetros biológicos, imágenes, identificación IA e historial.

PATCH /api/v1/samples/{id}/confirm — Confirmar identificación IA

Request:

{
  "confirmada": true,
  "especie_corregida_id": null,
  "notas_revision": "Identificación correcta"
}

POST /api/v1/search — Búsqueda en lenguaje natural

Request:

{
  "query": "Otolitos de sardina del Mediterráneo capturados entre 2015 y 2020",
  "limit": 20,
  "include_images": true
}

Response (200 OK):

{
  "results": [
    {
      "muestra_id": "uuid",
      "relevance_score": 0.89,
      "especie": "Sardina pilchardus",
      "fecha_captura": "2017-06-15",
      "thumbnail": "url",
      "snippet": "Otolito de sardina, campaña ECOMED 2017..."
    }
  ],
  "total": 47,
  "query_interpreted": "Búsqueda: otolitos + sardina + Mediterráneo + 2015-2020"
}

POST /api/v1/search/by-image — Búsqueda por similitud visual

Request (multipart/form-data):

Campo Tipo Descripción
image File Imagen de referencia
limit Int Número de resultados (default: 10)
min_score Float Score mínimo de similitud (default: 0.5)

3.3 Especies (/species)

GET /api/v1/species — Listar especies

Query Parameters: search (texto libre), familia, clase, page, size.

GET /api/v1/species/{id} — Detalle de especie con taxonomía completa.


3.4 Campañas (/campaigns)

GET /api/v1/campaigns — Listar campañas

Query Parameters: year, type, zona_id, page, size.

GET /api/v1/campaigns/{id} — Detalle de campaña con estadísticas.


3.5 Sistema (/system)

GET /api/v1/system/health — Estado de salud

{
  "status": "UP",
  "services": {
    "database": "UP",
    "chromadb": "UP",
    "ollama": "UP",
    "redis": "UP",
    "minio": "UP"
  },
  "version": "1.0.0",
  "uptime": "3d 12h 45m"
}

4. WebSocket — Vídeo en Tiempo Real

4.1 Conexión

ws://localhost:8080/ws/realtime
Header: Authorization: Bearer <jwt-token>

4.2 Protocolo de Mensajes

sequenceDiagram
    participant APP2 as App / Web
    participant WS as WebSocket Server
    participant PK as Dapr Sidecar

    APP2->>WS: CONNECT /ws/realtime
    WS->>PK: Crea RealTimeDetectionActor

    loop Cada N frames
        APP2->>WS: FRAME - base64 image
        WS->>PK: processFrame
        PK-->>WS: DETECTION result
        WS-->>APP2: DETECTION JSON
    end

    APP2->>WS: CAPTURE - frame actual
    WS->>PK: captureAndProcess
    PK-->>WS: SAMPLE_CREATED result
    WS-->>APP2: SAMPLE_CREATED JSON

    APP2->>WS: DISCONNECT
    WS->>PK: Destruye actor

4.3 Mensajes del Cliente

Tipo Payload Descripción
FRAME { "image": "base64...", "timestamp": 1234567890 } Frame de vídeo para clasificación
CAPTURE { "metadata": { ... } } Capturar frame actual como muestra
CONFIG { "fps_target": 15, "min_confidence": 0.6 } Configurar sesión

4.4 Mensajes del Servidor

Tipo Payload Descripción
DETECTION { "species": "...", "confidence": 0.85, "bbox": [x,y,w,h] } Resultado de detección por frame
SAMPLE_CREATED { "sample_id": "uuid", "identificacion": { ... } } Muestra registrada exitosamente
ERROR { "code": "...", "message": "..." } Error en procesamiento

5. Códigos de Error

Código HTTP Código Interno Descripción
400 INVALID_REQUEST Parámetros inválidos o faltantes
401 UNAUTHORIZED Token ausente o expirado
403 FORBIDDEN Sin permisos para este recurso
404 NOT_FOUND Recurso no encontrado
409 DUPLICATE Código interno duplicado
413 IMAGE_TOO_LARGE Imagen excede 20MB
422 UNSUPPORTED_FORMAT Formato de imagen no soportado
429 RATE_LIMITED Excedido el límite de peticiones
500 INTERNAL_ERROR Error interno del servidor
503 AI_UNAVAILABLE Motor IA no disponible (Ollama down)

Formato de error estándar:

{
  "error": {
    "code": "INVALID_REQUEST",
    "message": "El campo 'codigo_interno' es obligatorio",
    "timestamp": "2026-03-20T10:30:00Z",
    "request_id": "uuid"
  }
}

Documentos Relacionados

Nivel Documento Descripción
Arquitectura Arquitectura del Sistema Backend Quarkus + Dapr
Arquitectura Modelo de Datos Schema de las entidades
Especificación App Móvil Cliente principal de esta API
Especificación Web SPA Cliente web de esta API
Infraestructura Docker Compose Servicio api