MEMORIA FUNCIONAL — HashtagLab
Producto: HashtagLab — Motor de sugerencia de hashtags con IA
URL live: https://hashtaglab.theboomer.dev
Frontend real (v1): code/src/frontend/ — React+Vite+Clerk (App.tsx, components/GeneratorForm.tsx, components/HashtagResults.tsx, components/PlatformSelector.tsx, types/api.ts)
Frontend v2 (desplegado): code/hashtaglab-v2/ — landing genérica de plantilla (ver §7)
Backend: FastAPI (code/src/backend/app/main.py, api/routes.py, models/schemas.py, services/hashtag_generator.py, core/rate_limiter.py); MongoDB (Motor); auth Clerk; billing vía tentpole-stripe-api
Fuente: Documento generado a partir del código real del frontend v1 y del backend.
1. Introducción
HashtagLab genera conjuntos de hashtags optimizados para redes sociales a partir de un tema (y opcionalmente un nicho) y una plataforma objetivo. El motor devuelve una distribución estratégica en tres grupos: Relevantes (competencia media), Relacionados/Nicho (competencia baja) y Trending (competencia alta), en proporción 40/40/20. Cada hashtag incluye volumen estimado, nivel de competencia y categoría. La UI permite copiar hashtags individuales o todos a la vez, y avisa si el conjunto supera el límite de la plataforma.
Incluye: autenticación Clerk, cuota diaria por plan (anónimos 1/día, registrados free 3/día), límites por plataforma, caché de trends, 8 categorías, endpoints de billing Stripe y documentación de API (Scalar).
⚠️ Estado de despliegue: según el contexto del proyecto, el sitio live sirve actualmente
hashtaglab-v2(plantilla de landing genérica con hero/features/FAQ/CTA placeholder — ver §7). La aplicación funcional completa es el frontend v1 decode/src/frontend(con build endist/) + el backend decode/src/backend. Esta memoria documenta el producto real (v1 + backend) y describe la v2 como estado desplegado.
2. Tipos de usuario
| Tipo | Acceso |
|---|---|
| Usuario anónimo | Puede generar 1 set de hashtags/día (localStorage anon_hashtags + límite IP en backend). Tras la 1ª generación se abre el UpgradeDialog. |
| Usuario registrado (Free) | 3 generaciones/día (FREE_REGISTERED_DAILY_LIMIT). QuotaBadge usados/límite en el header. |
| Usuario Pro / Enterprise | Límites según plan (stripe-api); extra_rate para exceso con cargo; fallback a créditos bonus antes de rechazar. |
3. Funcionalidades
F-101 Autenticación con Clerk
- Descripción: Login/registro modal de Clerk (Google SSO). Token JWT en
localStorage('clerk_token')+Authorization: Bearer. - Flujo: "Iniciar sesión" → modal Clerk →
useClerkToken()→ UI autenticada (QuotaBadge + badge "Gratis" + UserButton).afterSignOutUrl="/". - Backend:
GET /api/v1/auth/meyPOST /api/v1/auth/sync(upsert del usuario Clerk en MongoDB:db.upsert_user).
F-102 Formulario de generación (GeneratorForm)
- Descripción: Formulario de 3 campos para generar hashtags.
- Campos:
- Topic or Keyword (obligatorio): input,
minLength=2,maxLength=100, placeholder "e.g., digital marketing, fitness tips, travel photography". - Niche (Optional): input,
maxLength=50, placeholder "e.g., tech, food, fitness, fashion". - Target Platform: grid de botones con icono y nombre — Instagram (default), X/Twitter, LinkedIn, TikTok, Facebook (5 plataformas; grid 2 col. móvil / 4 col. desktop).
- Botón submit: gradiente
from-primary to-accent, "Generate Hashtags" (Sparkles) / "Generating..." (spinner) en carga. - Validaciones:
!topic.trim() || isLoading→ submit deshabilitado;handleSubmitno envía sin tema. El backend (Pydantic) validatopic≥2 caracteres (normaliza a minúsculas) yplatform∈ {instagram, twitter, linkedin, tiktok}.
F-103 Generación de hashtags
- Descripción: Acción principal.
- Flujo:
1. Submit del formulario →
POST /api/v1/hashtags/generatecon{topic, platform, niche?}+ Bearer si hay sesión. 2. El backend ejecutacheck_hashtag_quota()(cuota diaria vía stripe-api). 3.generator.generate(): extrae keywords del tema (con expansión por sinónimos ES/EN), determina categoría (por nicho o match de keywords), recoge hashtags de la base de datos por categoría + primeros 3 de las demás categorías, filtra por volumen (10.000–100.000.000), clasifica por competencia, añade trending y balancea 40% nicho / 40% relevantes / 20% trending (redondeo) según el límite de la plataforma. 4. Guarda la generación en MongoDB (db.save_generation: counts por grupo) si hay usuario. 5. Respuesta:{success, relevant[], related[], trending[], generated_at, platform, topic}— cada item{tag, volume, competition, category}. - Estados frontend:
loading(spinner),error(banner en panel del formulario conerrData.detail),result(panel de resultados). Anónimo:incrementAnonUsage()→ UpgradeDialog; autenticado:incrementTodayUsage(). - Errores: 400 (topic <2 caracteres), 422 (platform inválida), 429 (cuota), 500 (interno).
F-104 Visualización de resultados (HashtagResults)
- Descripción: Panel "Generated Hashtags" con estadísticas y los 3 grupos.
- Header: título +
{topic} • {platform}+ botón X (cerrar →setResult(null)). - Barra de estadísticas: total
N hashtags(icono #); sitotal > límite de plataformaaviso ámbar "Over {platform} limit ({limit})"; botón "Copy All" → copia todos los tags unidos por espacios → "Copied!" (2s). - Secciones:
- Relevant (icono 💡, competencia media) — backend
relevant. - Related (Niche) (icono #, verde esmeralda, competencia baja) — backend
related. - Trending (icono 📈, rosa, competencia alta) — backend
trending. - Cada hashtag: tarjeta con
tag, badge de competencia coloreado (low→verde, medium→amarillo, high→rosa) y botón de copia individual (check verde 1.5s). - Límites por plataforma (frontend): Instagram 30, X/Twitter 10, LinkedIn 10, TikTok 25, Facebook 30.
- Animación: entrada escalonada por índice (
animationDelay: index * 50ms).
F-105 Copiar todos los hashtags
- Descripción: CTA en la vista de resultados.
- Flujo:
handleCopyAll()unerelevant + related + trending(tags con espacio) →navigator.clipboard.writeText→ feedback "Copied!" 2s.
F-106 Quota badge
- Descripción: Badge del header (solo autenticados)
usados/límite(ej.2/3) + código de plan; patrón idéntico a ThumbnailGen/SocialCutter (localStorage('usage_log')+plan.limits.daily_captionsvía stripe-api). Clic → Billing si plan free.
F-107 Upgrade dialog para anónimos
- Descripción: Modal tras la 1ª generación anónima ("Desbloquea mas hashtags" / "Crea una cuenta gratis y obten 3 hashtags al dia!" — texto del patrón común) con botón "Iniciar sesion con Google" y cierre (X/backdrop). Disparo:
!isSignedIn && getAnonUsageToday() > 0.
F-108 Tendencias y catálogos (API)
- Descripción: Endpoints de soporte.
GET /api/v1/trends/{platform}: hashtags trending por plataforma (instagram/twitter/linkedin/tiktok), con caché en MongoDB por plataforma (get_trending_cache/set_trending_cache); 400 si plataforma inválida.GET /api/v1/platforms: Instagram (30), Twitter, LinkedIn, TikTok conmax_hashtagse icono.GET /api/v1/categories: 8 categorías — Technology, Business & Marketing, Fitness & Health, Food & Cooking, Fashion, Travel, Photography, General.
F-109 Facturación (Billing)
- Descripción: Vista de facturación (patrón tentpole): "Tu Plan" (suscripción/estado/renovación/importe), "Planes de Precios" (PlanCard), "Gestionar facturación" (portal Stripe), paquetes de créditos e historial de facturas.
- Backend:
GET /billing/pricing-plans(cache desde Stripe),POST /billing/create-checkout-session,POST /billing/create-portal-session,GET /billing/summary(guest → plan free sin suscripción),POST /billing/create-user-profile(crea customer Stripe),POST /billing/webhook. Placeholders x-internal:/billing/invoices→[],/billing/credit-packs→[],/billing/buy-credits→ URL de ejemplo (créditos TBD en este producto).
F-110 Navegación y vistas
- Descripción: Tabs Generator | Historial | Facturación | Perfil (desktop + pestañas inferiores en móvil).
- Nota de código: en el
App.tsxv1 soloview === 'billing'renderiza contenido distinto; las vistas History y Profile están importadas y navegables pero no se renderizan (al pulsarlas se muestra el grid del Generator). La UI real activa es: Generator + Billing.
4. Pantallas (wireframes textuales)
P1 — Header
┌──────────────────────────────────────────────────────────────────────────┐
│ [# HashtagLab ] Generator | Historial | Facturación | Perfil │
│ Generador de hashtags│ [2/3 ⬢free]│
│ con IA │ [👤] [🌙☀️🖥] [EN ▾] ó [Iniciar sesión] │
└──────────────────────────────────────────────────────────────────────────┘
- Default idioma v1:
en(ES/EN). Móvil: pestañas inferiores (Generator | Historial | Facturación | Perfil).
P2 — Generator (grid 3 columnas)
┌───────────────────┬──────────────────────────────────────────────────────┐
│ Generator │ ┌────────────────────────────────────────────────┐ │
│ ┌───────────────┐ │ │ (sin resultado) │ │
│ │ Topic or │ │ │ # │ │
│ │ Keyword │ │ │ Generate Hashtags │ │
│ │ [input ______]│ │ │ "Enter a topic and select a platform to │ │
│ └───────────────┘ │ │ generate optimized hashtags" │ │
│ ┌───────────────┐ │ └────────────────────────────────────────────────┘ │
│ │ Niche (Opt.) │ │ │
│ │ [input ______]│ │ ── o con resultado ── │
│ └───────────────┘ │ {topic} • {platform} [📋 Copy All] │
│ Target Platform │ Generated Hashtags [×] │
│ ┌────┐ ┌────┐ │ [# 21 hashtags] [⚠ Over instagram limit (30)] │
│ │📸 │ │💬 │ │ ── Relevant ───────────── (N) │
│ │IG │ │X │ │ [#fitnessmotivation] [medium] [📋] │
│ └────┘ └────┘ │ ── Related (Niche) ──── (N) │
│ ┌────┐ ┌────┐ │ [#gymlife] [low] [📋] │
│ │💼 │ │🎵 │ │ ── Trending ───────────── (N) │
│ │LI │ │TT │ │ [#trending] [high] [📋] │
│ └────┘ └────┘ │ │
│ (FB en 2ª fila) │ │
│ [✨ Generate │ │
│ Hashtags] │ │
│ [banner error rojo]│ │
└───────────────────┴──────────────────────────────────────────────────────┘
P3 — Billing (Facturación)
- Mismo wireframe que ThumbnailGen/SocialCutter: "Tu Plan" · "Planes de Precios" · "Gestionar facturación" · Paquetes de Créditos (placeholder backend) · Historial de Facturas (placeholder backend). Bloqueo "Inicia sesión" sin token.
P4 — UpgradeDialog (modal)
┌───────────────────────────────┐
│ [×] │
│ ✨ │
│ Desbloquea mas hashtags │
│ "Crea una cuenta gratis y │
│ obten 3 hashtags al dia!" │
│ [G Iniciar sesion con Google]│
└───────────────────────────────┘
5. Flujos de trabajo
Flujo 1 — Anónimo genera hashtags
- Escribe el tema ("fitness") y pulsa "Generate Hashtags" (platform default Instagram).
- Spinner "Generating..." → panel de resultados: 3 secciones con badges de competencia.
- "Copy All" → pega los tags en su publicación.
- Tras la 1ª generación se abre el UpgradeDialog → registrarse (Free, 3/día) o cerrar (1/día por IP).
Flujo 2 — Usuario registrado con nicho
- Login → QuotaBadge
0/3. - Tema "digital marketing" + Niche "tech" + plataforma LinkedIn.
- Genera → resultado (LinkedIn limit 10 en frontend; backend limita a 5 según
MAX_HASHTAGS_LINKEDIN) → copia individual de los relevantes. - El backend guarda la generación (counts) en MongoDB para el historial futuro.
Flujo 3 — Cuota agotada
- Backend 429
quota_exceeded: "Has alcanzado tu limite diario de hashtags. Actualiza tu plan para generar mas." (conlimit,used,plan,upgrade_url: /billing); anónimo: 429quota_exceeded_anonconregister_url: /login. Antes de rechazar se intenta consumir créditos bonus y aplicarextra_rate.
6. Reglas de negocio
Distribución de hashtags (motor)
- Proporción 40% nicho / 40% relevantes / 20% trending (
NICHO_PERCENTAGE=0.4,RELEVANT_PERCENTAGE=0.4,TRENDING_PERCENTAGE=0.2), redondeada al entero y muestreada aleatoriamente (random.sample) de cada grupo clasificado por competencia. - Filtro de volumen: 10.000 ≤ volumen ≤ 100.000.000 (
MIN_VOLUME_NICHO/MAX_VOLUME_SATURATED). - Expansión por sinónimos ES/EN del tema (marketing, digital, business, tech, food, fitness, travel, fashion) y agregación de las 3 primeras tags de otras categorías.
Límites por plataforma
| Plataforma | Backend (config.py) |
Frontend v1 (HashtagResults) |
|---|---|---|
| 30 | 30 | |
3 (MAX_HASHTAGS_TWITTER) |
10 | |
5 (MAX_HASHTAGS_LINKEDIN) |
10 | |
| TikTok | 10 (MAX_HASHTAGS_TIKTOK) |
25 |
| no soportada por el backend (422) | 30 (permitida en el formulario) |
⚠️ Discrepancia detectada: el formulario v1 permite Facebook, pero el backend solo acepta instagram/twitter/linkedin/tiktok (Literal de Pydantic → 422). Además, los límites mostrados en el frontend (twitter 10, linkedin 10, tiktok 25) no coinciden con los del backend (3/5/10). El aviso "Over limit" del frontend usa los valores del frontend.
Cuotas diarias (stripe-api)
- Anónimo: 1 generación/día por IP (
FREE_ANON_DAILY_LIMIT). Frontend:localStorage('anon_hashtags'). - Registrado free: 3/día (
FREE_REGISTERED_DAILY_LIMIT); Pro/Enterprise según plan (limits.daily_hashtags). - Si
used + quantity > limit: conextra_rate > 0se permite con cargo; si no, se intentaconsume-bonus-credit; si no, 429. - Uso reportado a stripe-api (
report-usage, servicehashtaglab).
Validaciones
topic: obligatorio, ≥2 caracteres, ≤100 (Pydantic normaliza a minúsculas y limpia espacios).platform: literal cerrado (ver tabla).niche: opcional (usado como categoría directa si se indica).- Ruta
GET /trends/{platform}: plataforma inválida → 400.
API (endpoints implementados)
| Método | Endpoint | Descripción |
|---|---|---|
| POST | /api/v1/hashtags/generate |
Generar hashtags (40/40/20) |
| GET | /api/v1/trends/{platform} |
Trending por plataforma (caché Mongo) |
| GET | /api/v1/platforms |
Plataformas soportadas + máximos |
| GET | /api/v1/categories |
8 categorías |
| GET | /api/v1/auth/me |
Perfil (upsert usuario) |
| POST | /api/v1/auth/sync |
Sync Clerk → Mongo |
| GET | /api/v1/billing/pricing-plans |
Planes (cache Stripe) |
| POST | /api/v1/billing/create-checkout-session |
Checkout |
| POST | /api/v1/billing/create-portal-session |
Portal |
| GET | /api/v1/billing/summary |
Resumen (guest → free) |
| POST | /api/v1/billing/create-user-profile |
Crear customer Stripe |
| POST | /api/v1/billing/webhook |
Webhook Stripe |
| GET | /health |
Health check (Mongo ping) |
| GET | /docs |
Documentación Scalar |
Notas técnicas
- Auth: Clerk JWT verificado con JWKS;
localStorage('clerk_token'); versiónX-Tentpole-Versionen respuestas. - Base de datos de hashtags: catálogo estático de ~80 tags en 8 categorías con
volumeycompetition; trending fijo (8 tags genéricos). - Billing: patrón TimeTrack; placeholders para invoices/credit-packs/buy-credits (marcados
x-internal).
7. Frontend v2 (hashtaglab-v2) — estado desplegado
- Qué es: plantilla de landing genérica (react-i18next + shadcn/ui) generada por scaffold, sin funcionalidad del producto.
- Secciones: Header sticky (logo "Logo", nav Features/Pricing/FAQ/Contact, LanguageMenu, ModeToggle, menú móvil) · Hero (título/subtítulo placeholder + CTAs "Get Started"/"Learn More") · Features (3 cards genéricas: ⚡ Fast Performance, 🔒 Secure, 🌍 i18n Ready) · FAQ (acordeón con 3 preguntas genéricas) · CTA ("Ready to get started?" / "Start Free Trial") · Footer "© 2024 TentPole".
- i18n: 4 idiomas (en/es/fr/it) con textos placeholder ("Your Title Here", "Tu Título Aquí"...); detección por querystring
?lang=, cookie, localStorage o navigator; fallbacken. - Impacto: el sitio live no expone hoy el generador real; para activar el producto completo habría que desplegar el frontend v1 (
code/src/frontend/dist) contra el backend decode/src/backend.