Spec-First Workflow Status: Approved Score: 98/100 · A 18 tests generados

Portal de Membresías CULTIVA IA

Especificación formal completa — flujo de 6 fases aplicado antes de escribir una sola línea de código

Autor: Álvaro Gimeno Fecha: 2026-06-12 Stack: Astro · Vercel Edge · Supabase · Lemon Squeezy Sprint: 2 semanas / 2 devs
📋
Requisitos Funcionales
10 requisitos · RFC 2119 · MUST / SHOULD / MAY
FR-1 MUST Permitir registro e inicio de sesión mediante magic link enviado a email (sin contraseña).
FR-2 MUST Integrar con Lemon Squeezy para procesar pagos de plan mensual (19 €) y anual (149 €).
FR-3 MUST Verificar en cada descarga que el usuario tiene membresía activa antes de entregar el archivo.
FR-4 MUST Almacenar en Supabase el estado de membresía (active/expired/cancelled) y fecha de renovación.
FR-5 MUST Manejar webhooks de Lemon Squeezy para activar, renovar y cancelar membresías automáticamente.
FR-6 SHOULD Enviar email de confirmación cuando la membresía se active o renueve.
FR-7 SHOULD Mostrar en el dashboard la fecha de próxima renovación y el plan contratado.
FR-8 MAY Ofrecer un período de prueba de 7 días sin cargo (trial nativo de Lemon Squeezy).
FR-9 MUST NOT Exponer URLs de descarga directa sin verificación de membresía activa.
FR-10 MUST NOT Almacenar datos de tarjeta de crédito; todo el procesamiento es externo (Lemon Squeezy).
Criterios de Aceptación
10 criterios · formato Given/When/Then · trazabilidad completa
AC-1 Registro con magic link
FR-1
Givenvisitante no autenticado en /unirse
Whenintroduce su email y hace clic en "Enviar enlace mágico"
Thenrecibe un email con enlace único en < 30 segundos
Andel enlace tiene validez de 15 minutos
AC-3 Token expirado
FR-1 · NFR-S3
Givenmagic link con más de 15 min de antigüedad
Whenusuario intenta autenticarse
Thenerror "Enlace expirado. Solicita uno nuevo."
Andredirige a /unirse con formulario vacío
AC-5 Activación por webhook
FR-5 · FR-4
GivenLemon Squeezy emite subscription_created tras pago
Whenwebhook llega a /api/webhooks/lemon-squeezy
Thenfirma HMAC-SHA256 verificada → membresía activa creada
Anddevuelve HTTP 200
AC-6 Descarga protegida (miembro activo)
FR-3 · FR-9
Givenusuario con membresía active autenticado
WhenGET /api/download?slug=skill-seo-audit
Thenrecibe URL firmada con expiración de 60 segundos
AC-7 Descarga bloqueada (sin membresía)
FR-3 · FR-9
Givenusuario sin membresía activa (expired/cancelled)
Whensolicita descarga del recurso
ThenHTTP 403 · error MEMBERSHIP_REQUIRED + CTA upgrade
AC-8 Rate limit magic link
NFR-S2
Givenemail que ya solicitó 5 magic links en la última hora
Whensolicita un sexto magic link
ThenHTTP 429 "Demasiados intentos. Espera 1 hora."
🔌
Contratos de API
4 endpoints · TypeScript interfaces · errores documentados
POST /api/auth/magic-link
interface MagicLinkRequest { email: string; // RFC 5322, max 254 chars } // 200 OK interface MagicLinkResponse { message: "Enlace enviado. Revisa tu email."; } // 429 Rate Limited interface RateLimitError { error: "RATE_LIMITED"; retryAfterSeconds: number; }
GET /api/download?slug={slug}
// Headers: Cookie: sb-session=<jwt> // 200 OK interface DownloadResponse { signedUrl: string; // expira en 60s filename: string; expiresAt: string; // ISO 8601 } // 403 Forbidden interface AccessError { error: "MEMBERSHIP_REQUIRED" | "NOT_AUTHENTICATED"; upgradeUrl?: string; }
⚠️
Edge Cases
8 escenarios de fallo · una dependencia = al menos un fallo cubierto
EC-1
Email con caracteres unicode normalizar a lowercase ASCII antes de buscar en Supabase.
EC-2
Webhook duplicado (mismo event_id) responder 200 sin crear duplicados (idempotency check en processed_webhooks).
EC-3
Supabase no responde durante verificación devolver HTTP 503, no 403 (no bloquear al usuario por error de infra).
EC-4
Usuario cierra tab durante checkout de Lemon Squeezy antes de pagar no se crea membresía; DB no cambia.
EC-5
Magic link visitado dos veces (doble clic) segunda petición devuelve "Enlace ya utilizado" y redirige a /unirse.
EC-6
Campo custom_data.user_id falta en webhook log de error + HTTP 422 (no 200) para que Lemon reintente.
EC-7
Supabase free tier supera 500 MB de almacenamiento alerta al admin por email al llegar al 80% del límite.
EC-8
Usuario con dos cuentas en el mismo email merge automático en primera autenticación por magic link.
🚀
6 Fases del Workflow
Estado actual del proyecto
Fase 1 · Requisitos
Brief revisado, constraints identificados, unknowns documentados.
Fase 2 · Spec escrita
9 secciones completas: FR, NFR, AC, EC, API, Data Model, OOS.
Fase 3 · Validación
spec_validator.py: 98/100 (A) · 0 errores · 1 warning.
Fase 4 · Tests generados
test_extractor.py: 18 stubs (10 AC + 8 EC) · todos en rojo.
Fase 5 · Implementación
TDD: hacer tests pasar de rojo a verde, uno por AC.
6
Fase 6 · Self-Review
Checklist post-implementación: trazabilidad, contratos API, NFRs.
📊
Resultado del Validador
spec_validator.py --strict
98 /100
A
Context10/10
Functional Req.15/15
Non-Functional10/10
Acceptance Criteria20/20
Edge Cases10/10
API Contracts10/10
Data Models10/10
Out of Scope10/10
Metadata5/5
⚠ FR-6, FR-8, FR-10 sin AC aún
🧪
Tests Extraídos Automáticamente
18 stubs · pytest · todos en ROJO (fase TDD)
test_ac1_registro_con_magic_link
AC
test_ac2_activacion_de_sesion
AC
test_ac3_token_expirado
AC
test_ac4_checkout_membres_a_mensual
AC
test_ac5_activacion_por_webhook
AC
test_ac6_descarga_protegida
AC
test_ac7_descarga_bloqueada
AC
test_ac8_rate_limit_magic_link
AC
test_ac9_cancelacion_membres_a
AC
test_ac10_dashboard_membres_a
AC
test_ec2_webhook_duplicado
EC
test_ec3_supabase_no_responde
EC
test_ec5_magic_link_doble_clic
EC
test_ec6_missing_user_id_webhook
EC
test_ec8_merge_cuentas_duplicadas
EC
+ 3 más (EC-1, EC-4, EC-7)
🗄️
Modelos de Datos
3 tablas Supabase · constraints documentados
memberships
FieldTypeConstraints
idUUIDPK, auto-gen
user_idUUIDFK auth.users, NOT NULL
planENUMmonthly | annual
statusENUMactive | expired | cancelled | trialing
renews_atTIMESTAMPTZNullable si cancelled
expires_atTIMESTAMPTZNOT NULL
magic_link_tokens
FieldTypeConstraints
token_hashVARCHAR(64)SHA-256, UNIQUE
emailVARCHAR(254)lowercase
usedBOOLEANdefault false
expires_atTIMESTAMPTZnow + 15 min
Requisitos No Funcionales
Rendimiento · Seguridad · A11y · Escalabilidad
Rendimiento
NFR-P1Verificación membresía < 300 ms (p95) a 500 usuarios concurrentes.
NFR-P2Magic link < 500 ms (p95).
Seguridad
NFR-S2Máx. 5 magic links/email/hora (rate limit).
NFR-S3Tokens de magic link: 15 min · un solo uso.
NFR-S4Webhooks verificados con HMAC-SHA256.
Accesibilidad
NFR-A1Flow de magic link WCAG 2.1 AA.
NFR-A2Errores anunciados por lectores de pantalla (aria-live).
Escalabilidad
NFR-SC2Supabase free tier: < 500 MB storage, < 2 GB transferencia/mes.
🚫
Fuera de Alcance
6 exclusiones explícitas · anti-scope-creep
OS-1 OAuth social (Google/GitHub) — deferred a sprint 4.
OS-2 Panel de admin para membresías — usar dashboard Lemon Squeezy.
OS-3 Membresía de equipo — requiere spec separada y modelo de datos distinto.
OS-4 Cupones y descuentos — gestionados por Lemon Squeezy en checkout.
OS-5 API pública para terceros — no en v1.
OS-6 i18n — solo español en v1.
10
Requisitos
funcionales
10
Criterios de
aceptación
8
Edge
cases
18
Tests
generados
4
Endpoints
API
98
Puntuación
spec