diff --git a/frontend/src/api/services/sustainability.ts b/frontend/src/api/services/sustainability.ts index f13a717e..29a527af 100644 --- a/frontend/src/api/services/sustainability.ts +++ b/frontend/src/api/services/sustainability.ts @@ -162,7 +162,8 @@ const EnvironmentalConstants = { LAND_USE_PER_KG: 3.4, // m² per kg TREES_PER_TON_CO2: 50, SDG_TARGET_REDUCTION: 0.50, // 50% reduction target - EU_BAKERY_BASELINE_WASTE: 0.25 // 25% baseline + EU_BAKERY_BASELINE_WASTE: 0.25, // 25% baseline + MINIMUM_PRODUCTION_KG: 50 // Minimum production to show meaningful metrics }; /** @@ -320,6 +321,78 @@ function assessGrantReadiness(sdgCompliance: SDGCompliance): GrantReadiness { // ===== MAIN AGGREGATION FUNCTION ===== +/** + * Get default metrics for insufficient data state + */ +function getInsufficientDataMetrics( + totalProductionKg: number, + startDate?: string, + endDate?: string +): SustainabilityMetrics { + return { + period: { + start_date: startDate || new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(), + end_date: endDate || new Date().toISOString(), + days: 30 + }, + waste_metrics: { + total_waste_kg: 0, + production_waste_kg: 0, + expired_waste_kg: 0, + waste_percentage: 0, + waste_by_reason: {} + }, + environmental_impact: { + co2_emissions: { kg: 0, tons: 0, trees_to_offset: 0 }, + water_footprint: { liters: 0, cubic_meters: 0 }, + land_use: { square_meters: 0, hectares: 0 }, + human_equivalents: { car_km_equivalent: 0, smartphone_charges: 0, showers_equivalent: 0, trees_planted: 0 } + }, + sdg_compliance: { + sdg_12_3: { + baseline_waste_percentage: 0, + current_waste_percentage: 0, + reduction_achieved: 0, + target_reduction: 50, + progress_to_target: 0, + status: 'baseline', + status_label: 'Collecting Baseline Data', + target_waste_percentage: 0 + }, + baseline_period: 'not_available', + certification_ready: false, + improvement_areas: ['start_production_tracking'] + }, + avoided_waste: { + waste_avoided_kg: 0, + ai_assisted_batches: 0, + environmental_impact_avoided: { co2_kg: 0, water_liters: 0 }, + methodology: 'insufficient_data' + }, + financial_impact: { + waste_cost_eur: 0, + cost_per_kg: 3.50, + potential_monthly_savings: 0, + annual_projection: 0 + }, + grant_readiness: { + overall_readiness_percentage: 0, + grant_programs: { + life_circular_economy: { eligible: false, confidence: 'low', requirements_met: false, funding_eur: 73_000_000 }, + horizon_europe_cluster_6: { eligible: false, confidence: 'low', requirements_met: false, funding_eur: 880_000_000 }, + fedima_sustainability_grant: { eligible: false, confidence: 'low', requirements_met: false, funding_eur: 20_000 }, + eit_food_retail: { eligible: false, confidence: 'low', requirements_met: false, funding_eur: 45_000 }, + un_sdg_certified: { eligible: false, confidence: 'low', requirements_met: false, funding_eur: 0 } + }, + recommended_applications: [], + spain_compliance: { law_1_2025: false, circular_economy_strategy: false } + }, + data_sufficient: false, + minimum_production_required_kg: EnvironmentalConstants.MINIMUM_PRODUCTION_KG, + current_production_kg: totalProductionKg + }; +} + /** * Get comprehensive sustainability metrics by aggregating production and inventory data */ @@ -337,11 +410,22 @@ export async function getSustainabilityMetrics( getProductionAIImpact(tenantId, startDate, endDate) ]); + // Calculate total production + const totalProductionKg = productionData.total_planned || 0; + + // Check if we have sufficient data for meaningful metrics + // Minimum: 50kg production to avoid false metrics on empty accounts + const hasDataSufficient = totalProductionKg >= EnvironmentalConstants.MINIMUM_PRODUCTION_KG; + + // If insufficient data, return a "collecting data" state + if (!hasDataSufficient) { + return getInsufficientDataMetrics(totalProductionKg, startDate, endDate); + } + // Aggregate waste metrics const productionWaste = (productionData.total_production_waste || 0) + (productionData.total_defects || 0); const totalWasteKg = productionWaste + (inventoryData.inventory_waste_kg || 0); - const totalProductionKg = productionData.total_planned || 0; const wastePercentage = totalProductionKg > 0 ? (totalWasteKg / totalProductionKg) * 100 : 0; @@ -403,7 +487,10 @@ export async function getSustainabilityMetrics( sdg_compliance: sdgCompliance, avoided_waste: avoidedWaste, financial_impact: financialImpact, - grant_readiness: grantReadiness + grant_readiness: grantReadiness, + data_sufficient: true, + minimum_production_required_kg: EnvironmentalConstants.MINIMUM_PRODUCTION_KG, + current_production_kg: totalProductionKg }; } catch (error) { diff --git a/frontend/src/api/types/sustainability.ts b/frontend/src/api/types/sustainability.ts index 84a70609..112bc87b 100644 --- a/frontend/src/api/types/sustainability.ts +++ b/frontend/src/api/types/sustainability.ts @@ -114,6 +114,10 @@ export interface SustainabilityMetrics { avoided_waste: AvoidedWaste; financial_impact: FinancialImpact; grant_readiness: GrantReadiness; + // Data sufficiency flags + data_sufficient: boolean; + minimum_production_required_kg?: number; + current_production_kg?: number; } export interface SustainabilityWidgetData { diff --git a/frontend/src/components/domain/unified-wizard/ItemTypeSelector.tsx b/frontend/src/components/domain/unified-wizard/ItemTypeSelector.tsx index 212df639..4e4b6c4e 100644 --- a/frontend/src/components/domain/unified-wizard/ItemTypeSelector.tsx +++ b/frontend/src/components/domain/unified-wizard/ItemTypeSelector.tsx @@ -10,7 +10,6 @@ import { Users, UserPlus, Euro as EuroIcon, - Sparkles, FileText, Factory, Search, @@ -159,21 +158,6 @@ export const ItemTypeSelector: React.FC = ({ onSelect }) return (
- {/* Header */} -
-
-
- -
-
-

- {t('itemTypeSelector.title')} -

-

- {t('itemTypeSelector.description')} -

-
- {/* Search and Filters */}
{/* Search Bar */} @@ -256,7 +240,7 @@ export const ItemTypeSelector: React.FC = ({ onSelect }) )} {/* Content */} -
+
{/* Icon */}
= ({ onSelect })
{/* Text */} -
-

