Skip to content

Vendedores - Documentación Técnica Backend

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

Módulo: Ventas Feature: Vendedores Fecha: 2026-02-11 Tipo: Resource (CRUD)


Arquitectura Implementada

El módulo de vendedores sigue la arquitectura de 5 capas estándar de Sistema Bautista:

  • Route: Routes/Venta/VendedorRoute.php
  • Controller: controller/modulo-venta/VendedorController.php
  • Service: service/Venta/VendedorService.php
  • Model: models/modulo-venta/Vendedor.php
  • DTO: Resources/Venta/Vendedor.php
  • Tabla: ordven

Flujo de Operación

Request

Middleware Stack (CORS → JSON Parser → Connection → Auth)

Route (/api/mod-venta/vendedor)

VendedorController (HTTP handling)

VendedorService (Orquestación)

Vendedor Model (Data access + DTO mapping)

Database (ordven table)

API Endpoints

GET /api/mod-venta/vendedor

Responsabilidades:

  • Listar todos los vendedores o uno específico por ID
  • Soportar filtros de búsqueda (nombre, código)
  • Aplicar soft delete (excluir registros eliminados)
  • Opciones especiales: first, last, filter

Request:

Query Parameters (opcional):

  • id: int - Obtener vendedor específico por CVEN
  • filter: string - Buscar por nombre o código (ILIKE, limitado a 10 resultados)
  • first: boolean - Obtener primer vendedor por código (orden ASC)
  • last: boolean - Obtener último vendedor por código (orden DESC)

Response DTO (uno o múltiples):

typescript
{
  cven: int,           // Código único del vendedor
  nombre: string,      // Nombre completo (TRIM aplicado)
  documento: string,   // DNI/CUIL/CUIT
  comision: float,     // Porcentaje de comisión
  domicilio: string,   // Dirección
  localidad: int,      // ID de localidad (FK)
  telefono: string     // Número de teléfono
}

Status Codes:

  • 200 OK - Vendedor(es) encontrado(s) o array vacío

Comportamiento Especial:

  • Si filter está presente: LIMIT 10 (para autocompletado)
  • Si first=true: Retorna primer vendedor (código mínimo)
  • Si last=true: Retorna último vendedor (código máximo)
  • Sin parámetros: Retorna todos los vendedores activos

POST /api/mod-venta/vendedor

Responsabilidades:

  • Crear nuevo vendedor
  • Generar código automático (MAX(cven) + 1)
  • Validar estructura del DTO (controller)
  • Insertar en base de datos

Request Body DTO:

typescript
{
  nombre: string,        // Requerido, 3-35 caracteres
  comision: float,       // Requerido
  documento?: string,    // Opcional, validado con IsDocumento
  domicilio?: string,    // Opcional, 3-25 caracteres
  localidad?: int,       // Opcional, FK a localidades
  telefono?: string      // Opcional, validado con IsTelefono
}

Response DTO:

typescript
{
  cven: int  // Código del vendedor creado
}

Status Codes:

  • 200 OK - Vendedor creado exitosamente
  • 400 Bad Request - Datos inválidos (validación DTO)
  • 500 Server Error - Error de base de datos

Notas:

  • El campo cven se genera automáticamente usando MAX(cven) + 1
  • ⚠️ Potencial race condition: No hay transacción explícita ni lock para generación de código

PUT /api/mod-venta/vendedor

Responsabilidades:

  • Actualizar vendedor existente
  • Validar estructura del DTO
  • Actualizar todos los campos editables

Request Body DTO:

typescript
{
  cven: int,            // Requerido - identificador del vendedor a actualizar
  nombre: string,       // Requerido, 3-35 caracteres
  comision: float,      // Requerido
  documento?: string,   // Opcional
  domicilio?: string,   // Opcional
  localidad?: int,      // Opcional
  telefono?: string     // Opcional
}

Response:

typescript
{
  status: 200,
  message: "Datos recibidos correctamente."
}

