Files
notas-api/docs/API.md
T
Marcos 87cde27436
Despliegue Automático / desplegar (push) Successful in 1m7s
Add delete-all-data endpoint to auth API for user data removal
2026-05-20 17:25:46 +02:00

7.1 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/auth/delete-all-data

Elimina todas las notas, categorías y refresh tokens del usuario autenticado. El usuario se conserva.

Headers:

  • Authorization: Bearer <accessToken>: obligatorio.

Respuesta ejemplo:

{
  "message": "Datos del usuario eliminados correctamente; se eliminaron todos los tokens."
}

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,
        "isPermanentlyDeleted": false,
        "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",
  "name": "texto_cifrado",
  "serverVersion": 1,
  "isDeleted": false,
  "isDirty": true,
  "colorValue": 4281558681,
  "iconCodePoint": 58896,
  "updatedAt": "2026-05-18T10:05:00.000Z"
}

Campos:

  • id: UUID, obligatorio.
  • name: string (cifrado), obligatorio. Equivale a encrypted_name; ambos contienen el nombre encriptado.
  • serverVersion: number entero >= 0, obligatorio. Es la versión base local con la que se hizo el cambio.
  • isDeleted: boolean, opcional, por defecto false.
  • isDirty: boolean, opcional. El servidor lo ignora en la escritura y devuelve false en la respuesta de sync.
  • colorValue: number, opcional.
  • iconCodePoint: number, opcional.
  • 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.
  • isPermanentlyDeleted: boolean, opcional, por defecto false. Si llega en true, el servidor guarda la nota con encrypted_title y encrypted_body vacíos, position en 0 y isDeleted en true.
  • 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,
        "isPermanentlyDeleted": 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.