dimensionalpulsar commited on
Commit
28b0bde
·
verified ·
1 Parent(s): c86f085

Upload 49 files

Browse files
frontend/src/components/admin/DashboardOverview.jsx CHANGED
@@ -18,8 +18,8 @@ export default function DashboardOverview() {
18
  const cards = [
19
  { title: 'Ventas de Hoy', value: `$${stats.ventasHoy.toLocaleString()}`, icon: <TrendingUp size={24} color="var(--success)" />, bg: 'rgba(32, 201, 151, 0.1)' },
20
  { title: 'Mesas Activas', value: stats.mesasActivas, icon: <Users size={24} color="var(--info)" />, bg: 'rgba(50, 173, 230, 0.1)' },
21
- { title: 'Ticket Promedio', value: '$275.50', icon: <ShoppingBag size={24} color="var(--primary)" />, bg: 'rgba(255, 90, 95, 0.1)' },
22
- { title: 'Utilidad Est.', value: '$4,200', icon: <AlertCircle size={24} color="var(--warning)" />, bg: 'rgba(245, 166, 35, 0.1)' }
23
  ];
24
 
25
  return (
@@ -40,31 +40,9 @@ export default function DashboardOverview() {
40
  ))}
41
  </div>
42
 
43
- <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1.5rem' }}>
44
- <div className="glass-card">
45
- <h3 className="text-gradient" style={{ marginBottom: '1rem' }}>Saturación por Horario (Peak Hours)</h3>
46
- <div style={{ height: '10px', width: '100%', background: 'rgba(0,0,0,0.05)', borderRadius: '10px', overflow: 'hidden', display: 'flex' }}>
47
- <div style={{ width: '20%', background: 'var(--primary)', opacity: 0.3 }}></div>
48
- <div style={{ width: '40%', background: 'var(--primary)' }}></div>
49
- <div style={{ width: '15%', background: 'var(--primary)', opacity: 0.5 }}></div>
50
- <div style={{ width: '25%', background: 'rgba(0,0,0,0.05)' }}></div>
51
- </div>
52
- <p style={{ color: 'var(--text-muted)', fontSize: '0.8rem', marginTop: '1rem' }}>Punto máximo detectado: 14:00 - 15:30 (Almuerzo)</p>
53
- </div>
54
-
55
- <div className="glass-card">
56
- <h3 className="text-gradient" style={{ marginBottom: '1rem' }}>Alertas de Suministros (ERP)</h3>
57
- <div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
58
- <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.9rem' }}>
59
- <span>Tomate Cherry (Kg)</span>
60
- <span style={{ color: 'var(--danger)', fontWeight: '700' }}>1.2 (Bajo)</span>
61
- </div>
62
- <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.9rem' }}>
63
- <span>Aceite de Oliva (L)</span>
64
- <span style={{ color: 'var(--warning)', fontWeight: '700' }}>3.0 (Crítico)</span>
65
- </div>
66
- </div>
67
- </div>
68
  </div>
69
  </div>
70
  );
 
18
  const cards = [
19
  { title: 'Ventas de Hoy', value: `$${stats.ventasHoy.toLocaleString()}`, icon: <TrendingUp size={24} color="var(--success)" />, bg: 'rgba(32, 201, 151, 0.1)' },
20
  { title: 'Mesas Activas', value: stats.mesasActivas, icon: <Users size={24} color="var(--info)" />, bg: 'rgba(50, 173, 230, 0.1)' },
21
+ { title: 'Órdenes', value: '45', icon: <ShoppingBag size={24} color="var(--primary)" />, bg: 'rgba(255, 90, 95, 0.1)' },
22
+ { title: 'Stock Bajo', value: stats.stockBajo, icon: <AlertCircle size={24} color="var(--warning)" />, bg: 'rgba(245, 166, 35, 0.1)' }
23
  ];
24
 
25
  return (
 
40
  ))}
41
  </div>
42
 
43
+ <div className="glass-card">
44
+ <h3 className="text-gradient">Actividad Reciente</h3>
45
+ <p style={{ color: 'var(--text-muted)', marginTop: '0.5rem' }}>Módulo de notificaciones en tiempo real desde Cocina/Meseros...</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  </div>
47
  </div>
48
  );
