Crear Cotizaciones con HTML, CSS y JavaScript

Compartir

Descripción

En este tutorial aprenderás a crear un sistema simple de cotizaciones utilizando HTML, CSS y JavaScript. El cotizador permitirá agregar servicios o productos, calcular totales con IVA opcional y generar un documento listo para imprimir.

Estructura del Proyecto

cotizador/
│
├── index.html
├── assets/
│   ├── css/
│   │   └── main.css
│   ├── js/
│   │   └── main.js
│   └── img/
│       └── logo2.png
└──

1. Creando el archivo index.html

En index.html definimos la estructura básica del cotizador:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Cotizador de Servicios </title>
    <link rel="stylesheet" href="assets/css/main.css">
</head>
<body>
<div class="control-bar">
    <div class="container">
        <div class="row">
            <div class="col-2-4">
                <div class="slogan">COTIZADOR </div>

<label for="config_tax">IVA: <input type="checkbox" id="config_tax" /> </label> <label for="config_tax_rate" class="taxrelated">Tasa: <input type="text" id="config_tax_rate" value="13"/>% </label> <label for="config_note">Nota: <input type="checkbox" id="config_note" /> </label>

</div> <div class="col-4 text-right"> <a href="javascript:window.print()">Imprimir</a> </div></div></div></div><header class="row"> <div class="logoholder text-center" > <img src="assets/img/logo2.png"> </div><div class="me" > <p contenteditable> <strong>Nombre de la Empresa</strong><br> Giro de la Empresa<br> Ciudad, Departamento.<br>

</p> </div><div class="info"> <p contenteditable> Web: <a href="http://www.ejemplo.com">www.ejemplo.com</a><br> E-mail: <a href="mailto:info@ejemplo.com">info@ejemplo.com</a><br> Teléfono: +503 1234 5678<br> Facebook: NombreDeLaEmpresa </p> </div></header>

<div class="row section">

<div class="col-2"> <h1 contenteditable>COTIZACIÓN</h1> </div><div class="col-2 text-right details"> <p contenteditable> Fecha: <input type="text" class="datePicker" style="width:19mm !important;text-align:right"  /><br> Documento #: <input type="text" class="doc_num" value="100" style="width:19mm !important;text-align:right"  /><br> Vence: <input class="twoweeks" type="text" style="width:19mm !important;text-align:right" /> </p> </div><div class="col-2">

<p contenteditable class="client"> <strong>Cotizado a: </strong><br> [Nombre cliente]<br> [Nombre empresa]<br> [Dirección empresa]<br> [Tel empresa] </p> </div><div class="col-2">

<p contenteditable class="client"> <strong>Proveedor</strong><br> Nombre del Proveedor<br> Nombre de la Empresa<br> NRC: Número de Registro<br>

</p> </div></div><p class="text-center">A continuación presentamos nuestra oferta que esperamos sea de su conformidad.</p>

<div class="invoicelist-body"> <table> <thead contenteditable> <th width="60%">Descripción</th>

<th width="10%">Cant.</th> <th width="20%">Precio</th> <th class="taxrelated">IVA</th> <th width="10%">Total</th> </thead> <tbody> <tr>

<td width='60%'><a class="control removeRow" href="#">x</a> <span contenteditable>Flete desde [Origen] - [Destino]</span></td> <td class="amount"><input type="text" value="1"/></td> <td class="rate"><input type="text" value="230" /></td> <td class="tax taxrelated"></td> <td class="sum"></td> </tr> </tbody> </table> <a class="control newRow" href="#">+ Nueva fila</a> </div><div class="invoicelist-footer"> <table contenteditable> <tr class="taxrelated"> <td>Neto:</td> <td id="total_net"></td> </tr>

<tr class="taxrelated"> <td>IVA:</td> <td id="total_tax"></td> </tr> <tr> <td><strong>Total:</strong></td> <td id="total_price"></td> </tr> </table> </div>

<div class="note" contenteditable> <h2>Nota:</h2>  <p> Aquí puede escribir una nota</p> </div><div class="row section" style="margin-top:1rem"> <div class="col-1"> <table style='width:100%'> <thead contenteditable> <tr class="invoice_detail"> <th width="30%" style="text-align">Vendedor</th> <th width="30%">Validez de la oferta </th> <th width="40%">Términos y condiciones</th> </tr>  </thead> <tbody contenteditable> <tr class="invoice_detail"> <td width="30%" style="text-align">Nombre del Vendedor</td> <td width="30%">7 días </td>

