Skip to content

Implementación Multi-Schema para Movimientos de Caja y Caja (Tesorería)

Módulo: Tesorería Tipo: Process Estado: ✅ Implementado Fecha: 2025-12-29 Fecha de Implementación: 2025-12-30

📋 Documento Arquitectural General: Sistema de Búsqueda Multi-Schema para Operaciones Transaccionales

Este documento describe la aplicación específica del sistema multi-schema al módulo de Tesorería para operaciones de consulta, eliminación y modificación en tablas transaccionales. Para entender el problema general del search_path de PostgreSQL, la solución basada en ConfigurableMigration, y los patrones arquitecturales, consulte el documento general.


Resumen

Esta es la primera implementación del sistema de búsqueda multi-schema para operaciones transaccionales (consulta, eliminación, modificación), aplicada a las tablas transaccionales del módulo de Tesorería:

  • movimi (movimientos de caja)
  • caja (registros de apertura/cierre de caja)

El objetivo es resolver los problemas operativos cuando se necesita consultar, modificar o eliminar registros (comprobantes, movimientos) que están distribuidos en diferentes schemas según la configuración de niveles de la empresa.


Problema Específico en Tesorería

Configuración de Tablas

Tabla movimi (movimientos de caja):

  • Niveles por defecto: [1, 2, 3] (Empresa, Sucursal, Caja)
  • Configuración típica: [2, 3] (Sucursal y Caja)
  • Descripción: Registros de entradas y salidas de efectivo

Tabla caja (registros de caja):

  • Niveles por defecto: [1, 2, 3] (Empresa, Sucursal, Caja)
  • Configuración típica: [3] (solo Caja)
  • Descripción: Registros de apertura y cierre de caja

Escenarios Problemáticos en Tesorería

Escenario 1: Eliminación de Orden de Pago

Situación:

  • Se registra una orden de pago en suc0001caja0001
  • Los movimientos de caja se guardan en suc0001caja0001
  • Un usuario de suc0001caja0002 intenta eliminar la orden de pago

Problema actual:

Usuario en: suc0001caja0002
search_path: suc0001caja0002, suc0001, public

Al buscar movimientos relacionados:
- Busca solo en suc0001caja0002 (schema actual)
- NO encuentra movimientos en suc0001caja0001
- Solo elimina la orden, dejando movimientos huérfanos ❌

Solución con multi-schema:

Usuario en: suc0001caja0002
Configuración movimi: [2, 3]

Al buscar movimientos relacionados:
- Busca en: suc0001, suc0001caja0001, suc0001caja0002
- Encuentra movimientos en suc0001caja0001
- Elimina orden y TODOS los movimientos ✅

Escenario 2: Registro Manual de Comprobante

Situación:

  • Se registra manualmente un comprobante en suc0001caja0001
  • Los movimientos se guardan en suc0001caja0001
  • Un usuario de suc0001caja0002 necesita dar de baja el comprobante

Problema actual:

  • Sistema busca movimientos solo en suc0001caja0002
  • NO encuentra movimientos en suc0001caja0001
  • Eliminación parcial (comprobante sí, movimientos no)

Solución con multi-schema:

  • Sistema busca en todos los schemas de caja de la sucursal
  • Encuentra y elimina todos los movimientos
  • Eliminación completa sin huérfanos

Escenario 3: Comprobante a Nivel Sucursal con Movimientos a Nivel Caja

Situación:

  • Empresa configura movimi con niveles [2, 3]
  • Un comprobante se registra a nivel sucursal (suc0001)
  • Los movimientos se registran a nivel caja (suc0001caja0001)
  • Se intenta eliminar el comprobante

Problema actual:

Comprobante en: suc0001 (nivel sucursal)
search_path: suc0001, public

Al buscar movimientos:
- Busca en suc0001 y public
- NO busca en suc0001caja0001 (nivel inferior)
- Movimientos quedan huérfanos ❌

Solución con multi-schema:

Comprobante en: suc0001
Configuración movimi: [2, 3]

