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
| Field | Type | Constraints |
|---|---|---|
| id | UUID | PK, auto-gen |
| user_id | UUID | FK auth.users, NOT NULL |
| plan | ENUM | monthly | annual |
| status | ENUM | active | expired | cancelled | trialing |
| renews_at | TIMESTAMPTZ | Nullable si cancelled |
| expires_at | TIMESTAMPTZ | NOT NULL |
magic_link_tokens
| Field | Type | Constraints |
|---|---|---|
| token_hash | VARCHAR(64) | SHA-256, UNIQUE |
| VARCHAR(254) | lowercase | |
| used | BOOLEAN | default false |
| expires_at | TIMESTAMPTZ | now + 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
funcionales
10
Criterios de
aceptación
aceptación
8
Edge
cases
cases
18
Tests
generados
generados
4
Endpoints
API
API
98
Puntuación
spec
spec