frontend/src/components/admin/FinanceManager.jsx CHANGED
@@ -109,7 +109,7 @@ export default function FinanceManager() {
109
  <div style={{ ...statBoxStyle, background: 'rgba(76,217,100,0.05)', borderColor: 'var(--success)' }}>
110
  <span style={{ fontSize: '0.75rem', color: 'var(--success)' }}>Ventas Registradas</span>
111
  <div style={{ fontSize: '1.5rem', fontWeight: '900', color: 'var(--success)' }}>$0.00</div>
112
- <p style={{ fontSize: '0.7rem', opacity: 0.6, marginTop: '0.2rem', color: 'var(--text-muted)' }}>Sincronizado con POS en tiempo real</p>
113
  </div>
114
  <button onClick={closeCash} className="btn-glass" style={{ color: 'var(--primary)', borderColor: 'var(--primary)', padding: '0.8rem' }}>
115
  Cerrar Turno / Corte
@@ -168,13 +168,13 @@ export default function FinanceManager() {
168
  </thead>
169
  <tbody>
170
  {cashSessions.map(sess => (
171
- <tr key={sess.id} style={{ borderBottom: '1px solid var(--border-subtle)' }}>
172
  <td style={{ padding: '1rem' }}>{new Date(sess.openedAt).toLocaleDateString()}</td>
173
  <td style={{ padding: '1rem' }}>${sess.openingBalance.toFixed(2)}</td>
174
  <td style={{ padding: '1rem' }}>{sess.closedAt ? new Date(sess.closedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '-'}</td>
175
  <td style={{ padding: '1rem' }}>${(sess.totalSales || 0).toFixed(2)}</td>
176
  <td style={{ padding: '1rem' }}>
177
- <span style={{ padding: '2px 8px', borderRadius: '4px', background: sess.status === 'open' ? 'rgba(76,217,100,0.1)' : 'rgba(0,0,0,0.05)', color: sess.status === 'open' ? 'var(--success)' : 'var(--text-muted)', fontSize: '0.75rem' }}>
178
  {sess.status.toUpperCase()}
179
  </span>
180
  </td>
@@ -187,6 +187,6 @@ export default function FinanceManager() {
187
  );
188
  }
189
 
190
- const inputStyle = { width: '100%', padding: '0.8rem', borderRadius: '8px', background: 'rgba(0,0,0,0.03)', border: '1px solid var(--border-subtle)', color: 'var(--text-main)', outline: 'none' };
191
- const statBoxStyle = { padding: '1rem', borderRadius: '12px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--border-subtle)', display: 'flex', flexDirection: 'column', gap: '0.25rem' };
192
  const Plus = ({ size }) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>;
 
109
  <div style={{ ...statBoxStyle, background: 'rgba(76,217,100,0.05)', borderColor: 'var(--success)' }}>
110
  <span style={{ fontSize: '0.75rem', color: 'var(--success)' }}>Ventas Registradas</span>
111
  <div style={{ fontSize: '1.5rem', fontWeight: '900', color: 'var(--success)' }}>$0.00</div>
112
+ <p style={{ fontSize: '0.7rem', opacity: 0.5, marginTop: '0.2rem' }}>Sincronizado con POS en tiempo real</p>
113
  </div>
114
  <button onClick={closeCash} className="btn-glass" style={{ color: 'var(--primary)', borderColor: 'var(--primary)', padding: '0.8rem' }}>
115
  Cerrar Turno / Corte
 
168
  </thead>
169
  <tbody>
170
  {cashSessions.map(sess => (
171
+ <tr key={sess.id} style={{ borderBottom: '1px solid rgba(255,255,255,0.03)' }}>
172
  <td style={{ padding: '1rem' }}>{new Date(sess.openedAt).toLocaleDateString()}</td>
173
  <td style={{ padding: '1rem' }}>${sess.openingBalance.toFixed(2)}</td>
174
  <td style={{ padding: '1rem' }}>{sess.closedAt ? new Date(sess.closedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '-'}</td>
175
  <td style={{ padding: '1rem' }}>${(sess.totalSales || 0).toFixed(2)}</td>
176
  <td style={{ padding: '1rem' }}>
177
+ <span style={{ padding: '2px 8px', borderRadius: '4px', background: sess.status === 'open' ? 'rgba(76,217,100,0.1)' : 'rgba(255,255,255,0.05)', color: sess.status === 'open' ? 'var(--success)' : 'var(--text-muted)', fontSize: '0.75rem' }}>
178
  {sess.status.toUpperCase()}
179
  </span>
180
  </td>
 
187
  );
188
  }
189
 
190
+ const inputStyle = { width: '100%', padding: '0.8rem', borderRadius: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid var(--border-subtle)', color: '#fff', outline: 'none' };
191
+ const statBoxStyle = { padding: '1rem', borderRadius: '12px', background: 'rgba(255,255,255,0.02)', border: '1px solid var(--border-subtle)', display: 'flex', flexDirection: 'column', gap: '0.25rem' };
192
  const Plus = ({ size }) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>;
frontend/src/components/admin/InventoryControl.jsx CHANGED
@@ -137,11 +137,11 @@ export default function InventoryControl() {
137
 
138
  {/* Modal de Edición */}
139
  {isModalOpen && (
140
- <div style={{ position: 'fixed', top: 0, left: 0, width: '100vw', height: '100vh', background: 'rgba(0,0,0,0.4)', backdropFilter: 'blur(8px)', display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 9999, padding: '20px' }}>
141
  <div className="glass-panel animate-slide-up" style={{ maxWidth: '450px', width: '100%', padding: '2.5rem' }}>
142
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
143
  <h2 className="text-gradient" style={{ fontSize: '1.5rem' }}>Editar Insumo</h2>
144
- <button onClick={() => setIsModalOpen(false)} style={{ background: 'none', border: 'none', color: 'var(--text-muted)', cursor: 'pointer' }}><X size={24} /></button>
145
  </div>
146
 
147
  <div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
@@ -177,8 +177,8 @@ export default function InventoryControl() {
177
 
178
  const inputStyle = {
179
  width: '100%', padding: '0.8rem', borderRadius: '8px',
180
- background: 'rgba(0,0,0,0.03)', border: '1px solid var(--border-subtle)',
181
- color: 'var(--text-main)', outline: 'none'
182
  };
183
 
184
  const labelStyle = {
@@ -187,6 +187,6 @@ const labelStyle = {
187
 
188
  const actionBtnStyle = {
189
  width: '32px', height: '32px', borderRadius: '8px', border: '1px solid var(--border-subtle)',
190
- background: 'rgba(0,0,0,0.02)', color: 'var(--text-main)', cursor: 'pointer',
191
  display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 0.2s', fontSize: '0.75rem', fontWeight: '700'
192
  };
 
137
 
138
  {/* Modal de Edición */}
139
  {isModalOpen && (
140
+ <div style={{ position: 'fixed', top: 0, left: 0, width: '100vw', height: '100vh', background: 'rgba(0,0,0,0.85)', display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 9999, padding: '20px' }}>
141
  <div className="glass-panel animate-slide-up" style={{ maxWidth: '450px', width: '100%', padding: '2.5rem' }}>
142
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}>
143
  <h2 className="text-gradient" style={{ fontSize: '1.5rem' }}>Editar Insumo</h2>
144
+ <button onClick={() => setIsModalOpen(false)} style={{ background: 'none', border: 'none', color: '#fff', cursor: 'pointer' }}><X size={24} /></button>
145
  </div>
146
 
147
  <div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
 
177
 
178
  const inputStyle = {
179
  width: '100%', padding: '0.8rem', borderRadius: '8px',
180
+ background: 'rgba(255,255,255,0.05)', border: '1px solid var(--border-subtle)',
181
+ color: '#fff', outline: 'none'
182
  };
183
 
184
  const labelStyle = {
 
187
 
188
  const actionBtnStyle = {
189
  width: '32px', height: '32px', borderRadius: '8px', border: '1px solid var(--border-subtle)',
190
+ background: 'rgba(255,255,255,0.05)', color: 'var(--text-main)', cursor: 'pointer',
191
  display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 0.2s', fontSize: '0.75rem', fontWeight: '700'
192
  };
frontend/src/components/admin/MenuEditor.jsx CHANGED
@@ -139,7 +139,7 @@ export default function MenuEditor() {
139
  newIngs[idx].qty = parseFloat(e.target.value);
140
  setFormData({...formData, ingredients: newIngs});
141
  }}
142
- style={{...inputStyle, padding: '0.4rem', width: '60px', background: 'rgba(0,0,0,0.03)'}}
143
  />
144
  <button type="button" onClick={() => setFormData({...formData, ingredients: formData.ingredients.filter((_, i) => i !== idx)})} style={{color: 'var(--primary)', background: 'none', border: 'none'}}><X size={16}/></button>
145
  </div>
@@ -164,7 +164,7 @@ export default function MenuEditor() {
164
  <span style={{ color: 'var(--success)', fontWeight: '800' }}>${item.price}</span>
165
  </div>
166
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
167
- <span style={{ fontSize: '0.7rem', color: 'var(--text-muted)', background: 'rgba(0,0,0,0.03)', padding: '2px 8px', borderRadius: '4px' }}>{item.category}</span>
168
  {item.ingredients?.length > 0 && <span title="Tiene receta vinculada" style={{ color: 'var(--primary)' }}><ChefHat size={14} /></span>}
169
  </div>
170
  </div>
@@ -177,7 +177,7 @@ export default function MenuEditor() {
177
  <div className="glass-panel animate-scale-in" style={{ maxWidth: '600px', width: '100%', padding: '2rem', maxHeight: '90vh', overflowY: 'auto' }}>
178
  <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '2rem' }}>
179
  <h2 className="text-gradient">Editar: {editingItem.name}</h2>
180
- <button onClick={() => setIsModalOpen(false)} style={{ background: 'none', border: 'none', color: 'var(--text-muted)' }}><X size={24} /></button>
181
  </div>
182
 
183
  <div style={{ display: 'flex', flexDirection: 'column', gap: '1.25rem' }}>
@@ -226,6 +226,6 @@ export default function MenuEditor() {
226
  );
227
  }
228
 
229
- const inputStyle = { width: '100%', padding: '0.8rem', borderRadius: '8px', background: 'rgba(0,0,0,0.03)', border: '1px solid var(--border-subtle)', color: 'var(--text-main)', outline: 'none' };
230
  const labelStyle = { display: 'block', fontSize: '0.8rem', color: 'var(--text-muted)', marginBottom: '0.4rem' };
231
- const actionBtnStyle = { width: '32px', height: '32px', borderRadius: '8px', border: 'none', background: 'rgba(255,255,255,0.9)', color: 'var(--text-main)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' };
 
139
  newIngs[idx].qty = parseFloat(e.target.value);
140
  setFormData({...formData, ingredients: newIngs});
141
  }}
142
+ style={{...inputStyle, padding: '0.4rem', width: '60px'}}
143
  />
144
  <button type="button" onClick={() => setFormData({...formData, ingredients: formData.ingredients.filter((_, i) => i !== idx)})} style={{color: 'var(--primary)', background: 'none', border: 'none'}}><X size={16}/></button>
145
  </div>
 
164
  <span style={{ color: 'var(--success)', fontWeight: '800' }}>${item.price}</span>
165
  </div>
166
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
167
+ <span style={{ fontSize: '0.7rem', color: 'var(--text-muted)', background: 'rgba(255,255,255,0.05)', padding: '2px 8px', borderRadius: '4px' }}>{item.category}</span>
168
  {item.ingredients?.length > 0 && <span title="Tiene receta vinculada" style={{ color: 'var(--primary)' }}><ChefHat size={14} /></span>}
169
  </div>
170
  </div>
 
177
  <div className="glass-panel animate-scale-in" style={{ maxWidth: '600px', width: '100%', padding: '2rem', maxHeight: '90vh', overflowY: 'auto' }}>
178
  <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '2rem' }}>
179
  <h2 className="text-gradient">Editar: {editingItem.name}</h2>
180
+ <button onClick={() => setIsModalOpen(false)} style={{ background: 'none', border: 'none', color: '#fff' }}><X size={24} /></button>
181
  </div>
182
 
183
  <div style={{ display: 'flex', flexDirection: 'column', gap: '1.25rem' }}>
 
226
  );
227
  }
228
 
229
+ const inputStyle = { width: '100%', padding: '0.8rem', borderRadius: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid var(--border-subtle)', color: '#fff', outline: 'none' };
230
  const labelStyle = { display: 'block', fontSize: '0.8rem', color: 'var(--text-muted)', marginBottom: '0.4rem' };
231
+ const actionBtnStyle = { width: '32px', height: '32px', borderRadius: '8px', border: 'none', background: 'rgba(0,0,0,0.6)', color: '#fff', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center' };
frontend/src/components/admin/Reports.jsx CHANGED
@@ -6,7 +6,7 @@ import {
6
  LineElement, Title, Tooltip, Legend, BarElement
7
  } from 'chart.js';
8
  import { Line, Bar } from 'react-chartjs-2';
9
- import { TrendingUp, Package, Users as UsersIcon, Clock, Award, BarChart3 } from 'lucide-react';
10
 
11
  ChartJS.register(
12
  CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, BarElement
@@ -14,8 +14,6 @@ ChartJS.register(
14
 
15
  export default function Reports() {
16
  const [salesData, setSalesData] = useState([0, 0, 0, 0, 0, 0, 0]);
17
- const [hourlyData, setHourlyData] = useState(new Array(24).fill(0));
18
- const [waiterPerformance, setWaiterPerformance] = useState({ labels: [], data: [] });
19
  const [topProducts, setTopProducts] = useState({ labels: [], data: [] });
20
  const [stats, setStats] = useState({ totalSales: 0, orderCount: 0, avgTicket: 0 });
21
 
@@ -23,139 +21,112 @@ export default function Reports() {
23
  onValue(ref(db, 'orders'), (snapshot) => {
24
  const data = snapshot.val();
25
  if (data) {
26
- const orders = Object.values(data).filter(o => o.status === 'completed');
27
  const weeklySales = [0, 0, 0, 0, 0, 0, 0];
28
- const hourSales = new Array(24).fill(0);
29
  const productCounts = {};
30
- const waiterSales = {};
31
  let total = 0;
32
 
33
  orders.forEach(order => {
34
- total += order.total;
35
-
36
- const date = new Date(order.timestamp);
37
- // Weekly
38
- const day = date.getDay();
39
- const dayIdx = (day + 6) % 7;
40
- weeklySales[dayIdx] += order.total;
41
-
42
- // Hourly
43
- const hour = date.getHours();
44
- hourSales[hour] += 1; // Count orders per hour for peak density
45
-
46
- // Waiters
47
- const waiter = order.waiter || 'Sistema/Admin';
48
- waiterSales[waiter] = (waiterSales[waiter] || 0) + order.total;
49
-
50
- // Products
51
- order.items.forEach(item => {
52
- productCounts[item.name] = (productCounts[item.name] || 0) + item.qty;
53
- });
54
  });
55
 
56
  setSalesData(weeklySales);
57
- setHourlyData(hourSales);
58
  setStats({
59
- totalSales: total,
60
- orderCount: orders.length,
61
- avgTicket: total / (orders.length || 1)
62
  });
63
 
64
  const sortedProducts = Object.entries(productCounts)
65
- .sort((a,b) => b[1] - a[1])
66
- .slice(0, 5);
 
67
  setTopProducts({
68
- labels: sortedProducts.map(p => p[0]),
69
- data: sortedProducts.map(p => p[1])
70
- });
71
-
72
- const sortedWaiters = Object.entries(waiterSales)
73
- .sort((a,b) => b[1] - a[1]);
74
- setWaiterPerformance({
75
- labels: sortedWaiters.map(w => w[0].split('@')[0]),
76
- data: sortedWaiters.map(w => w[1])
77
  });
78
  }
79
  });
80
  }, []);
81
 
82
- const chartOptions = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  responsive: true,
84
  maintainAspectRatio: false,
85
- plugins: { legend: { labels: { color: '#9595a8', font: { size: 10 } } } },
86
  scales: {
87
- x: { ticks: { color: '#9595a8', font: { size: 10 } }, grid: { display: false } },
88
- y: { ticks: { color: '#9595a8', font: { size: 10 } }, grid: { color: 'rgba(0,0,0,0.05)' } }
89
  }
90
  };
91
 
92
  return (
93
- <div className="animate-fade-in" style={{ paddingBottom: '3rem' }}>
94
  <header style={{ marginBottom: '2.5rem' }}>
95
- <h2 className="text-gradient" style={{ fontSize: '2rem', fontWeight: '800' }}>RESTO Analytics PLUS</h2>
96
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))', gap: '1.5rem', marginTop: '1.5rem' }}>
97
- <StatCard title="Ventas Totales" value={`$${stats.totalSales.toFixed(2)}`} icon={<TrendingUp size={18}/>} color="var(--primary)" />
98
- <StatCard title="Pedidos" value={stats.orderCount} icon={<Package size={18}/>} color="var(--success)" />
99
- <StatCard title="Ticket Promedio" value={`$${stats.avgTicket.toFixed(2)}`} icon={<BarChart3 size={18}/>} color="#4dabf7" />
 
 
 
 
 
 
 
 
 
100
  </div>
101
  </header>
102
 
103
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(450px, 1fr))', gap: '2rem' }}>
104
- {/* Ventas Semanales */}
105
- <ChartCard title="Flujo de Ventas Semanal" icon={<Calendar size={18}/>}>
106
- <Line data={{
107
- labels: ['Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb', 'Dom'],
108
- datasets: [{ label: 'Ventas ($)', data: salesData, borderColor: 'var(--primary)', backgroundColor: 'rgba(255,90,95,0.1)', fill: true, tension: 0.4 }]
109
- }} options={chartOptions} />
110
- </ChartCard>
111
-
112
- {/* Rendimiento Meseros */}
113
- <ChartCard title="Rendimiento por Mesero" icon={<Award size={18}/>}>
114
- <Bar data={{
115
- labels: waiterPerformance.labels,
116
- datasets: [{ label: 'Ventas Totales ($)', data: waiterPerformance.data, backgroundColor: 'rgba(76,217,100,0.6)', borderRadius: 6 }]
117
- }} options={chartOptions} />
118
- </ChartCard>
119
-
120
- {/* Horas Pico */}
121
- <ChartCard title="Horas Pico (Densidad de Pedidos)" icon={<Clock size={18}/>}>
122
- <Line data={{
123
- labels: Array.from({length: 24}, (_, i) => `${i}:00`),
124
- datasets: [{ label: 'Pedidos', data: hourlyData, borderColor: '#4dabf7', backgroundColor: 'rgba(77,171,247,0.1)', fill: true, tension: 0.4 }]
125
- }} options={chartOptions} />
126
- </ChartCard>
127
-
128
- {/* Top Productos */}
129
- <ChartCard title="Productos Estrella" icon={<Package size={18}/>}>
130
- <Bar data={{
131
- labels: topProducts.labels,
132
- datasets: [{ label: 'Unidades', data: topProducts.data, backgroundColor: 'rgba(0,0,0,0.03)', borderColor: 'var(--border-subtle)', borderWidth: 1, borderRadius: 6 }]
133
- }} options={chartOptions} />
134
- </ChartCard>
135
- </div>
136
- </div>
137
- );
138
- }
139
-
140
- function StatCard({ title, value, icon, color }) {
141
- return (
142
- <div className="glass-card" style={{ padding: '1.5rem' }}>
143
- <div style={{ display: 'flex', justifyContent: 'space-between', color: 'var(--text-muted)', fontSize: '0.85rem', marginBottom: '0.75rem' }}>
144
- {title} <span style={{ color }}>{icon}</span>
145
- </div>
146
- <div style={{ fontSize: '1.75rem', fontWeight: '900' }}>{value}</div>
147
- </div>
148
- );
149
- }
150
 
151
- function ChartCard({ title, icon, children }) {
152
- return (
153
- <div className="glass-card" style={{ height: '380px', display: 'flex', flexDirection: 'column', padding: '1.5rem' }}>
154
- <h3 style={{ marginBottom: '1.5rem', fontSize: '1.1rem', fontWeight: '700', display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
155
- {icon} {title}
156
- </h3>
157
- <div style={{ flex: 1, position: 'relative' }}>
158
- {children}
159
  </div>
160
  </div>
161
  );
 
6
  LineElement, Title, Tooltip, Legend, BarElement
7
  } from 'chart.js';
8
  import { Line, Bar } from 'react-chartjs-2';
9
+ import { TrendingUp, Package, Users as UsersIcon, Calendar } from 'lucide-react';
10
 
11
  ChartJS.register(
12
  CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, BarElement
 
14
 
15
  export default function Reports() {
16
  const [salesData, setSalesData] = useState([0, 0, 0, 0, 0, 0, 0]);
 
 
17
  const [topProducts, setTopProducts] = useState({ labels: [], data: [] });
18
  const [stats, setStats] = useState({ totalSales: 0, orderCount: 0, avgTicket: 0 });
19
 
 
21
  onValue(ref(db, 'orders'), (snapshot) => {
22
  const data = snapshot.val();
23
  if (data) {
24
+ const orders = Object.values(data);
25
  const weeklySales = [0, 0, 0, 0, 0, 0, 0];
 
26
  const productCounts = {};
 
27
  let total = 0;
28
 
29
  orders.forEach(order => {
30
+ if (order.status === 'completed') {
31
+ total += order.total;
32
+ // Simple day mapping (last 7 days logic would be better but this is for demo)
33
+ const day = new Date(order.timestamp).getDay(); // 0-6
34
+ const index = (day + 6) % 7; // Map Mon-Sun
35
+ weeklySales[index] += order.total;
36
+
37
+ order.items.forEach(item => {
38
+ productCounts[item.name] = (productCounts[item.name] || 0) + item.qty;
39
+ });
40
+ }
 
 
 
 
 
 
 
 
 
41
  });
42
 
43
  setSalesData(weeklySales);
 
44
  setStats({
45
+ totalSales: total,
46
+ orderCount: orders.filter(o => o.status === 'completed').length,
47
+ avgTicket: total / (orders.filter(o => o.status === 'completed').length || 1)
48
  });
49
 
50
  const sortedProducts = Object.entries(productCounts)
51
+ .sort((a,b) => b[1] - a[1])
52
+ .slice(0, 5);
53
+
54
  setTopProducts({
55
+ labels: sortedProducts.map(p => p[0]),
56
+ data: sortedProducts.map(p => p[1])
 
 
 
 
 
 
 
57
  });
58
  }
59
  });
60
  }, []);
61
 
62
+ const lineData = {
63
+ labels: ['Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb', 'Dom'],
64
+ datasets: [{
65
+ label: 'Ventas ($)',
66
+ data: salesData,
67
+ borderColor: '#FF5A5F',
68
+ backgroundColor: 'rgba(255, 90, 95, 0.2)',
69
+ fill: true,
70
+ tension: 0.4
71
+ }]
72
+ };
73
+
74
+ const barData = {
75
+ labels: topProducts.labels,
76
+ datasets: [{
77
+ label: 'Unidades Vendidas',
78
+ data: topProducts.data,
79
+ backgroundColor: 'rgba(0, 166, 153, 0.7)',
80
+ borderRadius: 8
81
+ }]
82
+ };
83
+
84
+ const options = {
85
  responsive: true,
86
  maintainAspectRatio: false,
87
+ plugins: { legend: { labels: { color: '#9595a8' } } },
88
  scales: {
89
+ x: { ticks: { color: '#9595a8' }, grid: { color: 'rgba(255,255,255,0.05)' } },
90
+ y: { ticks: { color: '#9595a8' }, grid: { color: 'rgba(255,255,255,0.05)' } }
91
  }
92
  };
93
 
94
  return (
95
+ <div className="animate-fade-in">
96
  <header style={{ marginBottom: '2.5rem' }}>
97
+ <h2 className="text-gradient" style={{ fontSize: '2rem', fontWeight: '800' }}>Análisis de Negocio</h2>
98
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '1.5rem', marginTop: '1.5rem' }}>
99
+ <div className="glass-card" style={{ padding: '1.25rem' }}>
100
+ <div style={{ display: 'flex', justifyContent: 'space-between', color: 'var(--text-muted)', fontSize: '0.8rem' }}>Ventas Totales <TrendingUp size={16} /></div>
101
+ <div style={{ fontSize: '1.5rem', fontWeight: '800', marginTop: '0.5rem' }}>${stats.totalSales.toFixed(2)}</div>
102
+ </div>
103
+ <div className="glass-card" style={{ padding: '1.25rem' }}>
104
+ <div style={{ display: 'flex', justifyContent: 'space-between', color: 'var(--text-muted)', fontSize: '0.8rem' }}>Pedidos <Package size={16} /></div>
105
+ <div style={{ fontSize: '1.5rem', fontWeight: '800', marginTop: '0.5rem' }}>{stats.orderCount}</div>
106
+ </div>
107
+ <div className="glass-card" style={{ padding: '1.25rem' }}>
108
+ <div style={{ display: 'flex', justifyContent: 'space-between', color: 'var(--text-muted)', fontSize: '0.8rem' }}>Ticket Promedio <Calendar size={16} /></div>
109
+ <div style={{ fontSize: '1.5rem', fontWeight: '800', marginTop: '0.5rem' }}>${stats.avgTicket.toFixed(2)}</div>
110
+ </div>
111
  </div>
112
  </header>
113
 
114
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(400px, 1fr))', gap: '2rem' }}>
115
+ <div className="glass-card" style={{ height: '350px', display: 'flex', flexDirection: 'column' }}>
116
+ <h3 style={{ marginBottom: '1.5rem', fontSize: '1rem', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
117
+ Ventas por Día de la Semana
118
+ </h3>
119
+ <div style={{ flex: 1, position: 'relative' }}>
120
+ <Line data={lineData} options={options} />
121
+ </div>
122
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
+ <div className="glass-card" style={{ height: '350px', display: 'flex', flexDirection: 'column' }}>
125
+ <h3 style={{ marginBottom: '1.5rem', fontSize: '1rem' }}>Productos Estrella</h3>
126
+ <div style={{ flex: 1, position: 'relative' }}>
127
+ <Bar data={barData} options={options} />
128
+ </div>
129
+ </div>
 
 
130
  </div>
131
  </div>
132
  );
frontend/src/components/admin/TableManager.jsx CHANGED
@@ -72,7 +72,7 @@ export default function TableManager() {
72
  <h4 style={{ fontSize: '0.8rem', color: 'var(--text-muted)', marginBottom: '1rem', borderTop: '1px solid var(--border-subtle)', paddingTop: '1.5rem' }}>Listado</h4>
73
  <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', maxHeight: '300px', overflowY: 'auto' }}>
74
  {tables.map(t => (
75
- <div key={t.id} style={{ display: 'flex', justifyContent: 'space-between', padding: '0.6rem 1rem', background: 'rgba(0,0,0,0.03)', borderRadius: '8px', alignItems: 'center' }}>
76
  <span style={{ fontWeight: '700' }}>Mesa {t.number}</span>
77
  <div style={{ display: 'flex', gap: '0.5rem' }}>
78
  <span style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>{t.capacity} px</span>
@@ -85,45 +85,28 @@ export default function TableManager() {
85
  </aside>
86
 
87
  {/* Visual Map Area */}
88
- <section className="glass-panel" style={{ height: '600px', position: 'relative', overflow: 'hidden', background: 'var(--bg-card)', backgroundSize: '30px 30px', border: '1px solid var(--border-subtle)', backgroundImage: 'radial-gradient(circle, rgba(0,0,0,0.05) 1px, transparent 1px)' }}>
89
- <div style={{ position: 'absolute', top: '1.5rem', left: '1.5rem', display: 'flex', gap: '1.5rem', fontSize: '0.75rem' }}>
90
- <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}><div style={{ width: 12, height: 12, background: 'var(--success)', borderRadius: '2px' }} /> Libre</div>
91
- <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}><div style={{ width: 12, height: 12, background: 'var(--primary)', borderRadius: '2px' }} /> Ocupado</div>
92
- <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}><div style={{ width: 12, height: 12, background: '#fcc419', borderRadius: '2px' }} /> Reservado</div>
93
  </div>
94
 
95
- <div style={{ width: '100%', height: '100%', padding: '5rem 3rem', display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(110px, 1fr))', gap: '3rem' }}>
96
- {tables.map(t => {
97
- const statusColor = t.status === 'occupied' ? 'var(--primary)' : t.status === 'reserved' ? '#fcc419' : 'var(--success)';
98
- return (
99
- <div
100
- key={t.id}
101
- onClick={async () => {
102
- const nextStatus = t.status === 'available' ? 'occupied' : t.status === 'occupied' ? 'reserved' : 'available';
103
- await update(ref(db, `config/tables/${t.id}`), { status: nextStatus });
104
- }}
105
- className="animate-scale-in"
106
- style={{
107
- width: '100px', height: '100px',
108
- background: t.status === 'available' ? 'rgba(76,217,100,0.05)' : 'rgba(0,0,0,0.02)',
109
- border: `2px solid ${statusColor}`,
110
- borderRadius: t.capacity > 4 ? '16px' : '50%',
111
- display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
112
- boxShadow: t.status === 'occupied' ? '0 0 20px rgba(255,90,95,0.05)' : 'none',
113
- cursor: 'pointer', transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
114
- position: 'relative'
115
- }}
116
- >
117
- <span style={{ fontSize: '1.4rem', fontWeight: '900', color: statusColor }}>{t.number}</span>
118
- <span style={{ fontSize: '0.65rem', opacity: 0.6, color: 'var(--text-muted)' }}>{t.capacity} p</span>
119
- {t.status !== 'available' && (
120
- <div style={{ position: 'absolute', top: '-5px', right: '-5px', width: 12, height: 12, borderRadius: '50%', background: statusColor, border: '2px solid var(--bg-card)' }} />
121
- )}
122
- </div>
123
- );
124
- })}
125
  {tables.length === 0 && (
126
- <div style={{ gridColumn: '1 / -1', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', opacity: 0.1 }}>
127
  <Grid size={80} />
128
  </div>
129
  )}
@@ -135,5 +118,5 @@ export default function TableManager() {
135
  );
136
  }
137
 
138
- const inputStyle = { width: '100%', padding: '0.8rem', borderRadius: '8px', background: 'rgba(0,0,0,0.03)', border: '1px solid var(--border-subtle)', color: 'var(--text-main)', outline: 'none' };
139
  const labelStyle = { display: 'block', fontSize: '0.8rem', color: 'var(--text-muted)', marginBottom: '0.5rem' };
 
72
  <h4 style={{ fontSize: '0.8rem', color: 'var(--text-muted)', marginBottom: '1rem', borderTop: '1px solid var(--border-subtle)', paddingTop: '1.5rem' }}>Listado</h4>
73
  <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', maxHeight: '300px', overflowY: 'auto' }}>
74
  {tables.map(t => (
75
+ <div key={t.id} style={{ display: 'flex', justifyContent: 'space-between', padding: '0.6rem 1rem', background: 'rgba(255,255,255,0.03)', borderRadius: '8px', alignItems: 'center' }}>
76
  <span style={{ fontWeight: '700' }}>Mesa {t.number}</span>
77
  <div style={{ display: 'flex', gap: '0.5rem' }}>
78
  <span style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>{t.capacity} px</span>
 
85
  </aside>
86
 
87
  {/* Visual Map Area */}
88
+ <section className="glass-panel" style={{ height: '600px', position: 'relative', overflow: 'hidden', background: 'radial-gradient(circle, rgba(255,255,255,0.02) 1px, transparent 1px)', backgroundSize: '30px 30px', border: '1px solid var(--border-subtle)' }}>
89
+ <div style={{ position: 'absolute', top: '1rem', left: '1rem', display: 'flex', alignItems: 'center', gap: '0.5rem', color: 'var(--text-muted)', fontSize: '0.8rem' }}>
90
+ <MousePointer2 size={14} /> Arrastra para posicionar (Modo Edición)
 
 
91
  </div>
92
 
93
+ <div style={{ width: '100%', height: '100%', padding: '3rem', display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(100px, 1fr))', gap: '2rem' }}>
94
+ {tables.map(t => (
95
+ <div key={t.id} style={{
96
+ width: '90px', height: '90px',
97
+ background: 'rgba(255,90,95,0.05)',
98
+ border: '2px solid var(--primary)',
99
+ borderRadius: t.capacity > 4 ? '12px' : '50%',
100
+ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
101
+ boxShadow: '0 0 15px rgba(255,90,95,0.1)',
102
+ cursor: 'move'
103
+ }}>
104
+ <span style={{ fontSize: '1.2rem', fontWeight: '900', color: 'var(--primary)' }}>{t.number}</span>
105
+ <span style={{ fontSize: '0.65rem', opacity: 0.6 }}>{t.capacity} cap</span>
106
+ </div>
107
+ ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  {tables.length === 0 && (
109
+ <div style={{ gridColumn: '1 / -1', height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', opacity: 0.2 }}>
110
  <Grid size={80} />
111
  </div>
112
  )}
 
118
  );
119
  }
120
 
121
+ const inputStyle = { width: '100%', padding: '0.8rem', borderRadius: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid var(--border-subtle)', color: '#fff', outline: 'none' };
122
  const labelStyle = { display: 'block', fontSize: '0.8rem', color: 'var(--text-muted)', marginBottom: '0.5rem' };
frontend/src/components/admin/UserManager.jsx CHANGED
@@ -44,7 +44,7 @@ export default function UserManager() {
44
  <h3 style={{ fontSize: '1.1rem', fontWeight: '700' }}>{emp.name || 'Sin Nombre'}</h3>
45
  <p style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>{emp.email}</p>
46
  </div>
47
- <div style={{ background: 'rgba(0,0,0,0.03)', padding: '0.5rem', borderRadius: '50%' }}>
48
  <Shield size={20} style={{ color: roleColors[emp.role] }} />
49
  </div>
50
  </div>
@@ -54,7 +54,7 @@ export default function UserManager() {
54
  <select
55
  value={emp.role}
56
  onChange={(e) => handleUpdateRole(emp.uid, e.target.value)}
57
- style={{...inputStyle, background: 'rgba(0,0,0,0.03)', border: '1px solid var(--border-subtle)'}}
58
  >
59
  <option value="admin">Administrador (Total)</option>
60
  <option value="mesero">Mesero (POS)</option>
@@ -75,7 +75,7 @@ export default function UserManager() {
75
  ))}
76
 
77
  <div className="glass-card" style={{ border: '2px dashed var(--border-subtle)', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', textAlign: 'center', padding: '2rem', gap: '1rem' }}>
78
- <div style={{ background: 'rgba(0,0,0,0.03)', padding: '1rem', borderRadius: '50%', color: 'var(--text-muted)' }}>
79
  <UserPlus size={32} />
80
  </div>
81
  <h3 style={{ fontSize: '1rem', fontWeight: '700' }}>Registrar Nuevo Empleado</h3>
@@ -105,5 +105,5 @@ export default function UserManager() {
105
  );
106
  }
107
 
108
- const inputStyle = { width: '100%', padding: '0.8rem', borderRadius: '8px', background: 'rgba(0,0,0,0.03)', border: '1px solid var(--border-subtle)', color: 'var(--text-main)', outline: 'none' };
109
  const labelStyle = { display: 'block', fontSize: '0.8rem', color: 'var(--text-muted)', marginBottom: '0.5rem' };
 
44
  <h3 style={{ fontSize: '1.1rem', fontWeight: '700' }}>{emp.name || 'Sin Nombre'}</h3>
45
  <p style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>{emp.email}</p>
46
  </div>
47
+ <div style={{ background: 'rgba(255,255,255,0.05)', padding: '0.5rem', borderRadius: '50%' }}>
48
  <Shield size={20} style={{ color: roleColors[emp.role] }} />
49
  </div>
50
  </div>
 
54
  <select
55
  value={emp.role}
56
  onChange={(e) => handleUpdateRole(emp.uid, e.target.value)}
57
+ style={{...inputStyle, background: 'rgba(0,0,0,0.2)', border: '1px solid var(--border-subtle)'}}
58
  >
59
  <option value="admin">Administrador (Total)</option>
60
  <option value="mesero">Mesero (POS)</option>
 
75
  ))}
76
 
77
  <div className="glass-card" style={{ border: '2px dashed var(--border-subtle)', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', textAlign: 'center', padding: '2rem', gap: '1rem' }}>
78
+ <div style={{ background: 'rgba(255,255,255,0.05)', padding: '1rem', borderRadius: '50%', color: 'var(--text-muted)' }}>
79
  <UserPlus size={32} />
80
  </div>
81
  <h3 style={{ fontSize: '1rem', fontWeight: '700' }}>Registrar Nuevo Empleado</h3>
 
105
  );
106
  }
107
 
108
+ const inputStyle = { width: '100%', padding: '0.8rem', borderRadius: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid var(--border-subtle)', color: '#fff', outline: 'none' };
109
  const labelStyle = { display: 'block', fontSize: '0.8rem', color: 'var(--text-muted)', marginBottom: '0.5rem' };
frontend/src/index.css CHANGED
@@ -1,24 +1,24 @@
1
  @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap');
2
 
3
  :root {
4
- /* Premium Light Theme Core Palette */
5
- --bg-base: #f8f9fa;
6
- --bg-surface: #ffffff;
7
- --bg-glass: rgba(255, 255, 255, 0.85);
8
- --bg-glass-hover: rgba(245, 245, 250, 0.9);
9
- --bg-card: #ffffff;
10
 
11
  /* Brand Accents */
12
  --primary: #FF5A5F;
13
  --primary-hover: #ff4046;
14
- --primary-glow: rgba(255, 90, 95, 0.15);
15
  --secondary: #00A699;
16
  --secondary-hover: #008f84;
17
 
18
  /* Text */
19
- --text-main: #1a1a20;
20
- --text-muted: #6b6b7b;
21
- --text-disabled: #a5a5b8;
22
 
23
  /* Status Colors */
24
  --success: #20C997;
@@ -27,12 +27,12 @@
27
  --info: #32ADE6;
28
 
29
  /* Borders & Shadows */
30
- --border-subtle: rgba(0, 0, 0, 0.08);
31
- --border-focus: rgba(255, 90, 95, 0.3);
32
- --shadow-sm: 0 4px 12px rgba(0, 0, 0, 0.05);
33
- --shadow-md: 0 12px 30px rgba(0, 0, 0, 0.08);
34
  --shadow-glow: 0 0 20px var(--primary-glow);
35
- --glass-blur: blur(20px);
36
  --glass-border: 1px solid var(--border-subtle);
37
 
38
  /* Transitions */
@@ -61,8 +61,8 @@ body {
61
  overflow-x: hidden;
62
  -webkit-font-smoothing: antialiased;
63
  background-image:
64
- radial-gradient(circle at 10% 20%, rgba(255, 90, 95, 0.02) 0%, transparent 30%),
65
- radial-gradient(circle at 90% 80%, rgba(0, 166, 153, 0.02) 0%, transparent 30%);
66
  background-attachment: fixed;
67
  }
68
 
@@ -98,14 +98,13 @@ button {
98
  border-radius: var(--radius-md);
99
  transition: all var(--transition-normal);
100
  padding: 1.5rem;
101
- box-shadow: var(--shadow-sm);
102
  }
103
 
104
  .glass-card:hover {
105
  transform: translateY(-4px);
106
  background: var(--bg-glass-hover);
107
- border-color: rgba(0, 0, 0, 0.1);
108
- box-shadow: 0 12px 30px rgba(0, 0, 0, 0.06);
109
  }
110
 
111
  /* Premium Buttons */
@@ -135,8 +134,8 @@ button {
135
  }
136
 
137
  .btn-glass {
138
- background: rgba(0,0,0,0.03);
139
- border: 1px solid rgba(0,0,0,0.08);
140
  color: var(--text-main);
141
  padding: 0.75rem 1.75rem;
142
  border-radius: var(--radius-pill);
@@ -146,13 +145,13 @@ button {
146
  }
147
 
148
  .btn-glass:hover {
149
- background: rgba(0,0,0,0.06);
150
- border-color: rgba(0,0,0,0.12);
151
  }
152
 
153
  /* Typography styles */
154
  .text-gradient {
155
- background: linear-gradient(135deg, #1a1a20 0%, #6b6b7b 100%);
156
  -webkit-background-clip: text;
157
  -webkit-text-fill-color: transparent;
158
  background-clip: text;
 
1
  @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap');
2
 
3
  :root {
4
+ /* Premium Dark Theme Core Palette */
5
+ --bg-base: #0a0a0d;
6
+ --bg-surface: #14141a;
7
+ --bg-glass: rgba(20, 20, 26, 0.6);
8
+ --bg-glass-hover: rgba(40, 40, 50, 0.5);
9
+ --bg-card: rgba(30, 30, 40, 0.4);
10
 
11
  /* Brand Accents */
12
  --primary: #FF5A5F;
13
  --primary-hover: #ff4046;
14
+ --primary-glow: rgba(255, 90, 95, 0.3);
15
  --secondary: #00A699;
16
  --secondary-hover: #008f84;
17
 
18
  /* Text */
19
+ --text-main: #fcfcfd;
20
+ --text-muted: #9595a8;
21
+ --text-disabled: #555566;
22
 
23
  /* Status Colors */
24
  --success: #20C997;
 
27
  --info: #32ADE6;
28
 
29
  /* Borders & Shadows */
30
+ --border-subtle: rgba(255, 255, 255, 0.08);
31
+ --border-focus: rgba(255, 90, 95, 0.5);
32
+ --shadow-sm: 0 4px 12px rgba(0, 0, 0, 0.2);
33
+ --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.4);
34
  --shadow-glow: 0 0 20px var(--primary-glow);
35
+ --glass-blur: blur(16px);
36
  --glass-border: 1px solid var(--border-subtle);
37
 
38
  /* Transitions */
 
61
  overflow-x: hidden;
62
  -webkit-font-smoothing: antialiased;
63
  background-image:
64
+ radial-gradient(circle at 10% 20%, rgba(255, 90, 95, 0.03) 0%, transparent 40%),
65
+ radial-gradient(circle at 90% 80%, rgba(0, 166, 153, 0.03) 0%, transparent 40%);
66
  background-attachment: fixed;
67
  }
68
 
 
98
  border-radius: var(--radius-md);
99
  transition: all var(--transition-normal);
100
  padding: 1.5rem;
 
101
  }
102
 
103
  .glass-card:hover {
104
  transform: translateY(-4px);
105
  background: var(--bg-glass-hover);
106
+ border-color: rgba(255, 255, 255, 0.15);
107
+ box-shadow: 0 12px 30px rgba(0, 0, 0, 0.3);
108
  }
109
 
110
  /* Premium Buttons */
 
134
  }
135
 
136
  .btn-glass {
137
+ background: rgba(255,255,255,0.05);
138
+ border: 1px solid rgba(255,255,255,0.1);
139
  color: var(--text-main);
140
  padding: 0.75rem 1.75rem;
141
  border-radius: var(--radius-pill);
 
145
  }
146
 
147
  .btn-glass:hover {
148
+ background: rgba(255,255,255,0.1);
149
+ border-color: rgba(255,255,255,0.2);
150
  }
151
 
152
  /* Typography styles */
153
  .text-gradient {
154
+ background: linear-gradient(135deg, #fff 0%, #a5a5b8 100%);
155
  -webkit-background-clip: text;
156
  -webkit-text-fill-color: transparent;
157
  background-clip: text;
frontend/src/pages/AdminDashboard.jsx CHANGED
@@ -1,7 +1,7 @@
1
  import React, { useState } from 'react';
2
  import { useAuth } from '../context/AuthContext';
3
  import { useNavigate } from 'react-router-dom';
4
- import { LayoutDashboard, Utensils, Box, PieChart, LogOut, DollarSign, UtensilsCrossed, Users, Settings, Save, ShieldCheck, Truck } from 'lucide-react';
5
  import DashboardOverview from '../components/admin/DashboardOverview';
6
  import MenuEditor from '../components/admin/MenuEditor';
7
  import InventoryControl from '../components/admin/InventoryControl';
@@ -9,8 +9,6 @@ import Reports from '../components/admin/Reports';
9
  import FinanceManager from '../components/admin/FinanceManager';
10
  import UserManager from '../components/admin/UserManager';
11
  import TableManager from '../components/admin/TableManager';
12
- import CRMManager from '../components/admin/CRMManager';
13
- import SettingsPlus from '../components/admin/SettingsPlus';
14
 
15
  export default function AdminDashboard() {
16
  const { logout, currentUser } = useAuth();
@@ -27,11 +25,9 @@ export default function AdminDashboard() {
27
  { id: 'menu', label: 'Menú & Precios', icon: <Utensils size={20} /> },
28
  { id: 'inventory', label: 'Control de Stock', icon: <Box size={20} /> },
29
  { id: 'reports', label: 'Reportes y Analítica', icon: <PieChart size={20} /> },
30
- { id: 'cash', label: 'Finanzas & Caja', icon: <DollarSign size={20} /> },
31
- { id: 'users', label: 'Gestión de Personal', icon: <Users size={20} /> },
32
- { id: 'tables', label: 'Arquitectura Salón', icon: <LayoutDashboard size={20} /> },
33
- { id: 'crm', label: 'CRM & Operaciones', icon: <Truck size={20} /> },
34
- { id: 'settings', label: 'Configuración ERP', icon: <Settings size={20} /> },
35
  { id: 'kitchen', label: 'Pantalla Cocina', icon: <UtensilsCrossed size={20} />, action: () => window.open('/kitchen', '_blank') },
36
  { id: 'menu_public', label: 'Carta Digital', icon: <Utensils size={20} />, action: () => window.open('/menu', '_blank') }
37
  ];
@@ -40,9 +36,9 @@ export default function AdminDashboard() {
40
  <div className="app-container">
41
  {/* Sidebar */}
42
  <aside className="glass-panel" style={{ width: '280px', borderRadius: '0', borderRight: '1px solid var(--border-subtle)', display: 'flex', flexDirection: 'column' }}>
43
- <div style={{ padding: '2rem 1.5rem', borderBottom: '1px solid var(--border-subtle)' }}>
44
- <h1 className="text-gradient" style={{ fontSize: '1.5rem', fontWeight: '800' }}>RESTO PLUS</h1>
45
- <p style={{ color: 'var(--text-muted)', fontSize: '0.85rem', marginTop: '0.25rem' }}>Sistema ERP Inteligente</p>
46
  </div>
47
 
48
  <nav style={{ flex: 1, padding: '1.5rem 1rem', display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
@@ -85,8 +81,6 @@ export default function AdminDashboard() {
85
  {activeTab === 'cash' && <FinanceManager />}
86
  {activeTab === 'users' && <UserManager />}
87
  {activeTab === 'tables' && <TableManager />}
88
- {activeTab === 'crm' && <CRMManager />}
89
- {activeTab === 'settings' && <SettingsPlus />}
90
  </div>
91
  </main>
92
  </div>
 
1
  import React, { useState } from 'react';
2
  import { useAuth } from '../context/AuthContext';
3
  import { useNavigate } from 'react-router-dom';
4
+ import { LayoutDashboard, Utensils, Box, PieChart, LogOut, DollarSign, UtensilsCrossed, Users } from 'lucide-react';
5
  import DashboardOverview from '../components/admin/DashboardOverview';
6
  import MenuEditor from '../components/admin/MenuEditor';
7
  import InventoryControl from '../components/admin/InventoryControl';
 
9
  import FinanceManager from '../components/admin/FinanceManager';
10
  import UserManager from '../components/admin/UserManager';
11
  import TableManager from '../components/admin/TableManager';
 
 
12
 
13
  export default function AdminDashboard() {
14
  const { logout, currentUser } = useAuth();
 
25
  { id: 'menu', label: 'Menú & Precios', icon: <Utensils size={20} /> },
26
  { id: 'inventory', label: 'Control de Stock', icon: <Box size={20} /> },
27
  { id: 'reports', label: 'Reportes y Analítica', icon: <PieChart size={20} /> },
28
+ { id: 'cash', label: 'Caja y Finanzas', icon: <DollarSign size={20} /> },
29
+ { id: 'users', label: 'Equipo y Roles', icon: <Users size={20} /> },
30
+ { id: 'tables', label: 'Diseño Salón', icon: <LayoutDashboard size={20} /> },
 
 
31
  { id: 'kitchen', label: 'Pantalla Cocina', icon: <UtensilsCrossed size={20} />, action: () => window.open('/kitchen', '_blank') },
32
  { id: 'menu_public', label: 'Carta Digital', icon: <Utensils size={20} />, action: () => window.open('/menu', '_blank') }
33
  ];
 
36
  <div className="app-container">
37
  {/* Sidebar */}
38
  <aside className="glass-panel" style={{ width: '280px', borderRadius: '0', borderRight: '1px solid var(--border-subtle)', display: 'flex', flexDirection: 'column' }}>
39
+ <div style={{ padding: '2rem 1.5rem', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
40
+ <h1 className="text-gradient" style={{ fontSize: '1.5rem', fontWeight: '800' }}>Admin OS</h1>
41
+ <p style={{ color: 'var(--text-muted)', fontSize: '0.85rem', marginTop: '0.25rem' }}>{currentUser?.email}</p>
42
  </div>
43
 
44
  <nav style={{ flex: 1, padding: '1.5rem 1rem', display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
 
81
  {activeTab === 'cash' && <FinanceManager />}
82
  {activeTab === 'users' && <UserManager />}
83
  {activeTab === 'tables' && <TableManager />}
 
 
84
  </div>
85
  </main>
86
  </div>
frontend/src/pages/CustomerMenu.jsx CHANGED
@@ -8,7 +8,7 @@ export default function CustomerMenu() {
8
  const [menuItems, setMenuItems] = useState([]);
9
  const [categories, setCategories] = useState([]);
10
  const [activeCategory, setActiveCategory] = useState('Todos');
11
- const [menuTheme, setMenuTheme] = useState('light'); // 'dark' or 'light'
12
  const navigate = useNavigate();
13
 
14
  useEffect(() => {
 
8
  const [menuItems, setMenuItems] = useState([]);
9
  const [categories, setCategories] = useState([]);
10
  const [activeCategory, setActiveCategory] = useState('Todos');
11
+ const [menuTheme, setMenuTheme] = useState('dark'); // 'dark' or 'light'
12
  const navigate = useNavigate();
13
 
14
  useEffect(() => {
frontend/src/pages/KitchenView.jsx CHANGED
@@ -75,8 +75,7 @@ export default function KitchenView() {
75
  <div key={order.id} className="glass-card animate-fade-in" style={{
76
  padding: '1.5rem',
77
  borderLeft: order.status === 'preparing' ? '4px solid var(--warning)' : '4px solid var(--primary)',
78
- background: order.status === 'preparing' ? 'rgba(255,160,0,0.05)' : 'var(--bg-card)',
79
- boxShadow: 'var(--shadow-sm)'
80
  }}>
81
  <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '1.25rem', alignItems: 'center' }}>
82
  <span style={{ fontSize: '1.2rem', fontWeight: '800', color: order.status === 'preparing' ? 'var(--warning)' : 'var(--primary)' }}>
@@ -89,7 +88,7 @@ export default function KitchenView() {
89
 
90
  <div style={{ marginBottom: '1.5rem', minHeight: '80px' }}>
91
  {order.items.map((item, idx) => (
92
- <div key={idx} style={{ display: 'flex', justifyContent: 'space-between', padding: '0.5rem 0', borderBottom: '1px solid var(--border-subtle)' }}>
93
  <span style={{ fontWeight: '600' }}><span style={{ color: 'var(--primary)', fontWeight: '800' }}>{item.qty}</span> {item.name}</span>
94
  </div>
95
  ))}
 
75
  <div key={order.id} className="glass-card animate-fade-in" style={{
76
  padding: '1.5rem',
77
  borderLeft: order.status === 'preparing' ? '4px solid var(--warning)' : '4px solid var(--primary)',
78
+ background: order.status === 'preparing' ? 'rgba(255,200,0,0.02)' : 'var(--bg-card)'
 
79
  }}>
80
  <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '1.25rem', alignItems: 'center' }}>
81
  <span style={{ fontSize: '1.2rem', fontWeight: '800', color: order.status === 'preparing' ? 'var(--warning)' : 'var(--primary)' }}>
 
88
 
89
  <div style={{ marginBottom: '1.5rem', minHeight: '80px' }}>
90
  {order.items.map((item, idx) => (
91
+ <div key={idx} style={{ display: 'flex', justifyContent: 'space-between', padding: '0.5rem 0', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
92
  <span style={{ fontWeight: '600' }}><span style={{ color: 'var(--primary)', fontWeight: '800' }}>{item.qty}</span> {item.name}</span>
93
  </div>
94
  ))}
frontend/src/pages/Login.jsx CHANGED
@@ -50,7 +50,7 @@ export default function Login() {
50
 
51
  return (
52
  <div className="app-container" style={{
53
- background: 'var(--bg-base)',
54
  justifyContent: 'center', alignItems: 'center', minHeight: '100vh', padding: '20px'
55
  }}>
56
  {/* Icono de Ver Menú Público */}
@@ -76,7 +76,7 @@ export default function Login() {
76
 
77
  {/* Lado Izquierdo: Selección de Rol */}
78
  <div style={{
79
- width: '35%', background: 'rgba(0,0,0,0.02)',
80
  borderRight: '1px solid var(--border-subtle)', padding: '40px 30px',
81
  display: 'flex', flexDirection: 'column', gap: '20px'
82
  }}>
@@ -150,14 +150,14 @@ export default function Login() {
150
  <label style={{ display: 'block', marginBottom: '8px', fontSize: '0.9rem', color: 'var(--text-muted)' }}>Correo Electrónico</label>
151
  <input
152
  type="email" required value={email} onChange={(e) => setEmail(e.target.value)}
153
- style={{ width: '100%', padding: '14px', borderRadius: '10px', background: 'rgba(0,0,0,0.03)', border: '1px solid var(--border-subtle)', color: 'var(--text-main)' }}
154
  />
155
  </div>
156
  <div>
157
  <label style={{ display: 'block', marginBottom: '8px', fontSize: '0.9rem', color: 'var(--text-muted)' }}>Contraseña</label>
158
  <input
159
  type="password" required value={password} onChange={(e) => setPassword(e.target.value)}
160
- style={{ width: '100%', padding: '14px', borderRadius: '10px', background: 'rgba(0,0,0,0.03)', border: '1px solid var(--border-subtle)', color: 'var(--text-main)' }}
161
  />
162
  </div>
163
  <button type="submit" className="btn-primary" style={{ height: '55px', fontSize: '1.1rem', marginTop: '10px' }} disabled={loading}>
 
50
 
51
  return (
52
  <div className="app-container" style={{
53
+ background: 'radial-gradient(circle at top right, #1a1a1a, #0a0a0a)',
54
  justifyContent: 'center', alignItems: 'center', minHeight: '100vh', padding: '20px'
55
  }}>
56
  {/* Icono de Ver Menú Público */}
 
76
 
77
  {/* Lado Izquierdo: Selección de Rol */}
78
  <div style={{
79
+ width: '35%', background: 'rgba(255,255,255,0.03)',
80
  borderRight: '1px solid var(--border-subtle)', padding: '40px 30px',
81
  display: 'flex', flexDirection: 'column', gap: '20px'
82
  }}>
 
150
  <label style={{ display: 'block', marginBottom: '8px', fontSize: '0.9rem', color: 'var(--text-muted)' }}>Correo Electrónico</label>
151
  <input
152
  type="email" required value={email} onChange={(e) => setEmail(e.target.value)}
153
+ style={{ width: '100%', padding: '14px', borderRadius: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid var(--border-subtle)', color: '#fff' }}
154
  />
155
  </div>
156
  <div>
157
  <label style={{ display: 'block', marginBottom: '8px', fontSize: '0.9rem', color: 'var(--text-muted)' }}>Contraseña</label>
158
  <input
159
  type="password" required value={password} onChange={(e) => setPassword(e.target.value)}
160
+ style={{ width: '100%', padding: '14px', borderRadius: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid var(--border-subtle)', color: '#fff' }}
161
  />
162
  </div>
163
  <button type="submit" className="btn-primary" style={{ height: '55px', fontSize: '1.1rem', marginTop: '10px' }} disabled={loading}>
frontend/src/pages/WaiterPOS.jsx CHANGED
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
2
  import { useAuth } from '../context/AuthContext';
3
  import { useNavigate } from 'react-router-dom';
4
  import { db } from '../firebase/config';
5
- import { ref, onValue, push, set, update } from 'firebase/database';
6
  import { LogOut, Coffee, Send, Trash2, ShoppingBag, Truck, Receipt, CheckCircle, XCircle, CreditCard, Banknote, QrCode } from 'lucide-react';
7
 
8
  export default function WaiterPOS() {
@@ -16,7 +16,6 @@ export default function WaiterPOS() {
16
  const [tip, setTip] = useState(0);
17
  const [paymentMethod, setPaymentMethod] = useState('Efectivo');
18
  const [isCheckoutOpen, setIsCheckoutOpen] = useState(false);
19
- const [customerData, setCustomerData] = useState({ name: '', email: '', phone: '', address: '' });
20
 
21
  // Fetch Menu
22
  useEffect(() => {
@@ -61,9 +60,6 @@ export default function WaiterPOS() {
61
  table: orderType === 'Mesa' ? activeTable : orderType,
62
  type: orderType,
63
  waiter: currentUser?.email,
64
- customerName: customerData.name || 'Consumidor Final',
65
- customerEmail: customerData.email || 'anonimo@rest.os',
66
- address: customerData.address || '',
67
  items: cart,
68
  subtotal,
69
  discount,
@@ -79,7 +75,6 @@ export default function WaiterPOS() {
79
  if (!isCheckoutOpen) {
80
  setCart([]);
81
  setActiveTable(null);
82
- setCustomerData({ name: '', email: '', phone: '', address: '' });
83
  alert('¡Comanda enviada a cocina exitosamente!');
84
  } else {
85
  // Finalizar y descontar stock
@@ -87,16 +82,20 @@ export default function WaiterPOS() {
87
 
88
  // Lógica de Descuento de Inventario
89
  for (const item of cart) {
 
 
90
  const productRef = ref(db, `menu/${item.id}`);
91
  onValue(productRef, async (snapshot) => {
92
  const product = snapshot.val();
93
  if (product && product.ingredients) {
94
  for (const ing of product.ingredients) {
 
 
95
  onValue(ref(db, `inventory/${ing.id}`), async (invSnap) => {
96
  const invData = invSnap.val();
97
  if (invData) {
98
  const newQty = invData.quantity - (item.qty * ing.qty);
99
- await update(ref(db, `inventory/${ing.id}`), { quantity: newQty });
100
  }
101
  }, { onlyOnce: true });
102
  }
@@ -107,7 +106,6 @@ export default function WaiterPOS() {
107
  setIsCheckoutOpen(false);
108
  setCart([]);
109
  setActiveTable(null);
110
- setCustomerData({ name: '', email: '', phone: '', address: '' });
111
  alert('¡Venta realizada y stock actualizado!');
112
  }
113
  };
@@ -141,7 +139,7 @@ export default function WaiterPOS() {
141
  <div className="app-container" style={{ display: 'flex', flexDirection: 'column', height: '100vh', overflow: 'hidden' }}>
142
  {/* Navbar */}
143
  <header className="glass-panel" style={{ padding: '1rem 2rem', display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderRadius: '0', borderBottom: '1px solid var(--border-subtle)', background: 'var(--bg-base)' }}>
144
- <h1 className="text-gradient" style={{ fontSize: '1.5rem', fontWeight: '800' }}>RESTO PLUS POS</h1>
145
  <div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
146
  <span style={{ color: 'var(--text-muted)', fontSize: '0.9rem' }}>{currentUser?.email}</span>
147
  <button className="btn-glass" onClick={() => window.open('/menu', '_blank')} style={{ padding: '0.5rem 1rem' }}>Carta Digital</button>
@@ -176,35 +174,6 @@ export default function WaiterPOS() {
176
  </div>
177
  </div>
178
 
179
- <div className="glass-card" style={{ padding: '1.25rem', marginBottom: '1.5rem', display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: '1rem' }}>
180
- <div style={{ gridColumn: orderType === 'Delivery' ? 'span 2' : 'auto' }}>
181
- <label style={{ fontSize: '0.7rem', color: 'var(--text-muted)' }}>Cliente / Mesa</label>
182
- <input
183
- type="text" placeholder="Nombre del Cliente"
184
- value={customerData.name} onChange={e => setCustomerData({...customerData, name: e.target.value})}
185
- style={{ ...inputStyle, padding: '0.6rem' }}
186
- />
187
- </div>
188
- {orderType === 'Delivery' && (
189
- <div style={{ gridColumn: 'span 2' }}>
190
- <label style={{ fontSize: '0.7rem', color: 'var(--text-muted)' }}>Dirección de Entrega</label>
191
- <input
192
- type="text" placeholder="Calle, Número, Referencias"
193
- value={customerData.address} onChange={e => setCustomerData({...customerData, address: e.target.value})}
194
- style={{ ...inputStyle, padding: '0.6rem' }}
195
- />
196
- </div>
197
- )}
198
- <div>
199
- <label style={{ fontSize: '0.7rem', color: 'var(--text-muted)' }}>Email (Puntos)</label>
200
- <input
201
- type="email" placeholder="cliente@email.com"
202
- value={customerData.email} onChange={e => setCustomerData({...customerData, email: e.target.value})}
203
- style={{ ...inputStyle, padding: '0.6rem' }}
204
- />
205
- </div>
206
- </div>
207
-
208
  {orderType === 'Mesa' && (
209
  <div style={{ display: 'flex', gap: '1rem', overflowX: 'auto', paddingBottom: '1rem', marginBottom: '1.5rem' }}>
210
  {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map(table => (
@@ -213,12 +182,11 @@ export default function WaiterPOS() {
213
  onClick={() => setActiveTable(table)}
214
  style={{
215
  minWidth: '70px', height: '70px', borderRadius: 'var(--radius-lg)',
216
- background: activeTable === table ? 'var(--primary)' : 'rgba(0,0,0,0.03)',
217
  border: activeTable === table ? '2px solid transparent' : '1px solid var(--border-subtle)',
218
  color: activeTable === table ? '#fff' : 'var(--text-main)',
219
  display: 'flex', alignItems: 'center', justifyContent: 'center',
220
- fontWeight: '600', transition: 'all 0.2s', cursor: 'pointer',
221
- boxShadow: activeTable === table ? '0 4px 12px var(--primary-glow)' : 'none'
222
  }}
223
  >
224
  {table}
@@ -261,7 +229,7 @@ export default function WaiterPOS() {
261
 
262
  <div style={{ flex: 1, overflowY: 'auto', padding: '1rem', display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
263
  {cart.map((item) => (
264
- <div key={item.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '0.75rem', background: 'rgba(0,0,0,0.02)', borderRadius: 'var(--radius-md)', border: '1px solid var(--border-subtle)' }}>
265
  <div style={{ flex: 1 }}>
266
  <div style={{ fontWeight: '600', fontSize: '0.95rem' }}>{item.name}</div>
267
  <div style={{ color: 'var(--text-muted)', fontSize: '0.85rem' }}>{item.qty} x ${item.price}</div>
@@ -276,7 +244,7 @@ export default function WaiterPOS() {
276
  ))}
277
  </div>
278
 
279
- <div style={{ padding: '1.5rem', borderTop: '1px solid var(--border-subtle)', background: 'rgba(0,0,0,0.03)' }}>
280
  <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', marginBottom: '1rem', fontSize: '0.9rem', color: 'var(--text-muted)' }}>
281
  <div style={{ display: 'flex', justifyContent: 'space-between' }}>
282
  <span>Subtotal:</span>
@@ -325,7 +293,7 @@ export default function WaiterPOS() {
325
  {isCheckoutOpen && (
326
  <div style={{
327
  position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
328
- background: 'rgba(0,0,0,0.4)', backdropFilter: 'blur(10px)',
329
  display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000
330
  }}>
331
  <div className="glass-panel animate-scale-in" style={{ width: '100%', maxWidth: '500px', padding: '2.5rem' }}>
@@ -359,7 +327,7 @@ export default function WaiterPOS() {
359
  placeholder="$ 0.00"
360
  value={tip}
361
  onChange={(e) => setTip(e.target.value)}
362
- style={{ width: '100%', padding: '0.8rem', background: 'rgba(0,0,0,0.03)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', color: 'var(--text-main)' }}
363
  />
364
  </div>
365
 
@@ -405,7 +373,7 @@ export default function WaiterPOS() {
405
 
406
  <style>{`
407
  .btn-glass {
408
- background: rgba(0,0,0,0.03);
409
  border: 1px solid var(--border-subtle);
410
  color: var(--text-main);
411
  border-radius: var(--radius-md);
@@ -418,7 +386,7 @@ export default function WaiterPOS() {
418
  gap: 0.5rem;
419
  }
420
  .btn-glass:hover {
421
- background: rgba(0,0,0,0.06);
422
  transform: translateY(-2px);
423
  }
424
  .btn-primary {
@@ -433,12 +401,10 @@ export default function WaiterPOS() {
433
  align-items: center;
434
  justify-content: center;
435
  gap: 0.5rem;
436
- box-shadow: 0 4px 12px var(--primary-glow);
437
  }
438
  .btn-primary:hover {
439
  opacity: 0.9;
440
  transform: translateY(-2px);
441
- box-shadow: 0 6px 16px var(--primary-glow);
442
  }
443
  .animate-scale-in {
444
  animation: scaleIn 0.3s ease-out;
 
2
  import { useAuth } from '../context/AuthContext';
3
  import { useNavigate } from 'react-router-dom';
4
  import { db } from '../firebase/config';
5
+ import { ref, onValue, push, set } from 'firebase/database';
6
  import { LogOut, Coffee, Send, Trash2, ShoppingBag, Truck, Receipt, CheckCircle, XCircle, CreditCard, Banknote, QrCode } from 'lucide-react';
7
 
8
  export default function WaiterPOS() {
 
16
  const [tip, setTip] = useState(0);
17
  const [paymentMethod, setPaymentMethod] = useState('Efectivo');
18
  const [isCheckoutOpen, setIsCheckoutOpen] = useState(false);
 
19
 
20
  // Fetch Menu
21
  useEffect(() => {
 
60
  table: orderType === 'Mesa' ? activeTable : orderType,
61
  type: orderType,
62
  waiter: currentUser?.email,
 
 
 
63
  items: cart,
64
  subtotal,
65
  discount,
 
75
  if (!isCheckoutOpen) {
76
  setCart([]);
77
  setActiveTable(null);
 
78
  alert('¡Comanda enviada a cocina exitosamente!');
79
  } else {
80
  // Finalizar y descontar stock
 
82
 
83
  // Lógica de Descuento de Inventario
84
  for (const item of cart) {
85
+ // Buscamos el producto en el menú para ver su receta (ingredients)
86
+ // Nota: En un entorno real, esto se haría en el backend para evitar race conditions y asegurar integridad.
87
  const productRef = ref(db, `menu/${item.id}`);
88
  onValue(productRef, async (snapshot) => {
89
  const product = snapshot.val();
90
  if (product && product.ingredients) {
91
  for (const ing of product.ingredients) {
92
+ const invRef = ref(db, `inventory/${ing.id}/quantity`);
93
+ // Obtenemos cantidad actual y restamos (qty_producto * qty_ingrediente)
94
  onValue(ref(db, `inventory/${ing.id}`), async (invSnap) => {
95
  const invData = invSnap.val();
96
  if (invData) {
97
  const newQty = invData.quantity - (item.qty * ing.qty);
98
+ await set(invRef, newQty);
99
  }
100
  }, { onlyOnce: true });
101
  }
 
106
  setIsCheckoutOpen(false);
107
  setCart([]);
108
  setActiveTable(null);
 
109
  alert('¡Venta realizada y stock actualizado!');
110
  }
111
  };
 
139
  <div className="app-container" style={{ display: 'flex', flexDirection: 'column', height: '100vh', overflow: 'hidden' }}>
140
  {/* Navbar */}
141
  <header className="glass-panel" style={{ padding: '1rem 2rem', display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderRadius: '0', borderBottom: '1px solid var(--border-subtle)', background: 'var(--bg-base)' }}>
142
+ <h1 className="text-gradient" style={{ fontSize: '1.5rem', fontWeight: '800' }}>Terminal POS</h1>
143
  <div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
144
  <span style={{ color: 'var(--text-muted)', fontSize: '0.9rem' }}>{currentUser?.email}</span>
145
  <button className="btn-glass" onClick={() => window.open('/menu', '_blank')} style={{ padding: '0.5rem 1rem' }}>Carta Digital</button>
 
174
  </div>
175
  </div>
176
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  {orderType === 'Mesa' && (
178
  <div style={{ display: 'flex', gap: '1rem', overflowX: 'auto', paddingBottom: '1rem', marginBottom: '1.5rem' }}>
179
  {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map(table => (
 
182
  onClick={() => setActiveTable(table)}
183
  style={{
184
  minWidth: '70px', height: '70px', borderRadius: 'var(--radius-lg)',
185
+ background: activeTable === table ? 'var(--primary)' : 'rgba(255,255,255,0.05)',
186
  border: activeTable === table ? '2px solid transparent' : '1px solid var(--border-subtle)',
187
  color: activeTable === table ? '#fff' : 'var(--text-main)',
188
  display: 'flex', alignItems: 'center', justifyContent: 'center',
189
+ fontWeight: '600', transition: 'all 0.2s', cursor: 'pointer'
 
190
  }}
191
  >
192
  {table}
 
229
 
230
  <div style={{ flex: 1, overflowY: 'auto', padding: '1rem', display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
231
  {cart.map((item) => (
232
+ <div key={item.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '0.75rem', background: 'rgba(255,255,255,0.03)', borderRadius: 'var(--radius-md)' }}>
233
  <div style={{ flex: 1 }}>
234
  <div style={{ fontWeight: '600', fontSize: '0.95rem' }}>{item.name}</div>
235
  <div style={{ color: 'var(--text-muted)', fontSize: '0.85rem' }}>{item.qty} x ${item.price}</div>
 
244
  ))}
245
  </div>
246
 
247
+ <div style={{ padding: '1.5rem', borderTop: '1px solid var(--border-subtle)', background: 'rgba(0,0,0,0.2)' }}>
248
  <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', marginBottom: '1rem', fontSize: '0.9rem', color: 'var(--text-muted)' }}>
249
  <div style={{ display: 'flex', justifyContent: 'space-between' }}>
250
  <span>Subtotal:</span>
 
293
  {isCheckoutOpen && (
294
  <div style={{
295
  position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
296
+ background: 'rgba(0,0,0,0.85)', backdropFilter: 'blur(10px)',
297
  display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000
298
  }}>
299
  <div className="glass-panel animate-scale-in" style={{ width: '100%', maxWidth: '500px', padding: '2.5rem' }}>
 
327
  placeholder="$ 0.00"
328
  value={tip}
329
  onChange={(e) => setTip(e.target.value)}
330
+ style={{ width: '100%', padding: '0.8rem', background: 'rgba(255,255,255,0.05)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)', color: '#fff' }}
331
  />
332
  </div>
333
 
 
373
 
374
  <style>{`
375
  .btn-glass {
376
+ background: rgba(255,255,255,0.05);
377
  border: 1px solid var(--border-subtle);
378
  color: var(--text-main);
379
  border-radius: var(--radius-md);
 
386
  gap: 0.5rem;
387
  }
388
  .btn-glass:hover {
389
+ background: rgba(255,255,255,0.1);
390
  transform: translateY(-2px);
391
  }
392
  .btn-primary {
 
401
  align-items: center;
402
  justify-content: center;
403
  gap: 0.5rem;
 
404
  }
405
  .btn-primary:hover {
406
  opacity: 0.9;
407
  transform: translateY(-2px);
 
408
  }
409
  .animate-scale-in {
410
  animation: scaleIn 0.3s ease-out;