diff --git a/Tiltfile b/Tiltfile new file mode 100644 index 00000000..c5e7bc59 --- /dev/null +++ b/Tiltfile @@ -0,0 +1,217 @@ +# Tiltfile for Bakery IA - Local Development +# This replaces Skaffold for faster, smarter local Kubernetes development + +# Load Kubernetes manifests using Kustomize +k8s_yaml(kustomize('infrastructure/kubernetes/overlays/dev')) + +# No registry needed for local development - images are built locally + +# Common live update configuration for Python FastAPI services +def python_live_update(service_name, service_path): + return sync(service_path, '/app') + +# ============================================================================= +# FRONTEND (React + Vite + Nginx) +# ============================================================================= +docker_build( + 'bakery/dashboard', + context='./frontend', + dockerfile='./frontend/Dockerfile.kubernetes', + # Note: Frontend is a multi-stage build with nginx, live updates are limited + # For true hot-reload during frontend development, consider running Vite locally + # and using Telepresence to connect to the cluster + live_update=[ + # Sync source changes (limited usefulness due to nginx serving static files) + sync('./frontend/src', '/app/src'), + sync('./frontend/public', '/app/public'), + ] +) + +# ============================================================================= +# GATEWAY +# ============================================================================= +docker_build( + 'bakery/gateway', + context='.', + dockerfile='./gateway/Dockerfile', + live_update=[ + # Sync Python code changes + sync('./gateway', '/app'), + sync('./shared', '/app/shared'), + + # Restart on Python file changes + run('kill -HUP 1', trigger=['./gateway/**/*.py', './shared/**/*.py']), + ] +) + +# ============================================================================= +# MICROSERVICES - Python FastAPI Services +# ============================================================================= + +# Helper function to create docker build with live updates for Python services +def build_python_service(service_name, service_path): + docker_build( + 'bakery/' + service_name, + context='.', + dockerfile='./services/' + service_path + '/Dockerfile', + live_update=[ + # Sync service code + sync('./services/' + service_path, '/app'), + + # Sync shared libraries + sync('./shared', '/app/shared'), + + # Sync scripts + sync('./scripts', '/app/scripts'), + + # Install new dependencies if requirements.txt changes + run('pip install --no-cache-dir -r requirements.txt', + trigger=['./services/' + service_path + '/requirements.txt']), + + # Restart uvicorn on Python file changes (HUP signal triggers graceful reload) + run('kill -HUP 1', + trigger=[ + './services/' + service_path + '/**/*.py', + './shared/**/*.py' + ]), + ] + ) + +# Build all microservices +build_python_service('auth-service', 'auth') +build_python_service('tenant-service', 'tenant') +build_python_service('training-service', 'training') +build_python_service('forecasting-service', 'forecasting') +build_python_service('sales-service', 'sales') +build_python_service('external-service', 'external') +build_python_service('notification-service', 'notification') +build_python_service('inventory-service', 'inventory') +build_python_service('recipes-service', 'recipes') +build_python_service('suppliers-service', 'suppliers') +build_python_service('pos-service', 'pos') +build_python_service('orders-service', 'orders') +build_python_service('production-service', 'production') +build_python_service('alert-processor', 'alert_processor') + +# ============================================================================= +# RESOURCE DEPENDENCIES & ORDERING +# ============================================================================= + +# Databases and infrastructure should start first +k8s_resource('auth-db', labels=['databases']) +k8s_resource('tenant-db', labels=['databases']) +k8s_resource('training-db', labels=['databases']) +k8s_resource('forecasting-db', labels=['databases']) +k8s_resource('sales-db', labels=['databases']) +k8s_resource('external-db', labels=['databases']) +k8s_resource('notification-db', labels=['databases']) +k8s_resource('inventory-db', labels=['databases']) +k8s_resource('recipes-db', labels=['databases']) +k8s_resource('suppliers-db', labels=['databases']) +k8s_resource('pos-db', labels=['databases']) +k8s_resource('orders-db', labels=['databases']) +k8s_resource('production-db', labels=['databases']) + +k8s_resource('redis', labels=['infrastructure']) +k8s_resource('rabbitmq', labels=['infrastructure']) + +# Migration jobs depend on databases +k8s_resource('auth-migration', resource_deps=['auth-db'], labels=['migrations']) +k8s_resource('tenant-migration', resource_deps=['tenant-db'], labels=['migrations']) +k8s_resource('training-migration', resource_deps=['training-db'], labels=['migrations']) +k8s_resource('forecasting-migration', resource_deps=['forecasting-db'], labels=['migrations']) +k8s_resource('sales-migration', resource_deps=['sales-db'], labels=['migrations']) +k8s_resource('external-migration', resource_deps=['external-db'], labels=['migrations']) +k8s_resource('notification-migration', resource_deps=['notification-db'], labels=['migrations']) +k8s_resource('inventory-migration', resource_deps=['inventory-db'], labels=['migrations']) +k8s_resource('recipes-migration', resource_deps=['recipes-db'], labels=['migrations']) +k8s_resource('suppliers-migration', resource_deps=['suppliers-db'], labels=['migrations']) +k8s_resource('pos-migration', resource_deps=['pos-db'], labels=['migrations']) +k8s_resource('orders-migration', resource_deps=['orders-db'], labels=['migrations']) +k8s_resource('production-migration', resource_deps=['production-db'], labels=['migrations']) +k8s_resource('alert-processor-migration', resource_deps=['alert-processor-db'], labels=['migrations']) + +# Alert processor DB +k8s_resource('alert-processor-db', labels=['databases']) + +# Services depend on their databases AND migrations +k8s_resource('auth-service', + resource_deps=['auth-migration', 'redis'], + labels=['services']) + +k8s_resource('tenant-service', + resource_deps=['tenant-migration', 'redis'], + labels=['services']) + +k8s_resource('training-service', + resource_deps=['training-migration', 'redis'], + labels=['services']) + +k8s_resource('forecasting-service', + resource_deps=['forecasting-migration', 'redis'], + labels=['services']) + +k8s_resource('sales-service', + resource_deps=['sales-migration', 'redis'], + labels=['services']) + +k8s_resource('external-service', + resource_deps=['external-migration', 'redis'], + labels=['services']) + +k8s_resource('notification-service', + resource_deps=['notification-migration', 'redis', 'rabbitmq'], + labels=['services']) + +k8s_resource('inventory-service', + resource_deps=['inventory-migration', 'redis'], + labels=['services']) + +k8s_resource('recipes-service', + resource_deps=['recipes-migration', 'redis'], + labels=['services']) + +k8s_resource('suppliers-service', + resource_deps=['suppliers-migration', 'redis'], + labels=['services']) + +k8s_resource('pos-service', + resource_deps=['pos-migration', 'redis'], + labels=['services']) + +k8s_resource('orders-service', + resource_deps=['orders-migration', 'redis'], + labels=['services']) + +k8s_resource('production-service', + resource_deps=['production-migration', 'redis'], + labels=['services']) + +k8s_resource('alert-processor-service', + resource_deps=['alert-processor-migration', 'redis', 'rabbitmq'], + labels=['services']) + +# Gateway and Frontend depend on services being ready +# Access via ingress: http://localhost (frontend) and http://localhost/api (gateway) +k8s_resource('gateway', + resource_deps=['auth-service'], + labels=['frontend']) + +k8s_resource('frontend', + resource_deps=['gateway'], + labels=['frontend']) + +# ============================================================================= +# CONFIGURATION +# ============================================================================= + +# Update check interval - how often Tilt checks for file changes +update_settings(max_parallel_updates=3, k8s_upsert_timeout_secs=60) + +# Optimize for local development +# - Automatically stream logs from services with errors +# - Group resources by labels for better organization +# +# Note: You may see "too many open files" warnings on macOS with many services. +# This is a Kind/Kubernetes limitation and doesn't affect service functionality. +# To work on specific services only, use: tilt up diff --git a/frontend/src/components/domain/auth/SubscriptionSelection.tsx b/frontend/src/components/domain/auth/SubscriptionSelection.tsx index df865cf2..2589e4cc 100644 --- a/frontend/src/components/domain/auth/SubscriptionSelection.tsx +++ b/frontend/src/components/domain/auth/SubscriptionSelection.tsx @@ -93,41 +93,71 @@ export const SubscriptionSelection: React.FC = ({ return ( onPlanSelect(planKey)} > {/* Popular Badge */} {plan.popular && ( -
- +
+ {t('auth:subscription.popular', 'Más Popular')}
)} - {/* Horizontal Layout */} -
- {/* Left Section: Plan Info & Pricing */} -
-

