--- asset_id: MIN-EL-Gustavo-20260701-v01 tipo: MIN — Minuta del día owner: Gustavo sherpa: GusX ratificador: Gustavo fecha: 2026-07-01 intellibank: IB-EL-EmpowerLabs subbank: EQ-EL-Equipo proyecto: RCH — Rebelocity Club · directorio (tab Atletas / Coaches / Clubs) --- ## Qué se hizo ### Revisión de pendientes registrados en memoria - Verificado `admin.php`: los 5 cambios pendientes de `fecha_nacimiento` en `perfil_extra_data` (extracción, UPDATE, INSERT, `$queryParams`, SELECT) **ya estaban aplicados** en el archivo actual. Pendiente cerrado, memoria actualizada. - Referencia del mockup del Directorio (`SPEC-RCH-Directorio-v01.md`) confirmada, sin cambios. --- ### Análisis de validaciones — tab Atletas y Coaches (`getListAtletas` / `getListCoaches`) Auditoría de archivos (`general.php` en `api_rch/controllers/rebelocity/`, `admin.php`, `index.php`, `athlete.service.ts`, `athlete-directory.component.ts`): - Documentadas las tablas/condiciones de ambos endpoints: `colaborador` (`origen=169`) → `perfil` (JOIN por `wikiname`) → `perfil_extra_data` (JOIN por `email`, `visible_directorio=1`; Coaches agrega `rol='coach'`). - Confirmado: no hay autenticación/sesión en ninguna ruta de `api_rch` (solo CORS por origin, no es auth). - Confirmado: filtros y paginación del directorio son 100% client-side; el backend no los recibe. Detectado método muerto `AthleteService.getAthletes()` que apunta a una ruta `/directorio/miembros` inexistente en `index.php`. --- ### Bug encontrado — Coach no aparecía en el directorio **Causa identificada (case-sensitivity):** `general.php:197` filtra `ped.rol = 'coach'` (minúscula exacta), pero el dropdown `roles` en `perfil-usuario.component.ts:225-230` guarda el valor con mayúscula inicial (`"Coach"`), y `admin.php` lo persiste tal cual sin normalizar. **No se aplicó fix** — decisión explícita del usuario de no modificar por ahora para evitar romper otros flujos (ver Decisiones). **Segundo bug real, distinto — encontrado y resuelto hoy:** la pestaña Coaches no devolvía ningún dato en absoluto (ni con el bug de arriba de por medio). Diagnóstico paso a paso: 1. Confirmado con `console.log` en `getCoaches()` (`athlete-directory.component.ts`) que la función se ejecutaba pero la respuesta nunca llegaba a `next`. 2. Agregado log al callback `error` → reveló `HttpErrorResponse 404` contra `https://genniux.net/api_rch/rch/rebelocity/directorio/coaches`, body `{"message":"Not found"}` (404 nativo de Slim, ruta no registrada). 3. Auditoría de `git log` en `/home/gus/Trabajo/Volume/archivos-server/` (sin remoto configurado → despliegue es manual, no vía `git push`): la ruta `/directorio/coaches` se agregó en el commit más reciente (`d1d3375 "aupds"`), posterior al despliegue vigente en producción. La ruta `/directorio/atletas` (que sí funcionaba) venía de un commit anterior ya desplegado. 4. **Causa raíz: gap de despliegue**, no de código — `index.php`/`general.php` en `genniux.net` no tenían aún la ruta ni la función `getListCoaches()`. 5. Usuario desplegó los archivos actualizados → confirmado que los datos llegan correctamente al frontend (console.log de respuesta poblado). **Tercer hallazgo — no era bug:** tras el despliegue, los datos llegaban pero la tarjeta no se pintaba en pantalla. Causa: el filtro client-side `coachFilters.disponible = true` (chip "Disponibles", activo por defecto) descarta en `filterCoaches()` cualquier coach con `acepta_atletas` falsy. El coach de prueba no tenía ese campo activo. **Confirmado con el usuario que es comportamiento esperado** (se verificó desactivando el chip en la UI y la tarjeta apareció) — no requiere cambio. --- ### Tab Clubs — alineación con el mockup del Directorio **Contexto:** se comparó tarjeta, modal y filtros del tab Clubs (`club-card`, `club-profile-modal`, `athlete-directory` sección clubes) contra `SPEC-RCH-Directorio-v01.md`. El tab se había implementado antes de que existiera el spec y difería en varios puntos. **Hallazgos previos a implementar:** - El backend (`listClubs()`, `general.php:562`) ya devolvía `disciplinas` (JSON array de la columna `disciplines`) pero el frontend (`Club` model, card, modal, filtros) lo ignoraba por completo. - No existe columna de capacidad/cupo en `clubs` (confirmado con el `CREATE TABLE` real compartido por el usuario) ni campo en el formulario de registro de club — el badge de admisión del mockup ("✓ Admite miembros" / "Cupo completo") no tenía con qué calcularse. - El filtro `tipo_club` (Triatlón/Ciclismo) y el filtro de `ciudad` no están contemplados en el mockup (que solo pide Disciplina + País + Admisión). - El avatar de club mostraba iniciales sobre color sólido; el mockup pide un emoji en círculo `--card2`, sin gradiente — pero el HTML real del mockup no estaba disponible (solo quedó un placeholder de 1 línea), así que no había forma de saber el emoji exacto usado. **Decisiones tomadas con el usuario antes de implementar:** 1. **Admisión/cupo:** hardcodear temporalmente `admision: 'open'` en el backend para todos los clubes, en vez de construir el campo real en BD + formulario de registro (proceso extenso que el usuario decidió posponer). El frontend queda armado con la lógica binaria del badge (`open`/`full`) del mockup desde ahora; cuando exista la columna real, solo cambia el origen del dato en el backend, sin tocar frontend. 2. **Filtro tipo_club → disciplina:** reemplazado por fidelidad al mockup, reusando el array `disciplinas` ya disponible. 3. **Filtro ciudad:** eliminado, no está en el mockup. 4. **Emoji del avatar:** mapeado por disciplina principal del club (🏆 triatlón, 🏃 running, 🚴 ciclismo, 🏊 natación, ⚡ multi-sport, 👥 si no hay disciplina), en vez de un emoji fijo único o mantener las iniciales — decisión del usuario pese a no tener el mockup real como referencia exacta. **Implementación (plan ejecutado vía Plan Mode, aprobado por el usuario):** - Backend `general.php`, `listClubs()`: agregado `'admision' => 'open'` con comentario `TODO` de hardcode temporal. - `Club.ts`: nuevos campos `disciplinas?: string[]`, `admision?: 'open' | 'full'`. - `club.service.ts`: `ClubFilters` — quitado `tipo_club`/`ciudad`, agregado `disciplina`/`admision`. - `athlete-directory.component.ts`: `clubFilters` actualizado, `tipoClubOpts` eliminado (reusa `disciplinaOpts` existente), `filterClubs()` filtra por `disciplinas` (mismo patrón `normalize()` que Atletas/Coaches) y por `admision`, nuevo método `toggleClubAdmision()`, `onClubCiudadChange()` eliminado. - `athlete-directory.component.html`: fila de filtros de Clubs con `ms-select` de Disciplina (idéntico a Atletas/Coaches) + País + 3 chips de Admisión (Todos / Admite miembros / Cupo completo). - `club-card` (ts/html/less): avatar emoji reemplaza iniciales, orden de info corregido a "N miembros" → "Ciudad, País" (antes invertido), badge de admisión (verde/gris), chips de disciplinas reales reemplazan el chip fijo "Club" + `tipoLabel`, línea "Capitán" removida de la tarjeta (se mantiene en el modal). - `club-profile-modal` (ts/html/less): mismos cambios de fondo — emoji, badge de admisión, sección nueva de chips "Disciplinas" siguiendo el mismo patrón que `athlete-profile-modal`. - Verificado con `npx tsc --noEmit` y `ng build --configuration=production` (exit 0, sin errores en archivos tocados). Confirmado visualmente correcto por el usuario en navegador. --- ### Modal de Coach — análisis y reconstrucción completa **Punto de partida:** el modal que se abre al hacer clic en una tarjeta de Coach es el mismo componente compartido con Atletas (`app-athlete-profile-modal`), pero mostraba datos con el layout de Atleta (género/edad, sin especialidad). **Bug encontrado de paso:** `onCoachCardClick()` guardaba el coach en `this.selectedCoach`, pero el template del modal estaba bindeado a `[athlete]="selectedAthlete"` — **nunca leía `selectedCoach`**. El modal de coach nunca mostró el dato correcto. Se corrigió unificando a una sola variable `selectedAthlete` + nueva propiedad `modalMode: 'atleta' | 'coach'`. **Proceso de validación con el usuario:** se armó una tabla comparativa spec-vs-implementación basada en capturas reales del mockup (tarjeta de Coach + 4 variantes del modal de Coach compartidas por el usuario), no solo en el `.md` del spec (que no documenta modales). Se detectó que el mockup real: - Muestra "Coach · XX años" (rol+edad) en vez de género+edad. - Tiene secciones condicionales según haya dato: Biografía, badge de contacto (Strava/Instagram). - Usa "Experiencia" (mismo dato que `nivel`) en vez de "Nivel". - Tiene una sección "Me especializo en" (especialidad) que no existía en el modal. - "Club/Equipo" siempre se muestra con fallback "Ninguno" en vez de ocultarse si está vacío. **Backend (`getListCoaches()`):** agregado `p.bio` y `ped.evento_objetivo` al SELECT (no se seleccionaban); agregado `biografia`, `eventos_objetivo`, `link_strava`, `link_instagram` al array de salida (estos 2 últimos ya se seleccionaban pero nunca se devolvían). **Frontend (`athlete-profile-modal`):** se agregó `@Input() mode` y se dividió el template en dos bloques completos (coach / atleta) vía `*ngIf...else` con `ng-template`, sin tocar el bloque de Atleta existente. Nuevo bloque de Coach: Biografía → Disciplina(s) con ícono → Experiencia → Evento objetivo 2026 → Club/Equipo (con "Ninguno") → Me especializo en → Disponibilidad (badge verde/gris). --- ### Homologación visual de las 3 tarjetas (Atletas / Coaches / Clubs) **Contexto:** las 3 tarjetas del directorio se veían visiblemente distintas entre sí (altura, fondo, tipografía, borde de avatar, tamaño de avatar). Se usó `club-card` como referencia visual por decisión del usuario ("se ve mejor"), con ajustes de dirección en 2 puntos donde el usuario corrigió mi lectura inicial. **Iteración 1 — cambios directos:** - Altura: unificada a un valor fijo (inicialmente 360px, tomando el que ya usaba Clubs). - Fondo: `#ffffff` → `#f0f0f0` en Atletas/Coaches (antes solo lo tenía Clubs), con el mismo hover a blanco. - Tipografía (parcial): tamaño/color del nombre y texto de datos igualados. - Borde del avatar: `3px solid #fff` + sombra agregado a Atletas/Coaches (ya lo tenía Clubs). **Corrección del usuario — dirección invertida en 2 puntos:** 1. **Tamaño de avatar:** en vez de agrandar Atletas/Coaches a 110px (mi sugerencia inicial), el usuario pidió **achicar el de Clubs a 76px** para igualar a Atletas/Coaches. 2. **Alineación vertical del contenido:** el usuario corrigió que el patrón correcto es el de Atletas/Coaches (contenido anclado arriba, footer con `margin-top:auto` empujado al fondo) — esto evita que los avatares se desalineen entre tarjetas con distinta cantidad de contenido. **Clubs era el que estaba mal** con `justify-content:center`. Se restructuró `club-card` (HTML + LESS) para adoptar el mismo patrón: se quitó `justify-content:center`, se sacó el wrapper `&__info`, se agregó `&__footer` con `margin-top:auto` conteniendo badge+chips, igual que Atletas/Coaches. **Simulación de altura (a pedido explícito del usuario, antes de tocar código):** se calculó a mano el alto de cada tarjeta con TODOS sus elementos opcionales activos a la vez (padding + gaps + altura de línea de texto + badges/chips), usando los valores reales de cada `.less`: - Atleta (máx: 2 badges + meta 2 líneas + chips disciplina): **249px** - Coach (máx: badge + label especialidad + chips): **253px** - Club (máx: 2 líneas detalle + badge + chips): **259px** ← el mayor - Alto aplicado: **280px** (259px + colchón de seguridad) en las 3 tarjetas y en sus skeletons de carga (`athlete-list`, `club-list`), reemplazando los 360px iniciales que dejaban demasiado espacio vacío. **Iteración 2 — fuente del nombre (persistía diferencia pese a tamaño/color iguales):** - Causa real: `.athlete-card__name` tenía `font-family: 'Montserrat'` forzado; `.club-card__name` no declara `font-family` propio y hereda la variable global `--main-font` (`'Raleway'`, definida en `styles.less:9`). Se quitó el override de Montserrat en Atletas/Coaches para que ambas hereden la misma fuente global. - Se auditó exhaustivamente (`grep` en todo `src/`) que no existe ninguna otra definición de `athlete-card__name` ni `club-card__name` en el proyecto, ni overrides vía `::ng-deep` desde `athlete-directory.component.less` ni desde los componentes `-list` padres — se descartó cualquier conflicto de cascada CSS. **Iteración 3 — badge de estado (no es lo mismo que "chips" de disciplina/especialidad):** - `font-weight`: Atleta/Coach usaba `700`, Club usa `600` → igualado a `600`. - Verde del badge positivo: Atleta/Coach usaba `rgba(5,150,105,...)`, Club usa `rgba(16,185,129,...)` → igualado (incluso con el mismo color de texto `#059669`, el tono de fondo/borde traslúcido era distinto). **Iteración 4 — refactor de marcado (mejora de mantenibilidad pedida explícitamente):** - `athlete-card__meta` usaba una sola `