── The Boomer Dev Docs ← Volver a la app

MEMORIA FUNCIONAL — HookGenerator

Producto: HookGenerator — Generador de hooks y titulares virales para redes sociales URL live: https://hookgenerator.theboomer.dev Stack: React + Vite + React Router (frontend), FastAPI + Motor/MongoDB + OpenAI API (backend), Clerk (auth), Stripe (billing), Ghost CMS (blog), i18next (ES/EN) Fuente de verdad: code/frontend/src/App.tsx (356 líneas, router + tabs), pages/, context/AuthContext.tsx, services/, i18n/locales/ Fecha de documentación: 2026-08-05


1. Introducción

Propósito

HookGenerator genera hooks y titulares virales para contenido (especialmente videos de YouTube y redes sociales). El usuario introduce un tema, elige proveedor de IA (OpenAI o Gemini), cantidad de hooks (3/5/10) y obtiene una lista de hooks con su estilo identificado (curiosidad, urgencia, storytelling, etc.), cada uno copiable con un clic.

Público objetivo

Nota de arquitectura

A diferencia de CaptionAI, esta app usa React Router: rutas públicas (/, /pricing, /blog, /login, /register) y protegidas (/dashboard, /generator, /profile, /usage) dentro de un header global con tabs adicionales (Historial, Facturación, Perfil) gestionados por estado local. La autenticación combina Clerk con un AuthContext propio que provee login/register/logout y un fallback offline cuando falta la publishable key de Clerk. El blog se sirve desde Ghost CMS (services/ghost.service.ts). Nota de CLAUDE.md: este es el frontend "más completo" del grupo, con deployable + Playwright E2E + Stripe + Clerk integrados.


2. Tipos de usuario (roles)

Rol Identificación Capacidades
Visitante anónimo Sin sesión Clerk Ver Landing, Pricing, Blog. Puede usar el Generator (ruta /generator está protegida por ProtectedRoute, pero en la práctica el flujo anónimo de generación existe vía localStorage anon_hooks). Tras su primer hook ve el UpgradeDialog.
Usuario registrado (Free) Sesión Clerk + perfil backend (GET /user/profile) o fallback Clerk Dashboard, Generator, Usage, Historial, Perfil, Facturación. Cuota mensual según plan (p. ej. hooks_limit del backend).
Pro Suscripción Stripe (plan pro) Más hooks/mes (1.000 según pricing estático), todos los estilos, exportación CSV/JSON, historial completo, soporte prioritario.
Enterprise Suscripción (plan enterprise) Hooks ilimitados (hooks_limit === -1), API REST, múltiples usuarios, plantillas personalizadas, gestión de API keys en Perfil.

3. Funcionalidades

F3.1 Generación de hooks

