Skip to content

Carteras - Documentación Técnica Backend

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

Módulo: Ventas Feature: Gestión de Carteras de Clientes Fecha: 2026-02-11


Referencia a Requisitos de Negocio

Requisitos de Negocio - Carteras


Flujo de Arquitectura

Request

[JWT Auth Middleware]

[Connection Middleware] → X-Schema header → search_path

backend/carteras.php (Legacy Router)

CarteraController (controller/general/)

Cartera Model (models/general/)

PostgreSQL Database (tabla: carteras)

Nivel de arquitectura: Legacy (pre-DDD)

  • No usa Slim Framework (archivo PHP standalone en backend/)
  • No usa Service Layer
  • Controller delgado (delegación directa a Model)
  • Sin Domain Layer
  • Sin validadores middleware

API Endpoints

GET /backend/carteras.php

Responsabilidades:

  • Listar todas las carteras según tipo (cliente o proveedor)
  • Soportar filtros de autocomplete
  • Obtener primera/última cartera
  • Obtener cartera por ID

Request Query Parameters:

ParámetroTipoRequeridoDescripción
tipostringTipo de cartera: 'cliente' o 'proveedor'
idintNoFiltrar por ID específico (ccar para clientes, codigo para proveedores)
filterstringNoBúsqueda por nombre o ID (ILIKE, LIMIT 10)
firstbooleanNoObtener primer registro ordenado por ID ASC
lastbooleanNoObtener último registro ordenado por ID DESC

Lógica de Query:

  • tipo='cliente': Consulta tabla carteras, campos ccar (ID), nombre
  • tipo='proveedor': Consulta tabla deu_acr, campos codigo (ID), descri (nombre), imputa (cuenta contable)

Response DTO (tipo='cliente'):

typescript
{
  id: number,        // ccar::int
  nombre: string     // nombre
}

Response DTO (tipo='proveedor'):

typescript
{
  id: number,        // codigo::int
  nombre: string,    // descri
  cuenta: number     // imputa::bigint
}

Status Codes:

  • 200 OK - Consulta exitosa
  • 400 Bad Request - Tipo inválido o parámetros incorrectos
  • 401 Unauthorized - Token JWT ausente o inválido
  • 500 Internal Server Error - Error en la consulta

POST /backend/carteras.php

Responsabilidades:

  • Crear nueva cartera de clientes
  • Auto-generar ID incremental (MAX(ccar) + 1)
  • Insertar registro en tabla carteras

Request Body:

json
{
  "nombre": "string" // Nombre de la cartera (max 30 caracteres)
}

Lógica de ID:

sql
SELECT
    CASE
        WHEN MAX(ccar::int) IS NULL THEN 1
        ELSE MAX(ccar::int) + 1
    END AS id
FROM carteras

⚠️ Problema de Concurrencia Detectado: Usa MAX(ccar) + 1 sin transacción explícita. Potencial race condition en inserciones simultáneas.

Response DTO:

json
{
  "status": 201,
  "message": "Datos recibidos correctamente.",
  "data": {
    "id": number  // ccar asignado
  }
}

Status Codes:

  • 201 Created - Cartera creada exitosamente
  • 400 Bad Request - Nombre faltante o inválido
  • 401 Unauthorized - Token JWT ausente o inválido
  • 500 Internal Server Error - Error en INSERT o al obtener MAX(ccar)

PUT /backend/carteras.php

Responsabilidades:

  • Actualizar nombre de cartera existente
  • Modificar registro por ID (ccar)

Request Body:

json
{
  "id": number,      // ccar de la cartera
  "nombre": "string" // Nuevo nombre (max 30 caracteres)
}

SQL Ejecutado:

sql
UPDATE carteras
SET nombre = :nombre
WHERE ccar = :id

Response:

json
{
  "status": 204
}

Status Codes:

  • 204 No Content - Actualización exitosa (sin cuerpo de respuesta)
  • 400 Bad Request - ID o nombre faltante/inválido
  • 401 Unauthorized - Token JWT ausente o inválido
  • 500 Internal Server Error - Error en UPDATE

Modelo de Datos

Cartera Model

Archivo: models/general/Cartera.php

Métodos Implementados:

  • getAll(array $array): array

    • Obtiene carteras según tipo y filtros
    • Soporta búsqueda por ID, autocomplete, first/last
    • Consulta dinámica (carteras o deu_acr según tipo)
    • Sin paginación (LIMIT 10 solo en autocomplete)
  • insert(array $array): int

    • Crea nueva cartera
    • Genera ID incremental (MAX + 1)
    • Retorna ID asignado (ccar)
    • No usa transacción explícita
  • update(array $array): bool

    • Actualiza nombre de cartera
    • Retorna true/false según éxito
    • Sin validación de existencia previa

