# Sunbright FP&A Platform — Roadmap

## Fase 1 — MVP (QuickBooks Integration + Dashboard Funcional)

### 1.1 Setup del proyecto ✅
- [x] Inicializar proyecto PHP con Composer (Slim 4, phpdotenv, Guzzle, PHP-DI)
- [x] Configurar MySQL: base de datos `sunbright_fpa`, 8 tablas + seed data
- [x] Estructura de carpetas completa (src/, templates/, public/, migrations/)
- [x] `.env` con config para MAMP (Apache :80, MySQL 127.0.0.1:3306)
- [x] MAMP Document Root → `public/`, URL base: `http://localhost/`
- [x] Dashboard sirviendo desde Slim con templates PHP

### 1.2 QuickBooks OAuth 2.0 ✅
- [x] Flujo OAuth completo: `ConnectController` genérico (autorización → callback → almacenar tokens)
- [x] Soporte multi-entidad (1 empresa conectada, escalable a 5)
- [x] Auto-refresh de access tokens via `Qbo` config class
- [x] Página `/connect` para gestionar conexiones por empresa
- [x] Credenciales QBO sandbox configuradas y probadas
- [x] Sunbright Solar conectada (realm 9341456838451121)

### 1.3 Sync de reportes financieros ✅
- [x] `QboSyncService`: sync selectivo — usuario elige reportes, empresas, período
- [x] Página `/sync` con UI para seleccionar reportes (24 tipos en catálogo)
- [x] P&L, Balance Sheet, TransactionList sincronizados
- [x] `POST /api/sync/trigger` acepta `{companies[], reports[], startDate, endDate}`
- [x] `POST /api/sync/refresh` — re-sync solo reportes en cache (botón navbar ↻)
- [x] `GET /api/sync/status` con window function (ROW_NUMBER) optimizada
- [x] Single-pass P&L parser, JSON validation, amount sanitization
- [x] `ReportCatalog` — definiciones de reportes en DB, preloadAll()

### 1.4 Dashboard y visor de reportes ✅
- [x] KPIs reales de QBO (Revenue, Net Income, Margin, Cash)
- [x] Hub de reportes (`/reports`) — tarjetas de reportes sincronizados
- [x] Visor genérico `/reports/{type}` con tabla jerárquica de QBO
- [x] Secciones colapsables con totales inline al colapsar
- [x] Sticky header, separadores entre secciones, fondo diferenciado nivel 1
- [x] Selector de agrupación (Mensual/Trimestral/Anual/Total) con cache por versión
- [x] Sidebar dinámico con reportes QBO sincronizados

### 1.5 Drill-down funcional ✅
- [x] Drill-down on-demand: cache check → fetch QBO Detail si falta → extraer sección
- [x] Navegación por página (no modal): `/reports/{type}/drilldown?section=X`
- [x] Breadcrumb jerárquico navegable
- [x] Sub-secciones clicables para drill-down más profundo
- [x] Link directo a QBO por transacción individual
- [x] `DrilldownService` + `QboReportRenderer::findSection()`
- [x] Catálogo de reportes en DB (`ReportCatalog`) con 31 reportes (24 QBO + 4 Sunbase + 3 Sunbrite)

### 1.6 Export a Google Sheets ✅
- [x] `google/apiclient` instalado via Composer
- [x] Google OAuth del usuario (no service account) — Sheet se crea en el Drive del usuario
- [x] `POST /api/export/google-sheets` — crea Sheet nuevo con reporte + drill-down
- [x] Drill-down via hyperlinks embebidos en celdas (cell API, no fórmulas — evita problemas de locale)
- [x] Formato: header frozen + bold, totales con fondo gris, grand totals azul, números con formato
- [x] `GoogleSheetsExport` service + `GoogleClientFactory` (centraliza creación de Google\Client)
- [x] Exporta la versión de summarize que el usuario está viendo
- [x] Export a sheet existente con preservación de fórmulas (export templates)
- [x] Múltiples reportes en el mismo sheet (cada uno en tabs `Data_*` + `Detail_*`)
- [x] UI: botón dropdown con opciones (sync/exportar/configurar/quitar mapping)
- [x] Modal de configuración para mapear URL de Google Sheet
- [x] Sync individual o masivo (todos los reportes de un sheet)
- [x] `FetchesGLDetail` trait compartido entre ExportController y ExportTemplateController

