Skip to Content

El Ataque Fantasma: Cuando tu SQL en Odoo Devuelve Casi Nada (y Debería Devolverlo Todo)

🐛 El Problema
June 12, 2026 by
BloVox

Una historia de reconciliación bancaria, campos equivocados, y la verdad oculta en account.partial_reconcile.


"Es una trampa." — Almirante Ackbar, probablemente viendo tu reporte SAT de marzo.

🐛 El Problema

Imaginá que sos una empresa que necesita generar un reporte mensual de ventas para el fisco. Tenés 30.000 facturas emitidas en marzo. Tu servidor SQL ejecuta una acción largamente probada, esperás unos segundos, y... 125 registros. Ciento veinticinco. Contra los treinta mil que sabés que existen. Es como pedirle al Emperador que disuelva el Senado y que te devuelva solo 125 sistemas estelares. Algo no cierra.

No es un error de dedo. No es un filtro mal puesto. Es un bug silencioso que vive en la oscuridad de los JOINs, esperando a que corras tu reporte mensual a las 5 de la tarde del viernes. Justo antes del fin de semana largo.

Este caso real —que vamos a recorrer como si fuéramos Luke entrando en la Estrella de la Muerte con los planos, pero con más café y menos wookiees masticando— trata de una acción de servidor en Odoo que procesaba un reporte SAT. La acción recorría líneas contables (account.move.line) cuyas referencias empezaban con "FC%" (facturas de cliente), las emparejaba con sus extractos bancarios, y producía un reporte fiscal.

El problema: el campo usado para unir las tablas no existía en los registros que estábamos buscando. Como buscar a R2-D2 en el Área de la Burocracia: sabés que está, pero no lo vas a encontrar por ahí. O como buscar el Halcón Milenario en el hangar de la Estrella de la Muerte sin el rastreador correcto: sabés que hay algo, pero si no apuntás bien, no lo ves.

Síntomas observados:

  • La acción de servidor procesaba solo ~125 registros para un mes con ~30.000 facturas
  • Los únicos registros que aparecían eran transferencias bancarias directas
  • Las facturas de cliente con pago en efectivo/ruta no aparecían nunca — poof, como si el Emperador las hubiera borrado de la Fuerza

Impacto: Reportes fiscales incompletos, retrabajo manual para completar los datos faltantes, y el riesgo de presentar información incorrecta al SAT (la pesadilla fiscal de cualquier contador, justo al nivel de "te congelan en carbonita").


🔍 Diagnóstico — Episodio IV: Una Nueva Esperanza (de encontrar los registros)

Cuando abrimos el capó de la acción de servidor, encontramos una consulta SQL que hacía algo como esto:

SELECT aml.*
FROM account_move_line aml
JOIN account_bank_statement_line absl ON aml.statement_line_id = absl.id
WHERE aml.ref LIKE 'FC%'
  AND aml.date BETWEEN '2026-03-01' AND '2026-03-31'

Mirá fijo ese JOIN. El que está en aml.statement_line_id = absl.id. Ese pequeño pedazo de SQL, inocente como un droide de protocolo plateado, es el responsable de nuestra masacre fiscal. Este campo solo se llena cuando una línea contable está directamente vinculada a una línea de extracto bancario. Y acá está el problema, revelado como un holograma de Leia en R2-D2: los pagos de cliente en efectivo no tienen statement_line_id.

En Odoo v16, cuando un cliente paga en efectivo (o a través de una caja de rutas), el flujo contable es una coreografía digna del Templo Jedi:

Factura (FC% → cuenta "Clientes")
    ↓
Pago (cuenta "Caja rutas venta")
    ↓
Depósito bancario → Extracto bancario (este SÍ tiene statement_line_id)

Cada paso de la cadena genera sus propias líneas contables, cada una con un full_reconcile_id diferente. Como los sables de luz: cada Jedi tiene el suyo, no se comparten. El statement_line_id solo aparece en el último eslabón: el depósito bancario que se concilia con el extracto. Pero las facturas originales (FC%) no tienen ese campo. Es como buscar el hiperimpulsor del Halcón Milenario en la cabina del piloto cuando en realidad está en el compartimiento de carga secreto donde Han esconde los cargamentos ilegales.

Causa raíz: La consulta SQL asumía que todas las facturas de cliente estaban directamente vinculadas a una línea de extracto bancario mediante statement_line_id. En realidad, solo las transferencias bancarias directas (las rutas directas del hiperespacio, digamos) tienen esa relación. Los pagos en efectivo/ruta pasan por una cadena de reconciliación en cascada que usa account.partial_reconcile como puente entre cada paso. Es como la Fuerza: conecta todas las cosas, pero tenés que saber usarla.

Para el funcional/consultor que está leyendo esto:

  • Las facturas FC% aparecían correctamente en su módulo (en la UI todo se veía genial, como la Estrella de la Muerte desde afuera)
  • El reporte SAT mostraba solo transferencias bancarias
  • Los pagos de rutas/efectivo simplemente no aparecían, como si el Imperio los hubiera borrado de la existencia con un rayo tractor mal calibrado
  • Desde la UI no se veía el bug — esa es la parte más peligrosa. El sistema no tiraba error. Simplemente... omitía datos. Como Vader cuando dice "Estás absuelto" y no lo dice en serio.

🔧 La Solución — El Regreso del Partial Reconcile

