diff --git a/frontend/src/components/domain/unified-wizard/ItemTypeSelector.tsx b/frontend/src/components/domain/unified-wizard/ItemTypeSelector.tsx index fd734eca..212df639 100644 --- a/frontend/src/components/domain/unified-wizard/ItemTypeSelector.tsx +++ b/frontend/src/components/domain/unified-wizard/ItemTypeSelector.tsx @@ -42,119 +42,64 @@ export interface ItemTypeConfig { keywords?: string[]; } -export const ITEM_TYPES: ItemTypeConfig[] = [ - { - id: 'sales-entry', - title: 'Registro de Ventas', - subtitle: 'Manual o carga masiva', +// Base item types with icon and category only - titles/subtitles will be translated +export const ITEM_TYPE_CONFIGS: Record; category: 'daily' | 'common' | 'setup'; badgeColor: string }> = { + 'sales-entry': { icon: EuroIcon, - badge: '⭐ Más Común', + category: 'daily', badgeColor: 'bg-gradient-to-r from-amber-100 to-orange-100 text-orange-800 font-semibold', - isHighlighted: true, - category: 'daily', - keywords: ['ventas', 'sales', 'ingresos', 'caja', 'revenue'], }, - { - id: 'inventory', - title: 'Inventario', - subtitle: 'Ingrediente o Producto', + 'inventory': { icon: Package, - badge: 'Configuración', - badgeColor: 'bg-blue-100 text-blue-700', category: 'setup', - keywords: ['inventario', 'inventory', 'stock', 'ingredientes', 'productos'], + badgeColor: 'bg-blue-100 text-blue-700', }, - { - id: 'supplier', - title: 'Proveedor', - subtitle: 'Relación comercial', + 'supplier': { icon: Building, - badge: 'Configuración', - badgeColor: 'bg-blue-100 text-blue-700', category: 'setup', - keywords: ['proveedor', 'supplier', 'vendor', 'distribuidor'], + badgeColor: 'bg-blue-100 text-blue-700', }, - { - id: 'recipe', - title: 'Receta', - subtitle: 'Fórmula de producción', + 'recipe': { icon: ChefHat, - badge: 'Común', - badgeColor: 'bg-green-100 text-green-700', category: 'common', - keywords: ['receta', 'recipe', 'formula', 'producción'], + badgeColor: 'bg-green-100 text-green-700', }, - { - id: 'equipment', - title: 'Equipo', - subtitle: 'Activo de panadería', + 'equipment': { icon: Wrench, - badge: 'Configuración', - badgeColor: 'bg-blue-100 text-blue-700', category: 'setup', - keywords: ['equipo', 'equipment', 'maquinaria', 'horno', 'mixer'], + badgeColor: 'bg-blue-100 text-blue-700', }, - { - id: 'quality-template', - title: 'Plantilla de Calidad', - subtitle: 'Estándares y controles', + 'quality-template': { icon: ClipboardCheck, - badge: 'Configuración', - badgeColor: 'bg-blue-100 text-blue-700', category: 'setup', - keywords: ['calidad', 'quality', 'control', 'estándares', 'inspección'], + badgeColor: 'bg-blue-100 text-blue-700', }, - { - id: 'customer-order', - title: 'Pedido de Cliente', - subtitle: 'Nueva orden', + 'customer-order': { icon: ShoppingCart, - badge: 'Diario', - badgeColor: 'bg-amber-100 text-amber-700', category: 'daily', - keywords: ['pedido', 'order', 'cliente', 'customer', 'orden'], + badgeColor: 'bg-amber-100 text-amber-700', }, - { - id: 'customer', - title: 'Cliente', - subtitle: 'Perfil de cliente', + 'customer': { icon: Users, - badge: 'Común', - badgeColor: 'bg-green-100 text-green-700', category: 'common', - keywords: ['cliente', 'customer', 'comprador', 'contacto'], + badgeColor: 'bg-green-100 text-green-700', }, - { - id: 'team-member', - title: 'Miembro del Equipo', - subtitle: 'Empleado o colaborador', + 'team-member': { icon: UserPlus, - badge: 'Configuración', - badgeColor: 'bg-blue-100 text-blue-700', category: 'setup', - keywords: ['empleado', 'employee', 'team', 'staff', 'usuario'], + badgeColor: 'bg-blue-100 text-blue-700', }, - { - id: 'purchase-order', - title: 'Orden de Compra', - subtitle: 'Compra a proveedor', + 'purchase-order': { icon: FileText, - badge: 'Diario', - badgeColor: 'bg-amber-100 text-amber-700', category: 'daily', - keywords: ['compra', 'purchase', 'orden', 'proveedor', 'abastecimiento'], + badgeColor: 'bg-amber-100 text-amber-700', }, - { - id: 'production-batch', - title: 'Lote de Producción', - subtitle: 'Nueva orden de producción', + 'production-batch': { icon: Factory, - badge: 'Diario', - badgeColor: 'bg-amber-100 text-amber-700', category: 'daily', - keywords: ['producción', 'production', 'lote', 'batch', 'fabricación'], + badgeColor: 'bg-amber-100 text-amber-700', }, -]; +}; interface ItemTypeSelectorProps { onSelect: (itemType: ItemType) => void; @@ -165,9 +110,28 @@ export const ItemTypeSelector: React.FC = ({ onSelect }) const [searchQuery, setSearchQuery] = useState(''); const [selectedCategory, setSelectedCategory] = useState<'all' | 'daily' | 'common' | 'setup'>('all'); + // Build item types with translations + const itemTypes: ItemTypeConfig[] = useMemo(() => { + return Object.entries(ITEM_TYPE_CONFIGS).map(([id, config]) => ({ + id: id as ItemType, + title: t(`itemTypeSelector.items.${id}.title`), + subtitle: t(`itemTypeSelector.items.${id}.subtitle`), + icon: config.icon, + badge: config.category === 'daily' + ? t('itemTypeSelector.badges.daily') + : config.category === 'common' + ? t('itemTypeSelector.badges.common') + : t('itemTypeSelector.badges.setup'), + badgeColor: config.badgeColor, + isHighlighted: id === 'sales-entry', + category: config.category, + keywords: t(`itemTypeSelector.items.${id}.keywords`, { returnObjects: true }) as string[], + })); + }, [t]); + // Filter items based on search and category const filteredItems = useMemo(() => { - return ITEM_TYPES.filter(item => { + return itemTypes.filter(item => { // Category filter if (selectedCategory !== 'all' && item.category !== selectedCategory) { return false; @@ -184,13 +148,13 @@ export const ItemTypeSelector: React.FC = ({ onSelect }) return true; }); - }, [searchQuery, selectedCategory]); + }, [itemTypes, searchQuery, selectedCategory]); const categoryLabels = { - all: 'Todos', - daily: 'Diario', - common: 'Común', - setup: 'Configuración', + all: t('itemTypeSelector.categories.all'), + daily: t('itemTypeSelector.categories.daily'), + common: t('itemTypeSelector.categories.common'), + setup: t('itemTypeSelector.categories.setup'), }; return ( @@ -219,7 +183,7 @@ export const ItemTypeSelector: React.FC = ({ onSelect }) type="text" value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} - placeholder="Buscar por nombre o categoría..." + placeholder={t('itemTypeSelector.search.placeholder')} className="w-full pl-10 pr-10 py-2.5 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)] placeholder-[var(--text-tertiary)]" /> {searchQuery && ( @@ -254,9 +218,9 @@ export const ItemTypeSelector: React.FC = ({ onSelect }) {(searchQuery || selectedCategory !== 'all') && (
{filteredItems.length === 0 ? ( -

No se encontraron resultados

+

{t('itemTypeSelector.search.noResults')}

) : ( -

{filteredItems.length} {filteredItems.length === 1 ? 'resultado' : 'resultados'}

+

{filteredItems.length} {filteredItems.length === 1 ? t('itemTypeSelector.search.resultSingular') : t('itemTypeSelector.search.resultPlural')}

)}
)} diff --git a/frontend/src/components/domain/unified-wizard/wizards/ProductionBatchWizard.tsx b/frontend/src/components/domain/unified-wizard/wizards/ProductionBatchWizard.tsx index b7d2db67..9a670d52 100644 --- a/frontend/src/components/domain/unified-wizard/wizards/ProductionBatchWizard.tsx +++ b/frontend/src/components/domain/unified-wizard/wizards/ProductionBatchWizard.tsx @@ -22,15 +22,18 @@ import { ProcessStage } from '../../../../api/types/qualityTemplates'; import { Badge } from '../../../ui'; import { Card } from '../../../ui'; -// Stage labels for display -const STAGE_LABELS: Record = { - [ProcessStage.MIXING]: 'Mezclado', - [ProcessStage.PROOFING]: 'Fermentación', - [ProcessStage.SHAPING]: 'Formado', - [ProcessStage.BAKING]: 'Horneado', - [ProcessStage.COOLING]: 'Enfriado', - [ProcessStage.PACKAGING]: 'Empaquetado', - [ProcessStage.FINISHING]: 'Acabado', +// Helper to get stage label with translation +const getStageLabelKey = (stage: ProcessStage): string => { + const stageKeyMap: Record = { + [ProcessStage.MIXING]: 'mixing', + [ProcessStage.PROOFING]: 'proofing', + [ProcessStage.SHAPING]: 'shaping', + [ProcessStage.BAKING]: 'baking', + [ProcessStage.COOLING]: 'cooling', + [ProcessStage.PACKAGING]: 'packaging', + [ProcessStage.FINISHING]: 'finishing', + }; + return stageKeyMap[stage]; }; // Step 1: Product & Recipe Selection @@ -103,31 +106,31 @@ const ProductRecipeStep: React.FC = ({ dataRef, onDataChange })

