Etapa 5 completa.
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled

This commit is contained in:
2026-07-07 10:32:02 -03:00
parent bc9f70419c
commit ef9ec472d5
9 changed files with 511 additions and 15 deletions

View File

@@ -1,5 +1,5 @@
import { useNavigate, useSearch } from '@tanstack/react-router'
import { useCallback, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { UploadCloud, FileText, Loader2, FlaskConical } from 'lucide-react'
import { v4 as uuidv4 } from 'uuid'
import { Card, CardContent } from '@/components/ui/card'
@@ -43,6 +43,15 @@ export function ImportPage() {
const [isProcessing, setIsProcessing] = useState(false)
const [loadingSampleId, setLoadingSampleId] = useState<string | null>(null)
// Solo platform_admin y member pueden importar BPMN
useEffect(() => {
if (!user) return
const allowed = ['platform_admin', 'member']
if (!allowed.includes(user.platformRole)) {
void navigate({ to: '/' })
}
}, [user, navigate])
// groupId viene de la URL cuando el usuario importa desde dentro de un grupo
const search = useSearch({ from: '/import' })
const targetGroupId = (search as { groupId?: string }).groupId ?? null
@@ -190,6 +199,9 @@ export function ImportPage() {
}
}
const isClientRole = user?.platformRole === 'client_editor' || user?.platformRole === 'client_viewer'
if (isClientRole) return null
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex flex-col">
<AppHeader />

View File

@@ -447,8 +447,19 @@ function HomeView({
)
}
const isClientRole = user?.platformRole === 'client_editor' || user?.platformRole === 'client_viewer'
// Empty state: sin grupos ni procesos (ya terminó de cargar)
if (groups.length === 0 && processes.length === 0) {
if (isClientRole) {
return (
<div className="flex flex-col items-center justify-center py-24 gap-4">
<Building2 className="h-16 w-16 text-muted-foreground/30" />
<p className="text-base font-medium">No tenés procesos asignados aún</p>
<p className="text-[13px] text-muted-foreground">Contactá a tu administrador para que te asigne acceso</p>
</div>
)
}
return (
<div className="flex flex-col items-center justify-center py-24 gap-4">
<Building2 className="h-16 w-16 text-muted-foreground/30" />
@@ -527,8 +538,8 @@ function HomeView({
</div>
)}
{/* Vista normal — grupos */}
{!isSearching && (
{/* Vista normal — grupos (solo roles internos) */}
{!isSearching && !isClientRole && (
<>
{/* Tag cloud (wow): visible cuando hay >= 3 tags distintos */}
{cloud.length >= 3 && (
@@ -564,7 +575,7 @@ function HomeView({
)}
{/* Header sección grupos */}
<div className="flex items-center gap-2">
<div className="flex items-center gap-2" data-testid="groups-header">
<span className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
{settings.groupLabel}s
</span>
@@ -616,6 +627,21 @@ function HomeView({
)}
</>
)}
{/* Vista cliente: lista plana de procesos (sin grupos, RLS garantiza que solo ven los suyos) */}
{!isSearching && isClientRole && (
<div className="space-y-2">
{visibleProcesses.map((p) => (
<ProcessCardWithSim
key={p.id}
process={p}
currentUserId={currentUserId}
currentUserName={currentUserName}
onShared={onShared}
/>
))}
</div>
)}
</div>
)
}
@@ -788,6 +814,7 @@ export function LibraryPage() {
const currentUserId = user!.id
const currentUserName = user!.name
const totalGroups = groups.length
const isClientRole = user?.platformRole === 'client_editor' || user?.platformRole === 'client_viewer'
return (
<div className="min-h-screen bg-background">
@@ -833,14 +860,16 @@ export function LibraryPage() {
>
<Settings className="h-4 w-4" />
</button>
<button
onClick={() => handleImport(view === 'group' && activeGroupId ? activeGroupId : undefined)}
className="flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium text-white transition-opacity hover:opacity-90"
style={{ background: '#F59845' }}
>
<Upload className="h-4 w-4" />
Importar BPMN
</button>
{!isClientRole && (
<button
onClick={() => handleImport(view === 'group' && activeGroupId ? activeGroupId : undefined)}
className="flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium text-white transition-opacity hover:opacity-90"
style={{ background: '#F59845' }}
>
<Upload className="h-4 w-4" />
Importar BPMN
</button>
)}
</div>
</div>

View File

@@ -29,11 +29,13 @@ import { AutomationScenarioNote } from './AutomationScenarioNote'
import { MethodologyFooter } from './MethodologyFooter'
import { useHeatmapMode } from '@/lib/hooks/useHeatmapMode'
import { useSimulationStore } from '@/store/simulation-store'
import { useAuth } from '@/auth/AuthContext'
import { AppHeader } from '@/components/AppHeader'
export function ReportPage() {
const { processId } = useParams({ from: '/report/$processId' })
const navigate = useNavigate()
const { user } = useAuth()
const { process, simulation, activities, resources, isLoading, error } = useReportData(processId)
const { toast } = useToast()
@@ -108,7 +110,8 @@ export function ReportPage() {
const [isExportingPdf, setIsExportingPdf] = useState(false)
const [isExportingCsv, setIsExportingCsv] = useState(false)
const [activeTab, setActiveTab] = useState<'actual' | 'automatizado' | 'roi'>('actual')
const defaultTab: 'actual' | 'automatizado' | 'roi' = user?.platformRole === 'client_viewer' ? 'roi' : 'actual'
const [activeTab, setActiveTab] = useState<'actual' | 'automatizado' | 'roi'>(defaultTab)
// Tab "Actual" — cross-linking heatmap ↔ tabla
const handleHeatmapClickActual = useCallback((bpmnElementId: string) => {
@@ -294,7 +297,7 @@ export function ReportPage() {
/>
{/* ── Tabs ─────────────────────────────────────────────────────── */}
<Tabs defaultValue="actual" className="w-full" onValueChange={(v) => setActiveTab(v as typeof activeTab)}>
<Tabs defaultValue={defaultTab} className="w-full" onValueChange={(v) => setActiveTab(v as typeof activeTab)}>
<TabsList className="mb-6">
<TabsTrigger value="actual">Actual</TabsTrigger>

View File

@@ -60,6 +60,13 @@ export function WorkspacePage() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentProcess?.id, loadLatestForProcess])
// client_viewer no puede editar — redirigir al reporte
useEffect(() => {
if (user?.platformRole === 'client_viewer') {
void navigate({ to: '/report/$processId', params: { processId } })
}
}, [user, processId, navigate])
// Validación XOR: todos los gateways exclusivos deben sumar 1.0
const xorValidation = useMemo(() => {
const invalidIds = gateways
@@ -153,7 +160,7 @@ export function WorkspacePage() {
// isLoading manda siempre, incluso si currentProcess todavía tiene el proceso
// anterior en el store (navegación directa entre dos workspaces distintos).
if (isLoading) {
if (isLoading || user?.platformRole === 'client_viewer') {
return <WorkspaceSkeleton />
}