Descripción: Función principal. POST a /generate (base: VITE_API_URL o http://localhost:8001/api/v1) con {topic, count, provider, model?, api_key?}. Respuesta: HookResponse {success, topic, hooks: string[], count, styles_used: string[]}.

Flujo paso a paso (página /generator): 1. El usuario introduce el tema (input, placeholder "¿De qué quieres generar hooks?"). 2. Elige proveedor IA con tabs: OpenAI o Gemini (por defecto openai). 3. Introduce la API key del proveedor en un input tipo password (sk-... para OpenAI, AIza... para Gemini). El backend también acepta OPENAI_API_KEY configurada en servidor; si el usuario deja la key vacía y no hay key en servidor, se muestra el error "API key requerida (o configura OPENAI_API_KEY en el servidor)". 4. Selecciona cantidad de hooks: botones 3 / 5 / 10 (por defecto 5). 5. Pulsa "Generar Hooks" (deshabilitado mientras loading). 6. POST /generatesetHooks(response.hooks) + setStylesUsed(response.styles_used). 7. Si el usuario está logueado, refresca el uso (GET /user/usage).

Validaciones: - Tema obligatorio: si vacío → error "El tema es obligatorio" (generator.errors.topicRequired). - API key requerida en cliente (o key de servidor). - Errores de API: banner rojo con err.message. - El error y el estado se resetean al iniciar una nueva generación.

F3.2 Copiar hook individual

Flujo: 1. Pulsar botón "Copiar" (ghost) en la card del hook. 2. navigator.clipboard.writeText(hook). 3. El botón cambia a "¡Copiado!" durante 2 segundos (por índice).

F3.3 Indicador de uso (Usage bar)

Descripción: Barra superior del Generator (solo logueados) que muestra consumo mensual.

Flujo: 1. GET /user/usageUsageStats {month, hooks_used, hooks_limit, remaining, percent_used}. 2. Muestra "X usados / Y restantes" y una barra de progreso (percent_used) si el límite es > 0. 3. Si hooks_limit === -1 muestra "Ilimitado" sin barra.

F3.4 Autenticación (Clerk + AuthContext)

Descripción: Login/registro con Clerk (páginas /login y /register con componentes SignIn/SignUp de Clerk en modo path, con estilos adaptados al tema) y flujo alternativo NoClerkFallback (login/registro contra /auth/login y /auth/register con token en localStorage) cuando falta VITE_CLERK_PUBLISHABLE_KEY.

Flujo: 1. Páginas públicas /login (SignIn) y /register (SignUp), forceRedirectUrl="/dashboard". 2. AuthContext sincroniza: setTokenProvider(() => getToken({template: ''})) para que api.ts inyecte Authorization: Bearer. 3. Tras login, GET /user/profile para obtener el usuario backend; si falla, fallback con datos de Clerk (clerkUserToUser, plan 'free'). 4. Google OAuth disponible (signInWithGoogle/signUpWithGoogle → redirect a /dashboard). 5. Logout → signOut() + navegación a /.

F3.5 Rutas protegidas (ProtectedRoute)

Descripción: Wrapper que redirige a /login si no hay usuario.

Flujo: 1. Si auth.loading → spinner full-screen. 2. Si auth.user → renderiza children. 3. Si no → <Navigate to="/login" />. Aplica a /dashboard, /generator, /profile, /usage.

F3.6 Dashboard (Panel)

Descripción: Resumen para usuarios logueados.

Flujo: 1. GET /user/usage al montar; spinner mientras carga. 2. Uso mensual: hooks_used / hooks_limit (o ∞), "X remaining", barra de progreso, o "Unlimited plan" en verde. 3. Plan actual: nombre del plan (user.subscription.display_name o plan o 'Free'); si es free → botón "Upgrade" que enlaza a /billing. 4. Quick Generate: input de tema + botón "Generate" (deshabilitado si vacío); Enter o clic navega a /generator?topic=<tema> (el tema viaja por query string). 5. Recent Activity: si no hay hooks → "No hooks generated yet" + botón "Generate your first hook"; si hay → "X hooks generated this month (YYYY-MM)" + enlace "Generate more →".

F3.7 Página Usage (Uso)

Descripción: Vista detallada de consumo mensual.

Flujo: 1. Carga GET /user/usage. 2. Card "Monthly Usage (YYYY-MM)": hooks usados vs límite, barra de progreso con % usado y restantes. 3. Tres stat cards: This Month (hooks generados), Plan Limit (∞ o límite), Remaining (∞ o restantes).

F3.8 Historial de contenido (vista interna "Historial")

Descripción: Vista por tabs del header (no es una ruta). Lista de contenido generado con búsqueda por tema.

Flujo: 1. Tab "Historial" del header. 2. Input de búsqueda (filtra por tema, case-insensitive). 3. Lista de items {topic, platform, date} con botón "Descargar". 4. Estado actual: la lista se inicializa vacía (useState([])) → siempre muestra "Aún no has generado contenido" (estado placeholder, sin integración backend todavía).

F3.9 Facturación — suscripción Stripe (vista interna "Facturación")

Descripción: Idéntica estructura a CaptionAI: plan actual, PlanCards, paquetes de créditos, historial de facturas.

Flujo: 1. Tab "Facturación" del header → useBilling() carga loadAll() (pricing-plans, summary, invoices). 2. Sin clerk_token en localStorage: solo planes (botones "Inicia sesión"), créditos y prompt de login. 3. Con sesión: tarjeta "Tu Plan" (plan, estado, renovación, cancelación a fin de período, importe/intervalo), grid de planes, créditos ("Comprar"), tabla de facturas (nº, fecha, importe, estado coloreado, "Ver" → hostedUrl). 4. "Suscribirse" → create-checkout-session (successUrl /billing?success=true); "Gestionar facturación" → Customer Portal; "Comprar" créditos → buy-credits. 5. Back button: "Volver a HookGenerator".

F3.10 Perfil (vista interna "Perfil" + ruta /profile)

Descripción: Info de usuario Clerk, plan y uso, y gestión de API keys (Enterprise). Mismo componente que en CaptionAI (bilingüe).

Flujo: 1. Tab "Perfil" del header o ruta /profile. 2. Sin sesión → "Inicia sesión para acceder al perfil". 3. Con sesión: Información del Usuario (avatar, nombre, email, miembro desde), Plan y Uso (plan, límite diario, usados, restantes, créditos extra, acceso API). 4. Claves API: solo Enterprise (bloqueo "Actualiza a Enterprise" + botón "Ver Planes" → /billing si no lo es; generar con nombre, ver mascarada, copiar, revocar con confirmación, IP whitelist).

F3.11 Blog (Ghost CMS)

Descripción: Página /blog alimentada por Ghost (Content API) vía services/ghost.service.ts. Muestra artículos del blog de HookGenerator.

F3.12 Landing page

Descripción: Página pública / con hero, bento de características, sección de precios estáticos y CTA.

Flujo: 1. Hero: título "Crea hooks virales para tu próximo video", subtítulo, CTA "Empezar gratis" (→ /register) y "Ver precios" (→ /#pricing). 2. Features bento (4 cards: 🤖 IA avanzada, 🎨 Múltiples estilos, ⚡ Resultados rápidos, 📊 Analíticas). 3. Precios estáticos hardcodeados: Free $0 ("10 hooks/day", estilos básicos, soporte comunitario), Pro $9.99 ("100 hooks/day", todos los estilos, soporte prioritario, analíticas — marcado "popular"), Enterprise $29.99 (hooks ilimitados, acceso API, soporte dedicado, plantillas personalizadas). CTAs → /register. 4. CTA final ("Listo para destacar" → "Crear cuenta gratis").

F3.13 Página Pricing

Descripción: Página /pricing standalone con 3 planes hardcodeados (Free $0 / Pro $9 / Enterprise $49), toggle Mensual/Anual (visual, -20%), FAQ (4 preguntas) y CTA final.

Flujo: 1. Header propio (logo + "Iniciar sesión" + "Registrarse gratis"). 2. Hero con badge "Planes y Precios". 3. Toggle Mensual/Anual (interruptor visual; los precios no cambian — estático). 4. Cards: Free ("Perfecto para probar", 50 hooks/mes, estilos básicos, exportar a texto, soporte comunitario), Pro ("Para creadores de contenido", 1.000 hooks/mes, todos los estilos, exportar CSV/JSON, historial completo, prioridad en soporte — badge "Más popular"), Enterprise ("Para equipos y agencias", hooks ilimitados, API REST, múltiples usuarios, plantillas personalizadas, soporte dedicado). CTAs → /register. 5. FAQ: cambio de plan, exceso de límite (pausa hasta el siguiente ciclo), período de prueba, facturación mensual. 6. Footer con enlaces Blog/Privacidad/Términos.

F3.14 Selectores de tema e idioma (Navbar)

Descripción: ThemeSelector (claro/oscuro/sistema) y LanguageSelector (ES/EN) en el Navbar sticky. El idioma de i18next se persiste en localStorage.language (default en en i18n/index.ts; el App.tsx usa lang propio con default es para header/badges — doble sistema de i18n coexistente).

F3.15 QuotaBadge y UpgradeDialog

Descripción: Mismos patrones que CaptionAI: badge de cuota (usados/límite) para logueados; UpgradeDialog para anónimos tras el primer hook del día (contador anon_hooks en localStorage) con texto "Crea una cuenta gratis y obten 3 hooks al día!" y botón "Iniciar sesión con Google".

Descripción: Footer global con texto "HookGenerator" (copyright).


4. Pantallas (wireframes textuales)

┌────────────────────────────────────────────────────────────────────┐
│ HEADER (sticky, backdrop-blur)                                      │
│ ┌──────────┬─────────────────────────────┬───────────────────────┐ │
│ │ [✨]      │ Generator│Historial│         │ [QuotaBadge] [Gratis] │ │
│ │ HookGener.│ Facturación│Perfil│Docs      │ [avatar] [tema][lang] │ │
│ │ subtitle  │ (tabs desktop)              │ o [Iniciar sesión]    │ │
│ └──────────┴─────────────────────────────┴───────────────────────┘ │
│ MOBILE: tabs Generator|Historial|Facturación|Perfil|Docs            │
├────────────────────────────────────────────────────────────────────┤
│ MAIN: view interno (billing/profile/history) o <Routes> público     │
│   /  /pricing  /blog  /login  /register                             │
│   /dashboard  /generator  /profile  /usage  (protegidas)            │
├────────────────────────────────────────────────────────────────────┤
│ FOOTER: "HookGenerator"                                             │
│ [UpgradeDialog] [VersionBadge]                                      │
└────────────────────────────────────────────────────────────────────┘

P4.2 Landing /

┌─ HERO (py-20/32, centrado) ──────────────────────────┐
│  H1: Crea hooks virales para tu próximo video        │
│  P: Genera automáticamente openings irresistibles…   │
│  [Empezar gratis]  [Ver precios]                     │
├─ FEATURES (bg-muted/30, bento 4 col) ────────────────┤
│  🤖 IA avanzada   🎨 Múltiples estilos               │
│  ⚡ Resultados rápidos  📊 Analíticas                │
├─ PRICING (id=pricing, 3 cards) ──────────────────────┤
│  [Free $0] [Pro $9.99 ★popular] [Enterprise $29.99]  │
│  cada una: features con ✓ y botón → /register        │
├─ CTA (bg-primary) ───────────────────────────────────┤
│  "Listo para destacar" → [Crear cuenta gratis]        │
└──────────────────────────────────────────────────────┘

P4.3 Pricing /pricing

┌─ HEADER propio: HookGenerator | Iniciar sesión | Registrarse gratis ┐
│  [Planes y Precios] Elige el plan perfecto para ti                  │
│  Mensual [toggle] Anual -20%                                        │
│  [Free $0] [Pro $9 ★Más popular] [Enterprise $49]                   │
│  FAQ (2x2): ¿Puedo cambiar de plan? ¿Qué pasa si excedo mi límite?  │
│             ¿Hay período de prueba? ¿Cómo funciona la facturación?  │
│  CTA final: ¿Listo para crear contenido viral? → Empezar gratis     │
│  Footer: © 2026 | Blog Privacidad Términos                          │
└─────────────────────────────────────────────────────────────────────┘

P4.4 Login /login y Register /register

┌───────────────────────────────┐
│        HookGenerator          │
│  ┌─────────────────────────┐  │
│  │  Clerk <SignIn/SignUp>  │  │  (card-stripe, estilos adaptados)
│  │  email + password       │  │   → forceRedirect /dashboard
│  │  o botones sociales     │  │
│  └─────────────────────────┘  │
│  "¿No tienes cuenta? Regístrate" / "¿Ya tienes cuenta? Inicia sesión" │
└───────────────────────────────┘

P4.5 Dashboard /dashboard (protegido)

Bienvenido, {nombre}!

USO (2 cards)
┌─ Monthly Usage ────────┐  ┌─ Current Plan ────────┐
│ 12 / 50                │  │ FREE (capitalize)      │
│ 38 restantes           │  │ [Upgrade → /billing]   │
│ [████████░░] progreso  │  │ (si plan free)         │
└────────────────────────┘  └────────────────────────┘

QUICK GENERATE
[input tema............] [Generate]  → /generator?topic=

RECENT ACTIVITY
"No hooks generated yet. Start creating!"  [Generate your first hook]
— o — "12 hooks generated this month (2026-08)"  Generate more →

P4.6 Generator /generator (protegido, grid 5 cols: form 2 / resultados 3)

┌──────────────┬────────────────────────────────────────────┐
│ FORM (col-2) │ RESULTADOS (col-3)                          │
│ ┌──────────┐ │ [banner error rojo si error]                │
│ │ Tema     │ │ ┌─ Resultados ───────────────────────────┐ │
│ │ [input…] │ │ │ Estado vacío:                          │ │
│ │          │ │ │  ✨ Ingresa un tema y haz clic en       │ │
│ │ Proveedor│ │ │  "Generar Hooks"                        │ │
│ │ IA       │ │ │ Estado carga: spinner + "Generando…"    │ │
│ │ [OpenAI| │ │ │ Estado con resultados:                  │ │
│ │  Gemini] │ │ │  ┌─ hook card ───────────────────────┐ │ │
│ │ [API key │ │ │  │ hook texto (multi-línea)          │ │ │
│ │  sk-…    │ │ │  │ [PREGUNTA badge]        [Copiar]  │ │ │
│ │  (pass)] │ │ │  └──────────────────────────────────┘ │ │
│ │          │ │ │  (…una card por hook, badge con estilo│ │
│ │ Cantidad │ │ │   del array styles_used)              │ │
│ │ [3][5][10]│ │ └──────────────────────────────────────┘ │
│ │          │ │                                          │
│ │ [Generar │ │                                          │
│ │  Hooks]  │ │                                          │
│ └──────────┘ │                                          │
│ (arriba: barra uso "X usados / Y restantes" + progreso  │
│  si logueado)                                           │
└──────────────┴────────────────────────────────────────────┘

P4.7 Usage /usage (protegido)

Usage
┌─ Monthly Usage (2026-08) ──────────────────────┐
│ 12 hooks used        50 limit                  │
│ [████████████░░░░] progreso h-3                │
│ 24% used              38 remaining            │
└────────────────────────────────────────────────┘
┌─ This Month ──┐ ┌─ Plan Limit ──┐ ┌─ Remaining ──┐
│ 12            │ │ 50            │ │ 38           │
└───────────────┘ └───────────────┘ └──────────────┘

P4.8 Vista Historial (tab header)

[← Volver]
Historial de Contenido — Tu contenido generado anteriormente
[🔍 Buscar por tema.....................]
┌────────────────────────────────────────────┐
│ (estado actual: lista vacía)               │
│ [📄] Aún no has generado contenido          │
└────────────────────────────────────────────┘
— o — [item: tema | plataforma · fecha | Descargar]

P4.9 Vista Facturación (tab header)

[← Volver a HookGenerator]
Facturación — Gestiona tu suscripción y facturación  [Gestionar facturación ↗]
┌─ TU PLAN ───────────────────────────┐
│ Suscrito a: Pro [Activo🟢]          │
│ Fecha de renovación: …              │
│ 9.00 EUR/month                      │
└─────────────────────────────────────┘
┌─ PLANES DE PRECIOS ────────────────┐
│ [PlanCard Free] [Pro] [Enterprise] │
└────────────────────────────────────┘
┌─ PAQUETES DE CRÉDITOS ─────────────┐
│ [1000 créditos €X Comprar] …        │
└────────────────────────────────────┘
┌─ HISTORIAL DE FACTURAS ────────────┐
│ Nº | Fecha | Importe | Estado | Ver │
└────────────────────────────────────┘

P4.10 Vista Perfil (tab header) y ruta /profile

[← Volver]
Perfil — Gestiona la configuración de tu cuenta
┌─ INFORMACIÓN DEL USUARIO ──┐  ┌─ PLAN Y USO ────────────┐
│ [avatar] Nombre            │  │ Plan: PRO               │
│ email                      │  │ Límite diario: 100      │
│ Miembro desde: …           │  │ Usados/Restantes        │
│                            │  │ Créditos Extra: 0       │
└────────────────────────────┘  │ Acceso API: Activado    │
                                └─────────────────────────┘
┌─ CLAVES API (Enterprise only) ──────────────────────┐
│ no-Enterprise: [🛡] Actualiza a Enterprise [Ver Planes]│
│ Enterprise: [+ Generar Clave API] · lista · whitelist│
└──────────────────────────────────────────────────────┘

P4.11 Blog /blog

Header del sitio | Lista de artículos de Ghost CMS (cards) | Footer

P4.12 Modal UpgradeDialog

┌─ overlay ──────────────────────────┐
│ ┌───────────────────────────────┐  │
│ │ [✨]                    [X]   │  │
│ │ Desbloquea más hooks          │  │
│ │ Crea una cuenta gratis y      │  │
│ │ obten 3 hooks al día!         │  │
│ │ [G Iniciar sesión con Google] │  │
│ └───────────────────────────────┘  │
└────────────────────────────────────┘

5. Flujos de trabajo

Flujo A: Alta

  1. Visitante llega a / (Landing) o /pricing.
  2. Pulsa "Empezar gratis" / "Crear cuenta gratis" → /register (Clerk SignUp).
  3. Tras registrarse, forceRedirectUrl="/dashboard".
  4. AuthContext obtiene perfil backend (GET /user/profile); si el backend no responde, usa datos de Clerk con plan 'free'.
  5. Alternativa: Google OAuth (login o registro) → /dashboard.
  6. Fallback sin Clerk key: login/registro contra /auth/login y /auth/register (token + user en localStorage).

Flujo B: Configuración y generación

  1. Desde Dashboard (Quick Generate) o /generator (el tema puede llegar pre-relleno vía ?topic=).
  2. En /generator: tema → proveedor (OpenAI/Gemini) → API key → cantidad (3/5/10).
  3. Pulsar "Generar Hooks" → validación (tema + API key) → POST /generate.
  4. Ver spinner "Generando hooks..." y luego la lista de hooks con badges de estilo.
  5. Copiar hooks individuales (feedback "¡Copiado!" por 2s).

Flujo C: Consumo y límites

  1. El usuario logueado ve su uso en el Generator (barra), Dashboard y Usage.
  2. Si el plan es limitado y se acerca al límite, la barra de progreso crece; al llegar al 100% (o superar hooks_limit) el backend bloquea nuevas generaciones ("No te quedan regeneraciones disponibles").
  3. Con plan ilimitado (hooks_limit === -1) no hay restricciones.

Flujo D: Billing

  1. Tab "Facturación" o botón "Upgrade" del Dashboard.
  2. Ver planes; "Suscribirse" → Stripe Checkout → retorno a /billing?success=true.
  3. "Gestionar facturación" → Customer Portal (cambiar/cancelar suscripción).
  4. Créditos extra opcionales ("Comprar").

Flujo E: Perfil y API keys (Enterprise)

  1. Tab "Perfil" → datos de usuario, plan y consumo.
  2. Enterprise: generar API key con nombre (visible una sola vez), copiar, revocar con confirmación, IP whitelist.
  3. No-Enterprise: CTA "Ver Planes" → /billing.

Flujo F: Historial

  1. Tab "Historial" → búsqueda por tema + lista con descarga.
  2. Estado actual: placeholder vacío (sin persistencia backend aún).

6. Reglas de negocio

Regla Detalle
Cuota mensual por plan UsageStats.hooks_limit define el límite mensual (Free: 50/mes según Pricing estático; Pro: 1.000; Enterprise: -1 = ilimitado). hooks_used, remaining, percent_used vienen del backend (GET /user/usage).
Generación requiere API key El frontend exige API key del proveedor (OpenAI/Gemini) o la key del servidor (OPENAI_API_KEY). Sin ninguna → error visible.
Proveedores soportados openai y gemini (tabs). El campo model se envía como undefined si está vacío (backend decide el modelo).
Cantidad de hooks Solo 3, 5 o 10 (botones).
Estilos de hooks El backend devuelve styles_used[] alineado con hooks[]; la UI los muestra como badges (fallback "PREGUNTA" si falta).
Quota anónima Contador diario anon_hooks en localStorage; tras el primer hook del día se muestra el UpgradeDialog ("3 hooks al día!").
Quota logueados usage_log en localStorage alimenta el QuotaBadge (límite del plan vía stripe.getPricingPlans()/getSummary(); default free 3).
Rutas protegidas /dashboard, /generator, /profile, /usage redirigen a /login si no hay sesión; spinner mientras AuthContext carga.
Planes estáticos (Landing/Pricing) Precios hardcodeados que no están conectados a Stripe en esas páginas (Free $0, Pro $9.99/$9, Enterprise $29.99/$49, toggle anual -20% solo visual). El billing real (con Stripe) vive en la vista Facturación con planes desde /api/v1/billing/pricing-plans.
Suscripción Stripe Detectada por GET /api/v1/billing/summary; estados: active=verde, resto=ámbar; cancelAtPeriodEnd → aviso "Se cancelará al final del período".
Facturas Estados: paid (verde), open (ámbar), uncollectible (rojo), void (gris).
API keys Solo plan Enterprise; gestión en Perfil (/api/v1/api-keys*). El bloqueo muestra "Actualiza a Enterprise" con CTA a /billing.
Auth Clerk es el proveedor principal; AuthContext añade capa propia con fallback localStorage; token vía setTokenProvider → header Authorization: Bearer en todas las llamadas api.*.
i18n Doble sistema: react-i18next (localStorage.language, default en) para páginas de router, y objeto i18n inline del App.tsx (localStorage.lang, default es) para header/badges/vistas internas.
Tema ThemeContext/selector: dark (default), light, system; clase dark en <html>.

Endpoints consumidos por el frontend