From 1dcb2e9641d6bba64c47234dfb25022a8bc54f15 Mon Sep 17 00:00:00 2001 From: Vardamir Date: Wed, 26 Aug 2026 12:48:20 +0200 Subject: [PATCH 1/4] =?UTF-8?q?fix(agenda):=20hora=20fija=20en=20Madrid,?= =?UTF-8?q?=20bot=C3=B3n=20de=20ponente=20accesible=20y=20restricci=C3=B3n?= =?UTF-8?q?=20de=20menores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Formatear siempre las horas de actividades en Europe/Madrid (agendaTime.js), en vez de la zona horaria del navegador de quien visita la web, y añadir un aviso fijo en /agenda explicándolo. - Reemplazar el div sin semántica del ponente por un + {/each} + {:else if activity.short_description} +

{activity.short_description}

+ {/if} {#if activity.tag2}

@@ -138,22 +142,49 @@ word-break: break-word; } - .card .media { - margin-bottom: 0; + /* Botón de ponente: se distingue claramente de la tarjeta (fondo, borde y + cursor propios) para que quede claro que es un control aparte, con su + propio clic, y no parte del texto de la actividad. */ + .public-face { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; + margin: 0.5rem 0 0; + padding: 0.35rem 0.6rem; + background: rgba(67, 178, 220, 0.12); + border: 1px solid rgba(13, 59, 68, 0.18); + border-radius: 999px; + font: inherit; + text-align: left; + cursor: pointer; + transition: + background 0.18s ease, + border-color 0.18s ease; + } + + .public-face:first-child { + margin-top: 0; + } + + .public-face:hover, + .public-face:focus-visible { + background: rgba(67, 178, 220, 0.24); + border-color: rgba(13, 59, 68, 0.32); } - .media + .media { - margin-top: 0.25rem; - padding-top: 0.25rem; + .public-face:focus-visible { + outline: 2px solid #43b2dc; + outline-offset: 1px; } - .public-face .content { + .public-face .image { + flex-shrink: 0; + } + + .public-face__name { font-size: 0.96rem; /* 20% menos que el tamaño heredado (1.2rem) */ - text-align: left; - /* Sin límite de líneas: que use el espacio que haya. .media es flex, así - que también necesita min-width:0 para poder envolver en vez de - desbordar (mismo motivo que .card-header-title). El único límite real - es el overflow:hidden de .card/.card-content si de verdad no cabe. */ + color: #0d3b44; min-width: 0; overflow-wrap: break-word; word-break: break-word; diff --git a/frontend/src/routes/me/+page.server.js b/frontend/src/routes/me/+page.server.js index f431aa6..081263c 100644 --- a/frontend/src/routes/me/+page.server.js +++ b/frontend/src/routes/me/+page.server.js @@ -9,7 +9,7 @@ export async function load({ cookies, url }) { redirect(302, "/login"); } - const activeTab = url.searchParams.get("tab") || "datos"; + const activeTab = url.searchParams.get("tab") || "datos"; const [settings, user, allActivities] = await Promise.all([ fetchSingle("/setting"), @@ -21,17 +21,23 @@ export async function load({ cookies, url }) { redirect(302, "/login"); } - // Filter out activities already registered by this user. + // Filter out activities already registered by this user, and (for minors) + // activities marked as not allowed for them (`format`, ver ActivityCard.svelte). const registeredIds = new Set((user.activities_registered ?? []).map(activity => activity.id)); const queuedIds = new Set((user.activities_queued ?? []).map(activity => activity.id)); - const activities = allActivities.filter(activity => !registeredIds.has(activity.id) && (!queuedIds.has(activity.id))); + const isMinor = user.age === 'Menor'; + const activities = allActivities.filter(activity => + !registeredIds.has(activity.id) && + !queuedIds.has(activity.id) && + !(isMinor && activity.format) + ); return { settings, user, activities, activeTab }; - + } export const actions = { signIn: async ({ url, cookies }) => { - const activityId = url.searchParams.get("activityId"); + const activityId = url.searchParams.get("activityId"); const [settings, user, activity] = await Promise.all([ fetchSingle("/setting"), fetchBasic("/users/me", cookies), @@ -90,7 +96,7 @@ export const actions = { }, signOut: async ({ url, cookies }) => { - const activityId = url.searchParams.get("activityId"); + const activityId = url.searchParams.get("activityId"); const [settings, user, activity] = await Promise.all([ fetchSingle("/setting"), fetchBasic("/users/me", cookies), @@ -126,7 +132,7 @@ export const actions = { }, signOutQueued: async ({ url, cookies }) => { - const activityId = url.searchParams.get("activityId"); + const activityId = url.searchParams.get("activityId"); const [settings, user, activity] = await Promise.all([ fetchSingle("/setting"), fetchBasic("/users/me", cookies), @@ -160,4 +166,4 @@ export const actions = { return { success: false, message: error.message }; } } -}; \ No newline at end of file +}; From 2280bb03e604ad355f5dab95b6987792990e713f Mon Sep 17 00:00:00 2001 From: Vardamir Date: Wed, 26 Aug 2026 12:49:56 +0200 Subject: [PATCH 2/4] style(forms): centralizar estilos de formulario en app.scss y bloquear alta de usuarios MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mover a app.scss los estilos que login/+page.svelte y forgot-password/+page.svelte duplicaban en local (tipografía de inputs/textarea/select y placeholders, botones, envoltura de .button.is-text), y extenderlos a .help para mensajes no-error. - Bloquear /register, /registro, /get-link y /obtener-enlace con un redirect fijo a /no-disponible en +layout.server.js, independiente del flag de Strapi. - Ajustar el texto de reset_password_sent en es.json. Co-Authored-By: Claude Sonnet 5 --- frontend/messages/es.json | 2 +- frontend/src/app.scss | 46 +++++++++++++++++++ frontend/src/routes/+layout.server.js | 9 ++++ .../src/routes/forgot-password/+page.svelte | 17 ------- frontend/src/routes/login/+page.svelte | 28 ----------- frontend/src/routes/register/+page.server.js | 2 +- 6 files changed, 57 insertions(+), 47 deletions(-) diff --git a/frontend/messages/es.json b/frontend/messages/es.json index 391a1ae..741ed6c 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -56,7 +56,7 @@ "privacy_policy": "Política de privacidad", "register": "Registro", "registration": "Inscripción", - "reset_password_sent": "Si tu correo está registrado, hemos enviado un mensaje con un enlace para establecer una contraseña nueva.", + "reset_password_sent": "Si tu correo está registrado, se enviará un mensaje con un enlace para establecer una contraseña nueva.", "send": "Enviar", "speakers": "Invitadas", "sponsors": "EstelTienda", diff --git a/frontend/src/app.scss b/frontend/src/app.scss index da12dc6..26dfdfc 100644 --- a/frontend/src/app.scss +++ b/frontend/src/app.scss @@ -82,6 +82,52 @@ $control-border-width: 0.125rem; // 2px font-family: $font-family; } +// Controles de formulario (input/textarea/select): texto y placeholder con +// la tipografia secundaria (Lora) del cuerpo. Los navegadores no hacen que +// estos elementos hereden la fuente del body por defecto, así que hay que +// fijarlo explícitamente; se hace aquí una sola vez para toda la web en vez +// de repetirlo formulario a formulario. +.input, +.textarea, +.select select { + font-family: $font-family-secondary; +} + +.input::placeholder, +.textarea::placeholder { + font-family: $font-family-secondary; +} + +// Frases-enlace bajo los campos de un formulario (p.ej. "¿Olvidaste tu +// contraseña? Pulsa aquí."): tipografia secundaria, centradas como bloque y +// adaptadas al ancho del contenedor para que el texto pase a otra linea si +// no cabe en una sola, en vez del inline-flex sin ajustar de Bulma. +.button.is-text { + display: block; + width: 100%; + height: auto; + min-height: 2.5em; + white-space: normal; + text-align: center; + line-height: 1.3; + font-family: $font-family-secondary; +} + +// Botones: texto con la tipografia primaria (Cinzel) en vez de heredar la +// secundaria (Lora) del body. Se usa la custom property que expone Bulma +// (--bulma-family-primary) en vez de fijar el valor a fuego, siguiendo el +// mismo patron ya usado en varios botones sueltos del sitio. +.button { + font-family: var(--bulma-family-primary, #{$font-family-primary}); +} + +// Ayudas de formulario (.help): las de error de validacion (is-danger) +// mantienen el rojo de Bulma; el resto (mensajes informativos/de exito) +// usan el color secundario del sistema de diseno. +.help:not(.is-danger) { + color: $color-secondary; +} + .header--large { @include ec-typography(1.2, 3em, $font-family-secondary); @include mixins.tablet { diff --git a/frontend/src/routes/+layout.server.js b/frontend/src/routes/+layout.server.js index 9adccec..19827da 100644 --- a/frontend/src/routes/+layout.server.js +++ b/frontend/src/routes/+layout.server.js @@ -2,6 +2,11 @@ import { redirect } from '@sveltejs/kit'; import { fetchSingle } from '../services/api'; import { getUserCard } from '../services/users'; +// Rutas forzadas a /no-disponible ahora mismo, sin depender de ningún flag +// de Strapi (independiente de ROUTE_VISIBILITY_FLAGS de más abajo). Quita +// una entrada de aquí para que esa ruta vuelva a regirse solo por su flag. +const FORCE_BLOCKED_ROUTES = ['/registro', '/register', '/get-link', '/obtener-enlace']; + const ROUTE_VISIBILITY_FLAGS = { // Añade aquí las páginas que quieras bloquear cuando su flag esté en false. // Formato: @@ -41,6 +46,10 @@ function getVisibilityFlag(pathname) { } export async function load({ cookies, url }) { + if (FORCE_BLOCKED_ROUTES.includes(url.pathname)) { + redirect(302, '/no-disponible'); + } + let data = {}; const [settingsEntry, siteEntry] = await Promise.all([ diff --git a/frontend/src/routes/forgot-password/+page.svelte b/frontend/src/routes/forgot-password/+page.svelte index 12f9c05..6dbaf24 100644 --- a/frontend/src/routes/forgot-password/+page.svelte +++ b/frontend/src/routes/forgot-password/+page.svelte @@ -60,20 +60,3 @@ - \ No newline at end of file diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte index 9b4e872..93d1732 100644 --- a/frontend/src/routes/login/+page.svelte +++ b/frontend/src/routes/login/+page.svelte @@ -78,31 +78,3 @@ - - diff --git a/frontend/src/routes/register/+page.server.js b/frontend/src/routes/register/+page.server.js index b64dbb5..df7809e 100644 --- a/frontend/src/routes/register/+page.server.js +++ b/frontend/src/routes/register/+page.server.js @@ -38,4 +38,4 @@ export const actions = { const result = (response ? "éxito" : error?.message); return message(form, result); } - }; \ No newline at end of file + }; From f2d07e193be1eafd276be30d32eb4a52d5770dcb Mon Sep 17 00:00:00 2001 From: Vardamir Date: Wed, 26 Aug 2026 12:55:45 +0200 Subject: [PATCH 3/4] =?UTF-8?q?chore:=20limpiar=20repo=20de=20archivos=20i?= =?UTF-8?q?nnecesarios=20para=20producci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Versionar .gitignore de la raíz (antes existía solo en local, sin aplicarse a nadie más que clonara el repo) y ampliarlo: sistema operativo, node_modules/.env sueltos en la raíz, editores, y la configuración local de Claude Code (.claude/). - Quitar del repo .DS_Store (basura de Finder, y se borra también de disco) y package-lock.json de la raíz (lockfile vacío sin ningún package.json que lo use, sobra). Co-Authored-By: Claude Sonnet 5 --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 20 ++++++++++++++++++++ package-lock.json | 6 ------ 3 files changed, 20 insertions(+), 6 deletions(-) delete mode 100644 .DS_Store create mode 100644 .gitignore delete mode 100644 package-lock.json diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 8d7547d9ffef1aa43e09388c716e40e2abb12f28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKO>fgc5S?uUwyBVE0I8P>OI*{?LJL$}(lj|#0uD8T1E65nrdV~oQS3BAQ6!(? z$8hDB@LxE=o83*64>)i^i0(+UZ+7Ry-Y0q1OGKhQjCYCZMC3vlYd0|bLfFr`B5P*F z0SZ+kp`7C5OrA#BO0-?D3|I#KH3s{)o<9N=7^EUV(M?o2u!=fDyUi0jQlvy;j!{}w2jC%E3M=~$N zG*1R9A&nD+ym^)8u^hGKFpmqB8)%2;xn8fnIUeslZus{P4kitMy!&8pzv1scdN`SQ z?(Mtxo}Bc~vx{7QQtwRyJxa+h%vbm51d)f-p=ZD*-qLjFS^e)z8JJqR0t}e%!QHq5d$n7%3|I#Ki~;Ts zK9sR#u+pen2L^Qo0M@Xq1UCOPaE@=VWw6o+4@BrxpiUL$iXn75)_oJ#GFWNU=_JhM zLzpKEb3+m8>8S6ka1t$zcC-vw1{N7u*V8(m|J~o;{}+?&%ramZ_^%icwNB7!V@u|2 yUD+I;wLbJ6l!fCejVc9$IgXXVNAV_934A^qz?Q*EBUT{xM?leF2g|@;W#A_bbd?1F diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee55cb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Scripts locales de arranque (Windows), no se versionan +*.bat + +# Sistema operativo +.DS_Store +Thumbs.db + +# Dependencias y variables de entorno (cada subproyecto ya las ignora, +# se cubre también aquí por si se generan en la raíz) +node_modules/ +.env +.env.* +!.env.example + +# Editores +.vscode/ +.idea/ + +# Configuración local de Claude Code (no necesaria para producción) +.claude/ diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index bf7077f..0000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "e2026-main", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} From af2067bd27d4d55403142fc3cb54257d0dde9def Mon Sep 17 00:00:00 2001 From: Vardamir Date: Wed, 26 Aug 2026 16:08:48 +0200 Subject: [PATCH 4/4] =?UTF-8?q?dev:=20activar=20inscripci=C3=B3n=20a=20act?= =?UTF-8?q?ividades=20en=20local=20sin=20tocar=20el=20flag=20de=20Strapi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/routes/me/+page.server.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/frontend/src/routes/me/+page.server.js b/frontend/src/routes/me/+page.server.js index 081263c..5dda553 100644 --- a/frontend/src/routes/me/+page.server.js +++ b/frontend/src/routes/me/+page.server.js @@ -1,7 +1,13 @@ import { redirect } from '@sveltejs/kit'; +import { dev } from '$app/environment'; import { fetchCMSData, fetchCollection, fetchSingle, fetchBasic } from '../../services/api'; import { isAuthorizedUser } from '../../services/users'; +// En local (`npm run dev`) forzamos la inscripción a actividades activa para poder +// probarla sin tener que tocar el flag en Strapi. En producción no tiene efecto. +const withActivityRegistrationOverride = (settings) => + dev ? { ...settings, show_activity_registration: true } : settings; + /** @type {import('./$types').PageLoad} */ export async function load({ cookies, url }) { @@ -11,11 +17,12 @@ export async function load({ cookies, url }) { const activeTab = url.searchParams.get("tab") || "datos"; - const [settings, user, allActivities] = await Promise.all([ + const [rawSettings, user, allActivities] = await Promise.all([ fetchSingle("/setting"), fetchBasic("/users/me?populate[activities_registered][filters][publishedAt][$notNull]=true&populate[activities_registered][populate][track]=true&populate[activities_queued][filters][publishedAt][$notNull]=true&populate[activities_queued][populate][track]=true&populate[activities_staff][filters][publishedAt][$notNull]=true&populate[activities_staff][populate][track]=true", cookies), fetchCollection("/activities?filters[needs_registration][$eq]=true&sort=title:asc", cookies), ]); + const settings = withActivityRegistrationOverride(rawSettings); if (!user) { redirect(302, "/login"); @@ -38,11 +45,12 @@ export async function load({ cookies, url }) { export const actions = { signIn: async ({ url, cookies }) => { const activityId = url.searchParams.get("activityId"); - const [settings, user, activity] = await Promise.all([ + const [rawSettings, user, activity] = await Promise.all([ fetchSingle("/setting"), fetchBasic("/users/me", cookies), fetchSingle(`/activities/${activityId}?populate[registered_users][count]=true`, cookies), ]); + const settings = withActivityRegistrationOverride(rawSettings); if (!settings.show_activity_registration) { return { success: false, message: "La inscripción a actividades no está activada." }; } @@ -97,11 +105,12 @@ export const actions = { signOut: async ({ url, cookies }) => { const activityId = url.searchParams.get("activityId"); - const [settings, user, activity] = await Promise.all([ + const [rawSettings, user, activity] = await Promise.all([ fetchSingle("/setting"), fetchBasic("/users/me", cookies), fetchSingle(`/activities/${activityId}`, cookies), ]); + const settings = withActivityRegistrationOverride(rawSettings); if (!settings.show_activity_registration) { return { success: false, message: "La inscripción a actividades no está activada." }; } @@ -133,11 +142,12 @@ export const actions = { signOutQueued: async ({ url, cookies }) => { const activityId = url.searchParams.get("activityId"); - const [settings, user, activity] = await Promise.all([ + const [rawSettings, user, activity] = await Promise.all([ fetchSingle("/setting"), fetchBasic("/users/me", cookies), fetchSingle(`/activities/${activityId}`, cookies), ]); + const settings = withActivityRegistrationOverride(rawSettings); if (!settings.show_activity_registration) { return { success: false, message: "La inscripción a actividades no está activada." }; }