Skip to main content
The Dashboard provides real-time visibility into scrap metrics across the entire operation. It displays KPIs, trend charts, area breakdowns, and tolerance status for quick decision-making.

Overview

The dashboard automatically refreshes when new scrap is registered and provides multiple visualization types to understand scrap patterns.

Real-Time KPIs

Live metrics for today, this week, and trend comparisons

Interactive Charts

Line, bar, and pie charts built with Recharts library

Tolerance Monitoring

Visual alerts when scrap exceeds configured thresholds

Multi-Dimensional Views

Analyze by area, shift, category, chain, and part number

Key Performance Indicators (KPIs)

The top row displays 5 critical metrics:

1. Scrap Today

// From Dashboard.tsx:46
const todayQty = todayRecords.reduce((s, r) => s + Number(r.TOTAL_PZAS || 0), 0);
  • Value: Total pieces scrapped today
  • Color: Red (#E4002B — APTIV brand color)
  • Icon: Package icon
  • Updates: Real-time as records are added

2. Cost Today

// From Dashboard.tsx:47
const todayCost = todayRecords.reduce((s, r) => s + (r.COSTO || 0), 0);
  • Value: Total cost in USD for today
  • Format: Currency with 2 decimal places
  • Color: Orange (#f59e0b)
  • Icon: Dollar sign

3. Scrap This Week

// From Dashboard.tsx:48-49
const weekQty = weekRecords.reduce((s, r) => s + Number(r.TOTAL_PZAS || 0), 0);
const weekCost = weekRecords.reduce((s, r) => s + (r.COSTO || 0), 0);
  • Value: Total pieces for current week (Monday-Sunday)
  • Subtitle: Shows week cost
  • Color: Blue (#2563eb)
  • Icon: Activity icon

4. Top Area

// From Dashboard.tsx:53-55
const areaMap = new Map<string, number>();
todayRecords.forEach(r => areaMap.set(r.AREA, (areaMap.get(r.AREA) || 0) + Number(r.TOTAL_PZAS || 0)));
const topArea = [...areaMap.entries()].sort((a, b) => b[1] - a[1])[0];
  • Value: Area with highest scrap today
  • Subtitle: Piece count for that area
  • Color: Purple (#7c3aed)
  • Icon: Alert triangle

5. Week-over-Week Change

// From Dashboard.tsx:50-51
const prevWeekCost = prevWeekRecords.reduce((s, r) => s + (r.COSTO || 0), 0);
const weekChange = prevWeekCost > 0 ? ((weekCost - prevWeekCost) / prevWeekCost * 100) : 0;
  • Value: Percentage change vs. previous week
  • Color: Green if decrease, Red if increase
  • Icon: Trending up/down arrow
  • Logic: Compares current week (Mon-today) with previous week (Mon-Sun)
A positive percentage (red) means scrap increased — this is bad.
A negative percentage (green) means scrap decreased — this is good.

Charts & Visualizations

All charts use the Recharts library and support interactive tooltips.

1. Trend Line Chart (30 Days)

Displays scrap quantity and cost over the last 30 days.
// From Dashboard.tsx:61-74
const trendData = useMemo(() => {
  const data = [];
  for (let i = 29; i >= 0; i--) {
    const d = subDays(today, i);
    const ds = startOfDay(d); const de = endOfDay(d);
    const dayRecs = pesajes.filter(p => { 
      const pd = new Date(p.FECHA_REGISTRO); 
      return isWithinInterval(pd, { start: ds, end: de });
    });
    data.push({
      fecha: format(d, 'dd/MM', { locale: es }),
      cantidad: dayRecs.reduce((s, r) => s + Number(r.TOTAL_PZAS || 0), 0),
      costo: Math.round(dayRecs.reduce((s, r) => s + (r.COSTO || 0), 0) * 100) / 100,
    });
  }
  return data;
}, [pesajes, today]);
  • X-Axis: Date (dd/MM format)
  • Y-Axis: Quantity and cost
  • Lines:
    • Red line = Quantity (pieces)
    • Blue line = Cost (USD)
  • Smoothing: Monotone interpolation

2. Scrap by Area (Bar Chart)

// From Dashboard.tsx:76-80
const byArea = useMemo(() => {
  const map = new Map<string, { qty: number; cost: number }>();
  pesajes.forEach(r => { 
    const cur = map.get(r.AREA) || { qty: 0, cost: 0 }; 
    map.set(r.AREA, { qty: cur.qty + Number(r.TOTAL_PZAS || 0), cost: cur.cost + (r.COSTO || 0) });
  });
  return [...map.entries()].map(([name, v]) => ({ name, cantidad: v.qty, costo: Math.round(v.cost) })).sort((a, b) => b.costo - a.costo);
}, [pesajes]);
  • Type: Vertical bar chart
  • X-Axis: Area name
  • Y-Axis: Total cost (USD)
  • Sorting: Highest cost first
  • Color: Red (#E4002B)
  • Purpose: Identify which areas generate most scrap

3. By Category (Pie Chart)

// From Dashboard.tsx:82-86
const byCategory = useMemo(() => {
  const map = new Map<string, number>();
  pesajes.forEach(r => { 
    const cat = r.CATEGORIA || 'Sin categoría'; 
    map.set(cat, (map.get(cat) || 0) + Number(r.TOTAL_PZAS || 0));
  });
  return [...map.entries()].map(([name, value]) => ({ name, value }));
}, [pesajes]);
  • Type: Pie chart with percentage labels
  • Slices: Each scrap category
  • Colors: Rotates through 8-color palette
  • Labels: Category name + percentage
  • Use: Understand scrap distribution by type

4. By Shift (Bar Chart)

// From Dashboard.tsx:88-92
const byTurno = useMemo(() => {
  const map = new Map<string, { qty: number; cost: number }>();
  pesajes.forEach(r => { 
    const cur = map.get(r.TURNO) || { qty: 0, cost: 0 }; 
    map.set(r.TURNO, { qty: cur.qty + Number(r.TOTAL_PZAS || 0), cost: cur.cost + (r.COSTO || 0) });
  });
  return [...map.entries()].map(([name, v]) => ({ name, cantidad: v.qty, costo: Math.round(v.cost) }));
}, [pesajes]);
  • Type: Bar chart
  • X-Axis: Shift name (T1, T2, T3)
  • Y-Axis: Quantity (pieces)
  • Color: Purple (#7c3aed)
  • Insight: Compare shift performance

5. Top 5 Parts by Cost (Horizontal Bar)

// From Dashboard.tsx:94-98
const topParts = useMemo(() => {
  const map = new Map<string, number>();
  pesajes.forEach(r => map.set(r.NP, (map.get(r.NP) || 0) + (r.COSTO || 0)));
  return [...map.entries()].map(([name, costo]) => ({ name, costo: Math.round(costo) })).sort((a, b) => b.costo - a.costo).slice(0, 5);
}, [pesajes]);
  • Type: Horizontal bar chart
  • Y-Axis: Part number
  • X-Axis: Cost (USD)
  • Limit: Top 5 only
  • Color: Orange (#f59e0b)
  • Purpose: Focus improvement efforts on costliest parts

6. Cost by Chain (Bar Chart)

// From Dashboard.tsx:100-104
const byCadena = useMemo(() => {
  const map = new Map<string, number>();
  pesajes.forEach(r => map.set(r.CADENA, (map.get(r.CADENA) || 0) + (r.COSTO || 0)));
  return [...map.entries()].map(([name, costo]) => ({ name, costo: Math.round(costo) })).sort((a, b) => b.costo - a.costo);
}, [pesajes]);
  • Type: Bar chart
  • X-Axis: Production chain name
  • Y-Axis: Total cost
  • Color: Green (#059669)
  • Sorting: Descending by cost

Tolerance Monitoring

The dashboard displays real-time status of configured tolerances.

Tolerance Status Calculation

// From Dashboard.tsx:106-124
const toleranceStatus = useMemo(() => {
  return state.tolerancias.filter(t => t.activo).map(t => {
    let rr = pesajes;
    if (t.tipo_objetivo === 'area') rr = pesajes.filter(p => p.AREA === t.objetivo_id);
    else if (t.tipo_objetivo === 'cadena') rr = pesajes.filter(p => p.CADENA === t.objetivo_id);
    else if (t.tipo_objetivo === 'categoria') rr = pesajes.filter(p => p.CATEGORIA === t.objetivo_id);
    
    const now = new Date();
    if (t.tipo_periodo === 'diario') rr = rr.filter(p => isWithinInterval(new Date(p.FECHA_REGISTRO), { start: startOfDay(now), end: endOfDay(now) }));
    else if (t.tipo_periodo === 'semanal') rr = rr.filter(p => isWithinInterval(new Date(p.FECHA_REGISTRO), { start: startOfWeek(now, { weekStartsOn: 1 }), end: endOfDay(now) }));
    else { const ms = new Date(now.getFullYear(), now.getMonth(), 1); rr = rr.filter(p => { const d = new Date(p.FECHA_REGISTRO); return d >= ms && d <= now; }); }
    
    const totalQty = rr.reduce((s, r) => s + Number(r.TOTAL_PZAS || 0), 0);
    const totalCost = rr.reduce((s, r) => s + (r.COSTO || 0), 0);
    const qtyPct = t.cantidad_max > 0 ? (totalQty / t.cantidad_max) * 100 : 0;
    const costPct = t.costo_max > 0 ? (totalCost / t.costo_max) * 100 : 0;
    const maxPct = Math.max(qtyPct, costPct);
    const status = maxPct >= 100 ? 'red' : maxPct >= t.porcentaje_alerta ? 'yellow' : 'green';
    return { ...t, totalQty, totalCost, qtyPct, costPct, maxPct, status };
  });
}, [state.tolerancias, pesajes]);

Status Indicators

🟢 Within Meta

Scrap is below alert threshold (e.g., < 80% of max)

🟡 Warning

Scrap reached alert level (e.g., 80-99% of max)

🔴 Exceeded

Scrap exceeded maximum threshold (≥ 100%). Animated pulse effect.

Tolerance Card Display

// From Dashboard.tsx:154-190
<div style={cardStyle}>
  <h3>Estado de Tolerancias</h3>
  <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: '12px' }}>
    {toleranceStatus.map(t => (
      <div key={t.id}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <span>{t.nombre}</span>
          <span style={{ background: statusColor, color: statusColor }}>
            {t.status === 'green' ? '🟢 OK' : t.status === 'yellow' ? '🟡 Alerta' : '🔴 Excedido'}
          </span>
        </div>
        <div style={{ width: '100%', height: '8px', borderRadius: '4px', background: 'var(--border-color)' }}>
          <div style={{ width: `${Math.min(t.maxPct, 100)}%`, background: statusColor }} />
        </div>
        <div style={{ display: 'flex', justifyContent: 'space-between' }}>
          <span>{t.totalQty}/{t.cantidad_max} pzas</span>
          <span>${t.totalCost.toFixed(0)}/${t.costo_max} USD</span>
        </div>
      </div>
    ))}
  </div>
</div>
Features:
  • Progress bar shows percentage of max
  • Shows both quantity and cost limits
  • Animated red pulse when exceeded
  • Responsive grid layout
Exceeded tolerances trigger visual alerts but do not block scrap registration. They are informational only.

Top Failure Mode Alert

Displays the most common failure reason for today:
// From Dashboard.tsx:57-59
const fallaMap = new Map<string, number>();
todayRecords.forEach(r => { 
  if (r.MODO_FALLA && r.MODO_FALLA !== '-') 
    fallaMap.set(r.MODO_FALLA, (fallaMap.get(r.MODO_FALLA) || 0) + 1);
});
const topFalla = [...fallaMap.entries()].sort((a, b) => b[1] - a[1])[0];
Example:
“Top motivo hoy: Defecto de material (12 incidencias)”
This helps supervisors quickly identify recurring issues.

Data Filtering & Time Ranges

Today’s Data

// From Dashboard.tsx:28-35
const today = new Date();
const todayStart = startOfDay(today);
const todayEnd = endOfDay(today);

const todayRecords = useMemo(() =>
  pesajes.filter(p => { 
    const d = new Date(p.FECHA_REGISTRO); 
    return isWithinInterval(d, { start: todayStart, end: todayEnd });
  }),
  [pesajes, todayStart, todayEnd]
);

This Week’s Data

// From Dashboard.tsx:36-39
const weekStart = startOfWeek(today, { weekStartsOn: 1 }); // Monday

const weekRecords = useMemo(() =>
  pesajes.filter(p => { 
    const d = new Date(p.FECHA_REGISTRO); 
    return isWithinInterval(d, { start: weekStart, end: todayEnd });
  }),
  [pesajes, weekStart, todayEnd]
);

Previous Week’s Data

// From Dashboard.tsx:41-44
const prevWeekStart = subDays(weekStart, 7);
const prevWeekRecords = useMemo(() =>
  pesajes.filter(p => { 
    const d = new Date(p.FECHA_REGISTRO); 
    return isWithinInterval(d, { start: prevWeekStart, end: subDays(weekStart, 1) });
  }),
  [pesajes, prevWeekStart, weekStart]
);
All date filtering uses date-fns library with isWithinInterval for accurate boundary checks.

Theme Support

All charts and cards use CSS variables for dark/light theme support:
// From Dashboard.tsx:10-23
const cardStyle: React.CSSProperties = {
  background: 'var(--bg-card)',
  border: '1px solid var(--border-color)',
  borderRadius: '12px',
  padding: '16px',
};

const tooltipStyle = {
  background: 'var(--modal-bg)',
  border: '1px solid var(--border-color)',
  borderRadius: 8,
  color: 'var(--text-primary)',
};
CSS Variables Used:
  • --bg-card — Card background
  • --border-color — Border and grid color
  • --text-primary — Main text color
  • --text-secondary — Secondary text (labels, axes)
  • --chart-grid — Chart grid lines
  • --modal-bg — Tooltip background

Performance Optimization

All data aggregations use useMemo hooks to prevent unnecessary recalculations:
const byArea = useMemo(() => {
  // expensive calculation
}, [pesajes]); // only recalculates when pesajes changes
Benefits:
  • Charts don’t re-render on unrelated state changes
  • Smooth scrolling and interactions
  • Handles large datasets (1000+ records) efficiently

Responsive Design

All chart containers use ResponsiveContainer from Recharts:
<ResponsiveContainer width="100%" height={250}>
  <LineChart data={trendData}>
    {/* ... */}
  </LineChart>
</ResponsiveContainer>
Grid Layouts:
  • KPI cards: repeat(auto-fit, minmax(180px, 1fr))
  • Large charts: repeat(auto-fit, minmax(400px, 1fr))
  • Small charts: repeat(auto-fit, minmax(280px, 1fr))
This ensures proper layout on desktop, tablet, and large mobile screens.

Permissions

The dashboard is visible to all authenticated users. No special permission required. Data Filtering by Role:
  • Operators — See all data (no filtering)
  • Supervisors — See all data
  • Quality — See all data
  • Admin — Full visibility
Future enhancement: Add area-specific filtering for supervisors to see only their area’s data.

Scrap Registration

Register new scrap records

Reports

Detailed reports with export options

Tolerances

Configure scrap limits and alerts

Build docs developers (and LLMs) love