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

8.5 KiB
Raw Permalink Blame History

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í:

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 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):

// 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.

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.