Files
inq-roi-simulador-web/sprints/sprint-4/ETAPA_7_PROMPT.md
Marcos Benítez 892b8b3bc7
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Sprint 4 — Etapa 7 completada. Build limpio, 521/521 tests verdes. Resumen:
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.
2026-06-19 00:01:46 -03:00

203 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Sprint 4 — Etapa 7: ResourcesPanel — UX de catálogo global
## Contexto
InQ ROI — React 18 + Vite + TypeScript + TanStack Router + Zustand + shadcn/ui + Tailwind.
Etapas 16 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<ResourceType, string> = {
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.