Skip to content

Condición de Venta - Documentación Técnica Backend

⚠️ DOCUMENTACIÓN RETROSPECTIVA - Generada a partir de código implementado el 2026-02-11

Módulo: Ventas Feature: Condición de Venta (Formas de Pago) Tipo: Resource Estado: Implementado Fecha: 2026-02-11


Descripción General

Módulo que gestiona las diferentes condiciones de venta o formas de pago disponibles en el sistema de facturación. Permite consultar y actualizar los recargos asociados a cada condición de pago (Contado, Cuenta Corriente, Tarjeta).


Arquitectura Implementada

Ubicación de Componentes

CapaArchivoDescripción
Routebackend/condvta.phpEndpoint legacy (pre-Slim Framework)
Controllercontroller/modulo-venta/CondicionVentaController.phpControlador HTTP
Modelmodels/modulo-venta/CondicionVenta.phpAcceso a datos
DTOResources/Venta/CondicionVenta.phpData Transfer Object
EnumResources/Venta/Enums/CondicionVenta.phpConstantes de condiciones
Migrationmigrations/tenancy/20240823200727_new_table_condvta.phpEsquema de base de datos
Seedmigrations/seeds/tenancy/Condvta.phpDatos de referencia

Patrón de Arquitectura

Legacy Endpoint (condvta.php) → Controller → Model → Database

⚠️ NOTA: Esta implementación usa el patrón legacy (pre-Slim Framework). No sigue la arquitectura 5-layer DDD estándar del sistema.

Características:

  • No usa RouteCollectorProxy de Slim
  • No tiene capa Service (lógica directa en Model)
  • No tiene capa Domain
  • No tiene ValidationMiddleware
  • Autenticación via JWT legacy

API Endpoints

Endpoint Legacy

Base Path: /backend/condvta.php

Autenticación: JWT Token (legacy JwtHandler)

Headers Requeridos:

  • Authorization: Bearer {token}
  • Content-Type: application/json

GET - Listar Condiciones de Venta

URL: /backend/condvta.php

Método: GET

Descripción: Obtiene todas las condiciones de venta disponibles.

Request Body: {}

Respuesta Exitosa (200):

json
{
  "status": 200,
  "message": "Datos recibidos correctamente.",
  "data": [
    {
      "id": 1,
      "nombre": "Contado",
      "defecto": true,
      "es_tarjeta": false,
      "porcentaje_recargo": 0.000
    },
    {
      "id": 2,
      "nombre": "Ctas. Ctes",
      "defecto": false,
      "es_tarjeta": false,
      "porcentaje_recargo": 0.000
    },
    {
      "id": 3,
      "nombre": "Tarjeta",
      "defecto": false,
      "es_tarjeta": true,
      "porcentaje_recargo": 5.000
    }
  ]
}

Implementación:

php
// Controller
public function getAll()
{
    return $this->model->getAll();
}

// Model - Query
SELECT
    cod::int as id,
    descri as nombre,
    CASE WHEN defecto = 'T' THEN true ELSE false END as defecto,
    tarjeta as es_tarjeta,
    porcentaje_recargo
FROM condvta
ORDER BY cod

GET - Obtener Condición de Venta por ID

URL: /backend/condvta.php

Método: GET

Descripción: Obtiene una condición de venta específica por su ID.

Request Body:

json
{
  "id": 1
}

Respuesta Exitosa (200):

json
{
  "status": 200,
  "message": "Datos recibidos correctamente.",
  "data": {
    "id": 1,
    "nombre": "Contado",
    "defecto": true,
    "es_tarjeta": false,
    "porcentaje_recargo": 0.000
  }
}

Validaciones:

ValidaciónCondiciónError
ID requerido!isset($id)"Debe proporcionarse el código de condición de venta" (400)
ID entero!is_int($id)"El código proporcionado es inválido" (400)
ExisterowCount === 0Retorna null

Implementación:

php
// Model - Query
SELECT
    cod::int as id,
    descri as nombre,
    CASE WHEN defecto = 'T' THEN true ELSE false END as defecto,
    tarjeta as es_tarjeta,
    porcentaje_recargo
FROM condvta
WHERE cod = :id

PUT - Actualizar Porcentaje de Recargo