### 1.7 Auth de usuarios ✅
- [x] Google OAuth login con scopes de Sheets + Drive (un solo flujo: login + permisos export)
- [x] `RequireAuth` middleware — redirige a `/auth/google` si no hay sesión
- [x] Solo usuarios con `is_allowed=1` en tabla `users` pueden acceder
- [x] Sidebar muestra nombre real + email del usuario logueado + botón logout
- [x] Token de Google en sesión con auto-refresh via `GoogleClientFactory::fromToken()`
- [x] Usuarios seeded: Yaritza Rivera, Catalina Cardenas, Joseph Zambrano

### 1.8 Code quality y refactoring ✅
- [x] `ApiResponse` helper — respuestas JSON estandarizadas
- [x] `Qbo` config class — URLs, TTLs, auth header centralizados
- [x] Window function (ROW_NUMBER) en sync_log
- [x] Single Guzzle client en QboSyncService
- [x] XSS: `json_encode()` en PHP + `App.esc()` en JS para todas las inyecciones innerHTML
- [x] Índice `idx_company_id_desc` en sync_log
- [x] SELECT específico (no `*`) en TransactionsController
- [x] KPI queries combinadas (2→1)
- [x] Sidebar usa PhpRenderer attribute en vez de PDO propio

### 1.9 Reestructuración frontend ✅
- [x] CSS custom properties (tokens.css): tema claro default + dark via `html.dark`
- [x] theme.css refactoreado a `var(--token)` en vez de colores hardcodeados
- [x] Toggle de tema (luna/sol en navbar) con persistencia localStorage
- [x] Sidebar colapsable (icons-only mode) con persistencia localStorage
- [x] JS reorganizado: `shared/` + `components/` + `pages/` con carga condicional

### 1.10 Arquitectura de integraciones ✅
- [x] `IntegrationInterface` — contrato para todas las fuentes de datos
- [x] `IntegrationRegistry` — registro central inyectado via DI
- [x] `QuickBooksIntegration` — implementación completa (OAuth, sync, reports)
- [x] `SunbaseIntegration` + `SunbriteIntegration` — stubs con READMEs
- [x] `/connect` genérica — muestra 3 integraciones (QBO conectado, Sunbase/Sunbrite próximamente)
- [x] `/sync` genérica — reportes agrupados por integración
- [x] Rutas genéricas: `/integrate/{integration}/connect/{slug}`, callback, disconnect
- [x] `ConnectController` genérico reemplaza `QboConnectController` (eliminado)
- [x] `GET /api/integrations/status` — estado de las 3 integraciones
- [x] `GET /api/sync/available-reports` — reportes de TODAS las integraciones via registry

### 1.11 Custom Reports y feedback del cliente (mayo-junio 2026)

**Resuelto a partir del feedback de Catalina/Yaritza (correo 20-may-2026):**

- [x] Custom report builder en `/custom-reports` (filtros por account/vendor/customer/txn_type)
- [x] **Sesión persistente** (30 días con refresh token cifrado en DB — antes la sesión expiraba seguido)
- [x] **Fix custom report no traía transacciones de Outbound Expenses** — `reports/TransactionList` solo muestra la cuenta-origen del dinero (banco/AP/CC), no las categorías de gasto. Cambio a `reports/GeneralLedger` cuando hay filtro de cuenta.
- [x] **Fix "Out of sort memory"** al sincronizar a Sheets — subquery-by-PK para mantener el JSON fuera del sort buffer
- [x] Dedupe de cache (DELETE-then-INSERT para `Custom_*` — antes el UNIQUE key acumulaba una fila por día)
- [x] Expansión de cuenta padre → descendientes (defensa en profundidad para jerarquías QBO)
- [x] Paginación de cuentas (`STARTPOSITION + MAXRESULTS`) para realms con >1000 cuentas
- [x] `accounting_method` explícito leído de `Preferences.ReportPrefs.ReportBasis` (Cash/Accrual) para que el caché sea determinista por tenant
- [x] Columna Balance (running balance) en custom reports, propagada al sync de Sheets templates
- [x] Validación cruzada contra QBO: 80/80 transacciones idénticas, $92,553.78 ↔ $92,553.78 (Cash basis)
- [x] Endpoint diagnóstico `/api/custom-reports/{id}/debug` con jerarquía, IDs expandidos, raw + cached + live Cash/Accrual