+
+

{itemType.title}

diff --git a/frontend/src/components/ui/WizardModal/WizardModal.tsx b/frontend/src/components/ui/WizardModal/WizardModal.tsx index d500a157..95756dd5 100644 --- a/frontend/src/components/ui/WizardModal/WizardModal.tsx +++ b/frontend/src/components/ui/WizardModal/WizardModal.tsx @@ -1,4 +1,5 @@ import React, { useState, useCallback, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { X, ChevronLeft, ChevronRight, AlertCircle, CheckCircle } from 'lucide-react'; export interface WizardStep { @@ -48,12 +49,26 @@ export const WizardModal: React.FC = ({ dataRef, onDataChange }) => { + const { t } = useTranslation('wizards'); const [currentStepIndex, setCurrentStepIndex] = useState(0); const [isValidating, setIsValidating] = useState(false); const [validationError, setValidationError] = useState(null); const [validationSuccess, setValidationSuccess] = useState(false); const currentStep = steps[currentStepIndex]; + + // Helper to translate step title - if it looks like a translation key (contains dots), translate it + const translateStepTitle = (stepTitle: string): string => { + if (stepTitle.includes('.')) { + const translated = t(stepTitle); + // If translation returns the key itself, return just the last part as fallback + if (translated === stepTitle) { + return stepTitle.split('.').pop() || stepTitle; + } + return translated; + } + return stepTitle; + }; const isFirstStep = currentStepIndex === 0; const isLastStep = currentStepIndex === steps.length - 1; @@ -178,11 +193,11 @@ export const WizardModal: React.FC = ({ {/* Modal */}

e.stopPropagation()} > {/* Header */} -
+
{/* Title Bar */}
@@ -232,7 +247,7 @@ export const WizardModal: React.FC = ({ ? 'bg-[var(--color-primary)] shadow-md' : 'bg-[var(--bg-tertiary)] cursor-not-allowed' }`} - aria-label={`${step.title} - ${isCompleted ? 'Completado' : isCurrent ? 'En progreso' : 'Pendiente'}`} + aria-label={`${translateStepTitle(step.title)} - ${isCompleted ? 'Completado' : isCurrent ? 'En progreso' : 'Pendiente'}`} > {isCurrent && (
@@ -241,7 +256,7 @@ export const WizardModal: React.FC = ({ {/* Tooltip on hover */}
- {step.title} + {translateStepTitle(step.title)}
@@ -250,7 +265,7 @@ export const WizardModal: React.FC = ({
- {currentStep.title} + {translateStepTitle(currentStep.title)} {currentStep.isOptional && ( Opcional @@ -265,7 +280,7 @@ export const WizardModal: React.FC = ({
{/* Step Content */} -
+
{/* Validation Messages */} {validationError && (
@@ -304,7 +319,7 @@ export const WizardModal: React.FC = ({
{/* Footer Navigation */} -
+
{/* Keyboard Shortcuts Hint */}
diff --git a/frontend/src/locales/es/wizards.json b/frontend/src/locales/es/wizards.json index b9c10c60..ffcb1c0e 100644 --- a/frontend/src/locales/es/wizards.json +++ b/frontend/src/locales/es/wizards.json @@ -337,6 +337,97 @@ }, "customerOrder": { "title": "Agregar Pedido", + "customerTypes": { + "retail": "Minorista", + "wholesale": "Mayorista", + "event": "Evento", + "restaurant": "Restaurante", + "hotel": "Hotel", + "enterprise": "Empresa", + "individual": "Individual", + "business": "Negocio", + "central_bakery": "Panadería Central" + }, + "paymentTerms": { + "immediate": "Inmediato", + "net_30": "Neto 30", + "net_60": "Neto 60" + }, + "sections": { + "basicInfo": "Información Básica del Pedido", + "deliveryInfo": "Detalles de Entrega", + "paymentInfo": "Detalles de Pago", + "orderSummary": "Resumen del Pedido", + "advancedOptions": "Opciones Avanzadas", + "advancedOptionsDescription": "Campos opcionales para gestión completa de pedidos", + "pricingDetails": "Detalles de Precios", + "productionScheduling": "Producción y Programación", + "fulfillmentTracking": "Cumplimiento y Seguimiento", + "sourceChannel": "Origen y Canal", + "communicationNotes": "Comunicación y Notas", + "notifications": "Notificaciones", + "qualityRequirements": "Calidad y Requisitos", + "additionalOptions": "Opciones Adicionales" + }, + "orderTypes": { + "standard": "Estándar", + "custom": "Personalizado", + "bulk": "A Granel", + "urgent": "Urgente" + }, + "priorities": { + "low": "Baja", + "normal": "Normal", + "high": "Alta", + "urgent": "Urgente" + }, + "statuses": { + "pending": "Pendiente", + "confirmed": "Confirmado", + "in_production": "En Producción", + "ready": "Listo", + "delivered": "Entregado" + }, + "deliveryMethods": { + "pickup": "Recogida", + "pickupDesc": "Recogida del cliente", + "delivery": "Entrega", + "deliveryDesc": "Entrega a domicilio", + "shipping": "Envío", + "shippingDesc": "Servicio de mensajería" + }, + "paymentMethods": { + "cash": "Efectivo", + "card": "Tarjeta", + "bank_transfer": "Transferencia Bancaria", + "invoice": "Factura", + "account": "Cuenta" + }, + "paymentStatuses": { + "pending": "Pendiente", + "partial": "Parcial", + "paid": "Pagado", + "overdue": "Vencido" + }, + "orderSources": { + "manual": "Manual", + "phone": "Teléfono", + "email": "Correo Electrónico", + "website": "Sitio Web", + "app": "Aplicación Móvil" + }, + "salesChannels": { + "direct": "Directo", + "wholesale": "Mayorista", + "retail": "Minorista", + "online": "En Línea" + }, + "qualityCheckStatuses": { + "not_started": "No Iniciado", + "pending": "Pendiente", + "passed": "Aprobado", + "failed": "Reprobado" + }, "steps": { "customerSelection": "Selección de Cliente", "orderItems": "Artículos del Pedido", @@ -348,20 +439,23 @@ "searchPlaceholder": "Buscar clientes...", "createNew": "Crear nuevo cliente", "backToList": "← Volver a la lista de clientes", - "fields": { - "customerName": "Nombre del Cliente", - "customerNamePlaceholder": "Ej: Restaurante El Molino", - "customerType": "Tipo de Cliente", - "phone": "Teléfono", - "phonePlaceholder": "+34 123 456 789", - "email": "Correo Electrónico", - "emailPlaceholder": "contacto@restaurante.com" - }, + "customerName": "Nombre del Cliente", + "customerNamePlaceholder": "Ej: Restaurante El Molino", + "customerType": "Tipo de Cliente", + "phone": "Teléfono", + "phonePlaceholder": "+34 123 456 789", + "email": "Correo Electrónico", + "emailPlaceholder": "contacto@restaurante.com", "customerTypes": { "retail": "Minorista", "wholesale": "Mayorista", "event": "Evento", - "restaurant": "Restaurante" + "restaurant": "Restaurante", + "hotel": "Hotel", + "enterprise": "Empresa", + "individual": "Individual", + "business": "Negocio", + "central_bakery": "Panadería Central" } }, "orderItems": { @@ -371,7 +465,7 @@ "removeItem": "Eliminar artículo", "customer": "Cliente", "orderProducts": "Productos del Pedido", - "productNumber": "Producto #{{number}}", + "productNumber": "Producto #{number}", "product": "Producto", "productPlaceholder": "Seleccionar producto...", "selectProduct": "Seleccionar producto...", @@ -396,6 +490,74 @@ "deliveryPayment": { "title": "Detalles de Entrega y Pago", "subtitle": "Configurar entrega, pago y detalles del pedido", + "requestedDeliveryDate": "Fecha de Entrega Solicitada", + "orderNumberLabel": "Número de Pedido", + "orderNumberTooltip": "Generado automáticamente por el backend al crear el pedido (formato: ORD-AAAAMMDD-####)", + "autoGeneratedLabel": "Auto-generado", + "autoGeneratedPlaceholder": "Se generará automáticamente", + "status": "Estado", + "orderType": "Tipo de Pedido", + "priority": "Prioridad", + "products": "Productos", + "deliveryMethod": "Método de Entrega", + "deliveryAddressPlaceholder": "Calle, número, piso, código postal, ciudad...", + "deliveryContactName": "Nombre de Contacto para Entrega", + "deliveryContactNamePlaceholder": "Persona de contacto", + "deliveryContactPhone": "Teléfono de Contacto para Entrega", + "phoneNumberPlaceholder": "+34 123 456 789", + "paymentTerms": "Términos de Pago", + "paymentStatus": "Estado de Pago", + "paymentDueDate": "Fecha de Vencimiento del Pago", + "discount": "Descuento (%)", + "deliveryFee": "Tarifa de Entrega (€)", + "productionStartDate": "Fecha de Inicio de Producción", + "productionDueDate": "Fecha de Vencimiento de Producción", + "productionBatchNumber": "Número de Lote de Producción", + "deliveryTimeWindow": "Ventana de Tiempo de Entrega", + "deliveryTimeWindowPlaceholder": "Ej: 9:00 AM - 11:00 AM", + "productionNotes": "Notas de Producción", + "productionNotesPlaceholder": "Requisitos especiales de producción o notas", + "shippingTrackingNumber": "Número de Seguimiento de Envío", + "shippingTrackingNumberPlaceholder": "Número de seguimiento", + "shippingCarrier": "Transportista de Envío", + "shippingCarrierPlaceholder": "Ej: DHL, UPS, FedEx", + "pickupLocation": "Ubicación de Recogida", + "pickupLocationPlaceholder": "Ubicación de tienda para recogida", + "actualDeliveryDate": "Fecha Real de Entrega", + "orderSource": "Origen del Pedido", + "salesChannel": "Canal de Ventas", + "salesRepId": "ID del Representante de Ventas", + "salesRepIdPlaceholder": "ID o nombre del representante de ventas", + "customerPurchaseOrder": "Orden de Compra del Cliente #", + "customerPurchaseOrderPlaceholder": "Número de OC del cliente", + "deliveryInstructions": "Instrucciones de Entrega", + "deliveryInstructionsPlaceholder": "Instrucciones especiales de entrega", + "specialInstructions": "Instrucciones Especiales", + "specialInstructionsPlaceholder": "Cualquier requisito o instrucción especial", + "internalNotes": "Notas Internas", + "internalNotesPlaceholder": "Notas internas (no visibles para el cliente)", + "customerNotes": "Notas del Cliente", + "customerNotesPlaceholder": "Notas de/para el cliente", + "notifyOnStatusChange": "Notificar en Cambio de Estado", + "notifyOnDelivery": "Notificar en Entrega", + "notificationEmail": "Correo de Notificación", + "notificationEmailPlaceholder": "cliente@correo.com", + "notificationPhone": "Teléfono de Notificación", + "qualityCheckRequired": "Control de Calidad Requerido", + "qualityCheckStatus": "Estado del Control de Calidad", + "packagingInstructions": "Instrucciones de Empaquetado", + "packagingInstructionsPlaceholder": "Requisitos especiales de empaquetado", + "labelingRequirements": "Requisitos de Etiquetado", + "labelingRequirementsPlaceholder": "Requisitos de etiqueta personalizados", + "recurringOrder": "Pedido Recurrente", + "recurringSchedule": "Programa Recurrente", + "recurringSchedulePlaceholder": "Ej: Semanalmente los lunes, Cada 2 semanas", + "tags": "Etiquetas", + "tagsPlaceholder": "urgente, vip, mayorista", + "tagsTooltip": "Etiquetas separadas por comas para búsqueda y filtrado más fácil", + "metadata": "Metadatos (JSON)", + "metadataPlaceholder": "{\"campo_personalizado\": \"valor\"}", + "metadataTooltip": "Datos personalizados adicionales en formato JSON", "fields": { "requestedDeliveryDate": "Fecha de Entrega Solicitada", "orderNumber": "Número de Pedido", @@ -454,11 +616,6 @@ "invoice": "Factura", "account": "Cuenta" }, - "paymentTerms": { - "immediate": "Inmediato", - "net_30": "Neto 30", - "net_60": "Neto 60" - }, "paymentStatuses": { "pending": "Pendiente", "partial": "Parcial", @@ -483,103 +640,103 @@ "pending": "Pendiente", "passed": "Aprobado", "failed": "Reprobado" - }, - "messages": { - "loadingCustomers": "Cargando clientes...", - "loadingProducts": "Cargando productos...", - "errorLoadingCustomers": "Error al cargar clientes", - "errorLoadingProducts": "Error al cargar productos", - "noCustomersFound": "No se encontraron clientes", - "tryDifferentSearch": "Intenta con un término de búsqueda diferente", - "noProductsInOrder": "No hay productos en el pedido", - "clickAddProduct": "Haz clic en \"Agregar Producto\" para comenzar", - "newCustomer": "Nuevo Cliente", - "customer": "Cliente", - "products": "Productos", - "items": "artículos", - "total": "Total", - "productNumber": "Producto #", - "searchByName": "Buscar cliente por nombre...", - "selectCustomer": "Seleccionar Cliente", - "searchForCustomer": "Buscar un cliente existente o crear uno nuevo", - "orderItems": "Artículos del Pedido", - "addProducts": "Agregar Productos al Pedido", - "customerLabel": "Cliente:", - "productsLabel": "Productos:", - "totalLabel": "Total:", - "orderTotal": "Total del Pedido:", - "newCustomerHeader": "Nuevo Cliente", - "orderProducts": "Productos del Pedido", - "addProduct": "Agregar Producto", - "removeItem": "Eliminar artículo", - "optionalEmail": "Correo Electrónico (Opcional)", - "readOnlyAutoGenerated": "Número de Pedido (Solo lectura - Auto-generado)", - "willBeGeneratedAutomatically": "Se generará automáticamente", - "autoGeneratedOnSave": "Auto-generado al guardar", - "orderNumberFormat": "formato: ORD-AAAAMMDD-####", - "selectProduct": "Seleccionar producto...", - "deliveryAddress": "Dirección de Entrega", - "deliveryAddressPlaceholder": "Calle, número, piso, código postal, ciudad...", - "deliveryContactName": "Nombre de Contacto para Entrega", - "deliveryContactNamePlaceholder": "Persona de contacto", - "deliveryContactPhone": "Teléfono de Contacto para Entrega", - "deliveryMethod": "Método de Entrega", - "paymentMethod": "Método de Pago", - "paymentTerms": "Términos de Pago", - "paymentStatus": "Estado de Pago", - "paymentDueDate": "Fecha de Vencimiento del Pago", - "discountPercent": "Descuento (%)", - "deliveryFee": "Tarifa de Entrega (€)", - "productionStartDate": "Fecha de Inicio de Producción", - "productionDueDate": "Fecha de Vencimiento de Producción", - "productionBatchNumber": "Número de Lote de Producción", - "productionBatchNumberPlaceholder": "LOTE-001", - "deliveryTimeWindow": "Ventana de Tiempo de Entrega", - "deliveryTimeWindowPlaceholder": "Ej: 9:00 AM - 11:00 AM", - "productionNotes": "Notas de Producción", - "productionNotesPlaceholder": "Requisitos especiales de producción o notas", - "shippingTrackingNumber": "Número de Seguimiento de Envío", - "shippingTrackingNumberPlaceholder": "Número de seguimiento", - "shippingCarrier": "Transportista de Envío", - "shippingCarrierPlaceholder": "Ej: DHL, UPS, FedEx", - "pickupLocation": "Ubicación de Recogida", - "pickupLocationPlaceholder": "Ubicación de tienda para recogida", - "actualDeliveryDate": "Fecha Real de Entrega", - "orderSource": "Origen del Pedido", - "salesChannel": "Canal de Ventas", - "salesRepId": "ID del Representante de Ventas", - "salesRepIdPlaceholder": "ID o nombre del representante de ventas", - "customerPurchaseOrder": "Orden de Compra del Cliente #", - "customerPurchaseOrderPlaceholder": "Número de OC del cliente", - "deliveryInstructions": "Instrucciones de Entrega", - "deliveryInstructionsPlaceholder": "Instrucciones especiales de entrega", - "specialInstructions": "Instrucciones Especiales", - "specialInstructionsPlaceholder": "Cualquier requisito o instrucción especial", - "internalNotes": "Notas Internas", - "internalNotesPlaceholder": "Notas internas (no visibles para el cliente)", - "customerNotes": "Notas del Cliente", - "customerNotesPlaceholder": "Notas de/para el cliente", - "notifyOnStatusChange": "Notificar en Cambio de Estado", - "notifyOnDelivery": "Notificar en Entrega", - "notificationEmail": "Correo de Notificación", - "notificationEmailPlaceholder": "cliente@correo.com", - "notificationPhone": "Teléfono de Notificación", - "qualityCheckRequired": "Control de Calidad Requerido", - "qualityCheckStatus": "Estado del Control de Calidad", - "packagingInstructions": "Instrucciones de Empaquetado", - "packagingInstructionsPlaceholder": "Requisitos especiales de empaquetado", - "labelingRequirements": "Requisitos de Etiquetado", - "labelingRequirementsPlaceholder": "Requisitos de etiqueta personalizados", - "recurringOrder": "Pedido Recurrente", - "recurringSchedule": "Programa Recurrente", - "recurringSchedulePlaceholder": "Ej: Semanalmente los lunes, Cada 2 semanas", - "tags": "Etiquetas", - "tagsPlaceholder": "urgente, vip, mayorista", - "tagsTooltip": "Etiquetas separadas por comas para búsqueda y filtrado más fácil", - "metadata": "Metadatos (JSON)", - "metadataPlaceholder": "{\"campo_personalizado\": \"valor\"}", - "metadataTooltip": "Datos personalizados adicionales en formato JSON" } + }, + "messages": { + "loadingCustomers": "Cargando clientes...", + "loadingProducts": "Cargando productos...", + "errorLoadingCustomers": "Error al cargar clientes", + "errorLoadingProducts": "Error al cargar productos", + "noCustomersFound": "No se encontraron clientes", + "tryDifferentSearch": "Intenta con un término de búsqueda diferente", + "noProductsInOrder": "No hay productos en el pedido", + "clickAddProduct": "Haz clic en \"Agregar Producto\" para comenzar", + "newCustomer": "Nuevo Cliente", + "customer": "Cliente", + "products": "Productos", + "items": "artículos", + "total": "Total", + "productNumber": "Producto #", + "searchByName": "Buscar cliente por nombre...", + "selectCustomer": "Seleccionar Cliente", + "searchForCustomer": "Buscar un cliente existente o crear uno nuevo", + "orderItems": "Artículos del Pedido", + "addProducts": "Agregar Productos al Pedido", + "customerLabel": "Cliente:", + "productsLabel": "Productos:", + "totalLabel": "Total:", + "orderTotal": "Total del Pedido:", + "newCustomerHeader": "Nuevo Cliente", + "orderProducts": "Productos del Pedido", + "addProduct": "Agregar Producto", + "removeItem": "Eliminar artículo", + "optionalEmail": "Correo Electrónico (Opcional)", + "readOnlyAutoGenerated": "Número de Pedido (Solo lectura - Auto-generado)", + "willBeGeneratedAutomatically": "Se generará automáticamente", + "autoGeneratedOnSave": "Auto-generado al guardar", + "orderNumberFormat": "formato: ORD-AAAAMMDD-####", + "selectProduct": "Seleccionar producto...", + "deliveryAddress": "Dirección de Entrega", + "deliveryAddressPlaceholder": "Calle, número, piso, código postal, ciudad...", + "deliveryContactName": "Nombre de Contacto para Entrega", + "deliveryContactNamePlaceholder": "Persona de contacto", + "deliveryContactPhone": "Teléfono de Contacto para Entrega", + "deliveryMethod": "Método de Entrega", + "paymentMethod": "Método de Pago", + "paymentTerms": "Términos de Pago", + "paymentStatus": "Estado de Pago", + "paymentDueDate": "Fecha de Vencimiento del Pago", + "discountPercent": "Descuento (%)", + "deliveryFee": "Tarifa de Entrega (€)", + "productionStartDate": "Fecha de Inicio de Producción", + "productionDueDate": "Fecha de Vencimiento de Producción", + "productionBatchNumber": "Número de Lote de Producción", + "productionBatchNumberPlaceholder": "LOTE-001", + "deliveryTimeWindow": "Ventana de Tiempo de Entrega", + "deliveryTimeWindowPlaceholder": "Ej: 9:00 AM - 11:00 AM", + "productionNotes": "Notas de Producción", + "productionNotesPlaceholder": "Requisitos especiales de producción o notas", + "shippingTrackingNumber": "Número de Seguimiento de Envío", + "shippingTrackingNumberPlaceholder": "Número de seguimiento", + "shippingCarrier": "Transportista de Envío", + "shippingCarrierPlaceholder": "Ej: DHL, UPS, FedEx", + "pickupLocation": "Ubicación de Recogida", + "pickupLocationPlaceholder": "Ubicación de tienda para recogida", + "actualDeliveryDate": "Fecha Real de Entrega", + "orderSource": "Origen del Pedido", + "salesChannel": "Canal de Ventas", + "salesRepId": "ID del Representante de Ventas", + "salesRepIdPlaceholder": "ID o nombre del representante de ventas", + "customerPurchaseOrder": "Orden de Compra del Cliente #", + "customerPurchaseOrderPlaceholder": "Número de OC del cliente", + "deliveryInstructions": "Instrucciones de Entrega", + "deliveryInstructionsPlaceholder": "Instrucciones especiales de entrega", + "specialInstructions": "Instrucciones Especiales", + "specialInstructionsPlaceholder": "Cualquier requisito o instrucción especial", + "internalNotes": "Notas Internas", + "internalNotesPlaceholder": "Notas internas (no visibles para el cliente)", + "customerNotes": "Notas del Cliente", + "customerNotesPlaceholder": "Notas de/para el cliente", + "notifyOnStatusChange": "Notificar en Cambio de Estado", + "notifyOnDelivery": "Notificar en Entrega", + "notificationEmail": "Correo de Notificación", + "notificationEmailPlaceholder": "cliente@correo.com", + "notificationPhone": "Teléfono de Notificación", + "qualityCheckRequired": "Control de Calidad Requerido", + "qualityCheckStatus": "Estado del Control de Calidad", + "packagingInstructions": "Instrucciones de Empaquetado", + "packagingInstructionsPlaceholder": "Requisitos especiales de empaquetado", + "labelingRequirements": "Requisitos de Etiquetado", + "labelingRequirementsPlaceholder": "Requisitos de etiqueta personalizados", + "recurringOrder": "Pedido Recurrente", + "recurringSchedule": "Programa Recurrente", + "recurringSchedulePlaceholder": "Ej: Semanalmente los lunes, Cada 2 semanas", + "tags": "Etiquetas", + "tagsPlaceholder": "urgente, vip, mayorista", + "tagsTooltip": "Etiquetas separadas por comas para búsqueda y filtrado más fácil", + "metadata": "Metadatos (JSON)", + "metadataPlaceholder": "{\"campo_personalizado\": \"valor\"}", + "metadataTooltip": "Datos personalizados adicionales en formato JSON" } }, "itemTypeSelector": { diff --git a/frontend/src/pages/app/database/sustainability/SustainabilityPage.tsx b/frontend/src/pages/app/database/sustainability/SustainabilityPage.tsx index 559ca56b..cfeb185e 100644 --- a/frontend/src/pages/app/database/sustainability/SustainabilityPage.tsx +++ b/frontend/src/pages/app/database/sustainability/SustainabilityPage.tsx @@ -8,7 +8,6 @@ import { Target, Droplets, TreeDeciduous, - Calendar, Download, FileText, Info @@ -45,34 +44,34 @@ const SustainabilityPage: React.FC = () => { ['WASTE METRICS'], ['Total Waste (kg)', metrics.waste_metrics.total_waste_kg.toFixed(2)], ['Production Waste (kg)', metrics.waste_metrics.production_waste_kg.toFixed(2)], - ['Inventory Waste (kg)', metrics.waste_metrics.inventory_waste_kg.toFixed(2)], + ['Expired Waste (kg)', metrics.waste_metrics.expired_waste_kg.toFixed(2)], ['Waste Percentage (%)', metrics.waste_metrics.waste_percentage.toFixed(2)], [], ['SDG 12.3 COMPLIANCE'], ['Status', metrics.sdg_compliance.sdg_12_3.status_label], ['Reduction Achieved (%)', metrics.sdg_compliance.sdg_12_3.reduction_achieved.toFixed(2)], ['Progress to Target (%)', metrics.sdg_compliance.sdg_12_3.progress_to_target.toFixed(2)], - ['Target (%)', metrics.sdg_compliance.sdg_12_3.target_percentage], + ['Target (%)', metrics.sdg_compliance.sdg_12_3.target_reduction], [], ['ENVIRONMENTAL IMPACT'], ['CO2 Emissions (kg)', metrics.environmental_impact.co2_emissions.kg.toFixed(2)], ['CO2 Emissions (tons)', metrics.environmental_impact.co2_emissions.tons.toFixed(4)], ['Trees to Offset', metrics.environmental_impact.co2_emissions.trees_to_offset.toFixed(1)], - ['Equivalent Car KM', metrics.environmental_impact.co2_emissions.car_km_equivalent.toFixed(0)], + ['Equivalent Car KM', metrics.environmental_impact.human_equivalents.car_km_equivalent.toFixed(0)], ['Water Footprint (liters)', metrics.environmental_impact.water_footprint.liters.toFixed(0)], ['Water Footprint (m³)', metrics.environmental_impact.water_footprint.cubic_meters.toFixed(2)], - ['Equivalent Showers', metrics.environmental_impact.water_footprint.shower_equivalent.toFixed(0)], + ['Equivalent Showers', metrics.environmental_impact.human_equivalents.showers_equivalent.toFixed(0)], ['Land Use (m²)', metrics.environmental_impact.land_use.square_meters.toFixed(2)], ['Land Use (hectares)', metrics.environmental_impact.land_use.hectares.toFixed(4)], [], ['FINANCIAL IMPACT'], ['Waste Cost (EUR)', metrics.financial_impact.waste_cost_eur.toFixed(2)], ['Potential Monthly Savings (EUR)', metrics.financial_impact.potential_monthly_savings.toFixed(2)], - ['ROI on Prevention (%)', metrics.financial_impact.roi_on_waste_prevention.toFixed(2)], + ['Annual Projection (EUR)', metrics.financial_impact.annual_projection.toFixed(2)], [], ['AVOIDED WASTE (AI PREDICTIONS)'], - ['Waste Avoided (kg)', metrics.avoided_waste.total_waste_avoided_kg.toFixed(2)], - ['Cost Savings (EUR)', metrics.avoided_waste.cost_savings_eur.toFixed(2)], + ['Waste Avoided (kg)', metrics.avoided_waste.waste_avoided_kg.toFixed(2)], + ['AI Assisted Batches', metrics.avoided_waste.ai_assisted_batches], ['CO2 Avoided (kg)', metrics.avoided_waste.environmental_impact_avoided.co2_kg.toFixed(2)], ['Water Saved (liters)', metrics.avoided_waste.environmental_impact_avoided.water_liters.toFixed(0)], [], @@ -214,76 +213,6 @@ const SustainabilityPage: React.FC = () => { ); } - // Check if we have insufficient data - if (metrics.data_sufficient === false) { - return ( -
- - -
-
- -
-

- {t('sustainability:insufficient_data.title', 'Collecting Sustainability Data')} -

-

- {t('sustainability:insufficient_data.description', - 'Start producing batches to see your sustainability metrics and SDG compliance status.' - )} -

-
-

- {t('sustainability:insufficient_data.requirements_title', 'Minimum Requirements')} -

-
    -
  • - - - {t('sustainability:insufficient_data.req_production', - 'At least 50kg of production over the analysis period' - )} - -
  • -
  • - - - {t('sustainability:insufficient_data.req_baseline', - '90 days of production history for accurate baseline calculation' - )} - -
  • -
  • - - - {t('sustainability:insufficient_data.req_tracking', - 'Production batches with waste tracking enabled' - )} - -
  • -
-
-
- - - {t('sustainability:insufficient_data.current_production', - 'Current production: {{production}}kg of {{required}}kg minimum', - { - production: metrics.current_production_kg?.toFixed(1) || '0.0', - required: metrics.minimum_production_required_kg || 50 - } - )} - -
-
-
-
- ); - } - return (
{/* Page Header */}