Appearance
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
| Capa | Archivo | Descripción |
|---|---|---|
| Route | backend/condvta.php | Endpoint legacy (pre-Slim Framework) |
| Controller | controller/modulo-venta/CondicionVentaController.php | Controlador HTTP |
| Model | models/modulo-venta/CondicionVenta.php | Acceso a datos |
| DTO | Resources/Venta/CondicionVenta.php | Data Transfer Object |
| Enum | Resources/Venta/Enums/CondicionVenta.php | Constantes de condiciones |
| Migration | migrations/tenancy/20240823200727_new_table_condvta.php | Esquema de base de datos |
| Seed | migrations/seeds/tenancy/Condvta.php | Datos 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 codGET - 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ón | Condición | Error |
|---|---|---|
| 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) |
| Existe | rowCount === 0 | Retorna 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 = :idPUT - 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:
| Campo | Regla | Descripción |
|---|---|---|
id | required, integer | ID de la condición |
recargo | required, numeric | Porcentaje de recargo (0-100) |
Validación de Negocio:
- La condición de venta debe existir
- El recargo debe validarse contra las reglas del modelo
Respuesta Exitosa (204):
json
{
"status": 204
}Errores Posibles:
| Código | Error | Causa |
|---|---|---|
| 400 | "Condición de venta no encontrada" | ID no existe |
| 422 | Errores de validación | Datos 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).
| Campo | Tipo | Constraints | Descripción |
|---|---|---|---|
cod | INTEGER | PRIMARY KEY, NOT NULL | ID/Código de la condición de venta |
descri | VARCHAR(50) | NULL | Descripción de la condición de venta |
defecto | VARCHAR(1) | NULL | Indica si es la condición por defecto ('T'/'F') |
tipo | VARCHAR(1) | NULL | Sin uso - Campo legacy |
funesp | VARCHAR(100) | NULL | Sin uso - Campo legacy |
porcentaje_recargo | DECIMAL(5,3) | NULL | Recargo aplicable en facturación (0.000 - 99.999) |
tarjeta | BOOLEAN | NOT NULL, DEFAULT true | Determina 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:
- Obtiene condición por ID
- Valida existencia
- Actualiza
porcentaje_recargo - Valida modelo completo
- 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:
cod→id(cast a int)descri→nombredefecto→ booleano (T/F → true/false)tarjeta→es_tarjetaporcentaje_recargo→ sin cambios
DTOs (Data Transfer Objects)
CondicionVentaDTO
Namespace: App\Resources\Venta\CondicionVenta
Extends: App\Resources\DTO
Propiedades:
| Propiedad | Tipo | Nullable | Descripción |
|---|---|---|---|
id | int | Sí | ID de la condición de venta |
nombre | string | Sí | Nombre descriptivo |
defecto | bool | Sí | Indica si es la condición por defecto |
es_tarjeta | bool | Sí | Indica si es una tarjeta de crédito/débito |
porcentaje_recargo | float | Sí | Porcentaje de recargo (0.000 - 99.999) |
Métodos Heredados:
fromArray(array $data): self- Crea instancia desde arraytoArray(): array- Convierte a array
Enums
CondicionVenta (Enum)
Namespace: App\Resources\Venta\Enums\CondicionVenta
Backed Enum: int
Cases:
| Case | Value | Descripción |
|---|---|---|
CONTADO | 1 | Pago al contado |
CTA_CTE | 2 | Cuenta corriente (crédito) |
TARJETA | 3 | Tarjeta 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ónmodels/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 calculadoFlujo:
- Frontend selecciona condición de venta
- Backend obtiene recargo de
condvta - Se aplica recargo al total de factura
- 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/tarjetaPropó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)
| Regla | Validación | Error |
|---|---|---|
| Existencia | Condición debe existir antes de actualizar | "Condición de venta no encontrada" |
| ID válido | Debe ser entero positivo | "El código proporcionado es inválido" |
| ID presente | No 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 tablacondvta - Cada sucursal (
suc0001,suc0002) tiene su tablacondvta - 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:
- Frontend envía header X-Schema
- JWT legacy extrae schema
Databaseclass establecesearch_path- 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
| Aspecto | Análisis |
|---|---|
| N+1 Queries | No aplica (no hay relaciones cargadas) |
| Cache | No necesario (3-5 registros estáticos) |
| Índices | Suficiente con PRIMARY KEY en cod |
| Paginación | No 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
| Clase | Tipo | Ubicación |
|---|---|---|
PDO | PHP Extension | Core |
App\models\Model | Base Class | models/Model.php |
App\Resources\DTO | Base Class | Resources/DTO.php |
App\Errors\BadRequest | Exception | helper/exceptions.php |
Database | Connection | connection/Database.php |
JwtHandler | Auth | auth/JwtHandler.php |
Módulos que Dependen de Condición de Venta
| Módulo | Uso |
|---|---|
| Ventas/Facturación | Selección de forma de pago, aplicación de recargos |
| Ventas/Comprobantes | Notas de crédito/débito, registros manuales |
| Ventas/Reportes | Minuta de ventas (discriminación Cta Cte vs Contado) |
Migraciones Pendientes
Modernización a Arquitectura Slim
Estado Actual: Legacy endpoint
Migración Recomendada:
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']);Agregar Validator:
php// Validators/Venta/CondicionVentaValidator.php public function getRules(): array { return [ 'id' => v::intVal()->positive(), 'recargo' => v::floatVal()->min(0)->max(100) ]; }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 } }Agregar Tests:
- Unit tests para Service
- Integration tests para endpoints
Deprecar endpoint legacy:
- Marcar
backend/condvta.phpcomo deprecated - Actualizar frontend para usar nuevas rutas
- Eliminar archivo legacy en siguiente release
- Marcar
Notas Adicionales
Campos Sin Uso
| Campo | Descripción | Comentario en Migration |
|---|---|---|
tipo | VARCHAR(1) | "Sin uso" |
funesp | VARCHAR(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 unicidadValores 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)