--- asset_id: MIN-EL-Gustavo-20260702-v01 tipo: MIN — Minuta del día owner: Gustavo sherpa: GusX ratificador: Gustavo fecha: 2026-07-02 intellibank: IB-EL-EmpowerLabs subbank: EQ-EL-Equipo proyecto: RCH — Rebelocity Club --- ## Qué se hizo ### Fix de build — `isUrl` faltante en `GeneralService` - `npm run build:dev` fallaba con `TS2339: Property 'isUrl' does not exist on type 'GeneralService'` en `validate-user.component.ts:189`. - Localizado el patrón ya usado en otros componentes (`dashboard-comprador.component.ts`, `dashboard-proveedor.component.ts`): método `isUrl(url)` con regex `http/https/ftp`. - Agregado `isUrl(url:string)` a `general.service.ts` (junto a `isLocal()`), reutilizando el mismo regex. - Verificado con `npm run build:dev`: compila sin errores TS (solo quedan warnings preexistentes de CSS y de presupuesto de bundle, no relacionados). - Commiteado por el usuario: `62303f9 fix isUrl`. --- ### B09 — Integración del chat de solicitud atleta↔coach (arranque del feature completo) **Documentos compartidos por el usuario hoy:** `PROMPT-Integracion-Chat-Coaches-Claude-Code.md` (instrucciones completas del feature, pegado directo en el chat) + una carpeta nueva copiada al repo en `docs/usabilidad-rebelocity/`: `DESIGN-SYSTEM-RCH.md`, `SPEC-FLUJOS-RCH.md`, `DECISIONES-RCH.md`, `FLUJOS-DIRECTORIO-PARA-GUS.md`, y 3 mockups HTML/CSS/JS autocontenidos: `MOCKUP-RCH-Directorio-v01.html`, `MOCKUP-RCH-Mensajes-Coach-v01.html` (POV Ángeles = **atleta**, pese al nombre del archivo) y `MOCKUP-RCH-Mensajes-Javier-v01.html` (POV Javier = **coach**, también con nombre cruzado). El prompt exigía explícitamente detenerse en cada paso del plan para revisión antes de avanzar — así se trabajó toda la sesión. **Paso 1 — Fase de análisis (antes de tocar código), 5 puntos:** 1. El botón real en `athlete-profile-modal.component.html` no decía "Contactar al coach" (era "Enviar mensaje", navegaba a `/chat?to=id`) — el prompt asumía un botón condicionado por `acepta_atletas` que no existía. 2. `chat.service.ts`/`chat-master.service.ts` son un chat tipo "muro de comentarios" (actividad/grupo), no un sistema de hilos 1:1 con solicitud/aceptar/rechazar — solo sirven como referencia de convención, no se reutilizan. 3. Confirmado el stack: Angular clásico (módulos, no standalone), servicios `providedIn:'root'`, naming kebab-case. 4. Confirmado por grep en el backend (`api_rch`): no existe ningún endpoint de solicitud/inbox/aceptar/rechazar — hay que mockear con una capa de abstracción. 5. El campo de disponibilidad del coach es `acepta_atletas` (perfil-usuario.component.html:320-326) — el mismo que ya filtra `getListCoaches()`. Resultado: plan de archivos aprobado por el usuario + 2 preguntas de la sección 9 del prompt resueltas con el usuario: inbox del coach → **versión mínima** (no el mockup completo de inbox, otro sprint); tiempo real → **no por ahora**, fetch/refresh manual. Todo el desglose de este paso 1 y las respuestas quedaron documentados en el backlog como **B09**, con la instrucción explícita del usuario de ir implementando paso a paso a partir de ahí. **Paso 2 — `coach-request.service.ts` (capa de abstracción, mock):** Servicio nuevo con 8 métodos: los 7 de la sección 6 del prompt (`enviarSolicitud`, `obtenerEstadoSolicitud`, `listarSolicitudesPendientes`, `aceptarSolicitud`, `rechazarSolicitud`, `enviarMensaje`, `obtenerHistorialMensajes`) + `cancelarSolicitud` (agregado porque la sección 3.2 del prompt exige que el botón "Cancelar solicitud" del POV Atleta sea funcional, no un placeholder). Persistencia mock en `localStorage` (sobrevive reloads), todo expuesto como `Observable` con `delay()` simulado — misma forma que `club.service.ts` — para que el día que Jesús confirme el backend real el cambio sea interno al service, sin tocar componentes. **Paso 3a — `coach-message-panel` component, POV Atleta (4 pasos):** Componente nuevo (`src/app/custom/coach-message-panel/`) con `@Input() pov: 'atleta'|'coach'`, mismo patrón que `athlete-profile-modal`. Implementados los 4 pasos fieles a `MOCKUP-RCH-Mensajes-Coach-v01.html`: Redactar → Confirmación → En espera (con "Cancelar solicitud" real + botón "↻ Actualizar estado" para el refresh manual acordado) → Respuesta (chat activo si aceptó / "Buscar otro coach →" si rechazó). Reutiliza el patrón de avatar por gradiente de `athlete-card.component.ts`. Al abrir el panel, resuelve el estado real vía `obtenerEstadoSolicitud()` y retoma el paso correcto en vez de siempre arrancar en "Redactar". Colores hardcodeados con los valores exactos de tema claro de `DESIGN-SYSTEM-RCH.md §2` (`#234E9C`, `#059669`, `#DC2626`, etc.), sin forzar Montserrat (se respetó la decisión ya tomada en sesiones previas de heredar la tipografía global). **Paso 3b — POV Coach (3 pasos) + wiring del botón:** Completados los 3 pasos fieles a `MOCKUP-RCH-Mensajes-Javier-v01.html`: Solicitud entrante (badge NUEVA, tarjeta de contexto del atleta, mensaje recibido, nota informativa) → Confirmación (rama aceptada con dots 2/3, rama rechazada con dots **2/2 y sin el paso 3 en el DOM**, no solo oculto — regla D-020) → Chat activo. Wiring real: `athlete-profile-modal` ahora tiene botón **"Contactar al coach"** en modo `coach`, condicionado a `athlete.acepta_atletas` (si es `false`, muestra texto explicativo en vez de esconder todo sin decir por qué), emite `contactarCoach`. `athlete-directory.component` escucha el evento, cierra el modal de perfil y abre `app-coach-message-panel` en `pov="atleta"`. Verificado con `npm run build:dev` sin errores nuevos (solo el warning preexistente de presupuesto de bundle, que creció ~44kB por el componente nuevo). **Hallazgo durante el wiring — gap documentado como B10:** el getter `currentAthleteContext` (necesario para que el coach vea nivel/disciplinas/objetivo del atleta que lo contactó) solo pudo poblarse con `nombre`, porque `athlete-directory.component.ts` nunca carga el perfil extendido del usuario logueado. Se investigó y **ya se encontró la solución concreta** (no implementada aún, por decisión de mantener el alcance del paso 3 acotado): `UserService.getProfile(session.puesto, 'puesto', true)` — endpoint ya existente y probado (`admin.php:1084`, función `profileUser()`), que además ya devuelve `disciplina`/`evento_objetivo`/`especialidad` pre-parseados como arrays por el backend. Detalle completo, mapeo de campos y un mismatch de tipo pendiente (`eventoObjetivo` string vs array) quedaron documentados en el backlog como **B10**. **Paso 4 — `coach-inbox-badge` (punto de entrada mínimo del coach) + edge cases:** Componente nuevo (`src/app/custom/coach-inbox-badge/`), no el inbox completo del mockup (`MOCKUP-RCH-Inbox-v01/v02.html` sigue en backlog para su propio sprint) — solo lo necesario para que un coach vea sus solicitudes pendientes y dispare el panel. Se montó como botón con contador en el header del Directorio (`content-header`, visible solo si `isLoggedIn`); al hacer clic despliega un dropdown con `listarSolicitudesPendientes()`; al seleccionar una abre un **segundo** `app-coach-message-panel` (independiente del de atleta) en `pov="coach"`. Cuando el coach acepta/rechaza, el evento `(solicitudActualizada)` quita el ítem de la lista sin volver a pedirla al servicio. Edge cases del entregable 5 del prompt, revisados explícitamente: - Encontrados 3 puntos donde el componente ya guardaba `this.error` (fallos de red al aceptar/rechazar/cancelar/enviar mensaje) pero la plantilla nunca lo mostraba — agregado el banner visible en el paso "en espera" del atleta, el chat activo del atleta y el chat activo del coach. - Agregada pantalla de **"esta solicitud ya no está disponible"** en el POV coach, para el caso de que `incomingRequest` llegue nulo/inválido (ej. el atleta canceló la solicitud justo antes de que el coach la abriera desde el inbox). Verificado con `npm run build:dev` sin errores nuevos. **Paso 5 — Nota final de pendientes (entregable 6 del prompt):** Escrita como comentario-cabecera extenso en `coach-request.service.ts`, para que quede visible directamente en el código cuando Jesús lo abra: falta la tabla de "solicitud" atleta↔coach, la tabla de mensajes ligada (o evaluar reutilizar el chat existente), los 8 endpoints reales, definir el id real de atleta/coach (el mock usa `session.puesto` como placeholder), confirmación de que no hace falta tiempo real para el v1, y la dependencia con B10. Con esto, **las 6 piezas de "Entregables esperados" (sección 8 del prompt) quedan completas** como mock funcional end-to-end — componente con ambos POV, wiring del botón, wiring del lado coach, capa de servicio, manejo de estados vacíos/error, y nota de pendientes. B09 sigue abierto en el backlog únicamente por los 2 ítems que no dependen de más código: confirmar el backend real con Jesús (🔴 crítico) y resolver B10. --- ## Decisiones - Fix de `isUrl`: ninguna decisión de producto/alcance — fix puntual de compilación, siguiendo el patrón ya existente en el código. - **B09 se trabaja estrictamente paso a paso**, deteniéndose después de cada uno para revisión del usuario antes de continuar — instrucción explícita del prompt compartido, no una preferencia general de proceso. - El botón "Contactar al coach" se condiciona a `acepta_atletas` de forma defensiva, aunque hoy es técnicamente redundante (el backend ya excluye del listado a los coaches con `acepta_atletas=0` desde la sesión del 2026-07-01) — se prefirió cumplir la regla explícita del prompt en vez de confiar silenciosamente en el filtro del backend. - `athleteId` del mock se resolvió con `session.puesto` (no `session.usuario`, que resultó ser el wikiname) por ser el campo que ya usa el resto de la mensajería de la app (`chat.service`, `sherpa-chat`, etc.) — decisión de consistencia, no definitiva: el backend real de B09 definirá su propio id. - B10 (gap de contexto del atleta) se dejó **documentado con la solución ya identificada, sin implementar** — decisión de mantener acotado el alcance del paso 3 y no mezclar un fix de otro componente (`athlete-directory` cargando su propio perfil) dentro del paso de wiring del panel. - La nota final del entregable 6 se escribió **como comentario en el código** (`coach-request.service.ts`), no como documento aparte — para que Jesús la vea directamente al abrir el archivo que tiene que reemplazar, en vez de tener que ir a buscarla a otro lado. --- ## NEXTs Ver backlog vivo para el detalle completo: 📋 [`BCK-EL-Gustavo-RCH-v01.md`](../BCK-EL-Gustavo-RCH-v01.md) **B09 (chat coach↔atleta) — plan de 5 pasos, completado hoy en su totalidad como mock funcional end-to-end:** - ✅ Paso 1: fase de análisis + preguntas respondidas. - ✅ Paso 2: `coach-request.service.ts` (mock). - ✅ Paso 3: `coach-message-panel` (ambos POV completos) + wiring del botón en el Directorio. - ✅ Paso 4: `coach-inbox-badge` (punto de entrada mínimo) + edge cases/errores. - ✅ Paso 5: nota final de pendientes (comentario en `coach-request.service.ts`). Las 6 piezas de "Entregables esperados" (sección 8 del prompt) quedan cubiertas. B09 **sigue abierto en el backlog** solo por 2 ítems que no son "más pasos de código pendientes" sino dependencias externas/de otro componente: - **🔴 Pendiente crítico** — confirmar con Jesús el contrato real de backend para las 8 operaciones de `coach-request.service.ts`. - **B10** — `currentAthleteContext` incompleto (solución ya identificada: `UserService.getProfile`, no implementada). **Otros abiertos (heredados, sin tocar hoy):** - **B04** — normalización canónica de valores enum del perfil (roles/rol) y fix del filtro case-sensitive en `getListCoaches()`. - **B06** — relay con 2 estados (buscando/completo), pendiente el mecanismo de transición de chat 1:1 a grupal. --- ## Estado de git al cierre - **Frontend** (`rebelocity-club`, rama `gustavo`): commit `62303f9 fix isUrl` cerrado. Todo el trabajo de B09 (pasos 1-5) **sin commitear** al cierre de la sesión — pendiente de revisión final antes de commit: - Nuevos: `src/app/services/coach-request.service.ts`, `src/app/custom/coach-message-panel/` (`.ts`/`.html`/`.less`), `src/app/custom/coach-inbox-badge/` (`.ts`/`.html`/`.less`), `docs/usabilidad-rebelocity/` (documentación + mockups compartidos por el usuario). - Modificados: `app.module.ts` (registro de los 2 componentes nuevos), `athlete-profile-modal.component.{ts,html,less}` (botón "Contactar al coach"), `athlete-directory.component.{ts,html,less}` (wiring de ambos paneles + badge del inbox).