elsabro 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +268 -0
- package/agents/elsabro-analyst.md +176 -0
- package/agents/elsabro-debugger.md +293 -0
- package/agents/elsabro-executor.md +477 -0
- package/agents/elsabro-orchestrator.md +426 -0
- package/agents/elsabro-planner.md +278 -0
- package/agents/elsabro-qa.md +273 -0
- package/agents/elsabro-quick-dev.md +309 -0
- package/agents/elsabro-scrum-master.md +217 -0
- package/agents/elsabro-tech-writer.md +347 -0
- package/agents/elsabro-ux-designer.md +278 -0
- package/agents/elsabro-verifier.md +295 -0
- package/agents/elsabro-yolo-dev.md +322 -0
- package/bin/install.js +497 -0
- package/commands/elsabro/add-phase.md +114 -0
- package/commands/elsabro/add-todo.md +158 -0
- package/commands/elsabro/audit-milestone.md +147 -0
- package/commands/elsabro/check-todos.md +192 -0
- package/commands/elsabro/complete-milestone.md +138 -0
- package/commands/elsabro/debug.md +153 -0
- package/commands/elsabro/discuss-phase.md +160 -0
- package/commands/elsabro/execute.md +299 -0
- package/commands/elsabro/help.md +102 -0
- package/commands/elsabro/insert-phase.md +117 -0
- package/commands/elsabro/list-phase-assumptions.md +129 -0
- package/commands/elsabro/map-codebase.md +108 -0
- package/commands/elsabro/new-milestone.md +128 -0
- package/commands/elsabro/new.md +230 -0
- package/commands/elsabro/pause-work.md +261 -0
- package/commands/elsabro/plan-milestone-gaps.md +129 -0
- package/commands/elsabro/plan.md +272 -0
- package/commands/elsabro/progress.md +187 -0
- package/commands/elsabro/quick.md +99 -0
- package/commands/elsabro/remove-phase.md +136 -0
- package/commands/elsabro/research-phase.md +174 -0
- package/commands/elsabro/resume-work.md +288 -0
- package/commands/elsabro/set-profile.md +216 -0
- package/commands/elsabro/settings.md +185 -0
- package/commands/elsabro/start.md +204 -0
- package/commands/elsabro/update.md +71 -0
- package/commands/elsabro/verify-work.md +269 -0
- package/commands/elsabro/verify.md +207 -0
- package/hooks/dist/.gitkeep +2 -0
- package/package.json +45 -0
- package/references/error-handling-instructions.md +312 -0
- package/references/source-hierarchy.md +150 -0
- package/references/token-optimization.md +225 -0
- package/skills/api-setup.md +315 -0
- package/skills/auth-setup.md +180 -0
- package/skills/database-setup.md +238 -0
- package/skills/expo-app.md +261 -0
- package/skills/nextjs-app.md +206 -0
- package/skills/payments-setup.md +421 -0
- package/skills/sentry-setup.md +295 -0
- package/templates/error-handling-config.json +138 -0
- package/templates/session-state.json +69 -0
- package/templates/starters/.gitkeep +2 -0
- package/workflows/.gitkeep +2 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nextjs-app
|
|
3
|
+
description: Skill para crear aplicaciones web con Next.js. Usa este skill cuando el usuario quiere crear una página web, dashboard, o aplicación web.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Crear App Next.js
|
|
7
|
+
|
|
8
|
+
<when_to_use>
|
|
9
|
+
Usar cuando el usuario menciona:
|
|
10
|
+
- "página web"
|
|
11
|
+
- "sitio web"
|
|
12
|
+
- "aplicación web"
|
|
13
|
+
- "dashboard"
|
|
14
|
+
- "admin panel"
|
|
15
|
+
- "landing page"
|
|
16
|
+
- "e-commerce"
|
|
17
|
+
- "blog"
|
|
18
|
+
</when_to_use>
|
|
19
|
+
|
|
20
|
+
<before_starting>
|
|
21
|
+
## Investigación Obligatoria
|
|
22
|
+
|
|
23
|
+
**ANTES de crear cualquier archivo, verificar versiones actuales:**
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
1. Resolver Next.js:
|
|
27
|
+
mcp__plugin_context7_context7__resolve-library-id("next.js")
|
|
28
|
+
|
|
29
|
+
2. Buscar setup actual:
|
|
30
|
+
mcp__plugin_context7_context7__query-docs(id, "getting started app router")
|
|
31
|
+
|
|
32
|
+
3. Verificar TypeScript config:
|
|
33
|
+
mcp__plugin_context7_context7__query-docs(id, "typescript configuration")
|
|
34
|
+
|
|
35
|
+
4. Verificar Tailwind setup:
|
|
36
|
+
mcp__plugin_context7_context7__resolve-library-id("tailwindcss")
|
|
37
|
+
mcp__plugin_context7_context7__query-docs(id, "installation next.js")
|
|
38
|
+
```
|
|
39
|
+
</before_starting>
|
|
40
|
+
|
|
41
|
+
<standard_stack>
|
|
42
|
+
## Stack Estándar (Verificar con Context7)
|
|
43
|
+
|
|
44
|
+
| Tecnología | Propósito | Verificar |
|
|
45
|
+
|------------|-----------|-----------|
|
|
46
|
+
| Next.js 15+ | Framework React | Context7 |
|
|
47
|
+
| TypeScript | Type safety | Context7 |
|
|
48
|
+
| Tailwind CSS 4 | Estilos | Context7 |
|
|
49
|
+
| App Router | Routing | Context7 |
|
|
50
|
+
| Server Components | Rendimiento | Context7 |
|
|
51
|
+
</standard_stack>
|
|
52
|
+
|
|
53
|
+
<project_structure>
|
|
54
|
+
## Estructura de Proyecto
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
my-app/
|
|
58
|
+
├── src/
|
|
59
|
+
│ ├── app/ # App Router
|
|
60
|
+
│ │ ├── layout.tsx # Layout principal
|
|
61
|
+
│ │ ├── page.tsx # Página de inicio
|
|
62
|
+
│ │ ├── globals.css # Estilos globales
|
|
63
|
+
│ │ └── (routes)/ # Rutas agrupadas
|
|
64
|
+
│ ├── components/
|
|
65
|
+
│ │ ├── ui/ # Componentes base (Button, Input, etc.)
|
|
66
|
+
│ │ └── [feature]/ # Componentes por feature
|
|
67
|
+
│ ├── lib/ # Utilidades y helpers
|
|
68
|
+
│ └── types/ # TypeScript types
|
|
69
|
+
├── public/ # Assets estáticos
|
|
70
|
+
├── package.json
|
|
71
|
+
├── tsconfig.json
|
|
72
|
+
├── tailwind.config.ts
|
|
73
|
+
└── next.config.ts
|
|
74
|
+
```
|
|
75
|
+
</project_structure>
|
|
76
|
+
|
|
77
|
+
<setup_steps>
|
|
78
|
+
## Pasos de Setup
|
|
79
|
+
|
|
80
|
+
### Paso 1: Crear proyecto
|
|
81
|
+
```bash
|
|
82
|
+
# Verificar comando actual con Context7 antes de ejecutar
|
|
83
|
+
npx create-next-app@latest my-app --typescript --tailwind --eslint --app --src-dir
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Paso 2: Verificar que funciona
|
|
87
|
+
```bash
|
|
88
|
+
cd my-app
|
|
89
|
+
npm run dev
|
|
90
|
+
# Debe abrir http://localhost:3000
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Paso 3: Limpiar boilerplate
|
|
94
|
+
- Eliminar contenido de `src/app/page.tsx`
|
|
95
|
+
- Simplificar `src/app/globals.css`
|
|
96
|
+
- Crear estructura de carpetas
|
|
97
|
+
|
|
98
|
+
### Paso 4: Configurar para el proyecto
|
|
99
|
+
Basado en lo que el usuario quiere crear, agregar:
|
|
100
|
+
- Rutas necesarias
|
|
101
|
+
- Componentes base
|
|
102
|
+
- Configuración de Tailwind personalizada
|
|
103
|
+
</setup_steps>
|
|
104
|
+
|
|
105
|
+
<common_patterns>
|
|
106
|
+
## Patrones Comunes (Verificar con Context7)
|
|
107
|
+
|
|
108
|
+
### Layout con Sidebar
|
|
109
|
+
```typescript
|
|
110
|
+
// src/app/layout.tsx
|
|
111
|
+
// VERIFICAR patrón actual con Context7 antes de usar
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Página con Data Fetching
|
|
115
|
+
```typescript
|
|
116
|
+
// src/app/[route]/page.tsx
|
|
117
|
+
// VERIFICAR Server Components pattern con Context7
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### API Route
|
|
121
|
+
```typescript
|
|
122
|
+
// src/app/api/[route]/route.ts
|
|
123
|
+
// VERIFICAR Route Handlers pattern con Context7
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Client Component
|
|
127
|
+
```typescript
|
|
128
|
+
// src/components/[name].tsx
|
|
129
|
+
// VERIFICAR "use client" directive con Context7
|
|
130
|
+
```
|
|
131
|
+
</common_patterns>
|
|
132
|
+
|
|
133
|
+
<verification>
|
|
134
|
+
## Verificación
|
|
135
|
+
|
|
136
|
+
Antes de continuar, confirmar:
|
|
137
|
+
|
|
138
|
+
1. **Build funciona:**
|
|
139
|
+
```bash
|
|
140
|
+
npm run build
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
2. **Dev server funciona:**
|
|
144
|
+
```bash
|
|
145
|
+
npm run dev
|
|
146
|
+
# Abrir http://localhost:3000
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
3. **TypeScript sin errores:**
|
|
150
|
+
```bash
|
|
151
|
+
npx tsc --noEmit
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
4. **Usuario confirma:**
|
|
155
|
+
"¿Puedes ver la página en http://localhost:3000?"
|
|
156
|
+
</verification>
|
|
157
|
+
|
|
158
|
+
<user_testing>
|
|
159
|
+
## Guía de Prueba para Usuario
|
|
160
|
+
|
|
161
|
+
Si la IA no puede verificar algo, dar instrucciones claras:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
Para ti (Usuario):
|
|
165
|
+
|
|
166
|
+
1. Abre tu navegador
|
|
167
|
+
2. Ve a http://localhost:3000
|
|
168
|
+
3. Deberías ver [descripción de lo esperado]
|
|
169
|
+
|
|
170
|
+
¿Lo ves?
|
|
171
|
+
- SÍ → Todo funciona, continuamos
|
|
172
|
+
- NO → Dime qué ves y te ayudo
|
|
173
|
+
```
|
|
174
|
+
</user_testing>
|
|
175
|
+
|
|
176
|
+
<common_issues>
|
|
177
|
+
## Problemas Comunes
|
|
178
|
+
|
|
179
|
+
### "Module not found"
|
|
180
|
+
- Verificar que las dependencias están instaladas
|
|
181
|
+
- Verificar imports relativos vs absolutos
|
|
182
|
+
- Verificar configuración de paths en tsconfig.json
|
|
183
|
+
|
|
184
|
+
### "Hydration mismatch"
|
|
185
|
+
- Server y Client renderizan diferente
|
|
186
|
+
- Verificar uso correcto de "use client"
|
|
187
|
+
- Verificar que no usas APIs de browser en Server Components
|
|
188
|
+
|
|
189
|
+
### "Build failed"
|
|
190
|
+
- Ejecutar `npm run lint` para ver errores
|
|
191
|
+
- Verificar TypeScript con `npx tsc --noEmit`
|
|
192
|
+
- Buscar error específico con WebSearch
|
|
193
|
+
</common_issues>
|
|
194
|
+
|
|
195
|
+
<next_steps>
|
|
196
|
+
## Después del Setup
|
|
197
|
+
|
|
198
|
+
Una vez que el proyecto base funciona:
|
|
199
|
+
|
|
200
|
+
1. **Si necesita autenticación:** → @skills/auth-setup.md
|
|
201
|
+
2. **Si necesita base de datos:** → @skills/database-setup.md
|
|
202
|
+
3. **Si necesita pagos:** → @skills/payments-setup.md
|
|
203
|
+
4. **Si necesita monitoreo:** → @skills/sentry-setup.md
|
|
204
|
+
|
|
205
|
+
O continuar con `/elsabro:plan` para planificar las features.
|
|
206
|
+
</next_steps>
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: payments-setup
|
|
3
|
+
description: Skill para integrar pagos en cualquier proyecto. Usa este skill cuando el usuario quiere cobrar, vender, suscripciones, o Stripe.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill: Setup de Pagos
|
|
7
|
+
|
|
8
|
+
<when_to_use>
|
|
9
|
+
Usar cuando el usuario menciona:
|
|
10
|
+
- "pagos"
|
|
11
|
+
- "cobrar"
|
|
12
|
+
- "vender"
|
|
13
|
+
- "suscripción"
|
|
14
|
+
- "Stripe"
|
|
15
|
+
- "checkout"
|
|
16
|
+
- "carrito de compras"
|
|
17
|
+
- "e-commerce"
|
|
18
|
+
</when_to_use>
|
|
19
|
+
|
|
20
|
+
<before_starting>
|
|
21
|
+
## Investigación Obligatoria
|
|
22
|
+
|
|
23
|
+
**ANTES de implementar pagos, entender las necesidades:**
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
1. Preguntar al usuario:
|
|
27
|
+
- ¿Qué vas a vender? (productos, suscripciones, servicios)
|
|
28
|
+
- ¿Necesitas pagos recurrentes?
|
|
29
|
+
- ¿En qué países vas a operar?
|
|
30
|
+
|
|
31
|
+
2. Decidir modelo:
|
|
32
|
+
- Pago único → Stripe Checkout
|
|
33
|
+
- Suscripciones → Stripe Billing
|
|
34
|
+
- Marketplace → Stripe Connect
|
|
35
|
+
|
|
36
|
+
3. Buscar en Context7:
|
|
37
|
+
mcp__plugin_context7_context7__resolve-library-id("stripe")
|
|
38
|
+
mcp__plugin_context7_context7__query-docs(id, "checkout session")
|
|
39
|
+
```
|
|
40
|
+
</before_starting>
|
|
41
|
+
|
|
42
|
+
<recommended_stack>
|
|
43
|
+
## Stack Recomendado
|
|
44
|
+
|
|
45
|
+
### Para la mayoría de proyectos: Stripe
|
|
46
|
+
|
|
47
|
+
| Necesidad | Solución Stripe | Por qué |
|
|
48
|
+
|-----------|-----------------|---------|
|
|
49
|
+
| Pago único | Checkout | Hosted, PCI compliant |
|
|
50
|
+
| Suscripciones | Billing | Maneja todo automáticamente |
|
|
51
|
+
| Marketplace | Connect | Multi-vendor |
|
|
52
|
+
|
|
53
|
+
### Alternativas
|
|
54
|
+
|
|
55
|
+
| Proveedor | Cuándo Usar |
|
|
56
|
+
|-----------|-------------|
|
|
57
|
+
| Paddle | SaaS, impuestos automáticos |
|
|
58
|
+
| Lemonsqueezy | Productos digitales |
|
|
59
|
+
| PayPal | Audiencia que lo prefiere |
|
|
60
|
+
</recommended_stack>
|
|
61
|
+
|
|
62
|
+
<stripe_setup>
|
|
63
|
+
## Setup Stripe (Recomendado)
|
|
64
|
+
|
|
65
|
+
### Paso 1: Crear cuenta Stripe
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
Para ti (Usuario):
|
|
69
|
+
|
|
70
|
+
1. Ve a https://stripe.com
|
|
71
|
+
2. Crea una cuenta
|
|
72
|
+
3. Ve a Developers → API Keys
|
|
73
|
+
4. Copia la "Secret key" (empieza con sk_test_)
|
|
74
|
+
5. Copia la "Publishable key" (empieza con pk_test_)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Paso 2: Instalar SDK
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm install stripe @stripe/stripe-js
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Paso 3: Configurar variables de entorno
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# .env.local
|
|
87
|
+
STRIPE_SECRET_KEY=sk_test_xxx
|
|
88
|
+
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx
|
|
89
|
+
STRIPE_WEBHOOK_SECRET=whsec_xxx
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Paso 4: Crear cliente servidor
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// src/lib/stripe.ts
|
|
96
|
+
// VERIFICAR con Context7
|
|
97
|
+
|
|
98
|
+
import Stripe from 'stripe'
|
|
99
|
+
|
|
100
|
+
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
|
101
|
+
apiVersion: '2024-11-20.acacia', // Verificar versión actual
|
|
102
|
+
typescript: true,
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Paso 5: Crear cliente frontend
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// src/lib/stripe-client.ts
|
|
110
|
+
|
|
111
|
+
import { loadStripe } from '@stripe/stripe-js'
|
|
112
|
+
|
|
113
|
+
export const stripePromise = loadStripe(
|
|
114
|
+
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
</stripe_setup>
|
|
118
|
+
|
|
119
|
+
<checkout_implementation>
|
|
120
|
+
## Implementar Checkout (Pago Único)
|
|
121
|
+
|
|
122
|
+
### Paso 1: Crear endpoint de checkout
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// src/app/api/checkout/route.ts
|
|
126
|
+
// VERIFICAR con Context7
|
|
127
|
+
|
|
128
|
+
import { stripe } from '@/lib/stripe'
|
|
129
|
+
import { NextResponse } from 'next/server'
|
|
130
|
+
|
|
131
|
+
export async function POST(request: Request) {
|
|
132
|
+
const { priceId, quantity = 1 } = await request.json()
|
|
133
|
+
|
|
134
|
+
const session = await stripe.checkout.sessions.create({
|
|
135
|
+
mode: 'payment',
|
|
136
|
+
payment_method_types: ['card'],
|
|
137
|
+
line_items: [
|
|
138
|
+
{
|
|
139
|
+
price: priceId,
|
|
140
|
+
quantity,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
success_url: `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
|
|
144
|
+
cancel_url: `${process.env.NEXT_PUBLIC_URL}/cancel`,
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
return NextResponse.json({ url: session.url })
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Paso 2: Botón de compra
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// src/components/BuyButton.tsx
|
|
155
|
+
'use client'
|
|
156
|
+
|
|
157
|
+
import { useState } from 'react'
|
|
158
|
+
|
|
159
|
+
export function BuyButton({ priceId }: { priceId: string }) {
|
|
160
|
+
const [loading, setLoading] = useState(false)
|
|
161
|
+
|
|
162
|
+
const handleClick = async () => {
|
|
163
|
+
setLoading(true)
|
|
164
|
+
|
|
165
|
+
const response = await fetch('/api/checkout', {
|
|
166
|
+
method: 'POST',
|
|
167
|
+
headers: { 'Content-Type': 'application/json' },
|
|
168
|
+
body: JSON.stringify({ priceId }),
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const { url } = await response.json()
|
|
172
|
+
window.location.href = url
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<button onClick={handleClick} disabled={loading}>
|
|
177
|
+
{loading ? 'Procesando...' : 'Comprar'}
|
|
178
|
+
</button>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Paso 3: Página de éxito
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
// src/app/success/page.tsx
|
|
187
|
+
|
|
188
|
+
export default async function SuccessPage({
|
|
189
|
+
searchParams,
|
|
190
|
+
}: {
|
|
191
|
+
searchParams: { session_id: string }
|
|
192
|
+
}) {
|
|
193
|
+
return (
|
|
194
|
+
<div>
|
|
195
|
+
<h1>¡Gracias por tu compra!</h1>
|
|
196
|
+
<p>Tu pago fue procesado exitosamente.</p>
|
|
197
|
+
</div>
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
</checkout_implementation>
|
|
202
|
+
|
|
203
|
+
<subscription_implementation>
|
|
204
|
+
## Implementar Suscripciones
|
|
205
|
+
|
|
206
|
+
### Paso 1: Crear producto en Stripe Dashboard
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
Para ti (Usuario):
|
|
210
|
+
|
|
211
|
+
1. Ve a Stripe Dashboard → Products
|
|
212
|
+
2. Click "Add product"
|
|
213
|
+
3. Nombre: Tu plan (ej: "Pro")
|
|
214
|
+
4. Precio: Selecciona "Recurring"
|
|
215
|
+
5. Configura el precio mensual/anual
|
|
216
|
+
6. Copia el Price ID (empieza con price_)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Paso 2: Endpoint de suscripción
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
// src/app/api/subscribe/route.ts
|
|
223
|
+
|
|
224
|
+
import { stripe } from '@/lib/stripe'
|
|
225
|
+
import { NextResponse } from 'next/server'
|
|
226
|
+
|
|
227
|
+
export async function POST(request: Request) {
|
|
228
|
+
const { priceId, customerId } = await request.json()
|
|
229
|
+
|
|
230
|
+
const session = await stripe.checkout.sessions.create({
|
|
231
|
+
mode: 'subscription',
|
|
232
|
+
customer: customerId, // Opcional: si ya tienes el customer
|
|
233
|
+
payment_method_types: ['card'],
|
|
234
|
+
line_items: [
|
|
235
|
+
{
|
|
236
|
+
price: priceId,
|
|
237
|
+
quantity: 1,
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
success_url: `${process.env.NEXT_PUBLIC_URL}/dashboard?subscribed=true`,
|
|
241
|
+
cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing`,
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
return NextResponse.json({ url: session.url })
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Paso 3: Portal del cliente (cancelar/modificar)
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
// src/app/api/portal/route.ts
|
|
252
|
+
|
|
253
|
+
import { stripe } from '@/lib/stripe'
|
|
254
|
+
import { NextResponse } from 'next/server'
|
|
255
|
+
|
|
256
|
+
export async function POST(request: Request) {
|
|
257
|
+
const { customerId } = await request.json()
|
|
258
|
+
|
|
259
|
+
const session = await stripe.billingPortal.sessions.create({
|
|
260
|
+
customer: customerId,
|
|
261
|
+
return_url: `${process.env.NEXT_PUBLIC_URL}/dashboard`,
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
return NextResponse.json({ url: session.url })
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
</subscription_implementation>
|
|
268
|
+
|
|
269
|
+
<webhooks>
|
|
270
|
+
## Webhooks (Importante)
|
|
271
|
+
|
|
272
|
+
### Por qué necesitas webhooks
|
|
273
|
+
Stripe notifica a tu app cuando:
|
|
274
|
+
- Un pago es exitoso
|
|
275
|
+
- Una suscripción se cancela
|
|
276
|
+
- Un pago falla
|
|
277
|
+
- etc.
|
|
278
|
+
|
|
279
|
+
### Paso 1: Crear endpoint de webhook
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
// src/app/api/webhooks/stripe/route.ts
|
|
283
|
+
// VERIFICAR con Context7
|
|
284
|
+
|
|
285
|
+
import { stripe } from '@/lib/stripe'
|
|
286
|
+
import { headers } from 'next/headers'
|
|
287
|
+
import { NextResponse } from 'next/server'
|
|
288
|
+
|
|
289
|
+
export async function POST(request: Request) {
|
|
290
|
+
const body = await request.text()
|
|
291
|
+
const signature = headers().get('stripe-signature')!
|
|
292
|
+
|
|
293
|
+
let event
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
event = stripe.webhooks.constructEvent(
|
|
297
|
+
body,
|
|
298
|
+
signature,
|
|
299
|
+
process.env.STRIPE_WEBHOOK_SECRET!
|
|
300
|
+
)
|
|
301
|
+
} catch (err) {
|
|
302
|
+
return NextResponse.json(
|
|
303
|
+
{ error: 'Webhook signature verification failed' },
|
|
304
|
+
{ status: 400 }
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
switch (event.type) {
|
|
309
|
+
case 'checkout.session.completed':
|
|
310
|
+
const session = event.data.object
|
|
311
|
+
// Actualizar base de datos
|
|
312
|
+
// Enviar email de confirmación
|
|
313
|
+
break
|
|
314
|
+
|
|
315
|
+
case 'customer.subscription.deleted':
|
|
316
|
+
// Manejar cancelación
|
|
317
|
+
break
|
|
318
|
+
|
|
319
|
+
case 'invoice.payment_failed':
|
|
320
|
+
// Notificar al usuario
|
|
321
|
+
break
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return NextResponse.json({ received: true })
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Paso 2: Configurar webhook en Stripe
|
|
329
|
+
|
|
330
|
+
```
|
|
331
|
+
Para ti (Usuario):
|
|
332
|
+
|
|
333
|
+
En desarrollo:
|
|
334
|
+
1. Instala Stripe CLI: brew install stripe/stripe-cli/stripe
|
|
335
|
+
2. Login: stripe login
|
|
336
|
+
3. Escuchar eventos: stripe listen --forward-to localhost:3000/api/webhooks/stripe
|
|
337
|
+
4. Copia el webhook secret que aparece
|
|
338
|
+
|
|
339
|
+
En producción:
|
|
340
|
+
1. Ve a Stripe Dashboard → Webhooks
|
|
341
|
+
2. Add endpoint
|
|
342
|
+
3. URL: https://tu-dominio.com/api/webhooks/stripe
|
|
343
|
+
4. Selecciona eventos a escuchar
|
|
344
|
+
5. Copia el signing secret
|
|
345
|
+
```
|
|
346
|
+
</webhooks>
|
|
347
|
+
|
|
348
|
+
<verification>
|
|
349
|
+
## Verificación
|
|
350
|
+
|
|
351
|
+
### Con Stripe CLI
|
|
352
|
+
```bash
|
|
353
|
+
# Simular pago exitoso
|
|
354
|
+
stripe trigger checkout.session.completed
|
|
355
|
+
|
|
356
|
+
# Ver logs
|
|
357
|
+
stripe logs tail
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Manual (para usuario)
|
|
361
|
+
```
|
|
362
|
+
Para ti (Usuario):
|
|
363
|
+
|
|
364
|
+
1. Abre tu app
|
|
365
|
+
2. Click en el botón de comprar
|
|
366
|
+
3. Usa la tarjeta de prueba: 4242 4242 4242 4242
|
|
367
|
+
4. Fecha: cualquier fecha futura
|
|
368
|
+
5. CVC: cualquier número
|
|
369
|
+
|
|
370
|
+
¿Qué debería pasar?
|
|
371
|
+
- Se abre la página de Stripe
|
|
372
|
+
- Puedes ingresar la tarjeta de prueba
|
|
373
|
+
- Al pagar, vuelves a tu página de éxito
|
|
374
|
+
|
|
375
|
+
¿Funcionó?
|
|
376
|
+
```
|
|
377
|
+
</verification>
|
|
378
|
+
|
|
379
|
+
<test_cards>
|
|
380
|
+
## Tarjetas de Prueba
|
|
381
|
+
|
|
382
|
+
| Número | Resultado |
|
|
383
|
+
|--------|-----------|
|
|
384
|
+
| 4242 4242 4242 4242 | Pago exitoso |
|
|
385
|
+
| 4000 0000 0000 0002 | Tarjeta declinada |
|
|
386
|
+
| 4000 0000 0000 3220 | Requiere autenticación |
|
|
387
|
+
|
|
388
|
+
Usa cualquier:
|
|
389
|
+
- Fecha de expiración futura
|
|
390
|
+
- CVC de 3 dígitos
|
|
391
|
+
- Código postal válido
|
|
392
|
+
</test_cards>
|
|
393
|
+
|
|
394
|
+
<security_checklist>
|
|
395
|
+
## Checklist de Seguridad
|
|
396
|
+
|
|
397
|
+
- [ ] Secret key solo en servidor (nunca en frontend)
|
|
398
|
+
- [ ] Webhook signature verificada
|
|
399
|
+
- [ ] HTTPS en producción
|
|
400
|
+
- [ ] Precios definidos en Stripe (no enviados desde frontend)
|
|
401
|
+
- [ ] Validar session_id antes de dar acceso
|
|
402
|
+
- [ ] Manejar webhooks idempotentemente
|
|
403
|
+
</security_checklist>
|
|
404
|
+
|
|
405
|
+
<common_issues>
|
|
406
|
+
## Problemas Comunes
|
|
407
|
+
|
|
408
|
+
### "No such price"
|
|
409
|
+
- Verificar que el priceId existe en Stripe
|
|
410
|
+
- Verificar que estás usando las keys correctas (test vs live)
|
|
411
|
+
|
|
412
|
+
### "Webhook signature verification failed"
|
|
413
|
+
- Verificar STRIPE_WEBHOOK_SECRET
|
|
414
|
+
- En desarrollo: usar el secret del CLI
|
|
415
|
+
- Asegurar que el body no fue modificado
|
|
416
|
+
|
|
417
|
+
### "Invalid API Key"
|
|
418
|
+
- Verificar que STRIPE_SECRET_KEY está configurado
|
|
419
|
+
- Verificar que no hay espacios extra
|
|
420
|
+
- Verificar test vs live mode
|
|
421
|
+
</common_issues>
|