""" Part detail screen for the NEXUS AUTOPARTS console application. Shows full part information (OEM number, name, group, category, etc.) with a table of aftermarket alternatives. Number keys navigate to the comparator screen; F4 shows cross-references; F6 lists compatible vehicles. """ from console.core.screens import Screen from console.core.keybindings import Key from console.config import APP_NAME, VERSION from console.utils.formatting import format_currency, truncate, quality_bar # Footer labels _FOOTER = [ ("#", "Comparar"), ("F4", "Cross-Ref"), ("F6", "Vehiculos"), ("ESC", "Atras"), ] class ParteDetalleScreen(Screen): """Detail view for a single OEM part with aftermarket alternatives.""" def __init__(self): super().__init__(name="parte_detalle", title="Detalle de Parte") self._part = None self._alternatives = [] self._selected_alt = 0 # ------------------------------------------------------------------ # Data loading # ------------------------------------------------------------------ def _load(self, context, db): """Load part and alternatives from context['part_id'].""" part_id = context.get("part_id") if part_id is None: self._part = None self._alternatives = [] return self._part = db.get_part(part_id) self._alternatives = db.get_alternatives(part_id) if self._part else [] # ------------------------------------------------------------------ # Helpers # ------------------------------------------------------------------ @staticmethod def _format_warranty(months): """Format warranty months as 'X meses' or '──' if missing.""" if months is None: return "──" return f"{months} meses" @staticmethod def _format_weight(kg): """Format weight in kilograms or '──' if missing.""" if kg is None: return "──" return f"{kg} kg" @staticmethod def _format_discontinued(flag): """Format the is_discontinued flag as Si/No.""" if flag: return "Si" return "No" # ------------------------------------------------------------------ # Render # ------------------------------------------------------------------ def render(self, context, db, renderer): self._load(context, db) # Header renderer.draw_header( f" {APP_NAME} v{VERSION}", " DETALLE DE PARTE ", ) if self._part is None: renderer.draw_text(5, 4, "Parte no encontrada", "error") renderer.draw_footer([("ESC", "Atras")]) return p = self._part # ── Top section: part detail fields ── fields = [ ("Numero OEM", p.get("oem_part_number", "")), ("Nombre", p.get("name", "")), ("Nombre (ES)", p.get("name_es", "") or ""), ("Grupo", p.get("group_name_es") or p.get("group_name", "")), ("Categoria", p.get("category_name_es") or p.get("category_name", "")), ("Descripcion", p.get("description_es") or p.get("description", "") or ""), ("Material", p.get("material", "") or "──"), ("Peso", self._format_weight(p.get("weight_kg"))), ("Descontinuada", self._format_discontinued(p.get("is_discontinued"))), ] renderer.draw_detail(fields, title="INFORMACION DE LA PARTE") # ── Bottom section: alternatives table ── h, w = renderer.get_size() # Calculate where the detail section ends (title=3 rows + fields + 1 gap) table_start_row = 3 + 3 + len(fields) + 1 if self._alternatives: # Draw section title section_title = "\u2500\u2500 ALTERNATIVAS AFTERMARKET " + "\u2500" * max(w - 32, 4) renderer.draw_text(table_start_row, 2, section_title, "title") table_start_row += 1 headers = ["FABRICANTE", "NUMERO", "CALIDAD", "PRECIO", "GARANTIA"] widths = [14, 16, 10, 10, 10] rows = [] for alt in self._alternatives: rows.append(( truncate(alt.get("manufacturer_name", ""), 14), truncate(alt.get("part_number", ""), 16), (alt.get("quality_tier", "") or "").capitalize(), format_currency(alt.get("price_usd")), self._format_warranty(alt.get("warranty_months")), )) renderer.draw_table( headers, rows, widths, selected_row=self._selected_alt, ) else: renderer.draw_text( table_start_row, 4, "No hay alternativas aftermarket registradas", "info", ) renderer.draw_footer(_FOOTER) # ------------------------------------------------------------------ # Key handling # ------------------------------------------------------------------ def on_key(self, key, context, db, renderer, nav): # ESC: go back if key == Key.ESCAPE: return "back" # Arrow navigation for alternatives if key == Key.UP: if self._selected_alt > 0: self._selected_alt -= 1 return None if key == Key.DOWN: if self._alternatives and self._selected_alt < len(self._alternatives) - 1: self._selected_alt += 1 return None # Number keys (1-9): navigate to comparador for the selected alternative if 49 <= key <= 57: # '1'..'9' idx = key - 49 # 0-based if self._alternatives and 0 <= idx < len(self._alternatives): part_id = context.get("part_id") return ( "comparador", {"part_id": part_id, "selected_alt_index": idx}, "Comparador", ) return None # ENTER: navigate to comparador for the currently highlighted alternative if key == Key.ENTER: if self._alternatives and 0 <= self._selected_alt < len(self._alternatives): part_id = context.get("part_id") return ( "comparador", {"part_id": part_id, "selected_alt_index": self._selected_alt}, "Comparador", ) return None # F4: show cross-references if key == Key.F4: part_id = context.get("part_id") if part_id is None: return None xrefs = db.get_cross_references(part_id) if not xrefs: renderer.show_message("No hay cross-references para esta parte", "info") return None # Build message text grouped by reference type lines = [] by_type = {} for xr in xrefs: rtype = xr.get("reference_type", "other") or "other" by_type.setdefault(rtype, []).append( xr.get("cross_reference_number", "") ) for rtype, numbers in by_type.items(): lines.append(f"{rtype.capitalize()}: {', '.join(numbers)}") msg = "CROSS-REFERENCES\n" + "\n".join(lines) renderer.show_message(msg, "info") return None # F6: show vehicles that use this part if key == Key.F6: part_id = context.get("part_id") if part_id is None: return None vehicles = db.get_vehicles_for_part(part_id) if not vehicles: renderer.show_message( "No hay vehiculos registrados para esta parte", "info" ) return None # Build message with vehicle list (limit to avoid overflow) lines = [] for v in vehicles[:10]: brand = v.get("brand", "") model = v.get("model", "") year = v.get("year", "") engine = v.get("engine", "") line = f"{brand} {model} {year}" if engine: line += f" ({engine})" position = v.get("position", "") if position: line += f" - {position}" lines.append(line) if len(vehicles) > 10: lines.append(f"... y {len(vehicles) - 10} mas") msg = "VEHICULOS COMPATIBLES\n" + "\n".join(lines) renderer.show_message(msg, "info") return None return None