Skip to content

ABM Clientes - Documentación Técnica Backend

Módulo: ventas Feature: abm-clientes Tipo: Resource Fecha: 2026-02-11

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


Referencias

  • Requisitos de Negocio: [Pendiente de creación]
  • Documentación Frontend: [Pendiente de creación]

Arquitectura Implementada

Estructura de Archivos

API Layer (Legacy):

  • backend/ordcon.php - Endpoint legacy para operaciones CRUD de clientes

Controller Layer:

  • controller/modulo-venta/ClienteController.php - Controlador con lógica de coordinación

Model Layer:

  • models/modulo-venta/Cliente.php - Modelo principal con acceso a datos
  • models/modulo-ctacte/Cliente.php - Modelo auxiliar para consultas de cuenta corriente

Validation Layer:

  • Validators/Venta/ClienteValidator.php - Validador de estructura de datos

Database:

  • Tabla: ordcon (Clientes)
  • Nivel: EMPRESA + SUCURSAL (configurable)

Endpoints API

⚠️ NOTA: Este módulo usa endpoints legacy (PHP directo) en lugar de Slim Framework.

GET /backend/ordcon.php

Propósito: Listar clientes con diferentes modos de consulta

Parámetros Query:

ParámetroTipoDescripción
serverSidebooleanHabilita Server-Side Rendering para DataTables
idintegerObtiene cliente específico por ID
scopestringNivel de detalle: min (básico), max (completo)
filterstringFiltro de búsqueda (nombre, código, identificación)
defaultbooleanObtiene cliente marcado como defecto
firstbooleanObtiene primer cliente (ordenado por código)
lastbooleanObtiene último cliente
tiene_pendientebooleanIncluye flag de pedidos pendientes

Respuesta (scope: min):

json
{
  "status": 200,
  "data": [
    {
      "id": 1,
      "nombre": "Consumidor Final",
      "identificacion": "20123456789",
      "domicilio1": "Calle Principal 123",
      "telefono1": "1234567890",
      "email": "cliente@example.com",
      "defecto": true
    }
  ]
}

Respuesta (scope: max):

json
{
  "status": 200,
  "data": {
    "id": 1,
    "nombre": "Consumidor Final",
    "identificacion": "20123456789",
    "domicilio1": "Calle Principal 123",
    "domicilio2": null,
    "telefono1": "1234567890",
    "telefono2": null,
    "condicion_iva": {
      "id": 1,
      "nombre": "Consumidor Final"
    },
    "comision": 0.00,
    "nro_iibb": null,
    "iibb": {
      "id": 1,
      "nombre": "No inscripto"
    },
    "email": "cliente@example.com",
    "cartera": null,
    "fecha_alta": "2024-01-15",
    "margen_credito": 999999.00,
    "margen_fact_imp": 9999,
    "ultimo_mov": null,
    "tiene_ctacte": false,
    "lista": null,
    "localidad": {
      "id": 100,
      "nombre": "Buenos Aires",
      "cod_post": "1000",
      "provincia": "Buenos Aires"
    },
    "rel_lista": "N",
    "vendedor": {
      "cven": 1,
      "nombre": "Vendedor Principal"
    },
    "facturas_impagas": 0,
    "defecto": true
  }
}

Respuesta (Server-Side Rendering - DataTables):

json
{
  "draw": 1,
  "recordsTotal": 150,
  "recordsFiltered": 150,
  "data": [
    {
      "id": 1,
      "nombre": "Consumidor Final",
      "identificacion": "20123456789",
      "domicilio1": "Calle Principal 123",
      "telefono1": "1234567890",
      "email": "cliente@example.com",
      "defecto": true
    }
  ]
}

Status Codes:

  • 200 - Consulta exitosa
  • 400 - Parámetros inválidos
  • 500 - Error del servidor

POST /backend/ordcon.php

Propósito: Crear nuevo cliente

Request Body:

