|
1 | | -# React + TypeScript + Vite |
2 | | - |
3 | | -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. |
4 | | - |
5 | | -Currently, two official plugins are available: |
6 | | - |
7 | | -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh |
8 | | -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh |
9 | | - |
10 | | -## Expanding the ESLint configuration |
11 | | - |
12 | | -If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: |
13 | | - |
14 | | -- Configure the top-level `parserOptions` property like this: |
15 | | - |
16 | | -```js |
17 | | -export default tseslint.config({ |
18 | | - languageOptions: { |
19 | | - // other options... |
20 | | - parserOptions: { |
21 | | - project: ['./tsconfig.node.json', './tsconfig.app.json'], |
22 | | - tsconfigRootDir: import.meta.dirname, |
23 | | - }, |
24 | | - }, |
25 | | -}) |
26 | | -``` |
27 | | - |
28 | | -- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` |
29 | | -- Optionally add `...tseslint.configs.stylisticTypeChecked` |
30 | | -- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: |
31 | | - |
32 | | -```js |
33 | | -// eslint.config.js |
34 | | -import react from 'eslint-plugin-react' |
35 | | - |
36 | | -export default tseslint.config({ |
37 | | - // Set the react version |
38 | | - settings: { react: { version: '18.3' } }, |
39 | | - plugins: { |
40 | | - // Add the react plugin |
41 | | - react, |
42 | | - }, |
43 | | - rules: { |
44 | | - // other rules... |
45 | | - // Enable its recommended rules |
46 | | - ...react.configs.recommended.rules, |
47 | | - ...react.configs['jsx-runtime'].rules, |
48 | | - }, |
49 | | -}) |
50 | | -``` |
| 1 | +# FullStack E-commerce Frontend |
| 2 | + |
| 3 | +Una aplicación de e-commerce moderna construida con React Router, TypeScript, Prisma y diseñada específicamente para desarrolladores web. La aplicación incluye un sistema completo de variantes de producto que permite diferentes opciones como tallas y dimensiones. |
| 4 | + |
| 5 | +## 🚀 Características Principales |
| 6 | + |
| 7 | +- **Sistema de Variantes de Producto**: Soporte completo para productos con múltiples opciones (tallas para polos, dimensiones por stickers) |
| 8 | +- **Carrito de Compras Inteligente**: Gestión de productos con sus variantes específicas |
| 9 | +- **Checkout Seguro**: Integración con Culqi para pagos seguros |
| 10 | +- **Chat Bot AI**: Asistente virtual powered by Gemini para recomendaciones de productos |
| 11 | +- **Autenticación Completa**: Sistema de login/registro con gestión de sesiones |
| 12 | +- **Responsive Design**: Interfaz adaptativa con Tailwind CSS |
| 13 | + |
| 14 | +## 📋 Implementación de Variantes de Producto |
| 15 | + |
| 16 | +### 🗄️ Modificaciones en la Base de Datos |
| 17 | + |
| 18 | +El sistema de variantes se implementó mediante las siguientes entidades en [prisma/schema.prisma](prisma/schema.prisma): |
| 19 | + |
| 20 | +```prisma |
| 21 | +model VariantAttribute { |
| 22 | + id Int @id @default(autoincrement()) |
| 23 | + name String // "talla", "dimensiones", "color" |
| 24 | + |
| 25 | + variantAttributeValues VariantAttributeValue[] |
| 26 | +} |
| 27 | +
|
| 28 | +model VariantAttributeValue { |
| 29 | + id Int @id @default(autoincrement()) |
| 30 | + variantAttributeId Int @map("variant_attribute_id") |
| 31 | + productId Int @map("product_id") |
| 32 | + value String // "Small", "Medium", "Large", "3x3cm" |
| 33 | + price Decimal @db.Decimal(10, 2) |
| 34 | + |
| 35 | + variantAttribute VariantAttribute @relation(fields: [variantAttributeId], references: [id]) |
| 36 | + product Product @relation(fields: [productId], references: [id]) |
| 37 | + cartItems CartItem[] |
| 38 | +} |
| 39 | +``` |
| 40 | + |
| 41 | +### 🎨 Interfaz de Usuario para Variantes |
| 42 | + |
| 43 | +#### Página de Producto ([src/routes/product/index.tsx](src/routes/product/index.tsx)) |
| 44 | + |
| 45 | +La página de producto fue actualizada para mostrar selectores de variantes dinámicos: |
| 46 | + |
| 47 | +- **Detección Automática**: Solo muestra selectores para productos con variantes (polos y stickers) |
| 48 | +- **Agrupación por Atributo**: Las variantes se agrupan por tipo (talla, dimensión) |
| 49 | +- **Actualización de Precio**: El precio se actualiza automáticamente al seleccionar una variante |
| 50 | + |
| 51 | +```tsx |
| 52 | +// Ejemplo de selector de variantes |
| 53 | +{shouldShowVariants && ( |
| 54 | + <> |
| 55 | + {Object.entries(variantGroups).map(([attributeName, variants]) => ( |
| 56 | + <div key={attributeName} className="mb-9"> |
| 57 | + <p className="text-sm font-semibold text-accent-foreground mb-2"> |
| 58 | + {attributeName.charAt(0).toUpperCase() + attributeName.slice(1)} |
| 59 | + </p> |
| 60 | + <div className="flex gap-2"> |
| 61 | + {variants!.map((variant) => ( |
| 62 | + <Button |
| 63 | + key={variant.id} |
| 64 | + variant={selectedVariant === variant.id ? "default" : "secondary"} |
| 65 | + size="lg" |
| 66 | + className="w-[70px]" |
| 67 | + onClick={() => handleVariantChange(variant.id)} |
| 68 | + > |
| 69 | + {variant.value} |
| 70 | + </Button> |
| 71 | + ))} |
| 72 | + </div> |
| 73 | + </div> |
| 74 | + ))} |
| 75 | + </> |
| 76 | +)} |
| 77 | +``` |
| 78 | + |
| 79 | +#### Carrito de Compras ([src/routes/cart/index.tsx](src/routes/cart/index.tsx)) |
| 80 | + |
| 81 | +El carrito fue actualizado para mostrar la información completa de las variantes: |
| 82 | + |
| 83 | +- **Visualización de Variantes**: Muestra el valor de la variante seleccionada (ej: "Polo React (Medium)") |
| 84 | +- **Gestión de Cantidades**: Cada variante se trata como un item único en el carrito |
| 85 | +- **Precios Específicos**: Refleja el precio exacto de cada variante |
| 86 | + |
| 87 | +```tsx |
| 88 | +// Ejemplo de visualización en el carrito |
| 89 | +<h3 className="text-sm leading-5 font-medium mb-1"> |
| 90 | + {product.title} ({variantAttributeValue?.value}) |
| 91 | +</h3> |
| 92 | +<p className="text-sm text-muted-foreground"> |
| 93 | + S/{product.price!.toFixed(2)} c/u |
| 94 | +</p> |
| 95 | +``` |
| 96 | + |
| 97 | +#### Checkout ([src/routes/checkout/index.tsx](src/routes/checkout/index.tsx)) |
| 98 | + |
| 99 | +El proceso de checkout mantiene la información de variantes: |
| 100 | + |
| 101 | +- **Resumen Detallado**: Muestra cada producto con su variante específica |
| 102 | +- **Cálculo Correcto**: Los totales reflejan los precios exactos de cada variante |
| 103 | +- **Información Completa**: Se preserva toda la información para la orden final |
| 104 | + |
| 105 | +### 🛒 Lógica de Negocio |
| 106 | + |
| 107 | +#### Gestión de Carrito ([src/services/cart.service.ts](src/services/cart.service.ts)) |
| 108 | + |
| 109 | +```typescript |
| 110 | +// Agregar producto con variante específica |
| 111 | +export async function addToCart( |
| 112 | + userId: number | undefined, |
| 113 | + sessionCartId: string | undefined, |
| 114 | + attributeValueId: number |
| 115 | +): Promise<CartWithItems> |
| 116 | +``` |
| 117 | + |
| 118 | +### 🔍 Lógica de Filtrado por Precios con Variantes |
| 119 | + |
| 120 | +El sistema de filtros implementa una lógica inteligente para productos con variantes: |
| 121 | + |
| 122 | +#### Comportamiento del Filtro |
| 123 | +- **Evaluación por Variante**: El filtro analiza cada variante de precio individualmente |
| 124 | +- **Inclusión Condicional**: Un producto se incluye si AL MENOS UNA de sus variantes está dentro del rango seleccionado |
| 125 | +- **Visualización Resumida**: En las tarjetas se muestra el rango completo (precio mínimo - precio máximo) |
| 126 | + |
| 127 | +#### Ejemplo Práctico |
| 128 | +``` |
| 129 | +Producto: Sticker JavaScript |
| 130 | +Variantes: S/2.99, S/3.99, S/4.99 |
| 131 | +Rango mostrado: S/2.99 - S/4.99 |
| 132 | + |
| 133 | +Filtro S/1 - S/3: |
| 134 | +✅ Incluido (porque S/2.99 está en el rango) |
| 135 | + |
| 136 | +Filtro S/5 - S/10: |
| 137 | +❌ Excluido (ninguna variante está en el rango) |
| 138 | +``` |
| 139 | +
|
| 140 | +<!-- #### Creación de Órdenes ([src/services/order.service.ts](src/services/order.service.ts)) |
| 141 | +
|
| 142 | +Las órdenes preservan la información completa de variantes: |
| 143 | +
|
| 144 | +```typescript |
| 145 | +const items = cartItems.map((item) => ({ |
| 146 | + productId: item.product.id, |
| 147 | + quantity: item.quantity, |
| 148 | + title: item.product.title, |
| 149 | + price: item.product.price, |
| 150 | + imgSrc: item.product.imgSrc, |
| 151 | + // Información de variante preservada |
| 152 | +})); |
| 153 | +``` --> |
| 154 | +
|
| 155 | +### 🤖 Integración con Chat Bot |
| 156 | +
|
| 157 | +El chat bot fue actualizado para manejar preguntas sobre variantes ([src/services/chat-system-prompt.ts](src/services/chat-system-prompt.ts)): |
| 158 | +
|
| 159 | +#### Manejo Inteligente de Variantes |
| 160 | +
|
| 161 | +```typescript |
| 162 | +// Formatear variantes según el tipo |
| 163 | +switch (product.variantType) { |
| 164 | + case 'talla': |
| 165 | + const sizes = product.variants.map(v => v.value).join(", "); |
| 166 | + variantDisplay = `\n- 👕 Tallas disponibles: ${sizes}`; |
| 167 | + break; |
| 168 | + case 'dimensión': |
| 169 | + const dimensions = product.variants |
| 170 | + .map(v => `${v.value} (S/${v.price})`) |
| 171 | + .join(", "); |
| 172 | + variantDisplay = `\n- 📐 Dimensiones: ${dimensions}`; |
| 173 | + break; |
| 174 | +} |
| 175 | +``` |
| 176 | + |
| 177 | +#### Respuestas Contextuales |
| 178 | + |
| 179 | +- **Polos**: "¿Qué talla necesitas: Small, Medium o Large?" |
| 180 | +- **Stickers**: "Tenemos 3 tamaños: 3x3cm (S/2.99), 5x5cm (S/3.99) o 10x10cm (S/4.99)" |
| 181 | +- **Tazas**: Procede normal sin mencionar variantes |
| 182 | + |
| 183 | +<!-- ## 🧪 Testing |
| 184 | +
|
| 185 | +### Tests de Componentes |
| 186 | +
|
| 187 | +Los tests fueron actualizados para cubrir los casos de variantes: |
| 188 | +
|
| 189 | +#### Test de Producto ([src/routes/product/product.test.tsx](src/routes/product/product.test.tsx)) |
| 190 | +
|
| 191 | +```typescript |
| 192 | +const createTestProps = (overrides = {}): Route.ComponentProps => ({ |
| 193 | + loaderData: { |
| 194 | + product: createTestProduct({ |
| 195 | + variantAttributeValues: [ |
| 196 | + createTestVariantAttributeValue({ id: 1, value: "Small", price: 20 }), |
| 197 | + createTestVariantAttributeValue({ id: 2, value: "Medium", price: 20 }), |
| 198 | + ], |
| 199 | + ...overrides, |
| 200 | + }), |
| 201 | + }, |
| 202 | + // ... otros props |
| 203 | +}); |
| 204 | +``` |
| 205 | +
|
| 206 | +#### Test de Confirmación de Orden ([src/routes/order-confirmation/order-confirmation.test.tsx](src/routes/order-confirmation/order-confirmation.test.tsx)) |
| 207 | +
|
| 208 | +```typescript |
| 209 | +const createTestProps = (orderId = "test-123"): Route.ComponentProps => ({ |
| 210 | + loaderData: { orderId }, |
| 211 | + params: { orderId }, |
| 212 | + matches: [] as unknown as Route.ComponentProps["matches"], |
| 213 | +}); |
| 214 | +``` |
| 215 | +
|
| 216 | +### Tests E2E |
| 217 | +
|
| 218 | +Los tests end-to-end cubren el flujo completo con variantes ([src/e2e/guest-create-order.spec.ts](src/e2e/guest-create-order.spec.ts)): |
| 219 | +
|
| 220 | +```typescript |
| 221 | +test("Guest can create an order", async ({ page }) => { |
| 222 | + // Navegar y seleccionar producto |
| 223 | + await page.goto(baseUrl); |
| 224 | + await page.getByRole("menuitem", { name: "Polos" }).click(); |
| 225 | + await page.getByTestId("product-item").first().click(); |
| 226 | + |
| 227 | + // Agregar al carrito (automáticamente selecciona primera variante) |
| 228 | + await page.getByRole("button", { name: "Agregar al Carrito" }).click(); |
| 229 | + |
| 230 | + // Continuar con checkout... |
| 231 | +}); |
| 232 | +``` --> |
| 233 | + |
| 234 | +## 📱 Consideraciones de UX/UI |
| 235 | + |
| 236 | +### Decisiones de Diseño para Variantes |
| 237 | + |
| 238 | +1. **Mostrar Solo Cuando Necesario**: Los selectores de variantes solo aparecen para productos que las tienen |
| 239 | +2. **Agrupación Clara**: Las variantes se agrupan por tipo de atributo (talla, dimensión) |
| 240 | +3. **Feedback Visual**: El botón seleccionado tiene un estilo diferente |
| 241 | +4. **Precio Dinámico**: El precio se actualiza inmediatamente al cambiar variantes |
| 242 | +5. **Validación**: No se puede agregar al carrito sin seleccionar una variante |
| 243 | + |
| 244 | +### Manejo de Precios |
| 245 | + |
| 246 | +- **Productos Sin Variantes**: Muestran precio fijo |
| 247 | +- **Productos Con Variantes**: Muestran rango de precios, es decir el precio mínimo y máximo |
| 248 | +- **Carrito y Checkout**: Siempre muestran precios específicos de la variante |
| 249 | + |
| 250 | + |
| 251 | +## 🔧 Instalación y Configuración |
| 252 | + |
| 253 | +```bash |
| 254 | +# Instalar dependencias |
| 255 | +npm install |
| 256 | + |
| 257 | +# Configurar base de datos |
| 258 | +npm run prisma:generate |
| 259 | +npm run prisma:migrate:dev |
| 260 | + |
| 261 | +# Poblar con datos iniciales |
| 262 | +npm run prisma:seed |
| 263 | + |
| 264 | +# Ejecutar en desarrollo |
| 265 | +npm run dev |
| 266 | + |
| 267 | +# Ejecutar tests |
| 268 | +npm run test |
| 269 | +npm run test:e2e |
| 270 | +``` |
| 271 | + |
| 272 | +### Productos con Variantes |
| 273 | + |
| 274 | +```typescript |
| 275 | +// Polos - Variantes por talla |
| 276 | +{ |
| 277 | + title: "Polo React", |
| 278 | + variantAttributeValues: [ |
| 279 | + { value: "Small", price: 20.0, variantAttribute: { name: "talla" } }, |
| 280 | + { value: "Medium", price: 20.0, variantAttribute: { name: "talla" } }, |
| 281 | + { value: "Large", price: 20.0, variantAttribute: { name: "talla" } } |
| 282 | + ] |
| 283 | +} |
| 284 | + |
| 285 | +// Stickers - Variantes por dimensión |
| 286 | +{ |
| 287 | + title: "Sticker Docker", |
| 288 | + variantAttributeValues: [ |
| 289 | + { value: "3x3cm", price: 2.99, variantAttribute: { name: "dimensiones" } }, |
| 290 | + { value: "5x5cm", price: 3.99, variantAttribute: { name: "dimensiones" } }, |
| 291 | + { value: "10x10cm", price: 4.99, variantAttribute: { name: "dimensiones" } } |
| 292 | + ] |
| 293 | +} |
| 294 | +``` |
| 295 | + |
| 296 | +## 🚀 Tecnologías Utilizadas |
| 297 | + |
| 298 | +- **Frontend**: React Router v7, TypeScript, Tailwind CSS |
| 299 | +- **Backend**: Node.js con React Router Server Functions |
| 300 | +- **Base de Datos**: PostgreSQL con Prisma ORM |
| 301 | +- **Pagos**: Culqi Payment Gateway |
| 302 | +- **AI**: Google Gemini para el chat bot |
| 303 | +- **Testing**: Vitest, Playwright, React Testing Library |
| 304 | +- **Deployment**: Docker, Railway |
| 305 | + |
| 306 | +## 📈 Tareas realizadas por cada integrante |
| 307 | + |
| 308 | +### 👨💻 Mike Vera |
| 309 | +- **Sistema de Variantes de Producto**: Implementación completa del sistema de variantes con base de datos (Prisma schema) |
| 310 | +- **Carrito con Variantes**: Actualización del carrito para mostrar información detallada de variantes seleccionadas |
| 311 | + |
| 312 | +### 👨💻 Sebastian |
| 313 | +- **UI/UX Design & Responsive Design**: Diseño e implementación de la interfaz adaptativa con Tailwind CSS |
| 314 | +- **Chat Bot AI**: Integración con Google Gemini para el asistente virtual y sistema de recomendaciones |
| 315 | + |
| 316 | +### 👩💻 Janet |
| 317 | +- **Modificacion del archivo de data inicial**: Modificación del archivo 'initial_data.ts' para las diferentes variables de productos |
| 318 | +- **Actualización del servicio de productos**: Modificación de las funciones para integrar variantes de productos |
| 319 | +- **Filtros de Precio Inteligentes**: Implementación de la lógica de filtrado que considera todas las variantes de precio |
| 320 | + |
| 321 | +<!-- - **Testing de Variantes**: Actualización de tests unitarios y E2E para cubrir casos de uso con variantes --> |
| 322 | + |
| 323 | +### 🤝 Tareas Colaborativas |
| 324 | +- **Arquitectura del Proyecto**: Definición conjunta de la estructura para contemplar variables de productos |
| 325 | +- **Code Reviews**: Revisión cruzada de código y merge de pull requests entre los 3 integrantes |
| 326 | +<!-- - **Testing Strategy**: Definición colaborativa de estrategias de testing y coverage mínimo --> |
| 327 | +- **Integración de Features**: Trabajo conjunto para integrar variantes, en los diferente modulos, cart, |
| 328 | + |
| 329 | +### 🔧 Metodología de Trabajo |
| 330 | +- **Control de Versiones**: Git con GitHub Flow y branches por feature |
| 331 | +- **Gestión de Proyecto**: Kanban board para tracking de tareas y sprints semanales |
| 332 | +- **Comunicación**: Daily standups virtuales y sesiones de pair programming |
| 333 | +- **Calidad de Código**: ESLint, Prettier y Husky configurados en conjunto |
| 334 | + |
| 335 | +--- |
| 336 | + |
| 337 | +*Este proyecto fue desarrollado como parte del bootcamp de Codeable, implementando un sistema completo de e-commerce con variantes de producto para una experiencia de usuario optimizada.* |
0 commit comments