Skip to main content

Overview

The Cotizador (Quotation Tool) allows sales advisors to generate professional PDF quotes for clients showing property details, pricing breakdowns, and payment plan simulations.

Accessing the Quotation Tool

Route: /cotizador The cotizador is available to all commercial roles and displays a form-based interface for configuring quotes.

Quotation Workflow

1

Access Cotizador

Navigate to Cotizador from the Ventas menu or go directly to /cotizador.The system loads:
props: {
  proyectos: Array,     // Active projects
  clientes: Array,      // All clients
  empleado: Object,     // Current advisor
  inmuebles: Array      // Available properties (filtered)
}
2

Select Project

Choose a project from the dropdown:
proyectoId = ref('')
This filters available properties to only those in the selected project:
const inmueblesFiltrados = computed(() => {
  return props.inmuebles.filter(i => 
    String(i.id_proyecto) === String(proyectoId.value)
  )
})
3

Select Client

Choose the client for this quote:
clienteId = ref('')
const cliente = computed(() => 
  props.clientes.find(c => String(c.documento) === String(clienteId.value))
)
Note: Clients must be created first at /clientes/create if not in the system.
4

Select Property (Inmueble)

Choose a specific property from the filtered list:
inmuebleId = ref('')
const inmueble = computed(() => 
  inmueblesFiltrados.value.find(i => String(i.id) === String(inmuebleId.value))
)
Properties include:
  • Apartamentos with tipoApartamento details
  • Locales with commercial specifications
  • Associated parking (if included)
5

Select Payment Term (Plazo)

Choose the down payment term in months:
const plazosDisponibles = computed(() => {
  if (!proyecto.value) return []
  return Array.from(
    { length: proyecto.value.plazo_cuota_inicial_meses }, 
    (_, i) => i + 1
  )
})
Example: If project allows 12 months, options are [1, 2, 3, ..., 12].
6

Review Quote Summary

The form displays a live preview showing:Property Details:
  • Property type and unit number
  • Area (constructed/private)
  • Rooms and bathrooms
  • Current price
Payment Simulation:
const porcentajeCuotaInicial = proyecto.value.porcentaje_cuota_inicial_min || 30
const valorSeparacion = proyecto.value.valor_min_separacion
const valorInmueble = inmueble.value.valor_final || inmueble.value.valor_total

// Down payment
const cuotaInicial = valorInmueble * (porcentajeCuotaInicial / 100)

// Monthly installment (excluding separation)
const montoCuotas = cuotaInicial - valorSeparacion
const cuotaMensual = montoCuotas / plazo.value

// Remaining balance
const valorRestante = valorInmueble - cuotaInicial
7

Generate PDF

Click Generar PDF to create the quotation document.The system uses jsPDF with autoTable plugin:
import jsPDF from 'jspdf'
import autoTable from 'jspdf-autotable'

async function generarPDF() {
  // Validation
  if (!proyecto.value || !cliente.value || !inmueble.value || !plazo.value) {
    return alert('Complete all fields')
  }
  
  // Generate PDF with project branding
  const doc = new jsPDF()
  // ... PDF generation logic
}

Quote PDF Structure

The generated PDF includes:

Header Section

┌─────────────────────────────────────┐
│  [Project Logo/Name]                │
│  COTIZACIÓN INMOBILIARIA            │
│  Fecha: [Current Date]              │
│  Asesor: [Advisor Name]             │
└─────────────────────────────────────┘

Client Information

CLIENTE
────────────────────────────────────
Nombre:     [Cliente.nombre]
Documento:  [Cliente.documento]
Tipo:       [Cliente.tipoDocumento]
Teléfono:   [Cliente.telefono]
Email:      [Cliente.email]

Property Details

INMUEBLE
────────────────────────────────────
Proyecto:        [Proyecto.nombre]
Ubicación:       [Proyecto.ubicacion]
Tipo:            Apartamento / Local
Unidad:          [Apartamento.numero]
Torre:           [Torre.nombre_torre]
Piso:            [Piso.numero_piso]

Características:
  Área Construida:  [tipoApartamento.area_construida] m²
  Área Privada:     [tipoApartamento.area_privada] m²
  Habitaciones:     [tipoApartamento.cantidad_habitaciones]
  Baños:            [tipoApartamento.cantidad_banos]

Pricing Breakdown

DETALLE DE PRECIOS
────────────────────────────────────────────────────
Valor Inmueble:              $XXX,XXX,XXX
Parqueadero (si aplica):     $XX,XXX,XXX
                             ─────────────
VALOR TOTAL:                 $XXX,XXX,XXX

ESQUEMA DE PAGO
────────────────────────────────────────────────────
Separación:                  $X,XXX,XXX
Cuota Inicial (XX%):         $XX,XXX,XXX
Plazo Cuota Inicial:         [plazo] meses
Cuota Mensual:               $X,XXX,XXX

Saldo Restante (Mes XX+1):   $XXX,XXX,XXX

Payment Schedule Table

┌──────┬──────────────┬──────────────┬──────────────┐
│ Mes  │ Concepto     │ Valor        │ Saldo        │
├──────┼──────────────┼──────────────┼──────────────┤
│  0   │ Separación   │ $X,XXX,XXX   │ $XXX,XXX,XXX │
│  1   │ Cuota 1/X    │ $X,XXX,XXX   │ $XXX,XXX,XXX │
│  2   │ Cuota 2/X    │ $X,XXX,XXX   │ $XXX,XXX,XXX │
│ ...  │ ...          │ ...          │ ...          │
│  X   │ Cuota X/X    │ $X,XXX,XXX   │ $XXX,XXX,XXX │
│ X+1  │ Saldo Final  │ $XXX,XXX,XXX │ $0           │
└──────┴──────────────┴──────────────┴──────────────┘
────────────────────────────────────────────────────
NOTAS IMPORTANTES:
• Esta cotización tiene validez de 15 días calendario.
• Los valores están sujetos a cambios según política de precios.
• La separación debe realizarse dentro del plazo establecido.
• Consulte términos y condiciones completos.

