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
@@ -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." };
}