Files
notas-api/docs/API.md
T
2026-05-18 18:43:33 +02:00

6.2 KiB

API de api_notas

Esta guía resume las rutas disponibles, qué datos esperan y los tipos más importantes del proyecto.

Base URL

Todas las rutas cuelgan de:

/api

Ejemplo local:

http://localhost:3000/api

Tipos principales

  • UUID: identificador único en formato UUID.
  • string: texto plano o cifrado.
  • boolean: true o false.
  • number: valor numérico.
  • ISO date string: fecha en formato ISO 8601, por ejemplo 2026-05-18T10:00:00.000Z.

Rutas disponibles

GET /api/status

Comprueba que la API está viva.

Respuesta ejemplo:

{
  "message": "¡API funcionando correctamente!"
}

POST /api/auth/register

Crea un usuario nuevo.

Body:

{
  "username": "maria",
  "password": "123456",
  "encrypted_master_key": "texto_cifrado_opcional"
}

Campos:

  • username: string, obligatorio.
  • password: string, obligatorio.
  • encrypted_master_key: string | null, opcional.

Respuesta ejemplo:

{
  "message": "Usuario registrado con éxito",
  "accessToken": "jwt_access_token",
  "refreshToken": "token_largo_aleatorio"
}

POST /api/auth/login

Inicia sesión y devuelve tokens.

Body:

{
  "username": "maria",
  "password": "123456",
  "deviceName": "Flutter Pixel"
}

Campos:

  • username: string, obligatorio.
  • password: string, obligatorio.
  • deviceName: string, opcional.

Respuesta ejemplo:

{
  "message": "Login exitoso",
  "accessToken": "jwt_access_token",
  "refreshToken": "token_largo_aleatorio",
  "encrypted_master_key": "texto_cifrado"
}

POST /api/auth/refresh

Genera un nuevo accessToken usando un refreshToken válido.

Body:

{
  "refreshToken": "token_largo_aleatorio"
}

Campos:

  • refreshToken: string, obligatorio.

Respuesta ejemplo:

{
  "accessToken": "nuevo_jwt_access_token"
}

POST /api/auth/logout

Revoca la sesión del dispositivo actual.

Body:

{
  "refreshToken": "token_largo_aleatorio"
}

Campos:

  • refreshToken: string, obligatorio.

Respuesta ejemplo:

{
  "message": "Sesión cerrada en el dispositivo actual"
}

POST /api/sync

Endpoint único de sincronización offline-first.

Autenticación requerida:

  • Enviar Authorization: Bearer <accessToken> (obligatorio). El servidor ya no acepta userId en el body.

Request

Body ejemplo:

{
  "lastSyncAt": "2026-05-18T10:00:00.000Z",
  "changes": {
    "categories": [
      {
        "id": "uuid-categoria-1",
        "encrypted_name": "texto_cifrado...",
        "serverVersion": 1,
        "isDeleted": false,
        "updatedAt": "2026-05-18T10:05:00.000Z"
      }
    ],
    "notes": [
      {
        "id": "uuid-nota-1",
        "categoryId": "uuid-categoria-1",
        "encrypted_title": "titulo_cifrado...",
        "encrypted_body": "cuerpo_cifrado...",
        "serverVersion": 1,
        "position": 2000,
        "isDeleted": true,
        "updatedAt": "2026-05-18T10:10:00.000Z"
      }
    ]
  }
}

Campos:

  • lastSyncAt: string ISO, opcional. Si no se envía, el servidor usa 1970-01-01T00:00:00.000Z.
  • changes: object, obligatorio.
  • changes.categories: array, opcional.
  • changes.notes: array, opcional.

Estructura de categoría

{
  "id": "uuid",
  "encrypted_name": "texto_cifrado",
  "serverVersion": 1,
  "isDeleted": false,
  "updatedAt": "2026-05-18T10:05:00.000Z"
}

Campos:

  • id: UUID, obligatorio.
  • encrypted_name: string, obligatorio.
  • serverVersion: number entero >= 0, obligatorio. Es la versión base local con la que se hizo el cambio.
  • isDeleted: boolean, opcional, por defecto false.
  • updatedAt: ISO date string, opcional (solo informativo para UI).

Estructura de nota

{
  "id": "uuid",
  "categoryId": "uuid-categoria",
  "encrypted_title": "titulo_cifrado",
  "encrypted_body": "cuerpo_cifrado",
  "serverVersion": 1,
  "position": 2000,
  "isDeleted": false,
  "updatedAt": "2026-05-18T10:10:00.000Z"
}

Campos:

  • id: UUID, obligatorio.
  • categoryId: UUID | null, opcional.
  • encrypted_title: string, obligatorio.
  • encrypted_body: string, obligatorio.
  • serverVersion: number entero >= 0, obligatorio. Es la versión base local con la que se hizo el cambio.
  • position: number, opcional.
  • isDeleted: boolean, opcional, por defecto false.
  • updatedAt: ISO date string, opcional (solo informativo para UI).

Response

Respuesta ejemplo (nuevo contrato):

{
  "serverTimestamp": "2026-05-18T10:20:00.000Z",
  "synced": true,
  "changes": {
    "categories": [],
    "notes": [
      {
        "id": "uuid-nota-2",
        "categoryId": null,
        "encrypted_title": "titulo_cifrado...",
        "encrypted_body": "cuerpo_cifrado...",
        "serverVersion": 3,
        "position": 0,
        "isDeleted": false,
        "updatedAt": "2026-05-18T10:15:00.000Z"
      }
    ]
  }
}

Campos de salida:

  • serverTimestamp: string ISO con la hora del servidor.
  • synced: boolean.
  • changes.categories: array con todas las categorías cambiadas en el servidor desde lastSyncAt, incluidas las creadas por otros dispositivos.
  • changes.notes: array con todas las notas cambiadas en el servidor desde lastSyncAt, incluidas las creadas por otros dispositivos y las copias creadas automáticamente en caso de conflicto.

Reglas de sincronización

  • El cliente debe guardar su último lastSyncAt.
  • Un borrado no elimina el registro localmente: se marca con isDeleted: true.
  • El servidor decide conflictos comparando serverVersion (no updatedAt).
  • En categorías, solo se actualiza si incoming.serverVersion === serverVersion. Si no coincide, gana el servidor y no se crea duplicado.
  • Si la versión entrante de una nota coincide con la versión actual del servidor, acepta el cambio y sube versión (v -> v+1).
  • Si la versión del servidor es mayor que la entrante en una nota, el servidor crea una nueva nota duplicada con el contenido entrante para no perder datos.

Notas importantes

  • Las notas y categorías están pensadas para contenido cifrado del lado del cliente.
  • El servidor guarda encrypted_title, encrypted_body y encrypted_name, pero no interpreta el contenido.