import React, { useState } from 'react';
import { TrendingUp, DollarSign, Activity, AlertTriangle, Settings, CheckCircle, Wrench, Zap, Thermometer, Eye, Clock, CalendarClock, WrenchIcon, BarChart3, Bell, History, Lock } from 'lucide-react';
import { Card, StatsGrid, Button } from '../../../components/ui';
import { PageHeader } from '../../../components/layout';
import { QualityDashboard } from '../../../components/domain/production';
import ProductionCostAnalytics from '../../../components/domain/analytics/ProductionCostAnalytics';
import AIInsightsWidget from '../../../components/domain/dashboard/AIInsightsWidget';
import EquipmentStatusWidget from '../../../components/domain/dashboard/EquipmentStatusWidget';
import { Badge } from '../../../components/ui/Badge';
import { useSubscription } from '../../../api/hooks/subscription';
import { useCurrentTenant } from '../../../stores/tenant.store';
// Mock data for equipment (from MaquinariaPage)
const MOCK_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
}
}
];
// Mock chart data for equipment analytics (from MaquinariaPage)
const MOCK_CHART_DATA = [
{
id: 'efficiency',
name: 'Eficiencia',
type: 'bar' as const,
visible: true,
color: '#10B981',
data: [
{ x: 1, y: 92, label: 'Horno Principal #1' },
{ x: 2, y: 88, label: 'Batidora Industrial #2' },
{ x: 3, y: 0, label: 'Cámara de Fermentación #1' }
]
},
{
id: 'uptime',
name: 'Tiempo de Actividad',
type: 'line' as const,
visible: true,
color: '#3B82F6',
data: [
{ x: 1, y: 98.5, label: 'Horno Principal #1' },
{ x: 2, y: 94.2, label: 'Batidora Industrial #2' },
{ x: 3, y: 85.1, label: 'Cámara de Fermentación #1' }
]
},
{
id: 'energy',
name: 'Consumo Energético',
type: 'area' as const,
visible: true,
color: '#F59E0B',
data: [
{ x: 1, y: 45.2, label: 'Horno Principal #1' },
{ x: 2, y: 12.8, label: 'Batidora Industrial #2' },
{ x: 3, y: 0, label: 'Cámara de Fermentación #1' }
]
}
];
const MOCK_MAINTENANCE_CHART_DATA = [
{
id: 'costs',
name: 'Costos de Mantenimiento',
type: 'bar' as const,
visible: true,
color: '#8B5CF6',
data: [
{ x: 1, y: 150, label: 'Horno Principal #1' },
{ x: 2, y: 85, label: 'Batidora Industrial #2' },
{ x: 3, y: 200, label: 'Cámara de Fermentación #1' }
]
},
{
id: 'downtime',
name: 'Tiempo de Inactividad',
type: 'line' as const,
visible: true,
color: '#EF4444',
data: [
{ x: 1, y: 2, label: 'Horno Principal #1' },
{ x: 2, y: 4, label: 'Batidora Industrial #2' },
{ x: 3, y: 8, label: 'Cámara de Fermentación #1' }
]
}
];
const MOCK_STATUS_CHART_DATA = [
{
id: 'status',
name: 'Estado de Equipos',
type: 'pie' as const,
visible: true,
color: '#10B981',
data: [
{ x: 'Operativo', y: 1, label: 'Operativo' },
{ x: 'Advertencia', y: 1, label: 'Advertencia' },
{ x: 'Mantenimiento', y: 1, label: 'Mantenimiento' }
]
}
];
// Import statement is at the top of the file - already included
// The enhanced ProductionCostMonitor is now imported from the components directory
// Helper functions for equipment status
const getStatusConfig = (status: string) => {
const configs = {
operational: { color: '#10B981', text: 'Operativo', icon: CheckCircle },
warning: { color: '#F59E0B', text: 'Advertencia', icon: AlertTriangle },
maintenance: { color: '#3B82F6', text: 'Mantenimiento', icon: Wrench },
down: { color: '#EF4444', text: 'Fuera de Servicio', icon: AlertTriangle }
};
return configs[status as keyof typeof configs] || { color: '#6B7280', text: 'Desconocido', icon: Settings };
};
const getTypeIcon = (type: string) => {
const icons = {
oven: Thermometer,
mixer: Activity,
proofer: Settings,
freezer: Zap,
packaging: Settings,
other: Settings
};
return icons[type as keyof typeof icons] || Settings;
};
const getStatusColor = (status: string): string => {
const statusColors: { [key: string]: string } = {
operational: '#10B981', // green-500
warning: '#F59E0B', // amber-500
maintenance: '#3B82F6', // blue-500
down: '#EF4444' // red-500
};
return statusColors[status] || '#6B7280'; // gray-500 as default
};
// Equipment Analytics Component (from MaquinariaPage)
const EquipmentAnalytics: React.FC = () => {
const [activeTab, setActiveTab] = useState('overview');
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
// Filter equipment based on search and status
const filteredEquipment = MOCK_EQUIPMENT.filter(eq => {
const matchesSearch = !searchTerm ||
eq.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
eq.location.toLowerCase().includes(searchTerm.toLowerCase()) ||
eq.type.toLowerCase().includes(searchTerm.toLowerCase());
const matchesStatus = statusFilter === 'all' || eq.status === statusFilter;
return matchesSearch && matchesStatus;
});
// Calculate equipment stats
const equipmentStats = {
total: MOCK_EQUIPMENT.length,
operational: MOCK_EQUIPMENT.filter(e => e.status === 'operational').length,
warning: MOCK_EQUIPMENT.filter(e => e.status === 'warning').length,
maintenance: MOCK_EQUIPMENT.filter(e => e.status === 'maintenance').length,
down: MOCK_EQUIPMENT.filter(e => e.status === 'down').length,
avgEfficiency: MOCK_EQUIPMENT.reduce((sum, e) => sum + e.efficiency, 0) / MOCK_EQUIPMENT.length,
avgUptime: MOCK_EQUIPMENT.reduce((sum, e) => sum + e.uptime, 0) / MOCK_EQUIPMENT.length,
totalAlerts: MOCK_EQUIPMENT.reduce((sum, e) => sum + e.alerts.filter(a => !a.acknowledged).length, 0)
};
return (
{/* Stats Grid */}
= 90 ? 'success' as const : 'warning' as const
},
{
title: 'Alertas Activas',
value: equipmentStats.totalAlerts,
icon: Bell,
variant: equipmentStats.totalAlerts === 0 ? 'success' as const : 'error' as const
}
]}
columns={4}
/>
{/* Tabs for different views */}
setActiveTab('overview')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === 'overview'
? 'border-orange-500 text-[var(--color-primary)]'
: 'border-transparent text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] hover:border-[var(--border-secondary)]'
}`}
>
Resumen
setActiveTab('maintenance')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === 'maintenance'
? 'border-orange-500 text-[var(--color-primary)]'
: 'border-transparent text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] hover:border-[var(--border-secondary)]'
}`}
>
Mantenimiento
setActiveTab('status')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === 'status'
? 'border-orange-500 text-[var(--color-primary)]'
: 'border-transparent text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] hover:border-[var(--border-secondary)]'
}`}
>
Estado
{/* Overview Tab */}
{activeTab === 'overview' && (
{/* Charts for Equipment Analytics */}
Eficiencia y Tiempo de Actividad
{
if (canvas) {
const ctx = canvas.getContext('2d');
if (ctx) {
// Set canvas size
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw simple bar chart for efficiency
const padding = 40;
const chartWidth = canvas.width - 2 * padding;
const chartHeight = canvas.height - 2 * padding;
// Get max value for scaling
const maxValue = Math.max(...MOCK_CHART_DATA[0].data.map(d => d.y));
// Draw bars
MOCK_CHART_DATA[0].data.forEach((point, index) => {
const barWidth = chartWidth / MOCK_CHART_DATA[0].data.length - 10;
const x = padding + index * (chartWidth / MOCK_CHART_DATA[0].data.length) + 5;
const barHeight = (point.y / maxValue) * chartHeight;
const y = padding + chartHeight - barHeight;
// Bar
ctx.fillStyle = MOCK_CHART_DATA[0].color;
ctx.fillRect(x, y, barWidth, barHeight);
// Label
ctx.fillStyle = '#374151';
ctx.font = '12px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(point.label, x + barWidth / 2, canvas.height - 10);
ctx.fillText(point.y.toString(), x + barWidth / 2, y - 5);
});
// Axes
ctx.strokeStyle = '#374151';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(padding, padding);
ctx.lineTo(padding, padding + chartHeight);
ctx.lineTo(padding + chartWidth, padding + chartHeight);
ctx.stroke();
}
}
}} className="w-full h-full" />
Estado de Equipos
{
if (canvas) {
const ctx = canvas.getContext('2d');
if (ctx) {
// Set canvas size
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw pie chart for status distribution
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(canvas.width, canvas.height) / 3;
const total = MOCK_STATUS_CHART_DATA[0].data.reduce((sum, point) => sum + point.y, 0);
let startAngle = -Math.PI / 2;
const colors = ['#10B981', '#F59E0B', '#3B82F6'];
MOCK_STATUS_CHART_DATA[0].data.forEach((point, index) => {
const sliceAngle = (point.y / total) * 2 * Math.PI;
const color = colors[index];
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, radius, startAngle, startAngle + sliceAngle);
ctx.closePath();
ctx.fill();
// Draw slice border
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 2;
ctx.stroke();
// Draw labels
const labelAngle = startAngle + sliceAngle / 2;
const labelX = centerX + Math.cos(labelAngle) * (radius + 30);
const labelY = centerY + Math.sin(labelAngle) * (radius + 30);
ctx.fillStyle = '#374151';
ctx.font = '12px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(point.x, labelX, labelY);
const percentage = ((point.y / total) * 100).toFixed(1);
ctx.fillText(`${percentage}%`, labelX, labelY + 15);
startAngle += sliceAngle;
});
}
}
}} className="w-full h-full" />
{/* Controls */}
setStatusFilter(e.target.value)}
className="px-3 py-2 border border-[var(--border-primary)] rounded-md bg-[var(--bg-primary)] text-[var(--text-primary)]"
>
Todos los estados
Operativo
Advertencia
Mantenimiento
Fuera de Servicio
{/* Equipment Grid */}
{filteredEquipment.map((equipment) => {
const statusConfig = getStatusConfig(equipment.status);
const TypeIcon = getTypeIcon(equipment.type);
return (
{equipment.name}
{equipment.location}
Eficiencia
{equipment.efficiency}%
Tiempo Activo
{equipment.uptime.toFixed(1)}%
Consumo
{equipment.energyUsage} kW
Utilización Hoy
{equipment.utilizationToday}%
{statusConfig.text}
Ver
);
})}
)}
{/* Maintenance Tab */}
{activeTab === 'maintenance' && (
{/* Maintenance Charts */}
Costos de Mantenimiento
{
if (canvas) {
const ctx = canvas.getContext('2d');
if (ctx) {
// Set canvas size
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw bar chart for maintenance costs
const padding = 40;
const chartWidth = canvas.width - 2 * padding;
const chartHeight = canvas.height - 2 * padding;
// Get max value for scaling
const maxValue = Math.max(...MOCK_MAINTENANCE_CHART_DATA[0].data.map(d => d.y));
// Draw bars
MOCK_MAINTENANCE_CHART_DATA[0].data.forEach((point, index) => {
const barWidth = chartWidth / MOCK_MAINTENANCE_CHART_DATA[0].data.length - 10;
const x = padding + index * (chartWidth / MOCK_MAINTENANCE_CHART_DATA[0].data.length) + 5;
const barHeight = (point.y / maxValue) * chartHeight;
const y = padding + chartHeight - barHeight;
// Bar
ctx.fillStyle = MOCK_MAINTENANCE_CHART_DATA[0].color;
ctx.fillRect(x, y, barWidth, barHeight);
// Label
ctx.fillStyle = '#374151';
ctx.font = '12px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(point.label, x + barWidth / 2, canvas.height - 10);
ctx.fillText(`€${point.y}`, x + barWidth / 2, y - 5);
});
// Axes
ctx.strokeStyle = '#374151';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(padding, padding);
ctx.lineTo(padding, padding + chartHeight);
ctx.lineTo(padding + chartWidth, padding + chartHeight);
ctx.stroke();
}
}
}} className="w-full h-full" />
Tiempo de Inactividad
{
if (canvas) {
const ctx = canvas.getContext('2d');
if (ctx) {
// Set canvas size
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw line chart for downtime
const padding = 40;
const chartWidth = canvas.width - 2 * padding;
const chartHeight = canvas.height - 2 * padding;
// Get max value for scaling
const maxValue = Math.max(...MOCK_MAINTENANCE_CHART_DATA[1].data.map(d => d.y));
// Draw line
ctx.strokeStyle = MOCK_MAINTENANCE_CHART_DATA[1].color;
ctx.lineWidth = 2;
ctx.beginPath();
MOCK_MAINTENANCE_CHART_DATA[1].data.forEach((point, index) => {
const x = padding + (index * chartWidth) / (MOCK_MAINTENANCE_CHART_DATA[1].data.length - 1);
const y = padding + chartHeight - ((point.y / maxValue) * chartHeight);
if (index === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
// Draw points
ctx.fillStyle = MOCK_MAINTENANCE_CHART_DATA[1].color;
ctx.beginPath();
ctx.arc(x, y, 4, 0, 2 * Math.PI);
ctx.fill();
// Labels
ctx.fillStyle = '#374151';
ctx.font = '12px sans-serif';
ctx.textAlign = 'center';
ctx.fillText(point.label, x, canvas.height - 10);
ctx.fillText(`${point.y}h`, x, y - 10);
});
ctx.stroke();
// Axes
ctx.strokeStyle = '#374151';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(padding, padding);
ctx.lineTo(padding, padding + chartHeight);
ctx.lineTo(padding + chartWidth, padding + chartHeight);
ctx.stroke();
}
}
}} className="w-full h-full" />
{/* Maintenance Schedule */}
Programación de Mantenimiento
{MOCK_EQUIPMENT.map((equipment) => {
const nextMaintenanceDate = new Date(equipment.nextMaintenance);
const daysUntilMaintenance = Math.ceil((nextMaintenanceDate.getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24));
const isOverdue = daysUntilMaintenance < 0;
return (
{equipment.name}
{equipment.model}
{isOverdue ? 'Atrasado' : 'Programado'}
{nextMaintenanceDate.toLocaleDateString('es-ES')}
Técnico:
Juan Pérez
Reagendar
);
})}
)}
{/* Status Tab */}
{activeTab === 'status' && (
{/* Status Overview */}
{equipmentStats.operational}
Operativo
{equipmentStats.warning}
Advertencia
{equipmentStats.maintenance}
Mantenimiento
{equipmentStats.down}
Fuera de Servicio
{/* Active Alerts */}
Alertas Activas
{MOCK_EQUIPMENT.flatMap(eq =>
eq.alerts.filter(a => !a.acknowledged).map(alert => (
{new Date(alert.timestamp).toLocaleString('es-ES')}
{alert.message}
Acknowledge
))
)}
{MOCK_EQUIPMENT.flatMap(eq => eq.alerts.filter(a => !a.acknowledged)).length === 0 && (
No hay alertas activas
Todos los equipos están funcionando correctamente
)}
{/* Equipment Status Details */}
Detalles de Estado de Equipos
{MOCK_EQUIPMENT.map((equipment) => {
const statusConfig = getStatusConfig(equipment.status);
return (
{equipment.name}
{equipment.model}
{statusConfig.text}
{equipment.efficiency}%
Eficiencia
{equipment.uptime.toFixed(1)}%
Tiempo de Actividad
{equipment.energyUsage} kW
Consumo Energético
{equipment.utilizationToday}%
Utilización Hoy
);
})}
)}
);
};
const ProductionAnalyticsPage: React.FC = () => {
const [activeTab, setActiveTab] = useState('overview');
const { canAccessAnalytics } = useSubscription();
const currentTenant = useCurrentTenant();
const tenantId = currentTenant?.id || '';
// Check if user has access to advanced analytics (professional/enterprise)
const hasAdvancedAccess = canAccessAnalytics('advanced');
// If user doesn't have access to advanced analytics, show upgrade message
if (!hasAdvancedAccess) {
return (
Contenido exclusivo para planes Professional y Enterprise
El análisis avanzado de producción está disponible solo para usuarios con planes Professional o Enterprise.
Actualiza tu plan para acceder a todas las funcionalidades.
window.location.hash = '#/app/settings/profile'}
>
Actualizar Plan
);
}
return (
{/* Key Metrics */}
{/* Tabs Navigation */}
setActiveTab('overview')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === 'overview'
? 'border-orange-500 text-[var(--color-primary)]'
: 'border-transparent text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] hover:border-[var(--border-secondary)]'
}`}
>
Resumen
setActiveTab('costs')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === 'costs'
? 'border-orange-500 text-[var(--color-primary)]'
: 'border-transparent text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] hover:border-[var(--border-secondary)]'
}`}
>
Costos
setActiveTab('ai-insights')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === 'ai-insights'
? 'border-orange-500 text-[var(--color-primary)]'
: 'border-transparent text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] hover:border-[var(--border-secondary)]'
}`}
>
IA Insights
setActiveTab('maquinaria')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === 'maquinaria'
? 'border-orange-500 text-[var(--color-primary)]'
: 'border-transparent text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] hover:border-[var(--border-secondary)]'
}`}
>
Maquinaria
setActiveTab('quality')}
className={`py-2 px-1 border-b-2 font-medium text-sm ${
activeTab === 'quality'
? 'border-orange-500 text-[var(--color-primary)]'
: 'border-transparent text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] hover:border-[var(--border-secondary)]'
}`}
>
Calidad
{/* Tab Content */}
{activeTab === 'overview' && (
Resumen de Calidad
Productos aprobados
96%
Rechazos por calidad
2%
Reprocesos
2%
)}
{activeTab === 'costs' && (
)}
{activeTab === 'ai-insights' && (
)}
{activeTab === 'maquinaria' && (
)}
{activeTab === 'quality' && (
)}
);
};
export default ProductionAnalyticsPage;