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

This commit is contained in:
2026-07-07 17:55:19 -03:00
parent 10e477cc45
commit d64078d96a
6 changed files with 117 additions and 284 deletions

View File

@@ -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 14 completas; Etapas 56 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

View File

@@ -1,7 +1,7 @@
{
"name": "process-cost-platform",
"private": true,
"version": "0.0.0",
"version": "0.8.0",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -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 ImportPageya 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.
---

View File

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

View File

@@ -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
View File

@@ -0,0 +1 @@
export const APP_VERSION = '0.8.0'