Contacto:
[Project Contact Information]

Quote Calculation Logic

The cotizador performs real-time calculations based on project parameters:

Base Pricing

// Property base price (from PriceEngine)
const valorBase = inmueble.valor_final || inmueble.valor_total

// Parking (if included with apartment)
const valorParqueadero = inmueble.parqueadero?.precio || 0

// Total
const valorTotal = valorBase + valorParqueadero

Down Payment Calculation

// Minimum down payment percentage (from project)
const porcentajeMinimo = proyecto.porcentaje_cuota_inicial_min // e.g., 25%

// Down payment amount
const cuotaInicial = valorTotal * (porcentajeMinimo / 100)

// Separation deposit
const valorSeparacion = proyecto.valor_min_separacion

// Amount to be paid in installments
const montoCuotas = cuotaInicial - valorSeparacion

Installment Schedule

// Selected term
const plazoMeses = parseInt(plazo.value)

// Monthly installment (simple division)
const cuotaMensual = montoCuotas / plazoMeses

// Payment schedule array
const cronograma = [
  { mes: 0, concepto: 'Separación', valor: valorSeparacion },
  ...Array.from({ length: plazoMeses }, (_, i) => ({
    mes: i + 1,
    concepto: `Cuota ${i + 1}/${plazoMeses}`,
    valor: cuotaMensual
  })),
  { 
    mes: plazoMeses + 1, 
    concepto: 'Saldo Restante', 
    valor: valorTotal - cuotaInicial 
  }
]

Balance Calculation

// Running balance for table
let saldoPendiente = valorTotal

cronograma.forEach(item => {
  item.saldo = saldoPendiente
  saldoPendiente -= item.valor
})

Currency Formatting

All monetary values use Colombian Peso formatting:
function formatMoney(v) {
  return Number(v || 0).toLocaleString('es-CO', {
    style: 'currency',
    currency: 'COP',
    maximumFractionDigits: 0
  })
}

// Output: $123.456.789

Property Type Handling

Apartments

Apartments require tipoApartamento relationship:
if (inmueble.tipo === 'apartamento') {
  if (!inmueble.tipoApartamento) {
    alert('Este apartamento no tiene un tipo definido.')
    return
  }
  
  // Use type details
  const area = inmueble.tipoApartamento.area_construida
  const habitaciones = inmueble.tipoApartamento.cantidad_habitaciones
  // ...
}

Locals

Commercial locals use direct properties:
if (inmueble.tipo === 'local') {
  const area = inmueble.area_total
  const descripcion = inmueble.descripcion
  // ...
}

Validation Rules

Before generating PDF:
if (!proyecto.value) return alert('Debe seleccionar un proyecto.')
if (!cliente.value) return alert('Debe seleccionar un cliente.')
if (!inmueble.value) return alert('Debe seleccionar un inmueble.')
if (!plazo.value) return alert('Debe seleccionar un plazo.')
if (inmueble.tipo === 'apartamento' && !inmueble.tipoApartamento) {
  alert('Este apartamento no tiene un tipo definido.')
  return
}
// Plazo must be within project limits
const maxPlazo = proyecto.plazo_cuota_inicial_meses
if (plazo.value > maxPlazo) {
  alert(`Plazo máximo: ${maxPlazo} meses`)
  return
}

Integration with Sales Flow

Quotes are read-only documents for client presentation. To convert a quote to an actual transaction:
1

Generate Quote

Use cotizador to create PDF and present to client.
2

Client Approval

Client reviews and approves terms.
3

Create Separación or Venta

Navigate to /ventas/create and manually enter the transaction:
  • Pre-select the property using ?inmueble_tipo=apartamento&inmueble_id=X
  • Enter payment terms matching the quote
  • Process the sale/reservation
Tip: You can link directly from catalog to create form:
<Link :href="`/ventas/create?inmueble_tipo=${tipo}&inmueble_id=${id}`">
  Crear Venta
</Link>

Common Issues

”Este apartamento no tiene un tipo definido”

Cause: Apartment missing id_tipo_apartamento relationship. Fix:
  1. Edit apartment at /apartamentos/{id}/edit
  2. Assign a valid apartment type
  3. Return to cotizador

Empty Inmuebles Dropdown

Cause: No available properties in selected project. Fix:
  1. Verify project has properties with state “Disponible”
  2. Check project is activo = true
  3. Ensure properties exist at /apartamentos or /locales

Incorrect Pricing

Cause: PriceEngine hasn’t recalculated after recent sales. Fix:
  1. Administrator should trigger recalculation
  2. Pricing updates automatically after each sale
  3. Verify pricing policies at /politicas-precio-proyecto

Next Steps

Processing Sales

Learn how to convert quotes into actual sales and separations.

Technical Reference

  • Controller: app/Http/Controllers/Ventas/CotizadorWebController.php
  • View: resources/js/Pages/Ventas/Cotizador/Index.vue
  • Route: routes/web.php:398-399
  • Libraries: jspdf, jspdf-autotable

Build docs developers (and LLMs) love