- Seleccionar Producto y Receta + {t('productionBatch.productRecipe.title')}

- Elige el producto a producir y opcionalmente una receta + {t('productionBatch.productRecipe.description')}

{ingredientsLoading || recipesLoading ? (
- Cargando información... + {t('productionBatch.productRecipe.loading')}
) : ( <>
= ({ dataRef, onDataChange }) disabled={loadingRecipe} className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)] disabled:opacity-50" > - + {recipes.map((recipe: any) => (
@@ -167,7 +170,7 @@ const ProductRecipeStep: React.FC = ({ dataRef, onDataChange })

- Controles de Calidad Requeridos + {t('productionBatch.productRecipe.qualityControlsRequired')}

{selectedRecipe.quality_check_configuration && selectedRecipe.quality_check_configuration.stages ? (
@@ -177,32 +180,31 @@ const ProductRecipeStep: React.FC = ({ dataRef, onDataChange }) return (
- {STAGE_LABELS[stage as ProcessStage]} + {t(`productionBatch.additionalDetails.stages.${getStageLabelKey(stage as ProcessStage)}`)} - {config.template_ids.length} control{config.template_ids.length > 1 ? 'es' : ''} + {config.template_ids.length} {config.template_ids.length > 1 ? t('productionBatch.productRecipe.controls') : t('productionBatch.productRecipe.control')} - {config.blocking && Bloqueante} - {config.is_required && Requerido} + {config.blocking && {t('productionBatch.productRecipe.blocking')}} + {config.is_required && {t('productionBatch.productRecipe.required')}}
); } )}