Status Codes:

  • 200 OK - Vendedor actualizado exitosamente
  • 400 Bad Request - Datos inválidos (validación DTO)
  • 500 Server Error - Error de base de datos

Notas:

  • No verifica si el vendedor existe antes de actualizar
  • Si el cven no existe, la operación no falla pero no actualiza nada

DELETE /api/mod-venta/vendedor/

Responsabilidades:

  • Eliminar vendedor (soft delete)
  • Establecer deleted_at con fecha actual
  • Mantener integridad referencial

Path Parameters:

  • cven: int - Código del vendedor a eliminar

Response:

typescript
{
  status: 200,
  message: "Datos recibidos correctamente."
}

Status Codes:

  • 200 OK - Vendedor eliminado exitosamente
  • 500 Server Error - Error de base de datos

Notas:

  • Soft delete implementado (no elimina físicamente)
  • Establece deleted_at = NOW()
  • No verifica si el vendedor existe antes de eliminar
  • No verifica dependencias (facturas, ventas con este vendedor)

Capa de Servicio

VendedorService

Archivo: service/Venta/VendedorService.php

Responsabilidades:

  • Orquestación de operaciones CRUD
  • Delegación a Model layer
  • Punto de extensión para lógica de negocio futura

Dependencias:

  • PDO $conn - Conexión de base de datos
  • Vendedor $model - Modelo de datos

Métodos Implementados:

getById(int $id): VendedorDTO|array

  • Delega a Model.getById()
  • Retorna DTO o array vacío

getAll(array $options): VendedorDTO|array

  • Delega a Model.getAll()
  • Soporta opciones: filter, first, last
  • Retorna DTO individual, array de DTOs o array vacío

insert(VendedorDTO $vendedor): array

  • Delega a Model.insert()
  • Retorna array con cven generado

update(VendedorDTO $vendedor): void

  • Delega a Model.update()
  • Sin retorno

delete(int $id): void

  • Delega a Model.delete()
  • Sin retorno

Notas Arquitectónicas:

  • ⚠️ Service layer pass-through: Actualmente el Service no agrega lógica de negocio, solo delega
  • Punto de extensión: Permite agregar validaciones de negocio, transacciones, auditoría en el futuro
  • Sin auditoría: No implementa AuditableInterface ni registra operaciones CUD
  • Sin transacciones: No maneja transacciones explícitas

Lógica de Negocio

Generación de Código (CVEN)

Patrón: Auto-incremento manual usando MAX(cven) + 1

Implementación:

sql
SELECT MAX(CVEN) AS CVEN FROM ordven
-- Resultado + 1 = nuevo código

⚠️ Consideraciones Críticas:

  • Race condition: Sin transacción ni lock, dos inserciones simultáneas pueden generar el mismo código
  • Sin secuencia: No usa SERIAL ni SEQUENCE de PostgreSQL
  • Brecha en códigos: Si un registro se elimina, su código no se reutiliza
  • Recomendación: Migrar a SERIAL PRIMARY KEY o implementar lock explícito

Soft Delete

Patrón: Eliminación lógica con campo deleted_at

Implementación:

  • Todos los SELECT filtran por WHERE deleted_at IS NULL
  • DELETE ejecuta UPDATE ordven SET deleted_at = NOW()
  • Preserva datos históricos
  • Permite auditoría y restauración

Búsqueda y Filtrado

Opciones de Búsqueda:

  1. Por ID (id parameter):

    • Búsqueda exacta por CVEN
    • Retorna un solo vendedor o array vacío
  2. Por filtro (filter parameter):

    • Busca en nombre (ILIKE) o código (LIKE)
    • LIMIT 10 (optimizado para autocompletado)
    • Case-insensitive en nombre
  3. Primero/Último (first/last parameters):

    • first=true: MIN(cven)
    • last=true: MAX(cven)
    • Útil para navegación secuencial
  4. Todos (sin parámetros):

    • Retorna todos los vendedores activos
    • Sin paginación