Dependencias:

  • PDO connection (inyectado en constructor)
  • Tabla carteras
  • Tabla deu_acr (para tipo='proveedor')

Esquema de Base de Datos

Tabla: carteras

Nivel: EMPRESA (1) y SUCURSAL (2) - Configurable dinámicamente

⚠️ Multi-Tenancy Critical: Esta tabla soporta configuración dinámica de niveles.

Niveles por Defecto: [EMPRESA, SUCURSAL]

  • Permite tener carteras separadas por sucursal (comportamiento tradicional)

Configuración Común: Solo [EMPRESA]

  • Permite compartir carteras entre todas las sucursales

Para configurar:

bash
php manage-table-levels.php --set --database=mi_empresa --table=carteras --levels=1
CampoTipoConstraintsDescripción
ccarSMALLINTPRIMARY KEY, NOT NULLCódigo de cartera (auto-incremental manual)
cuentaDECIMAL(10)NULLCódigo de cuenta contable asociada
nombreVARCHAR(30)NULLNombre descriptivo de la cartera

Indexes:

  • PRIMARY KEY en ccar

Foreign Keys: Ninguna (referenciada por ordcon.ccar)

Constraints: Ninguna explícita


Tabla: ordcon (Relación con Clientes)

Nivel: EMPRESA (1) y SUCURSAL (2) - Configurable dinámicamente

⚠️ Relación Crítica: La tabla ordcon (clientes) referencia a carteras mediante el campo ccar.

Campo RelevanteTipoDescripción
cnroDECIMAL(6)PRIMARY KEY - Código de cliente
ccarDECIMAL(1)Referencia a carteras.ccar (sin FK explícita)
cvenDECIMAL(3)Código de vendedor (ordven.cven)

Relación de Negocio:

  • Un cliente (ordcon) pertenece a UNA cartera (ccar)
  • Una cartera agrupa MÚLTIPLES clientes
  • El vendedor asignado (cven) trabaja con clientes de su cartera

Sin Foreign Keys Explícitas: La relación ordcon.ccar → carteras.ccar no está implementada como FK en el esquema.


Validaciones Implementadas

Nivel 1: Validaciones Estructurales

⚠️ CRÍTICO - NO IMPLEMENTADAS: No existe Validator middleware para este endpoint.

Validaciones Faltantes:

  • Tipo de dato de nombre (debería ser string, max 30 chars)
  • Tipo de dato de id (debería ser int positivo)
  • Tipo de dato de tipo (debería ser enum 'cliente'|'proveedor')
  • Campos requeridos (nombre en POST/PUT, id en PUT)

Validación Manual en Código: Ninguna validación explícita en Controller/Model.


Nivel 2: Validaciones de Negocio

En Model (mínimas):

  • insert(): Verifica que MAX(ccar) retorne al menos 1 fila (lanza InsertError si falla)
  • getAll(): Filtra por tipo, pero sin validación de valores permitidos

Validaciones de Negocio NO Implementadas:

  • Unicidad de nombre (se permite duplicados)
  • Validación de que ccar no esté en uso antes de UPDATE
  • Validación de que cartera no tenga clientes asignados antes de eliminar (NO HAY DELETE)

Capa de Servicio

⚠️ NO IMPLEMENTADA: No existe Service Layer para Carteras.

Consecuencias:

  • No hay transacciones explícitas
  • No hay audit logging (ninguna operación CUD registra auditoría)
  • Lógica de negocio mínima (solo CRUD básico)
  • Sin orquestación de operaciones complejas

Puntos de Integración

Módulos Relacionados

Ventas:

  • Modelo Cliente (models/modulo-venta/Cliente.php)
    • Campo cartera (CLI.CCAR::int) referencia a carteras.ccar
    • Campo vendedor (CLI.CVEN::int) referencia a ordven.cven

Compras:

  • Tabla deu_acr (acreedores/proveedores)
    • Consultada en getAll() cuando tipo='proveedor'
    • Campos: codigo (ID), descri (nombre), imputa (cuenta)

Servicios Externos

Ninguno.


Estrategia de Testing

⚠️ NO EXISTEN TESTS: No se encontraron archivos de testing para Carteras.

Tests Recomendados (Unit)

Model Tests (con PHPUnit + mocks):

php
CarteraModelTest::testGetAllClientes()
CarteraModelTest::testGetAllProveedores()
CarteraModelTest::testGetAllConFiltro()
CarteraModelTest::testInsertGeneraIdCorrectamente()
CarteraModelTest::testUpdateModificaNombre()

Áreas Críticas a Testear:

  • Generación de ID incremental (MAX + 1)
  • Filtros de autocomplete (LIMIT 10)
  • Manejo de first/last
  • Comportamiento cuando no hay resultados

Tests Recomendados (Integration)

Con Base de Datos Real (usando BaseIntegrationTestCase):

