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.

Acrylitec’s data layer is defined in gestion/models.py using the Django ORM. There are 8 models across the gestion app, covering customers, quotes, materials, products, cost configuration, and sales — both through the legacy quotation flow and the newer point-of-sale (POS) cart flow.

Clientes

Stores customer contact information. Used by both Cotizaciones (legacy quotes) and Ventas (POS sales). Database table: clientes
id_cliente
AutoField
required
Primary key. Auto-incremented integer assigned by the database.
nombre
CharField(100)
Full name of the customer. Maximum 100 characters.
telefono
CharField(100)
Phone number. Stored as a string to support formatting variations. Maximum 100 characters.
email
CharField(100)
Email address. Maximum 100 characters.
direccion
TextField
Street or delivery address. Unrestricted length text field.

Cotizaciones

Represents a single-product quotation. Stores all inputs needed for pricing and automatically computes monto_total on every save via calcular_monto(). Database table: cotizaciones
id_cotizacion
AutoField
required
Primary key. Auto-incremented integer.
id_cliente
ForeignKey → Clientes
The customer this quote is for. Uses DO_NOTHING on delete — deleting a client does not cascade to their quotes. Database column: id_cliente.
id_producto
ForeignKey → Productos
The product being quoted. Uses DO_NOTHING on delete. Database column: id_producto.
id_material
ForeignKey → Materiales
The acrylic sheet material to be used. Uses DO_NOTHING on delete. Database column: id_material.
largo_pza
DecimalField(10, 2)
Length of the piece in centimetres. Verbose name: "Largo (cm)".
ancho_pza
DecimalField(10, 2)
Width of the piece in centimetres. Verbose name: "Ancho (cm)".
espesor_mm
IntegerField
Thickness of the acrylic sheet in millimetres. Used to look up the matching TabuladorCostos entry. Verbose name: "Espesor (mm)".
porcentaje_utilidad
IntegerField
Profit margin percentage applied on top of material cost. Defaults to 40. Verbose name: "Utilidad (%)".
minutos_lazer
IntegerField
Estimated laser-cutting time in minutes. Multiplied by ConfiguracionPrecios.tarifa_laser_minuto to compute laser cost.
monto_total
DecimalField(10, 2)
Final quoted price. This field is auto-calculated — do not set it manually. It is written by save() via calcular_monto().
fecha
DateField
Date the quote was created or issued.

Methods

calcular_monto() Returns the computed quote total as a Decimal. Implements the full pricing formula (see Pricing Engine):
  1. If id_producto.precio_fijo is set, returns that value immediately (no further calculation).
  2. Otherwise, computes area → looks up TabuladorCostos → applies profit margin → adds laser cost, then rounds to two decimal places via .quantize(Decimal('0.01')).
save(*args, **kwargs) Overrides the default Django save() to call calcular_monto() and write the result into monto_total before persisting the record.
def save(self, *args, **kwargs):
    self.monto_total = self.calcular_monto()
    super().save(*args, **kwargs)

Materiales

Represents a sheet of acrylic stock. Tracks physical dimensions and inventory levels. Referenced by both quotes and sale line items. Database table: materiales
id_material
AutoField
required
Primary key. Auto-incremented integer.
descripcion
CharField(100)
Human-readable description of the material (e.g., "Acrílico transparente 3mm"). Maximum 100 characters.
largo
DecimalField(10, 2)
Sheet length in centimetres.
ancho
DecimalField(10, 2)
Sheet width in centimetres.
stock_actual
IntegerField
required
Current quantity of sheets in stock. Decremented automatically when a POS sale is confirmed.
stock_minimo
IntegerField
required
Minimum stock threshold. When stock_actual <= stock_minimo, the system raises a low-stock alert in the POS flow.
The eliminar_material view guards against deleting a material that is still referenced by active Cotizaciones or DetalleVenta records. A deletion attempt will display an error message instead of removing the row.

Productos

Represents a product template (e.g., a keychain, a sign, a frame). Products carry a default profit margin and an optional fixed price that short-circuits the pricing formula entirely. Database table: productos
id_producto
AutoField
required
Primary key. Auto-incremented integer.
nombre
CharField(100)
Product name displayed in the UI (e.g., "Llavero"). Maximum 100 characters.
detalle
CharField(100)
Short description or variant detail (e.g., "Rectangular 5x10"). Maximum 100 characters.
precio_fijo
DecimalField(10, 2)
When populated, this value is returned directly by the pricing engine, bypassing all area/material/laser calculations. Verbose name: "Precio Fijo (Opcional)".
porcentaje_utilidad
IntegerField
Per-product default profit margin percentage. Defaults to 40. Can be overridden per-quote via Cotizaciones.porcentaje_utilidad.
foto
CharField(255)
Relative file path under MEDIA_ROOT where the product photo is stored (e.g., productos/abc123.jpg). Saved and deleted via Django’s default_storage backend.

TabuladorCostos

