Cambios implementados:
🎨 Diseño Visual Mejorado: Portada más espaciada - Espaciado superior aumentado para mejor presentación Cuadros con encabezados separados - Cada sección tiene un header coloreado profesional Bordes redondeados - Esquinas suavizadas para un look más moderno Separadores internos - Líneas delgadas entre filas para mejor legibilidad Etiquetas diferenciadas - Labels en gris claro, valores en negrita oscura 🚗 Cuadro de Vehículo: Header azul (#2563eb) con "🚗 INFORMACIÓN DEL VEHÍCULO" Contenido blanco con bordes redondeados Layout limpio: etiqueta arriba, valor abajo por campo 📄 Cuadro de Inspección: Header verde (#16a34a) con "📄 INFORMACIÓN DE LA INSPECCIÓN" Nombre de mecánico eliminado - Solo código de operario por privacidad Campos: Nº Pedido, OR Nº, Cód. Operario, Fecha 📊 Resumen de Evaluación: Título "📊 RESUMEN DE EVALUACIÓN" centrado Grid de 4 métricas: Puntuación, Porcentaje, Estado, Ítems Críticos Borde dinámico según resultado (verde/amarillo/rojo) Estado textual: EXCELENTE/ACEPTABLE/DEFICIENTE Separadores internos para cada métrica Versión Backend actualizada a 1.0.75
This commit is contained in:
@@ -204,7 +204,7 @@ def send_completed_inspection_to_n8n(inspection, db):
|
||||
# No lanzamos excepción para no interrumpir el flujo normal
|
||||
|
||||
|
||||
BACKEND_VERSION = "1.0.74"
|
||||
BACKEND_VERSION = "1.0.75"
|
||||
app = FastAPI(title="Checklist Inteligente API", version=BACKEND_VERSION)
|
||||
|
||||
# S3/MinIO configuration
|
||||
@@ -1374,58 +1374,98 @@ def generate_inspection_pdf(inspection_id: int, db: Session) -> str:
|
||||
checklist = db.query(models.Checklist).filter(models.Checklist.id == inspection.checklist_id).first()
|
||||
|
||||
# ===== PORTADA =====
|
||||
elements.append(Spacer(1, 10*mm))
|
||||
elements.append(Spacer(1, 15*mm))
|
||||
|
||||
# Título con diseño moderno
|
||||
elements.append(Paragraph("📋 INFORME DE INSPECCIÓN VEHICULAR", title_style))
|
||||
elements.append(Paragraph(f"N° {inspection.id}", subtitle_style))
|
||||
elements.append(Spacer(1, 5*mm))
|
||||
elements.append(Spacer(1, 10*mm))
|
||||
|
||||
# Cuadro de información del vehículo
|
||||
vehicle_data = [
|
||||
[Paragraph("<b>🚗 INFORMACIÓN DEL VEHÍCULO</b>", info_style)],
|
||||
[Table([
|
||||
[Paragraph("<b>Placa:</b>", small_style), Paragraph(f"{inspection.vehicle_plate}", info_style)],
|
||||
[Paragraph("<b>Marca:</b>", small_style), Paragraph(f"{inspection.vehicle_brand or 'N/A'}", info_style)],
|
||||
[Paragraph("<b>Modelo:</b>", small_style), Paragraph(f"{inspection.vehicle_model or 'N/A'}", info_style)],
|
||||
[Paragraph("<b>Kilometraje:</b>", small_style), Paragraph(f"{inspection.vehicle_km or 'N/A'} km", info_style)]
|
||||
], colWidths=[30*mm, 50*mm])]
|
||||
]
|
||||
# Estilo para etiquetas de información
|
||||
label_style = ParagraphStyle(
|
||||
'LabelStyle',
|
||||
parent=styles['Normal'],
|
||||
fontSize=9,
|
||||
textColor=colors.HexColor('#64748b'),
|
||||
spaceAfter=2
|
||||
)
|
||||
|
||||
vehicle_table = Table(vehicle_data, colWidths=[85*mm])
|
||||
vehicle_table.setStyle(TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#dbeafe')),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.HexColor('#1e40af')),
|
||||
value_style = ParagraphStyle(
|
||||
'ValueStyle',
|
||||
parent=styles['Normal'],
|
||||
fontSize=11,
|
||||
textColor=colors.HexColor('#1e293b'),
|
||||
fontName='Helvetica-Bold'
|
||||
)
|
||||
|
||||
# Cuadro de información del vehículo con diseño moderno
|
||||
vehicle_header = Table(
|
||||
[[Paragraph("<b>🚗 INFORMACIÓN DEL VEHÍCULO</b>", ParagraphStyle('veh_header', parent=info_style, fontSize=12, textColor=colors.white))]],
|
||||
colWidths=[85*mm]
|
||||
)
|
||||
vehicle_header.setStyle(TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, -1), colors.HexColor('#2563eb')),
|
||||
('PADDING', (0, 0), (-1, -1), 10),
|
||||
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 11),
|
||||
('PADDING', (0, 0), (-1, -1), 8),
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.white),
|
||||
('BOX', (0, 0), (-1, -1), 1, colors.HexColor('#93c5fd')),
|
||||
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||
('ROUNDEDCORNERS', [6, 6, 0, 0]),
|
||||
]))
|
||||
|
||||
# Cuadro de información del cliente e inspección
|
||||
inspection_data = [
|
||||
[Paragraph("<b>👤 INFORMACIÓN DEL CLIENTE</b>", info_style)],
|
||||
[Table([
|
||||
[Paragraph("<b>Nº Pedido:</b>", small_style), Paragraph(f"{inspection.order_number or 'N/A'}", info_style)],
|
||||
[Paragraph("<b>OR N°:</b>", small_style), Paragraph(f"{inspection.or_number or 'N/A'}", info_style)],
|
||||
[Paragraph("<b>Mecánico:</b>", small_style), Paragraph(f"{mechanic.full_name if mechanic else 'N/A'}", info_style)],
|
||||
[Paragraph("<b>Cód. Operario:</b>", small_style), Paragraph(f"{inspection.mechanic_employee_code or 'N/A'}", info_style)],
|
||||
[Paragraph("<b>Fecha:</b>", small_style), Paragraph(f"{inspection.started_at.strftime('%d/%m/%Y %H:%M') if inspection.started_at else 'N/A'}", info_style)]
|
||||
], colWidths=[30*mm, 50*mm])]
|
||||
]
|
||||
|
||||
inspection_info_table = Table(inspection_data, colWidths=[85*mm])
|
||||
inspection_info_table.setStyle(TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#dcfce7')),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.HexColor('#15803d')),
|
||||
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 11),
|
||||
('PADDING', (0, 0), (-1, -1), 8),
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.white),
|
||||
('BOX', (0, 0), (-1, -1), 1, colors.HexColor('#86efac')),
|
||||
vehicle_content = Table([
|
||||
[Paragraph("Placa", label_style), Paragraph(f"{inspection.vehicle_plate}", value_style)],
|
||||
[Paragraph("Marca", label_style), Paragraph(f"{inspection.vehicle_brand or 'N/A'}", value_style)],
|
||||
[Paragraph("Modelo", label_style), Paragraph(f"{inspection.vehicle_model or 'N/A'}", value_style)],
|
||||
[Paragraph("Kilometraje", label_style), Paragraph(f"{inspection.vehicle_km or 'N/A'} km", value_style)]
|
||||
], colWidths=[25*mm, 60*mm])
|
||||
vehicle_content.setStyle(TableStyle([
|
||||
('PADDING', (0, 0), (-1, -1), 10),
|
||||
('BACKGROUND', (0, 0), (-1, -1), colors.white),
|
||||
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||
('LINEBELOW', (0, 0), (-1, -2), 0.5, colors.HexColor('#e2e8f0')),
|
||||
]))
|
||||
|
||||
vehicle_table = Table(
|
||||
[[vehicle_header], [vehicle_content]],
|
||||
colWidths=[85*mm]
|
||||
)
|
||||
vehicle_table.setStyle(TableStyle([
|
||||
('BOX', (0, 0), (-1, -1), 1.5, colors.HexColor('#2563eb')),
|
||||
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||
('ROUNDEDCORNERS', [6, 6, 6, 6]),
|
||||
]))
|
||||
|
||||
# Cuadro de información del cliente e inspección (sin nombre de mecánico por privacidad)
|
||||
client_header = Table(
|
||||
[[Paragraph("<b>📄 INFORMACIÓN DE LA INSPECCIÓN</b>", ParagraphStyle('client_header', parent=info_style, fontSize=12, textColor=colors.white))]],
|
||||
colWidths=[85*mm]
|
||||
)
|
||||
client_header.setStyle(TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, -1), colors.HexColor('#16a34a')),
|
||||
('PADDING', (0, 0), (-1, -1), 10),
|
||||
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
||||
('ROUNDEDCORNERS', [6, 6, 0, 0]),
|
||||
]))
|
||||
|
||||
client_content = Table([
|
||||
[Paragraph("Nº Pedido", label_style), Paragraph(f"{inspection.order_number or 'N/A'}", value_style)],
|
||||
[Paragraph("OR N°", label_style), Paragraph(f"{inspection.or_number or 'N/A'}", value_style)],
|
||||
[Paragraph("Cód. Operario", label_style), Paragraph(f"{inspection.mechanic_employee_code or 'N/A'}", value_style)],
|
||||
[Paragraph("Fecha", label_style), Paragraph(f"{inspection.started_at.strftime('%d/%m/%Y %H:%M') if inspection.started_at else 'N/A'}", value_style)]
|
||||
], colWidths=[25*mm, 60*mm])
|
||||
client_content.setStyle(TableStyle([
|
||||
('PADDING', (0, 0), (-1, -1), 10),
|
||||
('BACKGROUND', (0, 0), (-1, -1), colors.white),
|
||||
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||
('LINEBELOW', (0, 0), (-1, -2), 0.5, colors.HexColor('#e2e8f0')),
|
||||
]))
|
||||
|
||||
inspection_info_table = Table(
|
||||
[[client_header], [client_content]],
|
||||
colWidths=[85*mm]
|
||||
)
|
||||
inspection_info_table.setStyle(TableStyle([
|
||||
('BOX', (0, 0), (-1, -1), 1.5, colors.HexColor('#16a34a')),
|
||||
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||
('ROUNDEDCORNERS', [6, 6, 6, 6]),
|
||||
]))
|
||||
|
||||
# Tabla con ambos cuadros lado a lado
|
||||
@@ -1437,28 +1477,48 @@ def generate_inspection_pdf(inspection_id: int, db: Session) -> str:
|
||||
elements.append(info_table)
|
||||
elements.append(Spacer(1, 8*mm))
|
||||
|
||||
# Resumen de puntuación
|
||||
# Resumen de puntuación con diseño mejorado
|
||||
percentage = inspection.percentage
|
||||
score_color = colors.HexColor('#22c55e') if percentage >= 80 else colors.HexColor('#eab308') if percentage >= 60 else colors.HexColor('#ef4444')
|
||||
score_label = "EXCELENTE" if percentage >= 80 else "ACEPTABLE" if percentage >= 60 else "DEFICIENTE"
|
||||
|
||||
score_data = [
|
||||
# Título de resumen
|
||||
score_title = Table(
|
||||
[[Paragraph("<b>📊 RESUMEN DE EVALUACIÓN</b>", ParagraphStyle('score_title', parent=info_style, fontSize=14, textColor=colors.HexColor('#1e293b'), alignment=TA_CENTER))]],
|
||||
colWidths=[180*mm]
|
||||
)
|
||||
score_title.setStyle(TableStyle([
|
||||
('PADDING', (0, 0), (-1, -1), 8),
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
]))
|
||||
elements.append(score_title)
|
||||
elements.append(Spacer(1, 3*mm))
|
||||
|
||||
# Cuadro de métricas con diseño moderno
|
||||
metric_label = ParagraphStyle('metric_label', parent=small_style, fontSize=9, textColor=colors.HexColor('#64748b'), alignment=TA_CENTER)
|
||||
metric_value = ParagraphStyle('metric_value', parent=info_style, fontSize=16, fontName='Helvetica-Bold', alignment=TA_CENTER)
|
||||
|
||||
metrics_data = [
|
||||
[Paragraph("Puntuación", metric_label), Paragraph("Porcentaje", metric_label), Paragraph("Estado", metric_label), Paragraph("Ítems Críticos", metric_label)],
|
||||
[
|
||||
Paragraph("<b>Puntuación Total</b>", info_style),
|
||||
Paragraph(f"<b>{inspection.score} / {inspection.max_score}</b>", info_style),
|
||||
Paragraph(f"<b>{percentage:.1f}%</b>", info_style),
|
||||
Paragraph(f"<b>⚠️ {inspection.flagged_items_count} Críticos</b>", info_style)
|
||||
Paragraph(f"<b>{inspection.score}</b> / {inspection.max_score}", metric_value),
|
||||
Paragraph(f"<b>{percentage:.1f}%</b>", metric_value),
|
||||
Paragraph(f"<b>{score_label}</b>", ParagraphStyle('status_value', parent=metric_value, textColor=score_color)),
|
||||
Paragraph(f"<b>{inspection.flagged_items_count}</b>", metric_value)
|
||||
]
|
||||
]
|
||||
|
||||
score_table = Table(score_data, colWidths=[45*mm, 45*mm, 45*mm, 45*mm])
|
||||
score_table = Table(metrics_data, colWidths=[45*mm, 45*mm, 45*mm, 45*mm])
|
||||
score_table.setStyle(TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, -1), score_color),
|
||||
('TEXTCOLOR', (0, 0), (-1, -1), colors.white),
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#f8fafc')),
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.white),
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
('FONTNAME', (0, 0), (-1, -1), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, -1), 12),
|
||||
('PADDING', (0, 0), (-1, -1), 10),
|
||||
('BOX', (0, 0), (-1, -1), 2, colors.white),
|
||||
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
||||
('PADDING', (0, 0), (-1, -1), 12),
|
||||
('BOX', (0, 0), (-1, -1), 2, score_color),
|
||||
('LINEABOVE', (0, 1), (-1, 1), 1.5, score_color),
|
||||
('GRID', (0, 0), (-1, -1), 0.5, colors.HexColor('#e2e8f0')),
|
||||
('ROUNDEDCORNERS', [8, 8, 8, 8]),
|
||||
]))
|
||||
elements.append(score_table)
|
||||
elements.append(PageBreak())
|
||||
|
||||
Reference in New Issue
Block a user