URL: /backend/condvta.php

Método: PUT

Descripción: Actualiza el porcentaje de recargo de una condición de venta.

Request Body:

json
{
  "id": 3,
  "recargo": 5.5
}

Validaciones:

CampoReglaDescripción
idrequired, integerID de la condición
recargorequired, numericPorcentaje de recargo (0-100)

Validación de Negocio:

  1. La condición de venta debe existir
  2. El recargo debe validarse contra las reglas del modelo

Respuesta Exitosa (204):

json
{
  "status": 204
}

Errores Posibles:

CódigoErrorCausa
400"Condición de venta no encontrada"ID no existe
422Errores de validaciónDatos inválidos
500"Error al modificar la condición de venta"Error en UPDATE

Implementación:

php
// Model - Update Logic
public function update(int $id, array $data): bool
{
    // 1. Verificar existencia
    $condvta = $this->getById($id);
    if (!$condvta) {
        throw new BadRequest("Condición de venta no encontrada");
    }

    // 2. Actualizar DTO
    $condvta->porcentaje_recargo = $data['recargo'];

    // 3. Validar
    self::validate($condvta->toArray());

    // 4. Ejecutar UPDATE
    UPDATE condvta
    SET porcentaje_recargo = :recargo
    WHERE cod = :id
}

Esquema de Base de Datos

Tabla: condvta

Nivel: EMPRESA + SUCURSAL (multi-schema)

Descripción: Registros de las condiciones de ventas posibles (formas de pago).

CampoTipoConstraintsDescripción
codINTEGERPRIMARY KEY, NOT NULLID/Código de la condición de venta
descriVARCHAR(50)NULLDescripción de la condición de venta
defectoVARCHAR(1)NULLIndica si es la condición por defecto ('T'/'F')
tipoVARCHAR(1)NULLSin uso - Campo legacy
funespVARCHAR(100)NULLSin uso - Campo legacy
porcentaje_recargoDECIMAL(5,3)NULLRecargo aplicable en facturación (0.000 - 99.999)
tarjetaBOOLEANNOT NULL, DEFAULT trueDetermina si es una tarjeta

Primary Key: cod

Indexes: Ninguno adicional

Foreign Keys: Ninguna


Migration (20240823200727)

php
$table = $this->table('condvta', ['id' => false, 'primary_key' => 'cod']);

$table->addColumn('cod', 'integer', ['null' => false])
    ->addColumn('descri', 'string', ['limit' => 50, 'null' => true])
    ->addColumn('defecto', 'string', ['limit' => 1, 'null' => true])
    ->addColumn('tipo', 'string', ['limit' => 1, 'null' => true]) // Sin uso
    ->addColumn('funesp', 'string', ['limit' => 100, 'null' => true]) // Sin uso
    ->addColumn('porcentaje_recargo', 'decimal', ['precision' => 5, 'scale' => 3, 'null' => true])
    ->addColumn('tarjeta', 'boolean', ['default' => true, 'null' => false])
    ->create();

Seed - Datos de Referencia

Niveles: EMPRESA + SUCURSAL

Datos iniciales:

php
[
    [
        'cod' => 1,
        'descri' => 'Contado',
        'defecto' => 'T',
        'tipo' => 'C',
        'porcentaje_recargo' => 0,
        'tarjeta' => false
    ],
    [
        'cod' => 3,
        'descri' => 'Tarjeta',
        'defecto' => 'F',
        'tipo' => 'B',
        'porcentaje_recargo' => 0,
        'tarjeta' => true
    ]
]

Condicional (si módulo CtaCte habilitado):

php
[
    'cod' => 2,
    'descri' => 'Ctas. Ctes',
    'defecto' => 'F',
    'tipo' => 'N',
    'porcentaje_recargo' => 0,
    'tarjeta' => false
]

Modelo de Datos

CondicionVenta (Model)

Namespace: App\models\Venta\CondicionVenta

Extends: App\models\Model

Métodos Públicos:

getById(int $id): ?CondicionVentaDTO

Obtiene una condición de venta por su ID.

Parámetros:

  • $id (int): ID de la condición de venta

Retorno: CondicionVentaDTO | null

Validaciones:

  • ID debe estar presente
  • ID debe ser entero