**Pendiente del feedback:**

- [x] **Multi-usuario: custom reports y export templates por-usuario** — hoy todos los usuarios comparten el mismo `custom_reports` y `export_templates`, así que el sync va a una sola Google Sheet. Cada usuario debe tener los suyos. Custom data (P&L, BS, …) sigue compartida a nivel compañía (solo cambia el cache key por fecha, lo cual ya soporta el feature de fechas customizadas).
- [x] **Fechas customizadas en reportes estándar** (P&L, BS, Cash Flow, Trial Balance) — actualmente el viewer asume year-to-date. Catalina quiere "P&L trimestral de este año + P&L mensual desde 2025". UI selector de rango + fetch on-demand sin sobrecargar el API de QBO (con cache hash por (start,end)).
- [x] **Fechas customizadas en custom reports** — el backend ya acepta `periodStart`/`periodEnd`; falta exponer un selector en la página de gestión / al hacer fetch.
- [x] **Drilldown desde celda en Google Sheets** — Catalina quiere "ver los detalles de las transacciones que corresponden exactamente a cierto valor de un reporte" (ej. el cruce mes × cuenta de un P&L exportado). El new-sheet export ya tiene hyperlinks a Detail tab; el template export no. UX clarificada (jul-2026): estilo QuickBooks, sin salir del spreadsheet → plan completo en **sección 1.12**.
- [ ] **Simplificar pasos de login con Google** — Reducir clicks del consent screen. **Bloqueado: requiere config de Google Workspace que está en proceso con IT (Vilmarie).**

**Validación pendiente con cliente:**

- [x] Confirmado con Catalina: el reporte "OBM Expenses" muestra las 80 transacciones correctas después del deploy del 28-may.

### 1.12 Drill-down desde Google Sheets sin salir del spreadsheet (julio 2026)

**Feedback de Catalina:** al hacer clic en un número azul del spreadsheet "te muestra todo el detalle en general y no las cosas específicas que hacen la suma de ese valor". Quiere que funcione como QuickBooks; requisito adicional: **sin salir del spreadsheet** (panel al lado, estilo LiveFlow).

**Investigación (verificada en navegador, jul-2026):**
- El drill de QBO abre un "Transaction Report" (solo las txns de cuenta × período + total exacto), pero sus URLs usan UUIDs temporales → **no hay deep-link construible a QBO**.
- Un link de celda en Sheets no puede filtrar ni abrir paneles; LiveFlow lo resuelve con un **add-on** (sidebar).
- Nuestra app ya tiene el backend QBO-style: `DrilldownService` fetchea GL en vivo filtrado por cuenta + rango.
- Apps Script corre en servidores de Google → no alcanza localhost; el sidebar se prueba contra prod.

**Arquitectura:** los links por celda (Fase A) llevan la metadata (company, section, accountId, from/to por columna). El sidebar (Fase B) lee el link de la celda seleccionada, consulta `GET /api/drilldown` con Google ID token y pinta las transacciones en el panel. El link clickeado abre la página completa de drilldown (fallback universal en sheets sin script).

