Appearance
Condición IVA - Documentación Técnica Backend
⚠️ DOCUMENTACIÓN RETROSPECTIVA - Generada a partir de código implementado el 2026-02-11
Metadata
Módulo: ventas Feature: Condición IVA (Condiciones frente al IVA) Tipo: Resource (CRUD) Fecha: 2026-02-11 Estado: Implementado (Backend Legacy)
Descripción General
Sistema de gestión de condiciones fiscales frente al IVA para clientes y proveedores. Permite mantener un catálogo de condiciones tributarias (Responsable Inscripto, Monotributo, Consumidor Final, etc.) que determinan el tratamiento impositivo en operaciones de compra/venta.
Propósito de Negocio: Cumplir con regulaciones de AFIP/ARCA sobre emisión de comprobantes fiscales según la condición tributaria de cada cliente/proveedor.
Arquitectura Implementada
🚨 Arquitectura Simplificada (Legacy)
El módulo NO sigue la arquitectura 5-layer DDD estándar de Bautista Backend:
Backend Legacy (ordiva.php)
↓
Controller (CondicionIvaController)
↓
Model (CondicionIva)
↓
Database (ordiva table)Capas ausentes:
- ❌ Service Layer (no hay transacciones complejas)
- ❌ Domain Layer (no hay lógica de negocio compleja)
- ❌ Validator Middleware (validaciones inline en Model)
- ❌ Slim Routes (usa backend legacy)
Ubicación de Archivos
| Componente | Path | Observaciones |
|---|---|---|
| Backend Legacy | backend/ordiva.php | Endpoints REST implementados |
| Controller | controller/modulo-venta/CondicionIvaController.php | Thin wrapper sobre Model |
| Model | models/modulo-venta/CondicionIva.php | CRUD + batch loading |
| Domain | Domain/Ventas/Facturacion/Strategy/Config/CondicionIvaFallback.php | Enum para estrategia fallback |
| Migration | migrations/migrations/tenancy/20240823200738_new_table_ordiva.php | Schema definition |
| Seed | migrations/seeds/tenancy/Ordiva.php | 12 condiciones predefinidas AFIP |
API Endpoints (Backend Legacy)
Base URL
/backend/ordiva.php⚠️ Nota: Este recurso usa el sistema legacy de routing (archivos PHP directos), NO Slim Framework routes.
GET /backend/ordiva.php
Descripción: Listar todas las condiciones de IVA o consultar una específica por ID.
Query Parameters:
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
id | int | No | Código de condición IVA (CIVA). Si se omite, devuelve todas. |
Responses:
200 OK - Lista de condiciones:
json
{
"status": 200,
"message": "Datos recibidos correctamente.",
"data": [
{
"id": 1,
"nombre": "RESP. INSCRIPTO",
"registra_cuit": true,
"discrimina_iva": true,
"abreviacion": "RI",
"defecto": false,
"habilitado": true
}
]
}200 OK - Condición específica (?id=1):
json
{
"status": 200,
"message": "Datos recibidos correctamente.",
"data": {
"id": 1,
"nombre": "RESP. INSCRIPTO",
"registra_cuit": true,
"discrimina_iva": true,
"abreviacion": "RI",
"defecto": false
}
}400 Bad Request - ID inválido:
json
{
"error": "El código proporcionado es inválido"
}PUT /backend/ordiva.php
Descripción: Actualización completa de una condición de IVA.
Request Body:
json
{
"id": 1,
"nombre": "RESPONSABLE INSCRIPTO",
"registra_cuit": true,
"discrimina_iva": true,
"defecto": false
}Validaciones:
id: requerido, enteronombre: requerido, stringregistra_cuit: requerido, booleandiscrimina_iva: requerido, booleandefecto: requerido, boolean
Responses:
204 No Content - Actualización exitosa (sin body)
400 Bad Request - Validación fallida:
json
{
"error": "Debe proporcionarse el código de condición de iva"
}500 Internal Server Error - Error en actualización:
json
{
"error": "Error al modificar la condición de IVA"
}⚠️ Bug identificado: SQL query en línea 104 tiene falta de coma después de ivadiscri:
sql
defecto = :defecto -- FALTA COMA aquí
WHERE civa = :idPATCH /backend/ordiva.php
Descripción: Actualización parcial de una condición de IVA. Actualmente solo soporta actualización del campo defecto.
Request Body:
json
{
"id": 5,
"defecto": true
}Lógica de Negocio Especial:
Si defecto: true:
- Primero desactiva
defecto = FALSEen TODAS las condiciones - Luego activa
defecto = TRUEen la condición especificada
✅ Garantía: Solo UNA condición puede tener defecto = TRUE simultáneamente.
Responses:
204 No Content - Actualización exitosa
400 Bad Request - ID inválido:
json
{
"error": "El código proporcionado es inválido"
}500 Internal Server Error - Error en actualización:
json
{
"error": "Error al modificar la condición de IVA"
}Capa de Modelo
CondicionIva Model
File: models/modulo-venta/CondicionIva.php
Responsabilidades:
- CRUD completo sobre tabla
ordiva - Validaciones de entrada
- Transformación de datos (CASE WHEN para booleans)
- Batch loading para prevenir N+1 queries
Métodos Públicos:
getById(int $id): array
Obtiene una condición de IVA por su código.
Validaciones:
- ID requerido
- ID debe ser entero
Query SQL:
sql
SELECT
CIVA::int AS id,
IDES AS nombre,
ICUIT AS registra_cuit,
CASE WHEN IVADISCRI = 'S' THEN true ELSE false END AS discrimina_iva,
RDES AS abreviacion,
DEFECTO AS defecto
FROM ordiva
WHERE civa = :idRetorno: Array asociativo con datos o [] si no existe.
getAll(): array
Obtiene todas las condiciones de IVA (incluye campo habilitado).
Query SQL:
sql
SELECT
CIVA::int AS id,
IDES AS nombre,
ICUIT AS registra_cuit,
habilitado,
CASE WHEN IVADISCRI = 'S' THEN true ELSE false END AS discrimina_iva,
RDES AS abreviacion,
DEFECTO AS defecto
FROM ordivaRetorno: Array de arrays asociativos.
update(int $id, array $data): bool
Actualización completa de condición de IVA.
Parámetros esperados ($data):
nombre: stringregistra_cuit: booleandiscrimina_iva: boolean (transformado a 'S'/'N')defecto: boolean
Validaciones:
- ID requerido y entero
- Transforma
discrimina_ivaboolean → 'S'/'N'
⚠️ Bug: Falta coma en SQL query (línea 104).
Retorno: true si actualización exitosa, false en caso contrario.
partialUpdate(int $id, array $data): bool
Actualización parcial. Solo implementado para campo defecto.
Lógica Especial:
php
if (isset($data['defecto'])) {
$defecto = (bool) $data['defecto'];
if ($defecto) {
// Desactivar todos los defectos
$this->conn->prepare("UPDATE ordiva SET defecto = FALSE")->execute();
}
// Activar/desactivar el registro específico
$sql = "UPDATE ordiva SET defecto = :defecto WHERE civa = :id";
// ... execute
return true;
}✅ Garantía de Exclusividad: Al activar un defecto, primero desactiva todos los demás.
Retorno: true si actualización exitosa, false si no hay cambios.
getByIds(array $ids): array ⭐
Batch loading para prevenir N+1 queries en consultas masivas.
Parámetros:
$ids: Array de enteros (códigos CIVA)
Proceso:
- Sanitiza IDs (solo enteros)
- Genera placeholders dinámicos
- Ejecuta query IN() con prepared statement
- Indexa resultados por ID para lookup O(1)
Query SQL:
sql
SELECT
CIVA::int AS id,
IDES AS nombre,
ICUIT AS registra_cuit,
CASE WHEN IVADISCRI = 'S' THEN true ELSE false END AS discrimina_iva,
RDES AS abreviacion,
DEFECTO AS defecto
FROM ordiva
WHERE CIVA IN (?, ?, ?) -- Placeholders dinámicosRetorno: Array asociativo indexado por ID:
php
[
1 => ['id' => 1, 'nombre' => 'RI', ...],
5 => ['id' => 5, 'nombre' => 'CF', ...],
]Uso: Módulo Membresía usa este método para enriquecer miembros con sus condiciones IVA sin causar N+1 queries.
Capa de Dominio
CondicionIvaFallback Enum
File: Domain/Ventas/Facturacion/Strategy/Config/CondicionIvaFallback.php
Tipo: Enum PHP 8.1+ (backed enum con valores int)
Propósito: Estrategia fallback para determinar tipo de comprobante (letra A/B/C) en facturación electrónica según condición IVA del cliente.
Casos Definidos:
php
enum CondicionIvaFallback: int
{
// Principales (más usadas)
case RESPONSABLE_INSCRIPTO = 1;
case RESP_NO_INSCRIPTO = 2;
case EXENTO = 4;
case CONSUMIDOR_FINAL = 5;
case MONOTRIBUTO = 6;
// Otras (menos comunes)
case SUJETO_NO_CATEGORIZADO = 7;
case PROVEEDOR_EXTERIOR = 8;
case CLIENTE_EXTERIOR = 9;
case IVA_LIBERADO = 10;
case MONOTRIBUTISTA_SOCIAL = 13;
case IVA_NO_ALCANZADO = 15;
case MON_TRAB_IN_PROM = 16;
}Métodos:
getFallbackLetra(): string
Determina letra de comprobante fallback (A/B/C) según condición IVA.
Lógica (delegada a TipoComprobanteCatalog):
- Letra A: RI, RNI, MON_TRAB_IN_PROM (discriminan IVA)
- Letra B: Monotributo, Exento, IVA Liberado, IVA No Alcanzado
- Letra C: Consumidor Final, Sujeto No Categorizado, clientes/proveedores exterior
requiereCuitEnFallback(): bool
Determina si se requiere CUIT en estrategia fallback.
Retorno:
true: RI, RNI, Exento, Monotributo, MS, IVA Liberado, IVA No Alcanzado, MON_TRAB_IN_PROMfalse: Consumidor Final, Sujeto No Categorizado, clientes/proveedores exterior
Esquema de Base de Datos
Tabla: ordiva
Nivel: EMPRESA ⚠️ (Compartida por todas las sucursales y cajas)
Primary Key: civa (SMALLINT)
Estructura:
| Campo | Tipo | Constraints | Descripción |
|---|---|---|---|
civa | SMALLINT | PRIMARY KEY, NOT NULL | Código/ID de condición IVA (1-16) |
ides | VARCHAR(30) | NOT NULL | Nombre completo de la condición |
rdes | VARCHAR(5) | NOT NULL | Abreviación (RI, CF, MON, etc.) |
icuit | BOOLEAN | NULL | Si requiere registrar CUIT/CUIL |
ivadiscri | CHAR(1) | NULL | Si discrimina IVA ('S'/'N') |
habilitado | BOOLEAN | NOT NULL, DEFAULT TRUE | Si está habilitada para uso |
defecto | BOOLEAN | NOT NULL, DEFAULT FALSE | Condición por defecto (solo UNA puede ser TRUE) |
ipor | DECIMAL(16,5) | NULL | Porcentaje IVA (sin uso actualmente) |
ipor2 | DECIMAL(16,5) | NULL | Segundo porcentaje IVA (sin uso actualmente) |
letra | CHAR(2) | NULL | Letra de comprobante (sin uso, reemplazado por enum) |
Índices: Ninguno adicional (solo PK)
Foreign Keys: Ninguna (tabla de referencia)
Constraints: Ninguno explícito en schema (lógica exclusividad defecto en código)
Schema SQL Completo
sql
CREATE TABLE ordiva (
civa SMALLINT NOT NULL PRIMARY KEY,
ides VARCHAR(30) NOT NULL,
ipor DECIMAL(16,5),
ipor2 DECIMAL(16,5),
icuit BOOLEAN,
ivadiscri CHAR(1),
letra CHAR(2),
rdes VARCHAR(5) NOT NULL,
habilitado BOOLEAN NOT NULL DEFAULT TRUE,
defecto BOOLEAN NOT NULL DEFAULT FALSE
);Datos Predefinidos (Seed)
File: migrations/seeds/tenancy/Ordiva.php
Nivel: EMPRESA
Condiciones AFIP/ARCA (12 registros):
| CIVA | Nombre | Abreviación | Registra CUIT | Discrimina IVA | Habilitado | Defecto |
|---|---|---|---|---|---|---|
| 1 | RESP. INSCRIPTO | RI | ✓ | ✓ | ✓ | ✗ |
| 2 | RESP. NO INSCRIPTO | RNI | ✓ | ✓ | ✗ | ✗ |
| 4 | EXENTO | EXE | ✓ | ✗ | ✓ | ✗ |
| 5 | CONS. FINAL | CF | ✗ | ✗ | ✓ | ✓ |
| 6 | RESP. MONOTRIBUTO | MON | ✓ | ✗ | ✓ | ✗ |
| 7 | SUJETO NO CATEGORIZADO | SNC | ✗ | ✓ | ✗ | ✗ |
| 8 | PROVEEDOR DEL EXTERIOR | EXT | ✗ | ✓ | ✗ | ✗ |
| 9 | CLIENTE DEL EXTERIOR | EXTC | ✗ | ✓ | ✗ | ✗ |
| 10 | IVA LIB. (LEY 19.640) | LIB | ✓ | ✓ | ✗ | ✗ |
| 13 | MONOTRIBUTISTA SOCIAL | MS | ✓ | ✗ | ✗ | ✗ |
| 15 | IVA NO ALCANZADO | INA | ✓ | ✓ | ✗ | ✗ |
| 16 | MON. TRAB. IN. PROM. | MTIP | ✓ | ✓ | ✗ | ✗ |
Nota: Los códigos CIVA corresponden a los códigos oficiales de AFIP/ARCA para facturación electrónica.
Validaciones Implementadas
Validaciones de Estructura (Model Layer)
En getById() y update():
| Campo | Validación | HTTP Code | Mensaje |
|---|---|---|---|
id | Requerido | 400 | "Debe proporcionarse el código de condición de iva" |
id | Debe ser entero | 400 | "El código proporcionado es inválido" |
En partialUpdate():
| Campo | Validación | HTTP Code | Mensaje |
|---|---|---|---|
id | Debe ser entero | 400 | "El código proporcionado es inválido" |
Validaciones de Negocio
Exclusividad de defecto:
- ✅ Implementada: Al activar
defecto = TRUEen un registro, se desactiva en todos los demás. - ⚠️ Implementación: Lógica manual en
partialUpdate(), NO constraint DB. - 🔒 Nivel de garantía: Aplicación (no garantizado si se modifica DB directamente).
Puntos de Integración
Módulos que Consumen CondicionIva
| Módulo | Uso | Tabla Relación |
|---|---|---|
| CtaCte (Clientes) | Campo civa en clientes | ordcon.civa → ordiva.civa |
| Compras (Proveedores) | Campo civa en proveedores | cpdprov.civa → ordiva.civa |
| CRM (Contactos) | Campo civa en contactos | contactos.civa → ordiva.civa |
| Membresía (Miembros) | Enriquecimiento batch con getByIds() | N/A (join virtual) |
| Ventas (Facturación) | Determinar tipo comprobante (letra A/B/C) | Vía CondicionIvaFallback enum |
Dependency Injection (v3.13.2+)
Container Binding (index.php línea 129-133):
php
CondicionIvaModel::class => factory(function ($container) {
$connManager = $container->get(ConnectionManager::class);
$conn = $connManager->get('oficial');
return new CondicionIvaModel($conn);
})Consumido por: Módulo Membresía para inyectar en servicios de enriquecimiento.
Estrategia de Testing
Tests Existentes
Integration Tests:
- ✅
Tests/Integration/Membresia/Domain/Facturacion/CondicionIvaIntegrationTest.php- Valida integración con módulo Membresía
- Prueba enriquecimiento batch con condiciones IVA
Unit Tests:
- ✅
Tests/Unit/Domain/Ventas/Facturacion/Strategy/CondicionIvaFallbackTest.php- Valida enum
CondicionIvaFallback - Prueba
getFallbackLetra()yrequiereCuitEnFallback()
- Valida enum
Tests Faltantes (Recomendados)
Unit Tests que deberían crearse:
php
Tests/Unit/Venta/CondicionIvaModelTest.php
├── testGetByIdWithValidId()
├── testGetByIdWithInvalidId()
├── testGetByIdThrowsExceptionWhenIdNotProvided()
├── testGetAll()
├── testUpdateWithValidData()
├── testUpdateThrowsExceptionWithInvalidId()
├── testPartialUpdateActivatesDefecto()
├── testPartialUpdateDeactivatesOtherDefectos() // Validar exclusividad
├── testGetByIdsWithMultipleIds()
├── testGetByIdsReturnsIndexedArray()
└── testGetByIdsWithEmptyArray()Integration Tests que deberían crearse:
php
Tests/Integration/Venta/CondicionIvaIntegrationTest.php
├── testGetAllReturnsAllRecords()
├── testUpdateChangesRecord()
├── testPartialUpdateEnforcesDefectoExclusivity() // Validar exclusividad en DB real
└── testForeignKeyIntegrityWithCliente() // Validar relación con ordconConsideraciones de Rendimiento
Optimizaciones Implementadas
✅ Batch Loading: Método getByIds() implementado para prevenir N+1 queries en consultas masivas.
Ejemplo de uso:
php
// ❌ N+1 queries (sin batch loading)
foreach ($clientes as $cliente) {
$condicion = $condicionIvaModel->getById($cliente['civa']); // 1 query por cliente
}
// ✅ 1 query total (con batch loading)
$civas = array_column($clientes, 'civa');
$condiciones = $condicionIvaModel->getByIds($civas); // 1 query para todos
foreach ($clientes as $cliente) {
$condicion = $condiciones[$cliente['civa']]; // Lookup O(1)
}Optimizaciones Recomendadas
⚠️ Índices faltantes: Considerar agregar índice en campos consultados frecuentemente:
habilitado(si se filtra por condiciones habilitadas)defecto(si se consulta frecuentemente la condición por defecto)
⚠️ Caché: Condiciones IVA son datos de configuración que casi nunca cambian. Considerar:
- Cache en memoria (APCu, Redis)
- Invalidación solo en PUT/PATCH
Seguridad
Vulnerabilidades Identificadas
⚠️ SQL Injection: Parcialmente mitigado.
- ✅ Métodos
getById(),update(),partialUpdate(),getByIds()usan prepared statements - ❌ Seed file usa concatenación directa en línea 149:phpAunque es seed (no input usuario), debería usar prepared statements.
$query = "SELECT * FROM ordiva WHERE civa = {$row['civa']}";
Auditoría
❌ NO implementada: No hay registro de auditoría en operaciones CUD.
Recomendación: Implementar AuditableInterface + Auditable trait en Service layer (cuando se cree).
Permisos
❌ NO validados: Backend legacy NO valida permisos antes de ejecutar operaciones.
Riesgo: Cualquier usuario autenticado puede modificar condiciones IVA.
Recomendación: Implementar validación de permisos en middleware o Service layer.
Multi-Tenancy
Nivel de Aislamiento
Nivel: EMPRESA (definido en migration línea 30)
Implicaciones:
- ✅ Tabla creada SOLO en schema
public - ✅ Compartida por TODAS las sucursales de la empresa
- ✅ Compartida por TODAS las cajas de todas las sucursales
- ✅ Datos consistentes en toda la organización
Schema search_path: Configurado automáticamente por ConnectionMiddleware vía header X-Schema.
Deuda Técnica Identificada
| Ítem | Severidad | Descripción | Recomendación |
|---|---|---|---|
| Arquitectura Legacy | 🟡 Media | No usa Slim routes, Service layer, Validator middleware | Migrar a arquitectura 5-layer DDD |
| Bug en SQL | 🔴 Alta | Falta coma en update() línea 104 | Corregir inmediatamente |
Constraint defecto | 🟡 Media | Exclusividad de defecto solo en código, no DB | Agregar constraint CHECK o trigger |
| Tests faltantes | 🟡 Media | No hay tests unitarios del Model | Crear tests según sección "Estrategia de Testing" |
| Auditoría | 🟢 Baja | No hay log de auditoría en CUD | Implementar cuando se cree Service layer |
| Permisos | 🔴 Alta | No valida permisos en operaciones | Implementar validación de permisos |
| Campos sin uso | 🟢 Baja | ipor, ipor2, letra no se usan | Considerar deprecar en futuras versiones |
| SQL Injection en Seed | 🟡 Media | Seed usa concatenación directa | Cambiar a prepared statements |
Plan de Migración Sugerido
Fase 1: Correcciones Críticas (Inmediato)
- ✅ Corregir bug SQL en
update()(falta coma línea 104) - ✅ Implementar validación de permisos
- ✅ Crear tests unitarios del Model
Fase 2: Modernización Arquitectónica (Corto Plazo)
- ✅ Crear Slim routes en
Routes/Venta/CondicionIvaRoute.php - ✅ Crear Validator middleware
Validators/Venta/CondicionIvaValidator.php - ✅ Crear Service layer
service/Venta/CondicionIvaService.phpcon:- Transaction management
- Audit logging (Auditable trait)
- Business validations
- ✅ Agregar constraint DB para exclusividad de
defecto
Fase 3: Optimizaciones (Mediano Plazo)
- ✅ Implementar caché en memoria para condiciones IVA
- ✅ Agregar índices en campos consultados frecuentemente
- ✅ Crear tests de integración completos
Referencias
- Arquitectura Bautista Backend - Guía completa de arquitectura 5-layer DDD
- Multi-Tenancy - Conceptos de multi-tenancy schema-based
- Migration System - Sistema de migraciones con ConfigurableMigration
Historial de Cambios
| Versión | Fecha | Cambios |
|---|---|---|
| v3.13.2 | 2025-XX-XX | Agregado DI container binding para CondicionIvaModel |
| v3.10.0+ | 2024-08-23 | Campo defecto agregado en migration |
| Legacy | 2024-08-23 | Implementación inicial con backend legacy |
⚠️ NOTA IMPORTANTE: Esta documentación fue generada automáticamente analizando el código implementado. Se recomienda:
- Corregir bug SQL en
update()antes de usar en producción - Implementar validación de permisos para evitar modificaciones no autorizadas
- Validar con equipo de desarrollo que la lógica de exclusividad de
defectocumple requisitos de negocio - Crear tests según plan sugerido para garantizar estabilidad
- Considerar migración a arquitectura 5-layer DDD para consistencia con el resto del sistema