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

16 KiB
Raw Permalink Blame History

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:

// ─── 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:

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

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

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