Query: SELECT con transformación de campos


getAll(): array

Obtiene todas las condiciones de venta ordenadas por código.

Retorno: CondicionVentaDTO[]

Ordenamiento: ORDER BY cod


update(int $id, array $data): bool

Actualiza el porcentaje de recargo de una condición de venta.

Parámetros:

  • $id (int): ID de la condición
  • $data (array): Datos con clave 'recargo'

Retorno: bool (éxito/fallo)

Lógica:

  1. Obtiene condición por ID
  2. Valida existencia
  3. Actualiza porcentaje_recargo
  4. Valida modelo completo
  5. Ejecuta UPDATE

Validaciones:

  • Existencia de registro
  • Validación de modelo completo

Reglas de Validación (Model)

php
$this->rules = [
    'id' => 'required|integer',
    'nombre' => 'required|max:50',
    'defecto' => 'required|boolean',
    'es_tarjeta' => 'required|boolean',
    'porcentaje_recargo' => 'required|numeric'
];

Field Mapping (DB → DTO)

php
private $fields = [
    'id' => 'cod::int as id',
    'nombre' => 'descri as nombre',
    'defecto' => "CASE WHEN defecto = 'T' THEN true ELSE false END as defecto",
    'es_tarjeta' => 'tarjeta as es_tarjeta',
    'porcentaje_recargo' => 'porcentaje_recargo'
];

Transformaciones:

  • codid (cast a int)
  • descrinombre
  • defecto → booleano (T/F → true/false)
  • tarjetaes_tarjeta
  • porcentaje_recargo → sin cambios

DTOs (Data Transfer Objects)

CondicionVentaDTO

Namespace: App\Resources\Venta\CondicionVenta

Extends: App\Resources\DTO

Propiedades:

PropiedadTipoNullableDescripción
idintID de la condición de venta
nombrestringNombre descriptivo
defectoboolIndica si es la condición por defecto
es_tarjetaboolIndica si es una tarjeta de crédito/débito
porcentaje_recargofloatPorcentaje de recargo (0.000 - 99.999)

Métodos Heredados:

  • fromArray(array $data): self - Crea instancia desde array
  • toArray(): array - Convierte a array

Enums

CondicionVenta (Enum)

Namespace: App\Resources\Venta\Enums\CondicionVenta

Backed Enum: int

Cases:

CaseValueDescripción
CONTADO1Pago al contado
CTA_CTE2Cuenta corriente (crédito)
TARJETA3Tarjeta de crédito/débito

Uso:

php
use App\Resources\Venta\Enums\CondicionVenta;

// En queries
WHERE condvta = {CondicionVenta::CTA_CTE->value}  // 2

// En comparaciones
if ($factura->condicionVenta === CondicionVenta::CONTADO->value) {
    // Lógica para contado
}

Ubicaciones de uso:

  • models/modulo-venta/Facturacion/Factura.php - Filtrado de facturas por condición
  • models/modulo-venta/Minuta.php - Reportes de ventas (separación Cta Cte vs Contado)

Integración con Otros Módulos

Módulo Ventas - Facturación

Archivo: models/modulo-venta/Facturacion/Factura.php

Relación: Factura → CondicionVenta (N:1)

Campos en factura:

php
'condicionVenta' => 'condvta::int as "condicionVenta"',
'condicionVentaRecargo' => 'condvta_recargo as "condicionVentaRecargo"'

Uso:

  • Selección de forma de pago al facturar
  • Aplicación automática de recargo según condición
  • Filtrado de reportes por condición de venta

Módulo Ventas - Comprobantes

Archivo: Resources/Venta/Facturacion/ComprobanteRequest.php

Request DTO:

php
public int $condicionVenta;           // ID de la condición
public ?int $condicionVentaRecargo = null;  // Recargo calculado

Flujo:

  1. Frontend selecciona condición de venta
  2. Backend obtiene recargo de condvta
  3. Se aplica recargo al total de factura
  4. Se registra en comprobante

Módulo Ventas - Reportes (Minuta)

Archivo: models/modulo-venta/Minuta.php

Uso del Enum:

php
// Separar ventas por condición
WHERE CONDVTA = {CondicionVenta::CTA_CTE->value}  // Ventas a crédito
WHERE CONDVTA <> {CondicionVenta::CTA_CTE->value} // Ventas al contado/tarjeta

