Delphi + SQLite: transacciones, Rollback, logs y consistencia de datos en PC Shop
En PC Shop sigo avanzando desde un CRUD que simplemente funciona hacia una aplicación de escritorio más robusta y mantenible.
En esta sesión el objetivo no ha sido añadir nuevas pantallas ni nuevas funciones visibles, sino trabajar aspectos que resultan fundamentales cuando una aplicación empieza a depender de una base de datos: transacciones, tratamiento de errores, diagnóstico, medición y consistencia entre SQLite y los objetos que mantiene la aplicación en memoria.
La arquitectura continúa siendo sencilla:
Formulario VCL
↓
TProducto
↓
Data Module
↓
FireDAC
↓
SQLite
Pero sobre esa estructura he añadido varios mecanismos para que un fallo de base de datos no deje la aplicación en un estado incoherente.
1. La regla principal: primero SQLite, después memoria
PC Shop mantiene una colección de objetos TProducto en
FProductos, que posteriormente se representa en un
TStringGrid.
Esto plantea una cuestión importante: ¿qué ocurre si modificamos primero la colección en memoria y después SQLite rechaza la operación?
Podríamos terminar con este estado:
FProductos ↓ producto aparentemente añadido TStringGrid ↓ producto visible SQLite ↓ INSERT fallido
La interfaz mostraría entonces información que realmente no existe en la base de datos.
La regla utilizada en PC Shop es justamente la contraria:
PRIMERO SQLite
↓
¿operación confirmada?
↓
sí
↓
modificar FProductos
↓
actualizar interfaz
Si SQLite devuelve un error, la colección y la interfaz permanecen intactas.
2. Introducción a las transacciones
El siguiente paso ha sido introducir una transacción FireDAC en la operación de alta de productos.
El esquema básico es:
StartTransaction
↓
operación SQL
↓
¿correcta?
┌───┴───┐
sí no
↓ ↓
Commit Rollback
¿Qué hace StartTransaction?
StartTransaction indica que comienza una unidad de trabajo sobre la base
de datos.
Los cambios realizados a partir de ese momento todavía pueden confirmarse o descartarse.
¿Qué significa Commit?
Cuando la operación termina correctamente utilizamos:
FDConnection1.Commit;
Con Commit confirmamos los cambios realizados dentro de la transacción.
Conceptualmente:
SQL correcto ↓ Commit ↓ cambio confirmado
¿Qué significa Rollback?
Rollback hace lo contrario: cancela la transacción y revierte los cambios
que todavía no se habían confirmado.
FDConnection1.Rollback;
Esto resulta especialmente importante cuando una operación está formada por varias sentencias SQL.
Por ejemplo:
StartTransaction
↓
INSERT A
↓
INSERT B
↓
ERROR
↓
Rollback
El objetivo es evitar que solo quede ejecutada una parte de la operación.
¿Y qué es InTransaction?
En el tratamiento de excepciones utilizo:
if FDConnection1.InTransaction then
FDConnection1.Rollback;
InTransaction indica si la conexión tiene actualmente una transacción
activa.
Es decir:
StartTransaction → InTransaction = True Commit → InTransaction = False Rollback → InTransaction = False
De esta forma no intentamos ejecutar un Rollback si ya no existe una
transacción que cancelar.
3. Aplicación de la transacción al INSERT
La operación de alta sigue aproximadamente este flujo:
StartTransaction
↓
INSERT
↓
RowsAffected
↓
¿se insertó una fila?
┌───┴───┐
sí no
↓ ↓
Commit Rollback
↓ ↓
True False
Además, si FireDAC o SQLite lanzan una excepción:
ERROR ↓ except ↓ InTransaction? ↓ Rollback ↓ False
Esto se integra con el comportamiento que ya tenía PC Shop: el formulario solo añade
el objeto a FProductos cuando InsertarProducto devuelve
True.
4. Registrar los errores en un log
Mostrar únicamente un ShowMessage resulta insuficiente para mantener una
aplicación.
Un usuario necesita un mensaje comprensible, pero quien desarrolla o mantiene el programa puede necesitar información mucho más detallada.
Por eso he añadido un registro técnico:
logs\pcshop.log
Cuando se produce una excepción de acceso a datos se guarda información como:
fecha y hora operación código del producto mensaje técnico de FireDAC / SQLite
Por ejemplo, un intento de insertar un código duplicado puede quedar registrado como:
2026-08-26 07:44:38 | INSERT PRODUCTO | Codigo=2 | UNIQUE constraint failed: PRODUCTOS.Codigo
5. Error funcional frente a error técnico
Uno de los objetivos de esta mejora es no tratar todos los problemas de la misma manera.
Error funcional
Es una situación que forma parte del funcionamiento normal de la aplicación.
Por ejemplo:
Ya existe un producto con este código.
No necesitamos mostrar al usuario detalles internos de SQLite para explicar ese problema.
Error técnico
Es un problema relacionado con la infraestructura o la capa de persistencia:
- archivo de base de datos inaccesible;
- SQLite bloqueado;
- problema de conexión;
- excepción FireDAC;
- restricción de base de datos inesperada.
En esos casos la aplicación puede mostrar un mensaje sencillo:
No se ha podido insertar el producto.
Mientras que el detalle completo queda almacenado en el log para poder investigarlo posteriormente.
6. El propio sistema de log también debe ser robusto
Existe además un caso curioso: ¿qué sucede si SQLite falla y, al intentar registrar
el error, también falla la escritura de pcshop.log?
Por ejemplo, por falta de permisos de escritura.
El sistema de logging se protege también mediante control de excepciones.
La idea es:
ERROR SQLite
↓
intentar escribir log
↓
si el log falla
↓
no sustituir el error original
Un mecanismo utilizado para diagnosticar problemas no debería convertirse él mismo en la causa de un nuevo fallo de la aplicación.
7. Medir antes de optimizar
En una sesión anterior ya había utilizado TStopwatch para medir las
búsquedas realizadas mediante SQLite.
Ahora he extendido esta idea al INSERT.
El cronómetro comienza antes de iniciar la transacción:
Cronometro := TStopwatch.StartNew;
y se detiene tanto si la operación termina correctamente como si se produce un error.
Durante desarrollo puedo obtener información del tipo:
INSERT PRODUCTO: 3 ms
o:
INSERT PRODUCTO ERROR: 2 ms
El objetivo no es enseñar esos tiempos al usuario final, sino disponer de una medida cuando sea necesario diagnosticar una operación anormalmente lenta.
La regla es sencilla:
Antes de optimizar, medir.
8. Comprobar la consistencia entre SQLite y memoria
PC Shop mantiene simultáneamente dos representaciones de los productos:
- los registros persistidos en SQLite;
- los objetos
TProductoexistentes enFProductos.
He añadido una comprobación básica para detectar si ambos estados dejan de coincidir.
SQLite ejecuta:
SELECT COUNT(*) AS Total
FROM PRODUCTOS;
y ese resultado se compara con:
FProductos.Count
Cuando la aplicación acaba de cargar toda la tabla, debería cumplirse:
COUNT(*) SQLite = FProductos.Count
Por ejemplo:
SQLite = 25 Memoria = 25 → consistente
Mientras que:
SQLite = 25 Memoria = 24 → posible desincronización
Si se detecta esa diferencia, PC Shop muestra un aviso y además registra el diagnóstico
en pcshop.log.
Esta comprobación se realiza cuando la colección contiene todos los productos, no mientras
se muestra una búsqueda filtrada, ya que en ese caso sería completamente normal que
FProductos.Count fuese inferior al total existente en SQLite.
9. Prueba real: INSERT duplicado
Para probar el tratamiento de errores fue necesario saltar temporalmente la validación de duplicados que ya existe en el formulario.
De esta forma el código repetido llegó realmente a SQLite.
El resultado esperado se confirmó:
INSERT duplicado
↓
SQLite rechaza la operación
↓
excepción
↓
Rollback
↓
InsertarProducto = False
↓
registro en pcshop.log
↓
FProductos no cambia
↓
TStringGrid no cambia
Después se restauró la validación normal del formulario.
Esto deja dos niveles de protección:
Formulario → evita normalmente el duplicado SQLite → protege la integridad si aun así llega hasta la BD
10. Prueba final de persistencia y consistencia
Finalmente se realizaron operaciones normales de alta, modificación y eliminación.
Después de cerrar y volver a abrir PC Shop se comprobó:
- el INSERT correcto permanece almacenado;
- el UPDATE permanece después de reiniciar;
- el producto eliminado no reaparece;
- SQLite y
FProductoscontinúan teniendo el mismo número de productos; - la aplicación continúa funcionando sin avisos de inconsistencia.
Conclusión
Esta sesión no añade una gran característica visual a PC Shop, pero sí incorpora elementos importantes para convertir un CRUD sencillo en una aplicación más resistente:
- transacciones FireDAC;
CommityRollback;- control mediante
InTransaction; - registro técnico de errores;
- separación entre mensaje de usuario y diagnóstico técnico;
- medición con
TStopwatch; - comprobación de consistencia SQLite/memoria;
- programación defensiva.
El objetivo no es añadir arquitectura porque sí, sino introducir mecanismos que ayuden a mantener, diagnosticar y hacer evolucionar una aplicación de escritorio real.
InformatOLI — Desarrollo de software y ciberseguridad desde 1996

Source code en… https://github.com/101aero/pcshopPB