Esquema de Base de Datos

Tabla: ordven

Nivel: EMPRESA y SUCURSAL (multi-tenancy configurado)

Descripción: Registro de vendedores del sistema

CampoTipoConstraintsDescripción
cvenDECIMAL(3,0)PRIMARY KEY, NOT NULLCódigo único del vendedor (3 dígitos)
vnomVARCHAR(35)NULLNombre del vendedor
vdniVARCHAR(13)NULLDNI/CUIL/CUIT del vendedor
vdomVARCHAR(25)NULLDomicilio
vposDECIMAL(6,0)NULLSin uso (legacy)
vlocINTEGERNULLID de localidad (FK a localidades)
vcomDECIMAL(16,5)NULLPorcentaje de comisión
vfotoVARCHAR(50)NULLSin uso (legacy)
passwordVARCHAR(35)NULLSin uso (legacy)
usuarioVARCHAR(35)NULLSin uso (legacy)
VTELVARCHAR(18)NULLNúmero de teléfono
deleted_atDATENULLSoft delete timestamp

Índices:

  • PRIMARY KEY en cven

Foreign Keys:

  • vloclocalidades.id_loc (implícita, no forzada por constraint)

Constraints:

  • Ningún constraint CHECK implementado

Migraciones Relacionadas:

  • 20240823200739_new_table_ordven.php - Creación inicial
  • 20250116133526_update_field_vloc_ordven.php - Migración de vloc a INTEGER (FK a localidades)
  • 20250116133546_new_field_soft_delete_ordven.php - Agregado de deleted_at

Notas de Schema:

  • Campos sin uso heredados de sistema legacy: vpos, vfoto, password, usuario
  • cven usa DECIMAL(3,0) limitando a 999 vendedores máximo
  • vloc cambió de string a integer para FK con localidades (migración 20250116133526)

Validaciones

Validaciones Estructurales (DTO Level)

Ubicación: Resources/Venta/Vendedor.php (Symfony Validator Constraints)

CampoReglaMensaje Implícito
nombreAssert\NotBlankCampo requerido
nombreAssert\Length(min: 3, max: 35)Entre 3 y 35 caracteres
domicilioAssert\Length(min: 3, max: 25)Entre 3 y 25 caracteres (si presente)
documento#[IsDocumento]Validación personalizada DNI/CUIL/CUIT
telefono#[IsTelefono]Validación personalizada formato teléfono
comisionAssert\NotBlankCampo requerido

Aplicación: Invocada en controller mediante $vendedor->validate(true)

Status Code: 400 Bad Request (validación estructural fallida)

Validaciones de Negocio (Service Level)

Estado actual: ❌ No implementadas

Validaciones faltantes:

  • No verifica duplicados por nombre
  • No verifica duplicados por documento
  • No valida rango de comisión (0-100%)
  • No verifica existencia de localidad al asignar vloc
  • No verifica dependencias antes de eliminar (facturas, ventas)

Recomendación: Agregar validaciones de negocio en Service layer:

- Validar unicidad de documento si se proporciona
- Validar rango de comisión (ej: 0 <= comision <= 100)
- Validar existencia de localidad antes de insert/update
- Validar que no existan ventas asociadas antes de delete

Integración con Otros Módulos

Módulos que Consumen Vendedores

Ventas:

  • Tabla factura puede referenciar vendedor
  • Búsqueda por filtro optimizada para autocompletado (LIMIT 10)

CtaCte:

  • Puede asociar vendedores a clientes
  • Tabla ordcon (clientes) comparte nivel de schema con ordven

CRM:

  • Puede asignar vendedores a actividades
  • Reportes por vendedor

Batch Loading (Prevención N+1)

Método: getByIds(array $ids): array

Propósito:

  • Cargar múltiples vendedores en una sola query
  • Retornar array indexado por CVEN para lookup O(1)
  • Prevenir N+1 queries en listados con vendedores asociados

