---
asset_id: MIN-EL-Jesus-20260626-v01
version: v01
tipo: MIN — Minuta
owner: Jesus
sherpa: Jess (SherpaX de Jesus)
ratificador: Jesus
fecha: 2026-06-26
intellibank: IB-EL-EmpowerLabs
subbank: EQ-EL-Equipo
---
# Minuta del Día · Jesús · 2026-06-26
## Qué se hizo
### Performance · `rebelocity-club` (Angular SPA)
**Análisis y planificación**
- Leída y analizada `Auditoria-home.md` (generada con extension Claude en Chrome): FCP 14s, bundle main.js 8.73 MB, 32 llamadas API al inicio, 37 imágenes sin lazy loading, GIF 505 KB.
- Creado `data/auditoria/Factibilidad-home.md`: evaluación ítem por ítem de la auditoría contra el código real. Hallazgo clave: el proyecto tiene **un solo AppModule** — no hay feature modules, lo que hace el lazy loading de rutas un refactor estructural de 3-5 días, no una tarde.
- Dos ítems movidos a sección "En espera" por riesgo identificado: Stripe.js defer (riesgo de romper checkout) y diferir calls API (riesgo de retrasar contenido visible).
**Fase 1 implementada — 5 fixes, build limpia**
- **Fix #7:** Eliminado `console.log('getCompanyUser activo')` en `inicio.component.ts:164`.
- **Fix #3a:** En `src/index.html`: movidos los ` ` de Google Fonts **antes** de los ` ` (el orden estaba invertido); agregado `preconnect` + `dns-prefetch` a `genniux.net`.
- **Fix #3c:** Skeleton screen en `src/index.html` dentro de ``. Estructura que replica la home: nav oscuro (#333) + banner + 3 filas de cards horizontales con animación shimmer. CSS inline en `` — visible desde el primer byte, sin esperar JS. Verificado visualmente vía browser automation.
- **Fix #6:** `congras.gif` (506 KB) convertido a `congras.webm` (172 KB, −66%) con ffmpeg. Template de `registro-empresa.component.html` actualizado de ` ` a `` con WebM como fuente primaria y GIF como fallback. `ngsw-config.json`: assets `updateMode` cambiado de `"prefetch"` a `"lazy"` para que el service worker no prefetchee todos los assets (incluido el gif) al actualizarse.
- **Fix #5:** Creada `src/app/directives/lazy-image.directive.ts` — selector `img:not([loading])`, aplica `loading="lazy"` y `decoding="async"` en `ngOnInit`. Declarada en `AppModule`. Cubre toda la app sin tocar templates individuales.
**Fase 2 implementada — 3 fixes, build con `pg` limpia**
- **Fix #4b:** `ngsw-config.json` dividido en 3 grupos de caché: `api-static` (strategy `performance`, maxAge 1h — datos que casi no cambian: zonas, clusters, módulos, quote), `api-content` (freshness, timeout 3s, maxAge 5m — noticias, playbooks, eventos), `api-dynamic` (freshness, timeout 3s, maxAge 5m — todo lo demás). Cambio más impactante: timeout de **30s → 3s** en toda la API.
- **Fix #2a:** `vendorChunk: true` + `buildOptimizer: true` activados en `angular.json` producción. Resultado: `main.js` bajó de 8.56 MB a **3.88 MB** (−55%); vendor separado a `vendor.js` (4.98 MB). En deploys futuros los usuarios solo re-descargan `main.js` (449 KB transfer), no el vendor.
- **Fix #2b:** Budgets corregidos de 135 MB (desactivados) a valores reales: initial 10 MB warning / 15 MB error; anyComponentStyle 30 KB warning / 40 KB error (el componente más grande era 27.85 KB).
**Clarificación sobre scripts de build:**
- `pg`: `--base-href /` → deploya a raíz (producción real)
- `build:dev`: `--base-href /dev/` → deploya a `/dev/` (pruebas en servidor)
- Ambos usan `--output-hashing none`; los flags de CLI sobreescriben `angular.json`
## Decisiones
- El lazy loading de rutas (Fix #2c) queda para Fase 3 al final — requiere crear NgModule por grupo de rutas; no es un quick win.
- El fallback GIF se mantiene en el `` para soporte en browsers sin WebM (Safari antiguo).
- `ngsw-config.json` assets pasa a `updateMode: "lazy"` — trade-off aceptado: assets no se pre-calientan en updates del SW, pero se evita descargar el gif innecesariamente.
- En Fase 3, Fix #2c (feature modules + lazy loading) se deja para el final; se empieza por Fix #2d (Quill.js).
### Sistema de puntos · `api-nodejs-mongo`
**getBalance — detalle por acción (`actions=true`)**
- Nuevo método `PointsService.getActionBreakdown(userId, organizationId)`: agrupa `point_transactions` por `action_key` (solo `points > 0`), JOIN con `point_actions` para el nombre legible, devuelve `actionKey`, `actionName`, `totalPoints`, `count`, `awardedAt` (`MAX(created_at)`).
- `PointsController.getBalance` actualizado: acepta `?actions=true`, corre `getActionBreakdown` en paralelo con `progress`. Respuesta incluye `response.actions`.
- Fix: `COALESCE(MAX(pa.name), ...)` para resolver error MySQL `only_full_group_by`.
**SQL de onboarding RClub (org 3)**
- Creado `sql/points/points_v2_2026-06-26_rclub_onboarding_actions.sql` con 11 acciones: 6 Checkpoints (cp2–cp7) y 5 Curiosity Pistas. `cooldown = once`, `requires_card = TRUE`, idempotente vía `ON DUPLICATE KEY UPDATE`.
## Decisiones
- `awardedAt` usa `MAX(created_at)` — para acciones `once` siempre es única; para repetibles devuelve la más reciente.
- Onboarding RClub: todos los checkpoints y pistas con `cooldown = once` (se ganan una sola vez por usuario).
**cp1_completado — award al completar formulario del Sherpa IA**
- `routes/ai-sherpa/index.js`: importados `PointsService` y `resolveOrganizationId`.
- En `POST /user/data/save`, cuando `model === 'profile'` y el guardado es exitoso, se dispara `PointsService.awardPoints({ actionKey: 'cp1_completado', ... })` de forma no bloqueante (`.catch()`) — la respuesta al cliente no se ve afectada si falla.
- `organizationId` resuelto con `resolveOrganizationId(req)` (cascade: req.user → body → domId → host → default 1).
## Decisiones
- `awardedAt` usa `MAX(created_at)` — para acciones `once` siempre es única; para repetibles devuelve la más reciente.
- Onboarding RClub: todos los checkpoints y pistas con `cooldown = once` (se ganan una sola vez por usuario).
- `cp1_completado` no bloquea la respuesta del guardado — fallo silencioso en log, no en UX.
**`GET /api/membership/credits/status` — performance (~3,442ms → ~50ms)**
- Diagnóstico: el bottleneck era `MembershipService.resolveMembership` → `axios.post` al PHP (`/api/playbook/membresia/get/user`) → el PHP hacía 4 queries + 2 file reads + round-trip HTTP.
- Fix 1: `CreditsService.getCreditStatus` — `getOrCreateMonthlyUsage` + `getOrCreateMonthlyBalance` ahora corren en `Promise.all` (eran secuenciales).
- Fix 2 (principal): `MembershipService.resolveMembership` reescrito — elimina el HTTP call al PHP y consulta directo: 1 query `pb_users JOIN pb_membresia` + lectura paralela de `limits.json` y `features.json` del filesystem.
- Fix 3: `_membresiaBasePath` resuelto con `path.join(__dirname, pathPbsMembresia)` — igual que `PbMembershipService`. Sin este fix los JSON no retornaban datos (path relativo sin base).
- Validado: local → `/var/www/html/playbooks/data/membresia` ✓ · prod → `/var/genniuxnet/public_html/playbooks/data/membresia` ✓
## Decisiones
- `awardedAt` usa `MAX(created_at)` — para acciones `once` siempre es única; para repetibles devuelve la más reciente.
- Onboarding RClub: todos los checkpoints y pistas con `cooldown = once` (se ganan una sola vez por usuario).
- `cp1_completado` no bloquea la respuesta del guardado — fallo silencioso en log, no en UX.
- `resolveMembership` ya no llama al PHP para membership — las 2 queries extra de uso (katia/sherpa) que hacía el PHP no se usaban en Node, se eliminan sin pérdida funcional.
**Validación BASIC_PRO + estructura `limits` correcta**
- `pbu.status = 'active'` agregado al query: membresías expiradas ya no bypasean créditos.
- `ORDER BY pbu.end_date DESC` — si hay múltiples membresías activas, toma la que vence más tarde.
- `limits.json` plano (`sherpa: 100`) transformado a `{ used, allowed, available, has_limit }` que espera el frontend (`CreditStatus.limits.sherpa`).
- `sherpa.used` computado consultando `ai_comment JOIN ai_chat` del mes actual (igual que hacía el PHP).
## Decisiones
- `awardedAt` usa `MAX(created_at)` — para acciones `once` siempre es única; para repetibles devuelve la más reciente.
- Onboarding RClub: todos los checkpoints y pistas con `cooldown = once` (se ganan una sola vez por usuario).
- `cp1_completado` no bloquea la respuesta del guardado — fallo silencioso en log, no en UX.
- `resolveMembership` ya no llama al PHP para membership — las 2 queries extra de uso (katia/sherpa) que hacía el PHP no se usaban en Node, se eliminan sin pérdida funcional.
- Membresías expiradas/inactivas/canceladas → FREE. Solo `status = 'active'` otorga el plan real.
## NEXTs
### Fase 3 · rebelocity-club
- Fix #2d: Extraer Quill.js del bundle global (actualmente en `scripts[]` de `angular.json` — carga en todas las vistas).
- Fix #8: Auditar estilos inline (diferir — si se decide atacar).
- Fix #2c: Crear feature modules y migrar a lazy loading real (al final, 3-5 días).