<td width="40%">Pago al contado</td> </tr> </tbody> </table> </div>

</div><footer class="row"> <div class="col-1 text-center"> <p class="notaxrelated" contenteditable>El monto de la cotización incluye NO incluye IVA.</p>

</div> </footer>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <script>window.jQuery || document.write('<script src="assets/bower_components/jquery/dist/jquery.min.js"><\/script>')</script> <script src="assets/js/main.js"></script> </body> </html>

  • Barra de configuración: permite activar/desactivar el IVA y agregar notas.
  • Encabezado: muestra el logo, información del proveedor y datos del cliente.
  • Tabla de cotización: donde se agregan los servicios o productos.
  • Resumen de totales: muestra el subtotal, IVA y el total final.
  • Sección de notas: para agregar condiciones o aclaraciones.

2. Estilizando con main.css

El archivo CSS se encargará de darle estilo al cotizador. Puedes personalizar colores, tipografías y el diseño general.

/*! normalize.css v3.0.2 | MIT License | git.io/normalize */

html { font-family: sans-serif; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100% }

body { margin: 0 }

article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { display: block }

audio, canvas, progress, video { display: inline-block; vertical-align: baseline }

audio:not([controls]) { display: none; height: 0 }

[hidden], template { display: none }

a { background-color: transparent }

a:active, a:hover { outline: 0 }

abbr[title] { border-bottom: 1px dotted }

b, strong { font-weight: bold }

dfn { font-style: italic }

h1 { font-size: 2em; margin: 0.67em 0 }

mark { background: #ff0; color: #000 }

small { font-size: 80% }

sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline }

sup { top: -0.5em }

sub { bottom: -0.25em }

img { border: 0 }

svg:not(:root) { overflow: hidden }

figure { margin: 1em 40px }

hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0 }

pre { overflow: auto }

code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em }

button, input, optgroup, select, textarea { color: inherit; font: inherit; margin: 0 }

button { overflow: visible }

button, select { text-transform: none }

button, html input[type="button"], input[type="reset"], input[type="submit"] { -webkit-appearance: button; cursor: pointer }

button[disabled], html input[disabled] { cursor: default }

button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0 }

input { line-height: normal }

input[type="checkbox"], input[type="radio"] { -moz-box-sizing: border-box; box-sizing: border-box; padding: 0 }

input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { height: auto }

input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; box-sizing: content-box }

input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none }

fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em }

legend { border: 0; padding: 0 }

textarea { overflow: auto }

optgroup { font-weight: bold }

table { border-collapse: collapse; border-spacing: 0 }

td, th { padding: 0 }