Propósito: Discriminar en reportes contables entre:

  • Ventas al contado (cobradas inmediatamente)
  • Ventas a cuenta corriente (generan deuda)

Validaciones Implementadas

Nivel 1: Validaciones Estructurales (Model)

Ubicación: models/modulo-venta/CondicionVenta.php

Reglas:

php
'id' => 'required|integer',
'nombre' => 'required|max:50',
'defecto' => 'required|boolean',
'es_tarjeta' => 'required|boolean',
'porcentaje_recargo' => 'required|numeric'

Aplicación: En método update() antes de ejecutar SQL


Nivel 2: Validaciones de Negocio (Model)

ReglaValidaciónError
ExistenciaCondición debe existir antes de actualizar"Condición de venta no encontrada"
ID válidoDebe ser entero positivo"El código proporcionado es inválido"
ID presenteNo puede ser null/undefined"Debe proporcionarse el código de condición de venta"

Multi-Tenancy

Configuración de Esquemas

Niveles soportados: EMPRESA + SUCURSAL

Configuración en Migration:

php
protected $level = [self::LEVEL_EMPRESA, self::LEVEL_SUCURSAL];

Significado:

  • Cada empresa (public) tiene su tabla condvta
  • Cada sucursal (suc0001, suc0002) tiene su tabla condvta
  • NO se crea en nivel CAJA

Seed por Schema

Seeds ejecutados en:

  • Schema public (empresa)
  • Cada schema suc* (sucursales)

Condición: Solo si módulo Ventas habilitado

php
public function shouldExecute(): bool
{
    return $this->shouldRunOnLevel() && $this->isVentasEnabled();
}

Propagación X-Schema

Header: X-Schema: suc0001

Flujo:

  1. Frontend envía header X-Schema
  2. JWT legacy extrae schema
  3. Database class establece search_path
  4. Queries se ejecutan contra schema correcto

Testing

Cobertura Actual

⚠️ SIN TESTS IMPLEMENTADOS

Archivos de test: Ninguno

Cobertura: 0%


Testing Recomendado

Tests Unitarios (Model)

php
// Tests/Unit/Venta/CondicionVentaModelTest.php

test('getAll returns all condiciones ordenadas por codigo')
test('getById returns CondicionVentaDTO when exists')
test('getById returns null when not exists')
test('getById throws BadRequest when id is invalid')
test('update modifica porcentaje_recargo correctamente')
test('update throws BadRequest when condicion no existe')
test('update validates against model rules')

Tests de Integración

php
// Tests/Integration/Venta/CondicionVentaControllerTest.php

test('GET /backend/condvta.php returns all condiciones')
test('GET /backend/condvta.php with id returns single condicion')
test('PUT /backend/condvta.php updates recargo successfully')
test('PUT /backend/condvta.php returns 400 when condicion not found')

Performance

Queries Simples

Todas las queries son simples (SELECT/UPDATE por PK).

Sin necesidad de:

  • Índices adicionales (PK ya existe)
  • Cache (datos maestros de baja frecuencia de cambio)
  • Paginación (máximo 3-5 registros)

Consideraciones

AspectoAnálisis
N+1 QueriesNo aplica (no hay relaciones cargadas)
CacheNo necesario (3-5 registros estáticos)
ÍndicesSuficiente con PRIMARY KEY en cod
PaginaciónNo necesaria (dataset muy pequeño)

Seguridad

Autenticación

Tipo: JWT Token (legacy JwtHandler)

Payload:

php
['db' => $db, 'schema' => $schema]

Validación: En backend/condvta.php (pre-middleware)


Sanitización

SQL Injection: Protegido por prepared statements

php
// CORRECTO - Prepared statements
$stmt = $this->conn->prepare("SELECT * FROM condvta WHERE cod = :id");
$stmt->execute([':id' => $id]);

Permisos

⚠️ NO HAY VALIDACIÓN DE PERMISOS IMPLEMENTADA

Recomendación: Agregar validación de permisos basada en payload JWT:

  • Permiso lectura: condiciones_venta.read
  • Permiso escritura: condiciones_venta.write

Auditoría