json
{
  "nombre": "Nuevo Cliente",
  "identificacion": "20987654321",
  "email": "nuevo@example.com",
  "domicilio1": "Avenida Libertador 456",
  "domicilio2": "Piso 3 Depto A",
  "telefono1": "1145678901",
  "telefono2": "1156789012",
  "localidad": {
    "id": 100
  },
  "condicion_iva": {
    "id": 2
  },
  "iibb": {
    "id": 1
  },
  "nro_iibb": "123456789",
  "vendedor": {
    "cven": 1
  },
  "comision": 5.00,
  "cartera": {
    "id": 1
  },
  "tiene_ctacte": true,
  "margen_credito": 50000.00,
  "margen_fact_imp": 30,
  "rel_lista": "N",
  "lista": null
}

Respuesta:

json
{
  "status": 201,
  "data": {
    "id": 152
  }
}

Validaciones de Negocio:

  • Identificación única (no puede existir otro cliente con el mismo CUIT/DNI)
  • Auto-generación de código (MAX(cnro) + 1)
  • Fecha de alta automática (CURRENT_DATE)
  • Campos obligatorios: nombre, localidad, condición IVA, IIBB, vendedor

Status Codes:

  • 201 - Cliente creado exitosamente
  • 400 - Datos inválidos (validación estructural)
  • 405 - Duplicado (identificación ya existe)
  • 500 - Error del servidor

PUT /backend/ordcon.php

Propósito: Actualizar cliente completo

Request Body:

json
{
  "id": 152,
  "nombre": "Cliente Actualizado",
  "identificacion": "20987654321",
  "email": "actualizado@example.com",
  "domicilio1": "Nueva Dirección 789",
  "domicilio2": null,
  "telefono1": "1134567890",
  "telefono2": null,
  "localidad": {
    "id": 101
  },
  "condicion_iva": {
    "id": 3
  },
  "iibb": {
    "id": 2
  },
  "nro_iibb": "987654321",
  "vendedor": {
    "cven": 2
  },
  "comision": 7.50,
  "cartera": {
    "id": 2
  },
  "tiene_ctacte": true,
  "margen_credito": 75000.00,
  "margen_fact_imp": 45,
  "rel_lista": "S",
  "lista": 2
}

Respuesta:

json
{
  "status": 204
}

Validaciones de Negocio:

  • Cliente debe existir
  • Identificación única (excluyendo el cliente actual)
  • Si tiene_ctacte = false, se mantienen valores anteriores de margen_credito y margen_fact_imp

Status Codes:

  • 204 - Actualización exitosa
  • 400 - Datos inválidos
  • 404 - Cliente no encontrado
  • 405 - Duplicado (identificación ya existe)
  • 500 - Error del servidor

PATCH /backend/ordcon.php

Propósito: Actualización parcial de cliente (principalmente para campo defecto)

Request Body:

json
{
  "id": 152,
  "defecto": true
}

Respuesta:

json
{
  "status": 204
}

Lógica de Negocio:

  • Solo un cliente puede ser defecto = true a la vez
  • Al marcar un cliente como defecto, automáticamente se desmarca el anterior
  • Si se intenta desmarcar el único cliente defecto, la operación se rechaza

Status Codes:

  • 204 - Actualización exitosa
  • 400 - Datos inválidos
  • 500 - Error del servidor

⚠️ OBSERVACIÓN: Existe el método partialUpdateCliente() en el modelo que permite actualización parcial de múltiples campos, pero actualmente NO tiene endpoint expuesto.


Capa de Controlador

ClienteController

Ubicación: controller/modulo-venta/ClienteController.php

Responsabilidades:

  • Coordinar llamadas entre routes y model
  • Agregar lógica adicional (validación de pendientes)
  • No realiza transacciones (delegadas al route)

Métodos Principales:

getClientesSSR(array $data): array

Obtiene clientes con paginación Server-Side para DataTables.

Parámetros:

  • $data: Array con parámetros de DataTables (draw, start, length, search, order)

Retorno: Array en formato DataTables con draw, recordsTotal, recordsFiltered, data

getClienteById(int|array $id, string $scope = 'max', array $options = []): array

Obtiene cliente(s) por ID con scope configurable.

Parámetros:

  • $id: ID único o array de IDs
  • $scope: 'min' (campos básicos) o 'max' (todos los campos)
  • $options: Opciones adicionales (ej: tiene_pendiente)

Retorno: Array con datos del cliente o array de clientes

getClientes(array $options, string $scope = 'min'): array

Obtiene listado de clientes con filtros.

