dimensionalpulsar commited on
Commit
952569a
·
verified ·
1 Parent(s): e828b00

Upload 26 files

Browse files
app/clients/page.tsx ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import React, { useState, useEffect } from "react";
4
+ import { db } from "@/lib/firebase";
5
+ import { collection, onSnapshot, addDoc, deleteDoc, doc } from "firebase/firestore";
6
+ import Link from "next/link";
7
+
8
+ interface Client {
9
+ id: string;
10
+ name: string;
11
+ email: string;
12
+ phone: string;
13
+ }
14
+
15
+ export default function ClientsPage() {
16
+ const [clients, setClients] = useState<Client[]>([]);
17
+ const [isModalOpen, setIsModalOpen] = useState(false);
18
+ const [newClient, setNewClient] = useState({ name: "", email: "", phone: "" });
19
+
20
+ useEffect(() => {
21
+ const unsub = onSnapshot(collection(db, "clients"), (snap) => {
22
+ const data = snap.docs.map(doc => ({ id: doc.id, ...doc.data() } as Client));
23
+ setClients(data);
24
+ });
25
+ return () => unsub();
26
+ }, []);
27
+
28
+ const handleAdd = async (e: React.FormEvent) => {
29
+ e.preventDefault();
30
+ await addDoc(collection(db, "clients"), newClient);
31
+ setIsModalOpen(false);
32
+ setNewClient({ name: "", email: "", phone: "" });
33
+ };
34
+
35
+ return (
36
+ <div className="min-h-screen bg-[#0f172a] text-white p-6">
37
+ <header className="flex justify-between items-center mb-10">
38
+ <div>
39
+ <Link href="/" className="text-blue-400 hover:text-blue-300 transition-colors flex items-center gap-2 mb-2">
40
+ ← Dashboard
41
+ </Link>
42
+ <h1 className="text-4xl font-black">Directorio de Clientes</h1>
43
+ </div>
44
+ <button
45
+ onClick={() => setIsModalOpen(true)}
46
+ className="px-8 py-3 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-500 hover:to-pink-500 rounded-2xl font-bold transition-all shadow-xl shadow-purple-900/20"
47
+ >
48
+ ➕ Nuevo Cliente
49
+ </button>
50
+ </header>
51
+
52
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
53
+ {clients.map((c) => (
54
+ <div key={c.id} className="bg-white/5 border border-white/10 rounded-[32px] p-8 backdrop-blur-xl relative overflow-hidden group hover:border-white/20 transition-all">
55
+ <div className="absolute -right-4 -top-4 w-24 h-24 bg-purple-500/10 blur-3xl rounded-full"></div>
56
+ <div className="relative z-10">
57
+ <div className="w-14 h-14 bg-white/10 rounded-2xl flex items-center justify-center text-2xl mb-6">👤</div>
58
+ <h3 className="text-xl font-bold mb-2 group-hover:text-purple-400 transition-colors">{c.name}</h3>
59
+ <p className="text-gray-400 text-sm mb-4">{c.email}</p>
60
+ <div className="flex items-center gap-2 text-xs font-mono text-purple-300 bg-purple-500/10 w-fit px-3 py-1 rounded-full border border-purple-500/20">
61
+ 📞 {c.phone}
62
+ </div>
63
+ <div className="mt-8 pt-6 border-t border-white/5 flex gap-4">
64
+ <button className="text-xs font-bold text-gray-500 hover:text-white transition-colors uppercase tracking-widest">Editar</button>
65
+ <button onClick={() => deleteDoc(doc(db, "clients", c.id))} className="text-xs font-bold text-gray-700 hover:text-red-400 transition-colors uppercase tracking-widest ml-auto">Eliminar</button>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ ))}
70
+ </div>
71
+
72
+ {isModalOpen && (
73
+ <div className="fixed inset-0 bg-black/80 backdrop-blur-md flex items-center justify-center p-6 z-50">
74
+ <div className="bg-[#1e293b] border border-white/10 p-10 rounded-[48px] w-full max-w-md shadow-2xl animate-in fade-in slide-in-from-bottom-5">
75
+ <h2 className="text-3xl font-black mb-8 text-center uppercase tracking-tighter">Registrar Cliente</h2>
76
+ <form onSubmit={handleAdd} className="space-y-4">
77
+ <input
78
+ type="text" placeholder="Nombre completo" required
79
+ value={newClient.name} onChange={e => setNewClient({...newClient, name: e.target.value})}
80
+ className="w-full bg-white/5 border border-white/10 rounded-2xl px-6 py-4 outline-none focus:border-purple-500/50"
81
+ />
82
+ <input
83
+ type="email" placeholder="Email" required
84
+ value={newClient.email} onChange={e => setNewClient({...newClient, email: e.target.value})}
85
+ className="w-full bg-white/5 border border-white/10 rounded-2xl px-6 py-4 outline-none focus:border-purple-500/50"
86
+ />
87
+ <input
88
+ type="tel" placeholder="Teléfono" required
89
+ value={newClient.phone} onChange={e => setNewClient({...newClient, phone: e.target.value})}
90
+ className="w-full bg-white/5 border border-white/10 rounded-2xl px-6 py-4 outline-none focus:border-purple-500/50"
91
+ />
92
+ <div className="flex flex-col gap-3 mt-10">
93
+ <button type="submit" className="w-full bg-white text-[#0f172a] py-5 rounded-2xl font-black transition-all hover:bg-gray-200">CREAR CLIENTE</button>
94
+ <button type="button" onClick={() => setIsModalOpen(false)} className="w-full py-4 text-gray-500 font-bold hover:text-white transition-colors">CANCELAR</button>
95
+ </div>
96
+ </form>
97
+ </div>
98
+ </div>
99
+ )}
100
+ </div>
101
+ );
102
+ }
app/hr/page.tsx ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import React, { useState, useEffect } from "react";
4
+ import { db } from "@/lib/firebase";
5
+ import { collection, onSnapshot, addDoc, deleteDoc, doc } from "firebase/firestore";
6
+ import Link from "next/link";
7
+
8
+ interface Employee {
9
+ id: string;
10
+ name: string;
11
+ position: string;
12
+ salary: number;
13
+ }
14
+
15
+ export default function HRPage() {
16
+ const [employees, setEmployees] = useState<Employee[]>([]);
17
+ const [isModalOpen, setIsModalOpen] = useState(false);
18
+ const [newEmp, setNewEmp] = useState({ name: "", position: "", salary: 0 });
19
+
20
+ useEffect(() => {
21
+ const unsub = onSnapshot(collection(db, "employees"), (snap) => {
22
+ const data = snap.docs.map(doc => ({ id: doc.id, ...doc.data() } as Employee));
23
+ setEmployees(data);
24
+ });
25
+ return () => unsub();
26
+ }, []);
27
+
28
+ const handleAdd = async (e: React.FormEvent) => {
29
+ e.preventDefault();
30
+ await addDoc(collection(db, "employees"), newEmp);
31
+ setIsModalOpen(false);
32
+ setNewEmp({ name: "", position: "", salary: 0 });
33
+ };
34
+
35
+ return (
36
+ <div className="min-h-screen bg-[#0f172a] text-white p-6">
37
+ <header className="flex justify-between items-center mb-10">
38
+ <div>
39
+ <Link href="/" className="text-cyan-400 hover:text-cyan-300 transition-colors flex items-center gap-2 mb-2 font-medium">
40
+ ← Panel Principal
41
+ </Link>
42
+ <h1 className="text-4xl font-extrabold tracking-tight">Gestión Humana</h1>
43
+ </div>
44
+ <button
45
+ onClick={() => setIsModalOpen(true)}
46
+ className="px-8 py-3 bg-white/10 hover:bg-white/20 border border-white/10 rounded-full font-bold transition-all backdrop-blur-md"
47
+ >
48
+ ➕ Registrar Personal
49
+ </button>
50
+ </header>
51
+
52
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
53
+ {employees.map((e) => (
54
+ <div key={e.id} className="bg-gradient-to-br from-[#1e293b] to-[#0f172a] border border-white/5 rounded-[2rem] p-8 hover:border-cyan-500/30 transition-all group shadow-2xl">
55
+ <div className="flex items-center gap-4 mb-6">
56
+ <div className="w-12 h-12 bg-cyan-500/20 text-cyan-400 rounded-2xl flex items-center justify-center text-xl font-bold">
57
+ {e.name.charAt(0)}
58
+ </div>
59
+ <div>
60
+ <h3 className="font-bold group-hover:text-cyan-400 transition-colors">{e.name}</h3>
61
+ <span className="text-[10px] text-gray-500 font-black uppercase tracking-widest">{e.position}</span>
62
+ </div>
63
+ </div>
64
+ <div className="flex justify-between items-center py-4 border-y border-white/5 mb-6">
65
+ <span className="text-xs text-gray-500 uppercase font-bold">Salario</span>
66
+ <span className="font-mono text-cyan-400 font-bold">${Number(e.salary).toLocaleString()}</span>
67
+ </div>
68
+ <button
69
+ onClick={() => deleteDoc(doc(db, "employees", e.id))}
70
+ className="w-full py-3 bg-red-500/5 hover:bg-red-500/20 text-red-500/60 hover:text-red-500 text-[10px] font-black uppercase tracking-widest rounded-xl transition-all border border-red-500/10"
71
+ >
72
+ Dar de Baja
73
+ </button>
74
+ </div>
75
+ ))}
76
+ </div>
77
+
78
+ {isModalOpen && (
79
+ <div className="fixed inset-0 bg-[#0f172a]/95 backdrop-blur-xl flex items-center justify-center p-6 z-50">
80
+ <div className="w-full max-w-sm">
81
+ <h2 className="text-4xl font-black mb-8 text-white italic tracking-tighter">NEW STAFF /</h2>
82
+ <form onSubmit={handleAdd} className="space-y-6">
83
+ <div className="space-y-1">
84
+ <label className="text-[10px] font-black text-cyan-500 uppercase ml-2">Nombre completo</label>
85
+ <input required value={newEmp.name} onChange={v => setNewEmp({...newEmp, name: v.target.value})} className="w-full bg-white/5 border-b border-white/20 px-4 py-4 outline-none focus:border-cyan-500 transition-all font-medium text-lg"/>
86
+ </div>
87
+ <div className="space-y-1">
88
+ <label className="text-[10px] font-black text-cyan-500 uppercase ml-2">Cargo / Posición</label>
89
+ <input required value={newEmp.position} onChange={v => setNewEmp({...newEmp, position: v.target.value})} className="w-full bg-white/5 border-b border-white/20 px-4 py-4 outline-none focus:border-cyan-500 transition-all font-medium text-lg"/>
90
+ </div>
91
+ <div className="space-y-1">
92
+ <label className="text-[10px] font-black text-cyan-500 uppercase ml-2">Salario Mensual</label>
93
+ <input type="number" required value={newEmp.salary} onChange={v => setNewEmp({...newEmp, salary: Number(v.target.value)})} className="w-full bg-white/5 border-b border-white/20 px-4 py-4 outline-none focus:border-cyan-500 transition-all font-medium text-lg"/>
94
+ </div>
95
+ <div className="flex gap-4 pt-10">
96
+ <button type="button" onClick={() => setIsModalOpen(false)} className="px-6 py-4 text-gray-500 font-black text-xs uppercase hover:text-white transition-colors">Cancelar</button>
97
+ <button type="submit" className="flex-1 bg-cyan-600 hover:bg-cyan-500 py-4 rounded-full font-black text-xs uppercase tracking-widest transition-all">Contratar</button>
98
+ </div>
99
+ </form>
100
+ </div>
101
+ </div>
102
+ )}
103
+ </div>
104
+ );
105
+ }
app/inventory/page.tsx ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import React, { useState, useEffect } from "react";
4
+ import { db } from "@/lib/firebase";
5
+ import { collection, onSnapshot, addDoc, deleteDoc, doc, updateDoc } from "firebase/firestore";
6
+ import Link from "next/link";
7
+
8
+ interface Product {
9
+ id: string;
10
+ name: string;
11
+ category: string;
12
+ price: number;
13
+ stock: number;
14
+ }
15
+
16
+ export default function InventoryPage() {
17
+ const [products, setProducts] = useState<Product[]>([]);
18
+ const [isModalOpen, setIsModalOpen] = useState(false);
19
+ const [newProduct, setNewProduct] = useState({ name: "", category: "", price: 0, stock: 0 });
20
+
21
+ useEffect(() => {
22
+ const unsub = onSnapshot(collection(db, "products"), (snap) => {
23
+ const data = snap.docs.map(doc => ({ id: doc.id, ...doc.data() } as Product));
24
+ setProducts(data);
25
+ });
26
+ return () => unsub();
27
+ }, []);
28
+
29
+ const handleAdd = async (e: React.FormEvent) => {
30
+ e.preventDefault();
31
+ await addDoc(collection(db, "products"), newProduct);
32
+ setIsModalOpen(false);
33
+ setNewProduct({ name: "", category: "", price: 0, stock: 0 });
34
+ };
35
+
36
+ const handleDelete = async (id: string) => {
37
+ if(confirm("¿Seguro que quieres eliminar este producto?")) {
38
+ await deleteDoc(doc(db, "products", id));
39
+ }
40
+ };
41
+
42
+ return (
43
+ <div className="min-h-screen bg-[#0f172a] text-white p-6">
44
+ <header className="flex justify-between items-center mb-8">
45
+ <div>
46
+ <Link href="/" className="text-blue-400 hover:text-blue-300 transition-colors flex items-center gap-2 mb-2">
47
+ ← Volver al Dashboard
48
+ </Link>
49
+ <h1 className="text-3xl font-bold">Gestión de Inventario</h1>
50
+ </div>
51
+ <button
52
+ onClick={() => setIsModalOpen(true)}
53
+ className="px-6 py-3 bg-blue-600 hover:bg-blue-500 rounded-2xl font-bold transition-all shadow-lg shadow-blue-900/20"
54
+ >
55
+ ➕ Añadir Producto
56
+ </button>
57
+ </header>
58
+
59
+ {/* Inventory Table */}
60
+ <div className="bg-white/5 border border-white/10 rounded-3xl overflow-hidden backdrop-blur-xl">
61
+ <table className="w-full text-left">
62
+ <thead className="bg-white/5 border-b border-white/10">
63
+ <tr>
64
+ <th className="px-6 py-4 font-semibold text-gray-400 uppercase text-xs tracking-wider">Nombre</th>
65
+ <th className="px-6 py-4 font-semibold text-gray-400 uppercase text-xs tracking-wider">Categoría</th>
66
+ <th className="px-6 py-4 font-semibold text-gray-400 uppercase text-xs tracking-wider">Precio</th>
67
+ <th className="px-6 py-4 font-semibold text-gray-400 uppercase text-xs tracking-wider">Stock</th>
68
+ <th className="px-6 py-4 font-semibold text-gray-400 uppercase text-xs tracking-wider">Acciones</th>
69
+ </tr>
70
+ </thead>
71
+ <tbody className="divide-y divide-white/5">
72
+ {products.map((p) => (
73
+ <tr key={p.id} className="hover:bg-white/5 transition-colors group">
74
+ <td className="px-6 py-4 font-medium text-gray-200">{p.name}</td>
75
+ <td className="px-6 py-4 text-gray-400">
76
+ <span className="px-3 py-1 bg-white/5 rounded-full text-xs border border-white/10">{p.category}</span>
77
+ </td>
78
+ <td className="px-6 py-4 font-bold text-blue-400">${Number(p.price).toLocaleString()}</td>
79
+ <td className="px-6 py-4">
80
+ <div className="flex items-center gap-2">
81
+ <span className={`w-2 h-2 rounded-full ${p.stock > 10 ? 'bg-green-400' : 'bg-red-400'}`}></span>
82
+ {p.stock} uds.
83
+ </div>
84
+ </td>
85
+ <td className="px-6 py-4">
86
+ <button onClick={() => handleDelete(p.id)} className="text-gray-600 hover:text-red-400 transition-colors">🗑️</button>
87
+ </td>
88
+ </tr>
89
+ ))}
90
+ </tbody>
91
+ </table>
92
+ </div>
93
+
94
+ {/* Modal Mockup */}
95
+ {isModalOpen && (
96
+ <div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center p-6 z-50">
97
+ <div className="bg-[#1e293b] border border-white/10 p-8 rounded-[40px] w-full max-w-lg shadow-2xl animate-in fade-in zoom-in-95">
98
+ <h2 className="text-2xl font-bold mb-6">Nuevo Producto</h2>
99
+ <form onSubmit={handleAdd} className="space-y-4">
100
+ <input
101
+ type="text" placeholder="Nombre" required
102
+ value={newProduct.name} onChange={e => setNewProduct({...newProduct, name: e.target.value})}
103
+ className="w-full bg-white/5 border border-white/10 rounded-2xl px-6 py-4 outline-none focus:border-blue-500/50"
104
+ />
105
+ <input
106
+ type="text" placeholder="Categoría" required
107
+ value={newProduct.category} onChange={e => setNewProduct({...newProduct, category: e.target.value})}
108
+ className="w-full bg-white/5 border border-white/10 rounded-2xl px-6 py-4 outline-none focus:border-blue-500/50"
109
+ />
110
+ <div className="grid grid-cols-2 gap-4">
111
+ <input
112
+ type="number" placeholder="Precio" required
113
+ value={newProduct.price} onChange={e => setNewProduct({...newProduct, price: Number(e.target.value)})}
114
+ className="w-full bg-white/5 border border-white/10 rounded-2xl px-6 py-4 outline-none focus:border-blue-500/50"
115
+ />
116
+ <input
117
+ type="number" placeholder="Stock" required
118
+ value={newProduct.stock} onChange={e => setNewProduct({...newProduct, stock: Number(e.target.value)})}
119
+ className="w-full bg-white/5 border border-white/10 rounded-2xl px-6 py-4 outline-none focus:border-blue-500/50"
120
+ />
121
+ </div>
122
+ <div className="flex gap-4 mt-8">
123
+ <button type="button" onClick={() => setIsModalOpen(false)} className="flex-1 py-4 text-gray-400 hover:text-white transition-colors">Cancelar</button>
124
+ <button type="submit" className="flex-1 bg-blue-600 hover:bg-blue-500 py-4 rounded-2xl font-bold transition-all shadow-lg shadow-blue-900/20">Guardar</button>
125
+ </div>
126
+ </form>
127
+ </div>
128
+ </div>
129
+ )}
130
+ </div>
131
+ );
132
+ }
app/layout.tsx CHANGED
@@ -1,20 +1,12 @@
1
  import type { Metadata } from "next";