html { font-size: 13px; line-height: 1.5; color: #000; background: #ddd; -moz-box-sizing: border-box; box-sizing: border-box }

body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; margin: 6rem auto 0; max-width: 800px; background: white; border: 1px solid #aaa; padding: 2rem }

.container { max-width: 800px; margin-left: auto; margin-right: auto; padding-left: 1rem; padding-right: 1rem }

*, *:before, *:after { -moz-box-sizing: inherit; box-sizing: inherit }

[contenteditable]:hover, [contenteditable]:focus, input:hover, input:focus { background: rgba(241, 76, 76, 0.1); outline: 1px solid #0984e3 }

.group:after, .row:after, .invoicelist-footer:after { content: ""; display: table; clear: both }

a { color: #0984e3; text-decoration: none }

p { margin: 0 }

.row { position: relative; display: block; width: 100%; font-size: 0 }

.col, .logoholder, .me, .info, .bank, [class*="col-"] { vertical-align: top; display: inline-block; font-size: 1rem; padding: 0 1rem; min-height: 1px }

.col-4 { width: 25% }

.col-3 { width: 33.33% }

.col-2 { width: 50% }

.col-2-4 { width: 75% }

.col-1 { width: 100% }

.text-center { text-align: center }

.text-right { text-align: right }

.control-bar { position: fixed; top: 0; left: 0; right: 0; z-index: 100; background: #0984e3; color: white; line-height: 4rem; height: 4rem }

.control-bar .slogan { font-weight: bold; font-size: 1.2rem; display: inline-block; margin-right: 2rem }

.control-bar label { margin-right: 1rem }

.control-bar a { margin: 0; padding: .5em 1em; background: rgba(255, 255, 255, 0.8) }

.control-bar a:hover { background: #fff }

.control-bar input { border: none; background: rgba(255, 255, 255, 0.2); padding: .5rem 0; max-width: 30px; text-align: center }

.control-bar input:hover { background: rgba(255, 255, 255, 0.3) }

.hidetax .taxrelated { display: none }

.showtax .notaxrelated { display: none }

.hidedate .daterelated { display: none }

.showdate .notdaterelated { display: none }

header { margin: 1rem 0 0; padding: 0 0 1rem 0; border-bottom: 3pt solid #0984e3 }

header p { font-size: .9rem }

header a { color: #000 }

.logo { margin: 0 auto; width: auto; height: auto; border: none; fill: #0984e3 }

.logoholder { width: 40% }

.me { width: 29% }

.info { width: 31% }

.bank { width: 26% }

.section { margin: 2rem 0 0 }

.smallme { display: inline-block; text-transform: uppercase; margin: 0 0 2rem 0; font-size: .9rem }

.client { margin: 0 0 3rem 0 }

.me strong { font-size: 16px; }

h1 { margin: 0; padding: 0; font-size: 2rem; color: #0984e3 }

.details input { display: inline; margin: 0 0 0 .5rem; border: none; width: 50px; min-width: 0; background: transparent; text-align: left }

.invoice_detail{ border: solid 1px #0984e3; padding:10px; height:25px; text-align:center }

.rate:before, .price:before, .sum:before, .tax:before, #total_price:before, #total_tax:before { content: '$' }

.invoicelist-body { margin: 1rem }

.invoicelist-body table { width: 100% }

.invoicelist-body thead { text-align: left; border-bottom: 1px solid #636e72; border-top: 1px solid #636e72 }

.invoicelist-body td, .invoicelist-body th { position: relative; padding: 1rem }

.invoicelist-body tr:nth-child(even) { background: #ccc }

.invoicelist-body tr:hover .removeRow { display: block }

.invoicelist-body input { display: inline; margin: 0; border: none; width: 80%; min-width: 0; background: transparent; text-align: left }

.invoicelist-body .control { display: inline-block; color: white; background: #0984e3; padding: 3px 7px; font-size: .9rem; text-transform: uppercase; cursor: pointer }

.invoicelist-body .control:hover { background: #f36464 }

.invoicelist-body .newRow { margin: .5rem 0; float: left }

.invoicelist-body .removeRow { display: none; position: absolute; top: .1rem; bottom: .1rem; left: -1.3rem; font-size: .7rem; border-radius: 3px 0 0 3px; padding: .5rem }

.invoicelist-footer { margin: 1rem }

.invoicelist-footer table { float: right; width: 25% }

.invoicelist-footer table td { padding: 1rem 2rem 0 1rem; text-align: right }

.invoicelist-footer table tr:nth-child(2) td { padding-top: 0 }

.invoicelist-footer table #total_price { font-size: 2rem; color: #0984e3 }

.note { margin: 1rem }

.hidenote .note { display: none }

.note h2 { margin: 0; font-size: 1rem; font-weight: bold }

footer { display: block; margin: 1rem 0; padding: 1rem 0 0 }

footer p { font-size: .8rem }

@media print { html { margin: 0; padding: 0; background: #fff } body { width: 100%; border: none; background: #fff; color: #000; margin: 0; padding: 0 } .control, .control-bar { display: none !important } [contenteditable]:hover, [contenteditable]:focus { outline: none } }

/*# sourceMappingURL=main.css.map */

3. Programando la lógica con main.js

Con JavaScript haremos que el cotizador sea dinámico, permitiendo:

  • Agregar/eliminar filas.
  • Calcular totales con o sin IVA.
  • Guardar el número de cotización.
const TAX_RATE_INPUT = $('#config_tax_rate');
let TAX_RATE = parseFloat(TAX_RATE_INPUT.val()) || 0; // Initialize with 0 if parsing fails
let TAX_SETTING = false;

// Document Number Handling let docNum = localStorage.getItem('doc_num') || 1; // Use default value if null docNum = Number(docNum) + 1; localStorage.setItem('doc_num', docNum);

$('body').addClass('hidetax hidenote hidedate');

function initializeDates() { const formatDate = (date) => { let month = date.getMonth() + 1; let day = date.getDate(); month = month < 10 ? "0" + month : month; day = day < 10 ? "0" + day : day; return `${day}-${month}-${date.getFullYear()}`; };

const today = new Date(); const twoWeeks = new Date(today.getTime() + 7 * 24 * 60 * 60 * 1000); // Directly calculate two weeks

$('.datePicker').val(formatDate(today)); $('.twoweeks').val(formatDate(twoWeeks)); $('.doc_num').val(docNum); }

function calculateTotal() { let totalPrice = 0; let totalTax = 0;

console.log(`CALCULATING - Tax Rate: ${TAX_RATE}`);

$('.invoicelist-body tbody tr').each(function () { const row = $(this); const rate = parseFloat(row.find('.rate input').val()) || 0; // Handle potential NaN const amount = parseFloat(row.find('.amount input').val()) || 0; // Handle potential NaN

const sum = rate * amount; const tax = (sum / (TAX_RATE + 100)) * TAX_RATE;

totalPrice += sum; totalTax += tax;

row.find('.sum').text(sum.toFixed(2)); row.find('.tax').text(tax.toFixed(2)); });

const totalNet = totalPrice - totalTax; // Calculate net total

$('#total_price').text(totalPrice.toFixed(2)); $('#total_tax').text(totalTax.toFixed(2)); $('#total_net').text(totalNet.toFixed(2)); }

const newRow = `<tr><td><a class="control removeRow" href="#">x</a><span contenteditable>Flete desde San Miguel - San Salvador</span></td><td class="amount"><input type="text" value="1"/></td><td class="rate"><input type="text" value="230" /></td><td class="tax taxrelated"></td><td class="sum"></td></tr>`;

$('.invoicelist-body').on('keyup', 'input', calculateTotal);

$('.newRow').on('click', function (e) { $('.invoicelist-body tbody').append(newRow); e.preventDefault(); calculateTotal(); });

$('body').on('click', '.removeRow', function (e) { $(this).closest('tr').remove(); e.preventDefault(); calculateTotal(); });

$('#config_note').on('change', function () { $('body').toggleClass('shownote hidenote'); });

$('#config_tax').on('change', function () { TAX_SETTING = !TAX_SETTING; $('body').toggleClass('showtax hidetax'); });

TAX_RATE_INPUT.on('keyup', function () { TAX_RATE = parseFloat($(this).val()) || 0; // Parse with default value TAX_RATE = Math.max(0, Math.min(TAX_RATE, 100)); // Constrain between 0 and 100 console.log(`Changed tax rate to: ${TAX_RATE}`); calculateTotal(); });

$('#config_date').on('change', function () { $('body').toggleClass('hidedate showdate'); });

initializeDates(); calculateTotal();

4. Funcionalidades clave

  • Activar/desactivar IVA con un simple checkbox.
  • Agregar y eliminar productos/servicios de la cotización.
  • Imprimir cotización directamente desde el navegador.
  • Persistencia de número de cotización usando localStorage.

Instalación


  • La instalación de esta aplicación para crear cotizaciones con HTML, CSS y JavaScript es realmente sencilla, basta con descargar los archivos fuentes del sistema en tu ordenador
  • Descomprimir el archivo
  • Abrir el archivo principal (index.html) y empezar a editar los datos de tu cotización.

Crear una cotización en el sistema

  • Para crear una cotización con este script basta con cambiar el logotipo, para ello ve a la carpeta llamada assets/img/  y busca el archivo logo1.png y reemplázalo por el logotipo de tu empresa. Luego todos los demás campos son editables desde la interfaz web del script.

DEMO DESCARGAR

Si estas buscando una solución mas profesional para controlar tu empresa y llevar el control de tu inventario, a continuación tengo una  solución de pago:

Sistema de cotizaciones

 

Obed Alvarado

Sobre el autor

Artículo redactado por Obed Alvarado. Comparto experiencias prácticas sobre PHP, MySQL, aplicaciones web, instalación en hosting y herramientas para negocios.

Comentarios