⚠️ NO HAY AUDITORÍA IMPLEMENTADA

Operaciones no auditadas:

  • Modificación de porcentaje de recargo

Recomendación: Agregar AuditLogger para registrar cambios en recargos (impacta facturación).


Dependencias

Dependencias Directas

ClaseTipoUbicación
PDOPHP ExtensionCore
App\models\ModelBase Classmodels/Model.php
App\Resources\DTOBase ClassResources/DTO.php
App\Errors\BadRequestExceptionhelper/exceptions.php
DatabaseConnectionconnection/Database.php
JwtHandlerAuthauth/JwtHandler.php

Módulos que Dependen de Condición de Venta

MóduloUso
Ventas/FacturaciónSelección de forma de pago, aplicación de recargos
Ventas/ComprobantesNotas de crédito/débito, registros manuales
Ventas/ReportesMinuta de ventas (discriminación Cta Cte vs Contado)

Migraciones Pendientes

Modernización a Arquitectura Slim

Estado Actual: Legacy endpoint

Migración Recomendada:

  1. Crear Route en Slim:

    php
    // Routes/Venta/CondicionVentaRoute.php
    $group->get('', [CondicionVentaController::class, 'getAll']);
    $group->get('/{id}', [CondicionVentaController::class, 'getById']);
    $group->put('/{id}', [CondicionVentaController::class, 'update']);
  2. Agregar Validator:

    php
    // Validators/Venta/CondicionVentaValidator.php
    public function getRules(): array
    {
        return [
            'id' => v::intVal()->positive(),
            'recargo' => v::floatVal()->min(0)->max(100)
        ];
    }
  3. Crear Service Layer:

    php
    // service/Venta/CondicionVentaService.php
    class CondicionVentaService implements AuditableInterface
    {
        public function updateRecargo(int $id, float $recargo): CondicionVentaDTO
        {
            // Lógica de negocio + auditoría
        }
    }
  4. Agregar Tests:

    • Unit tests para Service
    • Integration tests para endpoints
  5. Deprecar endpoint legacy:

    • Marcar backend/condvta.php como deprecated
    • Actualizar frontend para usar nuevas rutas
    • Eliminar archivo legacy en siguiente release

Notas Adicionales

Campos Sin Uso

CampoDescripciónComentario en Migration
tipoVARCHAR(1)"Sin uso"
funespVARCHAR(100)"Sin uso"

Recomendación: Evaluar eliminación en futuras migraciones si no se usan en ningún contexto.


Campo Defecto

Formato: 'T' (true) / 'F' (false) como VARCHAR(1)

Conversión: SQL CASE convierte a booleano en DTO

Consideración: Solo una condición puede tener defecto = 'T'.

⚠️ NO HAY CONSTRAINT: No existe validación DB que garantice unicidad del defecto.

Recomendación: Agregar lógica de negocio o constraint CHECK:

sql
-- Opción 1: Partial unique index (PostgreSQL)
CREATE UNIQUE INDEX idx_condvta_defecto_unico
ON condvta (defecto) WHERE defecto = 'T';

-- Opción 2: Trigger que valide unicidad

Valores de Recargo

Precisión: DECIMAL(5,3)

Rango: 0.000 a 99.999

Ejemplos válidos:

  • 0.000 (sin recargo)
  • 5.500 (5.5% de recargo)
  • 10.000 (10% de recargo)
  • 99.999 (99.999% de recargo)

Aplicación: El recargo se calcula en el módulo de facturación multiplicando el subtotal por (porcentaje_recargo / 100).


Referencias

  • Business Requirements: Pendiente de creación
  • Frontend Documentation: Pendiente de análisis
  • Migration: migrations/tenancy/20240823200727_new_table_condvta.php
  • Seed: migrations/seeds/tenancy/Condvta.php
  • Enum: Resources/Venta/Enums/CondicionVenta.php

⚠️ NOTA IMPORTANTE: Esta documentación fue generada automáticamente analizando el código implementado. Validar cambios futuros contra este baseline. Se recomienda modernizar el endpoint legacy a arquitectura Slim Framework con Service layer y auditoría completa.


Última actualización: 2026-02-11 Versión del sistema: 3.14.0+ Estado: Implementado (Legacy)