# 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 `#F59845` con í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í:** ```typescript 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`: ```typescript export type ResourceType = 'person' | 'role' | 'system' | 'equipment' | 'supply' ``` **Labels en español para la UI:** ```typescript const RESOURCE_TYPE_LABELS: Record = { person: 'Persona', role: 'Rol', system: 'Sistema', equipment: 'Equipo', supply: 'Insumo', } ``` **Íconos y colores por tipo (Lucide React / Tabler):** ```typescript // 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 `ResourceType` a los 5 valores definitivos (ver arriba) - Eliminar `processId?: UUID` de la interfaz `Resource` — 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 `processId` si existe en `fromRow` o `toRow` ### 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): ```typescript // 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 1. Actualizar `ResourceType` en `domain/types.ts` + eliminar `processId?` de `Resource` 2. Limpiar `resource-repo.ts` (eliminar processId, agregar JOIN con users) 3. Crear `src/features/resources/ResourceCatalogPage.tsx` 4. Registrar la ruta `/recursos` en el router 5. Redesign de `ResourcesPanel.tsx` (mockup como ground truth) 6. `npm run build` — debe pasar sin errores TypeScript 7. `npm run test` — debe mantener 521+ tests verdes 8. Commit: `feat(sprint-4/etapa-7): catálogo global de recursos + redesign ResourcesPanel` --- ## Criterios de validación - [ ] `ResourceType` tiene exactamente `'person' | 'role' | 'system' | 'equipment' | 'supply'` - [ ] `Resource` no tiene campo `processId` - [ ] Ruta `/recursos` existe 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 `/recursos` con 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.