Tipos: Resource.processId? eliminado (vestigial); ResourceType ya tenía los 5 valores correctos, sin cambios. resource-repo.ts: agregado getAllWithCreator() con JOIN a users!created_by (mismo patrón que Etapa 6) y tipo ResourceWithCreator. delete() ahora valida que no haya asignaciones activas en activity_resource_assignments antes de borrar — lanza error explicativo si las hay. Verifiqué ambos contra Supabase real: el JOIN funciona (FK reconocida por PostgREST) y el conteo de guarda también. Nuevo — src/features/resources/: resource-display.tsx — labels, colores/íconos por tipo (Lucide: User/Briefcase/Server/Wrench/Package) y ResourceTypeAvatar compartido ResourceFormSheet.tsx — Sheet reutilizable para crear/editar, usado tanto en el catálogo como en el panel del workspace ResourceCatalogPage.tsx — página /recursos: búsqueda, filtro por tipo, tabla con badge de tipo, costo, "creado por", editar/eliminar con confirmación Nuevo — src/components/ui/sheet.tsx: componente shadcn Sheet (no existía), construido sobre @radix-ui/react-dialog que ya estaba instalado — no agregué dependencias nuevas. ResourcesPanel.tsx rediseñado: ahora solo muestra recursos usados en el proceso actual (derivado de assignedResources), con conteo de actividades por recurso. CRUD completo se mudó a /recursos; el panel solo permite "Nuevo" (catálogo global) y tiene el link "Ver catálogo completo". Router: ruta /recursos registrada. ActivityPanel no se tocó, como indicaba el scope.
8.5 KiB
Sprint 4 — Etapa 7: ResourcesPanel — UX de catálogo global
Contexto
InQ ROI — React 18 + Vite + TypeScript + TanStack Router + Zustand + shadcn/ui + Tailwind.
Etapas 1–6 completadas. El backend de recursos ya es global desde Etapa 3: resource-repo.ts hace getAll() sin filtro, el upsert no envía processId, y la tabla resources en Supabase no tiene columna process_id.
Lo que falta es UX: el ResourcesPanel todavía muestra todos los recursos sin filtrar (con el header "Recursos del proceso" que ya no corresponde) y no existe página de catálogo.
Este prompt tiene un mockup aprobado como contrato visual. Claude Code lo replica sin interpretar libremente.
Mockup aprobado — descripción exacta
Vista 1: ResourcesPanel en workspace (panel lateral derecho)
Header del panel:
- Título: "Recursos" (no "del proceso")
- Botón naranja
#F59845con ícono+y texto "Nuevo" — abre un Sheet/Dialog para crear recurso en el catálogo global
Cuerpo del panel:
- Subheader: "Usados en este proceso" + cuenta
(N) - Lista de recursos que tienen al menos una asignación en las actividades del proceso actual
- Cada ítem: avatar circular con ícono del tipo (ver iconos y colores más abajo) + nombre del recurso + costo/hora a la derecha
- Segunda línea: tipo en texto + "N actividades" (contar cuántas actividades de este proceso usan este recurso)
- Si no hay recursos asignados: estado vacío con mensaje "Ningún recurso asignado a actividades aún"
Footer del panel:
- Texto de contexto: "Recursos del catálogo global del equipo"
- Botón secundario ancho completo: ícono
external-link+ "Ver catálogo completo" → navega a/recursos
La lista de recursos "usados en este proceso" se deriva así:
const usedResourceIds = new Set(
currentProcess.activities
.flatMap(a => a.assignedResources ?? [])
.map(ar => ar.resourceId)
)
const usedResources = resources.filter(r => usedResourceIds.has(r.id))
const usageCount = (resourceId: string) =>
currentProcess.activities.filter(
a => (a.assignedResources ?? []).some(ar => ar.resourceId === resourceId)
).length
Vista 2: Página catálogo /recursos
Header de la página:
- Título: "Catálogo de recursos"
- Subtítulo con ícono de equipo: "Compartido con todo el equipo · N recursos"
- Botón naranja
#F59845: "+ Nuevo recurso" → abre Sheet/Dialog de creación
Barra de herramientas:
- Input de búsqueda (filtra por nombre en tiempo real, client-side)
- Select de tipo (filtra por tipo: "Todos los tipos" + los 5 tipos)
Tabla:
| Recurso | Tipo | Costo/h | Creado por | Acciones |
|---|---|---|---|---|
| [avatar + nombre] | [badge] | [mono] | [nombre] | [edit] [delete] |
- Botones de acción: editar (abre Sheet con form pre-poblado) y eliminar (con confirmación)
- "Creado por" se resuelve desde el JOIN con
users(igual que en LibraryPage — mismo patrón de Etapa 6)
Tipos de recurso — valores definitivos
Los únicos valores válidos son: person | role | system | equipment | supply
Estos son los valores del schema real (migración 005). Actualizar ResourceType en domain/types.ts:
export type ResourceType = 'person' | 'role' | 'system' | 'equipment' | 'supply'
Labels en español para la UI:
const RESOURCE_TYPE_LABELS: Record<ResourceType, string> = {
person: 'Persona',
role: 'Rol',
system: 'Sistema',
equipment: 'Equipo',
supply: 'Insumo',
}
Íconos y colores por tipo (Lucide React / Tabler):
// Usando Lucide React (ya en el stack):
person: User — fondo #FEF3E7, ícono #F59845
role: Briefcase — fondo #FEF3E7, ícono #D97706
system: Server — fondo #EEF2FF, ícono #6366F1
equipment: Wrench — fondo #F0FDF4, ícono #16A34A
supply: Package — fondo #F1F5F9, ícono #64748B
Alcance estricto — qué entra en esta etapa
1. Limpieza de tipos
En src/domain/types.ts:
- Actualizar
ResourceTypea los 5 valores definitivos (ver arriba) - Eliminar
processId?: UUIDde la interfazResource— es un campo vestigial sin uso. Ya no se mapea ni se envía
En src/persistence/supabase/resource-repo.ts:
- Eliminar cualquier referencia a
processIdsi existe enfromRowotoRow
2. Página de catálogo — nueva ruta /recursos
Crear src/features/resources/ResourceCatalogPage.tsx con:
- Header + búsqueda + filtro por tipo + tabla (ver mockup)
- Sheet/Dialog compartido para crear y editar recursos (reutilizar el mismo componente para ambas acciones)
- Confirmación antes de eliminar (no eliminar recursos que están asignados a actividades — mostrar error explicativo si hay asignaciones activas)
JOIN con users para "Creado por" (mismo patrón que Etapa 6):
// En resource-repo.ts, método getAll():
const { data } = await supabase
.from('resources')
.select('*, creator:users!created_by(id, name)')
.order('name', { ascending: true })
Agregar tipo extendido ResourceWithCreator en resource-repo.ts (igual que ProcessWithUserNames en Etapa 6).
Ruta nueva en TanStack Router: src/router.tsx o donde estén definidas las rutas.
3. Navegación al catálogo
Agregar acceso a /recursos desde la app. Opciones (elegir la más limpia según la estructura de nav existente):
- Link en AppHeader junto al logo/título
- O link desde el botón "Ver catálogo completo" del ResourcesPanel (este ya está en el mockup)
- No agregar ítem de navegación lateral si no existe nav lateral — el acceso desde el panel es suficiente para Sprint 4
4. ResourcesPanel — redesign
Reemplazar el cuerpo actual de src/features/workspace/ResourcesPanel.tsx según el mockup aprobado:
- Lista filtrada "usados en este proceso" (ver derivación arriba)
- Botón "Nuevo" → Sheet/Dialog de creación (el mismo componente de la página de catálogo)
- Link "Ver catálogo completo" → navega a
/recursos - El CRUD completo (editar, eliminar, listar todos) vive en
/recursos, no en el panel del workspace
5. ActivityPanel — sin cambios
El dropdown de asignación de recursos ya funciona correctamente con el catálogo global. No tocar.
Qué NO entra en esta etapa
- Gestión de grupos de usuarios
- Permisos por recurso (los recursos son siempre visibles para todos los autenticados)
- Import/export de catálogo
- Historial de uso de un recurso (cuántos procesos lo usan)
- Cambios en PDF, simulación o workspace salvo ResourcesPanel
- Tests de integración de auth → Etapa 8
Orden de ejecución
- Actualizar
ResourceTypeendomain/types.ts+ eliminarprocessId?deResource - Limpiar
resource-repo.ts(eliminar processId, agregar JOIN con users) - Crear
src/features/resources/ResourceCatalogPage.tsx - Registrar la ruta
/recursosen el router - Redesign de
ResourcesPanel.tsx(mockup como ground truth) npm run build— debe pasar sin errores TypeScriptnpm run test— debe mantener 521+ tests verdes- Commit:
feat(sprint-4/etapa-7): catálogo global de recursos + redesign ResourcesPanel
Criterios de validación
ResourceTypetiene exactamente'person' | 'role' | 'system' | 'equipment' | 'supply'Resourceno tiene campoprocessId- Ruta
/recursosexiste y carga la página de catálogo - Tabla del catálogo muestra nombre, tipo (badge con color), costo/h, creado por
- Búsqueda client-side funciona por nombre
- Filtro por tipo funciona
- Crear recurso desde catálogo funciona (guarda en Supabase, aparece en la lista)
- Editar recurso funciona
- Eliminar recurso funciona (con confirmación)
- ResourcesPanel en workspace muestra SOLO recursos usados en el proceso actual
- ResourcesPanel "Ver catálogo completo" navega a
/recursos - ResourcesPanel "Nuevo" crea en el catálogo global
- ActivityPanel sigue funcionando (asignación de recursos sin cambios)
- Build limpio sin errores TypeScript
- 521+ tests verdes
- Validación visual del director — ver ResourcesPanel con lista filtrada y página
/recursoscon tabla antes del OK formal
Nota: Sheet vs Modal para el form de recurso
Usar Sheet de shadcn/ui (panel lateral deslizante) en lugar de Dialog (modal centrado) — es más consistente con el contexto del workspace donde el espacio es lateral. El Sheet se abre desde ambos lugares: botón "Nuevo" del ResourcesPanel y botón "+ Nuevo recurso" de la página de catálogo. Reutilizar el mismo componente ResourceFormSheet.tsx en ambos contextos.