Uso:

php
$vendedores = $vendedorModel->getByIds([1, 2, 3, 5, 8]);
// Retorna: [1 => VendedorDTO, 2 => VendedorDTO, ...]

Optimización:

  • Sanitiza IDs (solo enteros)
  • Usa prepared statement con placeholders
  • Retorna array indexado por CVEN

Estrategia de Testing

Unit Tests (Recomendados)

Service Layer:

✓ testGetByIdReturnsVendedor
✓ testGetByIdThrowsExceptionIfNotFound
✓ testGetAllReturnsAllVendedores
✓ testGetAllWithFilterReturnsLimited10
✓ testInsertGeneratesNewCven
✓ testUpdateModifiesVendedor
✓ testDeleteSetsSoftDelete

Mocks necesarios:

  • Mock PDO connection
  • Mock Vendedor model

Integration Tests (Recomendados)

Con base de datos real:

✓ testInsertVendedorPersistsInDatabase
✓ testUpdateVendedorModifiesFields
✓ testDeleteVendedorSetsSoftDelete
✓ testGetAllExcludesDeletedVendedores
✓ testGetByIdsReturnsBatchResults

Setup: BaseIntegrationTestCase con Docker PostgreSQL

E2E Tests (Opcionales)

Flujos completos:

✓ testCreateVendedorEndToEnd
✓ testSearchVendedorByFilter
✓ testDeleteVendedorAndVerifyNotInList

Performance

Índices Implementados

  • ✅ PRIMARY KEY en cven (búsqueda por ID)

Índices Recomendados

sql
-- Búsqueda por nombre (filtro ILIKE)
CREATE INDEX idx_ordven_vnom ON ordven USING gin (vnom gin_trgm_ops);

-- Exclusión de soft delete (WHERE frecuente)
CREATE INDEX idx_ordven_deleted_at ON ordven (deleted_at) WHERE deleted_at IS NULL;

-- Búsqueda por documento (si se agrega unicidad)
CREATE UNIQUE INDEX idx_ordven_vdni_unique ON ordven (vdni) WHERE deleted_at IS NULL AND vdni IS NOT NULL;

Optimizaciones

Query N+1 Prevention:

  • ✅ Método getByIds() implementado para batch loading
  • ✅ Indexado por CVEN en resultado para O(1) lookup

Caching:

  • ❌ No implementado
  • Recomendación: Cache de vendedores en frontend (TanStack Query) con staleTime configurado

Paginación:

  • ❌ No implementado (retorna todos los registros)
  • Recomendación: Agregar paginación estándar (limit/offset) usando PaginatedResponse

Seguridad

Sanitización de Input

Prepared Statements: Todas las queries usan prepared statements (prevención SQL injection)

Ejemplo:

php
$stmt = $this->conn->prepare("SELECT * FROM ordven WHERE cven = :id");
$stmt->execute([':id' => $id]);

Permisos

No implementado: No hay validación de permisos en los endpoints

Recomendación: Agregar validación de permisos:

  • vendedor.read para GET
  • vendedor.create para POST
  • vendedor.update para PUT
  • vendedor.delete para DELETE

Auditoría

No implementado: No registra operaciones CUD en tabla de auditoría

Recomendación: Implementar AuditableInterface en VendedorService:

php
$this->registrarAuditoria(
    "INSERT",
    "VENTAS",
    $this->model->getTable(),
    $result['cven']
);

Validación de Pertenencia (Multi-tenancy)

Implementado: ConnectionMiddleware establece search_path basado en JWT/X-Schema

Notas:

  • El schema se propaga automáticamente a todas las queries
  • No es necesaria validación adicional en Service layer
  • Tabla ordven tiene nivel EMPRESA y SUCURSAL configurado

Dependencias

