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 orden → 1FN → 2FN → 3FN
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.
| FacturaID | Fecha | Cliente | CUIT |
Items | Cantidades | PrecioUnit | Impuestos |
TotalFactura | AsientoID | Partidas |
| F001 | 2024-09-05 | Servicios SRL | 30-12345678-9 |
Hosting, Consultoría | 1,2 | 10000,5000 | IVA(21%), Percepción(3%) |
21000 | A100 | Debe: Clientes 21000; Haber: Ventas 17025, IVA 3975 |
| F002 | 2024-09-06 | Comercial Uno | 20-87654321-0 |
Producto A | 10 | 200 | IVA(21%) |
2420 | A101 | Debe: Clientes 2420; Haber: Ventas 2000, IVA 420 |
| F003 | 2024-09-06 | Servicios SRL | 30-12345678-9 |
Soporte | 1 | 3000 | IVA(21%) |
3630 | A102 | Debe: 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.
| FacturaID | Fecha | Cliente | CUIT |
Item | Cantidad | PrecioUnit | ImpuestosLinea | TotalLinea |
| F001 | 2024-09-05 | Servicios SRL | 30-12345678-9 | Hosting | 1 | 10000 | IVA(21%),Per(3%) | 12100 |
| F001 | 2024-09-05 | Servicios SRL | 30-12345678-9 | Consultoría | 2 | 5000 | IVA(21%),Per(3%) | 12100 |
| F002 | 2024-09-06 | Comercial Uno | 20-87654321-0 | Producto A | 10 | 200 | IVA(21%) | 2420 |
| F003 | 2024-09-06 | Servicios SRL | 30-12345678-9 | Soporte | 1 | 3000 | IVA(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) | Fecha | ClienteID (FK) | TotalFactura |
| F001 | 2024-09-05 | C001 | 24200 |
| F002 | 2024-09-06 | C002 | 2420 |
| F003 | 2024-09-06 | C001 | 3630 |
Clientes
| ClienteID | Nombre | CUIT |
| C001 | Servicios SRL | 30-12345678-9 |
| C002 | Comercial Uno | 20-87654321-0 |
DetalleFactura (líneas)
| DetalleID (PK) | FacturaID (FK) | ItemID | Cantidad | PrecioUnit | Descuento |
| D001 | F001 | I001 | 1 | 10000 | 0 |
| D002 | F001 | I002 | 2 | 5000 | 0 |
| D003 | F002 | I003 | 10 | 200 | 0 |
| D004 | F003 | I004 | 1 | 3000 | 0 |
Items (maestro)
| ItemID | Descripción |
| I001 | Hosting |
| I002 | Consultoría |
| I003 | Producto A |
| I004 | Soporte |
Impuestos (maestro)
| ImpuestoID | Nombre | % |
| T01 | IVA | 21 |
| T02 | Percepción | 3 |
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)
| CuentaID | Nombre | Tipo |
| 1001 | Clientes | Activo |
| 4001 | Ventas de Productos | Ingreso |
| 4002 | Ventas de Servicios | Ingreso |
| 2101 | IVA Débito Fiscal | Pasivo |
Asientos (JournalEntries)
| AsientoID | Fecha | Descripción | Origen (FacturaID) |
| A100 | 2024-09-05 | Venta F001 | F001 |
| A101 | 2024-09-06 | Venta F002 | F002 |
| A102 | 2024-09-06 | Venta F003 | F003 |
Partidas (JournalLines)
| LineaID | AsientoID | CuentaID | Debe | Haber |
| L1001 | A100 | 1001 | 24200 | 0 |
| L1002 | A100 | 4002 | 0 | 17025 |
| L1003 | A100 | 2101 | 0 | 3975 |
| L1011 | A101 | 1001 | 2420 | 0 |
| L1012 | A101 | 4001 | 0 | 2000 |
| L1013 | A101 | 2101 | 0 | 420 |
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.