dimensionalpulsar commited on
Commit
d3ab8d1
·
verified ·
1 Parent(s): 8e723d7

Upload 34 files

Browse files
app/billing/page.tsx ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import React, { useState } from "react";
4
+ import { motion, AnimatePresence } from "framer-motion";
5
+ import {
6
+ FileText,
7
+ Download,
8
+ Eye,
9
+ Search,
10
+ Filter,
11
+ CreditCard,
12
+ CheckCircle2,
13
+ Clock,
14
+ AlertCircle,
15
+ TrendingUp,
16
+ Receipt,
17
+ User,
18
+ Calendar,
19
+ DollarSign
20
+ } from "lucide-react";
21
+
22
+ const mockInvoices = [
23
+ {
24
+ id: "INV-2023-001",
25
+ customer: "Nexus Corp",
26
+ date: "2023-11-15",
27
+ amount: 15420.50,
28
+ status: "paid",
29
+ items: [
30
+ { description: "Soporte Técnico Enterprise", qty: 1, price: 5000 },
31
+ { description: "Licencia Anual ERP Nexus", qty: 1, price: 8000 },
32
+ { description: "Implementación Cloud", qty: 1, price: 2420.50 }
33
+ ]
34
+ },
35
+ {
36
+ id: "INV-2023-002",
37
+ customer: "Global Tech Solutions",
38
+ date: "2023-11-18",
39
+ amount: 4200.00,
40
+ status: "pending",
41
+ items: [
42
+ { description: "Consultoría IT", qty: 10, price: 420 }
43
+ ]
44
+ },
45
+ {
46
+ id: "INV-2023-003",
47
+ customer: "Alina Systems",
48
+ date: "2023-11-20",
49
+ amount: 1250.75,
50
+ status: "overdue",
51
+ items: [
52
+ { description: "Mantenimiento Preventivo", qty: 5, price: 250.15 }
53
+ ]
54
+ },
55
+ ];
56
+
57
+ export default function BillingPage() {
58
+ const [selectedInvoice, setSelectedInvoice] = useState<any>(null);
59
+
60
+ return (
61
+ <div className="p-10 bg-[#0f172a] min-h-screen text-white">
62
+ <header className="mb-12 flex justify-between items-end">
63
+ <div>
64
+ <h1 className="text-5xl font-black tracking-tighter italic bg-gradient-to-r from-white to-gray-500 bg-clip-text text-transparent">
65
+ FACTURACIÓN <span className="text-blue-500">/ BILLING</span>
66
+ </h1>
67
+ <p className="text-gray-500 font-bold mt-2 uppercase text-xs tracking-[0.3em]">Gestión de Ingresos y Comprobantes Fiscales</p>
68
+ </div>
69
+ <div className="flex gap-4">
70
+ <button className="bg-white/5 border border-white/10 px-6 py-3 rounded-2xl text-xs font-black uppercase tracking-widest hover:bg-white/10 transition-all flex items-center gap-2">
71
+ <Download size={16} /> Exportar CSV
72
+ </button>
73
+ <button className="bg-blue-600 px-6 py-3 rounded-2xl text-xs font-black uppercase tracking-widest hover:bg-blue-500 transition-all shadow-xl shadow-blue-900/40">
74
+ Nueva Factura
75
+ </button>
76
+ </div>
77
+ </header>
78
+
79
+ {/* Stats Quick View */}
80
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12">
81
+ <StatWidget label="Recaudado Mes" value="$20,871.25" icon={<TrendingUp className="text-emerald-400" />} />
82
+ <StatWidget label="Pendiente Cobro" value="$4,200.00" icon={<Clock className="text-orange-400" />} />
83
+ <StatWidget label="Facturas Activas" value="28" icon={<Receipt className="text-blue-400" />} />
84
+ </div>
85
+
86
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
87
+ {/* Invoice List */}
88
+ <div className="lg:col-span-2 space-y-4">
89
+ <div className="bg-white/5 border border-white/10 rounded-[2.5rem] p-8">
90
+ <div className="flex justify-between items-center mb-8">
91
+ <h2 className="text-lg font-black italic uppercase tracking-widest text-gray-400">Historial de <span className="text-white">Facturas</span></h2>
92
+ <div className="flex gap-2">
93
+ <div className="relative">
94
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500" size={14} />
95
+ <input className="bg-white/5 border border-white/10 rounded-xl py-2 pl-9 pr-4 text-xs focus:ring-1 focus:ring-blue-500 outline-none w-48" placeholder="Buscar..." />
96
+ </div>
97
+ <button className="bg-white/5 border border-white/10 p-2 rounded-xl text-gray-400 hover:text-white"><Filter size={16} /></button>
98
+ </div>
99
+ </div>
100
+
101
+ <div className="space-y-3">
102
+ {mockInvoices.map((inv) => (
103
+ <motion.div
104
+ key={inv.id}
105
+ whileHover={{ x: 10, backgroundColor: "rgba(255,255,255,0.05)" }}
106
+ onClick={() => setSelectedInvoice(inv)}
107
+ className={`flex items-center justify-between p-4 rounded-2xl border border-white/5 cursor-pointer transition-all ${selectedInvoice?.id === inv.id ? 'bg-white/10 border-blue-500/50 shadow-lg' : 'hover:border-white/10'}`}
108
+ >
109
+ <div className="flex items-center gap-4">
110
+ <div className={`w-10 h-10 rounded-xl flex items-center justify-center ${
111
+ inv.status === 'paid' ? 'bg-emerald-500/10 text-emerald-400' :
112
+ inv.status === 'pending' ? 'bg-orange-500/10 text-orange-400' : 'bg-red-500/10 text-red-400'
113
+ }`}>
114
+ <FileText size={18} />
115
+ </div>
116
+ <div>
117
+ <p className="text-sm font-black italic">{inv.id}</p>
118
+ <p className="text-[10px] text-gray-500 font-bold uppercase">{inv.customer}</p>
119
+ </div>
120
+ </div>
121
+ <div className="text-right">
122
+ <p className="text-sm font-black tracking-tighter">${inv.amount.toLocaleString()}</p>
123
+ <p className="text-[9px] text-gray-500 font-medium">{inv.date}</p>
124
+ </div>
125
+ <div className="flex items-center gap-2">
126
+ <StatusBadge status={inv.status} />
127
+ <button className="p-2 text-gray-500 hover:text-white transition-colors">
128
+ <Eye size={16} />
129
+ </button>
130
+ </div>
131
+ </motion.div>
132
+ ))}
133
+ </div>
134
+ </div>
135
+ </div>
136
+
137
+ {/* Invoice Detail Breakdown */}
138
+ <div className="lg:col-span-1">
139
+ <AnimatePresence mode="wait">
140
+ {selectedInvoice ? (
141
+ <motion.div
142
+ key={selectedInvoice.id}
143
+ initial={{ opacity: 0, x: 20 }}
144
+ animate={{ opacity: 1, x: 0 }}
145
+ exit={{ opacity: 0, x: 20 }}
146
+ className="bg-white text-slate-900 rounded-[3rem] p-8 shadow-2xl relative overflow-hidden h-full flex flex-col"
147
+ >
148
+ {/* Decorative Elements */}
149
+ <div className="absolute top-0 right-0 w-32 h-32 bg-blue-50 rounded-bl-[100%] z-0"></div>
150
+
151
+ <div className="relative z-10 flex-1">
152
+ <div className="flex justify-between items-start mb-10">
153
+ <div>
154
+ <h3 className="text-2xl font-black tracking-tighter italic uppercase text-blue-600">INVOICE</h3>
155
+ <p className="text-[10px] font-black uppercase text-slate-400">{selectedInvoice.id}</p>
156
+ </div>
157
+ <div className="w-12 h-12 bg-blue-600 rounded-2xl flex items-center justify-center text-white font-black italic text-xl">N</div>
158
+ </div>
159
+
160
+ <div className="grid grid-cols-2 gap-6 mb-10">
161
+ <div className="space-y-1">
162
+ <p className="text-[9px] font-black uppercase text-slate-400 tracking-widest flex items-center gap-1"><User size={10} /> Cliente</p>
163
+ <p className="text-xs font-bold text-slate-700">{selectedInvoice.customer}</p>
164
+ </div>
165
+ <div className="space-y-1">
166
+ <p className="text-[9px] font-black uppercase text-slate-400 tracking-widest flex items-center gap-1"><Calendar size={10} /> Fecha</p>
167
+ <p className="text-xs font-bold text-slate-700">{selectedInvoice.date}</p>
168
+ </div>
169
+ </div>
170
+
171
+ {/* Items Table */}
172
+ <div className="border-t border-slate-100 pt-6 mb-10">
173
+ <p className="text-[9px] font-black uppercase text-slate-400 tracking-widest mb-4">Desglose de Servicios</p>
174
+ <div className="space-y-4">
175
+ {selectedInvoice.items.map((item: any, idx: number) => (
176
+ <div key={idx} className="flex justify-between items-center text-sm border-b border-slate-50 pb-2 last:border-0">
177
+ <div className="flex flex-col">
178
+ <span className="font-bold text-slate-700">{item.description}</span>
179
+ <span className="text-[10px] text-slate-400 font-medium">Cant: {item.qty} x ${item.price.toLocaleString()}</span>
180
+ </div>
181
+ <span className="font-black text-slate-800">${(item.qty * item.price).toLocaleString()}</span>
182
+ </div>
183
+ ))}
184
+ </div>
185
+ </div>
186
+
187
+ {/* Calculations */}
188
+ <div className="space-y-2 mt-auto">
189
+ <div className="flex justify-between text-xs font-medium text-slate-400">
190
+ <span>Subtotal</span>
191
+ <span>${(selectedInvoice.amount * 0.84).toLocaleString()}</span>
192
+ </div>
193
+ <div className="flex justify-between text-xs font-medium text-slate-400 border-b border-slate-100 pb-2">
194
+ <span>IVA (16%)</span>
195
+ <span>${(selectedInvoice.amount * 0.16).toLocaleString()}</span>
196
+ </div>
197
+ <div className="flex justify-between items-center pt-2">
198
+ <span className="text-sm font-black uppercase tracking-widest text-slate-800">Total Neto</span>
199
+ <span className="text-2xl font-black italic tracking-tighter text-blue-600">${selectedInvoice.amount.toLocaleString()}</span>
200
+ </div>
201
+ </div>
202
+ </div>
203
+
204
+ <button className="mt-10 w-full bg-slate-900 text-white rounded-2xl py-4 font-black uppercase tracking-widest text-xs flex items-center justify-center gap-2 hover:bg-slate-800 active:scale-95 transition-all">
205
+ <Download size={16} /> Descargar PDF
206
+ </button>
207
+ </motion.div>
208
+ ) : (
209
+ <div className="bg-white/5 border border-white/10 border-dashed rounded-[3rem] p-8 flex flex-col items-center justify-center text-center h-[500px]">
210
+ <div className="w-16 h-16 bg-white/5 rounded-full flex items-center justify-center mb-6">
211
+ <AlertCircle className="text-gray-500" size={32} />
212
+ </div>
213
+ <h3 className="font-black italic uppercase tracking-widest text-gray-400 mb-2">Seleccione una factura</h3>
214
+ <p className="text-xs text-gray-600 font-medium max-w-[200px]">Haga clic en una factura de la lista para ver el desglose detallado para el cliente.</p>
215
+ </div>
216
+ )}
217
+ </AnimatePresence>
218
+ </div>
219
+ </div>
220
+
221
+ {/* Payment Methods Footer */}
222
+ <div className="mt-12 bg-white/5 border border-white/10 rounded-[2.5rem] p-8 flex items-center justify-between">
223
+ <div className="flex items-center gap-6">
224
+ <div className="flex items-center gap-2">
225
+ <div className="w-2 h-2 rounded-full bg-emerald-500 drop-shadow-[0_0_8px_rgba(16,185,129,0.5)]"></div>
226
+ <span className="text-[10px] font-black uppercase tracking-widest text-gray-400">Gateways Activos</span>
227
+ </div>
228
+ <div className="flex gap-4">
229
+ <div className="h-4 w-12 bg-white/10 rounded"></div>
230
+ <div className="h-4 w-12 bg-white/10 rounded"></div>
231
+ <div className="h-4 w-12 bg-white/10 rounded"></div>
232
+ </div>
233
+ </div>
234
+ <div className="flex items-center gap-4 text-[10px] font-black uppercase tracking-widest text-gray-500">
235
+ <span className="flex items-center gap-1"><CreditCard size={12} /> Pagos Automáticos</span>
236
+ <span className="w-1 h-1 bg-white/10 rounded-full"></span>
237
+ <span className="flex items-center gap-1"><DollarSign size={12} /> Liquidación Instantánea</span>
238
+ </div>
239
+ </div>
240
+ </div>
241
+ );
242
+ }
243
+
244
+ function StatWidget({ label, value, icon }: any) {
245
+ return (
246
+ <div className="bg-white/5 border border-white/10 rounded-[2rem] p-6 relative group overflow-hidden">
247
+ <div className="absolute top-0 left-0 w-1 h-full bg-blue-500/20 group-hover:bg-blue-500 transition-colors"></div>
248
+ <div className="flex justify-between items-start">
249
+ <div className="space-y-1">
250
+ <p className="text-[10px] font-black uppercase tracking-widest text-gray-500">{label}</p>
251
+ <p className="text-3xl font-black italic tracking-tighter">{value}</p>
252
+ </div>
253
+ <div className="w-10 h-10 bg-white/5 rounded-xl flex items-center justify-center">
254
+ {icon}
255
+ </div>
256
+ </div>
257
+ </div>
258
+ );
259
+ }
260
+
261
+ function StatusBadge({ status }: { status: "paid" | "pending" | "overdue" | string }) {
262
+ const styles: Record<string, { bg: string; text: string; label: string; icon: React.ReactNode }> = {
263
+ paid: { bg: "bg-emerald-500/10", text: "text-emerald-400", label: "Pagado", icon: <CheckCircle2 size={10} /> },
264
+ pending: { bg: "bg-orange-500/10", text: "text-orange-400", label: "Pendiente", icon: <Clock size={10} /> },
265
+ overdue: { bg: "bg-red-500/10", text: "text-red-400", label: "Vencido", icon: <AlertCircle size={10} /> },
266
+ };
267
+
268
+ const style = styles[status] || { bg: "bg-gray-500/10", text: "text-gray-400", label: "Desconocido", icon: null };
269
+
270
+ return (
271
+ <div className={`${style.bg} ${style.text} px-3 py-1.5 rounded-full text-[9px] font-black uppercase flex items-center gap-1 tracking-wider border border-white/5`}>
272
+ {style.icon}
273
+ {style.label}
274
+ </div>
275
+ );
276
+ }
app/layout.tsx CHANGED
@@ -10,6 +10,7 @@ export const metadata: Metadata = {
10
  };
