← Volver

Normalización — Caso Contabilidad (Facturación, Asientos y Cuentas)

Objetivo

Este ejercicio modela un caso muy común en pymes: un sistema inicial guarda facturas, asientos contables, líneas de detalle, impuestos y pagos en la misma tabla. Veremos cómo normalizar de sin orden1FN2FN3FN y terminaremos con un diseño relacional adecuado para contabilidad (facturas, clientes, cuentas, asientos, partidas).

Características del caso real

  • Facturas con múltiples ítems, impuestos y descuentos por línea.
  • Asientos contables registrados por factura (varias partidas: debe/haber).
  • Clientes y Proveedores como terceros.
  • Necesidad de generar un trial balance (balance de comprobación) y libros de ventas/compras.

1. Tabla original (sin normalizar) — ejemplo típico

Observá cómo se mezclan datos maestros, detalles y listas en la misma fila.

FacturaIDFechaClienteCUIT ItemsCantidadesPrecioUnitImpuestos TotalFacturaAsientoIDPartidas
F0012024-09-05Servicios SRL30-12345678-9 Hosting, Consultoría1,210000,5000IVA(21%), Percepción(3%) 21000A100Debe: Clientes 21000; Haber: Ventas 17025, IVA 3975
F0022024-09-06Comercial Uno20-87654321-0 Producto A10200IVA(21%) 2420A101Debe: Clientes 2420; Haber: Ventas 2000, IVA 420
F0032024-09-06Servicios SRL30-12345678-9 Soporte13000IVA(21%) 3630A102Debe: Clientes 3630; Haber: Ventas 2479, IVA 1151

Problemas evidentes

  • Columnas Items, Cantidades, PrecioUnit contienen listas → violación de 1FN.
  • Partidas contables están en texto libre; no hay estructura para cuentas, montos, tipo (Debe/Haber).
  • Cliente repetido con mismo CUIT → debería ser una entidad independiente.
  • No está claro cómo vincular impuestos por línea vs impuestos por factura.

2. Primera Forma Normal (1FN) — hacer datos atómicos

Convertimos las listas en filas atómicas: una fila por factura × ítem.

FacturaIDFechaClienteCUIT ItemCantidadPrecioUnitImpuestosLineaTotalLinea
F0012024-09-05Servicios SRL30-12345678-9Hosting110000IVA(21%),Per(3%)12100
F0012024-09-05Servicios SRL30-12345678-9Consultoría25000IVA(21%),Per(3%)12100
F0022024-09-06Comercial Uno20-87654321-0Producto A10200IVA(21%)2420
F0032024-09-06Servicios SRL30-12345678-9Soporte13000IVA(21%)3630

Comentarios

  • Ahora cada fila es atómica: fácil calcular totales por factura sumando TotalLinea.
  • Persisten redundancias: cliente repetido, impuestos escritos en texto.
  • Asientos contables aún no estructurados.

3. Segunda Forma Normal (2FN) — eliminar dependencias parciales

Separamos datos que dependen únicamente de parte de la clave compuesta.

Tablas propuestas en 2FN

Facturas

FacturaID (PK)FechaClienteID (FK)TotalFactura
F0012024-09-05C00124200
F0022024-09-06C0022420
F0032024-09-06C0013630

Clientes

ClienteIDNombreCUIT
C001Servicios SRL30-12345678-9
C002Comercial Uno20-87654321-0

DetalleFactura (líneas)

DetalleID (PK)FacturaID (FK)ItemIDCantidadPrecioUnitDescuento
D001F001I0011100000
D002F001I002250000
D003F002I003102000
D004F003I004130000

Items (maestro)

ItemIDDescripción
I001Hosting
I002Consultoría
I003Producto A
I004Soporte

Impuestos (maestro)

ImpuestoIDNombre%
T01IVA21
T02Percepción3

Notas

  • Separar Items e Impuestos como maestros elimina repetición de descripciones e porcentajes.
  • DetalleFactura relaciona factura con item y cantidad; el cálculo de impuestos puede realizarse mediante una tabla adicional que vincule detalle & impuestos.

4. Tercera Forma Normal (3FN) — eliminar dependencias transitivas

Estructuramos asientos contables y partidas como entidades propias para que no dependan transitivamente de la factura.

Tablas nuevas / refinadas