Después de rastrear la cadena de reconciliación completa (y tomar un cafecito o dos — uno por cada ~10.000 facturas perdidas), encontramos que la solución no era cambiar statement_line_id por full_reconcile_id, porque cada paso de la reconciliación tiene su propio full_reconcile_id. La factura FC2026-XX9999 tiene un full_reconcile_id, el pago ZAP16/2026/XXXXX tiene otro, y el depósito bancario tiene otro más. Son como los clones: individuales, numerados, y cada uno con su propia misión.

El verdadero puente entre todos estos mundos es la tabla account.partial_reconcile. Esta tabla registra cada reconciliación parcial entre dos líneas contables. Es como el hiperespacio: conecta puntos que de otra forma estarían desconectados. Es el mapa estelar que la consulta original no estaba consultando.

⚙️ El Fix Técnico (para mostrárselo a tu developer favorito)

El fix correcto fue cambiar los JOINs de las uniones para usar account.partial_reconcile en lugar de matching_number o statement_line_id. Acá va la cirugía con sable de luz:

-- 🚫 ANTES: la vieja República (no funcionaba para pagos en cascada)
-- Usaba matching_number, asumiendo que todas las líneas reconciliadas
-- compartían el mismo valor. SPOILER: no.
INNER JOIN account_move_line aml_e2
    ON aml_e2.matching_number = aml_e.matching_number
    AND aml_e2.statement_id IS NULL

-- ✅ DESPUÉS: el Imperio contraataca (con la tecnología correcta)
-- Usa account.partial_reconcile. No asume nada. Funciona siempre.
INNER JOIN account_partial_reconcile apr
    ON (apr.credit_move_id = aml_e.id OR apr.debit_move_id = aml_e.id)
INNER JOIN account_move_line aml_e2
    ON (aml_e2.id = apr.credit_move_id OR aml_e2.id = apr.debit_move_id)
    AND aml_e2.id != aml_e.id
    AND aml_e2.statement_id IS NULL

Explicación técnica para el funcional que tiene que explicarle al cliente por qué ahora funciona:

La tabla account.partial_reconcile contiene pares de (credit_move_id, debit_move_id) que vinculan dos líneas contables reconciliadas. Si la línea A está reconciliada con la línea B, hay un registro en account.partial_reconcile donde credit_move_id = A.id AND debit_move_id = B.id (o viceversa). Esto permite navegar la cadena de reconciliación en cualquier dirección, sin importar cuántos pasos intermedios haya. Como la Fuerza en una conversación entre Yoda y Luke: medio misterioso, pero conecta TODO.

Traducción al funcional: Necesitábamos viajar desde la factura original hasta su depósito bancario, pero había escalas intermedias (el pago en efectivo, la ruta). No podíamos ir directo como en una transferencia bancaria. account.partial_reconcile es la tabla que tiene la ruta completa de cada escala. Es como los archivos de navegación del Halcón Milenario: si no los tenés, no llegás a Endor.

Cómo verificar que se corrigió:

  1. Ejecutar la acción de servidor para marzo
  2. Comparar el conteo de registros: debería pasar de ~125 a ~30.000
  3. Verificar que aparezcan facturas de clientes con pago en efectivo (no solo transferencias bancarias)
  4. Confirmar que los montos totales coinciden con el libro de ventas (la Fuerza está en equilibrio)

✅ Resultado — El Retorno de los Registros

La consulta corregida devolvió los ~30.000 registros esperados para marzo. Como cuando la Alianza Rebelde finalmente encuentra el punto débil de la Estrella de la Muerte: el problema estaba ahí, visible, pero en el lugar equivocado. Solo hacía falta cambiar el ángulo de ataque — y el JOIN.

Checklist del Guerrero Jedi:

  • [x] Conteo de registros coincide con total de facturas FC% del período
  • [x] Aparecen facturas de todos los tipos de pago (transferencia, efectivo, ruta)
  • [x] Reporte SAT exportable sin datos faltantes
  • [x] Tiempo de ejecución aceptable (los índices en account.partial_reconcile ayudan a mantener la paz en la galaxia)
  • [x] Cliente final feliz (sin invocar al lado oscuro del SAT)
  • [x] Consultor durmiendo tranquilo

Lección aprendida: En la reconciliación bancaria de Odoo, nunca asumas que dos líneas contables comparten el mismo full_reconcile_id aunque estén en la misma cadena de pagos. Nunca. Es como asumir que todos los stormtroopers tienen buena puntería: sabés que no es cierto, pero a veces te olvidás.

La tabla account.partial_reconcile es el verdadero mapa estelar que conecta todos los puntos. Usala. Amala. Indexala. Ponela en un altar si querés. Pero nunca, NUNCA, la ignores en una consulta de reconciliación.


📚 Referencias — Los Archivos de la Alianza


💬 ¿Te pasó lo mismo? — ¡Contanos tu historia!

¿Tuviste reportes SAT que devolvían menos datos de los esperados? ¿Encontraste otra forma de rastrear la cadena de reconciliación bancaria en Odoo? ¿O simplemente querés compartir tu propia historia de un bug que resultó ser un JOIN equivocado mientras tomabas café como si no hubiera un mañana?

Los comentarios son tuyos. La galaxia es grande y los bugs son muchos. Compartí tu experiencia, ayudá a otros consultores que están luchando contra el Lado Oscuro de los JOINs.

Y recordá: que el account.partial_reconcile te acompañe. Siempre.

📬 ¿No querés perderte ningún artículo nuevo?

Dejanos tu correo y te avisamos cuando publiquemos algo.

¡Gracias por suscribirte!