Files
inq-roi-simulador-web/sprints/sprint-3/ETAPA_2_PROMPT.md
Marcos Benítez 67e259e6ed feat(sprint-3): biblioteca, recursos TO-BE, ROI mejorado e identidad de marca
Sprint 3 completo — 7 etapas:

- Etapa 1-2: biblioteca de procesos con agrupación por cliente (Dexie v4,
  LibraryPage, library-store, groupId/tags en Process)
- Etapa 3: modal de configuración global por proceso (horizonte, frecuencia,
  inversión, moneda) con validación y persistencia
- Etapa 4: panel de recursos rediseñado con soporte de unidades y minutos
  de utilización (utilizationMinutes + units)
- Etapa 5: recursos en escenario automatizado — motor de simulación,
  UI de edición en ActivityPanel, comparación AS-IS/TO-BE en reporte
- Etapa 6: PDF recursos automatizados, automatedAssignedResources requerido,
  561 tests pasando
- Etapa 7: identidad de marca — AppHeader gradiente naranja/magenta con logo
  InQuality en 5 páginas, favicon con logo real, logo/marca navegan a home

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 11:49:43 -03:00

339 lines
16 KiB
Markdown
Raw Permalink 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 3 — Etapa 2: Vista de biblioteca + gestión de grupos
## Contexto
Sos Claude Code trabajando en **InQ ROI**. Las reglas de implementación están en `simulador-web/CLAUDE.md`. El brief del sprint está en `sprints/sprint-3/BRIEF.md`.
**Estado al iniciar esta etapa:**
- Etapa 1 completada: Dexie v4 en producción. Tablas `groups` y `settings` disponibles. `Process.groupId?` y `Process.tags?` existen como campos opcionales. `Activity.automatedAssignedResources?` existe como campo opcional.
- La app tiene: `ImportPage` en `/` (home actual), `WorkspacePage` en `/workspace/$processId`, `ReportPage` en `/report/$processId`.
- Stack de routing: TanStack Router. Store: Zustand (`process-store.ts`, `simulation-store.ts`). Repos: `repositories.ts`.
**Esta etapa construye la Process Library y la convierte en la home de la app.**
---
## Objetivo
1. Crear `LibraryPage`: la nueva home de la aplicación — lista de grupos, navegación a procesos dentro de un grupo, búsqueda, tags, CRUD de grupos.
2. Crear `library-store.ts`: Zustand store para operaciones de biblioteca (grupos + lista de procesos).
3. Extender `repositories.ts` con `groupRepo` y `settingsRepo`.
4. Actualizar `router.tsx`: LibraryPage en `/`, ImportPage en `/import`.
5. Actualizar `ImportPage` y todos los archivos de tests que construyen `Process` o `Activity` literalmente para agregar los nuevos campos requeridos.
6. Promover `Process.groupId` y `Process.tags` a campos **requeridos** en `types.ts` (quitar `?`). `Activity.automatedAssignedResources` permanece opcional hasta Etapa 5.
---
## Contrato visual — respetar sin interpretar
El diseño fue aprobado por el director. Replicarlo fielmente. No agregar features visuales que no estén acá.
### Vista home (lista de grupos)
**Header:**
- Título "Biblioteca de procesos" (18px, weight 500) con subtítulo "N procesos · N clientes" (12px, color tertiary).
- Botón naranja `#F59845` "Importar BPMN" (ícono upload) alineado a la derecha. Al hacer click navega a `/import`.
- Sin botón de configuración en el header.
**Barra de búsqueda:**
- Input full-width con ícono de lupa a la izquierda. Placeholder "Buscar proceso por nombre...". Filtra en tiempo real sobre todos los procesos de todos los grupos (búsqueda global).
- Cuando hay búsqueda activa, mostrar resultados flat (sin agrupar por grupo) con chip que indica a qué grupo pertenece cada proceso.
**Fila de tags:**
- Label "Tags:" seguido de chips seleccionables (border redondeado, fondo blanco, borde tertiary).
- Estado activo: fondo `#fff3e6`, borde `#F59845`, texto `#7a3e00`.
- Múltiples tags activos filtran en modo AND (proceso debe tener TODOS los tags seleccionados).
- Los tags mostrados son los que existen en al menos un proceso. Si no hay tags: no mostrar la fila.
**Sección "Clientes" (o el `groupLabel` configurado):**
- Label de sección en uppercase, 12px, color secondary, con contador de grupos como badge gris pequeño.
- Grid de group cards: `repeat(auto-fill, minmax(160px, 1fr))`, gap 10px.
**Group card (existente):**
- Fondo `background-primary`, borde 0.5px tertiary, border-radius-lg, borde izquierdo 3px `#F59845`.
- Número de procesos: 24px, weight 500, color `#F59845`, arriba.
- Nombre del grupo: 13px, weight 500, debajo del número, truncado con ellipsis.
- Fecha: "Actualizado hace X días" (11px, color tertiary), calculada desde el `updatedAt` más reciente entre los procesos del grupo.
- Al hacer click: navega a la vista de grupo (estado interno, sin cambio de URL).
- Al hover: borde cambia a `border-secondary`.
**Group card "Nuevo cliente" (estado normal):**
- Mismo tamaño que las otras cards. Borde dashed 0.5px tertiary. Fondo `background-primary`.
- Contenido centrado: ícono `ti-plus` + texto "Nuevo cliente" (o el `groupLabel` configurado), color tertiary.
- Al hover: borde `#F59845`, texto `#F59845`.
- Al hacer click: **transforma inline en modo edición** (sin abrir modal).
**Group card "Nuevo cliente" (estado edición):**
- La misma card cambia: borde 1.5px `#F59845`, muestra input de texto + botones "Crear" (naranja) y "Cancelar" (ghost).
- Input con placeholder "Nombre del cliente...".
- Enter confirma. Escape cancela. Nombre vacío → cancelar sin crear.
- Al confirmar: la card de input se reemplaza por la nueva group card en la grilla, y aparece una nueva card "Nuevo cliente" al final.
**Sección "Sin clasificar":**
- Fila full-width, borde dashed, separada de la grilla de grupos.
- Ícono `ti-folder-off`, texto "Procesos sin cliente asignado" (13px, weight 500, color secondary), contador (11px, color tertiary).
- Solo visible si hay al menos un proceso con `groupId === null`.
- Al hacer click: navega a vista de grupo con `groupId === null`.
### Vista de grupo (detalle)
**Header:**
- Breadcrumb: link "← Biblioteca" (color `#F59845`, cursor pointer) + separador "/" + nombre del grupo (color primary, weight 500).
- Subtítulo: "N proceso(s)" (12px, color tertiary).
- Botón naranja "Importar BPMN" a la derecha. Al hacer click: navega a `/import?groupId=XXXX` para que el proceso importado quede asignado a este grupo automáticamente.
- Al hacer click en "← Biblioteca": vuelve a la vista home.
**Barra de búsqueda:**
- Input con placeholder "Buscar en este cliente...". Filtra los procesos del grupo en tiempo real.
**Lista de process cards:**
- Una card por proceso, apiladas verticalmente.
**Process card:**
- Ícono `ti-git-branch` sobre fondo `#fff3e6` (36×36px, border-radius-md), a la izquierda.
- Columna central: nombre del proceso (13px weight 500, truncado), debajo "Modificado hace X días" (ícono `ti-clock` + texto, 11px, color tertiary), debajo chips de tags (11px, bg secondary, borde tertiary, border-radius 10px).
- Columna derecha (min-width 90px, text-align right): badge de KPI + label.
**KPI badge — tres estados:**
1. **Con escenario automatizado simulado** (hay `resultAutomated` en la última simulación): badge verde — fondo `#f0fdf4`, borde `#86efac`, texto `#15803d`, ícono `ti-trending-up`, valor "USD X.XXX". Label debajo: "Ahorro anual proyectado" (10px, color tertiary).
2. **Simulado solo actual** (hay `result` pero no `resultAutomated`): badge neutro — fondo `background-secondary`, borde tertiary, texto secondary, ícono `ti-chart-bar`, valor "USD X.XXX". Label debajo: "Costo anual actual" (10px, color tertiary). El costo anual = `result.totalCost × annualFrequency` del proceso.
3. **Sin simular**: texto "Sin simular" (11px, italic, color tertiary). Sin badge.
### Empty states
**Home sin grupos ni procesos (primera vez):**
- Ícono grande `ti-building` o similar centrado, texto "Tu biblioteca está vacía" (16px), subtítulo "Importá tu primer BPMN para empezar" (13px, color secondary), botón naranja "Importar BPMN".
**Grupo sin procesos:**
- Ícono `ti-file-off`, texto "No hay procesos en este cliente.", subtítulo "Importá un BPMN para comenzar." (13px, color secondary).
---
## Cambios autorizados y arquitectura
### `src/domain/types.ts`
- Quitar `?` de `groupId` y `tags` en `Process` — campos requeridos.
- `automatedAssignedResources` permanece opcional (`?`) en `Activity` — se vuelve requerido en Etapa 5.
### `src/persistence/repositories.ts`
Agregar al final, siguiendo el patrón existente:
```typescript
// ─── ProcessGroup ─────────────────────────────────────────────────────────────
/** @internal Solo llamar desde src/store/library-store.ts */
export const groupRepo = {
async save(group: ProcessGroup): Promise<void> {
await db.groups.put(group)
},
async getAll(): Promise<ProcessGroup[]> {
return db.groups.orderBy('name').toArray()
},
async getById(id: string): Promise<ProcessGroup | undefined> {
return db.groups.get(id)
},
async delete(id: string): Promise<void> {
// Procesos del grupo pasan a "Sin clasificar" (groupId = null)
await db.transaction('rw', [db.groups, db.processes], async () => {
await db.processes.where('groupId').equals(id).modify({ groupId: null })
await db.groups.delete(id)
})
},
}
// ─── AppSettings ──────────────────────────────────────────────────────────────
/** @internal Solo llamar desde src/store/library-store.ts */
export const settingsRepo = {
async get(): Promise<AppSettings> {
const s = await db.settings.get('singleton')
return s ?? { id: 'singleton', groupLabel: 'Cliente' }
},
async update(updates: Partial<Omit<AppSettings, 'id'>>): Promise<void> {
await db.settings.update('singleton', updates)
},
}
```
Actualizar el import en la cabecera para incluir `ProcessGroup` y `AppSettings`.
### `src/store/library-store.ts` (NUEVO)
Zustand store con la siguiente interfaz:
```typescript
interface LibraryState {
groups: ProcessGroup[]
processes: Process[] // todos los procesos, para búsqueda global
settings: AppSettings
isLoading: boolean
// Inicialización
load: () => Promise<void>
// Grupos
createGroup: (name: string) => Promise<ProcessGroup>
deleteGroup: (id: string) => Promise<void>
// Procesos
assignProcessToGroup: (processId: string, groupId: string | null) => Promise<void>
// Computed helpers (derivados del estado, no acciones async)
getProcessesByGroup: (groupId: string | null) => Process[]
getLatestSimulation: (processId: string) => Promise<Simulation | undefined>
}
```
`load()` carga grupos, todos los procesos, y settings en paralelo.
`getProcessesByGroup(null)` retorna los procesos con `groupId === null` ("Sin clasificar").
`getLatestSimulation` usa `simulationRepo.getLatestByProcess` — agregar `simulationRepo` al import del store.
Respetar la regla arquitectónica: el store importa desde `repositories`, no directamente desde `db`.
### `src/features/library/` (NUEVO directorio)
Crear mínimamente:
- `LibraryPage.tsx` — componente principal, gestiona el estado `view: 'home' | 'group'` y `activeGroupId: string | null` con React state.
- Sub-componentes inline o en archivos separados según criterio del agente (Nivel 1 — decisión libre).
### `src/router.tsx`
```typescript
// Antes:
// indexRoute → path: '/', component: ImportPage
// Después:
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: LibraryPage, // nuevo home
})
const importRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/import',
component: ImportPage, // nueva ruta
})
// Agregar importRoute al routeTree
```
### `src/features/import/ImportPage.tsx`
Dos cambios:
1. Agregar `groupId: null, tags: []` al objeto `Process` que se construye al importar.
2. Leer el search param `groupId` de la URL: si está presente, usarlo en lugar de `null`. Usar `useSearch` de TanStack Router.
```typescript
// Leer groupId del search param (puede no estar presente)
const search = useSearch({ from: '/import' })
const targetGroupId = (search as { groupId?: string }).groupId ?? null
// Al construir el Process:
const process: Process = {
// ... campos existentes ...
groupId: targetGroupId,
tags: [],
}
```
Después de importar con éxito, en lugar de navegar a workspace: navegar de vuelta a `/` si el usuario vino sin groupId, o puede navegar directamente al workspace (comportamiento actual — mantener).
### Archivos de tests con objetos `Process` o `Activity` literales
Buscar todos los archivos bajo `tests/` que construyan objetos `{ id: ..., name: ..., bpmnXml: ... }` como `Process` o similar. Agregar `groupId: null, tags: []` a cada objeto `Process`. No agregar `automatedAssignedResources` todavía (campo sigue siendo opcional).
Este es un scope amplio autorizado: **cualquier archivo de tests que falle compilación TypeScript por `Process.groupId` o `Process.tags` faltantes está autorizado a ser modificado para agregar esos campos con sus defaults**.
---
## ❌ Lo que NO entra en esta etapa
- Renombrar grupos (edición del nombre post-creación) — Sprint 3 o 4
- Configuración de `groupLabel` en UI — Etapa 3
- Tags en procesos (chip input en workspace) — Etapa 3
- Drag & drop de procesos entre grupos — backlog
- Eliminar procesos desde la biblioteca — backlog
- Cambios en `WorkspacePage`, `ReportPage`, `ActivityPanel`, o cualquier feature de workspace
- Cambios en el motor de simulación o en exports PDF/CSV
- Cambios en `process-store.ts` o `simulation-store.ts`
- `Activity.automatedAssignedResources` sigue siendo opcional — no promover a requerido
Si agregar `groupId`/`tags` como requeridos en `types.ts` genera errores TypeScript en archivos fuera de los autorizados: listar los archivos afectados y reportar antes de modificarlos. No resolver silenciosamente. Ver AP.9.
---
## Verificación antes de entregar
```bash
# 1. Build limpio
npm run build
# 2. Tests (todos los previos deben seguir verdes)
npm run test
# 3. Control de scope
git diff --name-only HEAD
```
El diff debe mostrar únicamente archivos de:
- `src/domain/types.ts`
- `src/persistence/repositories.ts`
- `src/store/library-store.ts` (nuevo)
- `src/features/library/` (nuevos)
- `src/router.tsx`
- `src/features/import/ImportPage.tsx`
- `tests/**` (solo los que tienen objetos `Process` literales que necesitan `groupId`/`tags`)
Si aparece cualquier archivo de `src/features/workspace/`, `src/features/report/`, `src/store/process-store.ts`, o `src/lib/export/`: revertir y reportar.
---
## Criterios de aceptación
- [ ] LibraryPage es la home (`/`). ImportPage está en `/import`.
- [ ] Grupos se listan en grid con conteo, nombre y fecha de última modificación.
- [ ] "Nuevo cliente" crea un grupo inline (sin modal): click → input → Enter confirma / Escape cancela.
- [ ] "Sin clasificar" muestra procesos sin grupo. Oculto si no hay ninguno.
- [ ] Navegación home ↔ grupo funciona con breadcrumb.
- [ ] KPI badge en process card muestra: ahorro proyectado (verde), costo actual (neutro), o "Sin simular".
- [ ] Búsqueda en home filtra por nombre de proceso en tiempo real.
- [ ] Filtro por tags en modo AND funciona.
- [ ] "Importar BPMN" desde dentro de un grupo pasa `groupId` a ImportPage: el proceso queda asignado al grupo.
- [ ] `Process.groupId` y `Process.tags` son campos requeridos. Build sin errores TypeScript.
- [ ] Todos los tests previos siguen verdes.
- [ ] Validación visual del director aprobada.
---
## Criterio de wow
Los empty states y las transiciones visuales son la diferencia entre una herramienta que se siente terminada y una que se siente en construcción. Implementar:
- Empty state en home (primera vez sin grupos) con CTA prominente.
- Empty state en grupo vacío con mensaje contextual.
- Transición suave (CSS transition opacity/transform 150ms) al entrar y salir de un grupo.
---
## Reporte esperado al terminar
1. Resultado de `git diff --name-only HEAD` con la lista de archivos modificados.
2. Cantidad de archivos de tests actualizados con defaults `groupId`/`tags`.
3. Resultado de `npm run build` y `npm run test`.
4. Cualquier decisión de iniciativa tomada (Nivel 1/2/3).
5. Si hubo algún archivo fuera del scope que requirió modificación: declararlo explícitamente con justificación.
---
## Referencias
- `sprints/sprint-3/BRIEF.md` — sección 4 (Feature B)
- `src/store/process-store.ts` — patrón de Zustand a seguir
- `src/persistence/repositories.ts` — patrón de repos a seguir
- `src/router.tsx` — estado actual del routing
- `src/features/import/ImportPage.tsx` — construcción actual de objetos Process
- `methodology/ANTI_PATTERNS.md` — AP.9 (restricciones contradictorias)
- `methodology/PATTERNS.md` — C.7 (git diff como control preventivo)