Al buscar movimientos:
- Busca en: suc0001 Y suc0001caja0001, suc0001caja0002, etc.
- Encuentra movimientos en todos los niveles
- Eliminación completa ✅

Aplicación del Sistema Multi-Schema

Tablas Involucradas

Movimiento de Caja (movimi)

Relaciones críticas:

  • id_comprobante + id_tipo_comprobante → Comprobantes de venta/compra/ctacte
  • id_ctacte → Movimientos de cuenta corriente
  • nrocaj / nro_caja → Caja donde se registró

Operaciones afectadas:

  • Eliminación: Al eliminar comprobante, eliminar movimientos de todos los schemas
  • Consulta: Al consultar comprobante, obtener movimientos de todos los schemas
  • ⚠️ Modificación: Futuro (puede agregarse posteriormente)

Caja

Relación con movimi:

  • Controla el estado (abierta/cerrada) de la caja
  • Si caja está cerrada → movimientos son de solo lectura
  • Consistencia de schema: Si movimiento está en suc0001caja0002, la caja debe consultarse en suc0001caja0002

Validaciones:

  • Antes de eliminar movimiento → verificar que caja esté abierta
  • Si caja está cerrada → rechazar eliminación con mensaje descriptivo

Reglas de Negocio Específicas de Tesorería

RN-TES-001: Validación de estado de caja

Descripción: Antes de eliminar un movimiento de caja, verificar que la caja correspondiente esté abierta (fecha_cierre IS NULL).

Condición: Se intenta eliminar un movimiento de caja encontrado mediante búsqueda multi-schema.

Acción:

  • Para cada movimiento encontrado, identificar su schema de origen
  • Consultar el estado de la caja EN EL MISMO SCHEMA (RA-003)
  • Si la caja está cerrada → rechazar eliminación completa
  • Si todas las cajas están abiertas → proceder con eliminación

Mensaje de error:

"No se puede eliminar: existen movimientos en caja cerrada (caja {nrocaj} del schema {schema})"

RN-TES-002: Consistencia movimiento-caja

Descripción: Un movimiento y su caja siempre deben consultarse del mismo schema para garantizar consistencia.

Ejemplo:

sql
-- Movimiento encontrado en suc0001caja0002

-- ✅ CORRECTO - consultar caja en el mismo schema
SET search_path TO suc0001caja0002, suc0001, public;
SELECT * FROM caja WHERE fecha_cierre IS NULL;

-- ❌ INCORRECTO - consultar caja en schema actual del usuario
SET search_path TO suc0001caja0001, suc0001, public;
SELECT * FROM caja WHERE fecha_cierre IS NULL;

RN-TES-003: Alcance de búsqueda por sucursal

Descripción: La búsqueda de movimientos de caja solo incluye schemas de la sucursal actual del usuario.

Ejemplo:

Usuario en: suc0001caja0001
Configuración movimi: [2, 3]

Schemas a buscar:
- suc0001 (nivel sucursal)
- suc0001caja0001, suc0001caja0002, suc0001caja0003, ... (nivel caja)

NO buscar en:
- suc0002, suc0003 (otras sucursales)
- suc0002caja0001, etc. (cajas de otras sucursales)

Casos de Uso Específicos

UC-TES-001: Eliminar orden de pago con movimientos en otra caja

Actor: Usuario de tesorería desde caja0002

Objetivo: Eliminar una orden de pago registrada en caja0001

Precondiciones:

  • Usuario autenticado con permisos de eliminación en la sucursal
  • Orden de pago registrada en suc0001caja0001 con movimientos
  • Tabla movimi configurada con niveles [2, 3]
  • Caja0001 está abierta

