Rework de los bonus diarios + códigos canjeables — gana fragmentos cada día y canjea códigos de la comunidad
Dos cambios para la comunidad llegan juntos. (1) El bonus diario ya no reparte sobres gratis en un esquema plano — ahora otorga fragmentos cada día en un ciclo [3,3,3,5,5,5,8] y completar una semana consecutiva concede un bonus escalonado de sobres (1 → 2 → 3 → 4, tope en 4). Si fallas un día la racha vuelve a cero — completa la semana para llevarte el bonus de anclaje. (2) Nueva sección en el tab de Perfil para canjear códigos (p. ej. WELCOME-2026, códigos de partners, sorteos de creadores). Los códigos pueden conceder cualquier combinación de sobres, fragmentos y piezas por rareza; el admin los crea desde la nueva pestaña Códigos del panel de administración, con tope de usos y fecha de caducidad opcionales.
Bonus diarios
- Ciclo diario de fragmentos: [3, 3, 3, 5, 5, 5, 8] según la posición en tu racha. Los fragmentos se acumulan para forja y conversión a sobres.
- Bonus de sobres al completar la semana en el día 7 / 14 / 21 / 28 / …: +1 sobre la primera semana, +2 la segunda, +3 la tercera, +4 desde la semana 4 (tope).
- Si fallas un día → la racha se reinicia a 0; el siguiente bonus de 'semana completada' vuelve a +1. Los bonus anteriores de +3 / +5 / +10 sobres en el día 7 / 14 / 28 quedan retirados.
- UI del calendario actualizada: tira de 7 días con la semana actual y una etiqueta 'Sem N' que muestra las semanas completadas acumuladas.
- Las rachas existentes se reiniciaron a cero al desplegar para que todos arranquen la nueva programación en igualdad — no se traspasa crédito de media semana.
Códigos canjeables
- Nueva sección 'Canjear' en el tab de Perfil — introduce un código, mira la recompensa y revisa tus últimos 20 canjes.
- Cada usuario puede canjear cada código como máximo una vez (un índice único por (código, usuario) garantiza que no haya canjes duplicados).
- Los códigos pueden incluir sobres, fragmentos y piezas por rareza en cualquier combinación; el admin define la recompensa al crearlos.
- Tope total de canjes opcional (p. ej. 'solo los primeros 100 canjes') y fecha de caducidad por código (p. ej. 'válido hasta el viernes').
- Limitado a 5 intentos por minuto y usuario para descartar fuerza bruta por tipeo. Toda creación / edición / borrado de códigos por el admin queda en el audit log.
Sweep de traducciones en el juego
- Las etiquetas de carta (ATK / DEF / HP / PM / Pasiva), los badges (¡NUEVA!, ✦ Nueva, En rotación pronto, Legado) y las fases (Bebé / Fase 1-3 / MAX) ahora aparecen en tu idioma en colección, revelación de gacha e instancias en batalla.
- Campana de notificaciones + toasts de guerra de gremio: cada línea, sufijo de 'hace X', badge de victoria/derrota/empate y resumen de recompensas pasa ya por el idioma, con plurales correctos en polaco y ruso.
- Cromo de la sala de batalla (copy del lobby, overlay de rotar dispositivo, etiqueta de la mano, prefijo del rival, pista de objetivo, enlace Cancelar) y la overlay del anuncio recompensado (carga, motivos de error, botón de canjeo, aviso de mantener visible) ya están totalmente localizados.
- Flujo de confirmación de la galería de copias (progreso de nivel, confirmaciones de disolución, avisos de mazo bloqueado y trade pendiente, contador del rate limit, previsualización de fragmentos / piezas) — cada cadena que estaba solo en inglés ahora está por idioma.
- Fallback del ErrorBoundary (botón Recargar + copy seguro) y los tooltips de la WalletBar (sobres / fragmentos / pity / bonus diario disponible) ahora se muestran en el idioma activo en los 12 locales soportados.
- Pantallas previas al login traducidas de punta a punta: login / registro / 2FA / contraseña olvidada / restablecer contraseña (títulos, subtítulos, placeholders, botones, mensajes de error y éxito, copy del gate de edad y ToS, aviso de email sin verificar con temporizador).
- Overlay de onboarding del primer arranque (3 pasos activos + indicadores, botones saltar y siguiente, todas las descripciones de tipos de carta / rareza / holo / fase / habilidad) ahora en tu idioma para que el flujo del primer sobre llegue nativo.
- Frases con guiño durante la apertura de sobres: 58 frases únicas × 12 idiomas (~700 escrituras creativas) para que el texto que rota mientras se abre un sobre se lea en tu idioma en sobres estándar, dorados y diamante.
Pulido global de idiomas
- El banner de cookies ahora cambia de idioma correctamente cada vez que cambias de idioma — antes se quedaba en el primer idioma visitado a partir del segundo cambio (p. ej. /es → /en cambiaba, pero /en → /it dejaba el banner en inglés sobre la página en italiano).
- Cartas Destacadas (la página de showcase) traducida de punta a punta a los 12 idiomas soportados — cada nombre de carta y cada blurb de marketing de 100-150 palabras ahora es nativo en lugar de caer al español para visitantes que no son de habla hispana ni inglesa.
- Entradas de la página de Novedades: esta entrada y las dos anteriores (2026-04-28 — traducción del texto de las cartas + clarificación del tope de Disolver en masa) ahora también están totalmente en idioma nativo en los 12 locales. Las entradas más antiguas caen al inglés con elegancia mientras las vamos completando en próximas releases.
- Modal de detalles de carta: la etiqueta 'X copias' y el tope de Disolver en masa ahora reflejan la carta que estás viendo realmente — antes, al abrir una carta y navegar a otra en la misma sesión del modal se mostraba el conteo de la PRIMERA carta para cada carta posterior, dando números equivocados como '4 copias' en una pila de la que realmente tenías 19.
El texto de las cartas ahora en tu idioma — nombres, flavor y descripciones de habilidades traducidos para las 113 cartas
Hasta hoy la interfaz estaba localizada pero el contenido de las cartas se mantenía en inglés: nombres, líneas de flavor, nombres y descripciones de habilidades aparecían en inglés sin importar el idioma que tuvieras elegido. Hemos traducido las 113 cartas al español (España + fallback LATAM), francés, italiano, alemán, portugués (BR + fallback PT), polaco, neerlandés, ruso y turco — el nombre canónico más el flavor, más los nombres y descripciones de habilidades activas y pasivas cuando existen. La carga del servidor en inglés sigue siendo la autoritativa para la base de datos; lo que ves en pantalla se superpone en cliente según tu idioma, así la identidad de la carta se mantiene al cambiar idioma o intercambiar. Si falta alguna traducción para algún campo de alguna carta, cae al texto en inglés; nunca a un hueco.
Qué cambió
- Los 113 nombres de carta traducidos a los 11 idiomas objetivo (es, es-419, fr, it, de, pt-BR, pt-PT, pl, nl, ru, tr) — pt-PT y es-419 están ahora completamente poblados como variantes nativas, no como fallbacks.
- PT-PT (portugués europeo): grafías regionales (Fênix→Fénix, Tectônico→Tectónico, Gêiser→Géiser, Fumaça→Fumo), vocabulario distinto (mordida→dentada, filhote→cria, pulinho→saltinho, quica→salta, terremoto→terramoto, demais→restantes, libera→liberta, bravo→zangado), colocación enclítica (se alimenta→alimenta-se, a se tornar→a tornar-se), e imperativos en forma tu (Você não passará→Tu não passarás).
- es-419 (español de Latinoamérica): expresiones naturales LATAM (Picado en Bomba→Bombardeo en Picada), evitar 'Concha' (vulgar en el Cono Sur) reemplazado por 'Caparazón' por seguridad, y una nota clara de que el voseo (AR/UY) no se aplica intencionalmente para que el catálogo se mantenga universal en LATAM.
- Líneas de flavor traducidas para cada carta — son 113 cadenas únicas × 9 idiomas.
- Nombres y descripciones de habilidades activas traducidas para las 60 criaturas que las tienen.
- Nombres y descripciones de habilidades pasivas traducidas para las 50 criaturas que las tienen.
- Conectado en cada sitio donde se renderiza una carta: rejilla de colección, modal de carta, comparador de cartas, deck builder, modal de forja, pestaña de forja, revelación de gacha, sala de draft, mano e instancias en batalla, tooltips al pasar el cursor, previsualizaciones al arrastrar, desplegables de propuesta de intercambio y la previsualización del intercambio.
Notas
- Las líneas de stats de objetos como '+5 ATQ.' o 'Fuego +3 ATQ.' se mantienen tal cual — son texto mecánico donde traducir no aportaría nada.
- La identidad de la carta (card_id, instance_id, semilla holo, ATQ/DEF/PV/PM) se preserva al superponer el idioma — solo cambian las cadenas mostradas. Los intercambios, mazos y repeticiones de batalla son 100 % compatibles entre idiomas.
El tope de Disolver en masa ahora se explica solo — ves por qué tu máximo es el que es
Feedback de jugadores tras el lanzamiento de Disolver en masa: el changelog decía "hasta 50 por lote" pero un jugador con solo 21 copias de una carta veía un tope de 20 y asumía que algo estaba roto. Nada lo estaba — el servidor garantiza que conservas al menos 1 copia de cada carta, así que una pila de 21 copias tope en 20 de una vez. El tope siempre fue mín(máx servidor 50, copias elegibles, copias totales − 1); solo era invisible. Ahora la etiqueta del botón de entrada dice exactamente cuál es tu tope de lote en formato "máx N de M elegibles", y un tooltip al pasar el cursor explica la restricción que limita (tope del servidor, elegibilidad, o conservar-1). La cabecera del panel de selección repite la restricción para que no tengas que adivinar.
Qué cambió
- El botón de entrada de Disolver en masa ahora dice "💠💠 Disolver en masa... (máx N de M elegibles)" en vez de solo "(M elegibles)" — N es el tope real de selección honrando tope del servidor + conservar-1.
- El tooltip al pasar el cursor sobre el botón muestra la razón vinculante: "máx servidor 50", "M elegibles", o "conservando 1 de T totales".
- La cabecera del panel de selección ahora añade la restricción vinculante tras el número del tope, así la razón se ve sin pasar el cursor.
- Comportamiento sin cambios: la regla conservar-1 siempre se aplicó en el servidor. Este cambio es puramente claridad UX — las mismas protecciones, etiquetas más transparentes.
Disolver en masa — destruye hasta 50 copias de una carta en una sola acción
Acumuladores, esto es para vosotros. Disolver copias comunes de una en una os quemaba la ventana de rate-limit por usuario — un jugador con 80 copias comunes de una sola carta no podía realmente limpiarlas en una sentada. La vista de detalle de carta muestra ahora un botón "Disolver en masa..." debajo del Disolver por copia que ya había, que aparece siempre que tengas al menos 2 copias elegibles de la carta que estás viendo. Haz clic, elige qué números de impresión destruir desde una rejilla de mosaicos (las copias bloqueadas, en mazo, o en intercambio activo aparecen atenuadas y desactivadas), y todo el lote se procesa en una sola confirmación y una sola ranura de rate-limit. El tope del servidor es 50 por llamada, así que la respuesta sigue siendo rápida. La vista previa de recompensa te muestra los fragmentos exactos y las piezas por rareza que recibirás antes de pulsar Destruir todo.
Qué cambió
- Vista de detalle de carta → nuevo botón "💠💠 Disolver en masa... (N elegibles)" bajo el Disolver por copia. Solo aparece cuando tienes ≥2 copias elegibles (elegibles = desbloqueadas + no-en-mazo + no-en-intercambio-activo).
- La rejilla de mosaicos muestra cada copia con su número de impresión y marcadores holo / bloqueada / en-mazo / en-intercambio. Las copias no elegibles aparecen atenuadas y desactivadas. La selección se limita a mín(50, elegibles, copias − 1) para que nunca puedas vaciar la pila completa.
- Controles "Seleccionar todo" y "Limpiar" más una vista previa en vivo "+X fragmentos / +Y piezas R" que refleja la UI de recompensa por copia. El modal de confirmación pregunta una vez antes de cualquier llamada de red: "¿Disolver N copias de \"Nombre de la Carta\"? +X frags +Y piezas R. [Cancelar] [Destruir todo]".
- Servidor: se consume una sola ranura de rate-limit sea cual sea el tamaño del lote — esa es la razón de ser de la función. La transacción completa es atómica (las 50 copias se disuelven juntas o ninguna).
- Seguridad ante intercambios pendientes: si alguna copia seleccionada está en un intercambio activo, el servidor avisa y ofrece un aviso "Cancelar e disolver" que reenvía la llamada con confirmación explícita. Sin sobrescritura silenciosa de un intercambio en curso.
- Seguridad de última copia: por carta, el servidor garantiza que conservas al menos una copia. Un lote que dejaría una pila a cero se rechaza con un error claro y no se borra ninguna fila.
Colección: filtro "Próximas a rotar" + clasificaciones públicas de torneos de gremios
Dos mejoras pequeñas pero visibles en calidad de vida. En la pestaña Colección, el desplegable de disponibilidad ganó una opción "Próximas a rotar" que filtra la rejilla a cartas que expiran en los próximos 30 días — el mismo umbral que la insignia que llevaba un tiempo apareciendo en esas cartas, así que filtro e insignia coinciden siempre. En la pestaña Torneo, los torneos de gremios muestran ahora su panel de clasificaciones a cualquier visitante — no solo a los participantes inscritos. Antes, abrías un torneo de gremios y veías participantes pero ningún cuadro a menos que tu gremio estuviese registrado. Ahora el panel público se renderiza para todos, leyendo los mismos datos que el servidor ya devolvía, así que puedes ver quién va liderando sin tener que registrarte primero.
Qué cambió
- Colección → desplegable de disponibilidad → nueva opción "Próximas a rotar" filtra cartas cuyo `available_until` esté dentro de los próximos 30 días. La fila de chips activos muestra un chip "Próximas a rotar" retirable mientras el filtro está activo.
- La ventana de 30 días es ahora una constante compartida única, así que el filtro y la insignia "Próximas a rotar" existente no pueden divergir.
- Pestaña Torneo → torneos de gremios → el panel de clasificaciones se renderiza también para visitantes no inscritos. Mismos datos que la vista de participante; el panel te muestra quién va liderando incluso antes de que tu gremio se inscriba.
- Los torneos individuales quedan sin cambios — el panel público solo se renderiza cuando `tournament_type === "guild"`.
Endurecimiento de build y CI — gates de calidad movidos al CI, waivers para el audit de dependencias, refresco diario de la IAB GVL — barrido de revisión hostil de 14 áreas completo
La última área del barrido de revisión hostil de 14 áreas — pipelines de build y gates de CI. La mayoría de los cambios son invisibles para los jugadores (viven en `.github/workflows/` y `scripts/`) pero mueven la calidad de "solo local con bypass de --no-verify" a "bloquean el merge en cada PR". La suite completa de vitest (~2700 tests), el type-check de TypeScript, el drift gate de Drizzle, la paridad de claves i18n, el pin de BullMQ, la validación de battle-config y el baseline de bare-db-execute corren ahora en CI en cada PR. El escaneo de CVE de producción ganó una puerta consciente de waivers que expone vulnerabilidades moderadas (el umbral anterior silenciaba seis). La IAB Global Vendor List que usa el banner de consentimiento se refresca ahora diariamente con un workflow programado que abre un PR cuando cambia la lista. Dos tests skipped de la suite de batalla llevan referencias rastreadas (T-201, T-202) y una nueva invariante hace fallar el build ante cualquier skip futuro sin tag para que no se acumule código muerto.
Qué cambió para los jugadores
- Nada visual. Todo el lote es tooling interno — workflows de CI, gates de audit de dependencias, higiene de build. La nota de cierre importa porque la revisión de 14 áreas sacó a la luz unos ~600 hallazgos en todo el código (auth, motor de batalla, esquema, gacha, API de admin, API pública, Game.tsx y tabs, UI de cartas/mazos/batalla, internals de perfil/gremios/torneos, anticheat, i18n, legal/consentimiento, observabilidad, infra de build/test) — cada arreglo visible para jugadores de las trece áreas anteriores ya salió en entradas anteriores del changelog. Esta entrada solo cierra el barrido.
- Lado operador: la IAB Global Vendor List que el banner de consentimiento usa para la disclosure por proveedor se refresca ahora diariamente con una GitHub Action programada que abre un PR cuando cambia la lista. Una GVL obsoleta era un riesgo de deriva de alcance de proveedor (peor caso: Limited Ads de AdSense en la UE). Una persona revisa cada PR de refresco antes del merge.
- Dos dependencias de producción con vulnerabilidades conocidas (next-intl + postcss-vía-next) están ahora formalmente documentadas en `context/refs/cve-waivers.md` con fechas de revisión explícitas (2026-07-25). El gate de CVE anterior las escondía subiendo el umbral de severidad; el nuevo gate expone todas las moderadas-y-mayores y falla el CI a menos que una fila de waiver las cubra. Visibilidad honesta en lugar de riesgo silencioso.
Estabilidad del servidor y privacidad en los logs — handlers de errores del proceso, retención de audit aplicada, nombres de jugador redactados de los logs
Una pasada por la pipeline de observabilidad y auditoría cerró el hueco entre lo que dice la política de privacidad y lo que el servidor hace de verdad. Nada cambia visualmente para los jugadores, pero algunas cosas mejoraron por debajo: el proceso de Node.js ahora registra y cuenta los errores no capturados en lugar de caerse en silencio y dejar que la plataforma lo reinicie sin explicación; la tabla audit_log se purga diariamente con un cron que aplica la ventana de retención de 90 días que promete la política de privacidad; los nombres de jugador se redactan ahora de las líneas de log del servidor (el Considerando 30 del RGPD trata los nombres como datos personales); y la pipeline de auditoría de eventos de seguridad ganó comprobaciones de durabilidad y huella de IP por acción para que una inspección regulatoria pueda reconstruir cualquier acción de cuenta con marca de tiempo, actor y origen.
Qué cambió
- El proceso de Node.js ahora registra un error no capturado antes de que la plataforma lo reinicie. Antes el servidor moría en silencio, el contenedor se reiniciaba solo, y depurar después requería adivinar a partir de las marcas de tiempo de muerte del contenedor. Ahora cada error fatal aterriza en el log estructurado con el stack trace completo y un contador para graficar las tasas de caída con el tiempo.
- La política de privacidad dice que los logs de auditoría se conservan 90 días. Antes nada lo aplicaba de verdad — las filas antiguas se acumulaban para siempre. Un nuevo cron diario (`/api/cron/audit-retention`) elimina lo que tenga más de 90 días en lotes, así la tabla se mantiene acotada y la promesa de la política es honesta.
- Los nombres de jugador se redactan ahora de las líneas de log del servidor. El Considerando 30 del RGPD trata los nombres de usuario como datos personales; la forma anterior de los logs incluía los campos winnerName / challengerName / opponentName tal cual en cada línea de fin de batalla. Pino los enmascara como [Redacted] antes de llegar al agregador.
- Lado operador: los endpoints /api/metrics y /api/health admiten ahora una puerta de token Bearer en producción para que los datos de observabilidad no queden expuestos en la internet abierta (configura METRICS_BEARER_TOKEN / HEALTH_TOKEN; en dev se tolera la ausencia). El endpoint de reportes de violación CSP (/api/csp-report) limita ahora por IP para que un spammer no pueda ahogar las alertas CSP reales.
Endurecimiento legal y de consentimiento — string TCF v2.3 oficial, auditoría duradera del consentimiento, edad + condiciones aplicadas en servidor, lista real de proveedores, borrado y exportación de datos ampliados
Una pasada por la superficie legal y de consentimiento cerró una lista larga de huecos de cumplimiento. Titulares: el string de consentimiento IAB TCF v2.3 es ahora el formato oficial bit-packed que producen las librerías @iabtechlabtcf/* en lugar de un shim base64(JSON) — el parser de Google AdSense rechazaba nuestro shim desde la fecha límite del 2026-03-01, lo que silenciosamente dejaba a los visitantes de la UE en Limited Ads. Cada elección del banner de consentimiento (Aceptar todas / Rechazar todas / Personalizar / retirar / aviso de version bump / reapertura) escribe ahora una fila duradera en audit_log para que podamos demostrar el consentimiento bajo una inspección del Art.7(1) RGPD. La confirmación de edad 16+ y la aceptación de las Condiciones en el registro se aplican ahora del lado del servidor mediante los additional fields de Better Auth — el checkbox solo cliente anterior se podía saltar por curl. La política de privacidad nombra ahora a los terceros reales (Arsys para alojamiento + Google AdSense para anuncios) en lugar de copia genérica — Postgres y Redis corren sobre infraestructura gestionada por el operador en Arsys y no son encargados independientes. El borrado de cuenta limpia ahora un conjunto mucho mayor de tablas con datos personales (amistades, pertenencia a gremios, entradas de torneo, tickets, historial de anuncios, semillas provably-fair, tokens de verificación y varias más), usa un sufijo de seudónimo aleatorio criptográfico que rompe la trazabilidad anterior con audit_log, se niega a recrear una solicitud de borrado para una cuenta ya borrada por completo, y limita los reintentos del cron para que un fallo transitorio no entre en bucle infinito. El artefacto de exportación de datos ganó seis nuevas secciones (pertenencia a gremios, torneos, amistades, tickets de soporte, historial de anuncios, historial de semillas provably-fair) y topes por sección para que un usuario con mucha colección no pueda hacer OOM al servidor. El banner de consentimiento es ahora un diálogo modal correcto con focus trap y aria-modal, el selector de idioma es visible en cada breakpoint (estaba oculto en móvil), y los baneos de admin entregan ahora una Declaración de Motivos del Art.17 DSA mediante notificación en la app con la regla infringida y una ruta de apelación. Además: una IAB Global Vendor List nueva incluida y un script de refresco diario para que los cambios de alcance de proveedores no se queden obsoletos, las cookies de primera parte de AdSense de verdad se borran al retirar el consentimiento (antes solo se desmontaba el tag de script), y un nuevo gate CSRF con token en los endpoints de borrado de cuenta y exportación de datos para que una página atacante con un formulario falsificado no pueda lanzar la cuenta atrás del borrado para un visitante con sesión iniciada. Versión de consentimiento subida de 3.1 a 3.2 — todos los visitantes verán el banner una vez en su próxima visita para confirmar bajo la lista actualizada de procesadores.
String IAB TCF v2.3 real + auditoría duradera del consentimiento
- El string de consentimiento TCF v2.3 lo produce ahora el encoder oficial @iabtechlabtcf/core y la API de gestión de consentimiento es la librería oficial @iabtechlabtcf/cmpapi — sustituyendo un shim base64(JSON) que el parser de Google AdSense rechazaba desde el 2026-03-01, dejando a los visitantes de la UE en Limited Ads. El CMP se instala como `__tcfapi` en la página exactamente según la especificación IAB CMP v2.
- Cada resolución del banner de consentimiento (Aceptar todas / Rechazar todas / Personalizar / retirar / aviso de version bump / reapertura) escribe una fila duradera en audit_log etiquetada scope='legal' con el payload completo de consentimiento por finalidad, los opt-ins de funciones especiales, el string addtl_consent, la versión previa y una bandera de si la curación de Google ATP faltaba en el momento del consentimiento. Ahora podemos responder 'prueba que esta persona consintió en este momento bajo esta versión' solo desde audit_log — antes ese rastro no existía.
- La IAB Global Vendor List incluida se ha refrescado y un script `npm run refresh:gvl` permite que un cron diario suelte un snapshot fresco. Una GVL obsoleta era un riesgo de deriva de alcance de proveedor — cada refresco ejecuta una comprobación de esquema antes de renombrar atómicamente al disco para que un fetch interrumpido nunca deje un archivo a medio escribir.
Edad + Condiciones + CSRF + lista de procesadores aplicados en servidor
- El registro persiste ahora `age_confirmed_at` y `tos_version_accepted` en la fila de usuario mediante los additional fields de Better Auth, y un hook del servidor rechaza con dureza cualquier registro al que le falte cualquiera de los dos valores. Antes ambos eran checkboxes solo cliente que cualquier petición curl podía saltarse.
- La lista de encargados de la política de privacidad nombra ahora a los terceros reales: Arsys (España) para alojamiento + Google AdSense / AdMob para publicidad. Postgres y Redis corren sobre infraestructura gestionada por el operador en Arsys y no son encargados independientes. La línea hipotética de 'procesador de pagos' desaparece hasta que la tienda cosmética se lance de verdad bajo un futuro flag.
- Los endpoints de borrado de cuenta y exportación de datos requieren ahora un token CSRF (patrón double-submit cookie) para que una página atacante con un formulario falsificado no pueda lanzar la cuenta atrás del borrado de 30 días ni robar el artefacto de exportación de un usuario con sesión iniciada. La cookie de sesión de Better Auth es sameSite=Lax y de otra forma habría permitido POSTs cross-origin desde una navegación de nivel superior.
Borrado de cuenta y exportación de datos ampliados
- El borrado de cuenta borra o anonimiza ahora un conjunto mucho mayor de tablas con datos personales: amistades y solicitudes, pertenencia y chat de gremios, solicitudes de unión y wishlist de gremios, participantes y rondas de torneo, tickets de soporte y respuestas, tokens e historial de anuncios, historial de semillas provably-fair, tokens de verificación de email y reseteo de contraseña — y aplica la anonimización de centinela a match-telemetry, donaciones a fondos de gremios y varias tablas más con retención de auditoría. Antes más de diez tablas conservaban silenciosamente los datos incluso después de que el borrado se marcara como 'completado'.
- El seudónimo que se usa para sobrescribir el nombre y el correo de una cuenta borrada usa ahora un sufijo aleatorio criptográfico en lugar de un corte determinista de 8 caracteres del id original. El corte anterior llevaba 32 bits de entropía del id original, así que un regulador podía correlacionar el nombre centinela de vuelta al usuario borrado vía audit_log.target_id. El nuevo sufijo rompe el enlace por completo.
- Dos solicitudes de borrado simultáneas del mismo usuario se colapsan ahora en una sola fila en la base de datos (índice único parcial sobre `(user_id) WHERE status IN ('pending','processing')`); el POST de solicitud devuelve `already_pending` en vez de romperse con un 500. Un usuario cuyo borrado ya se completó no puede crear una nueva solicitud — la nueva comprobación `wasUserEverDeleted` la rechaza directamente, defendiendo contra un futuro un-ban que de otra forma resucitaría una cuenta a medio anonimizar.
- Si una transacción de borrado falla a mitad (violación de FK, lock timeout, blip de red) el worker del cron sube ahora un contador de reintentos y guarda el último mensaje de error en lugar de quedarse en bucle en `pending`. Tras cinco intentos fallidos la fila pasa a `failed` para atención de admin.
- La exportación de datos cubre ahora seis nuevas secciones: pertenencia a gremios, entradas de torneo, amistades, tickets de soporte y respuestas, historial de anuncios e historial de semillas provably-fair. La auto-documentación `gaps[]` previa las listaba como 'lo tenemos pero no lo damos' — eso violaba la portabilidad del Art.20 RGPD. Cada sección también lleva un tope de 10.000 filas para que un usuario con mucha colección no haga OUT OF MEMORY al servidor, y un campo `truncations[]` indica las exportaciones parciales con honestidad.
Accesibilidad del banner + higiene de cookies AdSense + entrega del Art.17 DSA
- El banner de cookies es ahora un diálogo modal correcto: role='dialog', aria-modal='true', aria-labelledby apuntando a una cabecera solo para lectores de pantalla, y un focus trap que captura el foco al abrir y lo restaura al cerrar. El Tab no puede escapar a la página de debajo. El Esc intencionadamente no cierra porque sería un rechazo implícito — un dark pattern.
- El selector de idioma (ES | EN) es ahora visible en cada breakpoint, incluido móvil — antes estaba oculto bajo el breakpoint sm:, lo que combinaba mal con el bug del middleware F-A11-009 que ya silenciosamente ponía a los visitantes en el idioma equivocado.
- Retirar el consentimiento publicitario borra ahora de verdad las cookies de primera parte de AdSense (`__gads`, `__gpi`, `__eoi`, `FCNEC`, `FCCDCF`) del navegador, no solo el tag de script inyectado. Antes una retirada dejaba esas cookies en su sitio, lo que violaba ePrivacy + Art.7(3) RGPD (la retirada debe equivaler al borrado de cookies).
- Cuando un admin banea a un usuario, el usuario recibe ahora una notificación dentro de la app con la Declaración de Motivos del Art.17 DSA con la regla infringida, la razón y una ruta de apelación — y la fila de audit_log lleva el mismo payload bajo scope='legal'. Antes los baneos eran silenciosos: el usuario no podía iniciar sesión, no sabía por qué, y no tenía ruta de apelación documentada. El endpoint rechaza ahora cualquier baneo que no suministre la regla y la razón.
Endurecimiento de i18n — chrome en español en todas partes, fechas según idioma, selector de idioma en móvil, correo de contacto unificado
Una pasada por la superficie bilingüe cerró todos los restos de chrome en inglés visibles en las páginas españolas y apretó algunos huecos de middleware y SEO. Titulares: el nav de marketing (Sobre nosotros / Cómo jugar / Cartas / FAQ / Contacto / Novedades / Iniciar sesión / Juega Gratis), cada enlace de cabecera de las páginas legales, el banner global de consentimiento de cookies, el banner offline y la etiqueta de lector de pantalla del selector de idioma leen ahora desde el bundle de mensajes del idioma activo — quien visita en español ya no ve envoltorios en inglés alrededor del contenido en español. El selector de idioma también es visible en móvil (estaba oculto bajo el breakpoint sm:), así que quien aterriza en el idioma equivocado desde el móvil por fin puede saltar dentro de la página. Las fechas del changelog se guardan en ISO YYYY-MM-DD y se renderizan con Intl.DateTimeFormat según el idioma — quien visita en español ve "26 de abril de 2026" / en inglés ve "April 26, 2026". Las direcciones de correo de toda la superficie legal y de contacto se han unificado en una sola canónica contact@drawntcg.com para que la gente no tenga que adivinar entre info / privacy / support. Un nuevo /robots.txt apunta a los crawlers al sitemap por idioma, y los crawlers de previsualización de enlaces (Slack, Discord, iMessage, Twitter) reciben una tarjeta Open Graph con la etiqueta de idioma correcta. Un bug sutil del middleware que hacía que el atributo <html lang="…"> siempre dijera "es" en las páginas /en/* — rompiendo en silencio el enrutado de idioma para lectores de pantalla y la verificación de hreflang de Google — está arreglado.
Chrome en español en cada superficie pública
- MarketingHeader (en cada página pública) lee las etiquetas del nav y los CTAs desde el namespace marketing_header.* — quien visita en español ve Sobre nosotros / Cómo jugar / Cartas / FAQ / Contacto / Novedades / Iniciar sesión / Juega Gratis en lugar de las equivalentes en inglés.
- El chrome de las páginas legales (el botón de volver, los cuatro enlaces cortos de la cabecera — Condiciones / Privacidad / Cookies / Aviso Legal — y las versiones largas del pie más la línea de todos-los-derechos-reservados) traducen todos. Antes /es/legal/* tenía cuerpo en español con chrome en inglés envolviéndolo.
- El banner global de consentimiento de cookies (la superficie TCF v2.3 obligatoria en la UE) traduce el texto de intro, los botones Aceptar todas / Rechazar todas / Personalizar / Guardar preferencias, cada cabecera de sección y cada aria-label de lector de pantalla para que quien visita en español no se encuentre un banner en inglés al aterrizar.
- El banner "Parece que estás sin conexión" (montado en cada página) y la etiqueta de accesibilidad del selector de idioma leen ahora del idioma activo.
Fechas según idioma + selector de idioma visible en móvil
- Las fechas de las entradas del changelog se guardan ahora en ISO YYYY-MM-DD y se renderizan con Intl.DateTimeFormat — "26 de abril de 2026" en español, "April 26, 2026" en inglés, con un envoltorio <time dateTime="…"> apropiado para SEO.
- El título de la página del changelog en español es ahora "Registro de cambios" en lugar de la palabra inglesa "Changelog".
- El selector de idioma (ES | EN) es ahora visible en cada breakpoint, incluido el móvil. Antes estaba oculto bajo el breakpoint sm:, así que quien aterrizaba en el idioma equivocado desde el móvil tenía que editar la URL a mano para cambiar.
Un solo correo de contacto canónico + texto de Discord más suave
- Cada canal de contacto en la superficie de marketing, legal y privacidad apunta a una única dirección canónica: contact@drawntcg.com. Antes una misma persona podía ver info@ en la página de contacto, privacy@ en la política de privacidad y support@ en las condiciones — confuso para cualquiera que intentara averiguar a dónde escribir.
- El callout de Discord se suavizó — en lugar de "el enlace de invitación está en marcha — cuando esté listo aparecerá aquí" ahora dice que la invitación llega pronto y apunta a la página de changelog para seguir las novedades mientras tanto. Sin promesa falsa de inminencia.
Arreglos de crawler y middleware (casi invisibles pero reales)
- Un nuevo /robots.txt enumera la superficie pública, bloquea del rastreo el shell autenticado del juego + admin + API + rutas solo de auth, y apunta a los buscadores hacia /sitemap.xml para que el sitemap por idioma deje de descubrirse de forma orgánica.
- Los crawlers de previsualización de enlaces (Slack, Discord, iMessage, Twitter / X) reciben ahora una tarjeta Open Graph con la etiqueta de idioma activo (es_ES o en_US) más la lista de idiomas alternativos y un hint summary_large_image de Twitter — los enlaces compartidos bilingües ya no exponen por accidente la variante en el idioma equivocado.
- Un bug sutil del middleware que hacía que <html lang="…"> siempre dijera "es" en las páginas /en/* está arreglado. Antes el reenvío de cabeceras de petición se ejecutaba antes de la inyección de idioma de next-intl, así que el root layout nunca veía el idioma activo y caía al por defecto — rompiendo en silencio el enrutado de idioma para lectores de pantalla y la verificación de hreflang de Google.
- Las cabeceras Set-Cookie se anexan (no se reemplazan) al fusionar la respuesta de next-intl — múltiples cookies en una sola respuesta ya no se colapsan en un único valor unido por comas que los navegadores interpretan mal.
Endurecimiento de anticheat y provably-fair — rotación de semilla atómica, scrubbing de replay, strip de estrategia para espectadores
Una pasada por la superficie de anticheat (semillas provably-fair, sanitización de replay, estado de espectador, telemetría de detección de colusión) cerró una lista pequeña pero real de defectos sutiles. Titulares: la rotación de semilla del pack-opening es ahora atómica frente a aperturas de pack en vuelo, así que un replay del verificador nunca puede reproducir N−1 de N aperturas dejando colgada la N sin verificar; el historial de replay scrubea los ids brutos de jugador de los strings del turn-log antes de persistir, así el archivo de replay publicable de verdad no se puede correlacionar por id; el estado de espectador strippea dos pistas de estrategia alpha (contadores de adrenalina y conteos de invocación por turno) que antes pasaban sin enmascarar; las rotaciones de semilla provably-fair y los cambios de client-seed están ahora auditados durablemente para que una disputa de jugador tenga un rastro recuperable; el endpoint de historial ya no 500ea con parámetros de página basura ni filtra errores brutos de Postgres al cliente.
Integridad provably-fair
- La rotación de semilla del pack-opening es ahora atómica frente a cualquier apertura de pack en vuelo. Antes una rotación que aterrizaba entre la lectura de semilla y la actualización de nonce de una apertura podía publicar un valor de total_nonces obsoleto, dejando la apertura en vuelo sin verificar por el verificador. La carrera se cierra con un row lock transaccional más una constraint única-semilla-activa a nivel de base de datos.
- Tanto las rotaciones de semilla como los cambios de client_seed suministrados por el jugador escriben ahora una fila durable en audit, así un jugador que después dispute "roté a las 12:34 y mi apertura está rota" tiene un rastro recuperable sólo en audit_log, sin depender de forensics de log lines.
- PATCH client_seed valida ahora el formato (8-64 caracteres hex) para que unicode, caracteres de control o separadores `:` que ambiguarían el HMAC del verificador no se puedan guardar.
- La página de historial provably-fair degrada con elegancia ante parámetros de página basura (?page=NaN, ?page=abc) y ya no filtra mensajes de error brutos de Postgres a clientes autenticados.
Privacidad de espectadores y replays
- El estado de espectador ya no expone contadores de adrenalina ni conteos de invocación por turno — estas son pistas de estrategia alpha sobre las que se podrían construir herramientas de análisis de metagame competitivo. Los combatientes los siguen viendo en su propio UI de planificación de acciones.
- Los replays persistidos scrubean ahora los ids brutos de jugador de los strings del turn-log antes de almacenarse. Antes los campos de identificador con clave JSON ya se pseudonimizaban a player_1 / player_2 pero las líneas de log de texto libre todavía podían embeber ids brutos — un lector no-admin del blob de replay podía correlacionar replays emparejando substrings de id.
Endurecimiento de perfil, gremios, torneos y UI de batalla — claridad en perfiles públicos, toasts de logros, timeouts en acciones
Una pasada por torneos, gremios, las secciones de perfil y las internals de batalla arregló una lista larga de bugs pequeños y añadió las affordances visibles que faltaban. Titulares: ver el perfil de un desconocido ahora te dice por qué bio / vitrina / logros están ocultos ("añade como amigo para ver más") en lugar de renderizar secciones en blanco; los toasts de logros se descartan solos a los 6 segundos (y se limitan a 5 visibles a la vez) en vez de apilarse para siempre; cada acción en batalla — invocar, atacar, habilidad, mulligan, fin de turno, forfeit — tiene ahora una red de seguridad de 5 segundos "sin respuesta del servidor" para que un socket inestable no deje la UI atascada en pending; el botón de forfeit usa un modal dentro de la app en lugar del popup nativo confirm() del navegador que las PWAs de iOS a veces suprimen en silencio; el cuadro de torneo marca los partidos de forfeit / desconexión / bye con una insignia pequeña para que los admins y espectadores los distingan de victorias reales; el guardado del deck-builder valida el límite de 3 copias por carta en el cliente antes de dejarte pulsar Save; y una lista de pulidos de quality-of-life en gestión de gremios (confirmación al kick / promote, límite en cantidad de donación, filtrado del refetch de fin de guerra, dedupe de mensajes idénticos).
Perfil público y toasts de logros
- Ver el perfil de otro jugador muestra ahora un aviso en línea ("Añade como amigo para ver bio, vitrina, logros y actividad reciente") cuando esas secciones están gateadas por la comprobación de amistad. Antes las secciones salían en blanco sin más y los jugadores pensaban que su cuenta estaba rota.
- El perfil público muestra ahora la insignia de división del jugador (Bronze / Silver / Gold / etc.) junto al ELO — faltaba en perfiles de otros jugadores aunque sí salía en el propio.
- Compartir el enlace del perfil ahora muestra feedback "Link copied!" / "Could not share" en lugar de no decir nada.
- Los toasts de logros se descartan solos tras 6 segundos y se limitan a 5 visibles a la vez. Antes se apilaban para siempre y los jugadores que desbloqueaban 5 logros en una batalla tenían que cerrar cada × a mano.
Robustez en batalla
- Cada acción del jugador — invocar, evolucionar, atacar, habilidad, equipar, poner trampa, colocar campo, usar objeto, mulligan, fin de turno, entrar a batalla, forfeit — corre ahora contra un timeout de ack del servidor de 5 segundos. Si el servidor no responde (caída de red, hipo del server) recibes un toast "No response from server" en lugar de la acción quedándose pending visualmente para siempre.
- El botón de forfeit usa un modal de confirmación dentro de la app que muestra las consecuencias (derrota en historial y rating). Reemplaza el popup nativo confirm() del navegador que las PWAs de iOS a veces suprimen en silencio, lo que producía reportes de "toqué forfeit y no pasó nada".
- La decisión de mulligan ya no puede dispararse múltiples veces: un doble-tap rápido en Keep/Mulligan envía ahora sólo una decisión al servidor.
- Endurecimiento de orientación de espectador: los handlers de acción en batalla son ahora no-op para espectadores como capa de defensa-en-profundidad sobre el gating de UI existente.
- Los tooltips de batalla ya no se cierran a medio camino en Android por la carrera touch+click; los tooltips de status / zone permanecen abiertos al tocar hasta que tocas en otro sitio.
- La pantalla del temporizador de mulligan y los modales de confirmación dentro de la app tienen ahora etiquetado aria-modal correcto para usuarios de lector de pantalla; antes no se anunciaban.
Torneos y gremios
- El cuadro de torneo muestra ahora una insignia pequeña FF / DC / BYE en los partidos terminados por forfeit, desconexión o avance automático — antes se renderizaban idénticos a victorias reales.
- Las acciones de kick / promote / demote en gremio requieren ahora un segundo click para confirmar (el botón cambia a "Click again" con auto-cancelación a los 4 segundos). Antes un solo click accidental en Kick eliminaba al miembro al instante.
- Chat de gremio: el spam de Enter ya no puede postear mensajes idénticos en menos de 5 segundos; los nombres con emoji y RTL renderizan su primer carácter correctamente en las burbujas de avatar.
- La cantidad de donación al pool de gremio ahora está limitada (1 a 99,999 fragmentos) y rechaza valores basura como NaN que el formulario podía generar antes.
- La notificación de fin de guerra de gremio ahora sólo refresca las guerras de nuestro propio gremio, no cada evento guild_war_ended del mundo.
Secciones de perfil
- Selección de Vitrina: el orden es ahora controlable por el usuario (botones ←→ en las cartas seleccionadas); las cartas intercambiadas o disueltas ya no se quedan como selecciones zombi; el tope de cartas por página subió a 1000 con un aviso claro si tienes más.
- Nombre / avatar / bio del perfil se guardan ahora de forma independiente — antes pulsar Save junto al avatar (tras escribir también un nombre nuevo) enviaba los dos, gastando por accidente el cooldown de cambio de nombre de 30 días.
- El campo de URL de avatar muestra ahora una vista previa en línea según escribes, con un fallback cuando la URL no es válida.
- La validación de cambio de contraseña en el cliente refleja ahora la política del servidor (mín 8 caracteres + al menos un dígito + al menos una letra + debe diferir de la actual). Antes la única comprobación era el mínimo de 8 caracteres.
- Los secretos de configuración de 2FA (URI TOTP, códigos de respaldo, campos de contraseña) se borran ahora del estado de React al desmontar para que extensiones de navegador no los puedan extraer de una snapshot rancia.
- Provably-fair Rotate Seed: los fallos muestran ahora un error claro (antes eran silenciosos), y el seed revelado tiene un botón "Copy seed" + un aviso de que la próxima rotación revelará uno nuevo y este no se podrá volver a mostrar.
- Solicitud de eliminación de cuenta: muestra "Deletion service is temporarily unavailable" desde el principio si el backend no está configurado, y los doble-clicks se rechazan de forma síncrona para que no se puedan programar dos filas de eliminación.
Arreglos en la UI de batalla — vista de espectador, temporizador de mulligan, deck builder más sólido
Una pasada por la pantalla de batalla, el deck builder y los modales de carta / intercambio arregló una lista larga de bugs pequeños pero visibles. Los titulares: los espectadores veían la batalla reflejada al revés (su vista etiquetaba al rival como "tú"), el temporizador de mulligan se reseteaba a 30 cada vez que pasaba algo en el tablero, el tamaño de la cola salía en blanco al unirse al casual, y los marcadores 🎯 del historial de intercambios mostraban la wishlist del jugador equivocado. El modal de carta ahora cancela las peticiones en vuelo cuando hojeas la galería, el deck builder limita cuántas páginas carga (para que un fallo del servidor no bloquee la pantalla), y copiar un export de mazo ahora te dice de verdad si funcionó.
Correcciones en batalla
- Los espectadores ahora ven la batalla con orientación fija — el challenger siempre abajo, el rival siempre arriba — en vez de que la vista se gire al lado equivocado y etiquete al jugador equivocado como tú.
- El temporizador de mulligan ya no se resetea a 30 cada vez que el rival actúa. Ahora empieza en 30 una vez al entrar a la fase de mulligan y cuenta hacia abajo de verdad.
- Los tooltips de carta ya no se cierran a media lectura cada vez que cambia un HP del rival. Ahora sólo se cierran en cambios reales de turno / fase.
- Si te vas de la pestaña de batalla y vuelves, la pantalla pide un estado fresco al servidor en vez de renderizar el snapshot rancio que tenía.
- Salir de una batalla desde el lobby (antes de que entre el rival) ahora libera de verdad tu slot en el servidor — antes podías quedarte atrapado con "ya estás en una batalla" al intentar volver a entrar en cola.
- El tamaño de la cola casual ahora se muestra bien al unirse (antes salía en blanco / NaN por un desfase del payload).
- El historial de batallas (la lista de resueltas debajo de tus mazos) ahora se actualiza en vivo al terminar una batalla — ya no hay que refrescar para ver el último combate.
- El banner de rematch ahora sólo sale tras una batalla que se resolvió de forma natural (victoria / derrota). Antes salía también tras forfeits y desconexiones del rival, lo cual confundía.
- El report-result de torneo ahora usa un modal dentro de la app que muestra el nombre del rival. Adiós a los popups nativos confirm() (un sitio menos donde no se aplicaba el diseño).
Deck builder y modal de carta
- El deck builder ahora valida el límite de 3 copias por carta antes de dejarte pulsar Save (antes se comprobaba sólo al añadir — decks corruptos o importados podían colarse con 4+ copias y fallar en silencio en el servidor).
- Cargar una colección enorme en el deck builder ahora tiene límite — hay un tope duro de páginas que pedirá (50, muy por encima de cualquier colección realista) para que un bug de paginación del servidor no bloquee la pantalla.
- El modal de carta cancela las peticiones de red en vuelo cuando saltas a otra carta. Antes los datos (wishlist / copias / evoluciones) de la carta anterior podían resolverse al final y sobrescribir los de la nueva.
- Bloquear / desbloquear una copia de carta ahora muestra un error si el servidor lo rechaza. Antes se tragaba el fallo sin avisar.
- El historial de intercambios muestra un botón de Retry si la carga falla, en lugar de un falso "No completed trades yet" eterno.
- Copiar un export de mazo al portapapeles ahora muestra un toast Copied! / Copy failed. Antes era silencioso.
- El listado de batallas en vivo (el panel de espectar) ya no muestra el botón Watch en tus propias batallas, y pausa el sondeo de 15 segundos cuando la pestaña está en segundo plano.
Feedback en acciones de batalla — fin del UI congelado cuando el servidor tose
Cuando invocas una criatura, atacas o terminas el turno, el cliente espera ahora hasta 5 segundos a que el servidor confirme. Si la confirmación nunca llega — fallo del servidor, caída de red, paquete perdido — recibes un mensaje claro "No response from server — check your connection" en lugar de un botón que sencillamente deja de responder. Si el servidor rechaza la acción (fuera de fase, objetivo ilegal, rate-limit, etc.) la razón del rechazo te llega ya inmediatamente incluso con conexiones inestables, en lugar de viajar con el siguiente push de estado y llegar segundos tarde. Las demás acciones (menos críticas) siguen igual. Español e inglés también reciben páginas de `not-found` y `loading` para que un enlace caducado o una carga lenta aterrice en una página localizada en lugar del default del framework.
Feedback en acciones de batalla
- Invocar, atacar y terminar turno muestran ahora un toast de error si el servidor no confirma en 5 segundos, en lugar de dejar la acción visualmente pendiente.
- Los rechazos del servidor (fuera de fase, objetivo ilegal, rate-limit) llegan ahora inmediatamente incluso cuando el push de estado va retrasado.
- Páginas localizadas de `not-found` y `loading` — los enlaces caducados y las cargas lentas aterrizan ahora en una página en español o inglés en lugar del default del framework.
Más privacidad y seguridad
- Bio de perfil, vitrina, actividad reciente y logros son ahora visibles sólo para amigos y para ti. Los desconocidos ven tu nombre, rating y división pero no tu vitrina — cierra una queja vieja de que cualquiera con la URL de tu usuario podía hojear tu contenido privado de perfil.
- Bloquear a un jugador es ahora un ocultamiento duro — tu perfil devuelve 404 a quien has bloqueado (y el suyo a ti), en lugar de ocultar pestañas y filtrar los datos básicos.
- El feed de actividad de amigos ya no expone campos que el evento original no tenía intención de compartir. Cada tipo de evento tiene una lista estricta (pull raro, logro, resultado de batalla, intercambio, ingreso en gremio, victoria de torneo) para que no se filtre nada extra aunque cambie el shape de un payload en el futuro.
- El leaderboard ahora pide login. Los bots anónimos ya no pueden raspar la tabla de rating.
Notificaciones e importación
- Ahora puedes marcar notificaciones individuales como leídas en lugar de todo-o-nada — la bandeja pasa los ids concretos en los que tocas.
- La importación de mazo ahora limita el texto pegado a 4 KB para que un pegado accidental enorme devuelva un error claro en lugar de quedarse colgado. El límite es de sobra para cualquier lista de mazo legítima.
Arreglos de notificaciones en vivo
- Las solicitudes de amistad, los accepts y los rejects ya actualizan en tiempo real los contadores y la lista de amigos sin refrescar a mano. El vocabulario entre cliente y servidor se había desincronizado y cada notificación caía en silencio en la lista blanca del servidor; los verbos canónicos son ahora compartidos y el relay los reenvía de extremo a extremo.
- Las ofertas de intercambio, los accepts, los declines y las cancelaciones también llegan en vivo al otro jugador — el contador sube en cuanto llega la oferta, sin esperar al próximo recargo de página.
- UI de intercambio: los marcadores 🎯 muestran ahora correctamente las cartas que el OTRO jugador quiere (antes mostraban tu propia wishlist en silencio en ambas columnas).
UX de pestañas y ahorro de batería
- Las pestañas en segundo plano ya no consumen ancho de banda — el leaderboard y los challenges pausan ahora sus suscripciones en vivo cuando estás en otra pestaña y se reanudan al volver.
- El leaderboard muestra ahora un error claro + botón de reintentar si la carga falla, en vez del spinner eterno de "Loading leaderboard…".
- Los botones de Challenge / Accept en Battles ya no se quedan bloqueados para siempre si el socket falla. Tras 15 segundos sin respuesta del servidor sale un toast "Network timeout — try again" y el botón se libera.
- La confirmación "I Won" en torneos es ahora un diálogo estilizado dentro de la app que muestra el nombre del rival, en lugar del popup nativo confirm() del navegador. Misma cosa para pulido visual y accesibilidad (aria-modal).
- Las respuestas a tickets de soporte son ahora un textbox multilínea (Shift+Enter para nueva línea, Enter para enviar) en vez de un input de una sola línea que truncaba en silencio respuestas de párrafo.
Repaso SEO — sitemap, hreflang, URLs canónicas en todos los idiomas — Fase 1 i18n totalmente cerrada
La superficie pública bilingüe queda conectada a los buscadores. Cada página en español e inglés emite un `<link rel="canonical">` apuntando a su propia URL de idioma más un mapa `<link rel="alternate" hreflang>` que cubre los dos idiomas y un `x-default` de respaldo, para que Google empareje las versiones /es y /en de la misma página en lugar de tratarlas como duplicadas. Un nuevo `/sitemap.xml` enumera cada página pública de la Fase 1 en cada idioma con los mismos hreflang por entrada. Con esta versión la Fase 1 del proyecto i18n queda totalmente cerrada: 4 páginas legales bilingües, 7 páginas de marketing bilingües, cláusulas de versión vinculante en los dos idiomas, selector de idioma en las dos cabeceras, hreflang en metadata y sitemap, y una plantilla de flujo escrita para cualquier página futura.
SEO + plantilla para páginas futuras
- Nuevo `/sitemap.xml` enumera cada página pública de Fase 1 (inicio, about, contact, FAQ, cómo jugar, showcase, changelog, legal/cookies, legal/notice, legal/privacy, legal/terms) en español e inglés, con alternates hreflang por entrada para que los buscadores emparejen las variantes de idioma como toca.
- Cada página traducida emite ahora una URL canónica propia apuntando a su ruta /es o /en y un mapa `languages` de hreflang con los dos idiomas más `x-default` (por defecto español), así no hay riesgo de penalización por contenido duplicado.
- Plantilla de flujo para páginas futuras añadida en `context/refs/marketing-hero-draft.md` — toda nueva página pública añadida después de la Fase 1 debe salir en español y en inglés desde el primer commit. La plantilla cubre el flujo de borrador bilingüe, el vocabulario TCG, las reglas de registro y el cableado SEO requerido.
- La Fase 1 de i18n se cierra aquí: 11 páginas públicas × 2 idiomas totalmente bilingües de principio a fin, más la infraestructura SEO, las cláusulas de versión vinculante, el selector de idioma y el contrato escrito para futuras páginas bilingües.
Páginas de marketing, maestría, torneos y un repaso a huecos de UX
Esta primavera ha llegado un buen lote de trabajo visible para los jugadores. Los titulares: una superficie de marketing pública para visitantes nuevos, un sistema de maestría de cartas que premia la fidelidad a cartas concretas, un visor de cuadro de torneo que por fin te deja ver cómo se está desarrollando un torneo, y una lista larga de pequeños arreglos de UX que cierran huecos que los jugadores llevan meses señalando. Abajo va la forma aproximada de lo que cambió, organizado por área. Las notas de parche de cartas concretas y los ajustes de balance viven en el feed de parches dentro del juego; este changelog cubre lo estructural y lo superficial.
Páginas de marketing (públicas)
- Nueva página de inicio que de verdad explica el juego, en lugar de soltarte directamente en un formulario de login. Los jugadores que vuelven tienen un "Log in" a un clic en el header pegajoso.
- About, Cómo Jugar, FAQ, Contacto, Cartas Destacadas y este Changelog — todas accesibles desde el mismo header en cada página pública.
- Banner de consentimiento de cookies actualizado al estándar IAB TCF v2.3 para que los jugadores de la UE tengan el flujo de opt-in correcto con todos los conmutadores por propósito.
Crafteo y colección
- Nueva pestaña Forge dedicada al crafteo de cartas a partir de los fragmentos que consigues al disolver duplicados.
- Sistema de maestría: a medida que juegas con la misma carta en distintas batallas, sube de nivel. Cuando una carta llega al nivel 5, consigues un 10% de descuento de crafteo en copias de ella. Al nivel 10 recibe la insignia "Mastered".
- El coste de crafteo ahora muestra el precio con descuento en el propio botón de craftear, así ves el ahorro sin meterte en el detalle de la carta.
- La barra de monedero ahora muestra tu reserva de fragmentos separada por rareza, en lugar de un único número agregado. Más fácil ver de un vistazo si te llega para craftear.
- Navegador de sets de cartas: mira cada set, qué cartas pertenecen a él y tu progreso. Te ayuda a decidir qué sets perseguir.
- Enlace persistente "Verify past pulls" en la pestaña de Gacha — tócalo cuando quieras para auditar cualquier sobre que hayas abierto contra el registro de RNG demostrablemente justo.
Batallas y competitivo
- Visor de cuadro de torneo: cuando entras en un torneo de eliminación directa, ahora ves el árbol completo del cuadro con cada partida, quién juega contra quién y quién ha ganado hasta el momento.
- Clasificaciones públicas de torneos de gremio: los no participantes que navegan por un torneo de gremio pueden ver el panel de clasificación sin estar inscritos.
- Las repeticiones de batalla tienen ahora su propia URL compartible. Abre la repetición en otra pestaña, manda el enlace a un amigo y apunta al momento del que quieres hablar. Funciona para cualquier batalla en la que cualquiera de los dos haya jugado.
- Los efectos de campo elemental tipo clima ahora aparecen como toca en los cálculos de daño durante la batalla — los campos de fuego potencian a las criaturas de fuego, los de agua a las de agua, etc.
- Los desafíos diarios y semanales tienen un sistema de rachas: completarlos en días seguidos te construye una racha con recompensas hito a los 7, 14 y 28 días.
- El panel de estadísticas de batalla muestra desgloses por elemento + filtrado por periodo, así puedes ver cómo le han ido a tus mazos de fuego esta semana frente al mes pasado.
- Arreglo de spam en batallas contra IA: una condición de carrera que permitía iniciar dos veces una batalla contra la IA queda cerrada. Se acabaron las ventanas de partida huérfanas.
Cuenta y confianza
- Autenticación en dos pasos disponible desde la sección de seguridad de tu perfil. Las apps de autenticación estándar (Authy, Google Authenticator, 1Password, Bitwarden) funcionan todas.
- Borrado de cuenta autoservicio en la sección de perfil — sin ticket de soporte, sin baile de no-te-dejamos-irte.
- Exportación de datos autoservicio: descarga una copia de todo lo que guardamos sobre tu cuenta.
- Seguridad de sesión más estricta: cambiar tu contraseña o activar 2FA ahora invalida automáticamente todas tus otras sesiones activas. Si alguien estaba conectado como tú en otro dispositivo, queda fuera.
- Mejoras en el flujo de verificación de email: mensajes más claros cuando la verificación está pendiente, reintento con tiempo de espera en el botón de reenviar.
Rendimiento y fiabilidad
- Mejoras de reconexión de batalla: si te caes a media partida durante menos de un minuto, te reconectas al mismo estado del juego sin penalización.
- Reescritura del manejo de deck-out en el servidor — los mazos lentos de control que ganan agotando la biblioteca del rival ahora funcionan limpiamente entre reconexiones.
- Los timeouts de forfeit de torneo se han movido a una cola de tareas duradera, así que un reinicio de servidor ya no se come un torneo con forfeit pendiente.
- Banner de detección sin conexión: si se te cae la conexión, ves un banner claro explicando qué pasó, en lugar de que la interfaz falle en silencio.
Notas para la próxima
- Seguimos añadiendo cartas, sets y modos. El mundo tiene de sobra para seguir creciendo.
- Estamos preparando el enlace de invitación de Discord — cuando esté, aparecerá en la página de contacto.
- Si ves algo aquí que no cuadra con lo que ves dentro del juego, dínoslo — lo más probable es que lo hayamos sacado distinto de lo que sugiere la nota.
Todas las páginas de marketing ya disponibles en español — Fase 1 completa
Seis páginas más de marketing ya son bilingües, cerrando la Fase 1 de traducción de marketing. /es/about presenta el texto sobre equipo / visión / mundo del juego en español. /es/contact traduce todos los canales. /es/faq cubre las 12 preguntas frecuentes. /es/how-to-play traduce el recorrido de diez minutos. /es/showcase muestra las 7 cartas destacadas con nombres y blurbs bilingües. /es/changelog (esta misma página) también es bilingüe — cada ChangelogEntry lleva texto en español y en inglés juntos en el código fuente para que las próximas actualizaciones aterricen en los dos idiomas a la vez. Las cuatro páginas legales salieron bilingües antes este mes, así que la superficie pública ya es completamente bilingüe de principio a fin.
Páginas de marketing traducidas
- Página About (Sobre Nosotros) traducida al español — hero 'Hecho entre amigos, por gusto', secciones de equipo / visión / mundo del juego, todas reflejan la fuente en inglés 1:1.
- Página de contacto traducida — cabeceras de sección 'Correo / Discord / Tickets de soporte / Una nota sobre los tiempos de respuesta', botones de navegación volver / FAQ.
- FAQ traducida — las 12 preguntas y respuestas en tres secciones (Sobre el juego / Cómo jugar / Cuenta), incluidas las preguntas sobre cómo funcionan los anuncios recompensados, cómo borrar la cuenta y dónde se almacenan los datos.
- Cómo Jugar traducido — tres secciones (Mecánicas, Tipos de cartas y elementos, Flujo de batalla — turno a turno). Glosario TCG mazo / sobre / carta / PV / zona de monstruo / mulligan se mantiene tal cual.
- Cartas Destacadas (showcase) traducidas — cada nombre de carta y blurb de marketing de 50-150 palabras. Etiquetas de elemento + clase de monstruo también traducidas (Fuego/Agua/Viento/Tierra/Metal/Rayo/Neutral; Dragón/Bestia/Constructo/Leviatán/Alado; slime se mantiene como término TCG).
- Página del changelog bilingüe — cada ChangelogEntry ahora lleva texto en español y en inglés juntos en el código fuente. Nota para mantenimiento actualizada: a partir de esta versión, cada nuevo ChangelogEntry DEBE aterrizar en español Y en inglés en el mismo commit. Las entradas en un solo idioma quedan prohibidas.
- Todas las versiones en español usan la voz indie y juguetona de la página de inicio — tú informal en todo, vocabulario TCG preservado (sobres, mazos, cartas, tablero, dorsos, gremios).
- El selector de idioma en la cabecera de marketing te deja saltar entre las versiones /es y /en de cualquier página sin perder dónde estabas. Traducciones de marketing Fase 1 ahora completas.
Página de inicio disponible en español — primera página de marketing traducida
El hero de la página de inicio ya es bilingüe. Los visitantes hispanohablantes que aterrizan en /es ven el discurso de marketing completo en español — posicionamiento anti-pay-to-win, promesa de sobres gratis diarios, modos a varios ritmos (casual, clasificatoria, IA o solo coleccionar), chips de elementos y el footer de 'hecho entre amigos' — todo en voz informal de tú. Los visitantes anglohablantes mantienen el texto original en /en. Es la primera página de marketing que aterriza en dos idiomas; el resto vienen detrás.
Páginas de marketing
- Página de inicio (hero de homepage) traducida al español — cada párrafo refleja el original inglés 1:1: badge del hero ('Gratis · Sin pay-to-win'), titular, subtítulo, tres tiles de 'por qué jugar', sección 'Cómo funciona' con chips de elemento (Fuego, Agua, Viento, Tierra, Metal, Rayo, Neutral) y el footer 'Hecho entre amigos por gusto'.
- La versión española usa el mismo registro indie y juguetón que la fuente inglesa — tú informal en todo, vocabulario de TCG que los jugadores ya conocen (sobres, mazos, cartas), sin lenguaje publicitario.
- Próximas traducciones de marketing: about, cómo jugar, FAQ, contacto, cartas destacadas y changelog. Cada una pasará por la misma puerta de revisión por página.
- El selector de idioma en la cabecera de marketing te deja saltar entre las versiones /es y /en de la página de inicio sin perder dónde estabas — el mismo flujo que ya hay en las páginas legales.
Política de Privacidad disponible en español — cobertura legal de Fase 1 completa
La Política de Privacidad ya es bilingüe, cerrando la Fase 1 del esfuerzo de localización de páginas legales. Los visitantes hispanohablantes aterrizan en un /es/legal/privacy totalmente traducido, los anglohablantes mantienen el texto original en /en/legal/privacy. Con esta versión, las cuatro páginas legales — Condiciones de Uso, Política de Cookies, Aviso Legal y Política de Privacidad — salen en inglés y español, cada una con el aviso de versión vinculante que designa al texto inglés como la versión legalmente autoritativa. La cobertura legal de Fase 1 está totalmente completa.
Páginas legales
- Política de Privacidad traducida al español — cada sección, cada tabla de datos y cada enumeración de derechos refleja la fuente inglesa 1:1 (responsable del tratamiento, categorías de datos recogidos, finalidades y base legal, compartición de datos con encargados, transferencias internacionales, plazos de conservación, derechos del interesado bajo el RGPD incluyendo acceso/rectificación/supresión/portabilidad/oposición/limitación/retirada del consentimiento, recuadro de oposición del Artículo 21 RGPD, ruta de reclamación a la AEPD, notificación de brechas de los Artículos 33/34 RGPD, derechos CCPA/CPRA para usuarios de California, divulgación de cookies, protecciones para menores de 16 años y divulgación de decisiones automatizadas).
- La versión española usa la terminología canónica del RGPD en todo momento: 'responsable del tratamiento', 'interesado', 'encargado del tratamiento', 'base legal', 'consentimiento', 'interés legítimo', 'Delegado de Protección de Datos (DPD)', más los nombres completos en español de cada derecho del RGPD ('derecho de acceso', 'derecho de supresión', 'derecho de oposición', 'derecho a la portabilidad', 'derecho a la limitación').
- Cobertura legal de Fase 1 totalmente completa: Condiciones, Cookies, Aviso Legal y Privacidad disponibles en español y en inglés con los avisos de versión vinculante en su sitio.
- El selector de idioma en la cabecera legal te deja saltar entre /es/legal/privacy y /en/legal/privacy sin perder dónde estabas — el mismo flujo que ya hay en las otras tres páginas legales.
Condiciones de Uso disponibles en español — tres páginas legales bilingües
La página de Condiciones de Uso ya es bilingüe. Los visitantes hispanohablantes aterrizan en un /es/legal/terms totalmente traducido, los anglohablantes mantienen el texto original en /en/legal/terms. Las dos versiones llevan el mismo aviso de versión vinculante arriba, dejando claro que el texto inglés sigue siendo la versión legalmente autoritativa y que la traducción al español se ofrece como referencia. Tres de cuatro páginas legales — Condiciones, Cookies y Aviso Legal — ya salen en los dos idiomas; Privacidad es la siguiente.
Páginas legales
- Condiciones de Uso traducidas al español — cada cláusula, cada elemento de lista y cada definición legal refleja la fuente inglesa 1:1 (elegibilidad, mecánicas de gacha, divulgación de anuncios, propiedad intelectual, flujo de apelación del Artículo 17 DSA, nota sobre gacha en Bélgica, derecho de desistimiento del consumidor europeo).
- Cobertura de páginas legales de Fase 1 ahora a tres cuartas partes: Condiciones, Cookies y Aviso Legal disponibles en español e inglés. Falta la traducción de la Política de Privacidad.
- Banner de versión vinculante presente arriba de cada página legal traducida en los dos idiomas: el texto inglés sigue siendo legalmente vinculante y las traducciones quedan marcadas explícitamente como no autoritativas para la interpretación legal.
- El selector de idioma en la cabecera legal te deja saltar entre las versiones /es y /en de cualquier página legal sin perder dónde estabas.
Política de Cookies y Aviso Legal disponibles en español
Las páginas de Política de Cookies y Aviso Legal ya son bilingües. Los visitantes hispanohablantes aterrizan en /es/legal/cookies y /es/legal/notice totalmente traducidos, los anglohablantes mantienen el texto original en /en/legal/cookies y /en/legal/notice. Las cuatro páginas llevan un aviso de versión vinculante arriba dejando claro que el texto inglés es la versión legalmente autoritativa y que la traducción al español se ofrece como referencia. Condiciones y privacidad seguirán el mismo patrón en próximas versiones.
Páginas legales
- Política de Cookies traducida al español — cada sección, cada tabla de cookies y cada nota sobre ajustes del navegador refleja la fuente inglesa 1:1.
- Aviso Legal traducido al español — identidad del operador, celda de NIF/CIF, dirección con base en España, lista de legislación aplicable LSSI-CE/RGPD/LOPDGDD/DSA, secciones de propiedad intelectual, responsabilidad y resolución de disputas, todo reflejado 1:1.
- Banner de versión vinculante añadido arriba en las dos páginas legales en los dos idiomas: el texto inglés sigue siendo la versión legalmente vinculante y las traducciones quedan marcadas explícitamente como no autoritativas para la interpretación legal.
- El selector de idioma en la cabecera legal te deja saltar entre /es/legal/* y /en/legal/* sin perder dónde estabas.
¿Quieres sugerir lo próximo? La página de contacto tiene email y Discord. ¿Quieres seguir las actualizaciones según salen? Guarda esta página — la actualizamos cada vez que aparece un cambio visible para los jugadores.