diff --git a/account_reconcile_bg/README.rst b/account_reconcile_bg/README.rst
new file mode 100644
index 00000000..95efee3c
--- /dev/null
+++ b/account_reconcile_bg/README.rst
@@ -0,0 +1,81 @@
+Account Reconcile Background
+============================
+
+This module enables background processing for bank reconciliation operations when dealing with large payment batches, preventing timeouts and improving user experience.
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Overview
+========
+
+When reconciling large payment batches (e.g., multiple payments included in a single batch) with a bank statement line, the operation can take a long time and may cause timeouts. This module solves this problem by automatically processing large reconciliations in the background, allowing users to continue working while the reconciliation completes.
+
+Features
+========
+
+* **Automatic Background Processing**: Large reconciliations are automatically sent to background processing
+* **Configurable Threshold**: System parameter to control when background processing kicks in (default: 50 lines)
+* **User Notifications**: Users receive notifications when background reconciliation completes
+* **No UI Blocking**: Users can continue reconciling other transactions while large batches process
+* **Seamless Integration**: Works transparently with existing bank reconciliation workflow
+
+How It Works
+============
+
+The module monitors the number of lines being reconciled in the bank reconciliation widget:
+
+1. When validating a reconciliation, it counts the number of source lines
+2. If the count is **below the threshold** (default 50), the reconciliation proceeds normally (synchronous)
+3. If the count is **above the threshold**, the reconciliation is enqueued as a background job
+4. The user receives an immediate success notification and can continue working
+5. When the background job completes, the user is notified via internal message
+
+Configuration
+=============
+
+The threshold for background processing can be configured via system parameters:
+
+* Navigate to **Settings > Technical > Parameters > System Parameters**
+* Find or create the parameter ``account_reconcile_bg.lines_threshold``
+* Default value: ``50``
+* Set to a higher value to process larger reconciliations synchronously
+* Set to a lower value to send more reconciliations to background
+
+Technical Details
+=================
+
+Dependencies
+------------
+
+* ``account_accountant``: Odoo Enterprise accounting module with bank reconciliation
+* ``base_bg``: Background job processing system
+
+Model Inheritance
+-----------------
+
+The module inherits from ``bank.rec.widget`` and overrides:
+
+* ``_js_action_validate()``: Detects large reconciliations and routes to background
+* ``_validate_in_background()``: Enqueues the job using base_bg
+* ``_do_validate()``: Executes the actual validation in background
+
+Credits
+=======
+
+Authors
+-------
+
+* ADHOC SA
+
+Contributors
+------------
+
+* ADHOC SA
+
+Maintainers
+-----------
+
+This module is maintained by ADHOC SA.
diff --git a/account_reconcile_bg/__init__.py b/account_reconcile_bg/__init__.py
new file mode 100644
index 00000000..3275ac2a
--- /dev/null
+++ b/account_reconcile_bg/__init__.py
@@ -0,0 +1,2 @@
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+from . import models
diff --git a/account_reconcile_bg/__manifest__.py b/account_reconcile_bg/__manifest__.py
new file mode 100644
index 00000000..7d7698b3
--- /dev/null
+++ b/account_reconcile_bg/__manifest__.py
@@ -0,0 +1,37 @@
+##############################################################################
+#
+# Copyright (C) 2026 ADHOC SA (http://www.adhoc.com.ar)
+# All Rights Reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+{
+ "name": "Account Reconcile Background",
+ "version": "18.0.1.0.0",
+ "category": "Accounting",
+ "author": "ADHOC SA",
+ "website": "https://www.adhoc.com.ar",
+ "license": "AGPL-3",
+ "summary": "Process bank reconciliation in background for large payment batches",
+ "depends": [
+ "account_accountant",
+ "base_bg",
+ ],
+ "data": [
+ "data/ir_config_parameter_data.xml",
+ ],
+ "installable": True,
+ "auto_install": False,
+}
diff --git a/account_reconcile_bg/data/ir_config_parameter_data.xml b/account_reconcile_bg/data/ir_config_parameter_data.xml
new file mode 100644
index 00000000..b24cd47f
--- /dev/null
+++ b/account_reconcile_bg/data/ir_config_parameter_data.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+ account_reconcile_bg.lines_threshold
+ 100
+
+
+
diff --git a/account_reconcile_bg/models/__init__.py b/account_reconcile_bg/models/__init__.py
new file mode 100644
index 00000000..da979872
--- /dev/null
+++ b/account_reconcile_bg/models/__init__.py
@@ -0,0 +1,5 @@
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+from . import bank_rec_widget
+from . import account_bank_statement_line
+from . import account_move_line
+from . import bg_job
diff --git a/account_reconcile_bg/models/account_bank_statement_line.py b/account_reconcile_bg/models/account_bank_statement_line.py
new file mode 100644
index 00000000..4e4102c7
--- /dev/null
+++ b/account_reconcile_bg/models/account_bank_statement_line.py
@@ -0,0 +1,84 @@
+##############################################################################
+# For copyright and license notices, see __manifest__.py file in module root
+# directory
+##############################################################################
+import logging
+
+from markupsafe import Markup
+from odoo import _, api, fields, models
+from odoo.exceptions import UserError
+
+
+class AccountBankStatementLine(models.Model):
+ _inherit = "account.bank.statement.line"
+
+ reconciliation_in_background = fields.Boolean(
+ string="Reconciliation in Background",
+ default=False,
+ readonly=True,
+ help="Indicates that this line is being reconciled in background",
+ )
+
+ def _bg_validate_reconciliation(self, selected_aml_ids=None):
+ """
+ Método ejecutado en background para validar la conciliación.
+ Se llama desde el job de base_bg.
+
+ :param selected_aml_ids: IDs de las líneas seleccionadas por el usuario
+ """
+ self.ensure_one()
+ _logger = logging.getLogger(__name__)
+
+ # Preparar datos para mensaje
+ base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url")
+ st_line_url = f"{base_url}/odoo/account.bank.statement.line/{self.id}"
+ st_line_name = self.name or f"Line {self.id}"
+
+ try:
+ # Crear el widget de conciliación
+ wizard = self.env["bank.rec.widget"].with_context(default_st_line_id=self.id).new({})
+
+ _logger.info(f"[BG] Wizard created for st_line {self.id}")
+
+ # Agregar las líneas al widget correctamente usando el método interno
+ if selected_aml_ids:
+ amls = self.env["account.move.line"].browse(selected_aml_ids)
+ wizard._action_add_new_amls(amls, allow_partial=False)
+
+ # Ejecutar la validación con el context manager
+ with wizard._action_validate_method():
+ wizard._action_validate()
+
+ # Retornar mensaje de éxito
+ return Markup(
+ _("Bank reconciliation completed successfully:
%s")
+ % (st_line_url, st_line_name)
+ )
+ except Exception as e:
+ return Markup(
+ _("Bank reconciliation failed:
%s
Error: %s")
+ % (st_line_url, st_line_name, str(e))
+ )
+ finally:
+ self.write({"reconciliation_in_background": False})
+
+ @api.constrains(
+ "amount",
+ "amount_currency",
+ "currency_id",
+ )
+ def _check_reconciliation_in_background(self):
+ """Valida que no se modifiquen líneas en proceso de conciliación background."""
+ if self.env.context.get("bg_job"):
+ return
+ for line in self:
+ if line.reconciliation_in_background:
+ raise UserError(
+ _(
+ "Cannot modify payment lines that are being reconciled in background. "
+ "Please wait until the reconciliation process is complete.\n"
+ "Journal Entry (id): %(entry)s (%(id)s)",
+ entry=line.move_id.name,
+ id=line.move_id.id,
+ )
+ )
diff --git a/account_reconcile_bg/models/account_move_line.py b/account_reconcile_bg/models/account_move_line.py
new file mode 100644
index 00000000..17d33f13
--- /dev/null
+++ b/account_reconcile_bg/models/account_move_line.py
@@ -0,0 +1,16 @@
+##############################################################################
+# For copyright and license notices, see __manifest__.py file in module root
+# directory
+##############################################################################
+from odoo import fields, models
+
+
+class AccountMoveLine(models.Model):
+ _inherit = "account.move.line"
+
+ reconciliation_in_background = fields.Boolean(
+ string="Reconciliation in Background",
+ default=False,
+ readonly=True,
+ help="Indicates that this line is being reconciled in background",
+ )
diff --git a/account_reconcile_bg/models/bank_rec_widget.py b/account_reconcile_bg/models/bank_rec_widget.py
new file mode 100644
index 00000000..1fb4f9fb
--- /dev/null
+++ b/account_reconcile_bg/models/bank_rec_widget.py
@@ -0,0 +1,120 @@
+##############################################################################
+# For copyright and license notices, see __manifest__.py file in module root
+# directory
+##############################################################################
+import logging
+
+from odoo import _, models
+from odoo.exceptions import UserError
+
+
+class BankRecWidget(models.Model):
+ _inherit = "bank.rec.widget"
+
+ def _js_action_validate(self):
+ """
+ Override para procesar conciliaciones grandes en background.
+ Si hay muchas líneas (> threshold), usa base_bg para procesarlo en 2do plano.
+ """
+ self.ensure_one()
+
+ # Verificar si ya está procesando en background
+ if self.st_line_id.reconciliation_in_background:
+ raise UserError(
+ _("This reconciliation is already being processed in background. Please wait until it finishes.")
+ )
+
+ # Invalidar caché y refrescar para obtener el estado actual desde la BD
+ self.selected_aml_ids.invalidate_recordset(fnames=["reconciliation_in_background"])
+
+ # Verificar si alguna de las líneas seleccionadas ya está en background (en cualquier extracto)
+ lines_in_bg = self.selected_aml_ids.filtered("reconciliation_in_background")
+ if lines_in_bg:
+ raise UserError(
+ _(
+ "Some of the selected payment lines (%s) are already being reconciled in background on another statement. "
+ "Please wait until they finish or select different lines."
+ )
+ % len(lines_in_bg)
+ )
+
+ # Obtener el umbral de líneas desde parámetros del sistema (default: 50)
+ threshold = int(self.env["ir.config_parameter"].sudo().get_param("account_reconcile_bg.lines_threshold", "50"))
+
+ # Contar las líneas seleccionadas para conciliar
+ lines_count = len(self.selected_aml_ids)
+
+ # DEBUG: Log para verificar
+
+ # Si hay pocas líneas, ejecutar el proceso normal de manera sincrónica
+ if lines_count < threshold:
+ return super()._js_action_validate()
+
+ # Si hay muchas líneas, procesar en background
+ return self._validate_in_background()
+
+ def _validate_in_background(self):
+ """
+ Encola la validación de conciliación en background usando base_bg.
+ Nota: Como bank.rec.widget no se persiste, encolamos usando st_line_id.
+ """
+ self.ensure_one()
+
+ _logger = logging.getLogger(__name__)
+
+ # Marcar la línea de extracto y las líneas de pago como procesando en background
+ self.st_line_id.write({"reconciliation_in_background": True})
+ self.selected_aml_ids.write({"reconciliation_in_background": True})
+
+ # Flush para asegurar que los cambios se escriben inmediatamente en la BD
+ # Esto previene condiciones de carrera donde otro usuario podría conciliar las mismas líneas
+ self.env.flush_all()
+
+ # Capturar los IDs antes de encolar
+ selected_ids = self.selected_aml_ids.ids
+ _logger.info(f"[account_reconcile_bg] Capturing selected_aml_ids: {selected_ids}")
+
+ try:
+ # Encolar el job usando la línea de extracto (modelo persistente)
+ _action, _jobs = self.env["base.bg"].bg_enqueue_records(
+ self.st_line_id,
+ "_bg_validate_reconciliation",
+ threshold=1, # Un job por línea
+ name=_("Bank Reconciliation: %s") % self.st_line_id.name,
+ priority=5, # Alta prioridad
+ selected_aml_ids=selected_ids, # Pasar solo los IDs (lista de enteros)
+ )
+ _logger.info("[account_reconcile_bg] Job enqueued successfully")
+ except Exception:
+ # Si falla al encolar, limpiar los flags
+ self.st_line_id.write({"reconciliation_in_background": False})
+ self.selected_aml_ids.write({"reconciliation_in_background": False})
+ raise
+
+ # Enviar notificación al usuario usando el bus
+ self.env["bus.bus"]._sendone(
+ self.env.user.partner_id,
+ "simple_notification",
+ {
+ "type": "success",
+ "message": _(
+ "This reconciliation is being processed in background. You will be notified when it's done."
+ ),
+ },
+ )
+
+ # Configurar el comando para el widget
+ self.return_todo_command = {"done": True}
+
+ # Retornar vacío - el widget usa return_todo_command
+ return
+
+ def _do_validate(self):
+ """
+ Método que ejecuta la validación real en background.
+ Se llama desde el job de base_bg.
+ """
+ self.ensure_one()
+ # Ejecutar la validación usando el método context manager
+ with self._action_validate_method():
+ self._action_validate()
diff --git a/account_reconcile_bg/models/bg_job.py b/account_reconcile_bg/models/bg_job.py
new file mode 100644
index 00000000..c6e4a321
--- /dev/null
+++ b/account_reconcile_bg/models/bg_job.py
@@ -0,0 +1,42 @@
+##############################################################################
+# For copyright and license notices, see __manifest__.py file in module root
+# directory
+##############################################################################
+from odoo import models
+
+
+class BgJob(models.Model):
+ _inherit = "bg.job"
+
+ def cancel(self, message: str | None = None):
+ """Override para limpiar el flag cuando se cancela el job."""
+ res = super().cancel(message=message)
+ self.filtered(
+ lambda j: j.model == "account.bank.statement.line" and j.method == "_bg_validate_reconciliation"
+ )._clean_reconciliation_flag()
+ return res
+
+ def fail(self, error_message: str):
+ """Override para limpiar el flag cuando falla el job."""
+ res = super().fail(error_message)
+ self.filtered(
+ lambda j: j.model == "account.bank.statement.line" and j.method == "_bg_validate_reconciliation"
+ )._clean_reconciliation_flag()
+ return res
+
+ def _clean_reconciliation_flag(self):
+ """Limpia el flag reconciliation_in_background para jobs de conciliación."""
+ for job in self:
+ kwargs = job.kwargs_json or {}
+ # Limpiar flag de la línea de extracto
+ record_ids = kwargs.get("_record_ids", [])
+ if record_ids:
+ lines = self.env["account.bank.statement.line"].browse(record_ids).exists()
+ if lines:
+ lines.write({"reconciliation_in_background": False})
+ # Limpiar flag de las líneas de pago (solo las que existen)
+ selected_aml_ids = kwargs.get("selected_aml_ids", [])
+ if selected_aml_ids:
+ amls = self.env["account.move.line"].browse(selected_aml_ids).exists()
+ if amls:
+ amls.write({"reconciliation_in_background": False})
diff --git a/account_reconcile_bg/tests/__init__.py b/account_reconcile_bg/tests/__init__.py
new file mode 100644
index 00000000..e414fa7e
--- /dev/null
+++ b/account_reconcile_bg/tests/__init__.py
@@ -0,0 +1,2 @@
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+from . import test_account_reconcile_bg
diff --git a/account_reconcile_bg/tests/test_account_reconcile_bg.py b/account_reconcile_bg/tests/test_account_reconcile_bg.py
new file mode 100644
index 00000000..5f8c1cc0
--- /dev/null
+++ b/account_reconcile_bg/tests/test_account_reconcile_bg.py
@@ -0,0 +1,91 @@
+##############################################################################
+# For copyright and license notices, see __manifest__.py file in module root
+# directory
+##############################################################################
+from odoo.addons.account_accountant.tests.test_bank_rec_widget_common import (
+ TestBankRecWidgetCommon,
+)
+from odoo.tests import tagged
+
+
+@tagged("post_install", "-at_install")
+class TestAccountReconcileBg(TestBankRecWidgetCommon):
+ """Test que la conciliación se envía a background cuando hay muchas líneas."""
+
+ def _create_test_invoices(self, count=10):
+ """Crea facturas de prueba para conciliar."""
+ invoices = self.env["account.move"]
+ for i in range(count):
+ invoice = self._create_invoice_line(
+ "out_invoice",
+ invoice_line_ids=[{"price_unit": 100.0}],
+ )
+ invoices |= invoice.move_id
+ return invoices
+
+ def test_sync_below_threshold(self):
+ """Con pocas líneas (< threshold) debe procesar sincrónico."""
+ self.env["ir.config_parameter"].sudo().set_param("account_reconcile_bg.lines_threshold", "3")
+
+ # Crear facturas y línea de extracto (2 < 3 = sync)
+ invoices = self._create_test_invoices(count=2)
+ st_line = self._create_st_line(amount=200.0)
+
+ # Contar jobs antes
+ jobs_before = self.env["bg.job"].search_count([])
+
+ # Crear widget y seleccionar facturas
+ wizard = self.env["bank.rec.widget"].with_context(default_st_line_id=st_line.id).new({})
+
+ # Simular selección de líneas
+ invoice_lines = invoices.line_ids.filtered(lambda l: l.account_id.account_type == "asset_receivable")
+ wizard.selected_aml_ids = invoice_lines
+
+ # Validar NO debe crear job (2 < 3)
+ jobs_after = self.env["bg.job"].search_count([])
+ self.assertEqual(jobs_before, jobs_after, "No debe crear jobs en sync")
+
+ def test_background_above_threshold(self):
+ """Con muchas líneas (>= threshold) debe ir a background y ejecutar correctamente."""
+ self.env["ir.config_parameter"].sudo().set_param("account_reconcile_bg.lines_threshold", "2")
+
+ # Crear facturas y línea de extracto (3 >= 2 = background)
+ invoices = self._create_test_invoices(count=3)
+ st_line = self._create_st_line(amount=300.0)
+
+ # Crear widget y seleccionar facturas
+ wizard = self.env["bank.rec.widget"].with_context(default_st_line_id=st_line.id).new({})
+ invoice_lines = invoices.line_ids.filtered(lambda l: l.account_id.account_type == "asset_receivable")
+ wizard.selected_aml_ids = invoice_lines
+
+ # Validar - debe crear job
+ wizard._js_action_validate()
+
+ # Buscar el job creado (por modelo, método y orden por fecha)
+ job = self.env["bg.job"].search(
+ [
+ ("model", "=", "account.bank.statement.line"),
+ ("method", "=", "_bg_validate_reconciliation"),
+ ],
+ order="create_date desc",
+ limit=1,
+ )
+ self.assertTrue(job, "Debe crear un job en background")
+ self.assertEqual(job.state, "enqueued", "El job debe estar encolado")
+ self.assertTrue(st_line.reconciliation_in_background, "El flag debe estar activo")
+
+ # Ejecutar el método directamente simulando el contexto que setea bg.job.run()
+ selected_aml_ids = job.kwargs_json.get("selected_aml_ids", [])
+ st_line.with_context(bg_job=True, bg_job_id=job.id)._bg_validate_reconciliation(
+ selected_aml_ids=selected_aml_ids
+ )
+
+ # Verificar que el flag se limpió
+ self.assertFalse(st_line.reconciliation_in_background, "El flag debe estar en False al terminar")
+
+ # Verificar que la línea de extracto está conciliada
+ self.assertTrue(st_line.is_reconciled, "La línea debe estar conciliada")
+
+ # Verificar que las facturas están conciliadas
+ for invoice in invoices:
+ self.assertEqual(invoice.payment_state, "paid", f"La factura {invoice.name} debe estar pagada")