feat(sprint-4/etapa-2): migración persistencia proceso/grupos/settings a Supabase

- Scripts SQL 002-004: process_groups, processes, app_settings con RLS
- supabaseProcessRepo: save/getById/getAll/delete con mapeo camelCase↔snake_case
  y limpieza Dexie en delete (actividades/gateways/resources/simulations)
- supabaseGroupRepo: save/getAll/getById/delete; FK ON DELETE SET NULL en processes
- supabaseSettingsRepo: get/update sobre fila singleton
- Stores actualizados: library-store y process-store usan repos Supabase para procesos
  (actividades/gateways/recursos siguen en Dexie hasta Etapa 3-4)
- ImportPage y useReportData actualizados a supabaseProcessRepo
- userId inyectado internamente en repos via supabase.auth.getSession() — no se
  threadea por la call stack; stores mantienen interfaces sin cambios
- 561/561 tests verdes, build limpio

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 00:23:38 -03:00
parent 78e776df6b
commit 11da15136d
10 changed files with 258 additions and 30 deletions

View File

@@ -1,6 +1,9 @@
import { create } from 'zustand'
import { v4 as uuidv4 } from 'uuid'
import { processRepo, groupRepo, settingsRepo, simulationRepo } from '@/persistence/repositories'
import { supabaseProcessRepo } from '@/persistence/supabase/process-repo'
import { supabaseGroupRepo } from '@/persistence/supabase/group-repo'
import { supabaseSettingsRepo } from '@/persistence/supabase/settings-repo'
import { simulationRepo } from '@/persistence/repositories'
import type { Process, ProcessGroup, AppSettings, Simulation } from '@/domain/types'
interface LibraryState {
@@ -14,6 +17,8 @@ interface LibraryState {
createGroup: (name: string) => Promise<ProcessGroup>
deleteGroup: (id: string) => Promise<void>
deleteProcess: (id: string) => Promise<void>
assignProcessToGroup: (processId: string, groupId: string | null) => Promise<void>
getProcessesByGroup: (groupId: string | null) => Process[]
@@ -33,42 +38,40 @@ export const useLibraryStore = create<LibraryState>((set, get) => ({
load: async () => {
set({ isLoading: true })
const [groups, rawProcesses, settings] = await Promise.all([
groupRepo.getAll(),
processRepo.getAll(),
settingsRepo.get(),
const [groups, processes, settings] = await Promise.all([
supabaseGroupRepo.getAll(),
supabaseProcessRepo.getAll(),
supabaseSettingsRepo.get(),
])
// Normalizar datos legacy que puedan llegar sin tags/groupId del IndexedDB
const processes = rawProcesses.map((p) => ({
...p,
groupId: p.groupId ?? null,
tags: Array.isArray(p.tags) ? p.tags : [],
}))
set({ groups, processes, settings, isLoading: false })
},
createGroup: async (name: string) => {
const now = Date.now()
const group: ProcessGroup = { id: uuidv4(), name, createdAt: now, updatedAt: now }
await groupRepo.save(group)
await supabaseGroupRepo.save(group)
set((state) => ({ groups: [...state.groups, group].sort((a, b) => a.name.localeCompare(b.name)) }))
return group
},
deleteGroup: async (id: string) => {
await groupRepo.delete(id)
await supabaseGroupRepo.delete(id)
set((state) => ({
groups: state.groups.filter((g) => g.id !== id),
// Los procesos del grupo pasan a groupId = null
processes: state.processes.map((p) => p.groupId === id ? { ...p, groupId: null } : p),
}))
},
deleteProcess: async (id: string) => {
await supabaseProcessRepo.delete(id)
set((state) => ({ processes: state.processes.filter((p) => p.id !== id) }))
},
assignProcessToGroup: async (processId: string, groupId: string | null) => {
const process = get().processes.find((p) => p.id === processId)
if (!process) return
const updated = { ...process, groupId, updatedAt: Date.now() }
await processRepo.save(updated)
await supabaseProcessRepo.save(updated)
set((state) => ({
processes: state.processes.map((p) => p.id === processId ? updated : p),
}))
@@ -86,7 +89,7 @@ export const useLibraryStore = create<LibraryState>((set, get) => ({
},
updateSettings: async (updates) => {
await settingsRepo.update(updates)
await supabaseSettingsRepo.update(updates)
set((state) => ({ settings: { ...state.settings, ...updates } }))
},