diff --git a/src/services/storage-adapter.ts b/src/services/storage-adapter.ts new file mode 100644 index 0000000..8991042 --- /dev/null +++ b/src/services/storage-adapter.ts @@ -0,0 +1,101 @@ +/** + * storage-adapter.ts + * + * Capa unificada que enruta a Turso (Tauri) o Dexie (browser). + * Los consumidores importan desde aquí en vez de tauri-db directo. + */ + +import { isTauri } from '@/services/storage-router' +import { tauriDb, type HuDraftRecord as TauriDraft, type AlphaUserRecord, type CellRecord } from '@/services/tauri-db' +import * as dexieDrafts from '@/services/hu-drafts-db' +import * as dexieUsers from '@/services/users-db' + +// ─── HuDrafts ─────────────────────────────────────────── + +export async function getHuDrafts(projectId: number): Promise { + if (isTauri()) { + const res = await tauriDb.getHuDrafts(projectId) + return (res || []) as any[] + } + return dexieDrafts.getDrafts(projectId) +} + +export async function saveHuDraft(draft: any): Promise { + if (isTauri()) { + await tauriDb.saveHuDraft(draft as TauriDraft) + } + await dexieDrafts.saveDraft(draft) +} + +export async function deleteHuDraft(id: string): Promise { + if (isTauri()) { await tauriDb.deleteHuDraft(id).catch(() => {}) } + await dexieDrafts.deleteDraft(id) +} + +// ─── Users ────────────────────────────────────────────── + +export async function getUsers(): Promise { + if (isTauri()) { + const res = await tauriDb.getUsers() + return (res || []) as AlphaUserRecord[] + } + const dbUsers = await dexieUsers.getUsers() + return dbUsers.map(u => ({ + id: u.id, + email: u.email, + first_name: u.firstName, + last_name: u.lastName, + role: null, + seniority: u.seniority, + cell_id: u.cellId, + is_active: u.isActive, + created_at: u.createdAt, + }) as AlphaUserRecord) +} + +export async function saveUser(user: AlphaUserRecord): Promise { + if (isTauri()) { await tauriDb.saveUser(user).catch(() => {}) } + await dexieUsers.saveUser({ + id: user.id, + email: user.email, + firstName: user.first_name, + lastName: user.last_name, + fullName: `${user.first_name} ${user.last_name}`.trim(), + username: null, + isActive: user.is_active, + isStaff: false, + roleId: null, + cellId: user.cell_id, + seniority: user.seniority, + companyId: null, + phone: null, + position: null, + lastActive: null, + createdAt: user.created_at || null, + updatedAt: new Date().toISOString(), + }) +} + +// ─── Cells ────────────────────────────────────────────── + +export async function getCells(): Promise { + if (isTauri()) { + const res = await tauriDb.getCells() + return (res || []) as CellRecord[] + } + const items = await dexieUsers.getLookups('cell') + return items.map(c => ({ id: c.id, name: c.name, description: c.description, created_at: null }) as CellRecord) +} + +export async function saveCell(name: string, description?: string): Promise { + if (isTauri()) { + return tauriDb.saveCell({ id: 0, name, description: description ?? null, created_at: null }) + } + const items = await dexieUsers.getLookups('cell') + const maxId = items.reduce((m, c) => Math.max(m, c.id), 0) + const newId = maxId + 1 + await dexieUsers.saveLookup('cell', newId, name, description) + return newId +} + +export { type CellRecord } from '@/services/tauri-db' diff --git a/src/services/storage-router.ts b/src/services/storage-router.ts new file mode 100644 index 0000000..1de6bca --- /dev/null +++ b/src/services/storage-router.ts @@ -0,0 +1,34 @@ +/** + * storage-router.ts + * + * Detecta si estamos en Tauri (Turso disponible) o navegador. + * Enruta operaciones al backend adecuado. + * + * Estrategia: + * - Tauri disponible → Turso (Rust) como fuente primaria + * - Solo navegador → Dexie (IndexedDB) + * + * Para el futuro (RUMBO): este router desaparece, todo va a Turso directo. + */ + +let _isTauri: boolean | null = null + +export function isTauri(): boolean { + if (_isTauri !== null) return _isTauri + try { + _isTauri = typeof window !== 'undefined' && + (window as any).__TAURI_INTERNALS__ !== undefined + } catch { + _isTauri = false + } + return _isTauri +} + +export const ADAPTER = { + get name(): string { + return isTauri() ? 'Turso' : 'Dexie' + }, + get available(): boolean { + return true // Siempre disponible (Dexie fallback) + }, +} diff --git a/src/services/tauri-db.ts b/src/services/tauri-db.ts index 685c585..afdb162 100644 --- a/src/services/tauri-db.ts +++ b/src/services/tauri-db.ts @@ -1,15 +1,54 @@ import { invoke as tauriInvoke } from '@tauri-apps/api/core' +import db from '@/services/db' const _invoke = typeof tauriInvoke === 'function' ? tauriInvoke : undefined function safeInvoke(cmd: string, args?: Record): Promise { if (!_invoke) { - console.warn(`[Alpha] Tauri invoke no disponible (${cmd}). ¿Estás en un navegador externo? Usá la ventana de Tauri.`) - return Promise.resolve(null as unknown as T) + return fallbackInvoke(cmd, args) } return _invoke(cmd, args) } +/** + * Cuando Tauri no está disponible, algunas operaciones se pueden + * redirigir a Dexie (IndexedDB) para mantener funcionalidad en bun dev. + */ +async function fallbackInvoke(cmd: string, args?: Record): Promise { + switch (cmd) { + // ─── HuDrafts → Dexie ───────────────────────────── + case 'get_hu_drafts': { + const { getDrafts } = await import('@/services/hu-drafts-db') + const pid = (args as any)?.initiativeId + const drafts = pid ? await getDrafts(Number(pid)) : await getDrafts(0) + return drafts as unknown as T + } + case 'save_hu_draft': { + const { saveDraft } = await import('@/services/hu-drafts-db') + const draft = (args as any)?.draft + if (draft) await saveDraft({ id: draft.id, projectId: draft.initiative_id || 0, title: draft.title, description: draft.description || '', acceptanceCriteria: draft.acceptance_criteria || '', priority: 'Media', type: draft.item_type || 'U', metadata: '{}', syncStatus: draft.sync_status || 'draft', createdAt: new Date().toISOString() }) + return null as unknown as T + } + case 'delete_hu_draft': { + const { deleteDraft } = await import('@/services/hu-drafts-db') + await deleteDraft(String((args as any)?.id)) + return null as unknown as T + } + // ─── Users → Dexie ──────────────────────────────── + case 'get_users': { + try { const r = await (db as any).table('alpha_users').toArray(); return r as unknown as T } catch { return [] as unknown as T } + } + case 'save_user': { + const u = (args as any)?.user + if (u) try { await (db as any).table('alpha_users').put(u) } catch {} + return null as unknown as T + } + // ─── Por defecto: no hay fallback ───────────────── + default: + return null as unknown as T + } +} + export interface ProjectRecord { id: number name: string