The Reports module provides advanced filtering, visualization, and export capabilities for scrap data analysis. Generate custom reports by date range, area, shift, category, and chain.
Overview
Reports offer more flexibility than the dashboard, with precise date range selection and multi-dimensional filtering.
Advanced Filters Filter by date range, area, shift, category, and chain
Export Options Download as CSV or PDF with full data tables
Detailed Tables View up to 50 records with sortable columns
Custom Charts Context-specific visualizations for filtered data
Report Filters
Date Range Selection
Choose from preset ranges or custom dates: // From Reports.tsx:15
const [ range , setRange ] = useState ( 'month' );
// From Reports.tsx:21-31
const dateRange = useMemo (() => {
const end = endOfDay ( new Date ());
let start : Date ;
switch ( range ) {
case 'today' : start = startOfDay ( new Date ()); break ;
case 'week' : start = subDays ( new Date (), 7 ); break ;
case '3months' : start = subMonths ( new Date (), 3 ); break ;
default : start = subMonths ( new Date (), 1 );
}
return { start , end };
}, [ range ]);
Available Ranges:
Today — Current day only
Last 7 days — Rolling 7-day window
Last month — Rolling 30 days
Last 3 months — Rolling 90 days
Area Filter
Filter by production area: // From Reports.tsx:16
const [ areaF , setAreaF ] = useState ( '' );
// From Reports.tsx:36
if ( areaF ) data = data . filter ( p => p . AREA === areaF );
Select from active areas only
Empty selection = all areas
Dropdown populated from state.areas.filter(a => a.activo)
Shift Filter
Filter by shift: // From Reports.tsx:17
const [ turnoF , setTurnoF ] = useState ( '' );
// From Reports.tsx:37
if ( turnoF ) data = data . filter ( p => p . TURNO === turnoF );
Shows all configured shifts (T1, T2, T3, etc.)
Empty = all shifts included
Category Filter
Filter by scrap category: // From Reports.tsx:18
const [ catF , setCatF ] = useState ( '' );
// From Reports.tsx:38
if ( catF ) data = data . filter ( p => p . CATEGORIA === catF );
Select material, process, quality, etc.
Only active categories shown
Chain Filter
Filter by production chain: // From Reports.tsx:19
const [ cadenaF , setCadenaF ] = useState ( '' );
// From Reports.tsx:39
if ( cadenaF ) data = data . filter ( p => p . CADENA === cadenaF );
Select assembly line or production chain
Empty = all chains
Combined Filtering Logic
// From Reports.tsx:33-41
const records = useMemo (() => {
let data = state . pesajes . filter ( p => ! p . ELIMINADO );
data = data . filter ( p => isWithinInterval ( new Date ( p . FECHA_REGISTRO ), dateRange ));
if ( areaF ) data = data . filter ( p => p . AREA === areaF );
if ( turnoF ) data = data . filter ( p => p . TURNO === turnoF );
if ( catF ) data = data . filter ( p => p . CATEGORIA === catF );
if ( cadenaF ) data = data . filter ( p => p . CADENA === cadenaF );
return data . sort (( a , b ) => new Date ( b . FECHA_REGISTRO ). getTime () - new Date ( a . FECHA_REGISTRO ). getTime ());
}, [ state . pesajes , dateRange , areaF , turnoF , catF , cadenaF ]);
All filters are applied cumulatively (AND logic). Results are sorted newest first.
Report KPIs
Three summary metrics display at the top:
// From Reports.tsx:43-44
const totalQty = records . reduce (( s , r ) => s + Number ( r . TOTAL_PZAS || 0 ), 0 );
const totalCost = records . reduce (( s , r ) => s + ( r . COSTO || 0 ), 0 );
Total Records Number of scrap entries matching filters
Total Quantity Sum of all pieces (formatted with thousand separators)
Total Cost Sum of all costs in USD (2 decimal precision)
Report Charts
Four visualization types help analyze filtered data:
1. Cost Trend Line Chart
// From Reports.tsx:46-50
const costByDay = useMemo (() => {
const map = new Map < string , number >();
records . forEach ( r => {
const d = format ( new Date ( r . FECHA_REGISTRO ), 'dd/MM' , { locale: es });
map . set ( d , ( map . get ( d ) || 0 ) + ( r . COSTO || 0 ));
});
return [ ... map . entries ()]. map (([ fecha , costo ]) => ({ fecha , costo: Math . round ( costo ) }));
}, [ records ]);
Type : Line chart
X-Axis : Date (dd/MM format)
Y-Axis : Daily cost (USD)
Color : Red (#E4002B)
Use : Identify cost spikes and patterns
2. Cost by Category Bar Chart
// From Reports.tsx:52-56
const costByCat = useMemo (() => {
const map = new Map < string , number >();
records . forEach ( r => map . set ( r . CATEGORIA || 'Sin cat.' , ( map . get ( r . CATEGORIA || 'Sin cat.' ) || 0 ) + ( r . COSTO || 0 )));
return [ ... map . entries ()]. map (([ name , costo ]) => ({ name , costo: Math . round ( costo ) })). sort (( a , b ) => b . costo - a . costo );
}, [ records ]);
Type : Vertical bar chart
X-Axis : Category name
Y-Axis : Total cost (USD)
Color : Blue (#2563eb)
Sorting : Highest cost first
Use : Compare scrap types
3. Top 10 Parts Horizontal Bar Chart
// From Reports.tsx:58-62
const topPartsReport = useMemo (() => {
const map = new Map < string , number >();
records . 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 , 10 );
}, [ records ]);
Type : Horizontal bar chart
Y-Axis : Part number
X-Axis : Cost (USD)
Color : Orange (#f59e0b)
Limit : Top 10 only
Use : Focus on highest-cost parts
4. Quantity by Shift Bar Chart
// From Reports.tsx:64-68
const byTurnoReport = useMemo (() => {
const map = new Map < string , { qty : number ; cost : number }>();
records . forEach ( r => map . set ( r . TURNO , ( map . get ( r . TURNO ) || 0 ) + Number ( r . TOTAL_PZAS || 0 )));
return [ ... map . entries ()]. map (([ name , cantidad ]) => ({ name , cantidad }));
}, [ records ]);
Type : Bar chart
X-Axis : Shift code
Y-Axis : Total quantity
Color : Purple (#7c3aed)
Use : Compare shift performance
5. Top Failure Modes Chart
// From Reports.tsx:70-74
const topMotivos = useMemo (() => {
const map = new Map < string , number >();
records . forEach ( r => {
if ( r . MODO_FALLA && r . MODO_FALLA !== '-' )
map . set ( r . MODO_FALLA , ( map . get ( r . MODO_FALLA ) || 0 ) + 1 );
});
return [ ... map . entries ()]. map (([ name , count ]) => ({ name , count })). sort (( a , b ) => b . count - a . count ). slice ( 0 , 10 );
}, [ records ]);
Type : Bar chart
X-Axis : Failure reason
Y-Axis : Incident count
Color : Pink (#ec4899)
Limit : Top 10 motives
Use : Root cause analysis
Data Table
The report includes a detailed data table showing up to 50 records:
// From Reports.tsx:218-259
< table >
< thead >
< tr >
< th > Fecha </ th >
< th > Área </ th >
< th > Cadena </ th >
< th > Línea </ th >
< th > Turno </ th >
< th > No.Parte </ th >
< th > Material </ th >
< th > Cat. </ th >
< th > Motivo </ th >
< th > Cant. </ th >
< th > C.Unit </ th >
< th > C.Total </ th >
< th > Usuario </ th >
</ tr >
</ thead >
< tbody >
{ records . slice ( 0 , 50 ). map ( r => (
< tr key = { r . ID } >
< td > { format ( new Date ( r . FECHA_REGISTRO ), 'dd/MM HH:mm' ) } </ td >
< td > { r . AREA } </ td >
< td > { r . CADENA } </ td >
< td > { r . LINEA || '-' } </ td >
< td > { r . TURNO } </ td >
< td > { r . NP } </ td >
< td > { r . MATERIAL } </ td >
< td > { r . CATEGORIA || '-' } </ td >
< td > { r . MODO_FALLA } </ td >
< td > { r . TOTAL_PZAS } </ td >
< td > $ { ( r . COSTO / Math . max ( Number ( r . TOTAL_PZAS ), 1 )). toFixed ( 2 ) } </ td >
< td > $ { r . COSTO ?. toFixed ( 2 ) } </ td >
< td > { state . usuarios . find ( u => u . id === r . USUARIO_ID )?. nombre || '-' } </ td >
</ tr >
)) }
</ tbody >
</ table >
Features:
Row limit : 50 records (prevents browser lag)
Unit cost : Calculated as Total Cost / Quantity
User lookup : Resolves USUARIO_ID to display name
Date format : dd/MM HH:mm (e.g., “15/03 14:32”)
Styling : Zebra striping, responsive overflow
// From Reports.tsx:248-255
< tfoot >
< tr >
< td colSpan = { 9 } > TOTALES: </ td >
< td > { totalQty . toLocaleString () } </ td >
< td > — </ td >
< td > $ { totalCost . toFixed ( 2 ) } </ td >
< td ></ td >
</ tr >
</ tfoot >
Shows grand totals for quantity and cost across all filtered records (not just visible 50).
Export Functions
CSV Export
Generate downloadable CSV file with all records:
// From Reports.tsx:76-86
const exportCSV = () => {
const headers = [ 'Fecha' , 'Área' , 'Cadena' , 'Línea' , 'Turno' , 'No.Parte' , 'Material' , 'Categoría' , 'Motivo' , 'Cantidad' , 'CostoUnit' , 'CostoTotal' , 'Usuario' ];
const rows = records . map ( r => [
format ( new Date ( r . FECHA_REGISTRO ), 'yyyy-MM-dd HH:mm' ), r . AREA , r . CADENA , r . LINEA || '' , r . TURNO , r . NP , r . MATERIAL , r . CATEGORIA || '' , r . MODO_FALLA ,
r . TOTAL_PZAS , ( r . COSTO / Math . max ( Number ( r . TOTAL_PZAS ), 1 )). toFixed ( 2 ), r . COSTO ?. toFixed ( 2 ),
state . usuarios . find ( u => u . id === r . USUARIO_ID )?. nombre || ''
]);
const csv = [ headers . join ( ',' ), ... rows . map ( r => r . join ( ',' ))]. join ( ' \n ' );
const blob = new Blob ([ csv ], { type: 'text/csv' });
const a = document . createElement ( 'a' ); a . href = URL . createObjectURL ( blob ); a . download = `reporte_scrap_ ${ format ( new Date (), 'yyyyMMdd' ) } .csv` ; a . click ();
};
Features:
Format : Standard CSV with comma delimiters
Filename : reporte_scrap_YYYYMMDD.csv
All records : Exports complete filtered dataset (not limited to 50)
User-friendly : Auto-downloads to browser default location
CSV Structure
Excel Compatibility
Fecha, Área, Cadena, Línea, Turno, No.Parte, Material, Categoría, Motivo, Cantidad, CostoUnit, CostoTotal, Usuario
2024-03-15 14:32, Assembly, Chain A, Line 1, T2, PN-12345, Steel, Material, Defect, 25, 1.50, 37.50, Juan Pérez
2024-03-15 10:15, Paint, Chain B, , T1, PN-67890, Paint, Process, Overspray, 10, 2.30, 23.00, María López
The CSV format is fully compatible with:
Microsoft Excel
Google Sheets
LibreOffice Calc
SAP import tools
ERP systems
Open CSV files with “Import Data” in Excel to preserve formatting and avoid locale issues.
PDF Export
Generate printable PDF report:
// From Reports.tsx:88-103
const exportPDF = async () => {
const { default : jsPDF } = await import ( 'jspdf' );
const autoTable = ( await import ( 'jspdf-autotable' )). default ;
const doc = new jsPDF ( 'landscape' );
doc . setFontSize ( 16 ); doc . text ( 'Reporte de Scrap - APTIV' , 14 , 15 );
doc . setFontSize ( 10 ); doc . text ( `Generado: ${ format ( new Date (), 'dd/MM/yyyy HH:mm' ) } | Registros: ${ records . length } | Costo Total: $ ${ totalCost . toFixed ( 2 ) } ` , 14 , 22 );
autoTable ( doc , {
startY: 28 ,
head: [[ 'Fecha' , 'Área' , 'Cadena' , 'Turno' , 'No.Parte' , 'Material' , 'Motivo' , 'Cant.' , 'Costo' ]],
body: records . slice ( 0 , 200 ). map ( r => [
format ( new Date ( r . FECHA_REGISTRO ), 'dd/MM/yy HH:mm' ), r . AREA , r . CADENA , r . TURNO , r . NP , r . MATERIAL , r . MODO_FALLA , r . TOTAL_PZAS , `$ ${ r . COSTO ?. toFixed ( 2 ) } `
]),
styles: { fontSize: 7 }, headStyles: { fillColor: [ 228 , 0 , 43 ] },
});
doc . save ( `reporte_scrap_ ${ format ( new Date (), 'yyyyMMdd' ) } .pdf` );
};
Features:
Orientation : Landscape (to fit all columns)
Header : APTIV branding with generation timestamp
Summary : Record count and total cost
Table styling : Red header (APTIV brand color), compact 7pt font
Record limit : First 200 records only (PDF size constraint)
Libraries : Uses jspdf + jspdf-autotable
PDF export is limited to 200 records to prevent performance issues. For larger datasets, use CSV export instead.
Permissions
Access control for reports:
// From Reports.tsx:105-106
if ( ! can ( 'view_area_reports' ) && ! can ( 'view_global_reports' ))
return < div > No tienes permiso para ver reportes. </ div > ;
Area Reports Permission : view_area_reportsRoles : Supervisor, Quality, AdminCan view reports filtered to their assigned area(s)
Global Reports Permission : view_global_reportsRoles : Quality, AdminCan view reports across all areas without restriction
Filter UI Implementation
// From Reports.tsx:122-135
< div style = { { display: 'flex' , flexWrap: 'wrap' , gap: '12px' } } >
{ [
{ v: range , set: setRange , opts: [[ 'today' , 'Hoy' ],[ 'week' , 'Última semana' ],[ 'month' , 'Último mes' ],[ '3months' , '3 meses' ]] },
{ v: areaF , set: setAreaF , opts: [[ '' , 'Todas las áreas' ], ... state . areas . filter ( a => a . activo ). map ( a => [ a . AREA , a . AREA ])] },
{ v: turnoF , set: setTurnoF , opts: [[ '' , 'Todos los turnos' ], ... state . turnos . map ( t => [ t . codigo , t . nombre ])] },
{ v: catF , set: setCatF , opts: [[ '' , 'Todas las categorías' ], ... state . categorias . filter ( c => c . activo ). map ( c => [ c . nombre , c . nombre ])] },
{ v: cadenaF , set: setCadenaF , opts: [[ '' , 'Todas las cadenas' ], ... state . cadenas . filter ( c => c . activo ). map ( c => [ c . nombre , c . nombre ])] },
]. map (( f , idx ) => (
< select key = { idx } value = { f . v } onChange = { e => f . set ( e . target . value ) } style = { inputStyle } >
{ f . opts . map (([ val , label ]) => < option key = { val } value = { val } > { label } </ option > ) }
</ select >
)) }
</ div >
Design Pattern:
Array of filter configurations
Dynamic <select> generation
Shared inputStyle for consistency
Responsive flex wrap layout
Memoization Strategy
All expensive calculations use useMemo:
const records = useMemo (() => { /* filter logic */ }, [ state . pesajes , dateRange , areaF , turnoF , catF , cadenaF ]);
const costByDay = useMemo (() => { /* aggregation */ }, [ records ]);
const topPartsReport = useMemo (() => { /* sorting */ }, [ records ]);
Benefits:
Only recalculates when dependencies change
Prevents chart flicker on unrelated state updates
Handles 1000+ records smoothly
Large Dataset Handling
Table Rendering
PDF Export
CSV Export
{ records . slice ( 0 , 50 ). map ( r => (
// render row
))}
Limits DOM nodes to 50 rows to prevent browser lag. records . slice ( 0 , 200 ). map ( r => [ ... ]);
Caps PDF at 200 records to control file size. const rows = records . map ( r => [ ... ]);
No limit — CSV can handle thousands of rows efficiently.
Common Use Cases
Set date range to “Hoy”
Leave all other filters empty
Export PDF for shift handover meeting
Review top failure modes chart
Area Performance Analysis
Set date range to “Última semana”
Select problematic shift (e.g., T3)
Review top failure modes
Click on specific part in “Top 10 Parts” chart
Export filtered data for 8D report
Monthly Executive Summary
Set date range to “Último mes”
Leave filters empty (all data)
Take screenshots of charts
Export PDF with summary KPIs
Include in monthly board presentation
Related Pages
Dashboard Real-time metrics and live charts
Scrap Registration Register new scrap entries
Audit Log Track who generated which reports