Compare commits
3 Commits
develop
...
feature/re
| Author | SHA1 | Date | |
|---|---|---|---|
| 40186f76b3 | |||
| 9df97a144a | |||
| e7d64e0094 |
207
MIGRATION_GUIDE.md
Normal file
207
MIGRATION_GUIDE.md
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
# Guía de Migración a TypeScript + shadcn/ui
|
||||||
|
|
||||||
|
## ✅ Sprint 0 Completado - Setup Inicial
|
||||||
|
|
||||||
|
Se han creado los siguientes archivos de configuración:
|
||||||
|
|
||||||
|
### Archivos TypeScript
|
||||||
|
- ✅ `frontend/tsconfig.json` - Configuración principal de TypeScript
|
||||||
|
- ✅ `frontend/tsconfig.node.json` - Configuración para archivos de Node (vite.config)
|
||||||
|
- ✅ `frontend/vite.config.ts` - Vite config migrado a TypeScript
|
||||||
|
- ✅ `frontend/tailwind.config.ts` - Tailwind config migrado a TypeScript
|
||||||
|
- ✅ `frontend/src/vite-env.d.ts` - Types para variables de entorno
|
||||||
|
|
||||||
|
### shadcn/ui
|
||||||
|
- ✅ `frontend/components.json` - Configuración de shadcn/ui
|
||||||
|
- ✅ `frontend/src/lib/utils.ts` - Helper cn() para clases de Tailwind
|
||||||
|
|
||||||
|
## 📋 Siguientes Pasos
|
||||||
|
|
||||||
|
### 1. Instalar Dependencias
|
||||||
|
|
||||||
|
Ejecuta en PowerShell desde `c:\Users\ADM\Downloads\checklist-mvp\frontend`:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Instalar TypeScript y tipos
|
||||||
|
npm install -D typescript @types/react @types/react-dom @types/node
|
||||||
|
npm install -D @typescript-eslint/eslint-plugin @typescript-eslint/parser
|
||||||
|
|
||||||
|
# Instalar shadcn/ui y dependencias
|
||||||
|
npm install @radix-ui/react-avatar @radix-ui/react-dialog @radix-ui/react-dropdown-menu
|
||||||
|
npm install @radix-ui/react-label @radix-ui/react-select @radix-ui/react-separator
|
||||||
|
npm install @radix-ui/react-slot @radix-ui/react-tabs @radix-ui/react-toast
|
||||||
|
npm install class-variance-authority tailwind-merge tailwindcss-animate vaul
|
||||||
|
|
||||||
|
# Verificar que ya tienes instalados (según package.json actual)
|
||||||
|
# - clsx (ya instalado)
|
||||||
|
# - lucide-react (ya instalado)
|
||||||
|
# - react 18.2.0 (ya instalado)
|
||||||
|
# - tailwindcss (ya instalado)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Instalar Componentes shadcn/ui
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Ejecutar desde frontend/
|
||||||
|
npx shadcn-ui@latest add button
|
||||||
|
npx shadcn-ui@latest add card
|
||||||
|
npx shadcn-ui@latest add badge
|
||||||
|
npx shadcn-ui@latest add input
|
||||||
|
npx shadcn-ui@latest add select
|
||||||
|
npx shadcn-ui@latest add dialog
|
||||||
|
npx shadcn-ui@latest add table
|
||||||
|
npx shadcn-ui@latest add tabs
|
||||||
|
npx shadcn-ui@latest add toast
|
||||||
|
npx shadcn-ui@latest add separator
|
||||||
|
npx shadcn-ui@latest add dropdown-menu
|
||||||
|
```
|
||||||
|
|
||||||
|
Esto creará automáticamente los componentes en `frontend/src/components/ui/`
|
||||||
|
|
||||||
|
### 3. Actualizar CSS (index.css)
|
||||||
|
|
||||||
|
Agregar al **inicio** de `frontend/src/index.css` (antes del código existente):
|
||||||
|
|
||||||
|
```css
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 222.2 84% 4.9%;
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 222.2 84% 4.9%;
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 222.2 84% 4.9%;
|
||||||
|
--primary: 221.2 83.2% 53.3%;
|
||||||
|
--primary-foreground: 210 40% 98%;
|
||||||
|
--secondary: 210 40% 96.1%;
|
||||||
|
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
--muted: 210 40% 96.1%;
|
||||||
|
--muted-foreground: 215.4 16.3% 46.9%;
|
||||||
|
--accent: 210 40% 96.1%;
|
||||||
|
--accent-foreground: 222.2 47.4% 11.2%;
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
--border: 214.3 31.8% 91.4%;
|
||||||
|
--input: 214.3 31.8% 91.4%;
|
||||||
|
--ring: 221.2 83.2% 53.3%;
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 222.2 84% 4.9%;
|
||||||
|
--foreground: 210 40% 98%;
|
||||||
|
--card: 222.2 84% 4.9%;
|
||||||
|
--card-foreground: 210 40% 98%;
|
||||||
|
--popover: 222.2 84% 4.9%;
|
||||||
|
--popover-foreground: 210 40% 98%;
|
||||||
|
--primary: 217.2 91.2% 59.8%;
|
||||||
|
--primary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
--secondary: 217.2 32.6% 17.5%;
|
||||||
|
--secondary-foreground: 210 40% 98%;
|
||||||
|
--muted: 217.2 32.6% 17.5%;
|
||||||
|
--muted-foreground: 215 20.2% 65.1%;
|
||||||
|
--accent: 217.2 32.6% 17.5%;
|
||||||
|
--accent-foreground: 210 40% 98%;
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
--border: 217.2 32.6% 17.5%;
|
||||||
|
--input: 217.2 32.6% 17.5%;
|
||||||
|
--ring: 224.3 76.3% 48%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Renombrar Archivos (IMPORTANTE)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Renombrar main.jsx a main.tsx
|
||||||
|
Rename-Item frontend/src/main.jsx frontend/src/main.tsx
|
||||||
|
|
||||||
|
# Eliminar archivos .js antiguos (ya tenemos .ts)
|
||||||
|
Remove-Item frontend/vite.config.js
|
||||||
|
Remove-Item frontend/tailwind.config.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Actualizar index.html
|
||||||
|
|
||||||
|
Cambiar la línea del script en `frontend/index.html`:
|
||||||
|
|
||||||
|
**De:**
|
||||||
|
```html
|
||||||
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
**A:**
|
||||||
|
```html
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Verificar que Compila
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd frontend
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Si todo está bien, deberías ver:
|
||||||
|
```
|
||||||
|
VITE v5.0.11 ready in XXX ms
|
||||||
|
|
||||||
|
➜ Local: http://localhost:5173/
|
||||||
|
➜ Network: use --host to expose
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Crear Rama Git
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Desde la raíz del proyecto
|
||||||
|
git checkout -b feature/typescript-shadcn-migration
|
||||||
|
git add frontend/tsconfig.json frontend/tsconfig.node.json
|
||||||
|
git add frontend/vite.config.ts frontend/tailwind.config.ts
|
||||||
|
git add frontend/components.json frontend/src/vite-env.d.ts
|
||||||
|
git add frontend/src/lib/utils.ts MIGRATION_GUIDE.md
|
||||||
|
git commit -m "feat(frontend): configure TypeScript + shadcn/ui setup
|
||||||
|
|
||||||
|
- Add tsconfig.json with strict mode
|
||||||
|
- Configure path aliases (@/*)
|
||||||
|
- Add shadcn/ui configuration
|
||||||
|
- Create lib/utils.ts helper
|
||||||
|
- Add TypeScript environment types
|
||||||
|
|
||||||
|
Backend version: sin cambios
|
||||||
|
Frontend version: 1.4.0 -> 1.5.0-beta"
|
||||||
|
git push -u origin feature/typescript-shadcn-migration
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Estado Actual
|
||||||
|
|
||||||
|
- ✅ Configuración TypeScript creada
|
||||||
|
- ✅ Configuración shadcn/ui creada
|
||||||
|
- ⏳ Pendiente: Instalar dependencias npm
|
||||||
|
- ⏳ Pendiente: Renombrar archivos
|
||||||
|
- ⏳ Pendiente: Instalar componentes shadcn
|
||||||
|
- ⏳ Pendiente: Actualizar index.css
|
||||||
|
- ⏳ Pendiente: Verificar compilación
|
||||||
|
|
||||||
|
## 📝 Notas
|
||||||
|
|
||||||
|
- El sistema actual (App.jsx) seguirá funcionando mientras no se renombre a .tsx
|
||||||
|
- Los archivos TypeScript coexisten con JavaScript hasta migración completa
|
||||||
|
- shadcn/ui se puede usar inmediatamente después de instalar componentes
|
||||||
|
- No hay cambios en el backend por ahora
|
||||||
|
|
||||||
|
## 🚀 Siguiente Sprint
|
||||||
|
|
||||||
|
Una vez completados estos pasos, continuaremos con:
|
||||||
|
- Sprint 1: Crear types para el módulo de Checklists
|
||||||
|
- Sprint 2: Backend del módulo de Recambios
|
||||||
|
- Sprint 3: Frontend del módulo de Recambios con TypeScript + shadcn
|
||||||
410
README_RECAMBIOS.md
Normal file
410
README_RECAMBIOS.md
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
# 📦 Sistema de Gestión de Recambios - Feature Branch
|
||||||
|
|
||||||
|
> **Rama**: `feature/typescript-shadcn-migration`
|
||||||
|
> **Estado**: 🚧 En Desarrollo
|
||||||
|
> **Stack**: TypeScript + shadcn/ui + React 18 + FastAPI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Objetivo de esta Rama
|
||||||
|
|
||||||
|
Desarrollar un **Sistema Integral de Gestión de Recambios** para talleres mecánicos, integrado con el sistema existente de checklists. Este módulo añade capacidades completas de gestión de pedidos, proveedores, inventario y facturación.
|
||||||
|
|
||||||
|
## 🏗️ Arquitectura del Módulo
|
||||||
|
|
||||||
|
### Frontend (TypeScript + shadcn/ui)
|
||||||
|
|
||||||
|
```
|
||||||
|
frontend/src/
|
||||||
|
├── modules/
|
||||||
|
│ └── recambios/
|
||||||
|
│ ├── RecambiosApp.tsx ✅ Componente principal con navegación
|
||||||
|
│ ├── pages/
|
||||||
|
│ │ ├── KanbanPedidos.tsx 🚧 Tablero Kanban de estados
|
||||||
|
│ │ ├── PanelProveedor.tsx 🚧 Gestión de proveedores
|
||||||
|
│ │ ├── GestionAlbaranes.tsx 🚧 Registro de albaranes
|
||||||
|
│ │ ├── GestionClientes.tsx 🚧 Base de datos clientes
|
||||||
|
│ │ ├── Inventario.tsx 🚧 Control de stock
|
||||||
|
│ │ └── Configuracion.tsx 🚧 Ajustes del módulo
|
||||||
|
│ ├── components/
|
||||||
|
│ │ ├── PedidoCard.tsx 🚧 Tarjeta de pedido
|
||||||
|
│ │ ├── ProveedorForm.tsx 🚧 Formulario proveedor
|
||||||
|
│ │ ├── AlbaranForm.tsx 🚧 Formulario albarán
|
||||||
|
│ │ └── EstadoBadge.tsx 🚧 Badge de estado
|
||||||
|
│ └── types/
|
||||||
|
│ └── recambios.types.ts 🚧 Interfaces TypeScript
|
||||||
|
├── components/ui/ ✅ shadcn/ui components
|
||||||
|
│ ├── button.tsx
|
||||||
|
│ ├── card.tsx
|
||||||
|
│ ├── badge.tsx
|
||||||
|
│ └── tabs.tsx
|
||||||
|
└── lib/
|
||||||
|
└── utils.ts ✅ Utilidades (cn helper)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend (FastAPI + SQLAlchemy)
|
||||||
|
|
||||||
|
```
|
||||||
|
backend/app/
|
||||||
|
├── modules/
|
||||||
|
│ └── recambios/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── models.py 🚧 15+ modelos SQLAlchemy
|
||||||
|
│ ├── schemas.py 🚧 Pydantic schemas
|
||||||
|
│ ├── routes.py 🚧 Endpoints REST
|
||||||
|
│ └── services.py 🚧 Lógica de negocio
|
||||||
|
└── migrations/
|
||||||
|
└── add_recambios_tables.sql 🚧 Schema inicial
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Modelo de Datos
|
||||||
|
|
||||||
|
### Entidades Principales
|
||||||
|
|
||||||
|
#### 1. **rec_clientes**
|
||||||
|
```sql
|
||||||
|
- id, nombre, telefono, email, direccion, cif_nif
|
||||||
|
- vehiculos_asociados (JSON)
|
||||||
|
- historial_pedidos
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. **rec_proveedores**
|
||||||
|
```sql
|
||||||
|
- id, nombre, contacto, telefono, email
|
||||||
|
- plazo_entrega_dias, condiciones_pago
|
||||||
|
- rating, notas
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. **rec_referencias**
|
||||||
|
```sql
|
||||||
|
- id, codigo_referencia, descripcion
|
||||||
|
- proveedor_id, precio_compra, precio_venta
|
||||||
|
- estado (13 estados posibles)
|
||||||
|
- stock_actual, ubicacion_almacen
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. **rec_pedidos_cliente**
|
||||||
|
```sql
|
||||||
|
- id, cliente_id, fecha_pedido, total
|
||||||
|
- estado, prioridad, asesor_id
|
||||||
|
- vehiculo_datos (JSON)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5. **rec_lineas_pedido**
|
||||||
|
```sql
|
||||||
|
- id, pedido_cliente_id, referencia_id
|
||||||
|
- cantidad, precio_unitario, subtotal
|
||||||
|
- estado_linea
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 6. **rec_pedidos_proveedor**
|
||||||
|
```sql
|
||||||
|
- id, proveedor_id, fecha_pedido
|
||||||
|
- estado, total_pedido, recambista_id
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 7. **rec_albaranes**
|
||||||
|
```sql
|
||||||
|
- id, pedido_proveedor_id, numero_albaran
|
||||||
|
- fecha_recepcion, recibido_por
|
||||||
|
- observaciones, foto_albaran_url
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 8. **rec_devoluciones**
|
||||||
|
```sql
|
||||||
|
- id, referencia_id, motivo, estado
|
||||||
|
- fecha_devolucion, fecha_abono
|
||||||
|
- importe_abonado
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 9. **rec_movimientos_stock**
|
||||||
|
```sql
|
||||||
|
- id, referencia_id, tipo_movimiento
|
||||||
|
- cantidad, fecha, usuario_id
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 10. **rec_facturas**
|
||||||
|
```sql
|
||||||
|
- id, cliente_id, numero_factura
|
||||||
|
- fecha_emision, total, estado
|
||||||
|
- archivo_pdf_url
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 Máquina de Estados (13 Estados)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ pendiente_decidir │ ← Estado inicial
|
||||||
|
└──────────┬──────────┘
|
||||||
|
│
|
||||||
|
├─→ pedido_proveedor
|
||||||
|
├─→ alternativa
|
||||||
|
├─→ usado
|
||||||
|
└─→ descartada
|
||||||
|
│
|
||||||
|
┌───────────┴───────────┐
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
recibida recibida_parcial
|
||||||
|
│ │
|
||||||
|
├─→ devolucion │
|
||||||
|
├─→ entregada ──────────┘
|
||||||
|
└─→ instalada
|
||||||
|
│
|
||||||
|
└─→ facturada
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transiciones de Estado
|
||||||
|
|
||||||
|
1. **pendiente_decidir** → Usuario decide acción
|
||||||
|
2. **pedido_proveedor** → Pedido enviado al proveedor
|
||||||
|
3. **alternativa** → Buscando opción alternativa
|
||||||
|
4. **usado** → Referencia de segunda mano
|
||||||
|
5. **recibida** → Llegó completa del proveedor
|
||||||
|
6. **recibida_parcial** → Llegó solo parte del pedido
|
||||||
|
7. **devolucion** → En proceso de devolución
|
||||||
|
8. **entregada** → Entregada al cliente
|
||||||
|
9. **instalada** → Instalada en el vehículo
|
||||||
|
10. **facturada** → Facturada al cliente
|
||||||
|
11. **descartada** → No se procede con la referencia
|
||||||
|
12. **pendiente_abono** → Esperando abono de devolución
|
||||||
|
13. **abonada** → Devolución abonada
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👥 Roles y Permisos
|
||||||
|
|
||||||
|
### Asesor de Servicio
|
||||||
|
- ✅ Crear pedidos de cliente
|
||||||
|
- ✅ Consultar estado de referencias
|
||||||
|
- ✅ Modificar prioridades
|
||||||
|
- ✅ Ver historial de cliente
|
||||||
|
|
||||||
|
### Recambista
|
||||||
|
- ✅ Gestionar pedidos a proveedores
|
||||||
|
- ✅ Registrar albaranes
|
||||||
|
- ✅ Gestionar stock
|
||||||
|
- ✅ Actualizar estados de referencias
|
||||||
|
- ✅ Procesar devoluciones
|
||||||
|
|
||||||
|
### Administración
|
||||||
|
- ✅ Generar facturas
|
||||||
|
- ✅ Ver reportes financieros
|
||||||
|
- ✅ Gestionar proveedores
|
||||||
|
- ✅ Configurar precios
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Diseño UI con shadcn/ui
|
||||||
|
|
||||||
|
### Componentes Implementados ✅
|
||||||
|
|
||||||
|
- **Button** - Botones con variantes (default, outline, ghost, destructive)
|
||||||
|
- **Card** - Tarjetas con header, content, footer
|
||||||
|
- **Badge** - Etiquetas de estado con colores
|
||||||
|
- **Tabs** - Navegación entre secciones
|
||||||
|
|
||||||
|
### Componentes Pendientes 🚧
|
||||||
|
|
||||||
|
- **Input** - Campos de texto
|
||||||
|
- **Select** - Dropdowns
|
||||||
|
- **Dialog** - Modales
|
||||||
|
- **Table** - Tablas de datos
|
||||||
|
- **Toast** - Notificaciones
|
||||||
|
- **Dropdown Menu** - Menús contextuales
|
||||||
|
- **Date Picker** - Selector de fechas
|
||||||
|
- **Form** - Formularios validados
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Plan de Implementación
|
||||||
|
|
||||||
|
### ✅ Sprint 0: Fundación (COMPLETADO)
|
||||||
|
- [x] Configuración TypeScript
|
||||||
|
- [x] Setup shadcn/ui
|
||||||
|
- [x] Componentes base UI
|
||||||
|
- [x] Estructura de carpetas
|
||||||
|
- [x] Prototipo Kanban visual
|
||||||
|
|
||||||
|
### 🚧 Sprint 1: Backend Core (EN PROGRESO)
|
||||||
|
- [ ] Modelos SQLAlchemy (15 tablas)
|
||||||
|
- [ ] Migraciones de base de datos
|
||||||
|
- [ ] Schemas Pydantic
|
||||||
|
- [ ] Endpoints CRUD básicos
|
||||||
|
- [ ] Autenticación y permisos
|
||||||
|
|
||||||
|
### 🔜 Sprint 2: Frontend Básico
|
||||||
|
- [ ] Páginas principales (6 tabs)
|
||||||
|
- [ ] Formularios con validación
|
||||||
|
- [ ] Integración API
|
||||||
|
- [ ] Gestión de estado
|
||||||
|
- [ ] Tipos TypeScript completos
|
||||||
|
|
||||||
|
### 🔜 Sprint 3: Funcionalidades Avanzadas
|
||||||
|
- [ ] Kanban drag & drop
|
||||||
|
- [ ] Upload de fotos (albaranes)
|
||||||
|
- [ ] Generación de PDFs
|
||||||
|
- [ ] Sistema de notificaciones
|
||||||
|
- [ ] Búsqueda y filtros
|
||||||
|
|
||||||
|
### 🔜 Sprint 4: Integración y Testing
|
||||||
|
- [ ] Integración con sistema de checklists
|
||||||
|
- [ ] Tests unitarios
|
||||||
|
- [ ] Tests E2E
|
||||||
|
- [ ] Optimización de rendimiento
|
||||||
|
- [ ] Documentación completa
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Comandos de Desarrollo
|
||||||
|
|
||||||
|
### Levantar Entorno de Desarrollo
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Frontend
|
||||||
|
cd frontend
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Backend
|
||||||
|
cd backend
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ver el Módulo de Recambios
|
||||||
|
|
||||||
|
1. Inicia sesión como **admin**
|
||||||
|
2. Haz clic en **📦 Recambios** en el sidebar
|
||||||
|
3. Navega entre las 6 pestañas
|
||||||
|
|
||||||
|
### Compilar TypeScript
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd frontend
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Tecnologías Utilizadas
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- **React 18** - Framework UI
|
||||||
|
- **TypeScript 5.9** - Type safety
|
||||||
|
- **shadcn/ui** - Component library
|
||||||
|
- **Radix UI** - Primitive components
|
||||||
|
- **Tailwind CSS 3.4** - Utility-first CSS
|
||||||
|
- **Lucide React** - Icon library
|
||||||
|
- **Vite 5** - Build tool
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- **FastAPI** - Python web framework
|
||||||
|
- **SQLAlchemy** - ORM
|
||||||
|
- **PostgreSQL 15** - Base de datos
|
||||||
|
- **Pydantic** - Data validation
|
||||||
|
- **JWT** - Autenticación
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Convenciones de Código
|
||||||
|
|
||||||
|
### TypeScript
|
||||||
|
```typescript
|
||||||
|
// Interfaces con prefijo 'I' o sufijo del dominio
|
||||||
|
interface Pedido {
|
||||||
|
id: number;
|
||||||
|
cliente: string;
|
||||||
|
estado: EstadoPedido;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tipos para estados
|
||||||
|
type EstadoPedido = 'pendiente_decidir' | 'pedido_proveedor' | 'recibida';
|
||||||
|
|
||||||
|
// Componentes con tipo React.FC
|
||||||
|
const PedidoCard: React.FC<PedidoCardProps> = ({ pedido }) => { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Python
|
||||||
|
```python
|
||||||
|
# Modelos con tabla explícita
|
||||||
|
class RecCliente(Base):
|
||||||
|
__tablename__ = "rec_clientes"
|
||||||
|
|
||||||
|
# Schemas con validación
|
||||||
|
class PedidoCreate(BaseModel):
|
||||||
|
cliente_id: int
|
||||||
|
referencias: List[LineaPedido]
|
||||||
|
|
||||||
|
# Rutas con prefijo /api/recambios
|
||||||
|
@router.post("/api/recambios/pedidos")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 Debugging
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
```powershell
|
||||||
|
# Ver errores TypeScript
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Limpiar caché
|
||||||
|
rm -r node_modules .vite
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
```powershell
|
||||||
|
# Ver logs
|
||||||
|
docker-compose logs -f backend
|
||||||
|
|
||||||
|
# Entrar al contenedor
|
||||||
|
docker-compose exec backend bash
|
||||||
|
|
||||||
|
# Verificar tablas
|
||||||
|
docker-compose exec postgres psql -U user -d checklist_db -c "\dt rec_*"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Documentación Relacionada
|
||||||
|
|
||||||
|
- [MIGRATION_GUIDE.md](./MIGRATION_GUIDE.md) - Guía de migración TypeScript
|
||||||
|
- [AI_FUNCTIONALITY.md](./AI_FUNCTIONALITY.md) - Sistema de IA (checklists)
|
||||||
|
- [API_DOCUMENTATION.md](./API_DOCUMENTATION.md) - Documentación API
|
||||||
|
- [shadcn/ui Docs](https://ui.shadcn.com/) - Documentación componentes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Próximos Pasos
|
||||||
|
|
||||||
|
1. **Crear modelos de backend** (15 tablas)
|
||||||
|
2. **Escribir schemas Pydantic**
|
||||||
|
3. **Implementar endpoints CRUD**
|
||||||
|
4. **Desarrollar páginas TypeScript**
|
||||||
|
5. **Conectar frontend con API**
|
||||||
|
6. **Agregar drag & drop al Kanban**
|
||||||
|
7. **Implementar búsqueda y filtros**
|
||||||
|
8. **Testing completo**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👨💻 Contribución
|
||||||
|
|
||||||
|
Esta es una rama de desarrollo activo. Para contribuir:
|
||||||
|
|
||||||
|
1. Crea una sub-rama desde `feature/typescript-shadcn-migration`
|
||||||
|
2. Implementa tu funcionalidad
|
||||||
|
3. Haz commit con mensajes descriptivos
|
||||||
|
4. Crea PR hacia esta rama (NO hacia main)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Contacto
|
||||||
|
|
||||||
|
**Proyecto**: Ayutec - Sistema Inteligente de Inspecciones
|
||||||
|
**Versión Recambios**: 0.1.0 (Alpha)
|
||||||
|
**Última Actualización**: Diciembre 2025
|
||||||
17
frontend/components.json
Normal file
17
frontend/components.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "default",
|
||||||
|
"rsc": false,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.ts",
|
||||||
|
"css": "src/index.css",
|
||||||
|
"baseColor": "slate",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils"
|
||||||
|
}
|
||||||
|
}
|
||||||
7305
frontend/package-lock.json
generated
Normal file
7305
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,22 +9,38 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@radix-ui/react-avatar": "^1.1.11",
|
||||||
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
|
"@radix-ui/react-separator": "^1.1.8",
|
||||||
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
|
"@radix-ui/react-toast": "^1.2.15",
|
||||||
|
"axios": "^1.6.5",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.0",
|
||||||
|
"lucide-react": "^0.303.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-markdown": "^9.0.1",
|
||||||
"react-router-dom": "^6.21.1",
|
"react-router-dom": "^6.21.1",
|
||||||
"axios": "^1.6.5",
|
|
||||||
"react-signature-canvas": "^1.0.6",
|
"react-signature-canvas": "^1.0.6",
|
||||||
"lucide-react": "^0.303.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"clsx": "^2.1.0",
|
"tailwindcss-animate": "^1.0.7"
|
||||||
"react-markdown": "^9.0.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.48",
|
"@types/node": "^24.10.2",
|
||||||
"@types/react-dom": "^18.2.18",
|
"@types/react": "^18.3.27",
|
||||||
|
"@types/react-dom": "^18.3.7",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.49.0",
|
||||||
|
"@typescript-eslint/parser": "^8.49.0",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
"postcss": "^8.4.33",
|
"postcss": "^8.4.33",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
"vite": "^5.0.11"
|
"vite": "^5.0.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import ReactMarkdown from 'react-markdown'
|
|||||||
import Sidebar from './Sidebar'
|
import Sidebar from './Sidebar'
|
||||||
import QuestionTypeEditor from './QuestionTypeEditor'
|
import QuestionTypeEditor from './QuestionTypeEditor'
|
||||||
import QuestionAnswerInput from './QuestionAnswerInput'
|
import QuestionAnswerInput from './QuestionAnswerInput'
|
||||||
|
import RecambiosApp from './modules/recambios/RecambiosApp'
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [user, setUser] = useState(null)
|
const [user, setUser] = useState(null)
|
||||||
@@ -70,13 +71,18 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Verificar si hay token guardado
|
// MODO TEST: Auto-login como admin sin verificación
|
||||||
const token = localStorage.getItem('token')
|
const testUser = {
|
||||||
const userData = localStorage.getItem('user')
|
id: 1,
|
||||||
|
username: 'admin',
|
||||||
if (token && userData) {
|
full_name: 'Administrador Test',
|
||||||
setUser(JSON.parse(userData))
|
role: 'admin',
|
||||||
|
employee_code: 'ADMIN001'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('token', 'test-token-bypass')
|
||||||
|
localStorage.setItem('user', JSON.stringify(testUser))
|
||||||
|
setUser(testUser)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@@ -420,6 +426,7 @@ function DashboardPage({ user, setUser }) {
|
|||||||
{activeTab === 'inspections' && '🔍'}
|
{activeTab === 'inspections' && '🔍'}
|
||||||
{activeTab === 'users' && '👥'}
|
{activeTab === 'users' && '👥'}
|
||||||
{activeTab === 'reports' && '📊'}
|
{activeTab === 'reports' && '📊'}
|
||||||
|
{activeTab === 'recambios' && '📦'}
|
||||||
{activeTab === 'settings' && '⚙️'}
|
{activeTab === 'settings' && '⚙️'}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-white font-semibold text-sm sm:text-base">
|
<span className="text-white font-semibold text-sm sm:text-base">
|
||||||
@@ -427,6 +434,7 @@ function DashboardPage({ user, setUser }) {
|
|||||||
{activeTab === 'inspections' && 'Inspecciones'}
|
{activeTab === 'inspections' && 'Inspecciones'}
|
||||||
{activeTab === 'users' && 'Usuarios'}
|
{activeTab === 'users' && 'Usuarios'}
|
||||||
{activeTab === 'reports' && 'Reportes'}
|
{activeTab === 'reports' && 'Reportes'}
|
||||||
|
{activeTab === 'recambios' && 'Recambios'}
|
||||||
{activeTab === 'settings' && 'Configuración'}
|
{activeTab === 'settings' && 'Configuración'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -437,6 +445,7 @@ function DashboardPage({ user, setUser }) {
|
|||||||
{activeTab === 'inspections' && '🔍'}
|
{activeTab === 'inspections' && '🔍'}
|
||||||
{activeTab === 'users' && '👥'}
|
{activeTab === 'users' && '👥'}
|
||||||
{activeTab === 'reports' && '📊'}
|
{activeTab === 'reports' && '📊'}
|
||||||
|
{activeTab === 'recambios' && '📦'}
|
||||||
{activeTab === 'settings' && '⚙️'}
|
{activeTab === 'settings' && '⚙️'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -461,6 +470,8 @@ function DashboardPage({ user, setUser }) {
|
|||||||
/>
|
/>
|
||||||
) : activeTab === 'inspections' ? (
|
) : activeTab === 'inspections' ? (
|
||||||
<InspectionsTab inspections={inspections} user={user} onUpdate={loadData} onContinue={setActiveInspection} />
|
<InspectionsTab inspections={inspections} user={user} onUpdate={loadData} onContinue={setActiveInspection} />
|
||||||
|
) : activeTab === 'recambios' ? (
|
||||||
|
<RecambiosApp />
|
||||||
) : activeTab === 'settings' ? (
|
) : activeTab === 'settings' ? (
|
||||||
<SettingsTab user={user} />
|
<SettingsTab user={user} />
|
||||||
) : activeTab === 'api-tokens' ? (
|
) : activeTab === 'api-tokens' ? (
|
||||||
|
|||||||
@@ -101,6 +101,22 @@ export default function Sidebar({ user, activeTab, setActiveTab, sidebarOpen, se
|
|||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
{user.role === 'admin' && (
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab('recambios')}
|
||||||
|
className={`w-full flex items-center ${sidebarOpen ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition ${
|
||||||
|
activeTab === 'recambios'
|
||||||
|
? 'bg-gradient-to-r from-emerald-600 to-teal-600 text-white shadow-lg'
|
||||||
|
: 'text-indigo-200 hover:bg-indigo-900/50'
|
||||||
|
}`}
|
||||||
|
title={!sidebarOpen ? 'Recambios' : ''}
|
||||||
|
>
|
||||||
|
<span className="text-xl">📦</span>
|
||||||
|
{sidebarOpen && <span>Recambios</span>}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
{user.role === 'admin' && (
|
{user.role === 'admin' && (
|
||||||
<>
|
<>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
36
frontend/src/components/ui/badge.tsx
Normal file
36
frontend/src/components/ui/badge.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const badgeVariants = cva(
|
||||||
|
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
||||||
|
secondary:
|
||||||
|
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
destructive:
|
||||||
|
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||||
|
outline: "text-foreground",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface BadgeProps
|
||||||
|
extends React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
VariantProps<typeof badgeVariants> {}
|
||||||
|
|
||||||
|
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||||
|
return (
|
||||||
|
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Badge, badgeVariants }
|
||||||
56
frontend/src/components/ui/button.tsx
Normal file
56
frontend/src/components/ui/button.tsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||||
|
outline:
|
||||||
|
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-10 px-4 py-2",
|
||||||
|
sm: "h-9 rounded-md px-3",
|
||||||
|
lg: "h-11 rounded-md px-8",
|
||||||
|
icon: "h-10 w-10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface ButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
asChild?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
|
const Comp = asChild ? Slot : "button"
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Button.displayName = "Button"
|
||||||
|
|
||||||
|
export { Button, buttonVariants }
|
||||||
79
frontend/src/components/ui/card.tsx
Normal file
79
frontend/src/components/ui/card.tsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Card = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Card.displayName = "Card"
|
||||||
|
|
||||||
|
const CardHeader = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardHeader.displayName = "CardHeader"
|
||||||
|
|
||||||
|
const CardTitle = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"text-2xl font-semibold leading-none tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardTitle.displayName = "CardTitle"
|
||||||
|
|
||||||
|
const CardDescription = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardDescription.displayName = "CardDescription"
|
||||||
|
|
||||||
|
const CardContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||||
|
))
|
||||||
|
CardContent.displayName = "CardContent"
|
||||||
|
|
||||||
|
const CardFooter = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex items-center p-6 pt-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardFooter.displayName = "CardFooter"
|
||||||
|
|
||||||
|
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||||
53
frontend/src/components/ui/tabs.tsx
Normal file
53
frontend/src/components/ui/tabs.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Tabs = TabsPrimitive.Root
|
||||||
|
|
||||||
|
const TabsList = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsList.displayName = TabsPrimitive.List.displayName
|
||||||
|
|
||||||
|
const TabsTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const TabsContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||||
@@ -2,13 +2,76 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
body {
|
@layer base {
|
||||||
margin: 0;
|
:root {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
--background: 0 0% 100%;
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
--foreground: 222.2 84% 4.9%;
|
||||||
sans-serif;
|
--card: 0 0% 100%;
|
||||||
-webkit-font-smoothing: antialiased;
|
--card-foreground: 222.2 84% 4.9%;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 222.2 84% 4.9%;
|
||||||
|
--primary: 222.2 47.4% 11.2%;
|
||||||
|
--primary-foreground: 210 40% 98%;
|
||||||
|
--secondary: 210 40% 96.1%;
|
||||||
|
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
--muted: 210 40% 96.1%;
|
||||||
|
--muted-foreground: 215.4 16.3% 46.9%;
|
||||||
|
--accent: 210 40% 96.1%;
|
||||||
|
--accent-foreground: 222.2 47.4% 11.2%;
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
--border: 214.3 31.8% 91.4%;
|
||||||
|
--input: 214.3 31.8% 91.4%;
|
||||||
|
--ring: 222.2 84% 4.9%;
|
||||||
|
--radius: 0.5rem;
|
||||||
|
--chart-1: 12 76% 61%;
|
||||||
|
--chart-2: 173 58% 39%;
|
||||||
|
--chart-3: 197 37% 24%;
|
||||||
|
--chart-4: 43 74% 66%;
|
||||||
|
--chart-5: 27 87% 67%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 222.2 84% 4.9%;
|
||||||
|
--foreground: 210 40% 98%;
|
||||||
|
--card: 222.2 84% 4.9%;
|
||||||
|
--card-foreground: 210 40% 98%;
|
||||||
|
--popover: 222.2 84% 4.9%;
|
||||||
|
--popover-foreground: 210 40% 98%;
|
||||||
|
--primary: 210 40% 98%;
|
||||||
|
--primary-foreground: 222.2 47.4% 11.2%;
|
||||||
|
--secondary: 217.2 32.6% 17.5%;
|
||||||
|
--secondary-foreground: 210 40% 98%;
|
||||||
|
--muted: 217.2 32.6% 17.5%;
|
||||||
|
--muted-foreground: 215 20.2% 65.1%;
|
||||||
|
--accent: 217.2 32.6% 17.5%;
|
||||||
|
--accent-foreground: 210 40% 98%;
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
--border: 217.2 32.6% 17.5%;
|
||||||
|
--input: 217.2 32.6% 17.5%;
|
||||||
|
--ring: 212.7 26.8% 83.9%;
|
||||||
|
--chart-1: 220 70% 50%;
|
||||||
|
--chart-2: 160 60% 45%;
|
||||||
|
--chart-3: 30 80% 55%;
|
||||||
|
--chart-4: 280 65% 60%;
|
||||||
|
--chart-5: 340 75% 55%;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
border-color: hsl(var(--border));
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: hsl(var(--background));
|
||||||
|
color: hsl(var(--foreground));
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
|
|||||||
6
frontend/src/lib/utils.ts
Normal file
6
frontend/src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { type ClassValue, clsx } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
405
frontend/src/modules/recambios/RecambiosApp.tsx
Normal file
405
frontend/src/modules/recambios/RecambiosApp.tsx
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
|
import {
|
||||||
|
Package,
|
||||||
|
Truck,
|
||||||
|
ClipboardList,
|
||||||
|
FileText,
|
||||||
|
Users,
|
||||||
|
Settings,
|
||||||
|
TrendingUp,
|
||||||
|
AlertCircle,
|
||||||
|
CheckCircle2,
|
||||||
|
Clock,
|
||||||
|
Search
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
interface Pedido {
|
||||||
|
id: number;
|
||||||
|
referencia: string;
|
||||||
|
cliente: string;
|
||||||
|
estado: 'pendiente_decidir' | 'pedido_proveedor' | 'recibida' | 'entregada';
|
||||||
|
prioridad: 'alta' | 'media' | 'baja';
|
||||||
|
total: number;
|
||||||
|
fecha: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RecambiosApp: React.FC = () => {
|
||||||
|
const [activeTab, setActiveTab] = useState('kanban');
|
||||||
|
|
||||||
|
// Datos de ejemplo
|
||||||
|
const pedidosEjemplo: Pedido[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
referencia: 'REF-2024-001',
|
||||||
|
cliente: 'Juan Pérez - VW Golf GTI',
|
||||||
|
estado: 'pendiente_decidir',
|
||||||
|
prioridad: 'alta',
|
||||||
|
total: 450.00,
|
||||||
|
fecha: '2024-12-09'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
referencia: 'REF-2024-002',
|
||||||
|
cliente: 'María González - Seat León',
|
||||||
|
estado: 'pedido_proveedor',
|
||||||
|
prioridad: 'media',
|
||||||
|
total: 320.00,
|
||||||
|
fecha: '2024-12-08'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
referencia: 'REF-2024-003',
|
||||||
|
cliente: 'Carlos Díaz - Audi A4',
|
||||||
|
estado: 'recibida',
|
||||||
|
prioridad: 'alta',
|
||||||
|
total: 680.00,
|
||||||
|
fecha: '2024-12-07'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const getEstadoColor = (estado: Pedido['estado']) => {
|
||||||
|
const colores = {
|
||||||
|
pendiente_decidir: 'bg-yellow-100 text-yellow-800 border-yellow-300',
|
||||||
|
pedido_proveedor: 'bg-blue-100 text-blue-800 border-blue-300',
|
||||||
|
recibida: 'bg-green-100 text-green-800 border-green-300',
|
||||||
|
entregada: 'bg-gray-100 text-gray-800 border-gray-300'
|
||||||
|
};
|
||||||
|
return colores[estado];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPrioridadColor = (prioridad: Pedido['prioridad']) => {
|
||||||
|
const colores = {
|
||||||
|
alta: 'bg-red-500 text-white',
|
||||||
|
media: 'bg-orange-500 text-white',
|
||||||
|
baja: 'bg-green-500 text-white'
|
||||||
|
};
|
||||||
|
return colores[prioridad];
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 p-6">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-4xl font-bold text-slate-900 mb-2">
|
||||||
|
Sistema de Gestión de Recambios
|
||||||
|
</h1>
|
||||||
|
<p className="text-slate-600">
|
||||||
|
Panel unificado para gestión integral de pedidos, proveedores y entregas
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<Button variant="outline" className="gap-2">
|
||||||
|
<Search className="w-4 h-4" />
|
||||||
|
Buscar
|
||||||
|
</Button>
|
||||||
|
<Button className="gap-2">
|
||||||
|
<Package className="w-4 h-4" />
|
||||||
|
Nuevo Pedido
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stats Cards */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
||||||
|
<Card className="border-l-4 border-l-yellow-500">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardDescription>Pendientes Decidir</CardDescription>
|
||||||
|
<CardTitle className="text-3xl">12</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex items-center gap-2 text-sm text-yellow-600">
|
||||||
|
<AlertCircle className="w-4 h-4" />
|
||||||
|
<span>Requieren atención</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="border-l-4 border-l-blue-500">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardDescription>Pedidos Proveedor</CardDescription>
|
||||||
|
<CardTitle className="text-3xl">8</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex items-center gap-2 text-sm text-blue-600">
|
||||||
|
<Clock className="w-4 h-4" />
|
||||||
|
<span>En proceso</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="border-l-4 border-l-green-500">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardDescription>Recibidas</CardDescription>
|
||||||
|
<CardTitle className="text-3xl">5</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex items-center gap-2 text-sm text-green-600">
|
||||||
|
<CheckCircle2 className="w-4 h-4" />
|
||||||
|
<span>Listas para entregar</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card className="border-l-4 border-l-purple-500">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardDescription>Volumen Mes</CardDescription>
|
||||||
|
<CardTitle className="text-3xl">€15.4K</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex items-center gap-2 text-sm text-purple-600">
|
||||||
|
<TrendingUp className="w-4 h-4" />
|
||||||
|
<span>+12% vs mes anterior</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tabs Navigation */}
|
||||||
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-6">
|
||||||
|
<TabsList className="grid grid-cols-6 w-full max-w-4xl">
|
||||||
|
<TabsTrigger value="kanban" className="gap-2">
|
||||||
|
<ClipboardList className="w-4 h-4" />
|
||||||
|
Kanban
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="proveedores" className="gap-2">
|
||||||
|
<Truck className="w-4 h-4" />
|
||||||
|
Proveedores
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="albaranes" className="gap-2">
|
||||||
|
<FileText className="w-4 h-4" />
|
||||||
|
Albaranes
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="clientes" className="gap-2">
|
||||||
|
<Users className="w-4 h-4" />
|
||||||
|
Clientes
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="inventario" className="gap-2">
|
||||||
|
<Package className="w-4 h-4" />
|
||||||
|
Inventario
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="config" className="gap-2">
|
||||||
|
<Settings className="w-4 h-4" />
|
||||||
|
Config
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
{/* Kanban View */}
|
||||||
|
<TabsContent value="kanban" className="space-y-4">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||||
|
{/* Columna: Pendiente Decidir */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-yellow-500"></div>
|
||||||
|
<h3 className="font-semibold text-slate-700">Pendiente Decidir</h3>
|
||||||
|
<Badge variant="secondary">1</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{pedidosEjemplo
|
||||||
|
.filter(p => p.estado === 'pendiente_decidir')
|
||||||
|
.map(pedido => (
|
||||||
|
<Card key={pedido.id} className="cursor-pointer hover:shadow-lg transition-shadow">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<div className="flex items-start justify-between mb-2">
|
||||||
|
<Badge className={getPrioridadColor(pedido.prioridad)}>
|
||||||
|
{pedido.prioridad}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-xs text-slate-500">{pedido.referencia}</span>
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-base">{pedido.cliente}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-lg font-bold text-slate-900">
|
||||||
|
€{pedido.total.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-slate-500">{pedido.fecha}</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Columna: Pedido Proveedor */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-blue-500"></div>
|
||||||
|
<h3 className="font-semibold text-slate-700">Pedido Proveedor</h3>
|
||||||
|
<Badge variant="secondary">1</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{pedidosEjemplo
|
||||||
|
.filter(p => p.estado === 'pedido_proveedor')
|
||||||
|
.map(pedido => (
|
||||||
|
<Card key={pedido.id} className="cursor-pointer hover:shadow-lg transition-shadow">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<div className="flex items-start justify-between mb-2">
|
||||||
|
<Badge className={getPrioridadColor(pedido.prioridad)}>
|
||||||
|
{pedido.prioridad}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-xs text-slate-500">{pedido.referencia}</span>
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-base">{pedido.cliente}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-lg font-bold text-slate-900">
|
||||||
|
€{pedido.total.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-slate-500">{pedido.fecha}</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Columna: Recibida */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-green-500"></div>
|
||||||
|
<h3 className="font-semibold text-slate-700">Recibida</h3>
|
||||||
|
<Badge variant="secondary">1</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{pedidosEjemplo
|
||||||
|
.filter(p => p.estado === 'recibida')
|
||||||
|
.map(pedido => (
|
||||||
|
<Card key={pedido.id} className="cursor-pointer hover:shadow-lg transition-shadow">
|
||||||
|
<CardHeader className="pb-3">
|
||||||
|
<div className="flex items-start justify-between mb-2">
|
||||||
|
<Badge className={getPrioridadColor(pedido.prioridad)}>
|
||||||
|
{pedido.prioridad}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-xs text-slate-500">{pedido.referencia}</span>
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-base">{pedido.cliente}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-lg font-bold text-slate-900">
|
||||||
|
€{pedido.total.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-slate-500">{pedido.fecha}</span>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Columna: Entregada */}
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-gray-400"></div>
|
||||||
|
<h3 className="font-semibold text-slate-700">Entregada</h3>
|
||||||
|
<Badge variant="secondary">0</Badge>
|
||||||
|
</div>
|
||||||
|
<Card className="border-dashed border-2 bg-slate-50">
|
||||||
|
<CardContent className="flex items-center justify-center h-32 text-slate-400">
|
||||||
|
Sin pedidos entregados
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
{/* Proveedores View */}
|
||||||
|
<TabsContent value="proveedores">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Panel de Proveedores</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Gestión de proveedores, pedidos y seguimiento de entregas
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex items-center justify-center h-64 text-slate-400">
|
||||||
|
<div className="text-center">
|
||||||
|
<Truck className="w-16 h-16 mx-auto mb-4 text-slate-300" />
|
||||||
|
<p>Próximamente: Panel completo de proveedores</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
{/* Albaranes View */}
|
||||||
|
<TabsContent value="albaranes">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Gestión de Albaranes</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Registro y seguimiento de albaranes de entrega
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex items-center justify-center h-64 text-slate-400">
|
||||||
|
<div className="text-center">
|
||||||
|
<FileText className="w-16 h-16 mx-auto mb-4 text-slate-300" />
|
||||||
|
<p>Próximamente: Sistema de albaranes</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
{/* Clientes View */}
|
||||||
|
<TabsContent value="clientes">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Gestión de Clientes</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Base de datos de clientes y su historial de pedidos
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex items-center justify-center h-64 text-slate-400">
|
||||||
|
<div className="text-center">
|
||||||
|
<Users className="w-16 h-16 mx-auto mb-4 text-slate-300" />
|
||||||
|
<p>Próximamente: Panel de clientes</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
{/* Inventario View */}
|
||||||
|
<TabsContent value="inventario">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Control de Inventario</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Stock, ubicaciones y movimientos de recambios
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex items-center justify-center h-64 text-slate-400">
|
||||||
|
<div className="text-center">
|
||||||
|
<Package className="w-16 h-16 mx-auto mb-4 text-slate-300" />
|
||||||
|
<p>Próximamente: Sistema de inventario</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
{/* Configuración View */}
|
||||||
|
<TabsContent value="config">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Configuración del Sistema</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Preferencias, categorías y parámetros del módulo
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex items-center justify-center h-64 text-slate-400">
|
||||||
|
<div className="text-center">
|
||||||
|
<Settings className="w-16 h-16 mx-auto mb-4 text-slate-300" />
|
||||||
|
<p>Próximamente: Panel de configuración</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RecambiosApp;
|
||||||
9
frontend/src/vite-env.d.ts
vendored
Normal file
9
frontend/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_API_URL: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv
|
||||||
|
}
|
||||||
80
frontend/tailwind.config.ts
Normal file
80
frontend/tailwind.config.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import type { Config } from 'tailwindcss'
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
darkMode: ["class"],
|
||||||
|
content: [
|
||||||
|
'./pages/**/*.{ts,tsx}',
|
||||||
|
'./components/**/*.{ts,tsx}',
|
||||||
|
'./app/**/*.{ts,tsx}',
|
||||||
|
'./src/**/*.{ts,tsx}',
|
||||||
|
],
|
||||||
|
prefix: "",
|
||||||
|
theme: {
|
||||||
|
container: {
|
||||||
|
center: true,
|
||||||
|
padding: "2rem",
|
||||||
|
screens: {
|
||||||
|
"2xl": "1400px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
border: "hsl(var(--border))",
|
||||||
|
input: "hsl(var(--input))",
|
||||||
|
ring: "hsl(var(--ring))",
|
||||||
|
background: "hsl(var(--background))",
|
||||||
|
foreground: "hsl(var(--foreground))",
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "hsl(var(--primary))",
|
||||||
|
foreground: "hsl(var(--primary-foreground))",
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: "hsl(var(--secondary))",
|
||||||
|
foreground: "hsl(var(--secondary-foreground))",
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: "hsl(var(--destructive))",
|
||||||
|
foreground: "hsl(var(--destructive-foreground))",
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: "hsl(var(--muted))",
|
||||||
|
foreground: "hsl(var(--muted-foreground))",
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: "hsl(var(--accent))",
|
||||||
|
foreground: "hsl(var(--accent-foreground))",
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: "hsl(var(--popover))",
|
||||||
|
foreground: "hsl(var(--popover-foreground))",
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
DEFAULT: "hsl(var(--card))",
|
||||||
|
foreground: "hsl(var(--card-foreground))",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
lg: "var(--radius)",
|
||||||
|
md: "calc(var(--radius) - 2px)",
|
||||||
|
sm: "calc(var(--radius) - 4px)",
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
"accordion-down": {
|
||||||
|
from: { height: "0" },
|
||||||
|
to: { height: "var(--radix-accordion-content-height)" },
|
||||||
|
},
|
||||||
|
"accordion-up": {
|
||||||
|
from: { height: "var(--radix-accordion-content-height)" },
|
||||||
|
to: { height: "0" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||||||
|
"accordion-up": "accordion-up 0.2s ease-out",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [require("tailwindcss-animate")],
|
||||||
|
} satisfies Config
|
||||||
|
|
||||||
|
export default config
|
||||||
31
frontend/tsconfig.json
Normal file
31
frontend/tsconfig.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
/* Path mapping */
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
10
frontend/tsconfig.node.json
Normal file
10
frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
server: {
|
server: {
|
||||||
host: true,
|
host: true,
|
||||||
port: 5173,
|
port: 5173,
|
||||||
|
|||||||
22
frontend/vite.config.ts
Normal file
22
frontend/vite.config.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 5173,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:8000',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user