Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/YonAnn99/Acrylitec/llms.txt

Use this file to discover all available pages before exploring further.

The pricing engine is the computational core of Acrylitec. It lives in two places: the calcular_monto() instance method on the Cotizaciones model (called automatically on every save()), and the _calcular_monto() helper function in gestion/views.py (called by the AJAX endpoint to deliver real-time price previews to the browser). Both implement the same formula — the view helper additionally returns a full breakdown dict so the UI can display each cost component individually.

Formula Steps

The engine evaluates steps in order and short-circuits early if a fixed price is configured on the product.

1. Fixed Price Check

Before any calculation, the engine checks whether the selected product carries a precio_fijo:
if producto_id:
    prod = Productos.objects.filter(pk=producto_id).first()
    if prod and prod.precio_fijo:
        return {
            'area': largo * ancho,
            'area_m2': (largo * ancho) / Decimal('10000'),
            'costo_material': Decimal('0.00'),
            'utilidad': Decimal('0.00'),
            'costo_laser': Decimal('0.00'),
            'monto_total': prod.precio_fijo,
        }
If precio_fijo is set, it is returned immediately as monto_total. Material cost, profit, and laser cost are all reported as 0.00. No further steps are executed.

2. Area Calculation

The piece dimensions (in centimetres) are converted to square metres:
area_cm2 = largo * ancho
area_m2 = area_cm2 / Decimal('10000')
Both largo and ancho are coerced to Decimal from the raw POST values to guarantee precise arithmetic throughout the pipeline.

3. Tabulador Lookup

The engine queries TabuladorCostos for an exact match on espesor_mm. If no matching row is found, factor_costo falls back to Decimal('0.00'):
try:
    tabulador = TabuladorCostos.objects.get(espesor_mm=espesor_mm)
    factor = tabulador.factor_costo
except TabuladorCostos.DoesNotExist:
    factor = Decimal('0.00')

4. Material Cost

The area (in m²) is multiplied by the tabulador factor (cost per m²) to produce the raw material cost:
costo_material = area_m2 * factor

5. Profit Margin

The profit margin is applied as a percentage on top of costo_material. The default is 40 %:
utilidad = costo_material * (porcentaje_utilidad / Decimal('100'))
porcentaje_utilidad may be customised per-product via Productos.porcentaje_utilidad or overridden per-quote via Cotizaciones.porcentaje_utilidad.

6. Laser Cost

The laser cutting cost is calculated by multiplying elapsed minutes by the current rate from the singleton ConfiguracionPrecios:
costo_laser = minutos_laser * _get_tarifa_laser()
_get_tarifa_laser() calls ConfiguracionPrecios.get_config().tarifa_laser_minuto, which defaults to 15.00 pesos per minute.

7. Total

The three cost components are summed and rounded to two decimal places using ROUND_HALF_UP:
monto_total = (costo_material + utilidad + costo_laser).quantize(
    Decimal('0.01'), rounding=ROUND_HALF_UP
)

Full Source Code

Below is the complete _calcular_monto() helper as it appears in gestion/views.py:
def _calcular_monto(largo, ancho, espesor_mm, porcentaje_utilidad, minutos_laser=0, producto_id=None):
    largo = Decimal(str(largo or 0))
    ancho = Decimal(str(ancho or 0))
    minutos_laser = Decimal(str(minutos_laser or 0))
    porcentaje_utilidad = Decimal(str(porcentaje_utilidad or 40))

    area_cm2 = largo * ancho
    area_m2 = area_cm2 / Decimal('10000')

    if producto_id:
            prod = Productos.objects.filter(pk=producto_id).first()
            if prod and prod.precio_fijo:
                return {
                    'area': largo * ancho,
                    'area_m2': (largo * ancho) / Decimal('10000'),
                    'costo_material': Decimal('0.00'),
                    'utilidad': Decimal('0.00'),
                    'costo_laser': Decimal('0.00'),
                    'monto_total': prod.precio_fijo,
                }

    try:
        tabulador = TabuladorCostos.objects.get(espesor_mm=espesor_mm)
        factor = tabulador.factor_costo
    except TabuladorCostos.DoesNotExist:
        factor = Decimal('0.00')

    costo_material = area_m2 * factor
    utilidad = costo_material * (porcentaje_utilidad / Decimal('100'))
    costo_laser = minutos_laser * _get_tarifa_laser()
    
    monto_total = (costo_material + utilidad + costo_laser).quantize(
        Decimal('0.01'), rounding=ROUND_HALF_UP
    )

    return {
        'area': area_cm2,
        'area_m2': area_m2,
        'costo_material': costo_material.quantize(Decimal('0.01')),
        'utilidad': utilidad.quantize(Decimal('0.01')),
        'costo_laser': costo_laser.quantize(Decimal('0.01')),
        'monto_total': monto_total,
    }

Return Value

When a fixed price does not apply, _calcular_monto() returns a dictionary with the following keys:
KeyTypeDescription
areaDecimalRaw area in cm² (largo × ancho)
area_m2DecimalArea converted to m² (area_cm2 / 10000)
costo_materialDecimalMaterial cost rounded to 2 decimal places
utilidadDecimalProfit margin amount rounded to 2 decimal places
costo_laserDecimalLaser cutting cost rounded to 2 decimal places
monto_totalDecimalFinal total rounded to 2 decimal places via ROUND_HALF_UP
When the fixed price path is taken, costo_material, utilidad, and costo_laser are all Decimal('0.00'), and monto_total equals producto.precio_fijo.

Example Calculation

The following example walks through a typical keychain quote with no fixed price. Inputs:
ParameterValue
ProductLlavero (no precio_fijo set)
porcentaje_utilidad40
largo_pza10 cm
ancho_pza5 cm
espesor_mm3 mm
factor_costo (from TabuladorCostos)850.0000
minutos_lazer5
tarifa_laser_minuto15.00
Step-by-step:
  1. Fixed price checkprecio_fijo is None → continue.
  2. Area:
    area_cm2 = 10 × 5 = 50 cm²
    area_m2  = 50 / 10000 = 0.005 m²
    
  3. Tabulador lookup — row for espesor_mm = 3 found; factor_costo = 850.0000.
  4. Material cost:
    costo_material = 0.005 × 850.0000 = 4.25
    
  5. Profit margin:
    utilidad = 4.25 × (40 / 100) = 1.70
    
  6. Laser cost:
    costo_laser = 5 × 15.00 = 75.00
    
  7. Total:
    monto_total = (4.25 + 1.70 + 75.00).quantize('0.01', ROUND_HALF_UP)
                = 80.95
    
Result: $80.95 The dict returned would be:
{
    'area':           Decimal('50'),
    'area_m2':        Decimal('0.005'),
    'costo_material': Decimal('4.25'),
    'utilidad':       Decimal('1.70'),
    'costo_laser':    Decimal('75.00'),
    'monto_total':    Decimal('80.95'),
}

AJAX Calculation Endpoint

The quotation form calls POST /ajax/calcular/ after any input change to display a live price preview without saving the quote. The view (calcular_precio_ajax) calls _calcular_monto() and returns the breakdown as JSON so the browser can populate each cost field individually. For full request and response schema details, see the AJAX API Reference.
If espesor_mm has no matching TabuladorCostos row, the engine silently sets factor_costo = Decimal('0.00'). This causes costo_material and utilidad to both be 0.00, meaning the quote total will only reflect laser time — severely undercharging the customer. Always configure a TabuladorCostos entry for every acrylic thickness your business uses before creating quotes.

Build docs developers (and LLMs) love