import 'package:notas/models/note.dart'; import 'package:notas/models/category.dart'; // DTOs para sincronización con el servidor class SyncRequest { SyncRequest({ DateTime? lastSyncAt, required this.changes, }) : lastSyncAt = lastSyncAt ?? DateTime.utc(1970, 1, 1); final DateTime lastSyncAt; final SyncChanges changes; Map toJson() { return { 'lastSyncAt': lastSyncAt.toIso8601String(), 'changes': changes.toJson(), }; } } class SyncChanges { const SyncChanges({ this.categories = const [], this.notes = const [], }); final List categories; final List notes; Map toJson() { return { if (categories.isNotEmpty) 'categories': categories.map((c) => c.toJson()).toList(), if (notes.isNotEmpty) 'notes': notes.map((n) => n.toJson()).toList(), }; } } class SyncChangesResponse { const SyncChangesResponse({ this.categories = const [], this.notes = const [], }); final List categories; final List notes; factory SyncChangesResponse.fromJson(Map json) { final List categoriesJson = json['categories'] as List? ?? []; final List notesJson = json['notes'] as List? ?? []; return SyncChangesResponse( categories: categoriesJson .map((c) => SyncCategoryResponse.fromJson(c as Map)) .toList(), notes: notesJson .map((n) => SyncNoteResponse.fromJson(n as Map)) .toList(), ); } } class SyncCategoryPayload { const SyncCategoryPayload({ required this.id, required this.encryptedName, required this.serverVersion, this.isDeleted = false, required this.updatedAt, }); final String id; // uuid final String encryptedName; final int serverVersion; final bool isDeleted; final DateTime updatedAt; factory SyncCategoryPayload.fromCategory(Category category) { return SyncCategoryPayload( id: category.uuid, encryptedName: category.encryptedName, serverVersion: category.serverVersion, isDeleted: category.isDeleted, updatedAt: category.updatedAt, ); } Map toJson() { return { 'id': id, 'encrypted_name': encryptedName, 'serverVersion': serverVersion, 'isDeleted': isDeleted, 'updatedAt': updatedAt.toIso8601String(), }; } } class SyncNotePayload { const SyncNotePayload({ required this.id, this.categoryId, required this.encryptedTitle, required this.encryptedBody, required this.serverVersion, this.position = 0, this.isDeleted = false, required this.updatedAt, }); final String id; // uuid final String? categoryId; final String encryptedTitle; final String encryptedBody; final int serverVersion; final int position; final bool isDeleted; final DateTime updatedAt; factory SyncNotePayload.fromNote( Note note, { required String encryptedTitle, required String encryptedBody, }) { return SyncNotePayload( id: note.uuid, categoryId: note.categoryId, encryptedTitle: encryptedTitle, encryptedBody: encryptedBody, serverVersion: note.serverVersion, position: note.index, isDeleted: note.isDeleted, updatedAt: note.updatedAt, ); } Map toJson() { return { 'id': id, if (categoryId != null) 'categoryId': categoryId, 'encrypted_title': encryptedTitle, 'encrypted_body': encryptedBody, 'serverVersion': serverVersion, if (position != 0) 'position': position, if (isDeleted) 'isDeleted': isDeleted, 'updatedAt': updatedAt.toIso8601String(), }; } } class SyncResponse { const SyncResponse({ required this.serverTimestamp, required this.synced, required this.changes, }); final DateTime serverTimestamp; final bool synced; final SyncChangesResponse changes; factory SyncResponse.fromJson(Map json) { return SyncResponse( serverTimestamp: DateTime.parse(json['serverTimestamp'] as String), synced: json['synced'] as bool? ?? false, changes: SyncChangesResponse.fromJson( json['changes'] as Map? ?? {}), ); } } class SyncCategoryResponse { const SyncCategoryResponse({ required this.id, required this.encryptedName, required this.serverVersion, this.isDeleted = false, required this.updatedAt, }); final String id; // uuid final String encryptedName; final int serverVersion; final bool isDeleted; final DateTime updatedAt; factory SyncCategoryResponse.fromJson(Map json) { return SyncCategoryResponse( id: json['id'] as String, encryptedName: json['encrypted_name'] as String, serverVersion: json['serverVersion'] as int, isDeleted: json['isDeleted'] as bool? ?? false, updatedAt: DateTime.parse(json['updatedAt'] as String), ); } Category toCategory() { return Category( uuid: id, encryptedName: encryptedName, serverVersion: serverVersion, isDeleted: isDeleted, updatedAt: updatedAt, ); } } class SyncNoteResponse { const SyncNoteResponse({ required this.id, this.categoryId, required this.encryptedTitle, required this.encryptedBody, required this.serverVersion, this.position = 0, this.isDeleted = false, required this.updatedAt, }); final String id; // uuid final String? categoryId; final String encryptedTitle; final String encryptedBody; final int serverVersion; final int position; final bool isDeleted; final DateTime updatedAt; factory SyncNoteResponse.fromJson(Map json) { return SyncNoteResponse( id: json['id'] as String, categoryId: json['categoryId'] as String?, encryptedTitle: json['encrypted_title'] as String, encryptedBody: json['encrypted_body'] as String, serverVersion: json['serverVersion'] as int, position: json['position'] as int? ?? 0, isDeleted: json['isDeleted'] as bool? ?? false, updatedAt: DateTime.parse(json['updatedAt'] as String), ); } Note toNote() { return Note( uuid: id, title: 'Encrypted', // placeholder, será descifrado por la app body: 'Encrypted', // placeholder, será descifrado por la app createdAt: updatedAt, updatedAt: updatedAt, index: position, serverVersion: serverVersion, isDeleted: isDeleted, categoryId: categoryId, ); } }