elsabro 2.1.0 → 2.2.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/commands/elsabro/add-phase.md +17 -0
- package/commands/elsabro/add-todo.md +111 -53
- package/commands/elsabro/audit-milestone.md +19 -0
- package/commands/elsabro/check-todos.md +210 -31
- package/commands/elsabro/complete-milestone.md +20 -1
- package/commands/elsabro/debug.md +19 -0
- package/commands/elsabro/discuss-phase.md +18 -1
- package/commands/elsabro/execute.md +288 -12
- package/commands/elsabro/insert-phase.md +18 -1
- package/commands/elsabro/list-phase-assumptions.md +17 -0
- package/commands/elsabro/new-milestone.md +19 -0
- package/commands/elsabro/new.md +19 -0
- package/commands/elsabro/pause-work.md +19 -0
- package/commands/elsabro/plan-milestone-gaps.md +20 -1
- package/commands/elsabro/plan.md +264 -36
- package/commands/elsabro/progress.md +203 -79
- package/commands/elsabro/quick.md +19 -0
- package/commands/elsabro/remove-phase.md +17 -0
- package/commands/elsabro/research-phase.md +18 -1
- package/commands/elsabro/resume-work.md +19 -0
- package/commands/elsabro/start.md +365 -98
- package/commands/elsabro/verify-work.md +109 -5
- package/package.json +1 -1
- package/references/SYSTEM_INDEX.md +241 -0
- package/references/command-flow.md +352 -0
- package/references/enforcement-rules.md +331 -0
- package/references/error-handling-instructions.md +26 -12
- package/references/state-sync.md +381 -0
- package/references/task-dispatcher.md +388 -0
- package/references/tasks-integration.md +380 -0
- package/skills/api-microservice.md +765 -0
- package/skills/api-setup.md +76 -3
- package/skills/auth-setup.md +46 -6
- package/skills/chrome-extension.md +584 -0
- package/skills/cicd-setup.md +1206 -0
- package/skills/cli-tool.md +884 -0
- package/skills/database-setup.md +41 -5
- package/skills/desktop-app.md +1351 -0
- package/skills/expo-app.md +35 -2
- package/skills/full-stack-app.md +543 -0
- package/skills/mobile-app.md +813 -0
- package/skills/nextjs-app.md +33 -2
- package/skills/payments-setup.md +76 -1
- package/skills/saas-starter.md +639 -0
- package/skills/sentry-setup.md +41 -7
- package/skills/testing-setup.md +1218 -0
package/skills/expo-app.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: expo-app
|
|
3
3
|
description: Skill para crear aplicaciones móviles con Expo/React Native. Usa este skill cuando el usuario quiere crear una app para celular (iOS/Android).
|
|
4
|
+
tags:
|
|
5
|
+
- mobile
|
|
6
|
+
- react-native
|
|
7
|
+
- expo
|
|
8
|
+
- ios
|
|
9
|
+
- android
|
|
10
|
+
- typescript
|
|
11
|
+
difficulty: intermediate
|
|
12
|
+
estimated_time: 30-45 min
|
|
4
13
|
---
|
|
5
14
|
|
|
6
15
|
# Skill: Crear App Expo (React Native)
|
|
@@ -16,6 +25,30 @@ Usar cuando el usuario menciona:
|
|
|
16
25
|
- "aplicación móvil"
|
|
17
26
|
</when_to_use>
|
|
18
27
|
|
|
28
|
+
<pre_requisites>
|
|
29
|
+
## Pre-requisitos
|
|
30
|
+
|
|
31
|
+
### Conocimientos
|
|
32
|
+
- JavaScript/TypeScript basico
|
|
33
|
+
- React fundamentals (components, hooks, state)
|
|
34
|
+
- Terminal/CLI basico
|
|
35
|
+
|
|
36
|
+
### Software
|
|
37
|
+
- Node.js 20+ LTS
|
|
38
|
+
- npm o yarn
|
|
39
|
+
- Editor de codigo (VS Code recomendado)
|
|
40
|
+
|
|
41
|
+
### Para desarrollo movil
|
|
42
|
+
- **iOS:** Mac con Xcode 15+ (opcional, puede usar Expo Go)
|
|
43
|
+
- **Android:** Android Studio con emulador (opcional, puede usar Expo Go)
|
|
44
|
+
- **Expo Go app** en tu celular (recomendado para empezar)
|
|
45
|
+
|
|
46
|
+
### Cuentas (opcionales para empezar)
|
|
47
|
+
- Cuenta de Expo (gratis) para EAS Build
|
|
48
|
+
- Apple Developer ($99/year) para publicar en App Store
|
|
49
|
+
- Google Play Developer ($25 una vez) para publicar en Play Store
|
|
50
|
+
</pre_requisites>
|
|
51
|
+
|
|
19
52
|
<before_starting>
|
|
20
53
|
## Investigación Obligatoria
|
|
21
54
|
|
|
@@ -42,8 +75,8 @@ Usar cuando el usuario menciona:
|
|
|
42
75
|
|
|
43
76
|
| Tecnología | Propósito | Verificar |
|
|
44
77
|
|------------|-----------|-----------|
|
|
45
|
-
| Expo SDK 52
|
|
46
|
-
| React Native 0.76
|
|
78
|
+
| Expo SDK 52.x | Framework React Native | Context7 |
|
|
79
|
+
| React Native 0.76.x | UI movil | Context7 |
|
|
47
80
|
| Expo Router | Navegación file-based | Context7 |
|
|
48
81
|
| TypeScript | Type safety | Context7 |
|
|
49
82
|
| NativeWind | Estilos (Tailwind) | Context7 |
|
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: full-stack-app
|
|
3
|
+
description: Crear aplicacion full-stack completa con Next.js 15, React 19, Tailwind CSS, Prisma y PostgreSQL. Incluye autenticacion con NextAuth v5 y deploy a Vercel.
|
|
4
|
+
tags: [nextjs, react, tailwind, prisma, postgresql, nextauth, vercel, fullstack]
|
|
5
|
+
difficulty: intermediate
|
|
6
|
+
estimated_time: 30min
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Skill: Full-Stack App
|
|
10
|
+
|
|
11
|
+
<when_to_use>
|
|
12
|
+
Usar cuando el usuario menciona:
|
|
13
|
+
- "crear app full-stack"
|
|
14
|
+
- "aplicacion web completa"
|
|
15
|
+
- "app con base de datos"
|
|
16
|
+
- "proyecto Next.js con auth"
|
|
17
|
+
- "webapp con backend"
|
|
18
|
+
</when_to_use>
|
|
19
|
+
|
|
20
|
+
<pre_requisites>
|
|
21
|
+
## Pre-requisitos
|
|
22
|
+
|
|
23
|
+
- Node.js 20+
|
|
24
|
+
- PostgreSQL (local o Neon/Supabase)
|
|
25
|
+
- Cuenta de Vercel (para deploy)
|
|
26
|
+
</pre_requisites>
|
|
27
|
+
|
|
28
|
+
<tech_stack>
|
|
29
|
+
## Stack Tecnologico
|
|
30
|
+
|
|
31
|
+
| Categoria | Tecnologia | Version |
|
|
32
|
+
|-----------|------------|---------|
|
|
33
|
+
| Framework | Next.js | 15.x |
|
|
34
|
+
| UI | React | 19.x |
|
|
35
|
+
| Styling | Tailwind CSS | 4.x |
|
|
36
|
+
| ORM | Prisma | 6.x |
|
|
37
|
+
| Database | PostgreSQL | 16.x |
|
|
38
|
+
| Auth | NextAuth.js | 5.x |
|
|
39
|
+
| Deploy | Vercel | - |
|
|
40
|
+
</tech_stack>
|
|
41
|
+
|
|
42
|
+
<project_structure>
|
|
43
|
+
## Estructura de Proyecto
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
my-app/
|
|
47
|
+
├── src/
|
|
48
|
+
│ ├── app/
|
|
49
|
+
│ │ ├── (auth)/
|
|
50
|
+
│ │ │ ├── login/page.tsx
|
|
51
|
+
│ │ │ └── register/page.tsx
|
|
52
|
+
│ │ ├── (dashboard)/
|
|
53
|
+
│ │ │ ├── layout.tsx
|
|
54
|
+
│ │ │ └── page.tsx
|
|
55
|
+
│ │ ├── api/
|
|
56
|
+
│ │ │ └── [...nextauth]/route.ts
|
|
57
|
+
│ │ ├── layout.tsx
|
|
58
|
+
│ │ ├── page.tsx
|
|
59
|
+
│ │ └── globals.css
|
|
60
|
+
│ ├── components/
|
|
61
|
+
│ │ ├── ui/
|
|
62
|
+
│ │ │ ├── button.tsx
|
|
63
|
+
│ │ │ ├── input.tsx
|
|
64
|
+
│ │ │ └── card.tsx
|
|
65
|
+
│ │ └── providers.tsx
|
|
66
|
+
│ ├── lib/
|
|
67
|
+
│ │ ├── auth.ts
|
|
68
|
+
│ │ ├── db.ts
|
|
69
|
+
│ │ └── utils.ts
|
|
70
|
+
│ └── actions/
|
|
71
|
+
│ └── user.ts
|
|
72
|
+
├── prisma/
|
|
73
|
+
│ └── schema.prisma
|
|
74
|
+
├── public/
|
|
75
|
+
├── .env.local
|
|
76
|
+
├── next.config.ts
|
|
77
|
+
├── tailwind.config.ts
|
|
78
|
+
├── tsconfig.json
|
|
79
|
+
└── package.json
|
|
80
|
+
```
|
|
81
|
+
</project_structure>
|
|
82
|
+
|
|
83
|
+
<setup_steps>
|
|
84
|
+
## Pasos de Setup
|
|
85
|
+
|
|
86
|
+
### Paso 1: Crear proyecto Next.js
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npx create-next-app@latest my-app --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"
|
|
90
|
+
cd my-app
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Paso 2: Instalar dependencias
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm install prisma @prisma/client next-auth@beta bcryptjs
|
|
97
|
+
npm install -D @types/bcryptjs
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Paso 3: Configurar Prisma
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npx prisma init
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Editar `prisma/schema.prisma`:
|
|
107
|
+
|
|
108
|
+
```prisma
|
|
109
|
+
generator client {
|
|
110
|
+
provider = "prisma-client-js"
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
datasource db {
|
|
114
|
+
provider = "postgresql"
|
|
115
|
+
url = env("DATABASE_URL")
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
model User {
|
|
119
|
+
id String @id @default(cuid())
|
|
120
|
+
name String?
|
|
121
|
+
email String @unique
|
|
122
|
+
emailVerified DateTime?
|
|
123
|
+
password String?
|
|
124
|
+
image String?
|
|
125
|
+
accounts Account[]
|
|
126
|
+
sessions Session[]
|
|
127
|
+
createdAt DateTime @default(now())
|
|
128
|
+
updatedAt DateTime @updatedAt
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
model Account {
|
|
132
|
+
id String @id @default(cuid())
|
|
133
|
+
userId String
|
|
134
|
+
type String
|
|
135
|
+
provider String
|
|
136
|
+
providerAccountId String
|
|
137
|
+
refresh_token String? @db.Text
|
|
138
|
+
access_token String? @db.Text
|
|
139
|
+
expires_at Int?
|
|
140
|
+
token_type String?
|
|
141
|
+
scope String?
|
|
142
|
+
id_token String? @db.Text
|
|
143
|
+
session_state String?
|
|
144
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
145
|
+
|
|
146
|
+
@@unique([provider, providerAccountId])
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
model Session {
|
|
150
|
+
id String @id @default(cuid())
|
|
151
|
+
sessionToken String @unique
|
|
152
|
+
userId String
|
|
153
|
+
expires DateTime
|
|
154
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Paso 4: Configurar variables de entorno
|
|
159
|
+
|
|
160
|
+
Crear `.env.local`:
|
|
161
|
+
|
|
162
|
+
```env
|
|
163
|
+
DATABASE_URL="postgresql://user:password@localhost:5432/myapp"
|
|
164
|
+
NEXTAUTH_SECRET="tu-secret-super-seguro-generado-con-openssl"
|
|
165
|
+
NEXTAUTH_URL="http://localhost:3000"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Paso 5: Crear cliente de base de datos
|
|
169
|
+
|
|
170
|
+
Crear `src/lib/db.ts`:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { PrismaClient } from "@prisma/client";
|
|
174
|
+
|
|
175
|
+
const globalForPrisma = globalThis as unknown as {
|
|
176
|
+
prisma: PrismaClient | undefined;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export const db = globalForPrisma.prisma ?? new PrismaClient();
|
|
180
|
+
|
|
181
|
+
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db;
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Paso 6: Configurar NextAuth
|
|
185
|
+
|
|
186
|
+
Crear `src/lib/auth.ts`:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import NextAuth from "next-auth";
|
|
190
|
+
import Credentials from "next-auth/providers/credentials";
|
|
191
|
+
import { PrismaAdapter } from "@auth/prisma-adapter";
|
|
192
|
+
import bcrypt from "bcryptjs";
|
|
193
|
+
import { db } from "./db";
|
|
194
|
+
|
|
195
|
+
export const { handlers, signIn, signOut, auth } = NextAuth({
|
|
196
|
+
adapter: PrismaAdapter(db),
|
|
197
|
+
session: { strategy: "jwt" },
|
|
198
|
+
pages: {
|
|
199
|
+
signIn: "/login",
|
|
200
|
+
},
|
|
201
|
+
providers: [
|
|
202
|
+
Credentials({
|
|
203
|
+
credentials: {
|
|
204
|
+
email: { label: "Email", type: "email" },
|
|
205
|
+
password: { label: "Password", type: "password" },
|
|
206
|
+
},
|
|
207
|
+
async authorize(credentials) {
|
|
208
|
+
if (!credentials?.email || !credentials?.password) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const user = await db.user.findUnique({
|
|
213
|
+
where: { email: credentials.email as string },
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
if (!user || !user.password) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const isValid = await bcrypt.compare(
|
|
221
|
+
credentials.password as string,
|
|
222
|
+
user.password
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
if (!isValid) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
id: user.id,
|
|
231
|
+
email: user.email,
|
|
232
|
+
name: user.name,
|
|
233
|
+
};
|
|
234
|
+
},
|
|
235
|
+
}),
|
|
236
|
+
],
|
|
237
|
+
callbacks: {
|
|
238
|
+
async jwt({ token, user }) {
|
|
239
|
+
if (user) {
|
|
240
|
+
token.id = user.id;
|
|
241
|
+
}
|
|
242
|
+
return token;
|
|
243
|
+
},
|
|
244
|
+
async session({ session, token }) {
|
|
245
|
+
if (session.user) {
|
|
246
|
+
session.user.id = token.id as string;
|
|
247
|
+
}
|
|
248
|
+
return session;
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Paso 7: Crear API route de NextAuth
|
|
255
|
+
|
|
256
|
+
Crear `src/app/api/auth/[...nextauth]/route.ts`:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { handlers } from "@/lib/auth";
|
|
260
|
+
|
|
261
|
+
export const { GET, POST } = handlers;
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Paso 8: Crear Server Actions
|
|
265
|
+
|
|
266
|
+
Crear `src/actions/user.ts`:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
"use server";
|
|
270
|
+
|
|
271
|
+
import { db } from "@/lib/db";
|
|
272
|
+
import { signIn } from "@/lib/auth";
|
|
273
|
+
import bcrypt from "bcryptjs";
|
|
274
|
+
import { redirect } from "next/navigation";
|
|
275
|
+
|
|
276
|
+
export async function register(formData: FormData) {
|
|
277
|
+
const email = formData.get("email") as string;
|
|
278
|
+
const password = formData.get("password") as string;
|
|
279
|
+
const name = formData.get("name") as string;
|
|
280
|
+
|
|
281
|
+
if (!email || !password) {
|
|
282
|
+
return { error: "Email y password son requeridos" };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const exists = await db.user.findUnique({ where: { email } });
|
|
286
|
+
if (exists) {
|
|
287
|
+
return { error: "El usuario ya existe" };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const hashedPassword = await bcrypt.hash(password, 10);
|
|
291
|
+
|
|
292
|
+
await db.user.create({
|
|
293
|
+
data: {
|
|
294
|
+
email,
|
|
295
|
+
password: hashedPassword,
|
|
296
|
+
name,
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
redirect("/login");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export async function login(formData: FormData) {
|
|
304
|
+
const email = formData.get("email") as string;
|
|
305
|
+
const password = formData.get("password") as string;
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
await signIn("credentials", {
|
|
309
|
+
email,
|
|
310
|
+
password,
|
|
311
|
+
redirectTo: "/dashboard",
|
|
312
|
+
});
|
|
313
|
+
} catch (error) {
|
|
314
|
+
return { error: "Credenciales invalidas" };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Paso 9: Crear componentes UI basicos
|
|
320
|
+
|
|
321
|
+
Crear `src/components/ui/button.tsx`:
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { ButtonHTMLAttributes, forwardRef } from "react";
|
|
325
|
+
import { cn } from "@/lib/utils";
|
|
326
|
+
|
|
327
|
+
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
328
|
+
variant?: "primary" | "secondary" | "outline";
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
332
|
+
({ className, variant = "primary", ...props }, ref) => {
|
|
333
|
+
return (
|
|
334
|
+
<button
|
|
335
|
+
ref={ref}
|
|
336
|
+
className={cn(
|
|
337
|
+
"px-4 py-2 rounded-lg font-medium transition-colors",
|
|
338
|
+
variant === "primary" && "bg-blue-600 text-white hover:bg-blue-700",
|
|
339
|
+
variant === "secondary" && "bg-gray-200 text-gray-900 hover:bg-gray-300",
|
|
340
|
+
variant === "outline" && "border border-gray-300 hover:bg-gray-50",
|
|
341
|
+
className
|
|
342
|
+
)}
|
|
343
|
+
{...props}
|
|
344
|
+
/>
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
Button.displayName = "Button";
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Crear `src/lib/utils.ts`:
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
import { clsx, type ClassValue } from "clsx";
|
|
355
|
+
import { twMerge } from "tailwind-merge";
|
|
356
|
+
|
|
357
|
+
export function cn(...inputs: ClassValue[]) {
|
|
358
|
+
return twMerge(clsx(inputs));
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Instalar clsx:
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
npm install clsx tailwind-merge
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Paso 10: Crear paginas
|
|
369
|
+
|
|
370
|
+
Crear `src/app/login/page.tsx`:
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
import { login } from "@/actions/user";
|
|
374
|
+
import { Button } from "@/components/ui/button";
|
|
375
|
+
|
|
376
|
+
export default function LoginPage() {
|
|
377
|
+
return (
|
|
378
|
+
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
|
379
|
+
<div className="w-full max-w-md p-8 bg-white rounded-xl shadow-lg">
|
|
380
|
+
<h1 className="text-2xl font-bold text-center mb-6">Iniciar Sesion</h1>
|
|
381
|
+
|
|
382
|
+
<form action={login} className="space-y-4">
|
|
383
|
+
<div>
|
|
384
|
+
<label htmlFor="email" className="block text-sm font-medium mb-1">
|
|
385
|
+
Email
|
|
386
|
+
</label>
|
|
387
|
+
<input
|
|
388
|
+
id="email"
|
|
389
|
+
name="email"
|
|
390
|
+
type="email"
|
|
391
|
+
required
|
|
392
|
+
className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
393
|
+
/>
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
<div>
|
|
397
|
+
<label htmlFor="password" className="block text-sm font-medium mb-1">
|
|
398
|
+
Password
|
|
399
|
+
</label>
|
|
400
|
+
<input
|
|
401
|
+
id="password"
|
|
402
|
+
name="password"
|
|
403
|
+
type="password"
|
|
404
|
+
required
|
|
405
|
+
className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
|
|
406
|
+
/>
|
|
407
|
+
</div>
|
|
408
|
+
|
|
409
|
+
<Button type="submit" className="w-full">
|
|
410
|
+
Entrar
|
|
411
|
+
</Button>
|
|
412
|
+
</form>
|
|
413
|
+
|
|
414
|
+
<p className="mt-4 text-center text-sm text-gray-600">
|
|
415
|
+
No tienes cuenta?{" "}
|
|
416
|
+
<a href="/register" className="text-blue-600 hover:underline">
|
|
417
|
+
Registrate
|
|
418
|
+
</a>
|
|
419
|
+
</p>
|
|
420
|
+
</div>
|
|
421
|
+
</div>
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Crear `src/app/(dashboard)/page.tsx`:
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
import { auth, signOut } from "@/lib/auth";
|
|
430
|
+
import { redirect } from "next/navigation";
|
|
431
|
+
import { Button } from "@/components/ui/button";
|
|
432
|
+
|
|
433
|
+
export default async function DashboardPage() {
|
|
434
|
+
const session = await auth();
|
|
435
|
+
|
|
436
|
+
if (!session) {
|
|
437
|
+
redirect("/login");
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return (
|
|
441
|
+
<div className="min-h-screen bg-gray-50">
|
|
442
|
+
<nav className="bg-white shadow">
|
|
443
|
+
<div className="max-w-7xl mx-auto px-4 py-4 flex justify-between items-center">
|
|
444
|
+
<h1 className="text-xl font-bold">Dashboard</h1>
|
|
445
|
+
<form
|
|
446
|
+
action={async () => {
|
|
447
|
+
"use server";
|
|
448
|
+
await signOut({ redirectTo: "/" });
|
|
449
|
+
}}
|
|
450
|
+
>
|
|
451
|
+
<Button variant="outline" type="submit">
|
|
452
|
+
Cerrar Sesion
|
|
453
|
+
</Button>
|
|
454
|
+
</form>
|
|
455
|
+
</div>
|
|
456
|
+
</nav>
|
|
457
|
+
|
|
458
|
+
<main className="max-w-7xl mx-auto px-4 py-8">
|
|
459
|
+
<div className="bg-white rounded-xl shadow p-6">
|
|
460
|
+
<h2 className="text-lg font-semibold mb-4">
|
|
461
|
+
Bienvenido, {session.user?.name || session.user?.email}
|
|
462
|
+
</h2>
|
|
463
|
+
<p className="text-gray-600">
|
|
464
|
+
Esta es tu area privada. Aqui puedes agregar tu contenido.
|
|
465
|
+
</p>
|
|
466
|
+
</div>
|
|
467
|
+
</main>
|
|
468
|
+
</div>
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### Paso 11: Ejecutar migraciones
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
npx prisma migrate dev --name init
|
|
477
|
+
npx prisma generate
|
|
478
|
+
```
|
|
479
|
+
</setup_steps>
|
|
480
|
+
|
|
481
|
+
<verification>
|
|
482
|
+
## Verificacion
|
|
483
|
+
|
|
484
|
+
### 1. Iniciar servidor de desarrollo
|
|
485
|
+
```bash
|
|
486
|
+
npm run dev
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### 2. Verificar paginas
|
|
490
|
+
- http://localhost:3000 - Home
|
|
491
|
+
- http://localhost:3000/login - Login
|
|
492
|
+
- http://localhost:3000/register - Registro
|
|
493
|
+
|
|
494
|
+
### 3. Probar flujo completo
|
|
495
|
+
1. Registrar nuevo usuario
|
|
496
|
+
2. Login con credenciales
|
|
497
|
+
3. Verificar acceso a dashboard
|
|
498
|
+
4. Logout
|
|
499
|
+
|
|
500
|
+
### 4. Verificar Prisma Studio
|
|
501
|
+
```bash
|
|
502
|
+
npx prisma studio
|
|
503
|
+
```
|
|
504
|
+
</verification>
|
|
505
|
+
|
|
506
|
+
<deploy>
|
|
507
|
+
## Deploy a Vercel
|
|
508
|
+
|
|
509
|
+
### 1. Push a GitHub
|
|
510
|
+
```bash
|
|
511
|
+
git init
|
|
512
|
+
git add .
|
|
513
|
+
git commit -m "Initial commit"
|
|
514
|
+
git remote add origin <tu-repo>
|
|
515
|
+
git push -u origin main
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### 2. Conectar a Vercel
|
|
519
|
+
```bash
|
|
520
|
+
npx vercel
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### 3. Configurar variables de entorno
|
|
524
|
+
En Vercel Dashboard:
|
|
525
|
+
- DATABASE_URL (usar Neon o Supabase para production)
|
|
526
|
+
- NEXTAUTH_SECRET
|
|
527
|
+
- NEXTAUTH_URL=https://tu-dominio.vercel.app
|
|
528
|
+
</deploy>
|
|
529
|
+
|
|
530
|
+
<common_issues>
|
|
531
|
+
## Problemas Comunes
|
|
532
|
+
|
|
533
|
+
### "PrismaClientInitializationError"
|
|
534
|
+
- Verificar DATABASE_URL en .env.local
|
|
535
|
+
- Ejecutar `npx prisma generate`
|
|
536
|
+
|
|
537
|
+
### "NEXTAUTH_SECRET is not set"
|
|
538
|
+
- Generar secret: `openssl rand -base64 32`
|
|
539
|
+
- Agregar a .env.local
|
|
540
|
+
|
|
541
|
+
### "Module not found: @prisma/client"
|
|
542
|
+
- Ejecutar `npx prisma generate`
|
|
543
|
+
</common_issues>
|