2
- import { Geist, Geist_Mono } from "next/font/google";
3
  import "./globals.css";
4
 
5
- const geistSans = Geist({
6
- variable: "--font-geist-sans",
7
- subsets: ["latin"],
8
- });
9
-
10
- const geistMono = Geist_Mono({
11
- variable: "--font-geist-mono",
12
- subsets: ["latin"],
13
- });
14
 
15
  export const metadata: Metadata = {
16
- title: "Create Next App",
17
- description: "Generated by create next app",
18
  };
19
 
20
  export default function RootLayout({
@@ -23,11 +15,8 @@ export default function RootLayout({
23
  children: React.ReactNode;
24
  }>) {
25
  return (
26
- <html
27
- lang="en"
28
- className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
29
- >
30
- <body className="min-h-full flex flex-col">{children}</body>
31
  </html>
32
  );
33
  }
 
1
  import type { Metadata } from "next";
2
+ import { Inter } from "next/font/google";
3
  import "./globals.css";
4
 
5
+ const inter = Inter({ subsets: ["latin"] });
 
 
 
 
 
 
 
 
6
 
7
  export const metadata: Metadata = {
8
+ title: "ERP Premium | Next Generation Control Panel",
9
+ description: "Modern Enterprise Resource Planning system powered by Firebase",
10
  };
11
 
12
  export default function RootLayout({
 
15
  children: React.ReactNode;
16
  }>) {
17
  return (
18
+ <html lang="es">
19
+ <body className={`${inter.className} bg-[#0f172a]`}>{children}</body>
 
 
 
20
  </html>
21
  );
22
  }
app/login/page.tsx ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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);
25
+ setError("");
26
+ try {
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="Email corporativo"
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-600"
62
+ />
63
+ </div>
64
+ <div>
65
+ <input
66
+ type="password"
67
+ placeholder="Contraseña"
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-600"
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 animate-shake">
77
+ {error}
78
+ </div>
79
+ )}
80
+
81
+ <button
82
+ type="submit"
83
+ disabled={loading}
84
+ 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"
85
+ >
86
+ {loading ? (
87
+ <span className="flex items-center justify-center gap-2">
88
+ <svg className="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
89
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
90
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
91
+ </svg>
92
+ Autenticando...
93
+ </span>
94
+ ) : (
95
+ "Entrar"
96
+ )}
97
+ </button>
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
+ }
app/page.tsx CHANGED
@@ -1,65 +1,130 @@
1
- import Image from "next/image";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- export default function Home() {
4
  return (
5
- <div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
6
- <main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
7
- <Image
8
- className="dark:invert"
9
- src="/next.svg"
10
- alt="Next.js logo"
11
- width={100}
12
- height={20}
13
- priority
14
- />
15
- <div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
16
- <h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
17
- To get started, edit the page.tsx file.
18
  </h1>
19
- <p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
20
- Looking for a starting point or more instructions? Head over to{" "}
21
- <a
22
- href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
23
- className="font-medium text-zinc-950 dark:text-zinc-50"
24
- >
25
- Templates
26
- </a>{" "}
27
- or the{" "}
28
- <a
29
- href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
30
- className="font-medium text-zinc-950 dark:text-zinc-50"
31
- >
32
- Learning
33
- </a>{" "}
34
- center.
35
- </p>
36
  </div>
37
- <div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
38
- <a
39
- className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
40
- href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
41
- target="_blank"
42
- rel="noopener noreferrer"
43
  >
44
- <Image
45
- className="dark:invert"
46
- src="/vercel.svg"
47
- alt="Vercel logomark"
48
- width={16}
49
- height={16}
50
- />
51
- Deploy Now
52
- </a>
53
- <a
54
- className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
55
- href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
56
- target="_blank"
57
- rel="noopener noreferrer"
58
- >
59
- Documentation
60
- </a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  </div>
62
- </main>
63
  </div>
64
  );
65
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import React, { useState, useEffect } from "react";
4
+ import { db, auth } from "@/lib/firebase";
5
+ import { collection, onSnapshot, query, orderBy, limit } from "firebase/firestore";
6
+ import Link from "next/link";
7
+
8
+ export default function Dashboard() {
9
+ const [stats, setStats] = useState({
10
+ products: 0,
11
+ clients: 0,
12
+ salesCount: 0,
13
+ revenue: 0,
14
+ });
15
+
16
+ useEffect(() => {
17
+ // Real-time stats from Firestore
18
+ const unsubProducts = onSnapshot(collection(db, "products"), (snap) => {
19
+ setStats((prev) => ({ ...prev, products: snap.size }));
20
+ });
21
+
22
+ const unsubClients = onSnapshot(collection(db, "clients"), (snap) => {
23
+ setStats((prev) => ({ ...prev, clients: snap.size }));
24
+ });
25
+
26
+ const unsubSales = onSnapshot(collection(db, "sales"), (snap) => {
27
+ let rev = 0;
28
+ snap.forEach((doc) => {
29
+ rev += doc.data().total || 0;
30
+ });
31
+ setStats((prev) => ({ ...prev, salesCount: snap.size, revenue: rev }));
32
+ });
33
+
34
+ return () => {
35
+ unsubProducts();
36
+ unsubClients();
37
+ unsubSales();
38
+ };
39
+ }, []);
40
 
 
41
  return (
42
+ <div className="min-h-screen bg-[#0f172a] text-white p-6 font-sans">
43
+ <header className="flex justify-between items-center mb-10">
44
+ <div>
45
+ <h1 className="text-4xl font-extrabold bg-gradient-to-r from-blue-400 to-indigo-500 bg-clip-text text-transparent">
46
+ ERP Premium Dashboard
 
 
 
 
 
 
 
 
47
  </h1>
48
+ <p className="text-gray-400 mt-1">Bienvenido al centro de control de tu negocio</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  </div>
50
+ <div className="flex gap-4">
51
+ <button
52
+ onClick={() => auth.signOut()}
53
+ className="px-6 py-2 bg-white/10 hover:bg-white/20 rounded-full border border-white/10 transition-all text-sm font-medium backdrop-blur-md"
 
 
54
  >
55
+ Cerrar Sesión
56
+ </button>
57
+ </div>
58
+ </header>
59
+
60
+ {/* Stats Grid */}
61
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-10">
62
+ <StatCard title="Productos" value={stats.products} icon="📦" color="from-blue-500 to-cyan-400" />
63
+ <StatCard title="Clientes" value={stats.clients} icon="👥" color="from-purple-500 to-pink-500" />
64
+ <StatCard title="Ventas Totales" value={stats.salesCount} icon="🛒" color="from-orange-500 to-yellow-400" />
65
+ <StatCard title="Ingresos" value={`$${stats.revenue.toLocaleString()}`} icon="💰" color="from-green-500 to-emerald-400" />
66
+ </div>
67
+
68
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
69
+ {/* Quick Actions */}
70
+ <div className="lg:col-span-1 bg-white/5 border border-white/10 rounded-3xl p-8 backdrop-blur-xl">
71
+ <h2 className="text-xl font-bold mb-6">Acciones Rápidas</h2>
72
+ <div className="space-y-4">
73
+ <QuickAction href="/inventory" label="Nueva Existencia" icon="➕" />
74
+ <QuickAction href="/sales" label="Registrar Venta" icon="🏷️" />
75
+ <QuickAction href="/clients" label="Añadir Cliente" icon="👤" />
76
+ <QuickAction href="/hr" label="Gestionar Personal" icon="👔" />
77
+ </div>
78
+ </div>
79
+
80
+ {/* Charts Mockup / Future Widget */}
81
+ <div className="lg:col-span-2 bg-white/5 border border-white/10 rounded-3xl p-8 backdrop-blur-xl relative overflow-hidden group">
82
+ <div className="absolute inset-0 bg-gradient-to-br from-blue-600/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700"></div>
83
+ <div className="relative z-10">
84
+ <h2 className="text-xl font-bold mb-6">Rendimiento Semanal</h2>
85
+ <div className="h-64 flex items-end justify-between gap-2">
86
+ {[60, 45, 80, 55, 95, 70, 85].map((h, i) => (
87
+ <div key={i} className="flex-1 flex flex-col items-center">
88
+ <div
89
+ className="w-full bg-gradient-to-t from-blue-600 to-cyan-400 rounded-t-lg transition-all duration-1000 delay-150"
90
+ style={{ height: `${h}%` }}
91
+ ></div>
92
+ <span className="text-[10px] text-gray-500 mt-2">{['L', 'M', 'X', 'J', 'V', 'S', 'D'][i]}</span>
93
+ </div>
94
+ ))}
95
+ </div>
96
+ <p className="mt-8 text-sm text-gray-400 items-center flex gap-2">
97
+ <span className="w-2 h-2 rounded-full bg-green-400 animate-pulse"></span>
98
+ Actualizado en tiempo real con Firestore
99
+ </p>
100
+ </div>
101
  </div>
102
+ </div>
103
  </div>
104
  );
105
  }
106
+
107
+ function StatCard({ title, value, icon, color }: { title: string, value: string | number, icon: string, color: string }) {
108
+ return (
109
+ <div className="bg-white/5 border border-white/10 rounded-3xl p-6 backdrop-blur-md relative overflow-hidden group hover:scale-[1.02] transition-all">
110
+ <div className={`absolute -right-4 -top-4 w-24 h-24 bg-gradient-to-br ${color} opacity-20 blur-2xl rounded-full group-hover:opacity-40 transition-opacity`}></div>
111
+ <div className="flex items-center gap-4 relative z-10">
112
+ <div className="text-3xl">{icon}</div>
113
+ <div>
114
+ <p className="text-sm text-gray-400 uppercase tracking-wider font-semibold">{title}</p>
115
+ <p className="text-2xl font-bold">{value}</p>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ );
120
+ }
121
+
122
+ function QuickAction({ href, label, icon }: { href: string, label: string, icon: string }) {
123
+ return (
124
+ <Link href={href} className="flex items-center gap-4 p-4 rounded-2xl bg-white/5 border border-white/5 hover:bg-white/10 hover:border-white/20 transition-all group">
125
+ <span className="text-xl group-hover:scale-125 transition-transform">{icon}</span>
126
+ <span className="font-medium text-gray-200">{label}</span>
127
+ <span className="ml-auto opacity-0 group-hover:opacity-100 transition-opacity">→</span>
128
+ </Link>
129
+ );
130
+ }
app/sales/page.tsx ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import React, { useState, useEffect } from "react";
4
+ import { db } from "@/lib/firebase";
5
+ import { collection, onSnapshot, addDoc, serverTimestamp } from "firebase/firestore";
6
+ import Link from "next/link";
7
+
8
+ interface Sale {
9
+ id: string;
10
+ client: string;
11
+ items: string;
12
+ total: number;
13
+ date: any;
14
+ }
15
+
16
+ export default function SalesPage() {
17
+ const [sales, setSales] = useState<Sale[]>([]);
18
+ const [products, setProducts] = useState<any[]>([]);
19
+ const [isModalOpen, setIsModalOpen] = useState(false);
20
+ const [newSale, setNewSale] = useState({ client: "", items: "", total: 0 });
21
+
22
+ useEffect(() => {
23
+ const unsubSales = onSnapshot(collection(db, "sales"), (snap) => {
24
+ const data = snap.docs.map(doc => ({ id: doc.id, ...doc.data() } as Sale));
25
+ setSales(data);
26
+ });
27
+ const unsubProducts = onSnapshot(collection(db, "products"), (snap) => {
28
+ setProducts(snap.docs.map(doc => ({ id: doc.id, ...doc.data() })));
29
+ });
30
+ return () => { unsubSales(); unsubProducts(); };
31
+ }, []);
32
+
33
+ const handleAdd = async (e: React.FormEvent) => {
34
+ e.preventDefault();
35
+ await addDoc(collection(db, "sales"), {
36
+ ...newSale,
37
+ date: serverTimestamp()
38
+ });
39
+ setIsModalOpen(false);
40
+ setNewSale({ client: "", items: "", total: 0 });
41
+ };
42
+
43
+ return (
44
+ <div className="min-h-screen bg-[#0f172a] text-white p-6">
45
+ <header className="flex justify-between items-center mb-10">
46
+ <div>
47
+ <Link href="/" className="text-orange-400 hover:text-orange-300 transition-colors flex items-center gap-2 mb-2 font-medium">
48
+ ← Home
49
+ </Link>
50
+ <h1 className="text-4xl font-black bg-gradient-to-r from-orange-400 to-yellow-500 bg-clip-text text-transparent">Historial de Ventas</h1>
51
+ </div>
52
+ <button
53
+ onClick={() => setIsModalOpen(true)}
54
+ className="px-10 py-4 bg-orange-600 hover:bg-orange-500 rounded-full font-black text-xs uppercase tracking-widest transition-all shadow-xl shadow-orange-900/40 transform hover:scale-105 active:scale-95"
55
+ >
56
+ ➕ Registrar Transacción
57
+ </button>
58
+ </header>
59
+
60
+ <div className="bg-white/5 border border-white/10 rounded-[3rem] p-10 backdrop-blur-2xl">
61
+ <div className="space-y-6">
62
+ {sales.map((s) => (
63
+ <div key={s.id} className="flex items-center justify-between p-6 bg-white/5 border border-white/5 rounded-3xl hover:bg-white/10 transition-all group">
64
+ <div className="flex items-center gap-6">
65
+ <div className="w-14 h-14 bg-gradient-to-br from-orange-500 to-yellow-500 rounded-2xl flex items-center justify-center text-2xl shadow-lg">🧾</div>
66
+ <div>
67
+ <h3 className="font-bold text-lg group-hover:text-orange-400 transition-colors uppercase tracking-tight">{s.client}</h3>
68
+ <p className="text-gray-500 text-xs font-medium mt-1">PRODUCTOS: <span className="text-gray-400">{s.items}</span></p>
69
+ </div>
70
+ </div>
71
+ <div className="text-right">
72
+ <p className="text-2xl font-black text-white">${Number(s.total).toLocaleString()}</p>
73
+ <p className="text-[10px] text-gray-600 font-black uppercase mt-1">Completado</p>
74
+ </div>
75
+ </div>
76
+ ))}
77
+ </div>
78
+ </div>
79
+
80
+ {isModalOpen && (
81
+ <div className="fixed inset-0 bg-[#0f172a]/95 backdrop-blur-3xl flex items-center justify-center p-6 z-50">
82
+ <div className="bg-white/5 border border-white/10 p-12 rounded-[50px] w-full max-w-lg shadow-2xl relative overflow-hidden">
83
+ <div className="absolute top-0 right-0 w-32 h-32 bg-orange-500/10 blur-[80px] rounded-full"></div>
84
+ <h2 className="text-3xl font-black mb-10 tracking-tighter italic">NUEVA VENTA /</h2>
85
+ <form onSubmit={handleAdd} className="space-y-6">
86
+ <input
87
+ placeholder="Nombre del Cliente" required
88
+ value={newSale.client} onChange={e => setNewSale({...newSale, client: e.target.value})}
89
+ className="w-full bg-white/5 border-b border-white/10 py-4 text-xl outline-none focus:border-orange-500 transition-all font-bold"
90
+ />
91
+ <input
92
+ placeholder="Descripción de Ítems" required
93
+ value={newSale.items} onChange={e => setNewSale({...newSale, items: e.target.value})}
94
+ className="w-full bg-white/5 border-b border-white/10 py-4 text-lg outline-none focus:border-orange-500 transition-all"
95
+ />
96
+ <div className="relative">
97
+ <span className="absolute left-0 top-1/2 -translate-y-1/2 text-3xl font-black text-gray-700">$</span>
98
+ <input
99
+ type="number" placeholder="0.00" required
100
+ value={newSale.total} onChange={e => setNewSale({...newSale, total: Number(e.target.value)})}
101
+ className="w-full bg-transparent border-b border-white/10 pl-10 py-6 text-6xl outline-none focus:border-orange-500 transition-all font-black text-orange-400 placeholder:text-gray-800"
102
+ />
103
+ </div>
104
+ <div className="flex gap-4 pt-12">
105
+ <button type="button" onClick={() => setIsModalOpen(false)} className="px-8 py-5 text-gray-500 font-black text-xs uppercase hover:text-white transition-colors tracking-widest">Descartar</button>
106
+ <button type="submit" className="flex-1 bg-orange-600 hover:bg-orange-500 py-5 rounded-3xl font-black text-xs uppercase tracking-[0.2em] transition-all shadow-xl shadow-orange-900/20">Finalizar Venta</button>
107
+ </div>
108
+ </form>
109
+ </div>
110
+ </div>
111
+ )}
112
+ </div>
113
+ );
114
+ }
lib/firebase.ts ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { initializeApp, getApps } from "firebase/app";
2
+ import { getAuth } from "firebase/auth";
3
+ import { getFirestore } from "firebase/firestore";
4
+
5
+ const firebaseConfig = {
6
+ apiKey: "AIzaSyD0gjMSH04oiyUIjqTiUs3zuLkW7UP1x-s",
7
+ authDomain: "erpjsf.firebaseapp.com",
8
+ projectId: "erpjsf",
9
+ storageBucket: "erpjsf.firebasestorage.app",
10
+ messagingSenderId: "996985286814",
11
+ appId: "1:996985286814:web:7e02e9a31da1deac638b8f"
12
+ };
13
+
14
+ // Initialize Firebase
15
+ const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
16
+ const auth = getAuth(app);
17
+ const db = getFirestore(app);
18
+
19
+ export { app, auth, db };