Corrección 1 — Delete de proceso restaurado +

This commit is contained in:
2026-05-29 18:45:48 -03:00
parent 0a3c60cd09
commit 2955399ade
19 changed files with 502 additions and 98 deletions

View File

@@ -4,8 +4,9 @@ import { UploadCloud, FileText, Loader2, FlaskConical } from 'lucide-react'
import { v4 as uuidv4 } from 'uuid'
import { Card, CardContent } from '@/components/ui/card'
import { parseBpmnXml, extractActivityElements, extractGatewayElements, BpmnParseError } from '@/domain/bpmn-parser'
import { activityRepo, gatewayRepo } from '@/persistence/repositories'
import { supabaseProcessRepo } from '@/persistence/supabase/process-repo'
import { supabaseActivityRepo } from '@/persistence/supabase/activity-repo'
import { supabaseGatewayRepo } from '@/persistence/supabase/gateway-repo'
import type { Process, Activity, GatewayConfig } from '@/domain/types'
import { bpmnTypeToGatewayType } from '@/domain/types'
import { useToast } from '@/components/ui/use-toast'
@@ -114,10 +115,20 @@ export function ImportPage() {
}
})
await Promise.all([
supabaseProcessRepo.save(process, user!.id),
activityRepo.saveMany(activities),
gatewayRepo.saveMany(gateways),
await Promise.race([
(async () => {
await supabaseProcessRepo.save(process, user!.id)
await Promise.all([
supabaseActivityRepo.saveMany(activities, user!.id),
supabaseGatewayRepo.saveMany(gateways),
])
})(),
new Promise<never>((_, reject) =>
setTimeout(
() => reject(new Error('Timeout: Supabase no respondió en 20s — verificá tu conexión')),
20_000,
)
),
])
toast({

View File

@@ -312,7 +312,7 @@ function HomeView({
onNavigateToGroup: (groupId: string | null) => void
onImport: (groupId?: string) => void
}) {
const { groups, processes, settings, createGroup } = useLibraryStore()
const { groups, processes, settings, createGroup, isLoading } = useLibraryStore()
const { user } = useAuth()
const [search, setSearch] = useState('')
const [activeTags, setActiveTags] = useState<string[]>([])
@@ -330,7 +330,26 @@ function HomeView({
setActiveTags((prev) => prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag])
}
// Empty state: sin grupos ni procesos
// Skeleton mientras carga y aún no hay datos (evita flash de empty state)
if (isLoading && groups.length === 0 && processes.length === 0) {
return (
<div className="space-y-5 animate-pulse">
<div className="h-4 w-32 bg-slate-100 rounded" />
<div className="grid gap-2.5" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(160px, 1fr))' }}>
{[1, 2, 3].map((i) => (
<div key={i} className="h-20 rounded-xl bg-slate-100" />
))}
</div>
<div className="space-y-2">
{[1, 2].map((i) => (
<div key={i} className="h-14 rounded-xl bg-slate-100" />
))}
</div>
</div>
)
}
// Empty state: sin grupos ni procesos (ya terminó de cargar)
if (groups.length === 0 && processes.length === 0) {
return (
<div className="flex flex-col items-center justify-center py-24 gap-4">
@@ -601,7 +620,7 @@ function GroupView({
export function LibraryPage() {
const navigate = useNavigate()
const { load, isLoading, processes, groups, settings } = useLibraryStore()
const { load, processes, groups, settings } = useLibraryStore()
const [view, setView] = useState<'home' | 'group'>('home')
const [activeGroupId, setActiveGroupId] = useState<string | null>(null)
const [transitioning, setTransitioning] = useState(false)
@@ -670,29 +689,23 @@ export function LibraryPage() {
</div>
</div>
{/* Contenido con transición */}
{isLoading ? (
<div className="flex items-center justify-center py-24">
<div className="h-6 w-6 rounded-full border-2 border-primary border-t-transparent animate-spin" />
</div>
) : (
<div
className="transition-all duration-150"
style={{ opacity: transitioning ? 0 : 1, transform: transitioning ? 'translateY(6px)' : 'translateY(0)' }}
>
{view === 'home' ? (
<HomeView
onNavigateToGroup={navigateToGroup}
onImport={handleImport}
/>
) : (
<GroupView
groupId={activeGroupId}
onBack={navigateHome}
/>
)}
</div>
)}
{/* Contenido — sin spinner bloqueante: skeleton dentro de HomeView */}
<div
className="transition-all duration-150"
style={{ opacity: transitioning ? 0 : 1, transform: transitioning ? 'translateY(6px)' : 'translateY(0)' }}
>
{view === 'home' ? (
<HomeView
onNavigateToGroup={navigateToGroup}
onImport={handleImport}
/>
) : (
<GroupView
groupId={activeGroupId}
onBack={navigateHome}
/>
)}
</div>
</div>
</div>
)

View File

@@ -1,6 +1,8 @@
import { useEffect, useState } from 'react'
import { activityRepo, resourceRepo, simulationRepo } from '@/persistence/repositories'
import { supabaseActivityRepo } from '@/persistence/supabase/activity-repo'
import { supabaseResourceRepo } from '@/persistence/supabase/resource-repo'
import { supabaseProcessRepo } from '@/persistence/supabase/process-repo'
import { simulationRepo } from '@/persistence/repositories'
import type { Process, Activity, Resource, Simulation } from '@/domain/types'
interface ReportData {
@@ -30,8 +32,8 @@ export function useReportData(processId: string): ReportData {
const [proc, sim, acts, res] = await Promise.all([
supabaseProcessRepo.getById(processId),
simulationRepo.getLatestByProcess(processId),
activityRepo.getByProcess(processId),
resourceRepo.getByProcess(processId),
supabaseActivityRepo.getByProcess(processId),
supabaseResourceRepo.getAll(),
])
if (cancelled) return
if (!proc) throw new Error('Proceso no encontrado en la base de datos local')

View File

@@ -136,8 +136,11 @@ export function ActivityPanel({ selectedElementId }: ActivityPanelProps) {
if (!localActivity) {
return (
<div className="flex items-center justify-center h-full">
<p className="text-xs text-slate-400">Esta actividad no tiene configuración todavía</p>
<div className="flex flex-col items-center justify-center h-full gap-2 px-6 text-center">
<p className="text-xs text-slate-400">Actividad no encontrada en la base de datos.</p>
<p className="text-[11px] text-slate-300">
Si importaste este proceso antes de la última actualización, volvé a importar el archivo BPMN.
</p>
</div>
)
}

View File

@@ -71,7 +71,6 @@ export function ResourcesPanel() {
} else {
const newResource: Resource = {
id: uuidv4(),
processId: currentProcess.id,
name: form.name.trim(),
type: form.type,
costPerHour,