ar-saas 0.3.1 → 0.3.2

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.
Files changed (114) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +338 -314
  3. package/dist/cli.js +19 -0
  4. package/dist/generator.js +166 -55
  5. package/package.json +52 -50
  6. package/templates/backend/.env.example +67 -67
  7. package/templates/backend/.prettierrc +4 -4
  8. package/templates/backend/README.md +249 -168
  9. package/templates/backend/eslint.config.mjs +35 -35
  10. package/templates/backend/nest-cli.json +8 -8
  11. package/templates/backend/package-lock.json +10979 -10979
  12. package/templates/backend/package.json +88 -88
  13. package/templates/backend/src/app.controller.spec.ts +24 -24
  14. package/templates/backend/src/app.controller.ts +15 -15
  15. package/templates/backend/src/app.module.ts +40 -40
  16. package/templates/backend/src/app.service.ts +11 -11
  17. package/templates/backend/src/common/base/base.repository.ts +221 -221
  18. package/templates/backend/src/common/base/base.schema.ts +24 -24
  19. package/templates/backend/src/common/decorators/cookie.decorator.ts +9 -9
  20. package/templates/backend/src/common/decorators/current-user.decorator.ts +20 -20
  21. package/templates/backend/src/common/decorators/workspace-id.decorator.ts +14 -14
  22. package/templates/backend/src/common/filters/global-exception.filter.ts +61 -61
  23. package/templates/backend/src/common/guards/jwt-auth.guard.ts +5 -5
  24. package/templates/backend/src/common/interceptors/workspace-tenant.interceptor.ts +45 -45
  25. package/templates/backend/src/main.ts +51 -51
  26. package/templates/backend/src/modules/auth/auth.controller.ts +158 -158
  27. package/templates/backend/src/modules/auth/auth.module.ts +20 -20
  28. package/templates/backend/src/modules/auth/auth.service.ts +257 -257
  29. package/templates/backend/src/modules/auth/dto/forgot-password.dto.ts +9 -9
  30. package/templates/backend/src/modules/auth/dto/login.dto.ts +14 -14
  31. package/templates/backend/src/modules/auth/dto/refresh-token.dto.ts +12 -12
  32. package/templates/backend/src/modules/auth/dto/register.dto.ts +26 -26
  33. package/templates/backend/src/modules/auth/dto/reset-password.dto.ts +16 -16
  34. package/templates/backend/src/modules/auth/dto/verify-email.dto.ts +9 -9
  35. package/templates/backend/src/modules/auth/strategies/jwt.strategy.ts +43 -43
  36. package/templates/backend/src/modules/mail/mail.module.ts +9 -9
  37. package/templates/backend/src/modules/mail/mail.service.ts +141 -141
  38. package/templates/backend/src/modules/users/schemas/user.schema.ts +54 -54
  39. package/templates/backend/src/modules/users/users.module.ts +14 -14
  40. package/templates/backend/src/modules/users/users.repository.ts +51 -51
  41. package/templates/backend/src/modules/users/users.service.ts +104 -104
  42. package/templates/backend/src/modules/workspaces/schemas/workspace.schema.ts +26 -26
  43. package/templates/backend/src/modules/workspaces/workspaces.module.ts +16 -16
  44. package/templates/backend/src/modules/workspaces/workspaces.repository.ts +34 -34
  45. package/templates/backend/src/modules/workspaces/workspaces.service.ts +42 -42
  46. package/templates/backend/test/app.e2e-spec.ts +25 -25
  47. package/templates/backend/test/jest-e2e.json +9 -9
  48. package/templates/backend/tsconfig.build.json +4 -4
  49. package/templates/backend/tsconfig.json +26 -26
  50. package/templates/frontend/.env.local.example +1 -1
  51. package/templates/frontend/README.md +152 -0
  52. package/templates/frontend/components.json +20 -20
  53. package/templates/frontend/eslint.config.mjs +14 -14
  54. package/templates/frontend/next.config.ts +5 -5
  55. package/templates/frontend/package-lock.json +6722 -6722
  56. package/templates/frontend/package.json +48 -48
  57. package/templates/frontend/pnpm-lock.yaml +5012 -5012
  58. package/templates/frontend/pnpm-workspace.yaml +3 -3
  59. package/templates/frontend/postcss.config.mjs +7 -7
  60. package/templates/frontend/src/app/(auth)/forgot-password/page.tsx +84 -84
  61. package/templates/frontend/src/app/(auth)/layout.tsx +28 -28
  62. package/templates/frontend/src/app/(auth)/login/page.tsx +111 -111
  63. package/templates/frontend/src/app/(auth)/register/page.tsx +161 -161
  64. package/templates/frontend/src/app/(auth)/reset-password/page.tsx +120 -120
  65. package/templates/frontend/src/app/(auth)/verify-email/page.tsx +78 -78
  66. package/templates/frontend/src/app/(dashboard)/billing/page.tsx +111 -111
  67. package/templates/frontend/src/app/(dashboard)/dashboard/page.tsx +105 -105
  68. package/templates/frontend/src/app/(dashboard)/layout.tsx +38 -38
  69. package/templates/frontend/src/app/(dashboard)/profile/page.tsx +226 -226
  70. package/templates/frontend/src/app/(dashboard)/settings/page.tsx +156 -156
  71. package/templates/frontend/src/app/(dashboard)/team/page.tsx +178 -178
  72. package/templates/frontend/src/app/(legal)/privacy/page.tsx +127 -127
  73. package/templates/frontend/src/app/(legal)/terms/page.tsx +118 -118
  74. package/templates/frontend/src/app/globals.css +81 -81
  75. package/templates/frontend/src/app/layout.tsx +26 -26
  76. package/templates/frontend/src/app/page.tsx +5 -45
  77. package/templates/frontend/src/app/setup/page.tsx +371 -275
  78. package/templates/frontend/src/components/dashboard/header.tsx +89 -89
  79. package/templates/frontend/src/components/dashboard/sidebar.tsx +71 -71
  80. package/templates/frontend/src/components/dashboard/stat-card.tsx +34 -34
  81. package/templates/frontend/src/components/landing/faq.tsx +39 -39
  82. package/templates/frontend/src/components/landing/features.tsx +54 -54
  83. package/templates/frontend/src/components/landing/footer.tsx +76 -76
  84. package/templates/frontend/src/components/landing/hero.tsx +72 -72
  85. package/templates/frontend/src/components/landing/navbar.tsx +78 -78
  86. package/templates/frontend/src/components/landing/pricing.tsx +90 -90
  87. package/templates/frontend/src/components/ui/accordion.tsx +52 -52
  88. package/templates/frontend/src/components/ui/avatar.tsx +46 -46
  89. package/templates/frontend/src/components/ui/badge.tsx +30 -30
  90. package/templates/frontend/src/components/ui/button.tsx +52 -52
  91. package/templates/frontend/src/components/ui/card.tsx +50 -50
  92. package/templates/frontend/src/components/ui/checkbox.tsx +27 -27
  93. package/templates/frontend/src/components/ui/dialog.tsx +100 -100
  94. package/templates/frontend/src/components/ui/dropdown-menu.tsx +173 -173
  95. package/templates/frontend/src/components/ui/form.tsx +158 -158
  96. package/templates/frontend/src/components/ui/input.tsx +21 -21
  97. package/templates/frontend/src/components/ui/label.tsx +22 -22
  98. package/templates/frontend/src/components/ui/separator.tsx +25 -25
  99. package/templates/frontend/src/components/ui/skeleton.tsx +7 -7
  100. package/templates/frontend/src/components/ui/switch.tsx +28 -28
  101. package/templates/frontend/src/components/ui/tabs.tsx +54 -54
  102. package/templates/frontend/src/components/ui/textarea.tsx +20 -20
  103. package/templates/frontend/src/components/ui/toast.tsx +109 -109
  104. package/templates/frontend/src/components/ui/toaster.tsx +30 -30
  105. package/templates/frontend/src/config/site.ts +197 -197
  106. package/templates/frontend/src/hooks/use-toast.ts +116 -116
  107. package/templates/frontend/src/lib/api/auth.ts +39 -39
  108. package/templates/frontend/src/lib/api/client.ts +66 -66
  109. package/templates/frontend/src/lib/hooks/use-auth.ts +1 -1
  110. package/templates/frontend/src/lib/utils.ts +6 -6
  111. package/templates/frontend/src/providers/auth-provider.tsx +60 -60
  112. package/templates/frontend/src/types/api.ts +12 -12
  113. package/templates/frontend/src/types/auth.ts +27 -27
  114. package/templates/frontend/tsconfig.json +23 -23
