Etapa 0 aprobada condicionalmente. Queda pendiente tu validación visual: abrí /library en https://inq-roi.inqualityhq.com y confirmá que el footer muestra InQ ROI · Powered by InQuality · v0.8.0 · Hecho desde Paraguay 🇵🇾.
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
Some checks failed
/ Deploy to Cloudflare Pages (push) Has been cancelled
This commit is contained in:
355
CHANGELOG.md
355
CHANGELOG.md
@@ -1,262 +1,97 @@
|
||||
# Changelog
|
||||
|
||||
Historial de cambios del proyecto. Formato basado en [Keep a Changelog](https://keepachangelog.com/es/).
|
||||
Los cambios se describen desde la perspectiva del código: qué archivos cambiaron,
|
||||
por qué, y qué decisiones de diseño están implícitas — para que futuras
|
||||
modificaciones profundas tengan contexto suficiente.
|
||||
|
||||
---
|
||||
|
||||
## [Unreleased] — Sprint 1: ROI y Escenarios de Automatización
|
||||
|
||||
### Estado
|
||||
En construcción. Etapas 1–4 completas; Etapas 5–6 pendientes.
|
||||
|
||||
### Etapa 1 — Cimientos del dominio (completada)
|
||||
|
||||
**Nuevos campos en el modelo de dominio** (`src/domain/types.ts`):
|
||||
|
||||
- `Activity` gana: `automatable: boolean`, `automatedCostFixed: number`, `automatedTimeMinutes: number`
|
||||
- `Process` gana: `annualFrequency: number`, `analysisHorizonYears: number`, `automationInvestment: number`
|
||||
- Nuevo tipo `Scenario = 'actual' | 'automated'`
|
||||
- `SimulationInput` acepta `scenario?: Scenario` (opcional, default `'actual'` para retrocompatibilidad)
|
||||
- `Simulation` gana `resultAutomated?: SimulationResult` (opcional, para retrocompatibilidad con snapshots viejos)
|
||||
|
||||
**Migración Dexie v1 → v2** (`src/persistence/db.ts`):
|
||||
|
||||
- `version(2).upgrade()` popula defaults en registros existentes:
|
||||
- Activities: `automatable=false`, `automatedCostFixed=0`, `automatedTimeMinutes=0`
|
||||
- Processes: `annualFrequency=1000`, `analysisHorizonYears=3`, `automationInvestment=0`
|
||||
- Sin cambio de índices — solo nuevos campos con defaults seguros.
|
||||
|
||||
**Nuevo módulo de ROI** (`src/domain/roi.ts`):
|
||||
|
||||
- Función pura `calculateRoi(input: RoiInput): RoiResult`
|
||||
- Fórmulas nominales simples (sin VPN ni TIR — decisión de diseño ADR-014)
|
||||
- Edge cases explicitados: inversión cero, ahorro negativo, costo actual cero (evitar div/0)
|
||||
- `paybackMonths = Infinity` cuando no hay ahorro, `roiAnnualPercent = Infinity` cuando inversión=0 y ahorro>0
|
||||
|
||||
**Motor de simulación dual** (`src/domain/simulation.ts`):
|
||||
|
||||
- Acepta `scenario` en `SimulationInput`
|
||||
- En `'automated'`: actividades con `automatable=true` usan `automatedCostFixed`/`automatedTimeMinutes`
|
||||
- Las no-automatable mantienen sus costos originales en ambos escenarios
|
||||
- La probabilidad de ejecución NO cambia entre escenarios — solo costos y tiempos
|
||||
|
||||
**Simulation store refactorizado** (`src/store/simulation-store.ts`):
|
||||
|
||||
- `result` (single) → `resultActual + resultAutomated + lastSimulatedAt`
|
||||
- Nueva acción `persistResults(processId, actual, automated)`: guarda en Dexie + actualiza memoria en un solo paso atómico
|
||||
- Nueva acción `invalidateResults()`: pone ambos a null
|
||||
- Suscripción reactiva a `process-store`: cualquier cambio en `activities` o `currentProcess` invalida ambos resultados automáticamente
|
||||
- **Dependencia intencional documentada**: `simulation-store` importa `process-store` en una dirección (sin inversa → sin ciclo)
|
||||
|
||||
**useSimulate refactorizado** (`src/features/simulation/useSimulate.ts`):
|
||||
|
||||
- Corre las dos simulaciones en el mismo ciclo y llama `persistResults` del store (eliminando el import directo de `simulationRepo` desde un hook de feature)
|
||||
- Retorna `{ resultActual, resultAutomated }` en lugar de un único result
|
||||
|
||||
**Infraestructura ESLint** (`eslint.config.js` — archivo nuevo):
|
||||
|
||||
- Primer config de ESLint del proyecto (antes no existía)
|
||||
- Regla `no-restricted-imports`: prohíbe importar `persistence/repositories` desde fuera de `store/`
|
||||
- Overrides para `store/**`, `features/import/**`, `features/report/useReportData.ts`, `tests/**`
|
||||
- Regla `react-hooks/set-state-in-effect: 'off'`: patrón de sync local state con useEffect es válido en este codebase
|
||||
- **Por qué importa**: si alguien llama `activityRepo.save()` directamente desde una feature sin pasar por el store, los resultados de simulación quedan stale (la suscripción reactiva no dispara)
|
||||
|
||||
**Tests nuevos** (32 tests):
|
||||
|
||||
- `tests/domain/roi.test.ts`: 16 casos cubriendo happy path, inversión cero, ahorro negativo, NaN
|
||||
- `tests/integration/dexie-migration.test.ts`: 6 casos de persistencia de los nuevos campos con valores default y no-default
|
||||
- `tests/store/simulation-store-invalidation.test.ts`: 7 casos de la invariante de invalidación dual
|
||||
- `tests/integration/sprint1-flow.test.ts`: 11 casos del flujo integrado BPMN → sim actual → sim automated → calculateRoi → KPIs
|
||||
- `tests/domain/simulation.test.ts`: +4 casos de `scenario=automated` con costos en cero, sin NaN, probabilidad invariante
|
||||
- `tests/integration/bpmn-collaboration.test.ts`: 3 casos para BPMNs de colaboración con múltiples pools
|
||||
|
||||
**Archivos de test actualizados** (fixtures con nuevos campos):
|
||||
|
||||
- `tests/integration/simulation-engine.test.ts`
|
||||
- `tests/integration/gateway-validation.test.ts`
|
||||
- `tests/integration/indexeddb-persistence.test.ts`
|
||||
- `tests/features/report/components.test.tsx`
|
||||
- `tests/features/report/derivations.test.ts`
|
||||
- `tests/lib/export/measure-sizes.test.ts`
|
||||
- `tests/lib/export/csv-export.test.ts`
|
||||
- `tests/lib/export/pdf-export.test.ts`
|
||||
- `tests/setup.ts`: mocks globales `ResizeObserver` y `PointerEvent` para jsdom (necesario desde Sprint 1 para tests con Slider de Radix)
|
||||
|
||||
### Etapa 2 — UI de Configuración (completada)
|
||||
|
||||
**Switch component** (`src/components/ui/switch.tsx` — nuevo):
|
||||
|
||||
- Implementación Tailwind pura sin `@radix-ui/react-switch`
|
||||
- `role="switch"` + `aria-checked` + `aria-label` para accesibilidad
|
||||
- Transiciones CSS: `transition-colors duration-200` en el fondo, `transition-transform duration-200` en el knob
|
||||
- **Decisión**: no instalar `@radix-ui/react-switch` para no agregar dependencia — el comportamiento visual es idéntico
|
||||
|
||||
**ActivityPanel ampliado** (`src/features/workspace/ActivityPanel.tsx`):
|
||||
|
||||
- Nueva sección "Automatización" al pie del panel, separada con `<Separator>`
|
||||
- Toggle Switch (`automatable`) oculta/muestra los campos con `transition-all duration-200 overflow-hidden max-h-{0|80}`
|
||||
- **Limitación conocida**: el max-height trick no anima a la altura real del contenido. Ver TODO.md para mejora con `useRef + scrollHeight`
|
||||
- Inputs numéricos `automatedCostFixed` y `automatedTimeMinutes` con mismo patrón que los existentes (placeholder cuando valor=0)
|
||||
- Helper ámbar de transparencia metodológica:
|
||||
- Aparece cuando `automatable=true && cost===0 && time===0`
|
||||
- **Siempre está en el DOM** (no conditional render) — la visibilidad se controla con `opacity-0/100 + max-h-0/20 + transition-all duration-150`
|
||||
- `aria-hidden` refleja el estado de visibilidad
|
||||
- **Por qué importa**: con conditional render (`&&`) el helper desaparece instantáneamente al tipear — sin transición. Al mantenerlo en el DOM, el fade de 150ms se aplica correctamente
|
||||
|
||||
**GlobalSettingsPanel ampliado** (`src/features/workspace/GlobalSettingsPanel.tsx`):
|
||||
|
||||
- Nueva sección "Análisis de Impacto" al pie del panel
|
||||
- `annualFrequency`: input numérico, min=1, sugerencias de referencia en texto helper
|
||||
- `analysisHorizonYears`: Slider de 1 a 10 años con valor en texto ("3 años")
|
||||
- `automationInvestment`: input numérico, default placeholder vacío cuando valor=0
|
||||
- La validación de rango de `analysisHorizonYears` (clamp 1-10) ocurre en `handleSave`, no en `onChange`
|
||||
|
||||
**BpmnCanvas ampliado** (`src/features/workspace/BpmnCanvas.tsx`):
|
||||
|
||||
- Nueva prop `automatableElementIds?: string[]`
|
||||
- `syncAutomationOverlays()`: callback memoizado con `useCallback([automatableElementIds])`
|
||||
- Limpia overlays anteriores con `overlays.remove({ type: 'automation-indicator' })`
|
||||
- Re-agrega un badge por cada elementId en la lista
|
||||
- Se llama después de `importXML` (en el `.then()`) y en un `useEffect` separado cuando cambia la lista
|
||||
- **Posicionamiento del badge**: `{ top: -8, left: element.width - 12 }`
|
||||
- Obtenido via `elementRegistry.get(elementId)` con fallback `width ?? 100`
|
||||
- **No usar** `position.right` de diagram-js: con `right: -8`, la fórmula interna `left = right * -1 + width` da `left = 8 + width`, colocando el badge enteramente fuera del nodo sin overlap de esquina
|
||||
- Con `left = width - 12`: badge left está 12px antes del borde derecho, su borde derecho queda 8px fuera → overlap visible en la esquina
|
||||
- **Badge HTML**: círculo 20×20px, fondo `#fffbeb` (amber-50), borde `#e2e8f0`, sombra `0 1px 3px rgba(0,0,0,0.12)`, ícono Bot Lucide en `#f59e0b` como SVG inline
|
||||
- `pointer-events: none` para no interferir con clicks en el nodo
|
||||
- `title` nativo del browser como tooltip
|
||||
- **Por qué `#fffbeb` y no `#ffffff`**: el fondo de los nodos BPMN también es blanco, el fondo ámbar le da identidad cromática propia y hace visible el círculo por contraste
|
||||
|
||||
**WorkspacePage** (`src/features/workspace/WorkspacePage.tsx`):
|
||||
|
||||
- Destructura `activities` del process-store
|
||||
- Calcula `automatableElementIds` con `useMemo`
|
||||
- Pasa `useDeferredValue(automatableElementIds)` al BpmnCanvas (evita cascada de re-renders al editar el panel)
|
||||
|
||||
**AutomationScenarioNote** (`src/features/report/AutomationScenarioNote.tsx` — nuevo):
|
||||
|
||||
- Componente de leyenda para el tab "Automatizado" del reporte
|
||||
- Preparado y listo para ser colocado por Etapa 3 — no tiene lógica propia
|
||||
|
||||
### Etapa 3 — Motor dual y reporte con tabs (completada)
|
||||
|
||||
**ReportPage ampliada** (`src/features/report/ReportPage.tsx`):
|
||||
|
||||
- La página ahora tiene 3 tabs: **Actual** | **Automatizado** | **Comparación + ROI**
|
||||
- El contenido existente (KpiBar, heatmap, 3 gráficos, tabla de actividades) pasa intacto al tab "Actual"
|
||||
- Los tabs "Automatizado" y "ROI" se deshabilitan si la simulación no tiene `resultAutomated` (snapshot viejo) — en ese caso se muestra `NeedsResimulate` que guía al usuario a re-simular
|
||||
- `useMemo` para `automatableIds` y `roi` se declaran antes de los early returns (cumple reglas de hooks)
|
||||
- Los refs de heatmap son independientes por tab (`heatmapRef` / `heatmapRefAuto`) — el tab "Actual" sigue siendo el fuente de imagen para el export PDF
|
||||
- Export PDF/CSV sin cambios — Etapa 4 los ampliará
|
||||
|
||||
**Tab "Automatizado"**:
|
||||
|
||||
- Mismo layout que "Actual": KpiBar → heatmap con cross-linking → 3 gráficos → tabla
|
||||
- Usa `simulation.resultAutomated` como fuente de datos
|
||||
- `AutomationScenarioNote` aparece al pie del heatmap (componente preparado en Etapa 2)
|
||||
- Heatmap y tabla tienen su propio estado `selectedIdAuto` — cross-linking funciona independientemente del tab "Actual"
|
||||
|
||||
**Tab "Comparación + ROI"**:
|
||||
|
||||
- `RoiKpiBar` (`src/features/report/RoiKpiBar.tsx` — nuevo): 5 KPI cards en grid 2×5 (lg)
|
||||
- Ahorro por ejecución (con % sobre costo actual)
|
||||
- Ahorro anual
|
||||
- Payback (meses o años según magnitud; "Inmediato" si inversión=0; "No se recupera" si Infinity)
|
||||
- Ahorro acumulado neto a N años
|
||||
- ROI anual % ("∞%" si inversión=0 y ahorro>0)
|
||||
- Los valores positivos se muestran en `text-emerald-700`, negativos en `text-red-600`
|
||||
- `ComparisonTable` (`src/features/report/ComparisonTable.tsx` — nuevo):
|
||||
- Tabla actividad por actividad: costo actual | costo automatizado | ahorro | % ahorro
|
||||
- Ícono Bot ámbar en la primera columna para actividades marcadas `automatable`
|
||||
- Ordenable por ahorro (↕) — default descendente
|
||||
- Usa `Set<bpmnElementId>` para el lookup de `automatable` en O(1)
|
||||
- `CumulativeSavingsChart` (`src/features/report/CumulativeSavingsChart.tsx` — nuevo):
|
||||
- Gráfico de línea + área (ECharts): X=años 0..N, Y=ahorro neto acumulado
|
||||
- Línea de break-even en y=0 siempre visible
|
||||
- Marker vertical dashed verde en `x=paybackYears` (solo si finite y dentro del horizonte)
|
||||
- Tooltip muestra valor del año exacto con color según signo
|
||||
- `TopSavingsChart` (`src/features/report/TopSavingsChart.tsx` — nuevo):
|
||||
- Top 5 actividades por ahorro positivo — barras horizontales verdes
|
||||
- Excluye actividades con ahorro ≤ 0
|
||||
- Altura dinámica: `max(180, n * 48)` px — no hay espacio vacío si hay menos de 5 actividades
|
||||
- `TransparencySection` (`src/features/report/TransparencySection.tsx` — nuevo):
|
||||
- Condicional — solo se renderiza si hay issues metodológicos detectados
|
||||
- **Issue 1**: actividades con `automatable=true` pero `automatedCostFixed=0` y `automatedTimeMinutes=0` → el ahorro puede estar inflado. Lista los nombres de cada actividad
|
||||
- **Issue 2**: `automationInvestment=0` → payback "inmediato" e ROI "infinito" no reflejan la realidad
|
||||
- Fondo amber-50, borde amber-200 — consistente con el helper del ActivityPanel
|
||||
|
||||
**Por qué ROI inline y no en `useReportData`**:
|
||||
El `calculateRoi` toma `simulation.result.totalCost` y `simulation.resultAutomated.totalCost` — valores ya disponibles en el componente. Moverlo al hook agregaría un `useEffect` extra sin beneficio. Se mantiene en el componente como `useMemo`.
|
||||
|
||||
**Tests nuevos** (17 tests):
|
||||
|
||||
- `tests/features/workspace/ActivityPanel.test.tsx`: 6 casos — toggle, helper ámbar con `aria-hidden`, dispatch de `updateActivity`
|
||||
- `tests/features/workspace/GlobalSettingsPanel.test.tsx`: 7 casos — defaults, edición de campos, dispatch de `updateProcess`
|
||||
- `tests/features/workspace/BpmnCanvas.test.tsx`: 4 casos — overlay add/remove, posición `left=width-12`, HTML con `#fffbeb` y `#f59e0b`
|
||||
|
||||
**Patrón de mocks para tests de componentes** (documentado para referencia futura):
|
||||
|
||||
- Los mocks del store con `vi.hoisted()` devuelven referencias estables
|
||||
- Sin `vi.hoisted`: cada llamada a `useProcessStore()` devuelve un nuevo objeto → el `useEffect([activities])` de ActivityPanel dispara en loop infinito → "Maximum update depth exceeded"
|
||||
- `simulation-store.ts` importa `process-store.ts` al nivel de módulo: los mocks deben ser compatibles o el store no carga
|
||||
|
||||
---
|
||||
|
||||
## [0.1.0-mvp] — MVP Fase 0 — 2026-05-13
|
||||
|
||||
### Contexto
|
||||
|
||||
Primera entrega funcional. Construida en una sesión de trabajo.
|
||||
Objetivo: demostrar en ≤15 minutos que se puede transformar un BPMN en un reporte de costos.
|
||||
|
||||
### Funcionalidades
|
||||
|
||||
- Importar BPMN 2.0 por drag & drop o desde procesos de ejemplo
|
||||
- Renderizar diagrama en canvas read-only (bpmn-js Viewer)
|
||||
- Configurar actividades: costo fijo, tiempo, recursos asignados con utilización
|
||||
- CRUD de recursos (rol/persona/sistema/equipo/insumo + costo/hora)
|
||||
- Configuración global: moneda, overhead %, nombre proceso, cliente
|
||||
- Motor de simulación determinístico con propagación de probabilidades por gateways XOR, AND, inclusivo
|
||||
- Detección de loops (back-edges via DFS): se advierte pero no bloquea
|
||||
- Reporte visual: mapa de calor en dos modos (relativo/absoluto), KPIs, top actividades, composición, costo por recurso
|
||||
- Export PDF: 3 páginas (header+KPIs+heatmap, análisis, tabla detalle), con jsPDF-autotable
|
||||
- Export CSV: con BOM UTF-8 y metadatos del proceso
|
||||
- Persistencia en IndexedDB (Dexie v1)
|
||||
- Deploy estático en Cloudflare Pages
|
||||
|
||||
### Decisiones de diseño relevantes para el futuro
|
||||
|
||||
**Parser BPMN multi-pool** (`bpmn-parser.ts`):
|
||||
|
||||
- Itera todos los `<bpmn:process>` hijos directos de `<definitions>` (no solo el primero)
|
||||
- Fix crítico: el parser original usaba `querySelector` que retorna solo el primero → todos los pools de una colaboración excepto el primero eran ignorados
|
||||
- Pools de soporte (SAP, bandeja manual) pueden no tener StartEvent propio — la validación exige al menos uno entre todos los pools
|
||||
|
||||
**Heatmap sobre bpmn-js** (`HeatmapCanvas.tsx`):
|
||||
|
||||
- bpmn-js rechaza la promesa de `importXML` pero el diagrama se renderiza parcialmente → usar `eventBus.on('import.done', ...)` en lugar de `.then()`
|
||||
- `element.style.fill = color` (NO `setAttribute('fill', color)`): bpmn-js aplica CSS que tiene mayor especificidad que atributos SVG
|
||||
- `captureImage()` espera 500ms después de importar para que la transición CSS `fill 0.35s ease` complete antes de la captura con html2canvas
|
||||
|
||||
**Validación XOR en WorkspacePage** (`WorkspacePage.tsx`):
|
||||
|
||||
- `hasLoadedOnce` ref: evita que el redirect "proceso no encontrado" dispare en el render inicial donde `isLoading=false` y `currentProcess=null` (antes del primer efecto de carga)
|
||||
|
||||
### Tests
|
||||
|
||||
268 tests unitarios + integración al cierre de MVP.
|
||||
5 tests E2E con Playwright (import → simulate → export PDF/CSV × 3 BPMNs + acentos + gradiente de color).
|
||||
El test de gradiente verifica con `sharp` que el heatmap del PDF contiene píxeles verdes, ámbar y rojos — evita regresiones en la lógica de coloreo.
|
||||
|
||||
---
|
||||
|
||||
## Convenciones de este changelog
|
||||
|
||||
- **Decisiones de diseño**: se documenta el *por qué*, no solo el *qué*
|
||||
- **Gotchas**: comportamientos contrarios a la intuición se marcan explícitamente
|
||||
- **Edge cases**: si un edge case motivó un test específico, se menciona
|
||||
- **Retrocompatibilidad**: cuando un campo es opcional por retrocompat, se indica
|
||||
- Las entradas son para humanos que modificarán el código, no para usuarios finales
|
||||
All notable changes to InQ ROI will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.8.0] - 2026-07-07
|
||||
### Added
|
||||
- CHANGELOG.md con historial de versiones
|
||||
- AppFooter con versión visible en todas las páginas
|
||||
- Conventional Commits adoptados como estándar de commits
|
||||
|
||||
## [0.7.0] - 2026-07-07
|
||||
### Added
|
||||
- Multi-tenancy: tabla `organizations`, 4 roles de plataforma (platform_admin, member, client_editor, client_viewer)
|
||||
- Edge Functions `invite-user` y `revoke-user` para gestión de acceso
|
||||
- Panel de administración `/admin` con gestión de Organizations y Users
|
||||
- Login email/password y página `/reset-password` para flujo de invitación
|
||||
- Back button contextual en workspace (muestra nombre del cliente via JOIN en getById)
|
||||
- Política RLS `simulations_update` para que client_editor pueda guardar simulaciones
|
||||
|
||||
### Fixed
|
||||
- Race condition en AuthContext: `isProfileLoaded` como guard antes de renderizar contenido rol-dependiente
|
||||
- `syncProfileFromDB` ahora lee `platform_role` y `org_id` desde DB (no inferido por dominio de email)
|
||||
- `normalizeGroupLabel()`: limpia artículos iniciales de labels de grupos con datos corruptos
|
||||
|
||||
## [0.6.0] - 2026-06-24
|
||||
### Added
|
||||
- Logo InQuality en header de PDFs (isotipo, páginas 2+, fallback a texto)
|
||||
- Panel de análisis de sensibilidad en tab ROI: sliders de volumen e inversión, recálculo en tiempo real
|
||||
- Doble línea en CumulativeSavingsChart (base gris dashed + ajustada naranja con área sombreada)
|
||||
- `whitespace-nowrap` en MetricCell de columna Eficiencia
|
||||
|
||||
### Fixed
|
||||
- `ProfileSheet.getAccessToken()` leía clave inexistente (`'supabase.auth.token'`); el guardado de perfil nunca llegaba a Supabase desde Sprint 4. Fix: calcular `sb-${projectRef}-auth-token`
|
||||
|
||||
## [0.5.0] - 2026-06-23
|
||||
### Added
|
||||
- Fix crítico: workspace loading — race condition auth/processId resuelto con `isProfileLoaded`
|
||||
- Controles de zoom BPMN: fit-viewport con centering correcto, botones zoom-in/zoom-out/fit
|
||||
- Heatmap con gradiente continuo de 4 stops + toggle percentile/min-max persistido
|
||||
- "Ver procesos por recurso" en catálogo de recursos
|
||||
- Campo "empresa" en perfil de usuario
|
||||
- Columna "Eficiencia de automatización" en ComparisonTable (+ columna "Ahorro esperado")
|
||||
|
||||
### Fixed
|
||||
- Flakiness en botón "Guardar cambios": debounce + Web Locks en process-store
|
||||
- Specs E2E migradas de Dexie a Supabase
|
||||
|
||||
## [0.4.0] - 2026-06-18
|
||||
### Added
|
||||
- Autenticación Google Workspace OAuth + Supabase
|
||||
- Migración completa IndexedDB (Dexie) → PostgreSQL (Supabase)
|
||||
- Row Level Security con `is_platform_admin()` y políticas granulares
|
||||
- Catálogo global de recursos (sin processId)
|
||||
- Identidad en UI: avatar/dropdown AppHeader, "Actualizado por" en proceso
|
||||
- ResourceCatalogPage + ResourceFormSheet
|
||||
- Fix Web Locks API deadlock (processLock)
|
||||
- Fix `.maybeSingle()` en simulation-repo
|
||||
- Fix `formatCostPerHour()` precisión dinámica
|
||||
- Fix Google OAuth `prompt: 'select_account'`
|
||||
- Catálogo con contexto de proceso (banner + query param fromProcess)
|
||||
|
||||
## [0.3.0] - 2026-05-27
|
||||
### Added
|
||||
- Biblioteca de procesos con grupos/tags/búsqueda (Dexie v4)
|
||||
- Indicador de simulación desactualizada ("Hay cambios sin simular")
|
||||
- Zoom BPMN fit-viewport
|
||||
- Recursos TO-BE en escenario automatizado (motor + UI + reporte + PDF)
|
||||
- Branding InQ ROI en todas las páginas (AppHeader + favicon + logos)
|
||||
- Kit de socialización del equipo: guía de usuario, guía de pre-venta, presentación PPTX
|
||||
|
||||
## [0.2.0] - 2026-05-20
|
||||
### Added
|
||||
- Modelo de recursos `utilizationMinutes + units` (migración Dexie v3)
|
||||
- ActivityPanel con cálculo de costo en tiempo real
|
||||
- Reporte web con filas expandibles de desglose de recursos
|
||||
- Edición inline de proceso/cliente
|
||||
- Tabla de recursos en PDF (página condicional)
|
||||
|
||||
## [0.1.5] - 2026-05-18
|
||||
### Added
|
||||
- Identidad visual completa: paleta naranja InQ #F59845, gradiente → magenta #ED3E8F
|
||||
- PDFs de clase profesional: 3 documentos (Actual, Automatizado, ROI)
|
||||
- Badge ⚡ en canvas BPMN para actividades automatizables
|
||||
- ROI con 5 KPIs
|
||||
- Sistema de componentes establecido
|
||||
|
||||
## [0.1.0] - 2026-05-01
|
||||
### Added
|
||||
- MVP: importar BPMN 2.0, parametrizar actividades, simular costos, exportar PDF
|
||||
- Motor de simulación AS-IS / TO-BE (Modelo A)
|
||||
- Heatmap de costos en canvas BPMN
|
||||
- Reporte web básico con tabla de actividades y gráfico de barras
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "process-cost-platform",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "0.8.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -47,40 +47,34 @@ export const APP_VERSION = '0.8.0'
|
||||
|
||||
Si Vite ya expone `import.meta.env.VITE_APP_VERSION` o similar, usar ese mecanismo. Si no, usar la constante string — es más simple y suficiente para el caso de uso.
|
||||
|
||||
### 3. Crear `src/components/AppFooter.tsx`
|
||||
### 3. Actualizar `src/components/AppFooter.tsx`
|
||||
|
||||
Componente discreto, una sola línea, esquina inferior. Visible en todas las páginas.
|
||||
|
||||
**Diseño:**
|
||||
- Texto: `InQ ROI v0.8.0 · Powered by InQuality`
|
||||
- Tipografía: `text-xs text-muted-foreground`
|
||||
- Alineación: centrado o derecha, `py-4`
|
||||
- Sin borde superior ni decoración adicional
|
||||
El componente **ya existe** con versión hardcodeada `v0.1.0`. Solo cambiar la versión hardcodeada por la constante de `version.ts`. Mantener el resto del texto y estilos exactamente igual.
|
||||
|
||||
```tsx
|
||||
import { APP_VERSION } from '@/lib/version'
|
||||
|
||||
export function AppFooter() {
|
||||
return (
|
||||
<footer className="w-full py-4 text-center">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
InQ ROI v{APP_VERSION} · Powered by InQuality
|
||||
<footer className="mt-12 py-4 border-t border-slate-100 text-center">
|
||||
<p className="text-xs text-slate-400">
|
||||
InQ ROI · Powered by InQuality · v{APP_VERSION} · Hecho desde Paraguay 🇵🇾
|
||||
</p>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Integrar `AppFooter` en las páginas principales
|
||||
**NO cambiar** el texto, los estilos, ni el formato — solo reemplazar `v0.1.0` por `v{APP_VERSION}`.
|
||||
|
||||
Agregar `<AppFooter />` al final del layout o directamente en las páginas indicadas. Mínimo obligatorio:
|
||||
### 4. Agregar `AppFooter` a LibraryPage
|
||||
|
||||
- `LibraryPage.tsx` — antes del cierre del contenedor principal
|
||||
- `ReportPage.tsx` — antes del cierre del contenedor principal
|
||||
El componente ya está integrado en `ReportPage.tsx` e `ImportPage.tsx`. Falta en LibraryPage.
|
||||
|
||||
Si existe un layout compartido que envuelva todas las páginas (verificar en `router.tsx` o `App.tsx`), preferir ponerlo ahí una sola vez. Si no existe layout compartido, agregar en las dos páginas listadas.
|
||||
- `LibraryPage.tsx` — importar `AppFooter` y agregarlo antes del cierre del contenedor principal
|
||||
|
||||
**NO modificar** el header `AppHeader.tsx` — la versión va en el footer, no en el header.
|
||||
**NO tocar** ReportPage ni ImportPage — ya tienen `<AppFooter />`.
|
||||
**NO modificar** `AppHeader.tsx`.
|
||||
|
||||
### 5. Crear `CHANGELOG.md`
|
||||
|
||||
@@ -233,18 +227,17 @@ Verificar específicamente que diga:
|
||||
|
||||
```
|
||||
simulador-web/
|
||||
├── package.json ← actualizar "version"
|
||||
├── package.json ← actualizar "version" a "0.8.0"
|
||||
├── CHANGELOG.md ← crear
|
||||
├── src/
|
||||
│ ├── lib/
|
||||
│ │ └── version.ts ← crear
|
||||
│ └── components/
|
||||
│ └── AppFooter.tsx ← crear
|
||||
├── src/features/library/LibraryPage.tsx ← agregar <AppFooter /> al final
|
||||
├── src/features/report/ReportPage.tsx ← agregar <AppFooter /> al final
|
||||
│ └── AppFooter.tsx ← actualizar (solo versión dinámica)
|
||||
└── src/features/library/LibraryPage.tsx ← agregar <AppFooter /> al final
|
||||
```
|
||||
|
||||
Si existe un layout global donde sea más correcto poner `AppFooter`, se puede usar en lugar de modificar LibraryPage y ReportPage — pero reportar la decisión antes de ejecutar (Nivel 2).
|
||||
**No hay otros archivos para tocar en esta etapa.** ReportPage e ImportPage ya tienen `<AppFooter />` y no se modifican.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { APP_VERSION } from '@/lib/version'
|
||||
|
||||
export function AppFooter() {
|
||||
return (
|
||||
<footer className="mt-12 py-4 border-t border-slate-100 text-center">
|
||||
<p className="text-xs text-slate-400">
|
||||
InQ ROI · Powered by InQuality · v0.1.0 · Hecho desde Paraguay 🇵🇾
|
||||
InQ ROI · Powered by InQuality · v{APP_VERSION} · Hecho desde Paraguay 🇵🇾
|
||||
</p>
|
||||
</footer>
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ import { supabaseProcessRepo, type ProcessWithUserNames } from '@/persistence/su
|
||||
import { formatCurrency, formatSimulationTimestamp } from '@/lib/format'
|
||||
import type { Process, ProcessGroup, Simulation } from '@/domain/types'
|
||||
import { AppHeader } from '@/components/AppHeader'
|
||||
import { AppFooter } from '@/components/AppFooter'
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -932,6 +933,7 @@ export function LibraryPage() {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<AppFooter />
|
||||
</div>
|
||||
|
||||
<Toast message={toastMessage} />
|
||||
|
||||
1
src/lib/version.ts
Normal file
1
src/lib/version.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const APP_VERSION = '0.8.0'
|
||||
Reference in New Issue
Block a user