Files
bakery-ia/frontend/src/utils/constants.ts

368 lines
12 KiB
TypeScript
Raw Normal View History

2025-08-28 10:41:04 +02:00
/**
* Application constants and configuration values
*/
// Application metadata
export const APP_INFO = {
name: 'Bakery IA',
version: '1.0.0',
description: 'Sistema inteligente de gestión para panaderías',
author: 'Bakery IA Team',
support_email: 'support@bakeryia.com',
website: 'https://bakeryia.com',
} as const;
// API configuration
export const API_CONFIG = {
base_url: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api/v1',
timeout: 30000,
retry_attempts: 3,
retry_delay: 1000,
} as const;
// Authentication
export const AUTH_CONFIG = {
token_key: 'access_token',
refresh_token_key: 'refresh_token',
user_data_key: 'user_data',
tenant_id_key: 'tenant_id',
session_timeout: 24 * 60 * 60 * 1000, // 24 hours
refresh_threshold: 5 * 60 * 1000, // 5 minutes before expiry
} as const;
// Pagination defaults
export const PAGINATION = {
default_page_size: 20,
max_page_size: 100,
page_size_options: [10, 20, 50, 100],
} as const;
// Date and time formats
export const DATE_FORMATS = {
display: 'dd/MM/yyyy',
display_with_time: 'dd/MM/yyyy HH:mm',
iso: 'yyyy-MM-dd',
iso_with_time: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
time_only: 'HH:mm',
month_year: 'MM/yyyy',
full_date: 'EEEE, dd MMMM yyyy',
} as const;
// Spanish locale settings
export const LOCALE_SETTINGS = {
locale: 'es-ES',
timezone: 'Europe/Madrid',
currency: 'EUR',
date_separator: '/',
decimal_separator: ',',
thousand_separator: '.',
} as const;
// Business hours
export const BUSINESS_HOURS = {
default_open: '06:00',
default_close: '20:00',
days_of_week: ['lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado', 'domingo'],
work_days: ['lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'],
} as const;
// Units of measure
export const UNITS_OF_MEASURE = {
weight: {
kg: { label: 'Kilogramo', symbol: 'kg', factor: 1000 },
g: { label: 'Gramo', symbol: 'g', factor: 1 },
lb: { label: 'Libra', symbol: 'lb', factor: 453.592 },
oz: { label: 'Onza', symbol: 'oz', factor: 28.3495 },
},
volume: {
l: { label: 'Litro', symbol: 'l', factor: 1000 },
ml: { label: 'Mililitro', symbol: 'ml', factor: 1 },
},
count: {
Fix invalid unit_of_measure 'dozen' causing 422 API errors **Issue:** Creating ingredients failed with 422 error: ``` Input should be 'kg', 'g', 'l', 'ml', 'units', 'pcs', 'pkg', 'bags' or 'boxes' input: "dozen" ``` **Root Cause:** Frontend was using units not supported by backend UnitOfMeasure enum: - "dozen" (docena) - "cup" (taza) - "tbsp" (cucharada) - "tsp" (cucharadita) - "piece" → should be "pcs" - "package" → should be "pkg" - "bag" → should be "bags" - "box" → should be "boxes" **Backend Supported Units (inventory.ts:28-38):** kg, g, l, ml, units, pcs, pkg, bags, boxes **Solution:** Replaced all invalid units with backend-compatible ones across codebase. **Files Modified:** 1. **UploadSalesDataStep.tsx:604** - Before: ['kg', 'g', 'L', 'ml', 'units', 'dozen'] - After: ['kg', 'g', 'l', 'ml', 'units', 'pcs', 'pkg', 'bags', 'boxes'] 2. **BatchAddIngredientsModal.tsx:53** - Before: ['kg', 'g', 'L', 'ml', 'units', 'dozen'] - After: ['kg', 'g', 'l', 'ml', 'units', 'pcs', 'pkg', 'bags', 'boxes'] 3. **QuickAddIngredientModal.tsx:69** - Before: ['kg', 'g', 'L', 'ml', 'units', 'dozen'] - After: ['kg', 'g', 'l', 'ml', 'units', 'pcs', 'pkg', 'bags', 'boxes'] 4. **inventory/index.ts:51-62** - Removed: 'piece', 'package', 'bag', 'box', 'dozen', 'cup', 'tbsp', 'tsp' - Added: 'units', 'pcs', 'pkg', 'bags', 'boxes' - Added comment: "must match backend UnitOfMeasure enum exactly" 5. **ingredientHelpers.ts:168** - Eggs unit changed from 'dozen' → 'units' 6. **utils/constants.ts:77-87** - Removed volume units: cup, tbsp, tsp - Removed count units: piece, dozen, package, bag, box - Added: units, pcs, pkg, bags, boxes - Now matches backend enum exactly **Also Fixed:** - Changed 'L' to lowercase 'l' for consistency with backend **Impact:** ✅ All ingredient creation now uses valid backend units ✅ No more 422 validation errors ✅ Frontend/backend unit enums synchronized **Build Status:** ✓ Successful in 22.23s
2025-11-07 08:36:35 +00:00
units: { label: 'Unidades', symbol: 'ud', factor: 1 },
pcs: { label: 'Piezas', symbol: 'pz', factor: 1 },
pkg: { label: 'Paquetes', symbol: 'paq', factor: 1 },
bags: { label: 'Bolsas', symbol: 'bolsa', factor: 1 },
boxes: { label: 'Cajas', symbol: 'caja', factor: 1 },
2025-08-28 10:41:04 +02:00
},
} as const;
// Temperature units
export const TEMPERATURE_UNITS = {
celsius: { label: 'Celsius', symbol: '°C' },
fahrenheit: { label: 'Fahrenheit', symbol: '°F' },
} as const;
// Product categories
export const PRODUCT_CATEGORIES = {
bread: {
label: 'Pan',
subcategories: ['Pan blanco', 'Pan integral', 'Pan de centeno', 'Pan especial'],
},
pastries: {
label: 'Bollería',
subcategories: ['Croissants', 'Magdalenas', 'Donuts', 'Ensaimadas'],
},
cakes: {
label: 'Tartas',
subcategories: ['Tartas de cumpleaños', 'Tartas de boda', 'Cheesecakes', 'Tartas de frutas'],
},
cookies: {
label: 'Galletas',
subcategories: ['Galletas caseras', 'Galletas decoradas', 'Galletas integrales'],
},
seasonal: {
label: 'Productos estacionales',
subcategories: ['Roscón de Reyes', 'Torrijas', 'Polvorones', 'Turrones'],
},
} as const;
// Ingredient categories
export const INGREDIENT_CATEGORIES = {
flours: {
label: 'Harinas',
items: ['Harina de trigo', 'Harina integral', 'Harina de centeno', 'Harina de maíz'],
},
sugars: {
label: 'Azúcares',
items: ['Azúcar blanco', 'Azúcar moreno', 'Azúcar glass', 'Miel'],
},
fats: {
label: 'Grasas',
items: ['Mantequilla', 'Margarina', 'Aceite de oliva', 'Aceite de girasol'],
},
dairy: {
label: 'Lácteos',
items: ['Leche', 'Nata', 'Queso', 'Yogur'],
},
eggs: {
label: 'Huevos',
items: ['Huevos frescos', 'Clara de huevo', 'Yema de huevo'],
},
leavening: {
label: 'Levaduras',
items: ['Levadura fresca', 'Levadura seca', 'Levadura química', 'Bicarbonato'],
},
spices: {
label: 'Especias',
items: ['Canela', 'Vainilla', 'Anís', 'Cardamomo'],
},
additives: {
label: 'Aditivos',
items: ['Conservantes', 'Colorantes', 'Aromas', 'Mejorantes'],
},
} as const;
// Quality statuses
export const QUALITY_STATUSES = [
{ value: 'excellent', label: 'Excelente', color: '#22c55e' },
{ value: 'good', label: 'Bueno', color: '#84cc16' },
{ value: 'fair', label: 'Regular', color: '#eab308' },
{ value: 'poor', label: 'Malo', color: '#f97316' },
{ value: 'damaged', label: 'Dañado', color: '#ef4444' },
{ value: 'expired', label: 'Caducado', color: '#dc2626' },
{ value: 'quarantine', label: 'Cuarentena', color: '#8b5cf6' },
] as const;
// Alert severities
export const ALERT_SEVERITIES = [
{ value: 'low', label: 'Baja', color: '#6b7280' },
{ value: 'medium', label: 'Media', color: '#f59e0b' },
{ value: 'high', label: 'Alta', color: '#f97316' },
{ value: 'critical', label: 'Crítica', color: '#dc2626' },
] as const;
// Order statuses
export const ORDER_STATUSES = [
{ value: 'pending', label: 'Pendiente', color: '#6b7280' },
{ value: 'confirmed', label: 'Confirmado', color: '#3b82f6' },
{ value: 'in_preparation', label: 'En preparación', color: '#f59e0b' },
{ value: 'ready', label: 'Listo', color: '#10b981' },
{ value: 'delivered', label: 'Entregado', color: '#22c55e' },
{ value: 'cancelled', label: 'Cancelado', color: '#ef4444' },
] as const;
// Production statuses
export const PRODUCTION_STATUSES = [
{ value: 'planned', label: 'Planificado', color: '#6b7280' },
{ value: 'ready_to_start', label: 'Listo para iniciar', color: '#3b82f6' },
{ value: 'in_progress', label: 'En progreso', color: '#f59e0b' },
{ value: 'quality_check', label: 'Control de calidad', color: '#8b5cf6' },
{ value: 'completed', label: 'Completado', color: '#22c55e' },
{ value: 'cancelled', label: 'Cancelado', color: '#ef4444' },
{ value: 'on_hold', label: 'En pausa', color: '#f97316' },
{ value: 'failed', label: 'Fallido', color: '#dc2626' },
] as const;
// Payment methods
export const PAYMENT_METHODS = [
{ value: 'cash', label: 'Efectivo', icon: '💵' },
{ value: 'card', label: 'Tarjeta', icon: '💳' },
{ value: 'digital_wallet', label: 'Wallet digital', icon: '📱' },
{ value: 'bank_transfer', label: 'Transferencia', icon: '🏦' },
{ value: 'check', label: 'Cheque', icon: '📝' },
{ value: 'store_credit', label: 'Crédito tienda', icon: '🎫' },
] as const;
// Weather conditions
export const WEATHER_CONDITIONS = [
{ value: 'sunny', label: 'Soleado', icon: '☀️', impact: 'positive' },
{ value: 'cloudy', label: 'Nublado', icon: '☁️', impact: 'neutral' },
{ value: 'rainy', label: 'Lluvioso', icon: '🌧️', impact: 'negative' },
{ value: 'stormy', label: 'Tormentoso', icon: '⛈️', impact: 'negative' },
{ value: 'snowy', label: 'Nevado', icon: '❄️', impact: 'negative' },
{ value: 'foggy', label: 'Con niebla', icon: '🌫️', impact: 'negative' },
{ value: 'windy', label: 'Ventoso', icon: '💨', impact: 'neutral' },
] as const;
// File upload limits
export const FILE_UPLOAD = {
max_size: 10 * 1024 * 1024, // 10MB
allowed_types: {
images: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
documents: ['application/pdf', 'text/csv', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
all: ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'application/pdf', 'text/csv', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
},
max_files: 5,
} as const;
// Notification settings
export const NOTIFICATION_SETTINGS = {
default_duration: 5000,
max_notifications: 10,
positions: ['top-right', 'top-left', 'bottom-right', 'bottom-left'],
types: ['success', 'error', 'warning', 'info'],
} as const;
// Chart colors
export const CHART_COLORS = {
primary: ['#3b82f6', '#1d4ed8', '#1e40af', '#1e3a8a'],
success: ['#22c55e', '#16a34a', '#15803d', '#166534'],
warning: ['#f59e0b', '#d97706', '#b45309', '#92400e'],
error: ['#ef4444', '#dc2626', '#b91c1c', '#991b1b'],
neutral: ['#6b7280', '#4b5563', '#374151', '#1f2937'],
rainbow: [
'#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#8b5cf6',
'#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1',
],
} as const;
// Breakpoints for responsive design
export const BREAKPOINTS = {
xs: '320px',
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
} as const;
// Z-index layers
export const Z_INDEX = {
base: 0,
dropdown: 10,
sticky: 20,
fixed: 30,
overlay: 40,
modal: 50,
popover: 60,
tooltip: 70,
toast: 80,
loading: 90,
maximum: 100,
} as const;
// Animation durations
export const ANIMATION_DURATION = {
fast: '150ms',
normal: '250ms',
slow: '350ms',
} as const;
// Border radius values
export const BORDER_RADIUS = {
none: '0',
sm: '0.25rem',
base: '0.375rem',
md: '0.5rem',
lg: '0.75rem',
xl: '1rem',
full: '9999px',
} as const;
// Shadow values
export const SHADOWS = {
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
base: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
} as const;
// Local storage keys
export const STORAGE_KEYS = {
theme: 'app_theme',
language: 'app_language',
sidebar_collapsed: 'sidebar_collapsed',
user_preferences: 'user_preferences',
recent_searches: 'recent_searches',
draft_forms: 'draft_forms',
tutorial_completed: 'tutorial_completed',
} as const;
// Regular expressions
export const REGEX_PATTERNS = {
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
phone_spain: /^[6789]\d{8}$/,
postal_code_spain: /^[0-5]\d{4}$/,
alphanumeric: /^[a-zA-Z0-9]+$/,
numbers_only: /^\d+$/,
letters_only: /^[a-zA-ZáéíóúüñÁÉÍÓÚÜÑ\s]+$/,
strong_password: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
url: /^https?:\/\/([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/,
slug: /^[a-z0-9]+(?:-[a-z0-9]+)*$/,
hex_color: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/,
} as const;
// Error codes
export const ERROR_CODES = {
NETWORK_ERROR: 'NETWORK_ERROR',
AUTHENTICATION_FAILED: 'AUTH_001',
UNAUTHORIZED: 'AUTH_002',
FORBIDDEN: 'AUTH_003',
VALIDATION_ERROR: 'VAL_001',
NOT_FOUND: 'NOT_FOUND',
SERVER_ERROR: 'SERVER_ERROR',
RATE_LIMITED: 'RATE_LIMITED',
MAINTENANCE_MODE: 'MAINTENANCE',
} as const;
// Success messages
export const SUCCESS_MESSAGES = {
SAVED: 'Guardado correctamente',
CREATED: 'Creado correctamente',
UPDATED: 'Actualizado correctamente',
DELETED: 'Eliminado correctamente',
SENT: 'Enviado correctamente',
IMPORTED: 'Importado correctamente',
EXPORTED: 'Exportado correctamente',
LOGGED_IN: 'Sesión iniciada',
LOGGED_OUT: 'Sesión cerrada',
} as const;
// Error messages
export const ERROR_MESSAGES = {
REQUIRED_FIELD: 'Este campo es obligatorio',
INVALID_EMAIL: 'Email no válido',
INVALID_PHONE: 'Teléfono no válido',
WEAK_PASSWORD: 'La contraseña debe ser más segura',
PASSWORDS_NOT_MATCH: 'Las contraseñas no coinciden',
NETWORK_ERROR: 'Error de conexión',
SERVER_ERROR: 'Error del servidor',
UNAUTHORIZED: 'No autorizado',
FORBIDDEN: 'Acceso denegado',
NOT_FOUND: 'No encontrado',
VALIDATION_ERROR: 'Error de validación',
FILE_TOO_LARGE: 'Archivo demasiado grande',
INVALID_FILE_TYPE: 'Tipo de archivo no válido',
} as const;