Opciones soportadas:

  • filter: Búsqueda por nombre, código o identificación
  • default: Obtiene cliente marcado como defecto
  • first: Primer cliente (orden por código)
  • last: Último cliente
  • tiene_pendiente: Incluye validación de pedidos pendientes

Retorno: Array de clientes

insertCliente(array $data): array

Crea nuevo cliente.

Retorno: Array con ['id' => X]

Validaciones:

  • Identificación única
  • Generación automática de código

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

Actualiza cliente completo.

Retorno: true si exitoso, exception si falla

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

Actualización parcial (campo defecto).

Lógica especial:

  • Desmarca otros clientes si defecto = true

Capa de Modelo

Cliente (Ventas)

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

Tabla: ordcon

Scopes Definidos:

Scope min (Campos básicos para listados):

php
[
  'id', 'nombre', 'identificacion',
  'domicilio1', 'telefono1', 'email', 'defecto'
]

Scope max (Campos completos para formularios):

php
[
  'id', 'nombre', 'identificacion', 'domicilio1', 'domicilio2',
  'telefono1', 'telefono2', 'condicion_iva', 'comision',
  'nro_iibb', 'iibb', 'email', 'cartera', 'fecha_alta',
  'margen_credito', 'margen_fact_imp', 'ultimo_mov',
  'tiene_ctacte', 'lista', 'localidad', 'rel_lista',
  'vendedor', 'facturas_impagas', 'defecto'
]

Métodos Principales:

getClientesSSR(array $data): array

Server-Side Rendering para DataTables con búsqueda y ordenamiento.

Búsqueda: Nombre (ILIKE), código (LIKE), identificación (LIKE)

Ordenamiento: Por columna seleccionada (cnro, cnom, ccui)

getClienteById(int|array $id, string $scope = 'max'): array

Obtiene cliente(s) con resolución de relaciones.

Relaciones resueltas:

  • localidadLocalidad::getById()
  • vendedorVendedor::getById()
  • condicion_ivaCondicionIva::getById()
  • iibbIIBB::getById()

Conversiones de tipos:

  • margen_credito: string → float
  • margen_fact_imp: string → int
  • comision: string → float

getClientes(array $options, string $scope = 'min'): array

Búsqueda flexible con múltiples opciones.

Filtros:

  • filter: Búsqueda parcial por nombre/código/identificación
  • default: WHERE defecto IS TRUE LIMIT 1
  • first: ORDER BY cnro ASC LIMIT 1
  • last: ORDER BY cnro DESC LIMIT 1

Límites:

  • Con filter: LIMIT 10 (autocomplete)
  • Con default/first/last: LIMIT 1
  • Sin filtros: Sin límite

insertCliente(array $data): array

Inserción con validaciones y auto-generación de código.

SQL de generación de código:

sql
SELECT
  CASE
    WHEN MAX(CNRO) IS NULL THEN 1
    ELSE MAX(CNRO) + 1
  END AS CNRO
FROM ordcon

⚠️ RIESGO DE CONCURRENCIA: Uso de MAX(cnro) + 1 sin transacción explícita puede generar duplicados en alta concurrencia.

Validaciones:

  • Duplicidad de identificación antes de insertar

Campos auto-generados:

  • cnro: MAX + 1
  • cfin: CURRENT_DATE

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

Actualización completa con validaciones.

Validaciones:

  • Identificación única (excluyendo cliente actual)

Lógica condicional de cuenta corriente:

sql
clim = CASE WHEN :tiene_ctacte = 1 THEN :margenCredito ELSE clim END,
climdia = CASE WHEN :tiene_ctacte = 1 THEN :margen_fact_imp ELSE climdia END

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

Actualización del campo defecto con lógica de exclusividad.

Lógica:

sql
-- Si defecto = true, primero desmarca todos
UPDATE ordcon SET defecto = FALSE

-- Luego marca el seleccionado
UPDATE ordcon SET defecto = :defecto WHERE cnro = :id

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

⚠️ MÉTODO SIN ENDPOINT: Actualización parcial flexible de múltiples campos.

Campos soportados:

  • Escalares: nombre, email, domicilio1/2, telefono1/2, identificacion, nro_iibb, comision, tiene_ctacte, margen_credito, margen_fact_imp, rel_lista, lista
  • Objetos: localidad, condicion_iva, iibb, vendedor, cartera

