Sprint 4 — Etapa 7 completada. Build limpio, 521/521 tests verdes. Resumen:
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
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.
This commit is contained in:
202
sprints/sprint-4/ETAPA_7_PROMPT.md
Normal file
202
sprints/sprint-4/ETAPA_7_PROMPT.md
Normal file
@@ -0,0 +1,202 @@
|
||||
# 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<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.
|
||||
Reference in New Issue
Block a user