7.1 KiB
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:trueofalse.number: valor numérico.ISO date string: fecha en formato ISO 8601, por ejemplo2026-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 aceptauserIden 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:stringISO, opcional. Si no se envía, el servidor usa1970-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 aencrypted_name; ambos contienen el nombre encriptado.serverVersion:numberentero >= 0, obligatorio. Es la versión base local con la que se hizo el cambio.isDeleted:boolean, opcional, por defectofalse.isDirty:boolean, opcional. El servidor lo ignora en la escritura y devuelvefalseen 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:numberentero >= 0, obligatorio. Es la versión base local con la que se hizo el cambio.position:number, opcional.isDeleted:boolean, opcional, por defectofalse.isPermanentlyDeleted:boolean, opcional, por defectofalse. Si llega entrue, el servidor guarda la nota conencrypted_titleyencrypted_bodyvacíos,positionen0yisDeletedentrue.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:stringISO con la hora del servidor.synced:boolean.changes.categories: array con todas las categorías cambiadas en el servidor desdelastSyncAt, incluidas las creadas por otros dispositivos.changes.notes: array con todas las notas cambiadas en el servidor desdelastSyncAt, 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(noupdatedAt). - 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_bodyyencrypted_name, pero no interpreta el contenido.