Validaciones:

  • Duplicidad de identificación si se modifica

Cliente (CtaCte)

Ubicación: models/modulo-ctacte/Cliente.php

Propósito: Consultas específicas para módulo de cuenta corriente

Métodos:

getAll(array $options): array

Obtiene clientes con campos mínimos y flag de cuenta corriente.

getById(int $id): array

Obtiene cliente específico con flag de cuenta corriente.

hasCarteras(): bool

Verifica si la tabla ordcon tiene columna ccar (manejo de carteras).

Implementación:

sql
SELECT column_name
FROM information_schema.columns
WHERE table_name = :table AND column_name = 'ccar'

Validaciones

Validador Estructural

Ubicación: Validators/Venta/ClienteValidator.php

⚠️ OBSERVACIÓN: El validador existe pero NO se aplica en el endpoint legacy /backend/ordcon.php.

Reglas Definidas:

CampoReglasMensaje de Error
nombrerequired, string, max:55, min:3El nombre es requerido / debe tener entre 3-55 caracteres
identificacionsometimes, dniOrCuitDebe ser un DNI o CUIT válido
emailsometimes, email, max:50Debe ser email válido de máximo 50 caracteres
domicilio1sometimes, string, max:35Máximo 35 caracteres
domicilio2sometimes, string, max:35Máximo 35 caracteres
telefono1sometimes, numericDebe ser numérico
telefono2sometimes, numericDebe ser numérico
localidadrequired, arrayLa localidad es requerida
localidad.idrequired, integerLa localidad es inválida
condicion_ivarequired, arrayLa condición de IVA es requerida
condicion_iva.idrequired, integerLa condición de IVA es inválida
iibbrequired, arrayLos Ingresos Brutos son requeridos
iibb.idrequired, integerLos Ingresos Brutos son inválidos
nro_iibbsometimes, string, max:13Máximo 13 caracteres
vendedorrequired, arrayEl vendedor es requerido
vendedor.cvenrequired, integerEl vendedor seleccionado es inválido
comisionsometimes, numericDebe ser un número
carterasometimes, arrayLa cartera seleccionada es inválida
cartera.idsometimes, integerLa cartera seleccionada es inválida

Regla Personalizada:

  • dniOrCuit: Valida formato de DNI (8 dígitos) o CUIT (11 dígitos con formato 20-12345678-9)

Validaciones de Negocio

En insertCliente():

  1. Duplicidad de identificación:
sql
SELECT COUNT(*) AS cantidad
FROM ordcon
WHERE ccui = :identificacion
  • Si cantidad > 0 → Exception 405: "Ya existe un cliente con ese número de identificación"

En updateCliente():

  1. Duplicidad de identificación (excluyendo cliente actual):
sql
SELECT cnro::int
FROM ordcon
WHERE ccui = :identificacion AND cnro != :id
  • Si rowCount > 0 → Exception 405: "Ya existe un cliente con ese número de identificación"

En partialUpdate():

  1. Exclusividad de cliente defecto:
  • Solo un cliente puede tener defecto = true
  • Al marcar uno, se desmarcan automáticamente los demás

Esquema de Base de Datos

Tabla: ordcon

Nivel: EMPRESA + SUCURSAL (configurable dinámicamente)

Configuración Personalizada Común: Solo EMPRESA (compartir clientes entre sucursales)

Campos Principales:

CampoTipoConstraintsDescripción
cnroDECIMAL(6)PRIMARY KEYCódigo del cliente
cnomVARCHAR(55)NULLNombre del cliente
email_clienteCHAR(40)NULLEmail del cliente
ccuiBIGINTNULLCUIT/DNI del cliente
cdom1VARCHAR(35)NULLDomicilio principal
cdom2VARCHAR(35)NULLDomicilio secundario
ctel1VARCHAR(18)NULLTeléfono principal
ctel2VARCHAR(18)NULLTeléfono secundario
id_localidadINTEGERNULLFK → localidades.cloc
civaDECIMAL(1)NULLFK → ordiva.civa (Condición IVA)
ciibDECIMAL(2)NULLFK → ordiib.ciib (Tipo de IIBB)
cnibVARCHAR(13)NULLNúmero de ingresos brutos
cvenDECIMAL(3)NULLFK → ordven.cven (Vendedor)
cporDECIMAL(16,5)NULLComisión del vendedor
ccarDECIMAL(1)NULLFK → carteras.ccar
cfinDATENULLFecha de alta
cfulDATENULLFecha último movimiento
cctaDECIMAL(1)NULLMarca de Cuenta Corriente (1=Tiene, 0=No tiene)
climDECIMAL(16,5)NULLLímite de crédito
climdiaDECIMAL(4)NULLMargen de facturas impagas
nrolisVARCHAR(3)NULLLista de precios propia
relispCHAR(1)DEFAULT 'N'Relaciona lista propia (S/N)
defectoBOOLEANNOT NULL DEFAULT falseCliente por defecto

