@@ -350,39 +350,45 @@ const LandingPage: React.FC = () => {
- {t('landing:pillar3.title', '🌱 Tus Datos, Tus Subvenciones')}
+ {t('landing:pillar3.title', 'Tus Datos, Tu Impacto Ambiental')}
- {t('landing:pillar3.intro', '100% de tus datos te pertenecen. Cumples ODS 12.3 de la ONU automáticamente, lo que te hace elegible para subvenciones de sostenibilidad.')}
+ {t('landing:pillar3.intro', '100% de tus datos te pertenecen. Mide tu impacto ambiental automáticamente y genera informes de sostenibilidad que cumplen con los estándares internacionales.')}
-
€500-2,000
+
+ {t('landing:pillar3.data_ownership_value', '100%')}
+
- {t('landing:pillar3.savings', 'Ahorro mensual')}
+ {t('landing:pillar3.data_ownership', 'Propiedad de datos')}
-
85kg CO₂
+
+ {t('landing:pillar3.co2_metric', 'CO₂')}
+
- {t('landing:pillar3.co2', 'Reducidos al mes')}
+ {t('landing:pillar3.co2', 'Medición automática')}
-
5
+
+ {t('landing:pillar3.sdg_value', 'ODS 12.3')}
+
- {t('landing:pillar3.grants', 'Programas de ayudas')}
+ {t('landing:pillar3.sdg', 'Seguimiento de cumplimiento')}
- {t('landing:pillar3.grants_title', '💶 Elegible para hasta €50,000 en subvenciones')}
+ {t('landing:pillar3.sustainability_title', 'Informes de Sostenibilidad Automatizados')}
- {t('landing:pillar3.grants_desc', 'PIMA Adapta, Planes Turismo, MOVES Circular, y más')}
+ {t('landing:pillar3.sustainability_desc', 'Genera informes que cumplen con los estándares internacionales de sostenibilidad y reducción de desperdicio alimentario')}
diff --git a/frontend/src/styles/themes/dark.css b/frontend/src/styles/themes/dark.css
index 1fc155d0..3b48ca59 100644
--- a/frontend/src/styles/themes/dark.css
+++ b/frontend/src/styles/themes/dark.css
@@ -52,50 +52,50 @@
--color-success-light: #4ade80;
--color-success-dark: #16a34a;
- /* Warning Colors */
- --color-warning-50: #fffbeb;
- --color-warning-100: #fef3c7;
- --color-warning-200: #fde68a;
- --color-warning-300: #fcd34d;
- --color-warning-400: #fbbf24;
+ /* Warning Colors - Inverted scale for dark mode */
+ --color-warning-50: #422006;
+ --color-warning-100: #78350f;
+ --color-warning-200: #9a3412;
+ --color-warning-300: #c2410c;
+ --color-warning-400: #ea580c;
--color-warning-500: #f59e0b;
- --color-warning-600: #ea580c;
- --color-warning-700: #c2410c;
- --color-warning-800: #9a3412;
- --color-warning-900: #7c2d12;
+ --color-warning-600: #fbbf24;
+ --color-warning-700: #fcd34d;
+ --color-warning-800: #fde68a;
+ --color-warning-900: #fef3c7;
--color-warning: #fb923c; /* Brighter for dark theme */
--color-warning-light: #fdba74;
--color-warning-dark: #ea580c;
- /* Error Colors */
- --color-error-50: #fef2f2;
- --color-error-100: #fee2e2;
- --color-error-200: #fecaca;
- --color-error-300: #fca5a5;
- --color-error-400: #f87171;
+ /* Error Colors - Inverted scale for dark mode */
+ --color-error-50: #450a0a;
+ --color-error-100: #7f1d1d;
+ --color-error-200: #991b1b;
+ --color-error-300: #b91c1c;
+ --color-error-400: #dc2626;
--color-error-500: #ef4444;
- --color-error-600: #dc2626;
- --color-error-700: #b91c1c;
- --color-error-800: #991b1b;
- --color-error-900: #7f1d1d;
+ --color-error-600: #f87171;
+ --color-error-700: #fca5a5;
+ --color-error-800: #fecaca;
+ --color-error-900: #fee2e2;
--color-error: #ef4444; /* Brighter for dark theme */
--color-error-light: #f87171;
--color-error-dark: #dc2626;
- /* Info Colors */
- --color-info-50: #eff6ff;
- --color-info-100: #dbeafe;
- --color-info-200: #bfdbfe;
- --color-info-300: #93c5fd;
- --color-info-400: #60a5fa;
- --color-info-500: #3b82f6;
- --color-info-600: #0284c7;
- --color-info-700: #0369a1;
- --color-info-800: #075985;
- --color-info-900: #0c4a6e;
- --color-info: #0ea5e9; /* Brighter for dark theme */
- --color-info-light: #0ea5e9;
- --color-info-dark: #0369a1;
+ /* Info Colors - Adjusted for dark mode */
+ --color-info-50: #0c4a6e;
+ --color-info-100: #075985;
+ --color-info-200: #0369a1;
+ --color-info-300: #0284c7;
+ --color-info-400: #0ea5e9;
+ --color-info-500: #38bdf8;
+ --color-info-600: #38bdf8;
+ --color-info-700: #60a5fa;
+ --color-info-800: #93c5fd;
+ --color-info-900: #bfdbfe;
+ --color-info: #38bdf8; /* Brighter cyan for dark theme */
+ --color-info-light: #60a5fa;
+ --color-info-dark: #0ea5e9;
/* === THEME-SPECIFIC COLORS === */
@@ -116,10 +116,10 @@
--text-muted: #64748b;
--text-disabled: #475569;
- /* Border Colors */
- --border-primary: #334155;
- --border-secondary: #475569;
- --border-tertiary: #64748b;
+ /* Border Colors - Enhanced visibility for dark mode */
+ --border-primary: #475569;
+ --border-secondary: #64748b;
+ --border-tertiary: #94a3b8;
--border-focus: #f59e0b;
--border-error: #ef4444;
--border-success: #22c55e;
diff --git a/services/inventory/app/services/sustainability_service.py b/services/inventory/app/services/sustainability_service.py
index e355dc2c..eeeabe72 100644
--- a/services/inventory/app/services/sustainability_service.py
+++ b/services/inventory/app/services/sustainability_service.py
@@ -734,44 +734,65 @@ class SustainabilityService:
def _assess_grant_readiness(self, sdg_compliance: Dict[str, Any]) -> Dict[str, Any]:
"""
Assess readiness for EU grant programs accessible to Spanish bakeries and retail.
- Based on 2025 research and Spain's Law 1/2025 on food waste prevention.
+ Based on 2026 verified research. Updated Dec 2025.
"""
reduction = sdg_compliance['sdg_12_3']['reduction_achieved']
grants = {
- 'life_circular_economy': {
- 'eligible': reduction >= 15,
- 'confidence': 'high' if reduction >= 25 else 'medium' if reduction >= 15 else 'low',
- 'requirements_met': reduction >= 15,
- 'funding_eur': 73_000_000, # €73M available for circular economy
- 'deadline': '2025-09-23',
- 'program_type': 'grant'
- },
- 'horizon_europe_cluster_6': {
+ 'horizon_europe_food_systems': {
'eligible': reduction >= 20,
'confidence': 'high' if reduction >= 35 else 'medium' if reduction >= 20 else 'low',
'requirements_met': reduction >= 20,
- 'funding_eur': 880_000_000, # €880M+ annually for food systems
- 'deadline': 'rolling_2025',
- 'program_type': 'grant'
+ 'funding_eur': 12_000_000, # €3-12M per project
+ 'deadline': '2026-02-18',
+ 'program_type': 'grant',
+ 'category': 'European Union'
},
- 'fedima_sustainability_grant': {
+ 'horizon_europe_circular_sme': {
'eligible': reduction >= 15,
- 'confidence': 'high' if reduction >= 20 else 'medium' if reduction >= 15 else 'low',
+ 'confidence': 'high' if reduction >= 25 else 'medium' if reduction >= 15 else 'low',
'requirements_met': reduction >= 15,
- 'funding_eur': 20_000, # €20k bi-annual
- 'deadline': '2025-06-30',
+ 'funding_eur': 10_000_000, # €10M total program
+ 'deadline': '2026-02-18',
'program_type': 'grant',
- 'sector_specific': 'bakery'
+ 'category': 'European Union'
},
- 'eit_food_retail': {
- 'eligible': reduction >= 20,
- 'confidence': 'high' if reduction >= 30 else 'medium' if reduction >= 20 else 'low',
- 'requirements_met': reduction >= 20,
- 'funding_eur': 45_000, # €15-45k range
- 'deadline': 'rolling',
+ 'eit_food_impact_2026': {
+ 'eligible': reduction >= 15,
+ 'confidence': 'high' if reduction >= 25 else 'medium' if reduction >= 15 else 'low',
+ 'requirements_met': reduction >= 15,
+ 'funding_eur': 1_000_000, # €50K-1M range
+ 'deadline': 'rolling_2026',
'program_type': 'grant',
- 'sector_specific': 'retail'
+ 'category': 'European Union'
+ },
+ 'eib_circular_economy': {
+ 'eligible': reduction >= 10,
+ 'confidence': 'high' if reduction >= 20 else 'medium' if reduction >= 10 else 'low',
+ 'requirements_met': reduction >= 10,
+ 'funding_eur': 12_500_000, # Up to €12.5M loans
+ 'deadline': 'ongoing_2026',
+ 'program_type': 'loan',
+ 'category': 'European Union'
+ },
+ 'circular_economy_perte': {
+ 'eligible': reduction >= 15,
+ 'confidence': 'high' if reduction >= 25 else 'medium' if reduction >= 15 else 'low',
+ 'requirements_met': reduction >= 15,
+ 'funding_eur': 10_000_000, # €150K-10M range
+ 'deadline': 'rolling_until_2026',
+ 'program_type': 'grant',
+ 'category': 'Spain'
+ },
+ 'planes_turismo_2026': {
+ 'eligible': reduction >= 10,
+ 'confidence': 'medium',
+ 'requirements_met': reduction >= 10,
+ 'funding_eur': 500_000, # Variable by region
+ 'deadline': '2026-12-31',
+ 'program_type': 'grant',
+ 'category': 'Spain',
+ 'sector_specific': 'tourism'
},
'un_sdg_certified': {
'eligible': reduction >= 50,
@@ -779,7 +800,8 @@ class SustainabilityService:
'requirements_met': reduction >= 50,
'funding_eur': 0, # Certification, not funding
'deadline': 'ongoing',
- 'program_type': 'certification'
+ 'program_type': 'certification',
+ 'category': 'International'
}
}
diff --git a/services/orchestrator/scripts/demo/seed_demo_orchestration_runs.py b/services/orchestrator/scripts/demo/seed_demo_orchestration_runs.py
index 5c29b8cf..54787d3a 100644
--- a/services/orchestrator/scripts/demo/seed_demo_orchestration_runs.py
+++ b/services/orchestrator/scripts/demo/seed_demo_orchestration_runs.py
@@ -189,30 +189,46 @@ async def generate_orchestration_for_tenant(
runs_created = 0
steps_created = 0
+ # Special case: Create at least 1 recent completed run for "today" (for dashboard visibility)
+ # This ensures the dashboard "Listo Para Planificar Tu Día" shows data
+ today_run_created = False
+
for i in range(total_runs):
- # Determine temporal distribution
- rand_temporal = random.random()
- cumulative = 0
- temporal_category = None
-
- for category, details in orch_config["temporal_distribution"].items():
- cumulative += details["percentage"]
- if rand_temporal <= cumulative:
- temporal_category = details
- break
-
- if not temporal_category:
+ # For the first run, create it for today with completed status
+ if i == 0 and not today_run_created:
temporal_category = orch_config["temporal_distribution"]["completed"]
+ # Use current time instead of BASE_REFERENCE_DATE
+ now = datetime.now(timezone.utc)
+ # Set offset to create run that started yesterday and completed today
+ offset_days = 0 # Today
+ run_date = now.date()
+ today_run_created = True
+ # Force status to completed for dashboard visibility
+ status = "completed"
+ else:
+ # Determine temporal distribution
+ rand_temporal = random.random()
+ cumulative = 0
+ temporal_category = None
- # Calculate run date
- offset_days = random.randint(
- temporal_category["offset_days_min"],
- temporal_category["offset_days_max"]
- )
- run_date = calculate_date_from_offset(offset_days)
+ for category, details in orch_config["temporal_distribution"].items():
+ cumulative += details["percentage"]
+ if rand_temporal <= cumulative:
+ temporal_category = details
+ break
- # Select status
- status = random.choice(temporal_category["statuses"])
+ if not temporal_category:
+ temporal_category = orch_config["temporal_distribution"]["completed"]
+
+ # Calculate run date
+ offset_days = random.randint(
+ temporal_category["offset_days_min"],
+ temporal_category["offset_days_max"]
+ )
+ run_date = calculate_date_from_offset(offset_days)
+
+ # Select status
+ status = random.choice(temporal_category["statuses"])
# Select run type
run_type_choice = weighted_choice(orch_config["run_types"])
@@ -232,19 +248,26 @@ async def generate_orchestration_for_tenant(
run_number = generate_run_number(tenant_id, i + 1, run_type)
# Calculate timing based on status
- started_at = calculate_datetime_from_offset(offset_days - 1)
- completed_at = None
- duration_seconds = None
+ # For today's run (i==0), use current datetime instead of BASE_REFERENCE_DATE
+ if i == 0 and today_run_created:
+ now = datetime.now(timezone.utc)
+ started_at = now - timedelta(hours=2) # Started 2 hours ago
+ completed_at = now - timedelta(minutes=30) # Completed 30 minutes ago
+ duration_seconds = int((completed_at - started_at).total_seconds())
+ else:
+ started_at = calculate_datetime_from_offset(offset_days - 1)
+ completed_at = None
+ duration_seconds = None
- if status in ["completed", "partial_success"]:
- completed_at = calculate_datetime_from_offset(offset_days)
- duration_seconds = int((completed_at - started_at).total_seconds())
- elif status == "failed":
- completed_at = calculate_datetime_from_offset(offset_days - 0.5)
- duration_seconds = int((completed_at - started_at).total_seconds())
- elif status == "cancelled":
- completed_at = calculate_datetime_from_offset(offset_days - 0.2)
- duration_seconds = int((completed_at - started_at).total_seconds())
+ if status in ["completed", "partial_success"]:
+ completed_at = calculate_datetime_from_offset(offset_days)
+ duration_seconds = int((completed_at - started_at).total_seconds())
+ elif status == "failed":
+ completed_at = calculate_datetime_from_offset(offset_days - 0.5)
+ duration_seconds = int((completed_at - started_at).total_seconds())
+ elif status == "cancelled":
+ completed_at = calculate_datetime_from_offset(offset_days - 0.2)
+ duration_seconds = int((completed_at - started_at).total_seconds())
# Generate step timing
forecasting_started_at = started_at