@@ -1,178 +1,178 @@
1
- 'use client'
2
-
3
- import { useState } from 'react'
4
- import { useForm } from 'react-hook-form'
5
- import { UserPlus, MoreHorizontal, Crown, User } from 'lucide-react'
6
- import { useAuth } from '@/lib/hooks/use-auth'
7
- import { Button } from '@/components/ui/button'
8
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
9
- import { Avatar, AvatarFallback } from '@/components/ui/avatar'
10
- import { Badge } from '@/components/ui/badge'
11
- import { Input } from '@/components/ui/input'
12
- import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
13
- import {
14
- Dialog,
15
- DialogContent,
16
- DialogDescription,
17
- DialogFooter,
18
- DialogHeader,
19
- DialogTitle,
20
- DialogTrigger,
21
- } from '@/components/ui/dialog'
22
- import {
23
- DropdownMenu,
24
- DropdownMenuContent,
25
- DropdownMenuItem,
26
- DropdownMenuTrigger,
27
- } from '@/components/ui/dropdown-menu'
28
-
29
- function getInitials(name: string) {
30
- return name.split(' ').map((n) => n[0]).slice(0, 2).join('').toUpperCase()
31
- }
32
-
33
- interface InviteForm {
34
- email: string
35
- }
36
-
37
- export default function TeamPage() {
38
- const { user } = useAuth()
39
- const [inviteOpen, setInviteOpen] = useState(false)
40
- const [invited, setInvited] = useState(false)
41
-
42
- const members = [
43
- { name: user?.name ?? 'Vos', email: user?.email ?? '', role: 'owner' as const },
44
- ]
45
-
46
- const form = useForm<InviteForm>({ defaultValues: { email: '' } })
47
-
48
- async function onInvite(data: InviteForm) {
49
- // TODO: llamar a POST /workspaces/invite con data.email
50
- console.log('Invitar:', data.email)
51
- setInvited(true)
52
- setTimeout(() => {
53
- setInvited(false)
54
- setInviteOpen(false)
55
- form.reset()
56
- }, 1500)
57
- }
58
-
59
- return (
60
- <div className="max-w-2xl space-y-6">
61
- <Card>
62
- <CardHeader>
63
- <div className="flex items-start justify-between">
64
- <div>
65
- <CardTitle>Miembros del equipo</CardTitle>
66
- <CardDescription>
67
- Gestioná quién tiene acceso a tu workspace.
68
- </CardDescription>
69
- </div>
70
- <Dialog open={inviteOpen} onOpenChange={setInviteOpen}>
71
- <DialogTrigger asChild>
72
- <Button size="sm" className="gap-2">
73
- <UserPlus className="size-4" />
74
- Invitar
75
- </Button>
76
- </DialogTrigger>
77
- <DialogContent>
78
- <DialogHeader>
79
- <DialogTitle>Invitar miembro</DialogTitle>
80
- <DialogDescription>
81
- Ingresá el email de la persona que querés agregar al workspace.
82
- </DialogDescription>
83
- </DialogHeader>
84
- <Form {...form}>
85
- <form onSubmit={form.handleSubmit(onInvite)} className="space-y-4">
86
- <FormField
87
- control={form.control}
88
- name="email"
89
- rules={{
90
- required: 'El email es requerido',
91
- pattern: { value: /\S+@\S+\.\S+/, message: 'Email inválido' },
92
- }}
93
- render={({ field }) => (
94
- <FormItem>
95
- <FormLabel>Email</FormLabel>
96
- <FormControl>
97
- <Input type="email" placeholder="colaborador@empresa.com" {...field} />
98
- </FormControl>
99
- <FormMessage />
100
- </FormItem>
101
- )}
102
- />
103
- <DialogFooter>
104
- <Button type="button" variant="ghost" onClick={() => setInviteOpen(false)}>
105
- Cancelar
106
- </Button>
107
- <Button type="submit" disabled={form.formState.isSubmitting}>
108
- {invited ? 'Invitación enviada' : 'Enviar invitación'}
109
- </Button>
110
- </DialogFooter>
111
- </form>
112
- </Form>
113
- </DialogContent>
114
- </Dialog>
115
- </div>
116
- </CardHeader>
117
- <CardContent>
118
- <div className="space-y-3">
119
- {members.map((member) => (
120
- <div
121
- key={member.email}
122
- className="flex items-center justify-between rounded-lg border p-3"
123
- >
124
- <div className="flex items-center gap-3">
125
- <Avatar className="size-8">
126
- <AvatarFallback className="text-xs">{getInitials(member.name)}</AvatarFallback>
127
- </Avatar>
128
- <div>
129
- <p className="text-sm font-medium">{member.name}</p>
130
- <p className="text-xs text-muted-foreground">{member.email}</p>
131
- </div>
132
- </div>
133
- <div className="flex items-center gap-2">
134
- <Badge variant={member.role === 'owner' ? 'default' : 'secondary'} className="gap-1 text-xs">
135
- {member.role === 'owner' ? (
136
- <><Crown className="size-3" /> Owner</>
137
- ) : (
138
- <><User className="size-3" /> Miembro</>
139
- )}
140
- </Badge>
141
- {member.role !== 'owner' && (
142
- <DropdownMenu>
143
- <DropdownMenuTrigger asChild>
144
- <Button variant="ghost" size="sm" className="size-7 p-0">
145
- <MoreHorizontal className="size-4" />
146
- </Button>
147
- </DropdownMenuTrigger>
148
- <DropdownMenuContent align="end">
149
- <DropdownMenuItem className="text-destructive focus:text-destructive">
150
- Eliminar miembro
151
- </DropdownMenuItem>
152
- </DropdownMenuContent>
153
- </DropdownMenu>
154
- )}
155
- </div>
156
- </div>
157
- ))}
158
- </div>
159
-
160
- <p className="mt-4 text-center text-xs text-muted-foreground">
161
- Plan Free · 1 de 3 usuarios
162
- </p>
163
- </CardContent>
164
- </Card>
165
-
166
- {/* Pending invitations placeholder */}
167
- <Card>
168
- <CardHeader>
169
- <CardTitle>Invitaciones pendientes</CardTitle>
170
- <CardDescription>Invitaciones enviadas que aún no fueron aceptadas.</CardDescription>
171
- </CardHeader>
172
- <CardContent>
173
- <p className="text-sm text-muted-foreground">No hay invitaciones pendientes.</p>
174
- </CardContent>
175
- </Card>
176
- </div>
177
- )
178
- }
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { useForm } from 'react-hook-form'
5
+ import { UserPlus, MoreHorizontal, Crown, User } from 'lucide-react'
6
+ import { useAuth } from '@/lib/hooks/use-auth'
7
+ import { Button } from '@/components/ui/button'
8
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
9
+ import { Avatar, AvatarFallback } from '@/components/ui/avatar'
10
+ import { Badge } from '@/components/ui/badge'
11
+ import { Input } from '@/components/ui/input'
12
+ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
13
+ import {
14
+ Dialog,
15
+ DialogContent,
16
+ DialogDescription,
17
+ DialogFooter,
18
+ DialogHeader,
19
+ DialogTitle,
20
+ DialogTrigger,
21
+ } from '@/components/ui/dialog'
22
+ import {
23
+ DropdownMenu,
24
+ DropdownMenuContent,
25
+ DropdownMenuItem,
26
+ DropdownMenuTrigger,
27
+ } from '@/components/ui/dropdown-menu'
28
+
29
+ function getInitials(name: string) {
30
+ return name.split(' ').map((n) => n[0]).slice(0, 2).join('').toUpperCase()
31
+ }
32
+
33
+ interface InviteForm {
34
+ email: string
35
+ }
36
+
37
+ export default function TeamPage() {
38
+ const { user } = useAuth()
39
+ const [inviteOpen, setInviteOpen] = useState(false)
40
+ const [invited, setInvited] = useState(false)
41
+
42
+ const members = [
43
+ { name: user?.name ?? 'Vos', email: user?.email ?? '', role: 'owner' as const },
44
+ ]
45
+
46
+ const form = useForm<InviteForm>({ defaultValues: { email: '' } })
47
+
48
+ async function onInvite(data: InviteForm) {
49
+ // TODO: llamar a POST /workspaces/invite con data.email
50
+ console.log('Invitar:', data.email)
51
+ setInvited(true)
52
+ setTimeout(() => {
53
+ setInvited(false)
54
+ setInviteOpen(false)
55
+ form.reset()
56
+ }, 1500)
57
+ }
58
+
59
+ return (
60
+ <div className="max-w-2xl space-y-6">
61
+ <Card>
62
+ <CardHeader>
63
+ <div className="flex items-start justify-between">
64
+ <div>
65
+ <CardTitle>Miembros del equipo</CardTitle>
66
+ <CardDescription>
67
+ Gestioná quién tiene acceso a tu workspace.
68
+ </CardDescription>
69
+ </div>
70
+ <Dialog open={inviteOpen} onOpenChange={setInviteOpen}>
71
+ <DialogTrigger asChild>
72
+ <Button size="sm" className="gap-2">
73
+ <UserPlus className="size-4" />
74
+ Invitar
75
+ </Button>
76
+ </DialogTrigger>
77
+ <DialogContent>
78
+ <DialogHeader>
79
+ <DialogTitle>Invitar miembro</DialogTitle>
80
+ <DialogDescription>
81
+ Ingresá el email de la persona que querés agregar al workspace.
82
+ </DialogDescription>
83
+ </DialogHeader>
84
+ <Form {...form}>
85
+ <form onSubmit={form.handleSubmit(onInvite)} className="space-y-4">
86
+ <FormField
87
+ control={form.control}
88
+ name="email"
89
+ rules={{
90
+ required: 'El email es requerido',
91
+ pattern: { value: /\S+@\S+\.\S+/, message: 'Email inválido' },
92
+ }}
93
+ render={({ field }) => (
94
+ <FormItem>
95
+ <FormLabel>Email</FormLabel>
96
+ <FormControl>
97
+ <Input type="email" placeholder="colaborador@empresa.com" {...field} />
98
+ </FormControl>
99
+ <FormMessage />
100
+ </FormItem>
101
+ )}
102
+ />
103
+ <DialogFooter>
104
+ <Button type="button" variant="ghost" onClick={() => setInviteOpen(false)}>
105
+ Cancelar
106
+ </Button>
107
+ <Button type="submit" disabled={form.formState.isSubmitting}>
108
+ {invited ? 'Invitación enviada' : 'Enviar invitación'}
109
+ </Button>
110
+ </DialogFooter>
111
+ </form>
112
+ </Form>
113
+ </DialogContent>
114
+ </Dialog>
115
+ </div>
116
+ </CardHeader>
117
+ <CardContent>
118
+ <div className="space-y-3">
119
+ {members.map((member) => (
120
+ <div
121
+ key={member.email}
122
+ className="flex items-center justify-between rounded-lg border p-3"
123
+ >
124
+ <div className="flex items-center gap-3">
125
+ <Avatar className="size-8">
126
+ <AvatarFallback className="text-xs">{getInitials(member.name)}</AvatarFallback>
127
+ </Avatar>
128
+ <div>
129
+ <p className="text-sm font-medium">{member.name}</p>
130
+ <p className="text-xs text-muted-foreground">{member.email}</p>
131
+ </div>
132
+ </div>
133
+ <div className="flex items-center gap-2">
134
+ <Badge variant={member.role === 'owner' ? 'default' : 'secondary'} className="gap-1 text-xs">
135
+ {member.role === 'owner' ? (
136
+ <><Crown className="size-3" /> Owner</>
137
+ ) : (
138
+ <><User className="size-3" /> Miembro</>
139
+ )}
140
+ </Badge>
141
+ {member.role !== 'owner' && (
142
+ <DropdownMenu>
143
+ <DropdownMenuTrigger asChild>
144
+ <Button variant="ghost" size="sm" className="size-7 p-0">
145
+ <MoreHorizontal className="size-4" />
146
+ </Button>
147
+ </DropdownMenuTrigger>
148
+ <DropdownMenuContent align="end">
149
+ <DropdownMenuItem className="text-destructive focus:text-destructive">
150
+ Eliminar miembro
151
+ </DropdownMenuItem>
152
+ </DropdownMenuContent>
153
+ </DropdownMenu>
154
+ )}
155
+ </div>
156
+ </div>
157
+ ))}
158
+ </div>
159
+
160
+ <p className="mt-4 text-center text-xs text-muted-foreground">
161
+ Plan Free · 1 de 3 usuarios
162
+ </p>
163
+ </CardContent>
164
+ </Card>
165
+
166
+ {/* Pending invitations placeholder */}
167
+ <Card>
168
+ <CardHeader>
169
+ <CardTitle>Invitaciones pendientes</CardTitle>
170
+ <CardDescription>Invitaciones enviadas que aún no fueron aceptadas.</CardDescription>
171
+ </CardHeader>
172
+ <CardContent>
173
+ <p className="text-sm text-muted-foreground">No hay invitaciones pendientes.</p>
174
+ </CardContent>
175
+ </Card>
176
+ </div>
177
+ )
178
+ }
@@ -1,127 +1,127 @@
1
- import Link from 'next/link'
2
- import { ArrowLeft } from 'lucide-react'
3
- import { siteConfig } from '@/config/site'
4
-
5
- export default function PrivacyPage() {
6
- const { legal } = siteConfig
7
-
8
- return (
9
- <div className="min-h-screen bg-background">
10
- <div className="mx-auto max-w-3xl px-4 py-12">
11
- <Link
12
- href="/"
13
- className="mb-8 inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground"
14
- >
15
- <ArrowLeft className="size-4" />
16
- Volver al inicio
17
- </Link>
18
-
19
- <h1 className="text-3xl font-bold">Política de Privacidad</h1>
20
- <p className="mt-2 text-sm text-muted-foreground">
21
- Última actualización: {legal.lastUpdated}
22
- </p>
23
-
24
- <div className="mt-10 space-y-8 text-sm leading-relaxed">
25
- <section>
26
- <h2 className="text-lg font-semibold">1. Información que recopilamos</h2>
27
- <p className="mt-3 text-muted-foreground">
28
- Recopilamos información que nos proporcionás directamente (nombre, email, contraseña)
29
- al crear una cuenta, así como datos de uso generados al interactuar con la Plataforma
30
- (logs de acceso, funcionalidades utilizadas, configuraciones).
31
- </p>
32
- </section>
33
-
34
- <section>
35
- <h2 className="text-lg font-semibold">2. Cómo usamos tu información</h2>
36
- <ul className="mt-3 list-inside list-disc space-y-2 text-muted-foreground">
37
- <li>Proveer, mantener y mejorar el servicio.</li>
38
- <li>Enviarte comunicaciones transaccionales (confirmaciones, alertas de seguridad).</li>
39
- <li>Responder consultas y brindar soporte técnico.</li>
40
- <li>Cumplir obligaciones legales aplicables.</li>
41
- <li>Prevenir fraude y garantizar la seguridad de la plataforma.</li>
42
- </ul>
43
- </section>
44
-
45
- <section>
46
- <h2 className="text-lg font-semibold">3. Compartir información</h2>
47
- <p className="mt-3 text-muted-foreground">
48
- No vendemos ni compartimos tu información personal con terceros, salvo que sea
49
- necesario para operar el servicio (proveedores de infraestructura, procesadores de
50
- pago) o cuando lo exija la ley. Todos los subprocesadores están obligados
51
- contractualmente a proteger tus datos.
52
- </p>
53
- </section>
54
-
55
- <section>
56
- <h2 className="text-lg font-semibold">4. Seguridad de los datos</h2>
57
- <p className="mt-3 text-muted-foreground">
58
- Implementamos medidas técnicas y organizativas razonables para proteger tu información,
59
- incluyendo encriptación en tránsito (TLS) y en reposo. Sin embargo, ningún sistema
60
- es 100% seguro; te recomendamos usar contraseñas únicas y fuertes.
61
- </p>
62
- </section>
63
-
64
- <section>
65
- <h2 className="text-lg font-semibold">5. Retención de datos</h2>
66
- <p className="mt-3 text-muted-foreground">
67
- Conservamos tu información mientras tu cuenta esté activa o según sea necesario para
68
- los fines descritos. Si eliminás tu cuenta, procederemos a borrar tus datos dentro
69
- de los 30 días hábiles posteriores, salvo que la ley exija conservarlos por más tiempo.
70
- </p>
71
- </section>
72
-
73
- <section>
74
- <h2 className="text-lg font-semibold">6. Tus derechos</h2>
75
- <p className="mt-3 text-muted-foreground">
76
- Tenés derecho a acceder, rectificar, eliminar o portar tus datos personales. También
77
- podés oponerte o solicitar la limitación del tratamiento. Para ejercer estos derechos,
78
- contactanos en{' '}
79
- <a
80
- href={`mailto:${legal.email}`}
81
- className="underline underline-offset-4 hover:text-foreground"
82
- >
83
- {legal.email}
84
- </a>
85
- .
86
- </p>
87
- </section>
88
-
89
- <section>
90
- <h2 className="text-lg font-semibold">7. Cookies</h2>
91
- <p className="mt-3 text-muted-foreground">
92
- Utilizamos cookies estrictamente necesarias para el funcionamiento de la sesión.
93
- No utilizamos cookies de tracking de terceros ni publicidad comportamental.
94
- </p>
95
- </section>
96
-
97
- <section>
98
- <h2 className="text-lg font-semibold">8. Cambios en esta política</h2>
99
- <p className="mt-3 text-muted-foreground">
100
- Podemos actualizar esta Política periódicamente. Te notificaremos por email ante
101
- cambios significativos. La fecha de última actualización siempre estará visible
102
- al inicio de este documento.
103
- </p>
104
- </section>
105
-
106
- <section>
107
- <h2 className="text-lg font-semibold">9. Contacto</h2>
108
- <p className="mt-3 text-muted-foreground">
109
- Para cualquier consulta sobre privacidad escribinos a{' '}
110
- <a
111
- href={`mailto:${legal.email}`}
112
- className="underline underline-offset-4 hover:text-foreground"
113
- >
114
- {legal.email}
115
- </a>
116
- . También podés consultar nuestros{' '}
117
- <Link href="/terms" className="underline underline-offset-4 hover:text-foreground">
118
- Términos y Condiciones
119
- </Link>
120
- .
121
- </p>
122
- </section>
123
- </div>
124
- </div>
125
- </div>
126
- )
127
- }
1
+ import Link from 'next/link'
2
+ import { ArrowLeft } from 'lucide-react'
3
+ import { siteConfig } from '@/config/site'
4
+
5
+ export default function PrivacyPage() {
6
+ const { legal } = siteConfig
7
+
8
+ return (
9
+ <div className="min-h-screen bg-background">
10
+ <div className="mx-auto max-w-3xl px-4 py-12">
11
+ <Link
12
+ href="/"
13
+ className="mb-8 inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground"
14
+ >
15
+ <ArrowLeft className="size-4" />
16
+ Volver al inicio
17
+ </Link>
18
+
19
+ <h1 className="text-3xl font-bold">Política de Privacidad</h1>
20
+ <p className="mt-2 text-sm text-muted-foreground">
21
+ Última actualización: {legal.lastUpdated}
22
+ </p>
23
+
24
+ <div className="mt-10 space-y-8 text-sm leading-relaxed">
25
+ <section>
26
+ <h2 className="text-lg font-semibold">1. Información que recopilamos</h2>
27
+ <p className="mt-3 text-muted-foreground">
28
+ Recopilamos información que nos proporcionás directamente (nombre, email, contraseña)
29
+ al crear una cuenta, así como datos de uso generados al interactuar con la Plataforma
30
+ (logs de acceso, funcionalidades utilizadas, configuraciones).
31
+ </p>
32
+ </section>
33
+
34
+ <section>
35
+ <h2 className="text-lg font-semibold">2. Cómo usamos tu información</h2>
36
+ <ul className="mt-3 list-inside list-disc space-y-2 text-muted-foreground">
37
+ <li>Proveer, mantener y mejorar el servicio.</li>
38
+ <li>Enviarte comunicaciones transaccionales (confirmaciones, alertas de seguridad).</li>
39
+ <li>Responder consultas y brindar soporte técnico.</li>
40
+ <li>Cumplir obligaciones legales aplicables.</li>
41
+ <li>Prevenir fraude y garantizar la seguridad de la plataforma.</li>
42
+ </ul>
43
+ </section>
44
+
45
+ <section>
46
+ <h2 className="text-lg font-semibold">3. Compartir información</h2>
47
+ <p className="mt-3 text-muted-foreground">
48
+ No vendemos ni compartimos tu información personal con terceros, salvo que sea
49
+ necesario para operar el servicio (proveedores de infraestructura, procesadores de
50
+ pago) o cuando lo exija la ley. Todos los subprocesadores están obligados
51
+ contractualmente a proteger tus datos.
52
+ </p>
53
+ </section>
54
+
55
+ <section>
56
+ <h2 className="text-lg font-semibold">4. Seguridad de los datos</h2>
57
+ <p className="mt-3 text-muted-foreground">
58
+ Implementamos medidas técnicas y organizativas razonables para proteger tu información,
59
+ incluyendo encriptación en tránsito (TLS) y en reposo. Sin embargo, ningún sistema
60
+ es 100% seguro; te recomendamos usar contraseñas únicas y fuertes.
61
+ </p>
62
+ </section>
63
+
64
+ <section>
65
+ <h2 className="text-lg font-semibold">5. Retención de datos</h2>
66
+ <p className="mt-3 text-muted-foreground">
67
+ Conservamos tu información mientras tu cuenta esté activa o según sea necesario para
68
+ los fines descritos. Si eliminás tu cuenta, procederemos a borrar tus datos dentro
69
+ de los 30 días hábiles posteriores, salvo que la ley exija conservarlos por más tiempo.
70
+ </p>
71
+ </section>
72
+
73
+ <section>
74
+ <h2 className="text-lg font-semibold">6. Tus derechos</h2>
75
+ <p className="mt-3 text-muted-foreground">
76
+ Tenés derecho a acceder, rectificar, eliminar o portar tus datos personales. También
77
+ podés oponerte o solicitar la limitación del tratamiento. Para ejercer estos derechos,
78
+ contactanos en{' '}
79
+ <a
80
+ href={`mailto:${legal.email}`}
81
+ className="underline underline-offset-4 hover:text-foreground"
82
+ >
83
+ {legal.email}
84
+ </a>
85
+ .
86
+ </p>
87
+ </section>
88
+
89
+ <section>
90
+ <h2 className="text-lg font-semibold">7. Cookies</h2>
91
+ <p className="mt-3 text-muted-foreground">
92
+ Utilizamos cookies estrictamente necesarias para el funcionamiento de la sesión.
93
+ No utilizamos cookies de tracking de terceros ni publicidad comportamental.
94
+ </p>
95
+ </section>
96
+
97
+ <section>
98
+ <h2 className="text-lg font-semibold">8. Cambios en esta política</h2>
99
+ <p className="mt-3 text-muted-foreground">
100
+ Podemos actualizar esta Política periódicamente. Te notificaremos por email ante
101
+ cambios significativos. La fecha de última actualización siempre estará visible
102
+ al inicio de este documento.
103
+ </p>
104
+ </section>
105
+
106
+ <section>
107
+ <h2 className="text-lg font-semibold">9. Contacto</h2>
108
+ <p className="mt-3 text-muted-foreground">
109
+ Para cualquier consulta sobre privacidad escribinos a{' '}
110
+ <a
111
+ href={`mailto:${legal.email}`}
112
+ className="underline underline-offset-4 hover:text-foreground"
113
+ >
114
+ {legal.email}
115
+ </a>
116
+ . También podés consultar nuestros{' '}
117
+ <Link href="/terms" className="underline underline-offset-4 hover:text-foreground">
118
+ Términos y Condiciones
119
+ </Link>
120
+ .
121
+ </p>
122
+ </section>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ )
127
+ }