**Fase A — Links por celda + drilldown preciso:** ✅ implementada y verificada en local (8-jul-2026: celda 550.00 → drilldown con 2 txns · total 550.00; fila "Total Job Materials" resuelve por nombre → 705.64 exacto; tests sintéticos 20/20)
- [x] `DrilldownService::getDrilldown()` acepta `accountId` opcional (salta resolución por nombre)
- [x] `ReportsController::drilldown` lee/valida `?accountId`
- [x] `GoogleSheetsExport`: helper `drillContext()` (lee `APP_URL`); `buildReportTab` genera URI por celda con from/to exactos de `columnRanges` + accountId; filas `total` → regex "Total for X"; sin `APP_URL` → anclas internas actuales (fallback)
- [x] Callers pasan `companySlug` (ExportController + SyncsReportToSheet — cubre export nuevo y los 3 flujos de plantilla)
- [x] Return-to tras login: `RequireAuth` guarda destino GET; callback de Google redirige ahí (validado same-origin)
- [x] Deploy: `APP_URL=https://connect.bonsai.com.ec` en `.env` de prod + verificación en prod (8-jul-2026: Sync Sheet del P&L → celda 24.591,28 de "43201 Equity Solar Revenue- M1 × Feb" → drilldown con 13 txns · total 24,591.28 exacto)

**Fase B — Sidebar en el spreadsheet (estilo LiveFlow):**
- [x] `GET /api/drilldown` JSON con auth dual: sesión web o Bearer Google ID token verificado (`verifyIdToken` + allowlist `users`) — smoke: 401 sin auth/token inválido; con sesión devuelve rows + total + qboUrl
- [x] `appsscript/` versionado en repo: `Code.gs` (menú Sunbright, lee link de celda activa, fetch con ID token), `Sidebar.html` (polling de selección, render txns + total + links QBO), `appsscript.json`, `README.md`
- [x] Instalado en prod (piloto, sheet de plantilla): menú Sunbright → Abrir drill-down → panel abre y autoriza OK
- [x] **Leer el hyperlink de celdas numéricas (Opción A, 21-jul-2026)** — `getRichTextValue()` NO expone el link del formato de celda; intento vía Sheets REST API dio 403 (SERVICE_DISABLED). Resuelto con el **servicio avanzado "Sheets API"** (`Sheets.Spreadsheets.get`, auto-habilita la API). Costo asumido: scope pasa a `spreadsheets` (full) + activar el servicio por instalación.
- [x] **Fix auth del endpoint** — `/api/drilldown` devolvía 401 aunque el email estaba en el allowlist: `apiAuthOk` exigía el claim `email_verified`, que `ScriptApp.getIdentityToken()` no incluye. Ahora solo se rechaza si viene explícitamente en false; el token ya está firmado por Google. Añadido `error_log` del motivo de rechazo (visible con `grep "drilldown auth"`).
- [x] **Fix token inválido** — `getIdentityToken()` daba "Wrong number of segments" porque el manifiesto autogenerado no incluía el scope **`openid`**. Aplicado el `appsscript.json` del repo (con `openid` + `userinfo.email`) y añadida la función `authorize()` para forzar el consentimiento de todos los scopes (`onOpen` no los dispara, así que el menú no aparecía).
- [x] **Verificado end-to-end en prod (21-jul-2026)**: seleccionar "14100 Notes Receivable" (B19) → panel muestra 7 transacciones · Total 5.800,00 · links a QBO por transacción · "Ver página completa". El panel lateral funciona sin salir del spreadsheet (paridad LiveFlow).
- [x] Pasos de instalación reales documentados en `appsscript/README.md` (servicio Sheets, manifiesto con `openid`, `authorize()`)
- [ ] Futuro V2: botón "Instalar sidebar" vía Apps Script API (scope `script.projects` + toggle one-time por usuario)
- [ ] Futuro V3: add-on privado de Marketplace (cero instalación por sheet; requiere Google Workspace o verificación de Google)

**Fase C — Drill-down correcto en reportes acumulativos (Balance Sheet)** — ✅ implementada 21-jul-2026:
- Síntoma: en Balance Sheet, el "Total" del detalle (movimientos del período) NO coincidía con la celda (saldo acumulado), y cuentas con saldo alto pero sin movimiento en el mes no traían nada. Verificado en prod: celda "15000 Investments × Jan" = 400.000,00; el drill de enero mostraba 1 txn = 100.000,00 (el saldo incluye 300.000 de antes de enero).
- Causa: el drill usaba `from` = inicio de la columna (correcto para P&L, de flujo), pero el Balance Sheet es un **saldo acumulado** "as of" el fin de columna.
- Fix (como QuickBooks): `QboReportRenderer::isCumulative()` marca `BalanceSheet` como acumulativo; `ReportsController::drilldown` y `drilldownApi` usan `from = CUMULATIVE_DRILL_START` (2000-01-01) hasta `to` = fin de columna, así el total del detalle = el saldo = la celda. P&L (flujo) no cambia. Funciona con los sheets ya sincronizados (no requiere re-sync).
- Nota UX pendiente (opcional): el subtítulo del panel/página muestra el rango con la fecha de inicio (2000-01-01); podría mostrarse "Saldo al {fecha}" para acumulativos (requiere tocar `Sidebar.html` + `drilldown.php`).

