Saltar al pie de página
USANDO IRONPDF

Visor de PDF para ASP.NET: Cómo mostrar PDFs en .NET 10

El Problema con la Generación Manual de Cotizaciones

Inicio de IronPDF Cada hora que un representante de ventas pasa copiando elementos de línea desde un CRM a una plantilla de Word es una hora que no están vendiendo. Entre un equipo de diez representantes con veinte oportunidades activas cada uno, ese tiempo se acumula, y el resultado ni siquiera es confiable. Los niveles de precios se aplican incorrectamente, los campos de descuento se dejan a las tarifas del último trimestre, y el lenguaje de términos de una versión antigua de la plantilla se cuela en un documento que se envía a un cliente sin revisión.

El problema del cronograma complica el problema de precisión. Un prospecto que solicita una cotización un martes por la tarde y la recibe el miércoles por la noche ha pasado un día preguntándose si el trato es real. Para cuando llega el archivo PDF, su atención puede haberse desplazado a un competidor que respondió más rápido.

Las herramientas CPQ existentes resuelven partes de esto, pero a precios de empresa y cronogramas de implementación que no se ajustan a equipos que solo necesitan un archivo PDF limpio a partir de sus datos de oportunidad existentes. Enviar por correo electrónico una hoja de cálculo o una lista de precios en texto simple en lugar de una propuesta formateada muestra al prospecto que el proceso de ventas de la empresa es improvisado. Y cuando el prospecto solicita revisiones, la confusión de versiones se multiplica, múltiples iteraciones flotan como archivos adjuntos de correo electrónico sin una única fuente de verdad adjunta al registro de CRM.

Los escenarios varían: una empresa de software B2B obteniendo cotizaciones de licencia de Salesforce, una firma de manufactura generando propuestas de partes y precios a partir de elementos de línea de ERP, una agencia produciendo documentos de alcance de proyecto a partir de estimaciones internas, un distribuidor mayorista creando ofertas con descuentos por volumen para compradores al por menor. La fricción subyacente es la misma en todos los casos.

La Solución: Generar Cotizaciones Directamente de Datos de Oportunidad

IronPDF es una poderosa biblioteca .NET PDF que permite a las aplicaciones .NET generar cotizaciones y propuestas PDF con marca directamente de datos de oportunidad de CRM. Cuando un representante marca un trato como listo para cotizar o hace clic en un botón en una herramienta interna, la aplicación extrae elementos de línea, precios, detalles del cliente y términos del CRM o base de datos, completa una plantilla HTML o una cadena de contenido HTML, y ChromePdfRenderer produce el PDF de C# a partir de ese HTML.

La cotización se envía por correo electrónico al prospecto y el documento enviado se registra nuevamente en el registro del CRM automáticamente, sin formateo manual, sin plantillas de Word, sin tarifas SaaS por documento. La renderización se ejecuta dentro de la aplicación .NET existente como un único paquete NuGet. La plantilla pertenece y es versionada por tu equipo, actualizada en un solo lugar, y reflejada en cada futura cotización sin redistribuir archivos al equipo de ventas.

Cómo funciona en la práctica

1. Un Disparador Inicia la Cotización

El flujo comienza cuando un representante hace clic en "Generar Cotización" en la interfaz de usuario del CRM, una oportunidad entra en una etapa específica del flujo automáticamente, o una plataforma de automatización externa realiza una llamada API al punto de acceso para cotización. Los tres puntos de entrada alimentan el mismo flujo de generación, el mecanismo de disparo no cambia lo que sucede a continuación.

La aplicación consulta el CRM o base de datos para todo lo que la cotización requiere: nombre de la compañía prospecto, nombre y correo electrónico de contacto, artículos de línea con SKUs, descripciones, cantidades, precios unitarios, porcentajes de descuento aplicados y totales de línea; el subtotal, cálculo de impuestos y total general; términos de pago, período de validez de la cotización y cualquier nota personalizada que el representante haya agregado a la oportunidad.

2. La Plantilla HTML Define el Diseño de la Cotización

La plantilla maneja la estructura visual completa de la cotización: logotipo de la empresa y encabezado, un bloque "Preparado para" con los detalles del prospecto, una tabla de elementos de línea con columnas para descripción, cantidad, precio unitario, descuento y total de línea, un resumen de subtotal y total general, una sección de términos y condiciones, y un pie de página con el nombre, título y contacto directo del representante.