Flujo principal:

  1. Usuario busca la orden de pago por número
  2. Sistema ejecuta búsqueda multi-schema (RA-001):
    • Consulta configuración de movimi → [2, 3]
    • Genera schemas: suc0001, suc0001caja0001, suc0001caja0002
  3. Sistema encuentra orden en suc0001caja0001
  4. Usuario selecciona eliminar
  5. Sistema busca movimientos relacionados en todos los schemas
  6. Sistema encuentra movimientos en suc0001caja0001
  7. Sistema consulta estado de caja en suc0001caja0001 (RN-TES-001, RA-003)
  8. Caja está abierta → validación OK
  9. Sistema inicia transacción atómica
  10. Sistema elimina movimientos de suc0001caja0001
  11. Sistema elimina orden de pago
  12. Sistema confirma transacción
  13. Sistema audita operación con schemas afectados

Postcondiciones:

  • Orden de pago eliminada
  • Movimientos eliminados de todos los schemas
  • Sin datos huérfanos
  • Operación auditada

Flujos alternativos:

  • Caja cerrada: Sistema muestra error y cancela operación
  • Error en eliminación: Rollback completo de todos los schemas

UC-TES-002: Eliminar comprobante manual con validación de caja

Actor: Usuario de caja0001

Objetivo: Eliminar comprobante registrado manualmente que tiene movimientos en múltiples schemas

Precondiciones:

  • Comprobante registrado con movimientos en suc0001 y suc0001caja0001
  • Configuración movimi: [2, 3]

Flujo principal:

  1. Usuario selecciona comprobante a eliminar
  2. Sistema confirma eliminación
  3. Sistema determina schemas relevantes (RA-001):
    • suc0001 (nivel sucursal)
    • suc0001caja0001, suc0001caja0002 (nivel caja)
  4. Sistema busca movimientos en todos los schemas
  5. Sistema encuentra:
    • 2 movimientos en suc0001
    • 1 movimiento en suc0001caja0001
  6. Para movimiento en suc0001caja0001:
    • Consulta caja en suc0001caja0001 (RN-TES-002)
    • Verifica estado → abierta
  7. Sistema inicia transacción
  8. Elimina movimientos de suc0001
  9. Elimina movimiento de suc0001caja0001
  10. Elimina comprobante
  11. Confirma transacción

Postcondiciones:

  • Comprobante y todos los movimientos eliminados
  • Sin registros huérfanos
  • Auditoría completa

UC-TES-003: Rechazo por caja cerrada en otro schema

Actor: Usuario de caja0002

Objetivo: Intentar eliminar comprobante con movimientos en caja cerrada de otro schema

Precondiciones:

  • Comprobante con movimientos en suc0001caja0001
  • Caja0001 está cerrada (fecha_cierre NOT NULL)
  • Usuario opera desde caja0002

Flujo principal:

  1. Usuario selecciona comprobante a eliminar
  2. Sistema busca movimientos en todos los schemas
  3. Sistema encuentra movimiento en suc0001caja0001
  4. Sistema consulta caja en suc0001caja0001 (RN-TES-002)
  5. Detecta fecha_cierre NOT NULL → caja cerrada
  6. Sistema rechaza operación (RN-TES-001)
  7. Sistema muestra mensaje: "No se puede eliminar: existen movimientos en caja cerrada (caja 1 del schema suc0001caja0001)"

Postcondiciones:

  • Operación cancelada
  • Datos sin cambios
  • Intento registrado en auditoría

Configuración Recomendada

Configuración Típica por Tipo de Empresa

Empresa con operación centralizada:

json
{
  "movimi": [2],  // Solo nivel sucursal
  "caja": [2]     // Solo nivel sucursal
}

Una caja por sucursal, datos consolidados

Empresa con múltiples cajas por sucursal:

json
{
  "movimi": [3],  // Solo nivel caja
  "caja": [3]     // Solo nivel caja
}

Control granular por caja, segregación máxima

Empresa mixta (recomendado):

json
{
  "movimi": [2, 3],  // Sucursal y caja
  "caja": [3]        // Solo caja
}

Flexibilidad para movimientos a nivel sucursal y caja

Comando de Configuración

bash
# Ver configuración actual
php manage-table-levels.php --show --database=mi_empresa

# Configurar movimi en niveles Sucursal y Caja
php manage-table-levels.php --set --database=mi_empresa --table=movimi --levels=2,3

