feat(sprint-3): biblioteca, recursos TO-BE, ROI mejorado e identidad de marca
Sprint 3 completo — 7 etapas: - Etapa 1-2: biblioteca de procesos con agrupación por cliente (Dexie v4, LibraryPage, library-store, groupId/tags en Process) - Etapa 3: modal de configuración global por proceso (horizonte, frecuencia, inversión, moneda) con validación y persistencia - Etapa 4: panel de recursos rediseñado con soporte de unidades y minutos de utilización (utilizationMinutes + units) - Etapa 5: recursos en escenario automatizado — motor de simulación, UI de edición en ActivityPanel, comparación AS-IS/TO-BE en reporte - Etapa 6: PDF recursos automatizados, automatedAssignedResources requerido, 561 tests pasando - Etapa 7: identidad de marca — AppHeader gradiente naranja/magenta con logo InQuality en 5 páginas, favicon con logo real, logo/marca navegan a home Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
98
src/store/library-store.ts
Normal file
98
src/store/library-store.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { create } from 'zustand'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { processRepo, groupRepo, settingsRepo, simulationRepo } from '@/persistence/repositories'
|
||||
import type { Process, ProcessGroup, AppSettings, Simulation } from '@/domain/types'
|
||||
|
||||
interface LibraryState {
|
||||
groups: ProcessGroup[]
|
||||
processes: Process[]
|
||||
settings: AppSettings
|
||||
isLoading: boolean
|
||||
|
||||
load: () => Promise<void>
|
||||
|
||||
createGroup: (name: string) => Promise<ProcessGroup>
|
||||
deleteGroup: (id: string) => Promise<void>
|
||||
|
||||
assignProcessToGroup: (processId: string, groupId: string | null) => Promise<void>
|
||||
|
||||
getProcessesByGroup: (groupId: string | null) => Process[]
|
||||
getLatestSimulation: (processId: string) => Promise<Simulation | undefined>
|
||||
|
||||
updateSettings: (updates: Partial<Omit<AppSettings, 'id'>>) => Promise<void>
|
||||
getAllTags: () => string[]
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: AppSettings = { id: 'singleton', groupLabel: 'Cliente' }
|
||||
|
||||
export const useLibraryStore = create<LibraryState>((set, get) => ({
|
||||
groups: [],
|
||||
processes: [],
|
||||
settings: DEFAULT_SETTINGS,
|
||||
isLoading: false,
|
||||
|
||||
load: async () => {
|
||||
set({ isLoading: true })
|
||||
const [groups, rawProcesses, settings] = await Promise.all([
|
||||
groupRepo.getAll(),
|
||||
processRepo.getAll(),
|
||||
settingsRepo.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)
|
||||
set((state) => ({ groups: [...state.groups, group].sort((a, b) => a.name.localeCompare(b.name)) }))
|
||||
return group
|
||||
},
|
||||
|
||||
deleteGroup: async (id: string) => {
|
||||
await groupRepo.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),
|
||||
}))
|
||||
},
|
||||
|
||||
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)
|
||||
set((state) => ({
|
||||
processes: state.processes.map((p) => p.id === processId ? updated : p),
|
||||
}))
|
||||
},
|
||||
|
||||
getProcessesByGroup: (groupId: string | null) => {
|
||||
const processes = get().processes
|
||||
return groupId === null
|
||||
? processes.filter((p) => p.groupId == null)
|
||||
: processes.filter((p) => p.groupId === groupId)
|
||||
},
|
||||
|
||||
getLatestSimulation: (processId: string) => {
|
||||
return simulationRepo.getLatestByProcess(processId)
|
||||
},
|
||||
|
||||
updateSettings: async (updates) => {
|
||||
await settingsRepo.update(updates)
|
||||
set((state) => ({ settings: { ...state.settings, ...updates } }))
|
||||
},
|
||||
|
||||
getAllTags: () => {
|
||||
const tagSet = new Set<string>()
|
||||
get().processes.forEach((p) => p.tags.forEach((t) => tagSet.add(t)))
|
||||
return [...tagSet].sort()
|
||||
},
|
||||
}))
|
||||
Reference in New Issue
Block a user