Campos Sin Uso (legacy):

  • cdni, cpos, cloc, ctdo, cpro, cfax, ctex, ccon, ciibant, csact, cfna, cnota, cjubil, ccat, cpos2, csini, verinq, czon, cnomfan, vtocui, coridat, id_tipo_cliente, id_actividad_cliente, clj, agencia, conynom, conydni, conydnif, conydnid, empres, empresdom, emprestel, refe1, refe2, cdnif, cdnid, conyfenac, titufenac, estado_civil, password, id_agencia

Índices:

  • Primary Key: cnro

⚠️ FALTA: Índice en ccui para mejorar performance de validación de duplicados

Foreign Keys (no definidas en migración, solo referencias lógicas):

  • id_localidadlocalidades.cloc
  • civaordiva.civa
  • ciibordiib.ciib
  • cvenordven.cven
  • ccarcarteras.ccar

Tablas Relacionadas:

  • rel_ordcon_categoria - Relación con categorías de clientes
  • rel_ordcon_disciplina - Relación con disciplinas (módulo membresías)
  • rel_ordcon_producto - Relación con productos favoritos
  • membresia_ordcon_data - Datos adicionales de membresías

Multi-Tenancy

Niveles Configurables

Por Defecto: [EMPRESA, SUCURSAL]

  • Clientes separados por sucursal

Configuración Común: [EMPRESA]

  • Clientes compartidos entre todas las sucursales

Comando para configurar:

bash
php manage-table-levels.php --set --database=mi_empresa --table=ordcon --levels=1

Propagación de Schema

Backend:

  • ConnectionManager configura search_path automáticamente
  • Schema obtenido del JWT payload ($payload['schema'])

Request Flow:

Cliente → X-Schema Header → AuthMiddleware → JWT Payload → ConnectionManager → SET search_path

Integración con Otros Módulos

Módulo de Cuenta Corriente (CtaCte)

Dependencia: Campos ccta, clim, climdia, cful

Validación de Permisos:

  • Frontend verifica permisos.modulo_ctacte antes de mostrar campos

Modelo Auxiliar:

  • models/modulo-ctacte/Cliente.php para consultas específicas de CtaCte

Módulo de Pedidos

Método: ClienteController::tienePendiente()

Lógica:

  1. Verifica si empresa tiene módulo de pedidos habilitado (empres.pedido)
  2. Consulta Pendiente::hasByCliente($cliente_id)
  3. Retorna boolean

Uso: Endpoint GET con parámetro tiene_pendiente=true

Módulo de Carteras

Campo: ccar

Validación Dinámica:

sql
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'ordcon' AND column_name = 'ccar'

Integración Frontend:

  • Componente React: CarteraSelect.tsx
  • Solo se muestra si empres.cartera = true

Decisiones Arquitectónicas

Por qué endpoints legacy en lugar de Slim?

Observación: Otros módulos usan Slim Framework, pero clientes usa /backend/ordcon.php.

Posibles Razones:

  • Tabla ordcon compartida entre múltiples módulos (ventas, membresías, CRM)
  • Código legacy anterior a la adopción de Slim
  • Migración progresiva aún no completada

Por qué MAX(cnro) + 1 sin transacción explícita?

Riesgo Detectado: Alta concurrencia puede generar duplicados.

Mitigación Actual:

  • Transacción iniciada en backend/ordcon.php (línea 50: $conn->beginTransaction())
  • Rollback en caso de error (línea 63: $conn->rollBack())

Mejora Sugerida: Usar secuencia PostgreSQL o SERIAL para auto-incremento.

Por qué dos modelos Cliente (Venta y CtaCte)?

