# 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: ```text /api ``` Ejemplo local: ```text 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: ```json { "message": "¡API funcionando correctamente!" } ``` ### `POST /api/auth/register` Crea un usuario nuevo. Body: ```json { "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: ```json { "message": "Usuario registrado con éxito", "accessToken": "jwt_access_token", "refreshToken": "token_largo_aleatorio" } ``` ### `POST /api/auth/login` Inicia sesión y devuelve tokens. Body: ```json { "username": "maria", "password": "123456", "deviceName": "Flutter Pixel" } ``` Campos: - `username`: `string`, obligatorio. - `password`: `string`, obligatorio. - `deviceName`: `string`, opcional. Respuesta ejemplo: ```json { "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: ```json { "refreshToken": "token_largo_aleatorio" } ``` Campos: - `refreshToken`: `string`, obligatorio. Respuesta ejemplo: ```json { "accessToken": "nuevo_jwt_access_token" } ``` ### `POST /api/auth/logout` Revoca la sesión del dispositivo actual. Body: ```json { "refreshToken": "token_largo_aleatorio" } ``` Campos: - `refreshToken`: `string`, obligatorio. Respuesta ejemplo: ```json { "message": "Sesión cerrada en el dispositivo actual" } ``` ### `POST /api/sync` Endpoint único de sincronización offline-first. Autenticación requerida: - Enviar `Authorization: Bearer ` (obligatorio). El servidor ya no acepta `userId` en el body. #### Request Body ejemplo: ```json { "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 ```json { "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 ```json { "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): ```json { "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.