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>
This commit is contained in:
2026-05-27 11:49:43 -03:00
parent d2746f07d1
commit 67e259e6ed
57 changed files with 4372 additions and 82 deletions

View File

@@ -1,4 +1,4 @@
import { useCallback, useMemo, useRef, useState } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useParams, useNavigate } from '@tanstack/react-router'
import { AlertTriangle, Home, Info } from 'lucide-react'
import { Button } from '@/components/ui/button'
@@ -27,6 +27,8 @@ import { TransparencySection } from './TransparencySection'
import { AutomationScenarioNote } from './AutomationScenarioNote'
import { MethodologyFooter } from './MethodologyFooter'
import type { HeatmapMode } from '@/lib/colors'
import { useSimulationStore } from '@/store/simulation-store'
import { AppHeader } from '@/components/AppHeader'
export function ReportPage() {
const { processId } = useParams({ from: '/report/$processId' })
@@ -35,6 +37,12 @@ export function ReportPage() {
const { toast } = useToast()
const [heatmapMode, setHeatmapMode] = useState<HeatmapMode>('relative')
const { lastPersistedExecutedAt, loadLatestForProcess } = useSimulationStore()
// Cargar executedAt persistido si el usuario llega directo al reporte (sin pasar por workspace)
useEffect(() => {
if (process) loadLatestForProcess(process.id)
}, [process?.id, loadLatestForProcess])
// Set de bpmnElementIds marcados como automatizables — debe estar antes de los early returns
const automatableIds = useMemo(
@@ -56,6 +64,14 @@ export function ReportPage() {
})
}, [simulation, process])
// Breakdown del escenario actual, indexado por activityId, para comparación lado a lado en tab Automatizado
const actualBreakdownById = useMemo(() => {
if (!simulation?.result) return undefined
return Object.fromEntries(
simulation.result.perActivity.map((a) => [a.activityId, a.resourceCostBreakdown])
)
}, [simulation?.result])
// Estado y refs para el tab "Actual"
const [selectedIdActual, setSelectedIdActual] = useState<string | null>(null)
const heatmapRef = useRef<HeatmapCanvasHandle>(null)
@@ -219,9 +235,29 @@ export function ReportPage() {
// marcadas como automatizables. Sin automatizables, los tabs muestran datos idénticos al actual.
const hasAutomatedScenario = hasAutomatedResult && automatableIds.size > 0
const isStale = Boolean(process && process.updatedAt > (lastPersistedExecutedAt ?? 0))
return (
<TooltipProvider>
<div className="min-h-screen bg-slate-50">
<AppHeader />
{/* Banner stale — cambios sin simular */}
{isStale && (
<div className="flex items-center justify-between px-6 py-2.5 text-[12px] font-medium"
style={{ background: '#fffbeb', borderBottom: '1px solid #f59e0b', color: '#92400e' }}>
<div className="flex items-center gap-2">
<AlertTriangle className="h-3.5 w-3.5 shrink-0" />
Los resultados pueden estar desactualizados hay cambios sin simular.
</div>
<button
onClick={() => navigate({ to: '/workspace/$processId', params: { processId } })}
className="underline hover:no-underline shrink-0 ml-4"
>
Ir al workspace
</button>
</div>
)}
<div className="max-w-screen-xl mx-auto px-6 py-10">
{/* ── Header (fuera de los tabs: aplica a todos los escenarios) ──── */}
@@ -449,6 +485,7 @@ export function ReportPage() {
</div>
<ActivitiesTable
perActivity={resultAutomated!.perActivity}
pairedBreakdown={actualBreakdownById}
mode={heatmapMode}
currency={process.currency}
highlightedId={selectedIdAuto}