ChartOfAccounts (Cuentas contables)
CuentaIDNombreTipo
1001ClientesActivo
4001Ventas de ProductosIngreso
4002Ventas de ServiciosIngreso
2101IVA Débito FiscalPasivo
Asientos (JournalEntries)
AsientoIDFechaDescripciónOrigen (FacturaID)
A1002024-09-05Venta F001F001
A1012024-09-06Venta F002F002
A1022024-09-06Venta F003F003
Partidas (JournalLines)
LineaIDAsientoIDCuentaIDDebeHaber
L1001A1001001242000
L1002A1004002017025
L1003A100210103975
L1011A101100124200
L1012A101400102000
L1013A10121010420

Ventajas del modelo 3FN

  • Asientos y Partidas son tablas independientes: fácil auditoría y conciliación.
  • ChartOfAccounts centraliza cuentas contables para reportes y saldos.
  • Facturas solo guardan referencia a asientos si corresponde; no contienen texto con partidas.

5. Modelo final (resumen de tablas y relaciones)

Tablas principales (3FN)

  • Clientes: ClienteID (PK), Nombre, CUIT, Domicilio, CondIva
  • Facturas: FacturaID (PK), Fecha, ClienteID (FK), TotalNeto, TotalImpuestos, TotalFactura
  • Items: ItemID (PK), Nombre, Tipo (bien/servicio)
  • DetalleFactura: DetalleID (PK), FacturaID (FK), ItemID (FK), Cantidad, PrecioUnit, Descuento
  • Impuestos: ImpuestoID (PK), Nombre, Porcentaje
  • DetalleImpuesto: ID, DetalleID (FK), ImpuestoID (FK), Monto
  • ChartOfAccounts: CuentaID (PK), Nombre, Tipo
  • JournalEntries: AsientoID (PK), Fecha, Descripción, OrigenTipo, OrigenID
  • JournalLines: LineaID (PK), AsientoID (FK), CuentaID (FK), Debe, Haber
  • Pagos: PagoID (PK), Fecha, FacturaID (FK), Monto, MedioPago

Relaciones clave

  • Facturas 1:N DetalleFactura
  • DetalleFactura 1:N DetalleImpuesto (un ítem puede tener varios impuestos)
  • Facturas 1:1 (opcional) JournalEntries — o 1:N si generan varios asientos
  • JournalEntries 1:N JournalLines

Diagrama ER simplificado (texto)

Clientes —<Factura>— DetalleFactura — DetalleImpuesto
            |
        JournalEntries — JournalLines
            |
         Pagos
    ChartOfAccounts (referenciado por JournalLines)
    

Consultas útiles (ejemplos SQL)

a) Balance de comprobación (trial balance) — suma de debe y haber por cuenta

-- Suma Debe/Haber por cuenta en un período SELECT c.CuentaID, c.Nombre, COALESCE(SUM(jl.Debe),0) AS TotalDebe, COALESCE(SUM(jl.Haber),0) AS TotalHaber, COALESCE(SUM(jl.Debe),0) - COALESCE(SUM(jl.Haber),0) AS Saldo FROM JournalLines jl JOIN ChartOfAccounts c ON jl.CuentaID = c.CuentaID JOIN JournalEntries je ON jl.AsientoID = je.AsientoID WHERE je.Fecha BETWEEN '2024-09-01' AND '2024-09-30' GROUP BY c.CuentaID, c.Nombre ORDER BY c.CuentaID;

b) Libro de Ventas — facturas con totales e impuestos

SELECT f.FacturaID, f.Fecha, cl.Nombre, f.TotalNeto, f.TotalImpuestos, f.TotalFactura FROM Facturas f JOIN Clientes cl ON f.ClienteID = cl.ClienteID WHERE f.Fecha BETWEEN '2024-09-01' AND '2024-09-30' ORDER BY f.Fecha, f.FacturaID;

c) Generar asiento automático a partir de una factura (lógica)

-- Pseudo-SQL/Propuesta lógica: -- 1) Registrar Asiento (JournalEntries) con Origen=FacturaID -- 2) Crear línea Debe: Cuenta Clientes (1001) = TotalFactura -- 3) Crear líneas Haber: por cada venta (cuenta de ventas), y por cada impuesto (cuenta IVA)

Observaciones finales

  • En contabilidad es clave mantener audit trail: cada asiento debe apuntar a su origen (factura, pago).
  • Separar impuestos por detalle evita errores cuando distintos artículos tienen distintas alícuotas.
  • Este modelo facilita conciliaciones bancarias, reportes fiscales y generación de estados contables.
whatsapp