11
 
12
  import Sidebar from "@/components/Sidebar";
 
13
 
14
  export default function RootLayout({
15
  children,
@@ -19,10 +20,12 @@ export default function RootLayout({
19
  return (
20
  <html lang="es">
21
  <body className={`${inter.className} bg-[#0f172a] text-white selection:bg-blue-500/30`}>
22
- <Sidebar />
23
- <main className="ml-72 min-h-screen">
24
- {children}
25
- </main>
 
 
26
  </body>
27
  </html>
28
  );
 
10
  };
11
 
12
  import Sidebar from "@/components/Sidebar";
13
+ import AuthGuard from "@/components/AuthGuard";
14
 
15
  export default function RootLayout({
16
  children,
 
20
  return (
21
  <html lang="es">
22
  <body className={`${inter.className} bg-[#0f172a] text-white selection:bg-blue-500/30`}>
23
+ <AuthGuard>
24
+ <Sidebar />
25
+ <main className="ml-72 min-h-screen">
26
+ {children}
27
+ </main>
28
+ </AuthGuard>
29
  </body>
30
  </html>
31
  );
app/login/page.tsx CHANGED
@@ -1,24 +1,19 @@
1
  "use client";
2
 
3
- import React, { useState, useEffect } from "react";
 
4
  import { auth } from "@/lib/firebase";
5
- import { signInWithEmailAndPassword, onAuthStateChanged } from "firebase/auth";
6
  import { useRouter } from "next/navigation";
 
 
7
 
8
  export default function LoginPage() {
9
- const [email, setEmail] = useState("");
10
- const [password, setPassword] = useState("");
11
  const [error, setError] = useState("");
12
  const [loading, setLoading] = useState(false);
13
  const router = useRouter();
14
 
15
- useEffect(() => {
16
- const unsub = onAuthStateChanged(auth, (user) => {
17
- if (user) router.push("/");
18
- });
19
- return () => unsub();
20
- }, [router]);
21
-
22
  const handleLogin = async (e: React.FormEvent) => {
23
  e.preventDefault();
24
  setLoading(true);
@@ -27,81 +22,101 @@ export default function LoginPage() {
27
  await signInWithEmailAndPassword(auth, email, password);
28
  router.push("/");
29
  } catch (err: any) {
30
- setError(err.message || "Error al iniciar sesión");
31
- } finally {
32
  setLoading(false);
33
  }
34
  };
35
 
36
  return (
37
- <div className="min-h-screen bg-[#0f172a] flex items-center justify-center p-6 relative overflow-hidden">
38
- {/* Background Decor */}
39
- <div className="absolute top-0 left-0 w-full h-full">
40
- <div className="absolute top-[-10%] left-[-10%] w-[40%] h-[40%] bg-blue-600/20 blur-[120px] rounded-full"></div>
41
- <div className="absolute bottom-[-10%] right-[-10%] w-[40%] h-[40%] bg-purple-600/20 blur-[120px] rounded-full"></div>
42
- </div>
43
 
44
- <div className="w-full max-w-md relative z-10 transition-all duration-700 animate-in fade-in slide-in-from-bottom-8">
45
- <div className="bg-white/5 border border-white/10 p-10 rounded-[40px] backdrop-blur-3xl shadow-2xl">
46
- <div className="text-center mb-10">
47
- <h1 className="text-4xl font-black bg-gradient-to-r from-blue-400 to-indigo-500 bg-clip-text text-transparent mb-2">
48
- ERP System
49
- </h1>
50
- <p className="text-gray-400 text-sm font-medium">Panel de control empresarial premium</p>
 
 
 
51
  </div>
 
 
 
 
 
52
 
53
- <form onSubmit={handleLogin} className="space-y-6">
54
- <div>
55
- <input
56
- type="email"
57
- placeholder="admin@empresa-erp.com"
58
- required
 
59
  value={email}
60
  onChange={(e) => setEmail(e.target.value)}
61
- className="w-full bg-white/5 border border-white/10 rounded-2xl px-6 py-4 outline-none focus:border-blue-500/50 focus:bg-white/10 transition-all placeholder:text-gray-700"
 
 
62
  />
63
  </div>
64
- <div>
65
- <input
66
- type="password"
67
- placeholder="Contraseña (ej: 123456)"
68
- required
 
 
 
69
  value={password}
70
  onChange={(e) => setPassword(e.target.value)}
71
- className="w-full bg-white/5 border border-white/10 rounded-2xl px-6 py-4 outline-none focus:border-blue-500/50 focus:bg-white/10 transition-all placeholder:text-gray-700"
 
 
72
  />
73
  </div>
 
74
 
75
- {error && (
76
- <div className="bg-red-500/10 border border-red-500/20 text-red-400 text-xs p-4 rounded-xl">
77
- {error}
78
- </div>
79
- )}
 
 
 
 
80
 
81
- <div className="flex flex-col gap-3">
82
- <button
83
- type="submit"
84
- disabled={loading}
85
- className="w-full bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 text-white font-bold py-4 rounded-2xl shadow-lg shadow-blue-900/20 hover:shadow-blue-900/40 transition-all transform active:scale-95 disabled:opacity-50"
86
- >
87
- {loading ? "Autenticando..." : "Entrar"}
88
- </button>
89
-
90
- <button
91
- type="button"
92
- onClick={() => { setEmail("admin@empresa-erp.com"); setPassword("123456"); }}
93
- className="w-full bg-white/5 border border-white/5 text-gray-400 hover:text-white hover:bg-white/10 py-3 rounded-2xl text-[10px] font-black uppercase tracking-widest transition-all"
94
- >
95
- Autocompletar Demo Admin
96
- </button>
97
- </div>
98
- </form>
99
 
100
- <p className="mt-8 text-center text-xs text-gray-500 font-medium">
101
- Acceso restringido para administradores autorizados
102
- </p>
 
 
 
 
 
 
 
103
  </div>
104
- </div>
105
  </div>
106
  );
107
  }
 
1
  "use client";
2
 
3
+ import React, { useState } from "react";
4
+ import { signInWithEmailAndPassword } from "firebase/auth";
5
  import { auth } from "@/lib/firebase";
 
6
  import { useRouter } from "next/navigation";
7
+ import { motion } from "framer-motion";
8
+ import { Lock, User, ArrowRight, ShieldCheck } from "lucide-react";
9
 
10
  export default function LoginPage() {
11
+ const [email, setEmail] = useState("admin@erp.com");
12
+ const [password, setPassword] = useState("admin123");
13
  const [error, setError] = useState("");
14
  const [loading, setLoading] = useState(false);
15
  const router = useRouter();
16
 
 
 
 
 
 
 
 
17
  const handleLogin = async (e: React.FormEvent) => {
18
  e.preventDefault();
19
  setLoading(true);
 
22
  await signInWithEmailAndPassword(auth, email, password);
23
  router.push("/");
24
  } catch (err: any) {
25
+ setError("Credenciales inválidas. Intente de nuevo.");
 
26
  setLoading(false);
27
  }
28
  };
29
 
30
  return (
31
+ <div className="fixed inset-0 bg-[#020617] flex items-center justify-center p-6 z-[100]">
32
+ {/* Background Decorative Elements */}
33
+ <div className="absolute top-[-10%] left-[-10%] w-[40%] h-[40%] bg-blue-600/20 rounded-full blur-[120px] animate-pulse"></div>
34
+ <div className="absolute bottom-[-10%] right-[-10%] w-[40%] h-[40%] bg-indigo-600/20 rounded-full blur-[120px] animate-pulse delay-1000"></div>
 
 
35
 
36
+ <motion.div
37
+ initial={{ opacity: 0, y: 20 }}
38
+ animate={{ opacity: 1, y: 0 }}
39
+ className="w-full max-w-md bg-white/5 border border-white/10 rounded-[2.5rem] p-10 backdrop-blur-3xl shadow-2xl relative overflow-hidden"
40
+ >
41
+ <div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-blue-500 via-indigo-500 to-purple-500"></div>
42
+
43
+ <div className="flex flex-col items-center mb-10">
44
+ <div className="w-16 h-16 bg-gradient-to-br from-blue-600 to-indigo-600 rounded-[1.5rem] flex items-center justify-center shadow-2xl shadow-blue-500/20 mb-6">
45
+ <ShieldCheck size={32} className="text-white" />
46
  </div>
47
+ <h1 className="text-3xl font-black tracking-tighter italic uppercase text-white">
48
+ NEXUS <span className="text-blue-500 italic">ERP</span>
49
+ </h1>
50
+ <p className="text-gray-500 font-bold mt-2 uppercase text-[10px] tracking-[0.3em]">Acceso Seguro al Panel de Control</p>
51
+ </div>
52
 
53
+ <form onSubmit={handleLogin} className="space-y-6">
54
+ <div className="space-y-2">
55
+ <label className="text-[10px] font-black uppercase tracking-widest text-gray-400 ml-4">Usuario / Email</label>
56
+ <div className="relative group">
57
+ <User className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-500 group-focus-within:text-blue-400 transition-colors" size={18} />
58
+ <input
59
+ type="email"
60
  value={email}
61
  onChange={(e) => setEmail(e.target.value)}
62
+ className="w-full bg-white/5 border border-white/10 rounded-2xl py-4 pl-12 pr-4 text-sm font-medium focus:ring-2 focus:ring-blue-500/50 outline-none transition-all focus:bg-white/10"
63
+ placeholder="usuario@empresa.com"
64
+ required
65
  />
66
  </div>
67
+ </div>
68
+
69
+ <div className="space-y-2">
70
+ <label className="text-[10px] font-black uppercase tracking-widest text-gray-400 ml-4">Contraseña</label>
71
+ <div className="relative group">
72
+ <Lock className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-500 group-focus-within:text-blue-400 transition-colors" size={18} />
73
+ <input
74
+ type="password"
75
  value={password}
76
  onChange={(e) => setPassword(e.target.value)}
77
+ className="w-full bg-white/5 border border-white/10 rounded-2xl py-4 pl-12 pr-4 text-sm font-medium focus:ring-2 focus:ring-blue-500/50 outline-none transition-all focus:bg-white/10"
78
+ placeholder="••••••••"
79
+ required
80
  />
81
  </div>
82
+ </div>
83
 
84
+ {error && (
85
+ <motion.p
86
+ initial={{ opacity: 0, x: -10 }}
87
+ animate={{ opacity: 1, x: 0 }}
88
+ className="text-red-400 text-xs font-bold text-center bg-red-500/10 py-3 rounded-xl border border-red-500/20"
89
+ >
90
+ {error}
91
+ </motion.p>
92
+ )}
93
 
94
+ <button
95
+ type="submit"
96
+ disabled={loading}
97
+ className="w-full bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 text-white font-black py-4 rounded-2xl shadow-xl shadow-blue-900/40 flex items-center justify-center gap-2 group transition-all active:scale-[0.98] disabled:opacity-50"
98
+ >
99
+ {loading ? "Iniciando Sesión..." : (
100
+ <>
101
+ Entrar al Sistema
102
+ <ArrowRight size={18} className="group-hover:translate-x-1 transition-transform" />
103
+ </>
104
+ )}
105
+ </button>
106
+ </form>
 
 
 
 
 
107
 
108
+ <div className="mt-8 pt-8 border-t border-white/5">
109
+ <div className="bg-blue-500/5 border border-blue-500/10 rounded-2xl p-4 flex items-center gap-4">
110
+ <div className="w-8 h-8 rounded-full bg-blue-500/10 flex items-center justify-center">
111
+ <ShieldCheck size={14} className="text-blue-400" />
112
+ </div>
113
+ <div>
114
+ <p className="text-[9px] font-black uppercase text-blue-400 tracking-wider">Modo Demo Activado</p>
115
+ <p className="text-[10px] text-gray-500 font-medium">Haga clic en 'Entrar' para acceder rápidamente.</p>
116
+ </div>
117
+ </div>
118
  </div>
119
+ </motion.div>
120
  </div>
121
  );
122
  }
components/AuthGuard.tsx ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { auth } from "@/lib/firebase";
5
+ import { onAuthStateChanged } from "firebase/auth";
6
+ import { useRouter, usePathname } from "next/navigation";
7
+ import { motion, AnimatePresence } from "framer-motion";
8
+
9
+ export default function AuthGuard({ children }: { children: React.ReactNode }) {
10
+ const [loading, setLoading] = useState(true);
11
+ const [user, setUser] = useState<any>(null);
12
+ const router = useRouter();
13
+ const pathname = usePathname();
14
+
15
+ useEffect(() => {
16
+ const unsubscribe = onAuthStateChanged(auth, (user) => {
17
+ setUser(user);
18
+ setLoading(false);
19
+
20
+ if (!user && pathname !== "/login") {
21
+ router.push("/login");
22
+ }
23
+ });
24
+
25
+ return () => unsubscribe();
26
+ }, [pathname, router]);
27
+
28
+ if (loading) {
29
+ return (
30
+ <div className="fixed inset-0 bg-[#0f172a] flex items-center justify-center z-[200]">
31
+ <motion.div
32
+ animate={{ rotate: 360 }}
33
+ transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
34
+ className="w-12 h-12 border-4 border-blue-500 border-t-transparent rounded-full shadow-lg shadow-blue-500/20"
35
+ />
36
+ </div>
37
+ );
38
+ }
39
+
40
+ // If not logged in and not on login page, don't show children (redirecting)
41
+ if (!user && pathname !== "/login") {
42
+ return null;
43
+ }
44
+
45
+ return <>{children}</>;
46
+ }
components/Sidebar.tsx CHANGED
@@ -13,6 +13,7 @@ import {
13
  Network,
14
  MessageSquare,
15
  Map as MapIcon,
 
16
  LogOut
17
  } from "lucide-react";
18
  import { motion } from "framer-motion";
@@ -25,6 +26,7 @@ const menuItems = [
25
  { icon: Users, label: "Clientes", href: "/clients" },
26
  { icon: UserSquare2, label: "Personal / RRHH", href: "/hr" },
27
  { icon: CheckSquare, label: "Tareas", href: "/tasks" },
 
28
  { icon: Network, label: "Organigrama", href: "/org-chart" },
29
  { icon: MessageSquare, label: "Intranet", href: "/intranet" },
30
  { icon: MapIcon, label: "Mapa Mental", href: "/mind-map" },
 
13
  Network,
14
  MessageSquare,
15
  Map as MapIcon,
16
+ FileText,
17
  LogOut
18
  } from "lucide-react";
19
  import { motion } from "framer-motion";
 
26
  { icon: Users, label: "Clientes", href: "/clients" },
27
  { icon: UserSquare2, label: "Personal / RRHH", href: "/hr" },
28
  { icon: CheckSquare, label: "Tareas", href: "/tasks" },
29
+ { icon: FileText, label: "Facturación", href: "/billing" },
30
  { icon: Network, label: "Organigrama", href: "/org-chart" },
31
  { icon: MessageSquare, label: "Intranet", href: "/intranet" },
32
  { icon: MapIcon, label: "Mapa Mental", href: "/mind-map" },