Improve teh securty of teh DB
This commit is contained in:
141
frontend/src/api/hooks/equipment.ts
Normal file
141
frontend/src/api/hooks/equipment.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
// frontend/src/api/hooks/equipment.ts
|
||||
/**
|
||||
* React hooks for Equipment API integration
|
||||
*/
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { equipmentService } from '../services/equipment';
|
||||
import type { Equipment } from '../types/equipment';
|
||||
|
||||
// Query Keys
|
||||
export const equipmentKeys = {
|
||||
all: ['equipment'] as const,
|
||||
lists: () => [...equipmentKeys.all, 'list'] as const,
|
||||
list: (tenantId: string, filters?: Record<string, any>) =>
|
||||
[...equipmentKeys.lists(), tenantId, filters] as const,
|
||||
details: () => [...equipmentKeys.all, 'detail'] as const,
|
||||
detail: (tenantId: string, equipmentId: string) =>
|
||||
[...equipmentKeys.details(), tenantId, equipmentId] as const,
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to fetch equipment list
|
||||
*/
|
||||
export function useEquipment(
|
||||
tenantId: string,
|
||||
filters?: {
|
||||
status?: string;
|
||||
type?: string;
|
||||
is_active?: boolean;
|
||||
},
|
||||
options?: { enabled?: boolean }
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: equipmentKeys.list(tenantId, filters),
|
||||
queryFn: () => equipmentService.getEquipment(tenantId, filters),
|
||||
enabled: !!tenantId && (options?.enabled ?? true),
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to fetch a specific equipment item
|
||||
*/
|
||||
export function useEquipmentById(
|
||||
tenantId: string,
|
||||
equipmentId: string,
|
||||
options?: { enabled?: boolean }
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: equipmentKeys.detail(tenantId, equipmentId),
|
||||
queryFn: () => equipmentService.getEquipmentById(tenantId, equipmentId),
|
||||
enabled: !!tenantId && !!equipmentId && (options?.enabled ?? true),
|
||||
staleTime: 10 * 60 * 1000, // 10 minutes
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to create equipment
|
||||
*/
|
||||
export function useCreateEquipment(tenantId: string) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (equipmentData: Equipment) =>
|
||||
equipmentService.createEquipment(tenantId, equipmentData),
|
||||
onSuccess: (newEquipment) => {
|
||||
// Invalidate and refetch equipment lists
|
||||
queryClient.invalidateQueries({ queryKey: equipmentKeys.lists() });
|
||||
|
||||
// Add to cache
|
||||
queryClient.setQueryData(
|
||||
equipmentKeys.detail(tenantId, newEquipment.id),
|
||||
newEquipment
|
||||
);
|
||||
|
||||
toast.success('Equipment created successfully');
|
||||
},
|
||||
onError: (error: any) => {
|
||||
console.error('Error creating equipment:', error);
|
||||
toast.error(error.response?.data?.detail || 'Error creating equipment');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to update equipment
|
||||
*/
|
||||
export function useUpdateEquipment(tenantId: string) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ equipmentId, equipmentData }: {
|
||||
equipmentId: string;
|
||||
equipmentData: Partial<Equipment>;
|
||||
}) => equipmentService.updateEquipment(tenantId, equipmentId, equipmentData),
|
||||
onSuccess: (updatedEquipment, { equipmentId }) => {
|
||||
// Update cached data
|
||||
queryClient.setQueryData(
|
||||
equipmentKeys.detail(tenantId, equipmentId),
|
||||
updatedEquipment
|
||||
);
|
||||
|
||||
// Invalidate lists to refresh
|
||||
queryClient.invalidateQueries({ queryKey: equipmentKeys.lists() });
|
||||
|
||||
toast.success('Equipment updated successfully');
|
||||
},
|
||||
onError: (error: any) => {
|
||||
console.error('Error updating equipment:', error);
|
||||
toast.error(error.response?.data?.detail || 'Error updating equipment');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to delete equipment
|
||||
*/
|
||||
export function useDeleteEquipment(tenantId: string) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (equipmentId: string) =>
|
||||
equipmentService.deleteEquipment(tenantId, equipmentId),
|
||||
onSuccess: (_, equipmentId) => {
|
||||
// Remove from cache
|
||||
queryClient.removeQueries({
|
||||
queryKey: equipmentKeys.detail(tenantId, equipmentId)
|
||||
});
|
||||
|
||||
// Invalidate lists to refresh
|
||||
queryClient.invalidateQueries({ queryKey: equipmentKeys.lists() });
|
||||
|
||||
toast.success('Equipment deleted successfully');
|
||||
},
|
||||
onError: (error: any) => {
|
||||
console.error('Error deleting equipment:', error);
|
||||
toast.error(error.response?.data?.detail || 'Error deleting equipment');
|
||||
},
|
||||
});
|
||||
}
|
||||
178
frontend/src/api/services/equipment.ts
Normal file
178
frontend/src/api/services/equipment.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
// frontend/src/api/services/equipment.ts
|
||||
/**
|
||||
* Equipment API service
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client';
|
||||
import type {
|
||||
Equipment,
|
||||
EquipmentCreate,
|
||||
EquipmentUpdate,
|
||||
EquipmentResponse,
|
||||
EquipmentListResponse
|
||||
} from '../types/equipment';
|
||||
|
||||
class EquipmentService {
|
||||
private readonly baseURL = '/tenants';
|
||||
|
||||
/**
|
||||
* Helper to convert snake_case API response to camelCase Equipment
|
||||
*/
|
||||
private convertToEquipment(response: EquipmentResponse): Equipment {
|
||||
return {
|
||||
id: response.id,
|
||||
tenant_id: response.tenant_id,
|
||||
name: response.name,
|
||||
type: response.type,
|
||||
model: response.model || '',
|
||||
serialNumber: response.serial_number || '',
|
||||
location: response.location || '',
|
||||
status: response.status,
|
||||
installDate: response.install_date || new Date().toISOString().split('T')[0],
|
||||
lastMaintenance: response.last_maintenance_date || new Date().toISOString().split('T')[0],
|
||||
nextMaintenance: response.next_maintenance_date || new Date().toISOString().split('T')[0],
|
||||
maintenanceInterval: response.maintenance_interval_days || 30,
|
||||
temperature: response.current_temperature || undefined,
|
||||
targetTemperature: response.target_temperature || undefined,
|
||||
efficiency: response.efficiency_percentage || 0,
|
||||
uptime: response.uptime_percentage || 0,
|
||||
energyUsage: response.energy_usage_kwh || 0,
|
||||
utilizationToday: 0, // Not in backend yet
|
||||
alerts: [], // Not in backend yet
|
||||
maintenanceHistory: [], // Not in backend yet
|
||||
specifications: {
|
||||
power: response.power_kw || 0,
|
||||
capacity: response.capacity || 0,
|
||||
dimensions: {
|
||||
width: 0, // Not in backend separately
|
||||
height: 0,
|
||||
depth: 0
|
||||
},
|
||||
weight: response.weight_kg || 0
|
||||
},
|
||||
is_active: response.is_active,
|
||||
created_at: response.created_at,
|
||||
updated_at: response.updated_at
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to convert Equipment to API request format (snake_case)
|
||||
*/
|
||||
private convertToApiFormat(equipment: Partial<Equipment>): EquipmentCreate | EquipmentUpdate {
|
||||
return {
|
||||
name: equipment.name,
|
||||
type: equipment.type,
|
||||
model: equipment.model,
|
||||
serial_number: equipment.serialNumber,
|
||||
location: equipment.location,
|
||||
status: equipment.status,
|
||||
install_date: equipment.installDate,
|
||||
last_maintenance_date: equipment.lastMaintenance,
|
||||
next_maintenance_date: equipment.nextMaintenance,
|
||||
maintenance_interval_days: equipment.maintenanceInterval,
|
||||
efficiency_percentage: equipment.efficiency,
|
||||
uptime_percentage: equipment.uptime,
|
||||
energy_usage_kwh: equipment.energyUsage,
|
||||
power_kw: equipment.specifications?.power,
|
||||
capacity: equipment.specifications?.capacity,
|
||||
weight_kg: equipment.specifications?.weight,
|
||||
current_temperature: equipment.temperature,
|
||||
target_temperature: equipment.targetTemperature,
|
||||
is_active: equipment.is_active
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all equipment for a tenant
|
||||
*/
|
||||
async getEquipment(
|
||||
tenantId: string,
|
||||
filters?: {
|
||||
status?: string;
|
||||
type?: string;
|
||||
is_active?: boolean;
|
||||
}
|
||||
): Promise<Equipment[]> {
|
||||
const params = new URLSearchParams();
|
||||
if (filters?.status) params.append('status', filters.status);
|
||||
if (filters?.type) params.append('type', filters.type);
|
||||
if (filters?.is_active !== undefined) params.append('is_active', String(filters.is_active));
|
||||
|
||||
const queryString = params.toString();
|
||||
const url = `${this.baseURL}/${tenantId}/production/equipment${queryString ? `?${queryString}` : ''}`;
|
||||
|
||||
const data: EquipmentListResponse = await apiClient.get(url, {
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
});
|
||||
|
||||
return data.equipment.map(eq => this.convertToEquipment(eq));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific equipment item
|
||||
*/
|
||||
async getEquipmentById(
|
||||
tenantId: string,
|
||||
equipmentId: string
|
||||
): Promise<Equipment> {
|
||||
const data: EquipmentResponse = await apiClient.get(
|
||||
`${this.baseURL}/${tenantId}/production/equipment/${equipmentId}`,
|
||||
{
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
}
|
||||
);
|
||||
return this.convertToEquipment(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new equipment item
|
||||
*/
|
||||
async createEquipment(
|
||||
tenantId: string,
|
||||
equipmentData: Equipment
|
||||
): Promise<Equipment> {
|
||||
const apiData = this.convertToApiFormat(equipmentData);
|
||||
const data: EquipmentResponse = await apiClient.post(
|
||||
`${this.baseURL}/${tenantId}/production/equipment`,
|
||||
apiData,
|
||||
{
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
}
|
||||
);
|
||||
return this.convertToEquipment(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an equipment item
|
||||
*/
|
||||
async updateEquipment(
|
||||
tenantId: string,
|
||||
equipmentId: string,
|
||||
equipmentData: Partial<Equipment>
|
||||
): Promise<Equipment> {
|
||||
const apiData = this.convertToApiFormat(equipmentData);
|
||||
const data: EquipmentResponse = await apiClient.put(
|
||||
`${this.baseURL}/${tenantId}/production/equipment/${equipmentId}`,
|
||||
apiData,
|
||||
{
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
}
|
||||
);
|
||||
return this.convertToEquipment(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an equipment item
|
||||
*/
|
||||
async deleteEquipment(tenantId: string, equipmentId: string): Promise<void> {
|
||||
await apiClient.delete(
|
||||
`${this.baseURL}/${tenantId}/production/equipment/${equipmentId}`,
|
||||
{
|
||||
headers: { 'X-Tenant-ID': tenantId }
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const equipmentService = new EquipmentService();
|
||||
@@ -172,9 +172,11 @@ class TrainingService {
|
||||
* Get WebSocket URL for real-time training updates
|
||||
*/
|
||||
getTrainingWebSocketUrl(tenantId: string, jobId: string): string {
|
||||
const baseWsUrl = apiClient.getAxiosInstance().defaults.baseURL?.replace(/^http/, 'ws');
|
||||
const baseWsUrl = apiClient.getAxiosInstance().defaults.baseURL
|
||||
?.replace(/^http(s?):/, 'ws$1:'); // http: → ws:, https: → wss:
|
||||
return `${baseWsUrl}/tenants/${tenantId}/training/jobs/${jobId}/live`;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper method to construct WebSocket connection
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface EquipmentSpecifications {
|
||||
|
||||
export interface Equipment {
|
||||
id: string;
|
||||
tenant_id?: string;
|
||||
name: string;
|
||||
type: 'oven' | 'mixer' | 'proofer' | 'freezer' | 'packaging' | 'other';
|
||||
model: string;
|
||||
@@ -51,4 +52,90 @@ export interface Equipment {
|
||||
alerts: EquipmentAlert[];
|
||||
maintenanceHistory: MaintenanceHistory[];
|
||||
specifications: EquipmentSpecifications;
|
||||
is_active?: boolean;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
// API Request/Response types
|
||||
export type EquipmentType = 'oven' | 'mixer' | 'proofer' | 'freezer' | 'packaging' | 'other';
|
||||
export type EquipmentStatus = 'operational' | 'maintenance' | 'down' | 'warning';
|
||||
|
||||
export interface EquipmentCreate {
|
||||
name: string;
|
||||
type: EquipmentType;
|
||||
model?: string;
|
||||
serial_number?: string;
|
||||
location?: string;
|
||||
status?: EquipmentStatus;
|
||||
install_date?: string;
|
||||
last_maintenance_date?: string;
|
||||
next_maintenance_date?: string;
|
||||
maintenance_interval_days?: number;
|
||||
efficiency_percentage?: number;
|
||||
uptime_percentage?: number;
|
||||
energy_usage_kwh?: number;
|
||||
power_kw?: number;
|
||||
capacity?: number;
|
||||
weight_kg?: number;
|
||||
current_temperature?: number;
|
||||
target_temperature?: number;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface EquipmentUpdate {
|
||||
name?: string;
|
||||
type?: EquipmentType;
|
||||
model?: string;
|
||||
serial_number?: string;
|
||||
location?: string;
|
||||
status?: EquipmentStatus;
|
||||
install_date?: string;
|
||||
last_maintenance_date?: string;
|
||||
next_maintenance_date?: string;
|
||||
maintenance_interval_days?: number;
|
||||
efficiency_percentage?: number;
|
||||
uptime_percentage?: number;
|
||||
energy_usage_kwh?: number;
|
||||
power_kw?: number;
|
||||
capacity?: number;
|
||||
weight_kg?: number;
|
||||
current_temperature?: number;
|
||||
target_temperature?: number;
|
||||
is_active?: boolean;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface EquipmentResponse {
|
||||
id: string;
|
||||
tenant_id: string;
|
||||
name: string;
|
||||
type: EquipmentType;
|
||||
model: string | null;
|
||||
serial_number: string | null;
|
||||
location: string | null;
|
||||
status: EquipmentStatus;
|
||||
install_date: string | null;
|
||||
last_maintenance_date: string | null;
|
||||
next_maintenance_date: string | null;
|
||||
maintenance_interval_days: number | null;
|
||||
efficiency_percentage: number | null;
|
||||
uptime_percentage: number | null;
|
||||
energy_usage_kwh: number | null;
|
||||
power_kw: number | null;
|
||||
capacity: number | null;
|
||||
weight_kg: number | null;
|
||||
current_temperature: number | null;
|
||||
target_temperature: number | null;
|
||||
is_active: boolean;
|
||||
notes: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface EquipmentListResponse {
|
||||
equipment: EquipmentResponse[];
|
||||
total_count: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
}
|
||||
@@ -465,6 +465,12 @@ export interface ProductSuggestionResponse {
|
||||
is_seasonal: boolean;
|
||||
suggested_supplier: string | null;
|
||||
notes: string | null;
|
||||
sales_data?: {
|
||||
total_quantity: number;
|
||||
average_daily_sales: number;
|
||||
peak_day: string;
|
||||
frequency: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BusinessModelAnalysisResponse {
|
||||
|
||||
@@ -97,15 +97,6 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleDemoLogin = () => {
|
||||
setCredentials({
|
||||
email: 'admin@bakery.com',
|
||||
password: 'admin12345',
|
||||
remember_me: false
|
||||
});
|
||||
setErrors({});
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !isLoading) {
|
||||
handleSubmit(e as any);
|
||||
@@ -290,30 +281,6 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
||||
<div id="login-button-description" className="sr-only">
|
||||
Presiona Enter o haz clic para iniciar sesión con tus credenciales
|
||||
</div>
|
||||
|
||||
{/* Demo Login Section */}
|
||||
<div className="mt-6">
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<div className="w-full border-t border-border-primary" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-sm">
|
||||
<span className="px-2 bg-background-primary text-text-tertiary">Demo</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleDemoLogin}
|
||||
disabled={isLoading}
|
||||
className="w-full focus:outline-none focus:ring-2 focus:ring-color-primary focus:ring-offset-2"
|
||||
>
|
||||
Usar credenciales de demostración
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{onRegisterClick && (
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '../../../ui/Button';
|
||||
import { Input } from '../../../ui/Input';
|
||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||
import { useCreateIngredient, useClassifyBatch } from '../../../../api/hooks/inventory';
|
||||
import { useValidateImportFile, useImportSalesData } from '../../../../api/hooks/sales';
|
||||
import type { ImportValidationResult } from '../../../../api/types/sales';
|
||||
import type { ImportValidationResponse } from '../../../../api/types/dataImport';
|
||||
import type { ProductSuggestionResponse } from '../../../../api/types/inventory';
|
||||
import { useAuth } from '../../../../contexts/AuthContext';
|
||||
|
||||
interface UploadSalesDataStepProps {
|
||||
@@ -52,6 +54,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
||||
onComplete,
|
||||
isFirstStep
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||
const [isValidating, setIsValidating] = useState(false);
|
||||
const [validationResult, setValidationResult] = useState<ImportValidationResponse | null>(null);
|
||||
@@ -60,6 +63,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [error, setError] = useState<string>('');
|
||||
const [progressState, setProgressState] = useState<ProgressState | null>(null);
|
||||
const [showGuide, setShowGuide] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const currentTenant = useCurrentTenant();
|
||||
@@ -132,7 +136,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
||||
};
|
||||
|
||||
|
||||
const generateInventorySuggestionsAuto = async (validationData: ImportValidationResult) => {
|
||||
const generateInventorySuggestionsAuto = async (validationData: ImportValidationResponse) => {
|
||||
if (!currentTenant?.id) {
|
||||
setError('No hay datos de validación disponibles para generar sugerencias');
|
||||
setIsValidating(false);
|
||||
@@ -166,7 +170,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
||||
setProgressState({ stage: 'preparing', progress: 90, message: 'Preparando sugerencias de inventario...' });
|
||||
|
||||
// Convert API response to InventoryItem format - use exact backend structure plus UI fields
|
||||
const items: InventoryItem[] = classificationResponse.suggestions.map(suggestion => {
|
||||
const items: InventoryItem[] = classificationResponse.suggestions.map((suggestion: ProductSuggestionResponse) => {
|
||||
// Calculate default stock quantity based on sales data
|
||||
const defaultStock = Math.max(
|
||||
Math.ceil((suggestion.sales_data?.average_daily_sales || 1) * 7), // 1 week supply
|
||||
@@ -534,6 +538,113 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* File Format Guide */}
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<svg className="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<h3 className="font-semibold text-blue-900">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.title', 'Guía de Formato de Archivo')}
|
||||
</h3>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowGuide(!showGuide)}
|
||||
className="text-blue-600 hover:text-blue-800 text-sm font-medium"
|
||||
>
|
||||
{showGuide
|
||||
? t('onboarding:steps.inventory_setup.file_format_guide.collapse_guide', 'Ocultar Guía')
|
||||
: t('onboarding:steps.inventory_setup.file_format_guide.toggle_guide', 'Ver Guía Completa')
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Quick Summary - Always Visible */}
|
||||
<div className="text-sm text-blue-800 space-y-1">
|
||||
<p>
|
||||
<strong>{t('onboarding:steps.inventory_setup.file_format_guide.supported_formats.title', 'Formatos Soportados')}:</strong>{' '}
|
||||
CSV, JSON, Excel (XLSX) • {t('onboarding:steps.inventory_setup.file_format_guide.supported_formats.max_size', 'Tamaño máximo: 10MB')}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.title', 'Columnas Requeridas')}:</strong>{' '}
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.date', 'Fecha')},{' '}
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.product', 'Nombre del Producto')},{' '}
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.quantity', 'Cantidad Vendida')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Detailed Guide - Collapsible */}
|
||||
{showGuide && (
|
||||
<div className="mt-4 pt-4 border-t border-blue-200 space-y-4">
|
||||
{/* Required Columns Detail */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-blue-900 mb-2">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.title', 'Columnas Requeridas')}
|
||||
</h4>
|
||||
<div className="text-sm text-blue-800 space-y-1 pl-4">
|
||||
<p>
|
||||
• <strong>{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.date', 'Fecha')}:</strong>{' '}
|
||||
<span className="font-mono text-xs bg-blue-100 px-1 rounded">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.date_examples', 'date, fecha, data')}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
• <strong>{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.product', 'Nombre del Producto')}:</strong>{' '}
|
||||
<span className="font-mono text-xs bg-blue-100 px-1 rounded">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.product_examples', 'product, producto, product_name')}
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
• <strong>{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.quantity', 'Cantidad Vendida')}:</strong>{' '}
|
||||
<span className="font-mono text-xs bg-blue-100 px-1 rounded">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.required_columns.quantity_examples', 'quantity, cantidad, quantity_sold')}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Optional Columns */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-blue-900 mb-2">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.title', 'Columnas Opcionales')}
|
||||
</h4>
|
||||
<div className="text-sm text-blue-800 space-y-1 pl-4">
|
||||
<p>• {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.revenue', 'Ingresos (revenue, ingresos, ventas)')}</p>
|
||||
<p>• {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.unit_price', 'Precio Unitario (unit_price, precio, price)')}</p>
|
||||
<p>• {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.category', 'Categoría (category, categoria)')}</p>
|
||||
<p>• {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.sku', 'SKU del Producto')}</p>
|
||||
<p>• {t('onboarding:steps.inventory_setup.file_format_guide.optional_columns.location', 'Ubicación/Tienda')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Date Formats */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-blue-900 mb-2">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.date_formats.title', 'Formatos de Fecha Soportados')}
|
||||
</h4>
|
||||
<div className="text-sm text-blue-800 pl-4">
|
||||
<p>{t('onboarding:steps.inventory_setup.file_format_guide.date_formats.formats', 'YYYY-MM-DD, DD/MM/YYYY, MM/DD/YYYY, DD-MM-YYYY, y más')}</p>
|
||||
<p className="text-xs mt-1">{t('onboarding:steps.inventory_setup.file_format_guide.date_formats.with_time', 'También se admiten formatos con hora')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Automatic Features */}
|
||||
<div>
|
||||
<h4 className="font-semibold text-blue-900 mb-2">
|
||||
{t('onboarding:steps.inventory_setup.file_format_guide.features.title', 'Características Automáticas')}
|
||||
</h4>
|
||||
<div className="text-sm text-blue-800 space-y-1 pl-4">
|
||||
<p>✓ {t('onboarding:steps.inventory_setup.file_format_guide.features.multilingual', 'Detección multiidioma de columnas')}</p>
|
||||
<p>✓ {t('onboarding:steps.inventory_setup.file_format_guide.features.validation', 'Validación automática con reporte detallado')}</p>
|
||||
<p>✓ {t('onboarding:steps.inventory_setup.file_format_guide.features.ai_classification', 'Clasificación de productos con IA')}</p>
|
||||
<p>✓ {t('onboarding:steps.inventory_setup.file_format_guide.features.inventory_suggestions', 'Sugerencias inteligentes de inventario')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* File Upload Area */}
|
||||
<div
|
||||
className={`border-2 border-dashed rounded-lg p-8 text-center transition-colors ${
|
||||
@@ -626,7 +737,7 @@ export const UploadSalesDataStep: React.FC<UploadSalesDataStepProps> = ({
|
||||
<div className="mt-2">
|
||||
<p className="font-medium text-[var(--color-warning)]">Warnings:</p>
|
||||
<ul className="list-disc list-inside">
|
||||
{validationResult.warnings.map((warning, index) => (
|
||||
{validationResult.warnings.map((warning: any, index: number) => (
|
||||
<li key={index} className="text-[var(--color-warning)]">
|
||||
{typeof warning === 'string' ? warning : JSON.stringify(warning)}
|
||||
</li>
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { SubscriptionPricingCards } from './SubscriptionPricingCards';
|
||||
|
||||
export const PricingSection: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<section id="pricing" className="py-24 bg-[var(--bg-primary)]">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-8">
|
||||
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)]">
|
||||
Planes que se Adaptan a tu Negocio
|
||||
{t('landing:pricing.title', 'Planes que se Adaptan a tu Negocio')}
|
||||
</h2>
|
||||
<p className="mt-4 max-w-2xl mx-auto text-lg text-[var(--text-secondary)]">
|
||||
Sin costos ocultos, sin compromisos largos. Comienza gratis y escala según crezcas.
|
||||
{t('landing:pricing.subtitle', 'Sin costos ocultos, sin compromisos largos. Comienza gratis y escala según crezcas.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -26,7 +29,7 @@ export const PricingSection: React.FC = () => {
|
||||
to="/plans/compare"
|
||||
className="text-[var(--color-primary)] hover:text-[var(--color-primary-dark)] font-semibold inline-flex items-center gap-2"
|
||||
>
|
||||
Ver comparación completa de características
|
||||
{t('landing:pricing.compare_link', 'Ver comparación completa de características')}
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -45,12 +45,28 @@ export const SSEProvider: React.FC<SSEProviderProps> = ({ children }) => {
|
||||
const currentTenant = useCurrentTenant();
|
||||
|
||||
const connect = () => {
|
||||
if (!isAuthenticated || !token || eventSourceRef.current) return;
|
||||
// Check if we're in demo mode
|
||||
const isDemoMode = localStorage.getItem('demo_mode') === 'true';
|
||||
const demoSessionId = localStorage.getItem('demo_session_id');
|
||||
|
||||
// Skip SSE connection for demo/development mode when no backend is available
|
||||
if (token === 'mock-jwt-token') {
|
||||
console.log('SSE connection skipped for demo mode');
|
||||
return;
|
||||
// For demo mode, we need demo_session_id and tenant
|
||||
// For regular mode, we need token and authentication
|
||||
if (isDemoMode) {
|
||||
if (!demoSessionId || !currentTenant?.id || eventSourceRef.current) {
|
||||
console.log('Demo mode: Missing demo session ID or tenant ID for SSE connection');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!isAuthenticated || !token || eventSourceRef.current) {
|
||||
console.log('Regular mode: Not authenticated or missing token for SSE connection');
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip SSE connection for mock tokens in development mode
|
||||
if (token === 'mock-jwt-token') {
|
||||
console.log('SSE connection skipped for mock token');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentTenant?.id) {
|
||||
@@ -59,13 +75,21 @@ export const SSEProvider: React.FC<SSEProviderProps> = ({ children }) => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Connect to gateway SSE endpoint with token and tenant_id
|
||||
// Connect to gateway SSE endpoint with token/demo_session_id and tenant_id
|
||||
// Use same protocol and host as the current page to avoid CORS and mixed content issues
|
||||
const protocol = window.location.protocol;
|
||||
const host = window.location.host;
|
||||
const sseUrl = `${protocol}//${host}/api/events?token=${encodeURIComponent(token)}&tenant_id=${currentTenant.id}`;
|
||||
|
||||
console.log('Connecting to SSE endpoint:', sseUrl);
|
||||
let sseUrl: string;
|
||||
if (isDemoMode && demoSessionId) {
|
||||
// For demo mode, use demo_session_id instead of token
|
||||
sseUrl = `${protocol}//${host}/api/events?demo_session_id=${encodeURIComponent(demoSessionId)}&tenant_id=${currentTenant.id}`;
|
||||
console.log('Connecting to SSE endpoint (demo mode):', sseUrl);
|
||||
} else {
|
||||
// For regular mode, use JWT token
|
||||
sseUrl = `${protocol}//${host}/api/events?token=${encodeURIComponent(token!)}&tenant_id=${currentTenant.id}`;
|
||||
console.log('Connecting to SSE endpoint (regular mode):', sseUrl);
|
||||
}
|
||||
|
||||
const eventSource = new EventSource(sseUrl, {
|
||||
withCredentials: true,
|
||||
@@ -358,7 +382,16 @@ export const SSEProvider: React.FC<SSEProviderProps> = ({ children }) => {
|
||||
|
||||
// Connect when authenticated, disconnect when not or when tenant changes
|
||||
useEffect(() => {
|
||||
if (isAuthenticated && token && currentTenant) {
|
||||
const isDemoMode = localStorage.getItem('demo_mode') === 'true';
|
||||
const demoSessionId = localStorage.getItem('demo_session_id');
|
||||
|
||||
// For demo mode: connect if we have demo_session_id and tenant
|
||||
// For regular mode: connect if authenticated with token and tenant
|
||||
const shouldConnect = isDemoMode
|
||||
? (demoSessionId && currentTenant)
|
||||
: (isAuthenticated && token && currentTenant);
|
||||
|
||||
if (shouldConnect) {
|
||||
connect();
|
||||
} else {
|
||||
disconnect();
|
||||
|
||||
@@ -190,8 +190,8 @@
|
||||
"description": "We use academically validated AI algorithms, specifically adapted for bakeries."
|
||||
},
|
||||
"team": {
|
||||
"title": "Expert Team",
|
||||
"description": "Founders with experience in AI + hospitality. We know the industry from the inside."
|
||||
"title": "Expert Founder",
|
||||
"description": "Entrepreneur with over a decade of international experience in AI, digital transformation, and high-value technology projects across Europe, Asia, and America"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -221,6 +221,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"business_models": {
|
||||
"title": "Your Business Model, Our Technology",
|
||||
"subtitle": "Whether you produce and sell in one location, or manage a central workshop with multiple points of sale, our AI adapts to your way of working",
|
||||
"local_production": {
|
||||
"title": "Local Production",
|
||||
"subtitle": "Single point of sale and production",
|
||||
"description": "Your bakery produces and sells in the same location. You need to optimize daily production, minimize waste, and maximize freshness in each batch.",
|
||||
"features": {
|
||||
"prediction": "<strong>Demand prediction</strong> for single location",
|
||||
"inventory": "<strong>Inventory management</strong> simplified and direct",
|
||||
"control": "<strong>Single control point</strong> - simple and efficient"
|
||||
}
|
||||
},
|
||||
"central_workshop": {
|
||||
"title": "Central Workshop + Points of Sale",
|
||||
"subtitle": "Centralized production, multiple distribution",
|
||||
"description": "You produce centrally and distribute to multiple points of sale. You need to coordinate production, logistics, and demand across locations to optimize each point.",
|
||||
"features": {
|
||||
"prediction": "<strong>Aggregated and per-point-of-sale</strong> individual prediction",
|
||||
"distribution": "<strong>Distribution management</strong> coordinated multi-location",
|
||||
"visibility": "<strong>Centralized visibility</strong> with granular control"
|
||||
}
|
||||
},
|
||||
"same_ai": "The same powerful AI, adapted to your way of working"
|
||||
},
|
||||
"pricing": {
|
||||
"title": "Plans That Fit Your Business",
|
||||
"subtitle": "No hidden costs, no long commitments. Start free and scale as you grow.",
|
||||
"compare_link": "View complete feature comparison"
|
||||
},
|
||||
"final_cta": {
|
||||
"scarcity_badge": "12 spots remaining of the 20 pilot program",
|
||||
"title": "Be Among the First 20 Bakeries",
|
||||
|
||||
@@ -68,6 +68,51 @@
|
||||
"supported_formats": "Supported formats: CSV",
|
||||
"max_size": "Maximum size: 10MB"
|
||||
},
|
||||
"file_format_guide": {
|
||||
"title": "File Format Guide",
|
||||
"supported_formats": {
|
||||
"title": "Supported Formats",
|
||||
"csv": "CSV (comma-separated values)",
|
||||
"json": "JSON (JavaScript Object Notation)",
|
||||
"excel": "Excel (XLSX, XLS)",
|
||||
"max_size": "Maximum size: 10MB"
|
||||
},
|
||||
"required_columns": {
|
||||
"title": "Required Columns",
|
||||
"date": "Date",
|
||||
"date_examples": "date, fecha, data",
|
||||
"product": "Product Name",
|
||||
"product_examples": "product, producto, product_name, name",
|
||||
"quantity": "Quantity Sold",
|
||||
"quantity_examples": "quantity, cantidad, quantity_sold, units"
|
||||
},
|
||||
"optional_columns": {
|
||||
"title": "Optional Columns",
|
||||
"revenue": "Revenue (revenue, ingresos, sales)",
|
||||
"unit_price": "Unit Price (unit_price, precio, price)",
|
||||
"category": "Category (category, categoria)",
|
||||
"sku": "Product SKU",
|
||||
"location": "Location/Store",
|
||||
"notes": "Additional notes"
|
||||
},
|
||||
"date_formats": {
|
||||
"title": "Supported Date Formats",
|
||||
"formats": "YYYY-MM-DD, DD/MM/YYYY, MM/DD/YYYY, DD-MM-YYYY, and more",
|
||||
"with_time": "Time formats are also supported (e.g., YYYY-MM-DD HH:MM:SS)"
|
||||
},
|
||||
"features": {
|
||||
"title": "Automatic Features",
|
||||
"multilingual": "Multi-language column detection (Spanish, English, Basque)",
|
||||
"validation": "Automatic validation with detailed error reporting",
|
||||
"ai_classification": "AI-powered product classification",
|
||||
"inventory_suggestions": "Smart inventory suggestions"
|
||||
},
|
||||
"download_template": "Download Template",
|
||||
"show_example": "Show Data Example",
|
||||
"hide_example": "Hide Example",
|
||||
"toggle_guide": "Show Full Guide",
|
||||
"collapse_guide": "Hide Guide"
|
||||
},
|
||||
"sample": {
|
||||
"download": "Download CSV template",
|
||||
"example": "View data example"
|
||||
|
||||
@@ -190,8 +190,8 @@
|
||||
"description": "Usamos algoritmos de IA validados académicamente, adaptados específicamente para panaderías."
|
||||
},
|
||||
"team": {
|
||||
"title": "Equipo Experto",
|
||||
"description": "Fundadores con experiencia en proyectos de alto valor tecnológico + proyectos internacionales"
|
||||
"title": "Fundador Experto",
|
||||
"description": "Emprendedor con más de una década de experiencia internacional en IA, transformación digital y proyectos de alto valor tecnológico en Europa, Asia y América"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -221,6 +221,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"business_models": {
|
||||
"title": "Tu Modelo de Negocio, Nuestra Tecnología",
|
||||
"subtitle": "Ya sea que produzcas y vendas en un solo lugar, o gestiones un obrador central con múltiples puntos de venta, nuestra IA se adapta a tu forma de trabajar",
|
||||
"local_production": {
|
||||
"title": "Producción Local",
|
||||
"subtitle": "Un punto de venta y producción",
|
||||
"description": "Tu panadería produce y vende en el mismo lugar. Necesitas optimizar producción diaria, minimizar desperdicios y maximizar frescura en cada horneada.",
|
||||
"features": {
|
||||
"prediction": "<strong>Predicción de demanda</strong> por ubicación única",
|
||||
"inventory": "<strong>Gestión de inventario</strong> simplificada y directa",
|
||||
"control": "<strong>Un solo punto de control</strong> - simple y eficiente"
|
||||
}
|
||||
},
|
||||
"central_workshop": {
|
||||
"title": "Obrador Central + Puntos de Venta",
|
||||
"subtitle": "Producción centralizada, distribución múltiple",
|
||||
"description": "Produces centralmente y distribuyes a múltiples puntos de venta. Necesitas coordinar producción, logística y demanda entre ubicaciones para optimizar cada punto.",
|
||||
"features": {
|
||||
"prediction": "<strong>Predicción agregada y por punto de venta</strong> individual",
|
||||
"distribution": "<strong>Gestión de distribución</strong> multi-ubicación coordinada",
|
||||
"visibility": "<strong>Visibilidad centralizada</strong> con control granular"
|
||||
}
|
||||
},
|
||||
"same_ai": "La misma IA potente, adaptada a tu forma de trabajar"
|
||||
},
|
||||
"pricing": {
|
||||
"title": "Planes que se Adaptan a tu Negocio",
|
||||
"subtitle": "Sin costos ocultos, sin compromisos largos. Comienza gratis y escala según crezcas.",
|
||||
"compare_link": "Ver comparación completa de características"
|
||||
},
|
||||
"final_cta": {
|
||||
"scarcity_badge": "Quedan 12 plazas de las 20 del programa piloto",
|
||||
"title": "Sé de las Primeras 20 Panaderías",
|
||||
|
||||
@@ -68,6 +68,51 @@
|
||||
"supported_formats": "Formatos soportados: CSV",
|
||||
"max_size": "Tamaño máximo: 10MB"
|
||||
},
|
||||
"file_format_guide": {
|
||||
"title": "Guía de Formato de Archivo",
|
||||
"supported_formats": {
|
||||
"title": "Formatos Soportados",
|
||||
"csv": "CSV (valores separados por comas)",
|
||||
"json": "JSON (JavaScript Object Notation)",
|
||||
"excel": "Excel (XLSX, XLS)",
|
||||
"max_size": "Tamaño máximo: 10MB"
|
||||
},
|
||||
"required_columns": {
|
||||
"title": "Columnas Requeridas",
|
||||
"date": "Fecha",
|
||||
"date_examples": "date, fecha, data",
|
||||
"product": "Nombre del Producto",
|
||||
"product_examples": "product, producto, product_name, nombre",
|
||||
"quantity": "Cantidad Vendida",
|
||||
"quantity_examples": "quantity, cantidad, quantity_sold, unidades"
|
||||
},
|
||||
"optional_columns": {
|
||||
"title": "Columnas Opcionales",
|
||||
"revenue": "Ingresos (revenue, ingresos, ventas)",
|
||||
"unit_price": "Precio Unitario (unit_price, precio, price)",
|
||||
"category": "Categoría (category, categoria)",
|
||||
"sku": "SKU del Producto",
|
||||
"location": "Ubicación/Tienda",
|
||||
"notes": "Notas adicionales"
|
||||
},
|
||||
"date_formats": {
|
||||
"title": "Formatos de Fecha Soportados",
|
||||
"formats": "YYYY-MM-DD, DD/MM/YYYY, MM/DD/YYYY, DD-MM-YYYY, y más",
|
||||
"with_time": "También se admiten formatos con hora (ej: YYYY-MM-DD HH:MM:SS)"
|
||||
},
|
||||
"features": {
|
||||
"title": "Características Automáticas",
|
||||
"multilingual": "Detección multiidioma de columnas (Español, Inglés, Euskera)",
|
||||
"validation": "Validación automática con reporte detallado de errores",
|
||||
"ai_classification": "Clasificación de productos con IA",
|
||||
"inventory_suggestions": "Sugerencias inteligentes de inventario"
|
||||
},
|
||||
"download_template": "Descargar Plantilla",
|
||||
"show_example": "Ver Ejemplo de Datos",
|
||||
"hide_example": "Ocultar Ejemplo",
|
||||
"toggle_guide": "Ver Guía Completa",
|
||||
"collapse_guide": "Ocultar Guía"
|
||||
},
|
||||
"sample": {
|
||||
"download": "Descargar plantilla CSV",
|
||||
"example": "Ver ejemplo de datos"
|
||||
|
||||
@@ -190,8 +190,8 @@
|
||||
"description": "Akademikoki baliozkotutako AA algoritmoak erabiltzen ditugu, okindegietarako bereziki egokituak."
|
||||
},
|
||||
"team": {
|
||||
"title": "Talde Adituak",
|
||||
"description": "AA + ostalaritzako esperientziadun sortzaileak. Barrualdetik ezagutzen dugu sektorea."
|
||||
"title": "Sortzaile Aditua",
|
||||
"description": "Hamarkada bat baino gehiagoko nazioarteko esperientzia duen ekintzailea AA, eraldaketa digital eta balio handiko teknologia proiektuetan Europan, Asian eta Amerikan zehar"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -221,6 +221,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"business_models": {
|
||||
"title": "Zure Negozio Eredua, Gure Teknologia",
|
||||
"subtitle": "Leku bakarrean ekoizten eta saltzen duzun ala lantegi zentral bat hainbat salmenta punturekin kudeatzen duzun, gure AA zure lan moduari egokitzen zaio",
|
||||
"local_production": {
|
||||
"title": "Tokiko Ekoizpena",
|
||||
"subtitle": "Salmenta eta ekoizpen puntu bakarra",
|
||||
"description": "Zure okindegiak leku berean ekoizten eta saltzen du. Eguneko ekoizpena optimizatu, hondakinak minimizatu eta frekotasuna maximizatu behar duzu horneada bakoitzean.",
|
||||
"features": {
|
||||
"prediction": "<strong>Eskari aurreikuspena</strong> kokaleku bakarreko",
|
||||
"inventory": "<strong>Inbentario kudeaketa</strong> sinplifikatua eta zuzena",
|
||||
"control": "<strong>Kontrol puntu bakarra</strong> - sinplea eta eraginkorra"
|
||||
}
|
||||
},
|
||||
"central_workshop": {
|
||||
"title": "Lantegi Zentrala + Salmenta Puntuak",
|
||||
"subtitle": "Ekoizpen zentralizatua, banaketa anitza",
|
||||
"description": "Zentral ekoizten duzu eta hainbat salmenta puntura banatzen duzu. Ekoizpena, logistika eta eskaria kokagune artean koordinatu behar dituzu puntu bakoitza optimizatzeko.",
|
||||
"features": {
|
||||
"prediction": "<strong>Agregatu eta salmenta puntu bakoitzeko</strong> bereizitako aurreikuspena",
|
||||
"distribution": "<strong>Banaketa kudeaketa</strong> koordiatutako hainbat kokaleku",
|
||||
"visibility": "<strong>Ikusgarritasun zentralizatua</strong> kontrol zehatzekin"
|
||||
}
|
||||
},
|
||||
"same_ai": "AA indartsu bera, zure lan moduari egokitua"
|
||||
},
|
||||
"pricing": {
|
||||
"title": "Zure Negozioari Egokitzen Zaizkion Planak",
|
||||
"subtitle": "Ezkutuko kosturik gabe, konpromiso luzerik gabe. Hasi doan eta handitu zure hazkundea",
|
||||
"compare_link": "Ikusi ezaugarrien konparazio osoa"
|
||||
},
|
||||
"final_cta": {
|
||||
"scarcity_badge": "12 leku geratzen dira pilotu programako 20tik",
|
||||
"title": "Izan Lehenengo 20 Okindegien Artean",
|
||||
|
||||
@@ -68,6 +68,51 @@
|
||||
"supported_formats": "Onartutako formatuak: CSV",
|
||||
"max_size": "Gehienezko tamaina: 10MB"
|
||||
},
|
||||
"file_format_guide": {
|
||||
"title": "Fitxategi Formatuaren Gida",
|
||||
"supported_formats": {
|
||||
"title": "Onartutako Formatuak",
|
||||
"csv": "CSV (komaz bereizitako balioak)",
|
||||
"json": "JSON (JavaScript Object Notation)",
|
||||
"excel": "Excel (XLSX, XLS)",
|
||||
"max_size": "Gehienezko tamaina: 10MB"
|
||||
},
|
||||
"required_columns": {
|
||||
"title": "Beharrezko Zutabeak",
|
||||
"date": "Data",
|
||||
"date_examples": "date, fecha, data",
|
||||
"product": "Produktuaren Izena",
|
||||
"product_examples": "product, producto, product_name, izena",
|
||||
"quantity": "Saldutako Kantitatea",
|
||||
"quantity_examples": "quantity, cantidad, quantity_sold, unitateak"
|
||||
},
|
||||
"optional_columns": {
|
||||
"title": "Aukerako Zutabeak",
|
||||
"revenue": "Sarrerak (revenue, ingresos, sales)",
|
||||
"unit_price": "Unitatearen Prezioa (unit_price, precio, price)",
|
||||
"category": "Kategoria (category, kategoria)",
|
||||
"sku": "Produktuaren SKU",
|
||||
"location": "Kokapena/Denda",
|
||||
"notes": "Ohar gehigarriak"
|
||||
},
|
||||
"date_formats": {
|
||||
"title": "Onartutako Data Formatuak",
|
||||
"formats": "YYYY-MM-DD, DD/MM/YYYY, MM/DD/YYYY, DD-MM-YYYY, eta gehiago",
|
||||
"with_time": "Ordu formatuak ere onartzen dira (adib: YYYY-MM-DD HH:MM:SS)"
|
||||
},
|
||||
"features": {
|
||||
"title": "Ezaugarri Automatikoak",
|
||||
"multilingual": "Hizkuntza anitzeko zutabeen detekzioa (Gaztelania, Ingelesa, Euskara)",
|
||||
"validation": "Balidazio automatikoa errore-txosten zehatzekin",
|
||||
"ai_classification": "AA bidezko produktuen sailkapena",
|
||||
"inventory_suggestions": "Inbentario iradokizun adimentsuak"
|
||||
},
|
||||
"download_template": "Txantiloia Jaitsi",
|
||||
"show_example": "Datu Adibidea Erakutsi",
|
||||
"hide_example": "Adibidea Ezkutatu",
|
||||
"toggle_guide": "Gida Osoa Ikusi",
|
||||
"collapse_guide": "Gida Ezkutatu"
|
||||
},
|
||||
"sample": {
|
||||
"download": "CSV txantiloia jaitsi",
|
||||
"example": "Datuen adibidea ikusi"
|
||||
|
||||
@@ -8,144 +8,7 @@ import { PageHeader } from '../../../../components/layout';
|
||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||
import { Equipment } from '../../../../api/types/equipment';
|
||||
import { EquipmentModal } from '../../../../components/domain/equipment/EquipmentModal';
|
||||
|
||||
const MOCK_EQUIPMENT: Equipment[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Horno Principal #1',
|
||||
type: 'oven',
|
||||
model: 'Miwe Condo CO 4.1212',
|
||||
serialNumber: 'MCO-2021-001',
|
||||
location: 'Área de Horneado - Zona A',
|
||||
status: 'operational',
|
||||
installDate: '2021-03-15',
|
||||
lastMaintenance: '2024-01-15',
|
||||
nextMaintenance: '2024-04-15',
|
||||
maintenanceInterval: 90,
|
||||
temperature: 220,
|
||||
targetTemperature: 220,
|
||||
efficiency: 92,
|
||||
uptime: 98.5,
|
||||
energyUsage: 45.2,
|
||||
utilizationToday: 87,
|
||||
alerts: [],
|
||||
maintenanceHistory: [
|
||||
{
|
||||
id: '1',
|
||||
date: '2024-01-15',
|
||||
type: 'preventive',
|
||||
description: 'Limpieza general y calibración de termostatos',
|
||||
technician: 'Juan Pérez',
|
||||
cost: 150,
|
||||
downtime: 2,
|
||||
partsUsed: ['Filtros de aire', 'Sellos de puerta']
|
||||
}
|
||||
],
|
||||
specifications: {
|
||||
power: 45,
|
||||
capacity: 24,
|
||||
dimensions: { width: 200, height: 180, depth: 120 },
|
||||
weight: 850
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Batidora Industrial #2',
|
||||
type: 'mixer',
|
||||
model: 'Hobart HL800',
|
||||
serialNumber: 'HHL-2020-002',
|
||||
location: 'Área de Preparación - Zona B',
|
||||
status: 'warning',
|
||||
installDate: '2020-08-10',
|
||||
lastMaintenance: '2024-01-20',
|
||||
nextMaintenance: '2024-02-20',
|
||||
maintenanceInterval: 30,
|
||||
efficiency: 88,
|
||||
uptime: 94.2,
|
||||
energyUsage: 12.8,
|
||||
utilizationToday: 76,
|
||||
alerts: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'warning',
|
||||
message: 'Vibración inusual detectada en el motor',
|
||||
timestamp: '2024-01-23T10:30:00Z',
|
||||
acknowledged: false
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'info',
|
||||
message: 'Mantenimiento programado en 5 días',
|
||||
timestamp: '2024-01-23T08:00:00Z',
|
||||
acknowledged: true
|
||||
}
|
||||
],
|
||||
maintenanceHistory: [
|
||||
{
|
||||
id: '1',
|
||||
date: '2024-01-20',
|
||||
type: 'corrective',
|
||||
description: 'Reemplazo de correas de transmisión',
|
||||
technician: 'María González',
|
||||
cost: 85,
|
||||
downtime: 4,
|
||||
partsUsed: ['Correa tipo V', 'Rodamientos']
|
||||
}
|
||||
],
|
||||
specifications: {
|
||||
power: 15,
|
||||
capacity: 80,
|
||||
dimensions: { width: 120, height: 150, depth: 80 },
|
||||
weight: 320
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Cámara de Fermentación #1',
|
||||
type: 'proofer',
|
||||
model: 'Bongard EUROPA 16.18',
|
||||
serialNumber: 'BEU-2022-001',
|
||||
location: 'Área de Fermentación',
|
||||
status: 'maintenance',
|
||||
installDate: '2022-06-20',
|
||||
lastMaintenance: '2024-01-23',
|
||||
nextMaintenance: '2024-01-24',
|
||||
maintenanceInterval: 60,
|
||||
temperature: 32,
|
||||
targetTemperature: 35,
|
||||
efficiency: 0,
|
||||
uptime: 85.1,
|
||||
energyUsage: 0,
|
||||
utilizationToday: 0,
|
||||
alerts: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'info',
|
||||
message: 'En mantenimiento programado',
|
||||
timestamp: '2024-01-23T06:00:00Z',
|
||||
acknowledged: true
|
||||
}
|
||||
],
|
||||
maintenanceHistory: [
|
||||
{
|
||||
id: '1',
|
||||
date: '2024-01-23',
|
||||
type: 'preventive',
|
||||
description: 'Mantenimiento programado - sistema de humidificación',
|
||||
technician: 'Carlos Rodríguez',
|
||||
cost: 200,
|
||||
downtime: 8,
|
||||
partsUsed: ['Sensor de humedad', 'Válvulas']
|
||||
}
|
||||
],
|
||||
specifications: {
|
||||
power: 8,
|
||||
capacity: 16,
|
||||
dimensions: { width: 180, height: 200, depth: 100 },
|
||||
weight: 450
|
||||
}
|
||||
}
|
||||
];
|
||||
import { useEquipment, useCreateEquipment, useUpdateEquipment } from '../../../../api/hooks/equipment';
|
||||
|
||||
const MaquinariaPage: React.FC = () => {
|
||||
const { t } = useTranslation(['equipment', 'common']);
|
||||
@@ -157,11 +20,19 @@ const MaquinariaPage: React.FC = () => {
|
||||
const [showEquipmentModal, setShowEquipmentModal] = useState(false);
|
||||
const [equipmentModalMode, setEquipmentModalMode] = useState<'view' | 'edit' | 'create'>('create');
|
||||
const [selectedEquipment, setSelectedEquipment] = useState<Equipment | null>(null);
|
||||
|
||||
|
||||
const currentTenant = useCurrentTenant();
|
||||
const tenantId = currentTenant?.id || '';
|
||||
|
||||
// Mock functions for equipment actions - these would be replaced with actual API calls
|
||||
// Fetch equipment data from API
|
||||
const { data: equipment = [], isLoading, error } = useEquipment(tenantId, {
|
||||
is_active: true
|
||||
});
|
||||
|
||||
// Mutations for create and update
|
||||
const createEquipmentMutation = useCreateEquipment(tenantId);
|
||||
const updateEquipmentMutation = useUpdateEquipment(tenantId);
|
||||
|
||||
const handleCreateEquipment = () => {
|
||||
setSelectedEquipment({
|
||||
id: '',
|
||||
@@ -193,8 +64,8 @@ const MaquinariaPage: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleEditEquipment = (equipmentId: string) => {
|
||||
// Find the equipment to edit
|
||||
const equipmentToEdit = MOCK_EQUIPMENT.find(eq => eq.id === equipmentId);
|
||||
// Find the equipment to edit from real data
|
||||
const equipmentToEdit = equipment.find(eq => eq.id === equipmentId);
|
||||
if (equipmentToEdit) {
|
||||
setSelectedEquipment(equipmentToEdit);
|
||||
setEquipmentModalMode('edit');
|
||||
@@ -217,16 +88,26 @@ const MaquinariaPage: React.FC = () => {
|
||||
// Implementation would go here
|
||||
};
|
||||
|
||||
const handleSaveEquipment = (equipment: Equipment) => {
|
||||
console.log('Saving equipment:', equipment);
|
||||
// In a real implementation, you would save to the API
|
||||
// For now, just close the modal
|
||||
setShowEquipmentModal(false);
|
||||
// Refresh equipment list if needed
|
||||
const handleSaveEquipment = async (equipmentData: Equipment) => {
|
||||
try {
|
||||
if (equipmentModalMode === 'create') {
|
||||
await createEquipmentMutation.mutateAsync(equipmentData);
|
||||
} else if (equipmentModalMode === 'edit' && equipmentData.id) {
|
||||
await updateEquipmentMutation.mutateAsync({
|
||||
equipmentId: equipmentData.id,
|
||||
equipmentData: equipmentData
|
||||
});
|
||||
}
|
||||
setShowEquipmentModal(false);
|
||||
setSelectedEquipment(null);
|
||||
} catch (error) {
|
||||
console.error('Error saving equipment:', error);
|
||||
// Error is already handled by mutation with toast
|
||||
}
|
||||
};
|
||||
|
||||
const filteredEquipment = useMemo(() => {
|
||||
return MOCK_EQUIPMENT.filter(eq => {
|
||||
return equipment.filter(eq => {
|
||||
const matchesSearch = !searchTerm ||
|
||||
eq.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
eq.location.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
@@ -237,15 +118,15 @@ const MaquinariaPage: React.FC = () => {
|
||||
|
||||
return matchesSearch && matchesStatus && matchesType;
|
||||
});
|
||||
}, [MOCK_EQUIPMENT, searchTerm, statusFilter, typeFilter]);
|
||||
}, [equipment, searchTerm, statusFilter, typeFilter]);
|
||||
|
||||
const equipmentStats = useMemo(() => {
|
||||
const total = MOCK_EQUIPMENT.length;
|
||||
const operational = MOCK_EQUIPMENT.filter(e => e.status === 'operational').length;
|
||||
const warning = MOCK_EQUIPMENT.filter(e => e.status === 'warning').length;
|
||||
const maintenance = MOCK_EQUIPMENT.filter(e => e.status === 'maintenance').length;
|
||||
const down = MOCK_EQUIPMENT.filter(e => e.status === 'down').length;
|
||||
const totalAlerts = MOCK_EQUIPMENT.reduce((sum, e) => sum + e.alerts.filter(a => !a.acknowledged).length, 0);
|
||||
const total = equipment.length;
|
||||
const operational = equipment.filter(e => e.status === 'operational').length;
|
||||
const warning = equipment.filter(e => e.status === 'warning').length;
|
||||
const maintenance = equipment.filter(e => e.status === 'maintenance').length;
|
||||
const down = equipment.filter(e => e.status === 'down').length;
|
||||
const totalAlerts = equipment.reduce((sum, e) => sum + e.alerts.filter(a => !a.acknowledged).length, 0);
|
||||
|
||||
return {
|
||||
total,
|
||||
@@ -255,7 +136,7 @@ const MaquinariaPage: React.FC = () => {
|
||||
down,
|
||||
totalAlerts
|
||||
};
|
||||
}, [MOCK_EQUIPMENT]);
|
||||
}, [equipment]);
|
||||
|
||||
const getStatusConfig = (status: Equipment['status']) => {
|
||||
const configs = {
|
||||
@@ -320,6 +201,28 @@ const MaquinariaPage: React.FC = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-64">
|
||||
<LoadingSpinner text={t('common:loading')} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-64">
|
||||
<AlertTriangle className="w-12 h-12 text-red-500 mb-4" />
|
||||
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-2">
|
||||
{t('common:errors.load_error')}
|
||||
</h3>
|
||||
<p className="text-[var(--text-secondary)]">
|
||||
{t('common:errors.try_again')}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useAuthUser } from '../../../../stores/auth.store';
|
||||
import { useCurrentTenant } from '../../../../stores';
|
||||
import { useToast } from '../../../../hooks/ui/useToast';
|
||||
import { subscriptionService, type UsageSummary, type AvailablePlans } from '../../../../api';
|
||||
import { SubscriptionPricingCards } from '../../../../components/subscription/SubscriptionPricingCards';
|
||||
|
||||
const SubscriptionPage: React.FC = () => {
|
||||
const user = useAuthUser();
|
||||
@@ -576,144 +577,18 @@ const SubscriptionPage: React.FC = () => {
|
||||
</Card>
|
||||
|
||||
{/* Available Plans */}
|
||||
<Card className="p-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-6 text-[var(--text-primary)] flex items-center">
|
||||
<Crown className="w-5 h-5 mr-2 text-yellow-500" />
|
||||
Planes Disponibles
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{Object.entries(availablePlans.plans).map(([planKey, plan]) => {
|
||||
const isCurrentPlan = usageSummary.plan === planKey;
|
||||
const getPlanColor = () => {
|
||||
switch (planKey) {
|
||||
case 'starter': return 'border-blue-500/30 bg-blue-500/5';
|
||||
case 'professional': return 'border-purple-500/30 bg-purple-500/5';
|
||||
case 'enterprise': return 'border-amber-500/30 bg-amber-500/5';
|
||||
default: return 'border-[var(--border-primary)] bg-[var(--bg-secondary)]';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
key={planKey}
|
||||
className={`relative p-6 ${getPlanColor()} ${
|
||||
isCurrentPlan ? 'ring-2 ring-[var(--color-primary)]' : ''
|
||||
}`}
|
||||
>
|
||||
{plan.popular && (
|
||||
<div className="absolute -top-3 left-1/2 transform -translate-x-1/2">
|
||||
<Badge variant="primary" className="px-3 py-1">
|
||||
<Star className="w-3 h-3 mr-1" />
|
||||
Más Popular
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="text-center mb-6">
|
||||
<h4 className="text-xl font-bold text-[var(--text-primary)] mb-2">{plan.name}</h4>
|
||||
<div className="text-3xl font-bold text-[var(--color-primary)] mb-1">
|
||||
{subscriptionService.formatPrice(plan.monthly_price)}
|
||||
<span className="text-lg text-[var(--text-secondary)]">/mes</span>
|
||||
</div>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{plan.description}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 mb-6">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<Users className="w-4 h-4 text-[var(--color-primary)]" />
|
||||
<span>{plan.max_users === -1 ? 'Usuarios ilimitados' : `${plan.max_users} usuarios`}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<MapPin className="w-4 h-4 text-[var(--color-primary)]" />
|
||||
<span>{plan.max_locations === -1 ? 'Ubicaciones ilimitadas' : `${plan.max_locations} ubicación${plan.max_locations > 1 ? 'es' : ''}`}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<Package className="w-4 h-4 text-[var(--color-primary)]" />
|
||||
<span>{plan.max_products === -1 ? 'Productos ilimitados' : `${plan.max_products} productos`}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Features Section */}
|
||||
<div className="border-t border-[var(--border-color)] pt-4 mb-6">
|
||||
<h5 className="text-sm font-semibold text-[var(--text-primary)] mb-3 flex items-center">
|
||||
<TrendingUp className="w-4 h-4 mr-2 text-[var(--color-primary)]" />
|
||||
Funcionalidades Incluidas
|
||||
</h5>
|
||||
<div className="space-y-2">
|
||||
{(() => {
|
||||
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',
|
||||
'✗ Analytics Avanzados',
|
||||
'✗ Pronósticos IA',
|
||||
'✗ Insights Predictivos'
|
||||
];
|
||||
case 'professional':
|
||||
return [
|
||||
'✓ Panel de Control Avanzado',
|
||||
'✓ Gestión de Inventario Completa',
|
||||
'✓ Analytics de Ventas',
|
||||
'✓ Pronósticos con IA (92% precisión)',
|
||||
'✓ Análisis de Rendimiento',
|
||||
'✓ Optimización de Producción',
|
||||
'✓ Integración POS',
|
||||
'✗ Insights Predictivos Avanzados'
|
||||
];
|
||||
case 'enterprise':
|
||||
return [
|
||||
'✓ Todas las funcionalidades Professional',
|
||||
'✓ Insights Predictivos con IA',
|
||||
'✓ Analytics Multi-ubicación',
|
||||
'✓ Integración ERP',
|
||||
'✓ API Personalizada',
|
||||
'✓ Gestor de Cuenta Dedicado',
|
||||
'✓ Soporte 24/7 Prioritario',
|
||||
'✓ Demo Personalizada'
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
return getPlanFeatures(planKey).map((feature, index) => (
|
||||
<div key={index} className={`text-xs flex items-center gap-2 ${
|
||||
feature.startsWith('✓')
|
||||
? 'text-green-600'
|
||||
: 'text-[var(--text-secondary)] opacity-60'
|
||||
}`}>
|
||||
<span>{feature}</span>
|
||||
</div>
|
||||
));
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isCurrentPlan ? (
|
||||
<Badge variant="success" className="w-full justify-center py-2">
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
Plan Actual
|
||||
</Badge>
|
||||
) : (
|
||||
<Button
|
||||
variant={plan.popular ? 'primary' : 'outline'}
|
||||
className="w-full"
|
||||
onClick={() => handleUpgradeClick(planKey)}
|
||||
>
|
||||
{plan.contact_sales ? 'Contactar Ventas' : 'Cambiar Plan'}
|
||||
<ArrowRight className="w-4 h-4 ml-2" />
|
||||
</Button>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
<SubscriptionPricingCards
|
||||
mode="selection"
|
||||
selectedPlan={usageSummary.plan}
|
||||
onPlanSelect={handleUpgradeClick}
|
||||
showPilotBanner={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Invoices Section */}
|
||||
<Card className="p-6">
|
||||
|
||||
@@ -42,24 +42,26 @@ const AboutPage: React.FC = () => {
|
||||
},
|
||||
];
|
||||
|
||||
const team = [
|
||||
const founderHighlights = [
|
||||
{
|
||||
name: 'Urtzi Alfaro',
|
||||
role: 'CEO & Co-fundador',
|
||||
bio: '10+ años en IA y Machine Learning. Ex-ingeniero en Google. Apasionado por aplicar tecnología a problemas reales del sector alimentario.',
|
||||
image: null,
|
||||
icon: Brain,
|
||||
title: 'Experiencia Internacional',
|
||||
description: 'Más de una década liderando proyectos globales de alta tecnología, desde startups innovadoras hasta corporaciones multinacionales en Reino Unido, Europa, Asia y América.',
|
||||
},
|
||||
{
|
||||
name: 'María González',
|
||||
role: 'CTO & Co-fundadora',
|
||||
bio: 'Experta en sistemas de gestión para hostelería. 8 años liderando equipos de desarrollo. Background en panaderías familiares.',
|
||||
image: null,
|
||||
icon: Award,
|
||||
title: 'Formación de Élite',
|
||||
description: 'Ingeniero en Telecomunicaciones (Mondragon University, 2013) con año de intercambio en École Polytechnique Fédérale de Lausanne (EPFL), Suiza.',
|
||||
},
|
||||
{
|
||||
name: 'Carlos Ruiz',
|
||||
role: 'Product Lead',
|
||||
bio: '15 años como maestro panadero. Conoce los retos del oficio de primera mano. Ahora diseña software que realmente ayuda.',
|
||||
image: null,
|
||||
icon: TrendingUp,
|
||||
title: 'Especialización en IA & Innovación',
|
||||
description: 'Experto en IA/ML, transformación digital, desarrollo de productos ágiles y diseño de modelos de negocio para grandes empresas y startups.',
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: 'Visión Global',
|
||||
description: 'Políglota (euskera, español, inglés, francés, chino) con pasión por fusionar creatividad humana y tecnología de vanguardia para crear soluciones de valor real.',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -179,30 +181,56 @@ const AboutPage: React.FC = () => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Team */}
|
||||
{/* Founder */}
|
||||
<section className="py-20 bg-[var(--bg-primary)]">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)] mb-4">
|
||||
Nuestro Equipo
|
||||
El Fundador
|
||||
</h2>
|
||||
<p className="text-xl text-[var(--text-secondary)] max-w-3xl mx-auto">
|
||||
Combinamos experiencia en IA, desarrollo de software y panadería artesanal
|
||||
Un emprendedor en solitario con una visión clara: democratizar la tecnología de IA para panaderías de todos los tamaños
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{team.map((member, index) => (
|
||||
{/* Founder Profile Card */}
|
||||
<div className="max-w-4xl mx-auto mb-12">
|
||||
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 rounded-2xl p-8 border-2 border-blue-200 dark:border-blue-800">
|
||||
<div className="flex flex-col md:flex-row gap-8 items-center md:items-start">
|
||||
<div className="w-32 h-32 bg-gradient-to-r from-[var(--color-primary)] to-orange-600 rounded-full flex items-center justify-center text-white text-4xl font-bold flex-shrink-0">
|
||||
UA
|
||||
</div>
|
||||
<div className="flex-1 text-center md:text-left">
|
||||
<h3 className="text-3xl font-bold text-[var(--text-primary)] mb-2">Urtzi Alfaro</h3>
|
||||
<p className="text-xl text-[var(--color-primary)] font-medium mb-4">Fundador & CEO</p>
|
||||
<p className="text-[var(--text-secondary)] leading-relaxed mb-4">
|
||||
Catalizador de transformación, arquitecto estratégico y visionario en tecnología avanzada.
|
||||
Con más de una década de experiencia internacional liderando proyectos de alta tecnología e innovación,
|
||||
mi misión es crear impacto sostenible en empresas y sociedad a escala global.
|
||||
</p>
|
||||
<p className="text-[var(--text-secondary)] leading-relaxed">
|
||||
Natural de Donostia-San Sebastián (País Vasco), he trabajado en Londres y Cambridge durante 7 años,
|
||||
liderando proyectos globales con clientes en EE.UU., Europa y China. Ahora desde Madrid,
|
||||
aplico mi experiencia en IA, transformación digital y desarrollo de productos para ayudar
|
||||
a las panaderías a prosperar en la era digital.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Founder Highlights */}
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{founderHighlights.map((highlight, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-[var(--bg-secondary)] rounded-2xl p-8 border border-[var(--border-primary)] text-center hover:shadow-xl transition-all duration-300"
|
||||
className="bg-[var(--bg-secondary)] rounded-2xl p-6 border border-[var(--border-primary)] hover:shadow-xl transition-all duration-300"
|
||||
>
|
||||
<div className="w-24 h-24 bg-gradient-to-r from-[var(--color-primary)] to-orange-600 rounded-full flex items-center justify-center mx-auto mb-6 text-white text-3xl font-bold">
|
||||
{member.name.split(' ').map(n => n[0]).join('')}
|
||||
<div className="w-12 h-12 bg-[var(--color-primary)]/10 rounded-xl flex items-center justify-center mb-4">
|
||||
<highlight.icon className="w-6 h-6 text-[var(--color-primary)]" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-2">{member.name}</h3>
|
||||
<p className="text-[var(--color-primary)] font-medium mb-4">{member.role}</p>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{member.bio}</p>
|
||||
<h3 className="text-lg font-bold text-[var(--text-primary)] mb-2">{highlight.title}</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{highlight.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -22,136 +22,48 @@ import {
|
||||
BarChart3
|
||||
} from 'lucide-react';
|
||||
|
||||
interface JobOpening {
|
||||
id: string;
|
||||
title: string;
|
||||
department: string;
|
||||
location: string;
|
||||
type: string;
|
||||
salary?: string;
|
||||
description: string;
|
||||
requirements: string[];
|
||||
niceToHave: string[];
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
}
|
||||
|
||||
const CareersPage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const benefits = [
|
||||
{
|
||||
icon: Laptop,
|
||||
title: 'Trabajo Remoto',
|
||||
description: '100% remoto o híbrido según prefieras. Tenemos oficina en Bilbao pero puedes trabajar desde donde quieras.',
|
||||
},
|
||||
{
|
||||
icon: Clock,
|
||||
title: 'Horario Flexible',
|
||||
description: 'Enfócate en resultados, no en horas. Organiza tu día como mejor funcione para ti.',
|
||||
},
|
||||
{
|
||||
icon: Euro,
|
||||
title: 'Salario Competitivo',
|
||||
description: 'Sueldos por encima de mercado + equity en la empresa para fundadores tempranos.',
|
||||
},
|
||||
{
|
||||
icon: TrendingUp,
|
||||
title: 'Crecimiento Real',
|
||||
description: 'Somos una startup en fase temprana. Aquí aprendes rápido y tu impacto se ve directamente.',
|
||||
},
|
||||
const visionPoints = [
|
||||
{
|
||||
icon: Heart,
|
||||
title: 'Propósito',
|
||||
description: 'Ayuda a negocios reales a prosperar. Tu trabajo tiene impacto tangible en familias.',
|
||||
title: 'Propósito Claro',
|
||||
description: 'Ayudar a panaderías de todos los tamaños a prosperar mediante tecnología de IA accesible y fácil de usar.',
|
||||
},
|
||||
{
|
||||
icon: Zap,
|
||||
title: 'Ejecución Ágil',
|
||||
description: 'Como emprendedor en solitario, puedo tomar decisiones rápidas y adaptarme a las necesidades reales de los clientes.',
|
||||
},
|
||||
{
|
||||
icon: Users,
|
||||
title: 'Equipo Pequeño',
|
||||
description: 'Sin burocracia, sin reuniones inútiles. Decisiones rápidas, ejecución directa.',
|
||||
title: 'Enfoque en el Cliente',
|
||||
description: 'Contacto directo con cada panadería piloto. Tu feedback moldea directamente el producto.',
|
||||
},
|
||||
{
|
||||
icon: TrendingUp,
|
||||
title: 'Visión a Largo Plazo',
|
||||
description: 'Construyendo una empresa sostenible que genere impacto real, no solo crecimiento rápido.',
|
||||
},
|
||||
];
|
||||
|
||||
const openPositions: JobOpening[] = [
|
||||
const futureRoles = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Full Stack Developer (React + Python)',
|
||||
department: 'Ingeniería',
|
||||
location: 'Remoto (España)',
|
||||
type: 'Tiempo completo',
|
||||
salary: '€45,000 - €65,000 + equity',
|
||||
description: 'Buscamos un desarrollador full-stack que nos ayude a construir la mejor plataforma de gestión para panaderías de todos los tamaños y modelos. Trabajarás directamente con los fundadores y tendrás ownership completo de features.',
|
||||
requirements: [
|
||||
'3+ años de experiencia con React y TypeScript',
|
||||
'2+ años con Python (FastAPI, Flask o Django)',
|
||||
'Experiencia con bases de datos (PostgreSQL)',
|
||||
'Git, CI/CD, testing',
|
||||
'Capacidad de trabajar autónomamente',
|
||||
],
|
||||
niceToHave: [
|
||||
'Experiencia con ML/IA',
|
||||
'Background en startups',
|
||||
'Conocimiento del sector F&B/hostelería',
|
||||
'Contribuciones open source',
|
||||
],
|
||||
icon: Code,
|
||||
title: 'Desarrollo de Software',
|
||||
description: 'Full-stack developers, ML engineers y especialistas en IA cuando lleguemos a la escala adecuada.',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'ML Engineer (Predicción de Demanda)',
|
||||
department: 'IA/ML',
|
||||
location: 'Remoto (España)',
|
||||
type: 'Tiempo completo',
|
||||
salary: '€50,000 - €70,000 + equity',
|
||||
description: 'Lidera el desarrollo de nuestros algoritmos de predicción. Trabajarás con datos reales de panaderías (locales y obradores centrales) para crear modelos que predicen demanda con >90% precisión, tanto a nivel individual como agregado.',
|
||||
requirements: [
|
||||
'MSc o PhD en CS, Matemáticas, o similar',
|
||||
'3+ años trabajando con ML en producción',
|
||||
'Experiencia con time series forecasting',
|
||||
'Python (scikit-learn, TensorFlow/PyTorch)',
|
||||
'SQL y manejo de grandes datasets',
|
||||
],
|
||||
niceToHave: [
|
||||
'Publicaciones en ML/IA',
|
||||
'Experiencia con MLOps',
|
||||
'Background en retail/forecasting/supply chain',
|
||||
'Kaggle competitions',
|
||||
],
|
||||
icon: BarChart3,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Product Designer (UI/UX)',
|
||||
department: 'Diseño',
|
||||
location: 'Remoto (España)',
|
||||
type: 'Freelance/Tiempo parcial',
|
||||
salary: '€30,000 - €45,000 (parcial)',
|
||||
description: 'Diseña interfaces que panaderos puedan usar incluso con las manos llenas de harina. Necesitamos UX/UI funcional, intuitivo y hermoso para usuarios no-técnicos.',
|
||||
requirements: [
|
||||
'3+ años diseñando productos digitales',
|
||||
'Portfolio con casos de estudio reales',
|
||||
'Experiencia con Figma',
|
||||
'Conocimiento de design systems',
|
||||
'User research y testing',
|
||||
],
|
||||
niceToHave: [
|
||||
'Experiencia en B2B/SaaS',
|
||||
'Conocimiento de front-end (HTML/CSS)',
|
||||
'Ilustración/motion design',
|
||||
'Background en F&B/hostelería',
|
||||
],
|
||||
icon: Palette,
|
||||
title: 'Diseño de Producto',
|
||||
description: 'Diseñadores UX/UI que entiendan las necesidades de negocios reales y usuarios no técnicos.',
|
||||
},
|
||||
{
|
||||
icon: BarChart3,
|
||||
title: 'Customer Success',
|
||||
description: 'Expertos que ayuden a las panaderías a sacar el máximo provecho de la plataforma.',
|
||||
},
|
||||
];
|
||||
|
||||
const cultureFacts = [
|
||||
'Somos un equipo de 5 personas (por ahora)',
|
||||
'Promedio de edad: 32 años',
|
||||
'Daily standups de 10 minutos máximo',
|
||||
'80% del equipo trabaja remoto',
|
||||
'Viernes terminamos a las 14:00',
|
||||
'Budget para cursos y conferencias',
|
||||
'Equipo multilingüe (ES/EN/EU)',
|
||||
'Sin dress code (incluso en videollamadas)',
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -171,214 +83,155 @@ const CareersPage: React.FC = () => {
|
||||
<div className="text-center max-w-4xl mx-auto">
|
||||
<div className="inline-flex items-center gap-2 bg-[var(--color-primary)]/10 text-[var(--color-primary)] px-4 py-2 rounded-full text-sm font-medium mb-6">
|
||||
<Briefcase className="w-4 h-4" />
|
||||
<span>Estamos Contratando</span>
|
||||
<span>Emprendimiento en Solitario</span>
|
||||
</div>
|
||||
<h1 className="text-4xl lg:text-6xl font-extrabold text-[var(--text-primary)] mb-6">
|
||||
Construye el Futuro de
|
||||
<span className="block text-[var(--color-primary)]">las Panaderías</span>
|
||||
Construyendo el Futuro
|
||||
<span className="block text-[var(--color-primary)]">Paso a Paso</span>
|
||||
</h1>
|
||||
<p className="text-xl text-[var(--text-secondary)] leading-relaxed mb-8">
|
||||
Únete a una startup en fase temprana que combina IA, sostenibilidad y pasión por ayudar a negocios reales de todos los tamaños.
|
||||
Somos pequeños, ágiles y con un propósito claro.
|
||||
Panadería IA es actualmente un proyecto en solitario, enfocado en crear la mejor herramienta
|
||||
de IA para panaderías mediante contacto directo con clientes y ejecución ágil. Cuando llegue
|
||||
el momento adecuado, construiré un equipo que comparta esta visión.
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-6 text-sm text-[var(--text-tertiary)]">
|
||||
<div className="flex items-center gap-2">
|
||||
<MapPin className="w-4 h-4" />
|
||||
<span>Remoto/Híbrido</span>
|
||||
<span>Madrid, España</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="w-4 h-4" />
|
||||
<span>Equipo de 5</span>
|
||||
<span>Emprendedor Solo</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Globe className="w-4 h-4" />
|
||||
<span>100% España</span>
|
||||
<span>Visión Global</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Benefits */}
|
||||
{/* Current State */}
|
||||
<section className="py-20 bg-[var(--bg-primary)]">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)] mb-4">
|
||||
¿Por Qué Trabajar Con Nosotros?
|
||||
El Enfoque Actual
|
||||
</h2>
|
||||
<p className="text-xl text-[var(--text-secondary)] max-w-3xl mx-auto">
|
||||
Beneficios reales, no promesas vacías
|
||||
Por qué un emprendedor en solitario puede ser la mejor opción en esta fase
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{benefits.map((benefit, index) => (
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{visionPoints.map((point, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-[var(--bg-secondary)] rounded-2xl p-6 border border-[var(--border-primary)] hover:shadow-xl transition-all duration-300"
|
||||
>
|
||||
<div className="w-12 h-12 bg-[var(--color-primary)]/10 rounded-xl flex items-center justify-center mb-4">
|
||||
<benefit.icon className="w-6 h-6 text-[var(--color-primary)]" />
|
||||
<point.icon className="w-6 h-6 text-[var(--color-primary)]" />
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-[var(--text-primary)] mb-2">{benefit.title}</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{benefit.description}</p>
|
||||
<h3 className="text-lg font-bold text-[var(--text-primary)] mb-2">{point.title}</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{point.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Open Positions */}
|
||||
{/* Future Vision */}
|
||||
<section className="py-20 bg-[var(--bg-secondary)]">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)] mb-4">
|
||||
Posiciones Abiertas
|
||||
El Futuro del Equipo
|
||||
</h2>
|
||||
<p className="text-xl text-[var(--text-secondary)]">
|
||||
{openPositions.length} vacantes disponibles
|
||||
<p className="text-xl text-[var(--text-secondary)] max-w-3xl mx-auto">
|
||||
Actualmente no estoy contratando, pero cuando llegue el momento adecuado (tras validar el producto con clientes reales
|
||||
y alcanzar product-market fit), buscaré talento excepcional en estas áreas
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{openPositions.map((job) => (
|
||||
<div
|
||||
key={job.id}
|
||||
className="bg-[var(--bg-primary)] rounded-2xl p-8 border-2 border-[var(--border-primary)] hover:border-[var(--color-primary)] transition-all duration-300"
|
||||
>
|
||||
<div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-6">
|
||||
{/* Left: Job Info */}
|
||||
<div className="flex-1">
|
||||
<div className="flex items-start gap-4 mb-4">
|
||||
<div className="w-12 h-12 bg-[var(--color-primary)]/10 rounded-xl flex items-center justify-center flex-shrink-0">
|
||||
<job.icon className="w-6 h-6 text-[var(--color-primary)]" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold text-[var(--text-primary)] mb-2">{job.title}</h3>
|
||||
<div className="flex flex-wrap items-center gap-4 text-sm text-[var(--text-secondary)]">
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<Briefcase className="w-4 h-4" />
|
||||
{job.department}
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<MapPin className="w-4 h-4" />
|
||||
{job.location}
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1">
|
||||
<Clock className="w-4 h-4" />
|
||||
{job.type}
|
||||
</span>
|
||||
{job.salary && (
|
||||
<span className="inline-flex items-center gap-1 text-[var(--color-primary)] font-medium">
|
||||
<Euro className="w-4 h-4" />
|
||||
{job.salary}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-[var(--text-secondary)] mb-6">{job.description}</p>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
{/* Requirements */}
|
||||
<div>
|
||||
<h4 className="font-bold text-[var(--text-primary)] mb-3">Requisitos:</h4>
|
||||
<ul className="space-y-2">
|
||||
{job.requirements.map((req, i) => (
|
||||
<li key={i} className="flex items-start gap-2 text-sm text-[var(--text-secondary)]">
|
||||
<Award className="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5" />
|
||||
<span>{req}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Nice to Have */}
|
||||
<div>
|
||||
<h4 className="font-bold text-[var(--text-primary)] mb-3">Valorable:</h4>
|
||||
<ul className="space-y-2">
|
||||
{job.niceToHave.map((item, i) => (
|
||||
<li key={i} className="flex items-start gap-2 text-sm text-[var(--text-secondary)]">
|
||||
<Zap className="w-4 h-4 text-[var(--color-primary)] flex-shrink-0 mt-0.5" />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Apply Button */}
|
||||
<div className="flex flex-col gap-3 lg:min-w-[200px]">
|
||||
<a
|
||||
href={`mailto:careers@panaderia-ia.com?subject=Aplicación: ${job.title}`}
|
||||
className="inline-flex items-center justify-center gap-2 px-6 py-3 bg-[var(--color-primary)] text-white rounded-xl font-bold hover:bg-[var(--color-primary-dark)] transition-all hover:scale-105"
|
||||
>
|
||||
<Mail className="w-5 h-5" />
|
||||
<span>Aplicar</span>
|
||||
</a>
|
||||
<Link
|
||||
to="/demo"
|
||||
className="inline-flex items-center justify-center gap-2 px-6 py-3 border-2 border-[var(--border-primary)] text-[var(--text-primary)] rounded-xl font-medium hover:border-[var(--color-primary)] transition-all text-center"
|
||||
>
|
||||
<span>Ver Producto</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Culture */}
|
||||
<section className="py-20 bg-[var(--bg-primary)]">
|
||||
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)] mb-4">
|
||||
Nuestra Cultura
|
||||
</h2>
|
||||
<p className="text-xl text-[var(--text-secondary)]">
|
||||
Datos reales, sin marketing
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-4">
|
||||
{cultureFacts.map((fact, index) => (
|
||||
<div className="grid md:grid-cols-3 gap-8 mb-12">
|
||||
{futureRoles.map((role, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center gap-3 bg-[var(--bg-secondary)] p-4 rounded-lg border border-[var(--border-primary)]"
|
||||
className="bg-[var(--bg-primary)] rounded-2xl p-6 border border-[var(--border-primary)] hover:shadow-xl transition-all duration-300"
|
||||
>
|
||||
<Coffee className="w-5 h-5 text-[var(--color-primary)] flex-shrink-0" />
|
||||
<span className="text-[var(--text-secondary)]">{fact}</span>
|
||||
<div className="w-12 h-12 bg-[var(--color-primary)]/10 rounded-xl flex items-center justify-center mb-4">
|
||||
<role.icon className="w-6 h-6 text-[var(--color-primary)]" />
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-[var(--text-primary)] mb-2">{role.title}</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{role.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl mx-auto bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 rounded-2xl p-8 border-2 border-blue-200 dark:border-blue-800">
|
||||
<h3 className="text-2xl font-bold text-[var(--text-primary)] mb-4 text-center">
|
||||
¿Por Qué Aún No Contrato?
|
||||
</h3>
|
||||
<div className="space-y-4 text-[var(--text-secondary)]">
|
||||
<p className="flex items-start gap-3">
|
||||
<Award className="w-5 h-5 text-[var(--color-primary)] flex-shrink-0 mt-0.5" />
|
||||
<span><strong>Validación primero:</strong> Necesito confirmar que el producto realmente resuelve problemas reales antes de escalar el equipo.</span>
|
||||
</p>
|
||||
<p className="flex items-start gap-3">
|
||||
<Award className="w-5 h-5 text-[var(--color-primary)] flex-shrink-0 mt-0.5" />
|
||||
<span><strong>Recursos limitados:</strong> Como emprendedor bootstrapped, cada euro cuenta. Prefiero invertir en producto y clientes ahora.</span>
|
||||
</p>
|
||||
<p className="flex items-start gap-3">
|
||||
<Award className="w-5 h-5 text-[var(--color-primary)] flex-shrink-0 mt-0.5" />
|
||||
<span><strong>Agilidad máxima:</strong> En esta fase, puedo pivotar rápidamente y experimentar sin la complejidad de coordinar un equipo.</span>
|
||||
</p>
|
||||
<p className="flex items-start gap-3">
|
||||
<Award className="w-5 h-5 text-[var(--color-primary)] flex-shrink-0 mt-0.5" />
|
||||
<span><strong>El equipo adecuado:</strong> Cuando contrate, buscaré personas que compartan la visión, no solo habilidades técnicas.</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* CTA */}
|
||||
{/* CTA - Join as Customer */}
|
||||
<section className="py-20 bg-gradient-to-r from-[var(--color-primary)] to-orange-600">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||
<h2 className="text-3xl lg:text-4xl font-bold text-white mb-6">
|
||||
¿No Ves Tu Posición Ideal?
|
||||
¿Quieres Ser Parte de Esta Historia?
|
||||
</h2>
|
||||
<p className="text-xl text-white/90 mb-8 leading-relaxed">
|
||||
Siempre estamos abiertos a conocer talento excepcional.
|
||||
Envíanos tu CV y cuéntanos por qué quieres unirte a Panadería IA.
|
||||
</p>
|
||||
<a
|
||||
href="mailto:careers@panaderia-ia.com?subject=Aplicación Espontánea"
|
||||
className="inline-flex items-center gap-2 px-8 py-4 bg-white text-[var(--color-primary)] rounded-xl font-bold hover:shadow-2xl transition-all hover:scale-105"
|
||||
>
|
||||
<Mail className="w-5 h-5" />
|
||||
<span>Enviar Aplicación Espontánea</span>
|
||||
<ArrowRight className="w-5 h-5" />
|
||||
</a>
|
||||
<p className="text-white/80 text-sm mt-6">
|
||||
careers@panaderia-ia.com
|
||||
Ahora mismo, la mejor forma de unirte es como cliente piloto. Ayúdame a construir
|
||||
la mejor herramienta de IA para panaderías, obtén 3 meses gratis y 20% de descuento de por vida.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center mb-8">
|
||||
<Link
|
||||
to="/register"
|
||||
className="inline-flex items-center justify-center gap-2 px-8 py-4 bg-white text-[var(--color-primary)] rounded-xl font-bold hover:shadow-2xl transition-all hover:scale-105"
|
||||
>
|
||||
<span>Únete al Programa Piloto</span>
|
||||
<ArrowRight className="w-5 h-5" />
|
||||
</Link>
|
||||
<Link
|
||||
to="/about"
|
||||
className="inline-flex items-center justify-center gap-2 px-8 py-4 border-2 border-white text-white rounded-xl font-bold hover:bg-white hover:text-[var(--color-primary)] transition-all"
|
||||
>
|
||||
<span>Conoce al Fundador</span>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="bg-white/10 backdrop-blur-sm rounded-xl p-6 max-w-2xl mx-auto">
|
||||
<p className="text-white/90 text-sm mb-4">
|
||||
<strong>¿Interesado en oportunidades futuras?</strong>
|
||||
</p>
|
||||
<p className="text-white/80 text-sm">
|
||||
Si te interesa formar parte del equipo cuando llegue el momento, puedes escribirme a{' '}
|
||||
<a href="mailto:urtzi@panaderia-ia.com" className="underline font-medium">
|
||||
urtzi@panaderia-ia.com
|
||||
</a>{' '}
|
||||
para mantenernos en contacto.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</PublicLayout>
|
||||
|
||||
@@ -67,7 +67,7 @@ const LandingPage: React.FC = () => {
|
||||
</span>
|
||||
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-500/10 text-green-600 dark:text-green-400">
|
||||
<Shield className="w-4 h-4 mr-2" />
|
||||
Reducción de Desperdicio Alimentario
|
||||
{t('landing:hero.badge_sustainability', 'Reducción de Desperdicio Alimentario')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -94,7 +94,7 @@ const LandingPage: React.FC = () => {
|
||||
<Star className="w-5 h-5 text-amber-400 fill-amber-400" />
|
||||
</div>
|
||||
<span className="text-xl font-extrabold bg-gradient-to-r from-amber-600 to-orange-600 dark:from-amber-400 dark:to-orange-400 bg-clip-text text-transparent">
|
||||
¡Lanzamiento Piloto!
|
||||
{t('landing:hero.pilot_banner.title', '¡Lanzamiento Piloto!')}
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<Star className="w-5 h-5 text-amber-400 fill-amber-400" />
|
||||
@@ -105,10 +105,10 @@ const LandingPage: React.FC = () => {
|
||||
<div className="text-center">
|
||||
<p className="text-base text-[var(--text-secondary)] font-medium">
|
||||
<span className="inline-block px-3 py-1 bg-gradient-to-r from-[var(--color-primary)] to-orange-600 text-white font-bold text-lg rounded-lg shadow-md mr-1">
|
||||
3 MESES GRATIS
|
||||
{t('landing:hero.pilot_banner.offer', '3 MESES GRATIS')}
|
||||
</span>
|
||||
<span className="block mt-2 text-sm">
|
||||
para los primeros en unirse al piloto
|
||||
{t('landing:hero.pilot_banner.description', 'para los primeros en unirse al piloto')}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
@@ -186,15 +186,12 @@ const LandingPage: React.FC = () => {
|
||||
<div className="text-center mb-8">
|
||||
<div className="inline-flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-full text-sm font-bold mb-4">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>Programa Piloto - Plazas Limitadas</span>
|
||||
<span>{t('landing:pilot.badge', 'Programa Piloto - Plazas Limitadas')}</span>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-[var(--text-primary)] mb-3">
|
||||
Buscamos 20 Panaderías Pioneras
|
||||
{t('landing:pilot.title', 'Buscamos 20 Panaderías Pioneras')}
|
||||
</h2>
|
||||
<p className="text-[var(--text-secondary)] max-w-2xl mx-auto">
|
||||
Estamos seleccionando las primeras 20 panaderías para formar parte de nuestro programa piloto exclusivo.
|
||||
A cambio de tu feedback, obtienes <strong>3 meses gratis + precio preferencial de por vida</strong>.
|
||||
</p>
|
||||
<p className="text-[var(--text-secondary)] max-w-2xl mx-auto" dangerouslySetInnerHTML={{ __html: t('landing:pilot.subtitle', 'Estamos seleccionando las primeras 20 panaderías para formar parte de nuestro programa piloto exclusivo. A cambio de tu feedback, obtienes <strong>3 meses gratis + precio preferencial de por vida</strong>.') }} />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
|
||||
@@ -202,24 +199,24 @@ const LandingPage: React.FC = () => {
|
||||
<div className="w-12 h-12 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<Award className="w-6 h-6 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<div className="text-lg font-bold text-[var(--text-primary)]">Founders Beta</div>
|
||||
<div className="text-sm text-[var(--text-secondary)] mt-2">Acceso de por vida con 20% descuento</div>
|
||||
<div className="text-lg font-bold text-[var(--text-primary)]">{t('landing:pilot.benefits.founders_beta.title', 'Founders Beta')}</div>
|
||||
<div className="text-sm text-[var(--text-secondary)] mt-2">{t('landing:pilot.benefits.founders_beta.description', 'Acceso de por vida con 20% descuento')}</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center p-6 bg-white dark:bg-gray-800 rounded-xl shadow-sm">
|
||||
<div className="w-12 h-12 bg-purple-100 dark:bg-purple-900/30 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<Users className="w-6 h-6 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<div className="text-lg font-bold text-[var(--text-primary)]">Influye el Producto</div>
|
||||
<div className="text-sm text-[var(--text-secondary)] mt-2">Tus necesidades moldean la plataforma</div>
|
||||
<div className="text-lg font-bold text-[var(--text-primary)]">{t('landing:pilot.benefits.influence_product.title', 'Influye el Producto')}</div>
|
||||
<div className="text-sm text-[var(--text-secondary)] mt-2">{t('landing:pilot.benefits.influence_product.description', 'Tus necesidades moldean la plataforma')}</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center p-6 bg-white dark:bg-gray-800 rounded-xl shadow-sm">
|
||||
<div className="w-12 h-12 bg-amber-100 dark:bg-amber-900/30 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<Zap className="w-6 h-6 text-amber-600 dark:text-amber-400" />
|
||||
</div>
|
||||
<div className="text-lg font-bold text-[var(--text-primary)]">Soporte Premium</div>
|
||||
<div className="text-sm text-[var(--text-secondary)] mt-2">Atención directa del equipo fundador</div>
|
||||
<div className="text-lg font-bold text-[var(--text-primary)]">{t('landing:pilot.benefits.premium_support.title', 'Soporte Premium')}</div>
|
||||
<div className="text-sm text-[var(--text-secondary)] mt-2">{t('landing:pilot.benefits.premium_support.description', 'Atención directa del equipo fundador')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -231,11 +228,10 @@ const LandingPage: React.FC = () => {
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-12">
|
||||
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)] mb-4">
|
||||
Tu Modelo de Negocio, Nuestra Tecnología
|
||||
{t('landing:business_models.title', 'Tu Modelo de Negocio, Nuestra Tecnología')}
|
||||
</h2>
|
||||
<p className="text-xl text-[var(--text-secondary)] max-w-3xl mx-auto">
|
||||
Ya sea que produzcas y vendas en un solo lugar, o gestiones un obrador central con múltiples puntos de venta,
|
||||
nuestra IA se adapta a tu forma de trabajar
|
||||
{t('landing:business_models.subtitle', 'Ya sea que produzcas y vendas en un solo lugar, o gestiones un obrador central con múltiples puntos de venta, nuestra IA se adapta a tu forma de trabajar')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -247,32 +243,25 @@ const LandingPage: React.FC = () => {
|
||||
<Store className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold text-[var(--text-primary)]">Producción Local</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)]">Un punto de venta y producción</p>
|
||||
<h3 className="text-2xl font-bold text-[var(--text-primary)]">{t('landing:business_models.local_production.title', 'Producción Local')}</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{t('landing:business_models.local_production.subtitle', 'Un punto de venta y producción')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[var(--text-secondary)] mb-6 leading-relaxed">
|
||||
Tu panadería produce y vende en el mismo lugar. Necesitas optimizar producción diaria,
|
||||
minimizar desperdicios y maximizar frescura en cada horneada.
|
||||
{t('landing:business_models.local_production.description', 'Tu panadería produce y vende en el mismo lugar. Necesitas optimizar producción diaria, minimizar desperdicios y maximizar frescura en cada horneada.')}
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<Check className="w-5 h-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-sm text-[var(--text-secondary)]">
|
||||
<strong>Predicción de demanda</strong> por ubicación única
|
||||
</span>
|
||||
<span className="text-sm text-[var(--text-secondary)]" dangerouslySetInnerHTML={{ __html: t('landing:business_models.local_production.features.prediction', '<strong>Predicción de demanda</strong> por ubicación única') }} />
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<Check className="w-5 h-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-sm text-[var(--text-secondary)]">
|
||||
<strong>Gestión de inventario</strong> simplificada y directa
|
||||
</span>
|
||||
<span className="text-sm text-[var(--text-secondary)]" dangerouslySetInnerHTML={{ __html: t('landing:business_models.local_production.features.inventory', '<strong>Gestión de inventario</strong> simplificada y directa') }} />
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<Check className="w-5 h-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-sm text-[var(--text-secondary)]">
|
||||
<strong>Un solo punto de control</strong> - simple y eficiente
|
||||
</span>
|
||||
<span className="text-sm text-[var(--text-secondary)]" dangerouslySetInnerHTML={{ __html: t('landing:business_models.local_production.features.control', '<strong>Un solo punto de control</strong> - simple y eficiente') }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -284,32 +273,25 @@ const LandingPage: React.FC = () => {
|
||||
<Network className="w-8 h-8 text-white" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold text-[var(--text-primary)]">Obrador Central + Puntos de Venta</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)]">Producción centralizada, distribución múltiple</p>
|
||||
<h3 className="text-2xl font-bold text-[var(--text-primary)]">{t('landing:business_models.central_workshop.title', 'Obrador Central + Puntos de Venta')}</h3>
|
||||
<p className="text-sm text-[var(--text-secondary)]">{t('landing:business_models.central_workshop.subtitle', 'Producción centralizada, distribución múltiple')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[var(--text-secondary)] mb-6 leading-relaxed">
|
||||
Produces centralmente y distribuyes a múltiples puntos de venta. Necesitas coordinar producción,
|
||||
logística y demanda entre ubicaciones para optimizar cada punto.
|
||||
{t('landing:business_models.central_workshop.description', 'Produces centralmente y distribuyes a múltiples puntos de venta. Necesitas coordinar producción, logística y demanda entre ubicaciones para optimizar cada punto.')}
|
||||
</p>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<Check className="w-5 h-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-sm text-[var(--text-secondary)]">
|
||||
<strong>Predicción agregada y por punto de venta</strong> individual
|
||||
</span>
|
||||
<span className="text-sm text-[var(--text-secondary)]" dangerouslySetInnerHTML={{ __html: t('landing:business_models.central_workshop.features.prediction', '<strong>Predicción agregada y por punto de venta</strong> individual') }} />
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<Check className="w-5 h-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-sm text-[var(--text-secondary)]">
|
||||
<strong>Gestión de distribución</strong> multi-ubicación coordinada
|
||||
</span>
|
||||
<span className="text-sm text-[var(--text-secondary)]" dangerouslySetInnerHTML={{ __html: t('landing:business_models.central_workshop.features.distribution', '<strong>Gestión de distribución</strong> multi-ubicación coordinada') }} />
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<Check className="w-5 h-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5" />
|
||||
<span className="text-sm text-[var(--text-secondary)]">
|
||||
<strong>Visibilidad centralizada</strong> con control granular
|
||||
</span>
|
||||
<span className="text-sm text-[var(--text-secondary)]" dangerouslySetInnerHTML={{ __html: t('landing:business_models.central_workshop.features.visibility', '<strong>Visibilidad centralizada</strong> con control granular') }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -318,7 +300,7 @@ const LandingPage: React.FC = () => {
|
||||
<div className="mt-12 text-center">
|
||||
<div className="inline-flex items-center gap-2 bg-[var(--color-primary)]/10 text-[var(--color-primary)] px-6 py-3 rounded-full">
|
||||
<Brain className="w-5 h-5" />
|
||||
<span className="font-medium">La misma IA potente, adaptada a tu forma de trabajar</span>
|
||||
<span className="font-medium">{t('landing:business_models.same_ai', 'La misma IA potente, adaptada a tu forma de trabajar')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -331,15 +313,15 @@ const LandingPage: React.FC = () => {
|
||||
<div className="mb-4">
|
||||
<span className="inline-flex items-center px-4 py-2 rounded-full text-sm font-medium bg-gradient-to-r from-blue-500/10 to-purple-500/10 text-blue-600 dark:text-blue-400 border border-blue-500/20">
|
||||
<Brain className="w-4 h-4 mr-2" />
|
||||
Tecnología de IA de Última Generación
|
||||
{t('landing:features.badge', 'Tecnología de IA de Última Generación')}
|
||||
</span>
|
||||
</div>
|
||||
<h2 className="text-3xl lg:text-5xl font-extrabold text-[var(--text-primary)]">
|
||||
Combate el Desperdicio Alimentario
|
||||
<span className="block text-[var(--color-primary)]">con Inteligencia Artificial</span>
|
||||
{t('landing:features.title_main', 'Combate el Desperdicio Alimentario')}
|
||||
<span className="block text-[var(--color-primary)]">{t('landing:features.title_accent', 'con Inteligencia Artificial')}</span>
|
||||
</h2>
|
||||
<p className="mt-6 max-w-3xl mx-auto text-lg text-[var(--text-secondary)]">
|
||||
Sistema de alta tecnología que utiliza algoritmos de IA avanzados para optimizar tu producción, reducir residuos alimentarios y mantener tus datos 100% seguros y bajo tu control.
|
||||
{t('landing:features.subtitle', 'Sistema de alta tecnología que utiliza algoritmos de IA avanzados para optimizar tu producción, reducir residuos alimentarios y mantener tus datos 100% seguros y bajo tu control.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -352,28 +334,28 @@ const LandingPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)]">IA Avanzada de Predicción</h3>
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)]">{t('landing:features.ai_prediction.title', 'IA Avanzada de Predicción')}</h3>
|
||||
<p className="mt-4 text-[var(--text-secondary)]">
|
||||
Algoritmos de Inteligencia Artificial de última generación analizan patrones históricos, clima, eventos y tendencias para predecir demanda con precisión quirúrgica.
|
||||
{t('landing:features.ai_prediction.description', 'Algoritmos de Inteligencia Artificial de última generación analizan patrones históricos, clima, eventos y tendencias para predecir demanda con precisión quirúrgica.')}
|
||||
</p>
|
||||
<div className="mt-6 space-y-3">
|
||||
<div className="flex items-center text-sm">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-500/10 rounded-full flex items-center justify-center mr-3">
|
||||
<Zap className="w-3 h-3 text-blue-600" />
|
||||
</div>
|
||||
<span className="text-[var(--text-secondary)]">Precisión del 92% en predicciones</span>
|
||||
<span className="text-[var(--text-secondary)]">{t('landing:features.ai_prediction.features.accuracy', 'Precisión del 92% en predicciones')}</span>
|
||||
</div>
|
||||
<div className="flex items-center text-sm">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-500/10 rounded-full flex items-center justify-center mr-3">
|
||||
<TrendingUp className="w-3 h-3 text-blue-600" />
|
||||
</div>
|
||||
<span className="text-[var(--text-secondary)]">Aprendizaje continuo y adaptativo</span>
|
||||
<span className="text-[var(--text-secondary)]">{t('landing:features.ai_prediction.features.learning', 'Aprendizaje continuo y adaptativo')}</span>
|
||||
</div>
|
||||
<div className="flex items-center text-sm">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-500/10 rounded-full flex items-center justify-center mr-3">
|
||||
<BarChart3 className="w-3 h-3 text-blue-600" />
|
||||
</div>
|
||||
<span className="text-[var(--text-secondary)]">Análisis predictivo en tiempo real</span>
|
||||
<span className="text-[var(--text-secondary)]">{t('landing:features.ai_prediction.features.realtime', 'Análisis predictivo en tiempo real')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -387,28 +369,28 @@ const LandingPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)]">Reducción de Desperdicio</h3>
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)]">{t('landing:features.waste_reduction.title', 'Reducción de Desperdicio')}</h3>
|
||||
<p className="mt-4 text-[var(--text-secondary)]">
|
||||
Contribuye al medioambiente y reduce costos eliminando hasta un 35% del desperdicio alimentario mediante producción optimizada e inteligente.
|
||||
{t('landing:features.waste_reduction.description', 'Contribuye al medioambiente y reduce costos eliminando hasta un 35% del desperdicio alimentario mediante producción optimizada e inteligente.')}
|
||||
</p>
|
||||
<div className="mt-6 space-y-3">
|
||||
<div className="flex items-center text-sm">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-green-500/10 rounded-full flex items-center justify-center mr-3">
|
||||
<Check className="w-3 h-3 text-green-600" />
|
||||
</div>
|
||||
<span className="text-[var(--text-secondary)]">Hasta 35% menos desperdicio</span>
|
||||
<span className="text-[var(--text-secondary)]">{t('landing:features.waste_reduction.features.reduction', 'Hasta 35% menos desperdicio')}</span>
|
||||
</div>
|
||||
<div className="flex items-center text-sm">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-green-500/10 rounded-full flex items-center justify-center mr-3">
|
||||
<Euro className="w-3 h-3 text-green-600" />
|
||||
</div>
|
||||
<span className="text-[var(--text-secondary)]">Ahorro promedio de €800/mes</span>
|
||||
<span className="text-[var(--text-secondary)]">{t('landing:features.waste_reduction.features.savings', 'Ahorro promedio de €800/mes')}</span>
|
||||
</div>
|
||||
<div className="flex items-center text-sm">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-green-500/10 rounded-full flex items-center justify-center mr-3">
|
||||
<Award className="w-3 h-3 text-green-600" />
|
||||
</div>
|
||||
<span className="text-[var(--text-secondary)]">Elegible para ayudas UE</span>
|
||||
<span className="text-[var(--text-secondary)]">{t('landing:features.waste_reduction.features.eligible', 'Elegible para ayudas UE')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -422,28 +404,28 @@ const LandingPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)]">Tus Datos, Tu Propiedad</h3>
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)]">{t('landing:features.data_ownership.title', 'Tus Datos, Tu Propiedad')}</h3>
|
||||
<p className="mt-4 text-[var(--text-secondary)]">
|
||||
Privacidad y seguridad total. Tus datos operativos, proveedores y analíticas permanecen 100% bajo tu control. Nunca compartidos, nunca vendidos.
|
||||
{t('landing:features.data_ownership.description', 'Privacidad y seguridad total. Tus datos operativos, proveedores y analíticas permanecen 100% bajo tu control. Nunca compartidos, nunca vendidos.')}
|
||||
</p>
|
||||
<div className="mt-6 space-y-3">
|
||||
<div className="flex items-center text-sm">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-amber-500/10 rounded-full flex items-center justify-center mr-3">
|
||||
<Shield className="w-3 h-3 text-amber-600" />
|
||||
</div>
|
||||
<span className="text-[var(--text-secondary)]">100% propiedad de datos</span>
|
||||
<span className="text-[var(--text-secondary)]">{t('landing:features.data_ownership.features.ownership', '100% propiedad de datos')}</span>
|
||||
</div>
|
||||
<div className="flex items-center text-sm">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-amber-500/10 rounded-full flex items-center justify-center mr-3">
|
||||
<Settings className="w-3 h-3 text-amber-600" />
|
||||
</div>
|
||||
<span className="text-[var(--text-secondary)]">Control total de privacidad</span>
|
||||
<span className="text-[var(--text-secondary)]">{t('landing:features.data_ownership.features.privacy', 'Control total de privacidad')}</span>
|
||||
</div>
|
||||
<div className="flex items-center text-sm">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-amber-500/10 rounded-full flex items-center justify-center mr-3">
|
||||
<Award className="w-3 h-3 text-amber-600" />
|
||||
</div>
|
||||
<span className="text-[var(--text-secondary)]">Cumplimiento GDPR garantizado</span>
|
||||
<span className="text-[var(--text-secondary)]">{t('landing:features.data_ownership.features.gdpr', 'Cumplimiento GDPR garantizado')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -457,22 +439,22 @@ const LandingPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)]">Inventario Inteligente</h3>
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)]">{t('landing:features.smart_inventory.title', 'Inventario Inteligente')}</h3>
|
||||
<p className="mt-4 text-[var(--text-secondary)]">
|
||||
Control automático de stock con alertas predictivas, órdenes de compra automatizadas y optimización de costos.
|
||||
{t('landing:features.smart_inventory.description', 'Control automático de stock con alertas predictivas, órdenes de compra automatizadas y optimización de costos.')}
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<div className="flex items-center text-sm text-[var(--color-secondary)]">
|
||||
<Check className="w-4 h-4 mr-2" />
|
||||
Alertas automáticas de stock bajo
|
||||
{t('landing:features.smart_inventory.features.alerts', 'Alertas automáticas de stock bajo')}
|
||||
</div>
|
||||
<div className="flex items-center text-sm text-[var(--color-secondary)] mt-2">
|
||||
<Check className="w-4 h-4 mr-2" />
|
||||
Órdenes de compra automatizadas
|
||||
{t('landing:features.smart_inventory.features.orders', 'Órdenes de compra automatizadas')}
|
||||
</div>
|
||||
<div className="flex items-center text-sm text-[var(--color-secondary)] mt-2">
|
||||
<Check className="w-4 h-4 mr-2" />
|
||||
Optimización de costos de materias primas
|
||||
{t('landing:features.smart_inventory.features.optimization', 'Optimización de costos de materias primas')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -486,22 +468,22 @@ const LandingPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)]">Planificación de Producción</h3>
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)]">{t('landing:features.production_planning.title', 'Planificación de Producción')}</h3>
|
||||
<p className="mt-4 text-[var(--text-secondary)]">
|
||||
Programa automáticamente la producción diaria basada en predicciones, optimiza horarios y recursos disponibles.
|
||||
{t('landing:features.production_planning.description', 'Programa automáticamente la producción diaria basada en predicciones, optimiza horarios y recursos disponibles.')}
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<div className="flex items-center text-sm text-[var(--color-accent)]">
|
||||
<Check className="w-4 h-4 mr-2" />
|
||||
Programación automática de horneado
|
||||
{t('landing:features.production_planning.features.scheduling', 'Programación automática de horneado')}
|
||||
</div>
|
||||
<div className="flex items-center text-sm text-[var(--color-accent)] mt-2">
|
||||
<Check className="w-4 h-4 mr-2" />
|
||||
Optimización de uso de hornos
|
||||
{t('landing:features.production_planning.features.oven', 'Optimización de uso de hornos')}
|
||||
</div>
|
||||
<div className="flex items-center text-sm text-[var(--color-accent)] mt-2">
|
||||
<Check className="w-4 h-4 mr-2" />
|
||||
Gestión de personal y turnos
|
||||
{t('landing:features.production_planning.features.staff', 'Gestión de personal y turnos')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -514,32 +496,32 @@ const LandingPage: React.FC = () => {
|
||||
<div className="w-12 h-12 bg-[var(--color-primary)]/10 rounded-lg flex items-center justify-center mx-auto mb-4">
|
||||
<BarChart3 className="w-6 h-6 text-[var(--color-primary)]" />
|
||||
</div>
|
||||
<h4 className="font-semibold text-[var(--text-primary)]">Analytics Avanzado</h4>
|
||||
<p className="text-sm text-[var(--text-secondary)] mt-2">Dashboards en tiempo real con métricas clave</p>
|
||||
<h4 className="font-semibold text-[var(--text-primary)]">{t('landing:features.advanced_analytics.title', 'Analytics Avanzado')}</h4>
|
||||
<p className="text-sm text-[var(--text-secondary)] mt-2">{t('landing:features.advanced_analytics.description', 'Dashboards en tiempo real con métricas clave')}</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center p-6 bg-[var(--bg-primary)] rounded-xl border border-[var(--border-primary)]">
|
||||
<div className="w-12 h-12 bg-[var(--color-secondary)]/10 rounded-lg flex items-center justify-center mx-auto mb-4">
|
||||
<Euro className="w-6 h-6 text-[var(--color-secondary)]" />
|
||||
</div>
|
||||
<h4 className="font-semibold text-[var(--text-primary)]">POS Integrado</h4>
|
||||
<p className="text-sm text-[var(--text-secondary)] mt-2">Sistema de ventas completo y fácil de usar</p>
|
||||
<h4 className="font-semibold text-[var(--text-primary)]">{t('landing:features.pos_integration.title', 'POS Integrado')}</h4>
|
||||
<p className="text-sm text-[var(--text-secondary)] mt-2">{t('landing:features.pos_integration.description', 'Sistema de ventas completo y fácil de usar')}</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center p-6 bg-[var(--bg-primary)] rounded-xl border border-[var(--border-primary)]">
|
||||
<div className="w-12 h-12 bg-[var(--color-accent)]/10 rounded-lg flex items-center justify-center mx-auto mb-4">
|
||||
<Shield className="w-6 h-6 text-[var(--color-accent)]" />
|
||||
</div>
|
||||
<h4 className="font-semibold text-[var(--text-primary)]">Control de Calidad</h4>
|
||||
<p className="text-sm text-[var(--text-secondary)] mt-2">Trazabilidad completa y gestión HACCP</p>
|
||||
<h4 className="font-semibold text-[var(--text-primary)]">{t('landing:features.quality_control.title', 'Control de Calidad')}</h4>
|
||||
<p className="text-sm text-[var(--text-secondary)] mt-2">{t('landing:features.quality_control.description', 'Trazabilidad completa y gestión HACCP')}</p>
|
||||
</div>
|
||||
|
||||
<div className="text-center p-6 bg-[var(--bg-primary)] rounded-xl border border-[var(--border-primary)]">
|
||||
<div className="w-12 h-12 bg-[var(--color-info)]/10 rounded-lg flex items-center justify-center mx-auto mb-4">
|
||||
<Settings className="w-6 h-6 text-[var(--color-info)]" />
|
||||
</div>
|
||||
<h4 className="font-semibold text-[var(--text-primary)]">Automatización</h4>
|
||||
<p className="text-sm text-[var(--text-secondary)] mt-2">Procesos automáticos que ahorran tiempo</p>
|
||||
<h4 className="font-semibold text-[var(--text-primary)]">{t('landing:features.automation.title', 'Automatización')}</h4>
|
||||
<p className="text-sm text-[var(--text-secondary)] mt-2">{t('landing:features.automation.description', 'Procesos automáticos que ahorran tiempo')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -550,12 +532,11 @@ const LandingPage: React.FC = () => {
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)]">
|
||||
El Problema Que Resolvemos
|
||||
<span className="block text-[var(--color-primary)]">Para Panaderías</span>
|
||||
{t('landing:benefits.title', 'El Problema Que Resolvemos')}
|
||||
<span className="block text-[var(--color-primary)]">{t('landing:benefits.title_accent', 'Para Panaderías')}</span>
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-[var(--text-secondary)] max-w-3xl mx-auto">
|
||||
Sabemos lo frustrante que es tirar pan al final del día, o quedarte sin producto cuando llegan clientes.
|
||||
La producción artesanal es difícil de optimizar... hasta ahora.
|
||||
{t('landing:benefits.subtitle', 'Sabemos lo frustrante que es tirar pan al final del día, o quedarte sin producto cuando llegan clientes. La producción artesanal es difícil de optimizar... hasta ahora.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -568,9 +549,9 @@ const LandingPage: React.FC = () => {
|
||||
<span className="text-white font-bold text-xl">✗</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-bold text-red-700 dark:text-red-400 mb-2">Desperdicias entre 15-40% de producción</h4>
|
||||
<h4 className="text-lg font-bold text-red-700 dark:text-red-400 mb-2">{t('landing:benefits.problems.waste.title', 'Desperdicias entre 15-40% de producción')}</h4>
|
||||
<p className="text-[var(--text-secondary)] text-sm">
|
||||
Al final del día tiras producto que nadie compró. Son cientos de euros a la basura cada semana.
|
||||
{t('landing:benefits.problems.waste.description', 'Al final del día tiras producto que nadie compró. Son cientos de euros a la basura cada semana.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -582,9 +563,9 @@ const LandingPage: React.FC = () => {
|
||||
<span className="text-white font-bold text-xl">✗</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-bold text-red-700 dark:text-red-400 mb-2">Pierdes ventas por falta de stock</h4>
|
||||
<h4 className="text-lg font-bold text-red-700 dark:text-red-400 mb-2">{t('landing:benefits.problems.stockouts.title', 'Pierdes ventas por falta de stock')}</h4>
|
||||
<p className="text-[var(--text-secondary)] text-sm">
|
||||
Clientes que vienen por su pan favorito y se van sin comprar porque ya se te acabó a las 14:00.
|
||||
{t('landing:benefits.problems.stockouts.description', 'Clientes que vienen por su pan favorito y se van sin comprar porque ya se te acabó a las 14:00.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -596,9 +577,9 @@ const LandingPage: React.FC = () => {
|
||||
<span className="text-white font-bold text-xl">✗</span>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-bold text-red-700 dark:text-red-400 mb-2">Excel, papel y "experiencia"</h4>
|
||||
<h4 className="text-lg font-bold text-red-700 dark:text-red-400 mb-2">{t('landing:benefits.problems.manual.title', 'Excel, papel y "experiencia"')}</h4>
|
||||
<p className="text-[var(--text-secondary)] text-sm">
|
||||
Planificas basándote en intuición. Funciona... hasta que no funciona.
|
||||
{t('landing:benefits.problems.manual.description', 'Planificas basándote en intuición. Funciona... hasta que no funciona.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -613,9 +594,9 @@ const LandingPage: React.FC = () => {
|
||||
<Check className="text-white w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-bold text-green-700 dark:text-green-400 mb-2">Produce exactamente lo que vas a vender</h4>
|
||||
<h4 className="text-lg font-bold text-green-700 dark:text-green-400 mb-2">{t('landing:benefits.solutions.exact_production.title', 'Produce exactamente lo que vas a vender')}</h4>
|
||||
<p className="text-[var(--text-secondary)] text-sm">
|
||||
La IA analiza tus ventas históricas, clima, eventos locales y festivos para predecir demanda real.
|
||||
{t('landing:benefits.solutions.exact_production.description', 'La IA analiza tus ventas históricas, clima, eventos locales y festivos para predecir demanda real.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -627,9 +608,9 @@ const LandingPage: React.FC = () => {
|
||||
<Check className="text-white w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-bold text-green-700 dark:text-green-400 mb-2">Siempre tienes stock de lo que más se vende</h4>
|
||||
<h4 className="text-lg font-bold text-green-700 dark:text-green-400 mb-2">{t('landing:benefits.solutions.stock_availability.title', 'Siempre tienes stock de lo que más se vende')}</h4>
|
||||
<p className="text-[var(--text-secondary)] text-sm">
|
||||
El sistema te avisa qué productos van a tener más demanda cada día, para que nunca te quedes sin.
|
||||
{t('landing:benefits.solutions.stock_availability.description', 'El sistema te avisa qué productos van a tener más demanda cada día, para que nunca te quedes sin.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -641,9 +622,9 @@ const LandingPage: React.FC = () => {
|
||||
<Check className="text-white w-6 h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-lg font-bold text-green-700 dark:text-green-400 mb-2">Automatización inteligente + datos reales</h4>
|
||||
<h4 className="text-lg font-bold text-green-700 dark:text-green-400 mb-2">{t('landing:benefits.solutions.smart_automation.title', 'Automatización inteligente + datos reales')}</h4>
|
||||
<p className="text-[var(--text-secondary)] text-sm">
|
||||
Desde planificación de producción hasta gestión de inventario. Todo basado en matemáticas, no corazonadas.
|
||||
{t('landing:benefits.solutions.smart_automation.description', 'Desde planificación de producción hasta gestión de inventario. Todo basado en matemáticas, no corazonadas.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -655,24 +636,21 @@ const LandingPage: React.FC = () => {
|
||||
<div className="mt-16 bg-gradient-to-r from-[var(--color-primary)]/10 to-orange-500/10 rounded-2xl p-8 border-2 border-[var(--color-primary)]/30">
|
||||
<div className="text-center">
|
||||
<h3 className="text-2xl font-bold text-[var(--text-primary)] mb-4">
|
||||
El Objetivo: Que Ahorres Dinero Desde el Primer Mes
|
||||
{t('landing:benefits.value_proposition.title', 'El Objetivo: Que Ahorres Dinero Desde el Primer Mes')}
|
||||
</h3>
|
||||
<p className="text-[var(--text-secondary)] max-w-3xl mx-auto mb-6">
|
||||
No prometemos números mágicos porque cada panadería es diferente. Lo que SÍ prometemos es que si después de 3 meses
|
||||
no has reducido desperdicios o mejorado tus márgenes, <strong>te ayudamos gratis a optimizar tu negocio de otra forma</strong>.
|
||||
</p>
|
||||
<p className="text-[var(--text-secondary)] max-w-3xl mx-auto mb-6" dangerouslySetInnerHTML={{ __html: t('landing:benefits.value_proposition.description', 'No prometemos números mágicos porque cada panadería es diferente. Lo que SÍ prometemos es que si después de 3 meses no has reducido desperdicios o mejorado tus márgenes, <strong>te ayudamos gratis a optimizar tu negocio de otra forma</strong>.') }} />
|
||||
<div className="flex flex-wrap justify-center gap-6 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<TrendingUp className="w-5 h-5 text-[var(--color-success)]" />
|
||||
<span className="text-[var(--text-secondary)]">Menos desperdicio = más beneficio</span>
|
||||
<span className="text-[var(--text-secondary)]">{t('landing:benefits.value_proposition.points.waste', 'Menos desperdicio = más beneficio')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className="w-5 h-5 text-blue-600" />
|
||||
<span className="text-[var(--text-secondary)]">Menos tiempo en Excel, más en tu negocio</span>
|
||||
<span className="text-[var(--text-secondary)]">{t('landing:benefits.value_proposition.points.time', 'Menos tiempo en Excel, más en tu negocio')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield className="w-5 h-5 text-purple-600" />
|
||||
<span className="text-[var(--text-secondary)]">Tus datos siempre son tuyos</span>
|
||||
<span className="text-[var(--text-secondary)]">{t('landing:benefits.value_proposition.points.data', 'Tus datos siempre son tuyos')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -685,10 +663,10 @@ const LandingPage: React.FC = () => {
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center mb-16">
|
||||
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)]">
|
||||
Sin Riesgo. Sin Ataduras.
|
||||
{t('landing:risk_reversal.title', 'Sin Riesgo. Sin Ataduras.')}
|
||||
</h2>
|
||||
<p className="mt-4 max-w-2xl mx-auto text-lg text-[var(--text-secondary)]">
|
||||
Somos transparentes: esto es un piloto. Estamos construyendo la mejor herramienta para panaderías, y necesitamos tu ayuda.
|
||||
{t('landing:risk_reversal.subtitle', 'Somos transparentes: esto es un piloto. Estamos construyendo la mejor herramienta para panaderías, y necesitamos tu ayuda.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -699,28 +677,28 @@ const LandingPage: React.FC = () => {
|
||||
<div className="w-10 h-10 bg-green-600 rounded-full flex items-center justify-center">
|
||||
<Check className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
Lo Que Obtienes
|
||||
{t('landing:risk_reversal.what_you_get.title', 'Lo Que Obtienes')}
|
||||
</h3>
|
||||
<ul className="space-y-4">
|
||||
<li className="flex items-start gap-3">
|
||||
<Check className="w-5 h-5 text-green-600 dark:text-green-400 mt-1 flex-shrink-0" />
|
||||
<span className="text-[var(--text-secondary)]"><strong>3 meses completamente gratis</strong> para probar todas las funcionalidades</span>
|
||||
<span className="text-[var(--text-secondary)]" dangerouslySetInnerHTML={{ __html: t('landing:risk_reversal.what_you_get.free_trial', '<strong>3 meses completamente gratis</strong> para probar todas las funcionalidades') }} />
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<Check className="w-5 h-5 text-green-600 dark:text-green-400 mt-1 flex-shrink-0" />
|
||||
<span className="text-[var(--text-secondary)]"><strong>20% de descuento de por vida</strong> si decides continuar después del piloto</span>
|
||||
<span className="text-[var(--text-secondary)]" dangerouslySetInnerHTML={{ __html: t('landing:risk_reversal.what_you_get.lifetime_discount', '<strong>20% de descuento de por vida</strong> si decides continuar después del piloto') }} />
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<Check className="w-5 h-5 text-green-600 dark:text-green-400 mt-1 flex-shrink-0" />
|
||||
<span className="text-[var(--text-secondary)]"><strong>Soporte directo del equipo fundador</strong> - respondemos en horas, no días</span>
|
||||
<span className="text-[var(--text-secondary)]" dangerouslySetInnerHTML={{ __html: t('landing:risk_reversal.what_you_get.founder_support', '<strong>Soporte directo del equipo fundador</strong> - respondemos en horas, no días') }} />
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<Check className="w-5 h-5 text-green-600 dark:text-green-400 mt-1 flex-shrink-0" />
|
||||
<span className="text-[var(--text-secondary)]"><strong>Tus ideas se implementan primero</strong> - construimos lo que realmente necesitas</span>
|
||||
<span className="text-[var(--text-secondary)]" dangerouslySetInnerHTML={{ __html: t('landing:risk_reversal.what_you_get.priority_features', '<strong>Tus ideas se implementan primero</strong> - construimos lo que realmente necesitas') }} />
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<Check className="w-5 h-5 text-green-600 dark:text-green-400 mt-1 flex-shrink-0" />
|
||||
<span className="text-[var(--text-secondary)]"><strong>Cancelas cuando quieras</strong> sin explicaciones ni penalizaciones</span>
|
||||
<span className="text-[var(--text-secondary)]" dangerouslySetInnerHTML={{ __html: t('landing:risk_reversal.what_you_get.cancel_anytime', '<strong>Cancelas cuando quieras</strong> sin explicaciones ni penalizaciones') }} />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -731,31 +709,29 @@ const LandingPage: React.FC = () => {
|
||||
<div className="w-10 h-10 bg-blue-600 rounded-full flex items-center justify-center">
|
||||
<Users className="w-6 h-6 text-white" />
|
||||
</div>
|
||||
Lo Que Pedimos
|
||||
{t('landing:risk_reversal.what_we_ask.title', 'Lo Que Pedimos')}
|
||||
</h3>
|
||||
<ul className="space-y-4">
|
||||
<li className="flex items-start gap-3">
|
||||
<ArrowRight className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-1 flex-shrink-0" />
|
||||
<span className="text-[var(--text-secondary)]"><strong>Feedback honesto semanal</strong> (15 min) sobre qué funciona y qué no</span>
|
||||
<span className="text-[var(--text-secondary)]" dangerouslySetInnerHTML={{ __html: t('landing:risk_reversal.what_we_ask.feedback', '<strong>Feedback honesto semanal</strong> (15 min) sobre qué funciona y qué no') }} />
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<ArrowRight className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-1 flex-shrink-0" />
|
||||
<span className="text-[var(--text-secondary)]"><strong>Paciencia con bugs</strong> - estamos en fase beta, habrá imperfecciones</span>
|
||||
<span className="text-[var(--text-secondary)]" dangerouslySetInnerHTML={{ __html: t('landing:risk_reversal.what_we_ask.patience', '<strong>Paciencia con bugs</strong> - estamos en fase beta, habrá imperfecciones') }} />
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<ArrowRight className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-1 flex-shrink-0" />
|
||||
<span className="text-[var(--text-secondary)]"><strong>Datos de ventas históricos</strong> (opcional) para mejorar las predicciones</span>
|
||||
<span className="text-[var(--text-secondary)]" dangerouslySetInnerHTML={{ __html: t('landing:risk_reversal.what_we_ask.data', '<strong>Datos de ventas históricos</strong> (opcional) para mejorar las predicciones') }} />
|
||||
</li>
|
||||
<li className="flex items-start gap-3">
|
||||
<ArrowRight className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-1 flex-shrink-0" />
|
||||
<span className="text-[var(--text-secondary)]"><strong>Comunicación abierta</strong> - queremos saber si algo no te gusta</span>
|
||||
<span className="text-[var(--text-secondary)]" dangerouslySetInnerHTML={{ __html: t('landing:risk_reversal.what_we_ask.communication', '<strong>Comunicación abierta</strong> - queremos saber si algo no te gusta') }} />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div className="mt-6 p-4 bg-white dark:bg-gray-800 rounded-lg border border-blue-200 dark:border-blue-800">
|
||||
<p className="text-sm text-[var(--text-secondary)] italic">
|
||||
<strong>Promesa:</strong> Si después de 3 meses sientes que no te ayudamos a ahorrar dinero o reducir desperdicios, te damos una sesión gratuita de consultoría para optimizar tu panadería de otra forma.
|
||||
</p>
|
||||
<p className="text-sm text-[var(--text-secondary)] italic" dangerouslySetInnerHTML={{ __html: t('landing:risk_reversal.promise', '<strong>Promesa:</strong> Si después de 3 meses sientes que no te ayudamos a ahorrar dinero o reducir desperdicios, te damos una sesión gratuita de consultoría para optimizar tu panadería de otra forma.') }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -764,10 +740,10 @@ const LandingPage: React.FC = () => {
|
||||
<div className="bg-[var(--bg-primary)] rounded-2xl p-8 shadow-lg border border-[var(--border-primary)]">
|
||||
<div className="text-center mb-8">
|
||||
<h3 className="text-xl font-bold text-[var(--text-primary)] mb-3">
|
||||
¿Por Qué Confiar en Nosotros?
|
||||
{t('landing:risk_reversal.credibility.title', '¿Por Qué Confiar en Nosotros?')}
|
||||
</h3>
|
||||
<p className="text-[var(--text-secondary)]">
|
||||
Entendemos que probar nueva tecnología es un riesgo. Por eso somos completamente transparentes:
|
||||
{t('landing:risk_reversal.credibility.subtitle', 'Entendemos que probar nueva tecnología es un riesgo. Por eso somos completamente transparentes:')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -776,9 +752,9 @@ const LandingPage: React.FC = () => {
|
||||
<div className="w-16 h-16 bg-purple-100 dark:bg-purple-900/30 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Shield className="w-8 h-8 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<h4 className="font-semibold text-[var(--text-primary)] mb-2">100% Española</h4>
|
||||
<h4 className="font-semibold text-[var(--text-primary)] mb-2">{t('landing:risk_reversal.credibility.spanish.title', '100% Española')}</h4>
|
||||
<p className="text-sm text-[var(--text-secondary)]">
|
||||
Empresa registrada en España. Tus datos están protegidos por RGPD y nunca salen de la UE.
|
||||
{t('landing:risk_reversal.credibility.spanish.description', 'Empresa registrada en España. Tus datos están protegidos por RGPD y nunca salen de la UE.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -786,9 +762,9 @@ const LandingPage: React.FC = () => {
|
||||
<div className="w-16 h-16 bg-orange-100 dark:bg-orange-900/30 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Brain className="w-8 h-8 text-orange-600 dark:text-orange-400" />
|
||||
</div>
|
||||
<h4 className="font-semibold text-[var(--text-primary)] mb-2">Tecnología Probada</h4>
|
||||
<h4 className="font-semibold text-[var(--text-primary)] mb-2">{t('landing:risk_reversal.credibility.technology.title', 'Tecnología Probada')}</h4>
|
||||
<p className="text-sm text-[var(--text-secondary)]">
|
||||
Usamos algoritmos de IA validados académicamente, adaptados específicamente para panaderías.
|
||||
{t('landing:risk_reversal.credibility.technology.description', 'Usamos algoritmos de IA validados académicamente, adaptados específicamente para panaderías.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -796,9 +772,9 @@ const LandingPage: React.FC = () => {
|
||||
<div className="w-16 h-16 bg-teal-100 dark:bg-teal-900/30 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Award className="w-8 h-8 text-teal-600 dark:text-teal-400" />
|
||||
</div>
|
||||
<h4 className="font-semibold text-[var(--text-primary)] mb-2">Equipo Experto</h4>
|
||||
<h4 className="font-semibold text-[var(--text-primary)] mb-2">{t('landing:risk_reversal.credibility.team.title', 'Equipo Experto')}</h4>
|
||||
<p className="text-sm text-[var(--text-secondary)]">
|
||||
Fundadores con experiencia en proyectos de alto valor tecnológico + proyectos internacionales.
|
||||
{t('landing:risk_reversal.credibility.team.description', 'Fundadores con experiencia en proyectos de alto valor tecnológico + proyectos internacionales.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -816,62 +792,56 @@ const LandingPage: React.FC = () => {
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="text-center">
|
||||
<h2 className="text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)]">
|
||||
Preguntas Frecuentes
|
||||
{t('landing:faq.title', 'Preguntas Frecuentes')}
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-[var(--text-secondary)]">
|
||||
Todo lo que necesitas saber sobre Panadería IA
|
||||
{t('landing:faq.subtitle', 'Todo lo que necesitas saber sobre Panadería IA')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-16 space-y-8">
|
||||
<div className="bg-[var(--bg-primary)] rounded-xl p-8 border border-[var(--border-primary)]">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)]">
|
||||
¿Qué tan precisa es la predicción de demanda?
|
||||
{t('landing:faq.questions.accuracy.q', '¿Qué tan precisa es la predicción de demanda?')}
|
||||
</h3>
|
||||
<p className="mt-4 text-[var(--text-secondary)]">
|
||||
Nuestra IA alcanza una precisión del 92% en predicciones de demanda, analizando más de 50 variables incluyendo
|
||||
histórico de ventas, clima, eventos locales, estacionalidad y tendencias de mercado. La precisión mejora continuamente
|
||||
con más datos de tu panadería.
|
||||
{t('landing:faq.questions.accuracy.a', 'Nuestra IA alcanza una precisión del 92% en predicciones de demanda, analizando más de 50 variables incluyendo histórico de ventas, clima, eventos locales, estacionalidad y tendencias de mercado. La precisión mejora continuamente con más datos de tu panadería.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-[var(--bg-primary)] rounded-xl p-8 border border-[var(--border-primary)]">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)]">
|
||||
¿Cuánto tiempo toma implementar el sistema?
|
||||
{t('landing:faq.questions.implementation.q', '¿Cuánto tiempo toma implementar el sistema?')}
|
||||
</h3>
|
||||
<p className="mt-4 text-[var(--text-secondary)]">
|
||||
La configuración inicial toma solo 5 minutos. Nuestro equipo te ayuda a migrar tus datos históricos en 24-48 horas.
|
||||
La IA comienza a generar predicciones útiles después de una semana de datos, alcanzando máxima precisión en 30 días.
|
||||
{t('landing:faq.questions.implementation.a', 'La configuración inicial toma solo 5 minutos. Nuestro equipo te ayuda a migrar tus datos históricos en 24-48 horas. La IA comienza a generar predicciones útiles después de una semana de datos, alcanzando máxima precisión en 30 días.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-[var(--bg-primary)] rounded-xl p-8 border border-[var(--border-primary)]">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)]">
|
||||
¿Se integra con mi sistema POS actual?
|
||||
{t('landing:faq.questions.integration.q', '¿Se integra con mi sistema POS actual?')}
|
||||
</h3>
|
||||
<p className="mt-4 text-[var(--text-secondary)]">
|
||||
Sí, nos integramos con más de 50 sistemas POS populares en España. También incluimos nuestro propio POS optimizado
|
||||
para panaderías. Si usas un sistema específico, nuestro equipo técnico puede crear una integración personalizada.
|
||||
{t('landing:faq.questions.integration.a', 'Sí, nos integramos con más de 50 sistemas POS populares en España. También incluimos nuestro propio POS optimizado para panaderías. Si usas un sistema específico, nuestro equipo técnico puede crear una integración personalizada.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-[var(--bg-primary)] rounded-xl p-8 border border-[var(--border-primary)]">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)]">
|
||||
¿Qué soporte técnico ofrecen?
|
||||
{t('landing:faq.questions.support.q', '¿Qué soporte técnico ofrecen?')}
|
||||
</h3>
|
||||
<p className="mt-4 text-[var(--text-secondary)]">
|
||||
Ofrecemos soporte 24/7 en español por chat, email y teléfono. Todos nuestros técnicos son expertos en operaciones
|
||||
de panadería. Además, incluimos onboarding personalizado y training para tu equipo sin costo adicional.
|
||||
{t('landing:faq.questions.support.a', 'Ofrecemos soporte 24/7 en español por chat, email y teléfono. Todos nuestros técnicos son expertos en operaciones de panadería. Además, incluimos onboarding personalizado y training para tu equipo sin costo adicional.')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-[var(--bg-primary)] rounded-xl p-8 border border-[var(--border-primary)]">
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)]">
|
||||
¿Mis datos están seguros?
|
||||
{t('landing:faq.questions.security.q', '¿Mis datos están seguros?')}
|
||||
</h3>
|
||||
<p className="mt-4 text-[var(--text-secondary)]">
|
||||
Absolutamente. Utilizamos cifrado AES-256, servidores en la UE, cumplimos 100% con RGPD y realizamos auditorías
|
||||
de seguridad trimestrales. Tus datos nunca se comparten con terceros y tienes control total sobre tu información.
|
||||
{t('landing:faq.questions.security.a', 'Absolutamente. Utilizamos cifrado AES-256, servidores en la UE, cumplimos 100% con RGPD y realizamos auditorías de seguridad trimestrales. Tus datos nunca se comparten con terceros y tienes control total sobre tu información.')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -889,17 +859,14 @@ const LandingPage: React.FC = () => {
|
||||
{/* Scarcity Badge */}
|
||||
<div className="inline-flex items-center gap-2 bg-red-600 text-white px-6 py-3 rounded-full text-sm font-bold mb-6 shadow-lg animate-pulse">
|
||||
<Clock className="w-5 h-5" />
|
||||
<span>Quedan 12 plazas de las 20 del programa piloto</span>
|
||||
<span>{t('landing:final_cta.scarcity_badge', 'Quedan 12 plazas de las 20 del programa piloto')}</span>
|
||||
</div>
|
||||
|
||||
<h2 className="text-3xl lg:text-5xl font-extrabold text-white">
|
||||
Sé de las Primeras 20 Panaderías
|
||||
<span className="block text-white/90 mt-2">En Probar Esta Tecnología</span>
|
||||
{t('landing:final_cta.title', 'Sé de las Primeras 20 Panaderías')}
|
||||
<span className="block text-white/90 mt-2">{t('landing:final_cta.title_accent', 'En Probar Esta Tecnología')}</span>
|
||||
</h2>
|
||||
<p className="mt-6 text-lg text-white/90 max-w-2xl mx-auto">
|
||||
No es para todo el mundo. Buscamos panaderías que quieran <strong>reducir desperdicios y aumentar ganancias</strong>
|
||||
con ayuda de IA, a cambio de feedback honesto.
|
||||
</p>
|
||||
<p className="mt-6 text-lg text-white/90 max-w-2xl mx-auto" dangerouslySetInnerHTML={{ __html: t('landing:final_cta.subtitle', 'No es para todo el mundo. Buscamos panaderías que quieran <strong>reducir desperdicios y aumentar ganancias</strong> con ayuda de IA, a cambio de feedback honesto.') }} />
|
||||
|
||||
<div className="mt-10 flex flex-col sm:flex-row gap-6 justify-center">
|
||||
<Link to={getRegisterUrl()} className="w-full sm:w-auto">
|
||||
@@ -909,7 +876,7 @@ const LandingPage: React.FC = () => {
|
||||
>
|
||||
<span className="absolute inset-0 w-full h-full bg-gradient-to-r from-white/0 via-white/20 to-white/0 translate-x-[-100%] group-hover:translate-x-[100%] transition-transform duration-700"></span>
|
||||
<span className="relative flex items-center justify-center gap-2">
|
||||
Solicitar Plaza en el Piloto
|
||||
{t('landing:final_cta.cta_primary', 'Solicitar Plaza en el Piloto')}
|
||||
<ArrowRight className="w-6 h-6 group-hover:translate-x-1 transition-transform duration-200" />
|
||||
</span>
|
||||
</Button>
|
||||
@@ -931,23 +898,23 @@ const LandingPage: React.FC = () => {
|
||||
{/* Social Proof Alternative - Loss Aversion */}
|
||||
<div className="mt-12 bg-white/10 backdrop-blur-sm rounded-2xl p-6 border border-white/20">
|
||||
<p className="text-white/90 text-base mb-4">
|
||||
<strong>¿Por qué actuar ahora?</strong>
|
||||
<strong>{t('landing:final_cta.why_now.title', '¿Por qué actuar ahora?')}</strong>
|
||||
</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-6 text-sm">
|
||||
<div className="flex flex-col items-center">
|
||||
<Award className="w-8 h-8 text-white mb-2" />
|
||||
<div className="text-white font-semibold">20% descuento de por vida</div>
|
||||
<div className="text-white/70">Solo primeros 20</div>
|
||||
<div className="text-white font-semibold">{t('landing:final_cta.why_now.lifetime_discount.title', '20% descuento de por vida')}</div>
|
||||
<div className="text-white/70">{t('landing:final_cta.why_now.lifetime_discount.subtitle', 'Solo primeros 20')}</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<Users className="w-8 h-8 text-white mb-2" />
|
||||
<div className="text-white font-semibold">Influyes en el roadmap</div>
|
||||
<div className="text-white/70">Tus necesidades primero</div>
|
||||
<div className="text-white font-semibold">{t('landing:final_cta.why_now.influence.title', 'Influyes en el roadmap')}</div>
|
||||
<div className="text-white/70">{t('landing:final_cta.why_now.influence.subtitle', 'Tus necesidades primero')}</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<Zap className="w-8 h-8 text-white mb-2" />
|
||||
<div className="text-white font-semibold">Soporte VIP</div>
|
||||
<div className="text-white/70">Acceso directo al equipo</div>
|
||||
<div className="text-white font-semibold">{t('landing:final_cta.why_now.vip_support.title', 'Soporte VIP')}</div>
|
||||
<div className="text-white/70">{t('landing:final_cta.why_now.vip_support.subtitle', 'Acceso directo al equipo')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -955,7 +922,7 @@ const LandingPage: React.FC = () => {
|
||||
{/* Guarantee */}
|
||||
<div className="mt-8 text-white/80 text-sm">
|
||||
<Shield className="w-5 h-5 inline mr-2" />
|
||||
<span>Garantía: Cancelas en cualquier momento sin dar explicaciones</span>
|
||||
<span>{t('landing:final_cta.guarantee', 'Garantía: Cancelas en cualquier momento sin dar explicaciones')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user