Appearance
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 CVENfilter: 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
filterestá 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 exitosamente400 Bad Request- Datos inválidos (validación DTO)500 Server Error- Error de base de datos
Notas:
- El campo
cvense genera automáticamente usandoMAX(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 exitosamente400 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
cvenno existe, la operación no falla pero no actualiza nada
DELETE /api/mod-venta/vendedor/
Responsabilidades:
- Eliminar vendedor (soft delete)
- Establecer
deleted_atcon 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 exitosamente500 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 datosVendedor $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
cvengenerado
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:
Por ID (
idparameter):- Búsqueda exacta por CVEN
- Retorna un solo vendedor o array vacío
Por filtro (
filterparameter):- Busca en nombre (ILIKE) o código (LIKE)
- LIMIT 10 (optimizado para autocompletado)
- Case-insensitive en nombre
Primero/Último (
first/lastparameters):first=true: MIN(cven)last=true: MAX(cven)- Útil para navegación secuencial
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
| Campo | Tipo | Constraints | Descripción |
|---|---|---|---|
cven | DECIMAL(3,0) | PRIMARY KEY, NOT NULL | Código único del vendedor (3 dígitos) |
vnom | VARCHAR(35) | NULL | Nombre del vendedor |
vdni | VARCHAR(13) | NULL | DNI/CUIL/CUIT del vendedor |
vdom | VARCHAR(25) | NULL | Domicilio |
vpos | DECIMAL(6,0) | NULL | Sin uso (legacy) |
vloc | INTEGER | NULL | ID de localidad (FK a localidades) |
vcom | DECIMAL(16,5) | NULL | Porcentaje de comisión |
vfoto | VARCHAR(50) | NULL | Sin uso (legacy) |
password | VARCHAR(35) | NULL | Sin uso (legacy) |
usuario | VARCHAR(35) | NULL | Sin uso (legacy) |
VTEL | VARCHAR(18) | NULL | Número de teléfono |
deleted_at | DATE | NULL | Soft delete timestamp |
Índices:
- PRIMARY KEY en
cven
Foreign Keys:
vloc→localidades.id_loc(implícita, no forzada por constraint)
Constraints:
- Ningún constraint CHECK implementado
Migraciones Relacionadas:
20240823200739_new_table_ordven.php- Creación inicial20250116133526_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 cvenusa DECIMAL(3,0) limitando a 999 vendedores máximovloccambió 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)
| Campo | Regla | Mensaje Implícito |
|---|---|---|
nombre | Assert\NotBlank | Campo requerido |
nombre | Assert\Length(min: 3, max: 35) | Entre 3 y 35 caracteres |
domicilio | Assert\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 |
comision | Assert\NotBlank | Campo 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 deleteIntegración con Otros Módulos
Módulos que Consumen Vendedores
Ventas:
- Tabla
facturapuede referenciar vendedor - Búsqueda por filtro optimizada para autocompletado (LIMIT 10)
CtaCte:
- Puede asociar vendedores a clientes
- Tabla
ordcon(clientes) comparte nivel de schema conordven
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
✓ testDeleteSetsSoftDeleteMocks necesarios:
- Mock PDO connection
- Mock Vendedor model
Integration Tests (Recomendados)
Con base de datos real:
✓ testInsertVendedorPersistsInDatabase
✓ testUpdateVendedorModifiesFields
✓ testDeleteVendedorSetsSoftDelete
✓ testGetAllExcludesDeletedVendedores
✓ testGetByIdsReturnsBatchResultsSetup: BaseIntegrationTestCase con Docker PostgreSQL
E2E Tests (Opcionales)
Flujos completos:
✓ testCreateVendedorEndToEnd
✓ testSearchVendedorByFilter
✓ testDeleteVendedorAndVerifyNotInListPerformance
Í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.readpara GETvendedor.createpara POSTvendedor.updatepara PUTvendedor.deletepara 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
ordventiene 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
localidadestable
- Si se asigna localidad, debe existir en
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
- Agregar Auditoría: Implementar AuditableInterface en VendedorService
- Validar Unicidad: Agregar validación de documento único en Service layer
- Agregar Paginación: Implementar PaginatedResponse en getAll()
- Agregar Permisos: Validar permisos en middleware o Service layer
- Migrar a SERIAL: Cambiar
cvena SERIAL PRIMARY KEY para evitar race conditions - Agregar FK: Establecer constraint FK entre
vlocylocalidades - Testing: Crear suite de tests unitarios y de integración
- 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.