--- asset_id: MIN-EL-Gustavo-20260629-v01 tipo: MIN — Minuta del día owner: Gustavo sherpa: GusX ratificador: Gustavo fecha: 2026-06-29 intellibank: IB-EL-EmpowerLabs subbank: EQ-EL-Equipo proyecto: RCH — Rebelocity Club · perfil-usuario --- ## Qué se hizo ### Sección "Mi deporte" — perfil-usuario.component - **Análisis comparativo mockup vs componente** en la sección "Mi deporte": identificadas 3 brechas (relay, bloque club, toggle registrar club). - **Niveles corregidos:** array `niveles` actualizado — "Amateur" → "Aficionado", "Competitivo" → "Avanzado", añadido "Coach". - **Gran objetivo:** array `eventosObjetivo` reemplazado con las 10 opciones del mockup (Triatlón Sprint, Olímpico, 70.3 Ironman, Ironman completo, Medio maratón, Maratón, Gran fondo ciclismo, Open Water, Otro, Todos). Añadido getter `eventosOtroMode` + input condicional. "Todos" excluye "Otro" del bulk-select. - **Validación de rol:** añadido `case 'rol'` en `changeValueUser` — al cambiar a Coach limpia `busco_coach`; al salir de Coach limpia `acepta_atletas`, `especialidad` y `especialidad_otro`. - **Campo `evento_objetivo_otro`:** flujo completo implementado — columna SQL (`VARCHAR(200) AFTER evento_objetivo`), extracción param, UPDATE, INSERT, queryParams y SELECT en `admin.php`. Angular ya tenía el binding. - **Bloque Relay — flujo completo:** - Toggle "Busco equipo para Relay" visible para todos los roles. - Bloque condicional con 3 campos single-select: Disciplina que cubro, Tipo de relay, Carrera (con fecha). - Script SQL: 4 columnas con prefijo `relay_` (`busco_relay`, `relay_disciplina`, `relay_tipo`, `relay_carrera`). - `admin.php` actualizado: extracción, UPDATE, INSERT, queryParams, SELECT. - Angular TS: arrays `relayDisciplinas`, `relayTipos`, `relayCarreras` + `busco_relay` en switch 0/1. - Estilos LESS: `.prof-toggle-row--relay` (gold accent), `.prof-relay-block` (contenedor gold tenue), `.prof-chip__fecha`. ### Backend — admin.php - Confirmado como controlador del endpoint `POST /api/admin/user/profile/upd` → tabla `perfil_extra_data`. - Referencia guardada en memoria para futuras sesiones. --- ## Decisiones - `evento_objetivo_otro` se guarda como campo separado (mismo patrón que `especialidad_otro`); `'Otro'` sí se adjunta al array `evento_objetivo`. - Campos relay se quedan en `perfil_extra_data` (1 fila por usuario) con prefijo `relay_` para facilitar futura migración a tabla propia. - Diseño relay sigue opción 2 del mockup: bloque encadenado bajo el toggle, acento gold en ambos. --- ## Continuación — sesión tarde 2026-06-29 ### Bloque "Mi club" — perfil-usuario (punto 2) - **Análisis de tablas:** `club_members` (status, resolved_at) + `clubs` (name, representative_full_name). Identificados los 3 campos a mostrar: nombre del club, responsable, fecha de registro (`resolved_at`). - **Nota de enum documentada en Angular:** comentario en `perfil-usuario.component.ts` sobre los 4 valores válidos de `club_members.status` (pending / active / rejected / left) para evitar confusión con tinyint. - **Backend — 2 métodos nuevos en `general.php`:** - `getMyMembership($userId)` — JOIN `club_members` + `clubs`, filtra `status = 'active'`, regresa `null` si no hay membresía. - `leaveClub($userId)` — UPDATE `status = 'left'`, valida `rowCount()`. - **Backend — 2 rutas nuevas en `index.php`:** - `POST /rch/clubs/my-membership` - `POST /rch/clubs/leave` - **`ClubService` Angular:** métodos `getMyMembership(userId)` y `leaveClub(userId)` agregados. - **Componente Angular:** propiedad `myClub`, método `loadMyMembership()` (llamado al cargar solo en RCH + perfil propio), método `onLeaveClub()` con swal de confirmación (`alertOptions`). - **HTML:** bloque `prof-club-block` con badge, nombre, responsable, fecha (pipe `date:'MMM yyyy'`) y botón "Salir del club". Visible solo cuando `myClub !== null`. - **LESS:** `.prof-club-block` con acento azul primario, diferenciado del gold del relay. - **SQL corrido en BD:** `ALTER TABLE club_members MODIFY COLUMN status ENUM('pending','active','rejected','left')`. - **Resultado:** funcional — muestra info, botón salir con confirmación swal, oculta bloque tras confirmar salida. --- ### Toggle "Quiero registrar mi club" — punto 3 - **Toggle row implementado:** visible solo cuando `myClub === null` (usuario no pertenece a ningún club activo). - **Estado local:** propiedad `wantsClubRegister = false` — sin llamada al backend, solo controla visibilidad en Angular. - **Hint condicional:** cuando el toggle está ON aparece debajo: `Edita la información de tu club en la pestaña Mi club →`. - **Estilos:** `.prof-club-register-hint` + `&__link` en LESS, consistente con el lenguaje visual del componente. - **`goToMiClubTab()`:** método stub listo — navega al modelo `'mi-club'` en `opsList` cuando exista. --- ## Continuación — sesión noche 2026-06-29 ### Componente `app-mi-club` — integración en `perfil-usuario` - **HTML limpiado:** eliminados FAB `fab-club-register` y bloque `` de `perfil-usuario.component.html`. - **Tab "Mi club" conectado:** placeholder `` reemplazado por `` con todos sus `@Input()`: `captainId`, `captainNombre`, `captainEmail`, `captainTelefono`, `countryList`, `domainConfig`, `autor`. - **`app.module.ts`:** `MiClubComponent` ya declarado desde sesión anterior — confirmado sin cambios adicionales. --- ### Estilos del componente `app-mi-club` — alineación con mockup - **Problema detectado:** `mi-club.component.html` usaba clases `prof-*` (definidas solo en `perfil-usuario.component.less` con encapsulación Angular por componente) → los elementos no tenían estilo real en la vista. - **Convención aplicada:** renombradas todas las clases `prof-*` → `mc-*` dentro del template del componente, siguiendo el patrón ya establecido (`crm-*` en `club-register-modal`, `prof-*` en `perfil-usuario`). - **`mi-club.component.less` reescrito** desde cero replicando fielmente el sistema visual del mockup (ya traducido a tema claro en `perfil-usuario.component.less`): - `mc-card` / `__hd` / `__icon` / `__title` / `__sub` / `__body` — cards blancas, border-radius 12px, borde `rgba(0,0,0,0.07)` - `mc-row`, `mc-row--2` — grid 2 columnas responsivo - `mc-field`, `mc-label`, `mc-req`, `mc-opt`, `mc-hint` — tipografía idéntica al sistema `prof-*` - `mc-chips`, `mc-chip`, `mc-chip--on` — acento `var(--main-primary-bg, #234E9C)` (reemplazó el blue genérico `#4A80D6`) - `mc-toggle-row`, `mc-toggle-info`, `mc-toggle-name`, `mc-toggle-desc` - `mc-info-box`, `mc-members-hd`, `mc-btn-add`, `mc-add-bar`, `mc-add-row`, `mc-btn-confirm` - `mc-member-list`, `mc-member-row`, `mc-member-av`, `mc-member-name`, `mc-member-since`, `mc-btn-remove` - `mc-empty`, `mc-save-bar` / `__hint` / `__btns` — simplificado para igualar `prof-save-bar` (sin caja, punto naranja `●` `#D97706`) - `em-switch::ng-deep` — mismos overrides que en el componente padre --- ### DB — `ALTER TABLE clubs` (ya ejecutado) Tres columnas nuevas + cuatro restricciones relajadas para soportar el formulario lean: ```sql ALTER TABLE clubs ADD COLUMN description TEXT NULL AFTER name, ADD COLUMN disciplines TEXT NULL AFTER description, ADD COLUMN visible_in_directory TINYINT(1) NOT NULL DEFAULT 1 AFTER social_url; ALTER TABLE clubs MODIFY club_type enum('tri_club','cycling_club') COLLATE utf8mb4_unicode_ci DEFAULT NULL, MODIFY country varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, MODIFY city varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL, MODIFY active_members int UNSIGNED NOT NULL DEFAULT 0; ``` - `disciplines` se almacena como JSON string (`["Triatlón","Running"]`), serializado/deserializado con `json_encode`/`json_decode` en PHP. - `club_type`, `country`, `city` pasan a nullable para no bloquear el flujo lean (formulario nuevo no los requiere). --- ### Backend — ruta + función lean `registerClub` - **Decisión:** crear ruta y función nuevas en lugar de modificar `addClub` existente, para evitar conflictos con el wizard anterior. - **`index.php`:** nueva ruta `POST /rch/clubs/register` — valida solo `nombre_equipo` + `capitan.id`. - **`general.php`:** nueva función `registerClub($values)` — estructura idéntica a `addClub` pero sin campos wizard (`tipo_club`, `miembros_activos`, `is_ironman_member`, `contact_preference`, `whatsapp`, `atrae_red`, `comentarios`, `estimados`). Incluye `description`, `disciplines` (JSON encode), `visible_in_directory`. - **`listClubs()`:** SELECT y mapeo de respuesta extendidos con `description`, `disciplines` (json_decode → array), `visible_in_directory`. --- ### Backend — `getMyClub` + `updateClub` Motivación: el formulario creaba un registro nuevo en cada guardado porque `save()` siempre llamaba `registerClub()` (INSERT puro), sin distinguir crear de editar. - **`index.php`:** dos rutas nuevas: - `POST /rch/clubs/my-club` — recibe `captain_id`, llama `getMyClub()` - `POST /rch/clubs/update` — recibe `id` + `nombre_equipo`, llama `updateClub()` - **`general.php` — `getMyClub($captainId)`:** SELECT con `WHERE captain_user_id = :captain_user_id ORDER BY created_at DESC LIMIT 1`. Devuelve `null` si no existe club, o el objeto con los campos del formulario. - **`general.php` — `updateClub($values)`:** UPDATE de los 8 campos editables (`name`, `description`, `disciplines`, `country`, `city`, `social_url`, `visible_in_directory`) por `id`. Sin transacción (operación simple, sin tablas relacionadas). --- ### Frontend — `ClubService` + `MiClubComponent` **`club.service.ts`** — 3 métodos nuevos: - `registerClub(data)` → `POST /rch/clubs/register` - `getMyClub(captainId)` → `POST /rch/clubs/my-club` - `updateClub(data)` → `POST /rch/clubs/update` **`mi-club.component.ts`** — flujo create/edit completo: - `clubId: number | null = null` — centinela de modo (null = crear, número = editar) - `loading: boolean = true` — para futura gestión de estado de carga - `ngOnInit()` llama `getMyClub(captainId)`: - Si responde con club → precarga `form` + `formBkp` + `clubId` (modo edición desde el primer render) - Si responde `null` → form vacío (modo creación) - `save()` bifurcado: - `clubId === null` → `registerClub(payload con capitan)` → guarda `res.detail.id` en `clubId` → `formBkp` sincronizado - `clubId !== null` → `updateClub({ id, ...form })` → `formBkp` sincronizado - En ambos casos: `hasChanges` vuelve a `false` → barra de guardado desaparece --- ## NEXTs Completados en esta sesión: - [x] Correr script SQL en BD: columna `evento_objetivo_otro` + 4 columnas `relay_*`. - [x] Implementar bloque "pertenece a un club" (read-only + botón salir) en "Mi deporte". - [x] Implementar toggle "Quiero registrar mi club" con lógica de visibilidad y hint condicional. - [x] Pestaña `mi-club` en `opsList` + vista conectada con `app-mi-club` y sus inputs. - [x] Estilos de `app-mi-club` alineados al mockup (sistema `mc-*` propio del componente). - [x] DB: columnas `description`, `disciplines`, `visible_in_directory` + restricciones relajadas. - [x] Backend lean: rutas + funciones `registerClub`, `getMyClub`, `updateClub`. - [x] Frontend: flujo create/edit completo con `clubId` como centinela de modo. Pendientes abiertos → ver backlog vivo: 📋 [`BCK-EL-Gustavo-RCH-v01.md`](../BCK-EL-Gustavo-RCH-v01.md)