La tabla de elementos de línea se construye iterando sobre los elementos de línea de la oportunidad en el manejador de C# y construyendo las filas HTML antes de pasar la plantilla completa a IronPDF. Esto mantiene la lógica en la capa de aplicación en lugar de en la propia plantilla.

Plantilla de Archivo HTML de Ejemplo

Plantilla HTML de ejemplo

3. ChromePdfRenderer Produce el Documento PDF de la Cotización

El poderoso motor de renderización de IronPDF hace que la conversión de HTML a PDF sea sencilla, a menudo se realiza en solo unas pocas líneas de código.

using IronPdf;

var renderer = new ChromePdfRenderer();

renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;

renderer.RenderingOptions.MarginTop = 20;

renderer.RenderingOptions.MarginBottom = 20;

var lineItemRows = string.Concat(opportunity.LineItems.Select(item => $@"
    <tr>
        <td>{item.Description}</td>
        <td style='text-align:center'>{item.Quantity}</td>
        <td style='text-align:right'>{item.UnitPrice:C}</td>
        <td style='text-align:center'>{item.DiscountPct}%</td>
        <td style='text-align:right'>{item.LineTotal:C}</td>
    </tr>"));

string html = $@"
    <h1>Quote — {opportunity.ProspectCompany}</h1>
    <p>Prepared for: {opportunity.ContactName} &lt;{opportunity.ContactEmail}&gt;</p>
    <p>Valid until: {opportunity.QuoteExpiry:MMMM d, yyyy} &nbsp;|&nbsp; Terms: {opportunity.PaymentTerms}</p>
    <table border='1' cellpadding='6' width='100%'>
        <thead><tr><th>Description</th><th>Qty</th><th>Unit Price</th><th>Discount</th><th>Total</th></tr></thead>
        <tbody>{lineItemRows}</tbody>
    </table>
    <p style='text-align:right'><strong>Subtotal:</strong> {opportunity.Subtotal:C}</p>
    <p style='text-align:right'><strong>Tax ({opportunity.TaxRatePct}%):</strong> {opportunity.TaxAmount:C}</p>
    <p style='text-align:right'><strong>Grand Total:</strong> {opportunity.GrandTotal:C}</p>
    <hr/><p style='font-size:11px'>{opportunity.TermsAndConditions}</p>";

PdfDocument quote = renderer.RenderHtmlAsPdf(html);
using IronPdf;

var renderer = new ChromePdfRenderer();

renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;

renderer.RenderingOptions.MarginTop = 20;

renderer.RenderingOptions.MarginBottom = 20;

var lineItemRows = string.Concat(opportunity.LineItems.Select(item => $@"
    <tr>
        <td>{item.Description}</td>
        <td style='text-align:center'>{item.Quantity}</td>
        <td style='text-align:right'>{item.UnitPrice:C}</td>
        <td style='text-align:center'>{item.DiscountPct}%</td>
        <td style='text-align:right'>{item.LineTotal:C}</td>
    </tr>"));

string html = $@"
    <h1>Quote — {opportunity.ProspectCompany}</h1>
    <p>Prepared for: {opportunity.ContactName} &lt;{opportunity.ContactEmail}&gt;</p>
    <p>Valid until: {opportunity.QuoteExpiry:MMMM d, yyyy} &nbsp;|&nbsp; Terms: {opportunity.PaymentTerms}</p>
    <table border='1' cellpadding='6' width='100%'>
        <thead><tr><th>Description</th><th>Qty</th><th>Unit Price</th><th>Discount</th><th>Total</th></tr></thead>
        <tbody>{lineItemRows}</tbody>
    </table>
    <p style='text-align:right'><strong>Subtotal:</strong> {opportunity.Subtotal:C}</p>
    <p style='text-align:right'><strong>Tax ({opportunity.TaxRatePct}%):</strong> {opportunity.TaxAmount:C}</p>
    <p style='text-align:right'><strong>Grand Total:</strong> {opportunity.GrandTotal:C}</p>
    <hr/><p style='font-size:11px'>{opportunity.TermsAndConditions}</p>";

PdfDocument quote = renderer.RenderHtmlAsPdf(html);
Imports IronPdf

Dim renderer As New ChromePdfRenderer()

renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter

renderer.RenderingOptions.MarginTop = 20

renderer.RenderingOptions.MarginBottom = 20

Dim lineItemRows As String = String.Concat(opportunity.LineItems.Select(Function(item) $"
    <tr>
        <td>{item.Description}</td>
        <td style='text-align:center'>{item.Quantity}</td>
        <td style='text-align:right'>{item.UnitPrice:C}</td>
        <td style='text-align:center'>{item.DiscountPct}%</td>
        <td style='text-align:right'>{item.LineTotal:C}</td>
    </tr>"))

Dim html As String = $"
    <h1>Quote — {opportunity.ProspectCompany}</h1>
    <p>Prepared for: {opportunity.ContactName} &lt;{opportunity.ContactEmail}&gt;</p>
    <p>Valid until: {opportunity.QuoteExpiry:MMMM d, yyyy} &nbsp;|&nbsp; Terms: {opportunity.PaymentTerms}</p>
    <table border='1' cellpadding='6' width='100%'>
        <thead><tr><th>Description</th><th>Qty</th><th>Unit Price</th><th>Discount</th><th>Total</th></tr></thead>
        <tbody>{lineItemRows}</tbody>
    </table>
    <p style='text-align:right'><strong>Subtotal:</strong> {opportunity.Subtotal:C}</p>
    <p style='text-align:right'><strong>Tax ({opportunity.TaxRatePct}%):</strong> {opportunity.TaxAmount:C}</p>
    <p style='text-align:right'><strong>Grand Total:</strong> {opportunity.GrandTotal:C}</p>
    <hr/><p style='font-size:11px'>{opportunity.TermsAndConditions}</p>"

Dim quote As PdfDocument = renderer.RenderHtmlAsPdf(html)
$vbLabelText   $csharpLabel

Documento PDF Generado

Ejemplo de salida de IronPDF

ConsejosIncrusta el logotipo de tu empresa y cualquier archivo de fuente como URIs de datos base64 en la plantilla, o establece un BaseUrlPath como el segundo parámetro en RenderHtmlAsPdf() para que IronPDF resuelva rutas de activos relativas independientemente de dónde se despliegue la aplicación.

4. Cotización Entregada y Registrada

using System.Net.Mail;
using System.IO;

// Save versioned copy to document storage
string outputPath = $"quotes/{opportunity.Id}/v{opportunity.QuoteVersion}.pdf";

quote.SaveAs(outputPath);

// Email to prospect
using var stream = new MemoryStream(quote.BinaryData);
using var attachment = new Attachment(stream,
    $"Quote-{opportunity.ProspectCompany}-v{opportunity.QuoteVersion}.pdf",
    "application/pdf");

var message = new MailMessage("sales@yourcompany.com", opportunity.ContactEmail)
{
    Subject = $"Your Quote from {opportunity.RepName} — {opportunity.ProspectCompany}",
    Body = $"Hi {opportunity.ContactName},\n\nPlease find your quote attached. It is valid until {opportunity.QuoteExpiry:MMMM d, yyyy}.\n\n{opportunity.RepName}"
};

message.Attachments.Add(attachment);

using var smtp = new SmtpClient("smtp.yourprovider.com");

await smtp.SendMailAsync(message);

// Log reference back to CRM
await _crmService.LogQuoteSentAsync(opportunity.Id, outputPath, DateTime.UtcNow);
using System.Net.Mail;
using System.IO;

// Save versioned copy to document storage
string outputPath = $"quotes/{opportunity.Id}/v{opportunity.QuoteVersion}.pdf";

quote.SaveAs(outputPath);

// Email to prospect
using var stream = new MemoryStream(quote.BinaryData);
using var attachment = new Attachment(stream,
    $"Quote-{opportunity.ProspectCompany}-v{opportunity.QuoteVersion}.pdf",
    "application/pdf");

var message = new MailMessage("sales@yourcompany.com", opportunity.ContactEmail)
{
    Subject = $"Your Quote from {opportunity.RepName} — {opportunity.ProspectCompany}",
    Body = $"Hi {opportunity.ContactName},\n\nPlease find your quote attached. It is valid until {opportunity.QuoteExpiry:MMMM d, yyyy}.\n\n{opportunity.RepName}"
};

message.Attachments.Add(attachment);

using var smtp = new SmtpClient("smtp.yourprovider.com");

await smtp.SendMailAsync(message);

// Log reference back to CRM
await _crmService.LogQuoteSentAsync(opportunity.Id, outputPath, DateTime.UtcNow);
Imports System.Net.Mail
Imports System.IO

' Save versioned copy to document storage
Dim outputPath As String = $"quotes/{opportunity.Id}/v{opportunity.QuoteVersion}.pdf"

quote.SaveAs(outputPath)

' Email to prospect
Using stream As New MemoryStream(quote.BinaryData)
    Using attachment As New Attachment(stream, $"Quote-{opportunity.ProspectCompany}-v{opportunity.QuoteVersion}.pdf", "application/pdf")
        Dim message As New MailMessage("sales@yourcompany.com", opportunity.ContactEmail) With {
            .Subject = $"Your Quote from {opportunity.RepName} — {opportunity.ProspectCompany}",
            .Body = $"Hi {opportunity.ContactName}," & vbCrLf & vbCrLf & $"Please find your quote attached. It is valid until {opportunity.QuoteExpiry:MMMM d, yyyy}." & vbCrLf & vbCrLf & $"{opportunity.RepName}"
        }

        message.Attachments.Add(attachment)

        Using smtp As New SmtpClient("smtp.yourprovider.com")
            Await smtp.SendMailAsync(message)
        End Using
    End Using
End Using

' Log reference back to CRM
Await _crmService.LogQuoteSentAsync(opportunity.Id, outputPath, DateTime.UtcNow)
$vbLabelText   $csharpLabel

Ejemplo de Correo Enviado con PDF Adjunto

Correo electrónico con cotización PDF adjunta Un número de versión en ambas, la ruta del archivo y el nombre del archivo, le da a cada revisión su propio registro permanente. Cuando el prospecto solicita cambios, el representante actualiza la oportunidad en el CRM, la aplicación regenera con una versión incrementada y la nueva cotización se envía, con la historia completa retenida en el almacenamiento de documentos y registrada en el registro del CRM.

Beneficios reales

Velocidad al prospecto. Una cotización se genera y envía por correo electrónico en segundos después de que el representante la inicia. No hay retraso en el formato, ningún documento sentado en una carpeta de borradores, y no hay demora entre la decisión del representante de cotizar y el prospecto recibiéndolo.

Precisión de precios. Los elementos de línea, niveles de descuento y totales generales se extraen directamente de la oportunidad del CRM. No hay un paso de transcripción manual ni riesgo de que un representante aplique los precios del trimestre pasado o el horario de descuento incorrecto.

Consistencia de marca. Cada cotización que la empresa envía lleva el mismo logotipo, diseño, lenguaje de términos y pie de página, sin importar qué representante la haya generado o desde qué etapa del flujo. No hay plantillas de Word descontroladas flotando en computadoras portátiles individuales.

Seguimiento de versión. Cada cotización generada se almacena con su ID de oportunidad, número de versión y una marca de tiempo de cuándo fue enviada. El registro del CRM le da a los gerentes de ventas una visibilidad completa de lo que fue cotizado, a quién y cuándo, sin tener que buscar a través de hilos de correo electrónico.

Reutilización de plantilla. Cuando el equipo legal actualiza el lenguaje de términos o el equipo de marca refresca el logotipo, un archivo HTML cambia y cada cotización futura lo refleja. No hay reenvío de plantillas de Word al equipo de ventas.

No hay costos por documento. La renderización se ejecuta en el proceso dentro de la aplicación .NET. No hay suscripción CPQ, no hay medición de API de generación de documentos, y no hay un modelo de costos que escale contra el volumen de tratos.

Cierre

La brecha entre los datos de oportunidad del CRM y un PDF profesional en la bandeja de entrada del prospecto es más pequeña de lo que parece, es una plantilla, una llamada de renderización y un envío de correo electrónico, todo activado por una sola acción en el flujo de trabajo de ventas. Eliminar el paso de formato manual no solo ahorra tiempo; elimina una categoría de errores que pueden costar tratos.

Ese flujo es sencillo de construir y mantener dentro de una aplicación .NET existente. IronPDF maneja todo el ciclo de vida de generación de PDF en C# en ironpdf.com, desde la renderización de plantillas HTML hasta guardar, transmitir y manipular documentos. La prueba gratuita de 30 días te brinda el tiempo suficiente para construir y probar un flujo completo de generación de cotizaciones contra tus propios datos de CRM y flujo de trabajo de ventas.

Curtis Chau
Escritor Técnico

Curtis Chau tiene una licenciatura en Ciencias de la Computación (Carleton University) y se especializa en el desarrollo front-end con experiencia en Node.js, TypeScript, JavaScript y React. Apasionado por crear interfaces de usuario intuitivas y estéticamente agradables, disfruta trabajando con frameworks modernos y creando manuales bien ...

Leer más

Equipo de soporte de Iron

Estamos disponibles online las 24 horas, 5 días a la semana.
Chat
Email
Llámame