A lookup table that maps acrylic thickness values to a cost factor used in the area-based pricing formula. Each row represents one thickness configuration. Database table: tabulador_costos
id_tabulador
AutoField
required
Primary key. Auto-incremented integer.
espesor_mm
IntegerField
required
Thickness in millimetres (e.g., 3, 5, 8). The pricing engine queries this field with an exact match against Cotizaciones.espesor_mm.
factor_costo
DecimalField(10, 4)
required
Cost factor in pesos per square metre (m²). Multiplied by the piece area to produce costo_material. Four decimal places for precision.
If no TabuladorCostos row exists for a given espesor_mm, the pricing engine defaults factor_costo to 0.00, resulting in a zero material cost. Ensure every thickness used in quotes has a corresponding tabulador entry.

Ventas

Represents a confirmed sale. Supports two flows:
  • Legacy flow — linked 1-to-1 with a Cotizaciones record via id_cotizacion.
  • POS flow — linked directly to a Clientes record via id_cliente, with one or more DetalleVenta line items.
Database table: ventas
id_venta
AutoField
required
Primary key. Auto-incremented integer.
id_cotizacion
ForeignKey → Cotizaciones
Links to the originating quote in the legacy single-product flow. Uses DO_NOTHING on delete. Database column: id_cotizacion.
id_cliente
ForeignKey → Clientes
Direct client link used in the POS cart flow (not via a quote). Uses SET_NULL on delete so that deleting a client does not erase the sale record. Database column: id_cliente_directo.
monto_abonado
DecimalField(10, 2)
Amount already paid or deposited by the customer. Defaults to 0.00. Verbose name: "Anticipo/Abono".
estatus
CharField(20)
Current status of the sale. Defaults to "pendiente". Valid choices:
ValueDisplay
pendientePendiente
en_produccionEn producción
pagadaPagada
entregadaEntregada
fecha_entrega
DateField
Promised delivery date. Set manually by staff via the status-update form.
fecha_venta
DateField
Date the sale was created. Set automatically via auto_now_add=True.

Properties

saldo_restante A computed property (not stored in the database) that returns the outstanding balance owed by the customer:
@property
def saldo_restante(self):
    if self.id_cotizacion:
        total = self.id_cotizacion.monto_total          # legacy flow
    else:
        total = sum(detalle.subtotal for detalle in self.detalles.all())  # POS flow
    return (total or Decimal('0.00')) - self.monto_abonado

ConfiguracionPrecios

A singleton model — only one row ever exists (primary key 1). Stores system-wide pricing parameters configurable by administrators. Database table: configuracion_precios
tarifa_laser_minuto
DecimalField(8, 2)
Laser cutting rate charged per minute, in pesos. Defaults to 15.00. Verbose name: "Tarifa láser por minuto ($)".
actualizado
DateTimeField
Timestamp of the last update. Managed automatically via auto_now=True — updated on every save().

Class Methods

get_config() Returns the singleton configuration object, creating it with default values if it does not yet exist:
@classmethod
def get_config(cls):
    obj, _ = cls.objects.get_or_create(pk=1)
    return obj
Always access pricing configuration through ConfiguracionPrecios.get_config() rather than querying the table directly. This ensures the singleton is created on first access rather than raising a DoesNotExist error.

DetalleVenta

Represents a single line item in a POS-flow sale. Multiple DetalleVenta records belong to one Ventas record via the detalles reverse relation. Database table: detalle_venta
id_detalle
AutoField
required
Primary key. Auto-incremented integer.
id_venta
ForeignKey → Ventas
required
The parent sale record. Uses CASCADE on delete — removing a sale also removes all its line items. related_name='detalles' enables venta.detalles.all().
id_producto
ForeignKey → Productos
required
The product on this line. Uses CASCADE on delete.
id_material
ForeignKey → Materiales
required
The material used for this line item. Uses CASCADE on delete.
cantidad
IntegerField
Number of units ordered for this line item. Defaults to 1. Each unit decrements Materiales.stock_actual by 1 when the sale is confirmed.
largo_pza
DecimalField(10, 2)
required
Length of the piece in centimetres, captured at the time of sale.
ancho_pza
DecimalField(10, 2)
required
Width of the piece in centimetres, captured at the time of sale.
espesor_mm
DecimalField(5, 2)
required
Thickness in millimetres, stored as a decimal on the line item (contrast with Cotizaciones.espesor_mm which is an IntegerField).
minutos_lazer
IntegerField
Laser-cutting minutes for this line item. Defaults to 0.
subtotal
DecimalField(10, 2)
required
Pre-calculated price for this line item. Computed in the browser via the pricing engine and submitted with the cart payload. Summed by Ventas.saldo_restante in the POS flow.

Relationships Diagram

The two sale flows connect the models differently, so it helps to think of them separately. Legacy quotation flow: A Clientes record is linked to a Cotizaciones record, which in turn references one Productos entry and one Materiales entry. When the quote is confirmed as a sale, a Ventas record is created with id_cotizacion populated. The total comes directly from Cotizaciones.monto_total. POS cart flow: A Clientes record is linked directly to a Ventas record via id_cliente (stored as id_cliente_directo). One or more DetalleVenta rows are attached to that sale via the detalles reverse relation, each carrying its own Productos and Materiales foreign keys. The sale total is the sum of all DetalleVenta.subtotal values. Shared lookup tables: Both flows rely on TabuladorCostos (consulted at pricing time by espesor_mm) and ConfiguracionPrecios (the singleton that provides the laser rate). Neither of these tables holds foreign keys back into the sale chain — they are pure reference data consumed by the pricing engine.

Build docs developers (and LLMs) love