- Umbral de calidad mínimo:{' '} + {t('productionBatch.productRecipe.minimumQualityThreshold')}:{' '} {selectedRecipe.quality_check_configuration.overall_quality_threshold || 7.0}/10

{selectedRecipe.quality_check_configuration.critical_stage_blocking && (

- ⚠️ Bloqueo crítico activado: El lote no - puede avanzar si fallan checks críticos + ⚠️ {t('productionBatch.productRecipe.criticalBlockingActivated')}: {t('productionBatch.productRecipe.batchCannotAdvance')}

)}
) : (

- Esta receta no tiene controles de calidad configurados. + {t('productionBatch.productRecipe.noQualityControls')}

)}
@@ -257,13 +259,13 @@ const PlanningDetailsStep: React.FC = ({ dataRef, onDataChange
-

Planificación de Producción

-

Define tiempos y cantidades de producción

+

{t('productionBatch.planningDetails.title')}

+

{t('productionBatch.planningDetails.description')}

- + = ({ dataRef, onDataChange
= ({ dataRef, onDataChange
= ({ dataRef, onDataChange disabled className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg bg-[var(--bg-secondary)] text-[var(--text-tertiary)] cursor-not-allowed" /> -

Se calcula automáticamente

+

{t('productionBatch.planningDetails.autoCalculated')}

= ({ dataRef, onDataChange
-

Resumen de Planificación:

+

{t('productionBatch.planningDetails.planningSummary')}:

- Inicio: {new Date(getValue('planned_start_time')).toLocaleString('es-ES')} + {t('productionBatch.planningDetails.start')}: {new Date(getValue('planned_start_time')).toLocaleString('es-ES')}

-

Fin: {new Date(getValue('planned_end_time')).toLocaleString('es-ES')}

+

{t('productionBatch.planningDetails.end')}: {new Date(getValue('planned_end_time')).toLocaleString('es-ES')}

- Duración: {getValue('planned_duration_minutes', 0)} minutos ( - {(getValue('planned_duration_minutes', 0) / 60).toFixed(1)} horas) + {t('productionBatch.planningDetails.durationSummary')}: {getValue('planned_duration_minutes', 0)} {t('productionBatch.planningDetails.minutes')} ( + {(getValue('planned_duration_minutes', 0) / 60).toFixed(1)} {t('productionBatch.planningDetails.hours')})

@@ -358,13 +360,13 @@ const PriorityResourcesStep: React.FC = ({ dataRef, onDataChang
-

Prioridad y Recursos

-

Configura la prioridad y asigna recursos

+

{t('productionBatch.priorityResources.title')}

+

{t('productionBatch.priorityResources.description')}

- + handleFieldChange({ batch_number: e.target.value })} - placeholder="Se generará automáticamente si se deja vacío" + placeholder={t('productionBatch.priorityResources.autoGeneratedPlaceholder')} className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)]" />
@@ -396,7 +398,7 @@ const PriorityResourcesStep: React.FC = ({ dataRef, onDataChang onChange={(e) => handleFieldChange({ is_rush_order: e.target.checked })} className="rounded border-[var(--border-secondary)] text-[var(--color-primary)] focus:ring-[var(--color-primary)]" /> - +
@@ -406,69 +408,69 @@ const PriorityResourcesStep: React.FC = ({ dataRef, onDataChang onChange={(e) => handleFieldChange({ is_special_recipe: e.target.checked })} className="rounded border-[var(--border-secondary)] text-[var(--color-primary)] focus:ring-[var(--color-primary)]" /> - +
handleFieldChange({ staff_assigned_string: e.target.value })} - placeholder="Separar nombres con comas (Ej: Juan Pérez, María García)" + placeholder={t('productionBatch.priorityResources.staffPlaceholder')} className="w-full px-3 py-2 border border-[var(--border-secondary)] rounded-lg focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)] bg-[var(--bg-primary)] text-[var(--text-primary)]" /> -

Lista de nombres separados por comas

+

{t('productionBatch.priorityResources.staffHint')}