{
  "id": 151762,
  "title": "I built a real quote system with .NET 10 and Blazor Server — and this is what I learned",
  "url": "https://urgent.news/2026/08/04/construi-un-sistema-de-cotizaciones-real-con-net-10-y-blazor-server-y",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-08-04T23:30:37.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/kiddtobal/construi-un-sistema-de-cotizaciones-real-con-net-10-y-blazor-server-y-esto-fue-lo-que-aprendi-4il3"
  },
  "original_language": "es",
  "account": "A few weeks ago, I started a personal project with a simple goal: to automate the quoting process of a glassworks company that, until then, had been preparing each budget by hand. Repeated calculations, different formats each time, zero ordered history. It ended up being much more than I initially imagined: a complete budget management system, with a product catalog, multiple prices per supplier, margin calculation, export to PDF with the actual format already used by the business, and deployment as a service on a local network.\n\nStack: .NET 10 + Blazor Server — server-rendered interactivity, without the need for a separate API\nEntity Framework Core + SQLite\nMudBlazor for the interface\nQuestPDF for generating the final document\n\nIt wasn't a tutorial followed to the letter. It was about solving a real problem at a time. Here are three moments that I thought were interesting to share.\n\nThinking well about deletion rules, not just the model\n\nWhen modeling relationships in Entity Framework Core, it's easy to focus only on which field references which other, and leave OnDelete in its default behavior. But each relationship has a business question behind it: what has to happen if I delete this?\n\nIn this project, a Client with associated budgets cannot be deleted — we have to avoid losing commercial history by accident. On the other hand, an ItemPresupuesto doesn't make sense to exist without its parent Presupuesto, so if the budget is deleted, its items go with it.\n\nBlock 1 — Relationships and deletion rules (VidrieriaContext.cs)\n\nentity.HasOne(p => p.Cliente)\n.WithMany(c => c.Presupuestos)\n.HasForeignKey(p => p.ClienteId)\n.OnDelete(DeleteBehavior.Restrict);\n\nentity.HasOne(p => p.Cotizador)\n.WithMany()\n.HasForeignKey(p => p.CotizadorId)\n.OnDelete(DeleteBehavior.Restrict);\n\nentity.HasOne(i => i.Presupuesto)\n.WithMany(p => p.Items)\n.HasForeignKey(i => i.PresupuestoId)\n.OnDelete(DeleteBehavior.Cascade);\n\nSame tool (OnDelete), two opposite decisions — and both correct, depending on what each relationship represents in the business.\n\nA catalog selector that scales without becoming illegible\n\nThe system has categories with subcategories, products within each one, and each product with several reference prices depending on the supplier. With few loaded data, a simple tree works well — but as the catalog grows, navigating by force of clicks becomes tedious and increases the risk of choosing the wrong item.\n\nThe solution was a component with a search that filters the entire tree in memory and auto-expands the branches that match:\n\nBlock 2 — Recursive filtering (SelectorProductoCatalogo.razor)\n\nprivate bool CategoriaVisible(Categoria categoria) =>\n!HayBusqueda ||\nCategoriaNombreCoincide(categoria) ||\nproductosPorCategoria[categoria.Id].Any(ProductoCoincide) ||\ncategoriasPorPadre[categoria.Id].Any(CategoriaVisible);\n\nA category is visible if its name matches, if it has a product that matches, or if any of its subcategories is visible (recursively).\n\nThat's enough for the tree to be reduced to only the relevant branches when writing in the search — without touching the database on each keystroke, everything solved on data already loaded in memory with ILookup.\n\nThe bug that didn't throw any error\n\nThis was the most difficult to diagnose. A button stopped responding — without exceptions, without anything in the browser console, without anything on the server. The Blazor circuit was alive, other buttons worked.\n\nThe cause: some global components (notification and dialog providers) lived in the root layout of the application, outside the tree of any page with interactive render mode. They were rendered only once, in static mode, and never connected to the real circuit.\n\nBlock 3 — The fix of the render mode that left buttons \"dead\"\n\nThe lesson I take away: in Blazor Web App, the render mode is not inherited between \"brother\" components in the tree — it only propagates to the descendants of the one that declares it.\n\nA small detail, but one that, without understanding it, can make you lose hours.\n\nClosing\n\nNone of these problems appeared in a tutorial. They appeared using the real system, with real data, with a real user giving me feedback.\n\nThat was the most valuable part of the project: not just writing code that works, but understanding why something didn't work before fixing it.\n\nIf you're interested in the architecture, some specific technical detail, or just want to chat about the project, comments are open.",
  "summary": "Hace unas semanas empecé un proyecto personal con un objetivo simple: automatizar el proceso de cotización de una vidriería que hasta entonces armaba cada presupuesto a mano. Cálculos repetidos, formato distinto cada vez, cero historial ordenado. Terminó siendo bastante más de lo que imaginé al principio: un sistema completo de gestión de presupuestos, con catálogo de productos, múltiples precios…",
  "key_points": [],
  "editors_take": "Desarrollar un sistema de cotizaciones con .NET 10 y Blazor Server requiere considerar reglas de borrado y renderizado de componentes para evitar errores y asegurar la interacción dinámica.",
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}