Separación de Responsabilidades:

  • Venta\Cliente: CRUD completo, scope rico, lógica de negocio
  • CtaCte\Cliente: Consultas simples, campos mínimos, flag de cuenta corriente

Ventaja: Módulo CtaCte puede consumir datos de clientes sin dependencia directa del módulo Ventas.

Por qué validador sin aplicar?

Hipótesis:

  • Validador creado para migración futura a Slim
  • Endpoint legacy usa validaciones directas en modelo
  • Preparación para refactorización

Testing Strategy

Tests Necesarios (No Implementados)

Unit Tests (Controller):

  • ClienteController::getClienteById() con scope min/max
  • ClienteController::getClientes() con diferentes filtros
  • ClienteController::tienePendiente() con/sin pedidos

Unit Tests (Model):

  • Cliente::insertCliente() - validación de duplicados
  • Cliente::updateCliente() - lógica condicional de CtaCte
  • Cliente::partialUpdate() - exclusividad de defecto
  • Cliente::getClientesSSR() - paginación DataTables

Integration Tests:

  • POST /backend/ordcon.php → INSERT con transacción
  • PUT /backend/ordcon.php → UPDATE con validación de duplicados
  • PATCH /backend/ordcon.php → Actualización de defecto exclusivo
  • GET /backend/ordcon.php?serverSide=true → Server-Side Rendering

Edge Cases:

  • Inserción concurrente de clientes (race condition en MAX + 1)
  • Actualización de identificación a valor duplicado
  • Intento de desmarcar único cliente defecto
  • Cliente con carteras en empresa sin módulo de carteras

Performance

Optimizaciones Actuales

Server-Side Rendering:

  • DataTables con paginación server-side
  • Reduce carga de datos en frontend
  • Búsqueda con ILIKE optimizada

Scopes:

  • min: 7 campos para listados
  • max: 23 campos para formularios
  • Reduce transferencia de datos

Mejoras Recomendadas

Índices Faltantes:

sql
-- Mejora validación de duplicados
CREATE UNIQUE INDEX idx_ordcon_ccui ON ordcon(ccui) WHERE ccui IS NOT NULL;

-- Mejora búsquedas de autocomplete
CREATE INDEX idx_ordcon_cnom_trgm ON ordcon USING gin(cnom gin_trgm_ops);

-- Mejora filtro de cliente defecto
CREATE INDEX idx_ordcon_defecto ON ordcon(defecto) WHERE defecto = TRUE;

N+1 Queries:

  • Actualmente: 1 query por cliente para resolver relaciones (localidad, vendedor, condicion_iva, iibb)
  • Mejora: JOIN en query principal o carga en batch

Caching:

  • Datos de configuración (condiciones IVA, IIBB, vendedores) raramente cambian
  • Candidatos para cache con invalidación por eventos

Seguridad

Sanitización de Inputs

Prepared Statements: ✅ Todos los queries usan parámetros preparados

Validación de Tipos: ✅ Casting explícito (int, float, bool)

Autorización

Nivel de Endpoint: ⚠️ No implementado (usa AuthMiddleware genérico de JWT)

Nivel de Campo:

  • Frontend valida permisos.modulo_ctacte para campos de cuenta corriente
  • Frontend valida empres.cartera para campo de carteras

Mejora Recomendada: Middleware de permisos por endpoint (ej: can:clientes.create, can:clientes.update)

Auditoría

⚠️ NO IMPLEMENTADO: El módulo NO registra auditoría de operaciones CUD.

Mejora Recomendada: Implementar AuditableInterface y Auditable trait en service layer.


Preguntas Técnicas Pendientes

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

Ver: Preguntas sobre ABM Clientes


Referencias

  • Código Fuente:

    • Backend: backend/ordcon.php, controller/modulo-venta/ClienteController.php, models/modulo-venta/Cliente.php
    • Validador: Validators/Venta/ClienteValidator.php
    • Migración: migrations/tenancy/20240823200741_new_table_ordcon.php
  • Documentación Relacionada:


⚠️ NOTA IMPORTANTE: Esta documentación fue generada automáticamente analizando el código implementado. Validar cambios futuros contra este baseline.


Versión: 1.0 Última Actualización: 2026-02-11 Analizado por: implemented-code-documenter