Dependencias Directas

  • Base de Datos: PostgreSQL (multi-tenant con search_path)
  • Framework: Slim Framework 4 (routing)
  • Conexión: PDO (database access)
  • Validación: Symfony Validator (DTO validation)
  • DTO: DragonCode DataTransferObject (DTO base)

Dependencias de Módulos

  • Localidades (opcional): FK en campo vloc
    • Si se asigna localidad, debe existir en localidades table

Dependientes de Este Módulo

Módulos que referencian vendedores:

  • Ventas (facturas, comprobantes)
  • CtaCte (clientes con vendedor asignado)
  • CRM (actividades, tareas)

Preguntas Técnicas Pendientes

1. Race Condition en Generación de Código

Observación: El método insert() usa MAX(cven) + 1 sin transacción explícita ni lock.

Pregunta: ¿Han experimentado duplicados de CVEN en producción? ¿Debería implementarse un lock o migrar a SERIAL?

Impacto: Potencial violación de PRIMARY KEY en inserciones concurrentes.


2. Campos Legacy sin Uso

Observación: Los campos vpos, vfoto, password, usuario están marcados como "Sin uso" en migración.

Pregunta: ¿Estos campos pueden eliminarse del schema o hay dependencias en sistema legacy?

Impacto: Limpieza de schema y reducción de tamaño de tabla.


3. Foreign Key vloc no Forzada

Observación: El campo vloc referencia localidades.id_loc pero no hay constraint de FK.

Pregunta: ¿Debería agregarse un constraint de FK con ON DELETE SET NULL? ¿O es intencional para permitir localidades inexistentes?

Impacto: Integridad referencial y prevención de IDs inválidos.


4. Sin Validación de Unicidad en Documento

Observación: El campo vdni (documento) no tiene constraint UNIQUE ni validación en Service.

Pregunta: ¿Un mismo documento puede estar en múltiples vendedores? ¿Debería validarse unicidad?

Impacto: Duplicados de documento y potenciales problemas en reportes AFIP.


5. Límite de 999 Vendedores

Observación: cven es DECIMAL(3,0), limitando a 999 vendedores.

Pregunta: ¿Es suficiente este límite? ¿Hay empresas con más de 999 vendedores?

Impacto: Overflow de PRIMARY KEY en empresas grandes.


6. Sin Paginación en getAll()

Observación: getAll() sin filtro retorna todos los vendedores sin paginación.

Pregunta: ¿Es necesaria paginación? ¿Cuántos vendedores promedio tiene una empresa?

Impacto: Performance en empresas con muchos vendedores.


7. Sin Auditoría de Operaciones

Observación: Service no implementa AuditableInterface ni registra operaciones CUD.

Pregunta: ¿Requiere auditoría el módulo de vendedores? ¿O no es crítico para el negocio?

Impacto: Trazabilidad de cambios y compliance.


8. Delete sin Verificación de Dependencias

Observación: delete() no verifica si existen ventas/facturas asociadas al vendedor.

Pregunta: ¿Un vendedor con ventas asociadas puede eliminarse? ¿O debería bloquearse con mensaje de error?

Impacto: Integridad de datos históricos y reportes.


Referencias


Próximos Pasos Recomendados

  1. Agregar Auditoría: Implementar AuditableInterface en VendedorService
  2. Validar Unicidad: Agregar validación de documento único en Service layer
  3. Agregar Paginación: Implementar PaginatedResponse en getAll()
  4. Agregar Permisos: Validar permisos en middleware o Service layer
  5. Migrar a SERIAL: Cambiar cven a SERIAL PRIMARY KEY para evitar race conditions
  6. Agregar FK: Establecer constraint FK entre vloc y localidades
  7. Testing: Crear suite de tests unitarios y de integración
  8. Responder Preguntas: Validar preguntas técnicas pendientes con equipo

⚠️ NOTA IMPORTANTE: Esta documentación fue generada automáticamente analizando el código implementado. Validar cambios futuros contra este baseline y responder las preguntas técnicas pendientes antes de refactorizaciones mayores.