{plan.name}

-
- - {subscriptionService.formatPrice(plan.monthly_price)} - - /mes + {/* Card Content */} +
+ {/* Header Section: Plan Info & Pricing */} +
+
+

{plan.name}

+
+ + {subscriptionService.formatPrice(plan.monthly_price)} + + /mes +
+

{plan.description}

-

{plan.description}

+ {/* Action Button - Desktop position */} +
+ +
+
+ + {/* Body Section: Limits & Features */} +
{/* Plan Limits */} -
+
- {plan.max_users === -1 ? 'Usuarios ilimitados' : `${plan.max_users} usuarios`} + {plan.max_users === -1 ? 'Usuarios ilimitados' : `${plan.max_users} usuario${plan.max_users > 1 ? 's' : ''}`}
@@ -135,72 +165,69 @@ export const SubscriptionSelection: React.FC = ({
- {plan.max_products === -1 ? 'Productos ilimitados' : `${plan.max_products} productos`} + {plan.max_products === -1 ? 'Productos ilimitados' : `${plan.max_products} producto${plan.max_products > 1 ? 's' : ''}`} +
+
+ + {/* Features */} +
+
+ +
+ {t('auth:subscription.features', 'Funcionalidades Incluidas')} +
+
+ +
+ {(() => { + const getPlanFeatures = (planKey: string) => { + switch (planKey) { + case 'starter': + return [ + 'Panel de Control Básico', + 'Gestión de Inventario', + 'Gestión de Pedidos', + 'Gestión de Proveedores', + 'Punto de Venta Básico' + ]; + case 'professional': + return [ + 'Todo lo de Starter', + 'Panel Avanzado', + 'Analytics de Ventas', + 'Pronósticos con IA', + 'Optimización de Producción' + ]; + case 'enterprise': + return [ + 'Todo lo de Professional', + 'Insights Predictivos IA', + 'Analytics Multi-ubicación', + 'Integración ERP', + 'Soporte 24/7 Prioritario', + 'API Personalizada' + ]; + default: + return []; + } + }; + + return getPlanFeatures(planKey).map((feature, index) => ( +
+ + {feature} +
+ )); + })()}
- {/* Divider */} -
- - {/* Right Section: Features */} -
-
- -
- {t('auth:subscription.features', 'Funcionalidades Incluidas')} -
-
- -
- {(() => { - const getPlanFeatures = (planKey: string) => { - switch (planKey) { - case 'starter': - return [ - 'Panel de Control Básico', - 'Gestión de Inventario', - 'Gestión de Pedidos', - 'Gestión de Proveedores', - 'Punto de Venta Básico' - ]; - case 'professional': - return [ - 'Todo lo de Starter', - 'Panel Avanzado', - 'Analytics de Ventas', - 'Pronósticos con IA', - 'Optimización de Producción' - ]; - case 'enterprise': - return [ - 'Todo lo de Professional', - 'Insights Predictivos IA', - 'Analytics Multi-ubicación', - 'Integración ERP', - 'Soporte 24/7 Prioritario', - 'API Personalizada' - ]; - default: - return []; - } - }; - - return getPlanFeatures(planKey).map((feature, index) => ( -
- - {feature} -
- )); - })()} -
-
- - {/* Action Button */} -
+ {/* Action Button - Mobile position */} +