---

## Fase 2 — Integraciones adicionales

### 2.1 Sunbase CRM
- [x] Stub + README creados (`src/Integrations/Sunbase/`)
- [x] Reportes definidos: Clientes, Pipeline, Instalaciones, Operaciones
- [x] Aparece en `/connect` y `/sync` como "Próximamente"
- [ ] Conectar API real de Sunbase (API key auth)
- [ ] Sync de clientes, etapas de venta, pipeline
- [ ] Datos de CRM visibles en dashboard (Pipeline Activo ya tiene slot)
- [ ] Cruce CRM + QuickBooks para ROI por proyecto

### 2.2 Sunbrite Commissions
- [x] Stub + README creados (`src/Integrations/SunbriteCommissions/`)
- [x] Reportes definidos: Comisiones, Pagos, Rendimiento por instalador
- [x] Aparece en `/connect` y `/sync` como "Próximamente"
- [ ] Conectar API real de comisiones (API key auth)
- [ ] Sync de comisiones por instalador
- [ ] Comisiones reales en chart y tabla del dashboard
- [ ] Drill-down de comisiones por instalador

### 2.3 Consolidación multi-fuente
- [ ] Vistas consolidadas: datos QBO + CRM + Comisiones en un solo dashboard
- [ ] Alertas automáticas cruzando fuentes (margen bajo + comisiones altas)
- [ ] Entity selector: ver por empresa individual o consolidado

---

## Fase 3 — Features avanzados

### 3.1 Presupuestos (Budgeting)
- [ ] Módulo de creación de budgets anuales/trimestrales
- [ ] Budget vs. Actual automático desde QBO
- [ ] Varianza por cuenta, zona, segmento

### 3.2 Forecasting IA
- [ ] Modelo de forecasting (Prophet o similar)
- [ ] Forecast de ingresos, gastos, margen
- [ ] Intervalos de confianza visualizados en el chart existente
- [ ] Re-entrenamiento automático con datos frescos

### 3.3 Agente IA Financiero
- [ ] Chat conectado a datos reales (no wrapper genérico)
- [ ] Text-to-SQL: preguntas en español → queries sobre cached_transactions
- [ ] Análisis de varianza, recomendaciones
- [ ] Fuentes citadas (QuickBooks, CRM, Comisiones)

### 3.4 Permisos y colaboración
- [ ] Sistema de roles: admin / contributor / viewer
- [ ] Permisos granulares por entidad y reporte
- [ ] Audit log de acciones

### 3.5 Automatización
- [ ] Envío programado de dashboards por email (cadencia semanal/mensual)
- [ ] Generación automática de Board Deck (slides) desde KPIs
- [ ] Sync automático con QBO cada N horas (cron job)
- [ ] Alertas por email/Slack cuando métricas cruzan umbrales

---

## Hitos clave

| Hito                       | Descripción                              | Fase|
|----------------------------|------------------------------------------|-----|
| OAuth QBO funcional        | Conectar sandbox, almacenar tokens       | 1.2 |
| Primer sync exitoso        | P&L real en MySQL                        | 1.3 |
| Dashboard con datos reales | KPIs y tabla con datos QBO               | 1.4 |
| Drill-down funcional       | Navegación por sección con transacciones | 1.5 |
| Export a Sheets            | P&L en Google Sheet con drill-down links | 1.6 |
| Export templates           | Actualizar sheets existentes del usuario | 1.6 |
| Multi-fuente               | QBO + CRM + Comisiones integrados        | 2.3 |
| Chat IA conectado          | Preguntas sobre datos reales             | 3.3 |