php
CarteraIntegrationTest::testInsertConcurrenteGeneraDuplicados()
CarteraIntegrationTest::testGetAllRespetaMultiTenancy()
CarteraIntegrationTest::testUpdateDeCarteraInexistente()

Áreas Críticas:

  • Race conditions: Inserción simultánea de 2+ carteras
  • Multi-tenancy: Schemas diferentes no ven carteras ajenas
  • Integridad referencial: Cartera con clientes asignados

Consideraciones de Rendimiento

Índices Implementados

Tabla carteras:

  • PRIMARY KEY en ccar (index automático)

NO EXISTEN índices adicionales:

  • nombre (usado en ILIKE, escaneo secuencial)

Impacto:

  • Búsquedas ILIKE sin índice (aceptable si hay <1000 carteras)
  • MAX(ccar) hace full table scan (aceptable para tabla pequeña)

Caching

⚠️ NO IMPLEMENTADO: Sin estrategia de cache.

Oportunidades de Cache:

  • Lista de carteras (cambia raramente)
  • Autocomplete de carteras (TanStack Query en frontend)

Problemas N+1

NO DETECTADOS: Carteras no hace consultas anidadas.

Potencial N+1 en Consumidores:

  • Si frontend carga 100 clientes y cada uno busca cartera por separado
  • Recomendación: Implementar batch loading o incluir nombre de cartera en query de clientes

Consideraciones de Seguridad

Sanitización de Entradas

Implementado:

  • Prepared statements en todas las queries (previene SQL injection)
  • Binding de parámetros con PDO

NO Implementado:

  • Validación de tipos (acepta cualquier valor en nombre)
  • Escape de caracteres especiales (delegado a PDO)

Permisos

NO IMPLEMENTADOS EXPLÍCITAMENTE: No hay verificación de permisos en el código.

Asume:

  • Middleware de autenticación valida JWT (archivo auth/JwtHandler.php)
  • No hay verificación de roles/permisos específicos para operaciones CRUD

Recomendación: Agregar verificación de permisos por operación:

  • CARTERAS_VIEW - GET
  • CARTERAS_CREATE - POST
  • CARTERAS_EDIT - PUT

Auditoría

⚠️ NO IMPLEMENTADA: Ninguna operación registra auditoría.

Operaciones Sin Auditar:

  • INSERT de cartera (debería registrar quién creó y cuándo)
  • UPDATE de cartera (debería registrar quién modificó y cuándo)

Recomendación: Migrar a Service Layer con Auditable trait:

php
$this->registrarAuditoria("INSERT", "VENTAS", "carteras", $carteraId);

Preguntas Técnicas Pendientes

⚠️ Aclaraciones Requeridas: Hay aspectos técnicos que requieren validación.

Ver: Preguntas sobre Carteras

Resumen de Preguntas Pendientes:

  • ¿Por qué no hay endpoint DELETE? ¿Se pueden eliminar carteras?
  • ¿Deberían implementarse validaciones de negocio (unicidad, carteras con clientes)?
  • ¿Debería migrarse a arquitectura DDD 5-layer?
  • ¿Debería implementarse auditoría de operaciones CUD?
  • ¿Cómo resolver race condition en MAX(ccar) + 1?

Limitaciones y Deuda Técnica

Arquitectura Legacy

Problema: No sigue arquitectura DDD 5-layer del backend moderno.

Consecuencias:

  • Sin Service Layer (no hay transacciones, no hay auditoría)
  • Sin Validators (no hay validación estructural)
  • Sin Slim Framework (routing manual en archivo PHP)
  • Sin DTOs (arrays asociativos en lugar de objetos tipados)

Esfuerzo de Migración: Medio (2-3 días)


Concurrencia

Problema: Uso de MAX(ccar) + 1 sin transacción explícita.

Escenario de Falla:

  1. Request A lee MAX(ccar) = 5
  2. Request B lee MAX(ccar) = 5 (antes de que A haga INSERT)
  3. Request A inserta ccar=6
  4. Request B inserta ccar=6 → DUPLICATE KEY ERROR

Solución: Usar SERIAL o SEQUENCE de PostgreSQL, o envolver en transacción con LOCK.


Validaciones

Problema: Sin validadores middleware ni validaciones de negocio.

Consecuencias:

  • Se puede insertar nombre vacío/NULL
  • Se puede actualizar cartera inexistente sin error
  • Se pueden crear carteras duplicadas (mismo nombre)

Esfuerzo de Mejora: Bajo (1 día para validadores + reglas de negocio)


Referencias


⚠️ NOTA IMPORTANTE: Esta documentación fue generada automáticamente analizando el código implementado. Validar cambios futuros contra este baseline. Se recomienda priorizar la migración a arquitectura DDD y la resolución del problema de concurrencia en generación de IDs.