# Configurar caja solo en nivel Caja
php manage-table-levels.php --set --database=mi_empresa --table=caja --levels=3

Criterios de Aceptación Específicos

Movimientos de Caja

  • [x] AC-TES-001: Al eliminar un comprobante, se eliminan todos los movimientos de caja relacionados de todos los schemas según configuración de niveles ✅
  • [x] AC-TES-002: La eliminación valida que todas las cajas donde hay movimientos estén abiertas ✅
  • [x] AC-TES-003: Si alguna caja está cerrada, la eliminación se rechaza con mensaje descriptivo indicando el schema ✅
  • [x] AC-TES-004: Al consultar un movimiento en un schema, la caja se consulta en el mismo schema (consistencia) ✅

Caja

  • [x] AC-TES-005: El estado de la caja (abierta/cerrada) se verifica en el schema correcto del movimiento ✅
  • [x] AC-TES-006: Los movimientos de cajas cerradas son de solo lectura (no se pueden eliminar) ✅

Transaccionalidad

  • [x] AC-TES-007: Las eliminaciones que afectan múltiples schemas son atómicas ✅
  • [x] AC-TES-008: Si falla la eliminación en algún schema, se revierten todos los cambios ✅

Auditoría

  • [x] AC-TES-009: Las operaciones multi-schema registran todos los schemas consultados y afectados ✅
  • [x] AC-TES-010: Los rechazos por caja cerrada quedan registrados en auditoría ✅

Impacto en Frontend

IMPORTANTE: La implementación multi-schema es completamente transparente para el frontend de tesorería. No requiere cambios visuales, de componentes, ni de interfaz de usuario.

Sin Cambios Requeridos

  • Formularios de órdenes de pago: Sin cambios
  • Gestión de comprobantes: Sin cambios
  • Botones de eliminación: Sin cambios
  • Confirmaciones: Sin cambios
  • Mensajes de éxito: Sin cambios

Mensajes Mejorados (Opcional)

Mensaje de error mejorado:

Antes: "No se puede eliminar el comprobante"
Ahora:  "No se puede eliminar: existen movimientos en caja cerrada (caja 1 del schema suc0001caja0001)"

Proporciona más contexto para el usuario sin requerir cambios de interfaz


Extensión Futura

Tabla iteban (Movimientos Bancarios)

Estado: Pendiente

Configuración esperada:

json
{
  "iteban": [1, 2]  // Niveles Empresa y Sucursal
}

Casos de uso similares:

  • Eliminar comprobante con movimientos bancarios en múltiples schemas
  • Validar estado de cuenta bancaria

Implementación:

  • Seguir mismo patrón que movimi
  • Agregar reglas de negocio específicas para cuentas bancarias
  • No requiere cambios en infraestructura base (RA-005)

Referencias

Documentación Arquitectural

Documentación Técnica

  • ConfigurableMigration: /server/migrations/migrations/ConfigurableMigration.php
  • Migración movimi: /server/migrations/migrations/tenancy/20240823200758_new_table_movimi.php
  • Migración caja: /server/migrations/migrations/tenancy/20240917132424_newtable_caja.php

Código Relacionado

  • MovimientoCaja Model: /server/models/modulo-tesoreria/MovimientoCaja.php
  • MovimientoCajaService: /server/service/Tesoreria/MovimientoCajaService.php
  • Caja Model: /server/models/modulo-tesoreria/Caja.php

Historial de Cambios

FechaVersiónAutorDescripción
2025-12-303.0SistemaImplementación completa con tests comprehensivos. Actualización de estado a Implementado.
2025-12-292.1SistemaCorrección de enfoque: el sistema es para TODAS las operaciones transaccionales (consulta, eliminación, modificación), no solo eliminación. Consulta es tan prioritaria como eliminación.
2025-12-292.0SistemaReestructuración como documento específico de tesorería. Referencia al documento arquitectural general. Enfoque en aplicación concreta a movimi y caja con casos de uso específicos del módulo.
2025-12-291.0SistemaVersión inicial combinada (general + específica).