Manual de EstrategiaAjuste masivo de pólizas · Veniu
MANUAL DE ESTRATEGIA · Ajuste masivo y correctivo de pólizas
Producto: Veniu Pólizas de Cobro (veniu_polizas_cobro 19.0.1.1.0) · Odoo 19 Enterprise · México Grupo Veniu · VSolutions · 11 de agosto de 2026
De qué trata este manual
De cómo se agrupa, se reacomoda y se corrige en bloque un universo de cientos de facturas sin perder la trazabilidad factura por factura y sin mover de periodo el IVA ya declarado.
No es el manual de usuario —ése explica cómo se cierra la caja del día, y vive en 90_docs/MANUAL_USUARIO.md—. Tampoco es el runbook de implantación (90_docs/MANUAL_IMPLANTACION.md) ni el contrato técnico del módulo (00_estrategia/SPEC_MODULO.md). Este documento es la doctrina: qué se decide, en qué orden, con qué criterio, y qué no se hace nunca.
Para quién
| Lector | Capítulos |
|---|---|
| El contador que opera esto todos los días | 1 a 4, 7 a 9, 12 y los anexos |
| Quien lo implanta en un cliente nuevo | 5, 6, 10, 11 y los anexos |
Los dos deberían leer el capítulo 8: es el árbol de decisión de las correcciones, y es donde se gana o se pierde el dinero.
La idea en cuatro frases
- La política PUE/PPD no determina la cuenta contable. La determina la forma de pago SAT.
- El disparador del IVA de base de flujo es la conciliación, no la póliza. Por eso corregir la
cuenta es barato y desconciliar es caro.
- Toda corrección conserva la fecha original y deja rastro: nada se borra, todo se revierte o
se libera.
- El ciclo se cierra o miente: una caja que solo recibe cargos declara efectivo eterno.
Índice
| # | Capítulo |
|---|---|
| 1 | Por qué existe este módulo |
| 2 | La tesis contable en una página |
| 3 | Ruta 0: lo que nunca entra a una póliza |
| 4 | IVA base flujo: la regla que gobierna toda la estrategia |
| 5 | Anatomía del módulo |
| 6 | Implantación: del catálogo vacío al primer corte |
| 7 | El ciclo diario: detectar, agrupar, previsualizar, aplicar |
| 8 | Los cuatro asientos correctivos y el árbol de decisión |
| 9 | Cerrar el ciclo: depósito a banco y liquidación de terminal |
| 10 | Controles, segregación de funciones y expediente |
| 11 | Automatizar o no: el cron de cierre |
| 12 | Cierre mensual y evidencia |
| 13 | Anexos: tabla SAT, mensajes, gotchas y límites |
Las reglas marcadas con 📅 dependen de la Resolución Miscelánea Fiscal, que cambia cada año. Valídalas contra el texto vigente antes de citarlas ante un cliente o un auditor.
01Por qué existe este módulo
El cliente nunca describe el problema en términos contables. Lo dice así:
- "Mis facturas de contado siguen apareciendo como no pagadas."
- "Clientes crece cada mes aunque cobramos igual que siempre."
- "La cuenta de caja nunca baja."
- "El banco marca cero."
Los cuatro síntomas son la misma enfermedad en dos etapas.
Etapa uno: el cobro nunca se registra. La factura se emite, se timbra como PUE —es decir, se le declara al SAT que ya se cobró en una sola exhibición— y el dinero entra al cajón, a la terminal o a la cuenta bancaria. En Odoo no ocurre absolutamente nada. La factura queda con payment_state en not_paid, la cuenta por cobrar queda cargada y el balance afirma un activo que ya no existe. Con IVA base flujo, además, el asiento de IVA trasladado cobrado no se genera nunca, porque en Odoo lo dispara la conciliación, no el timbrado.
El caso que originó el producto llegó exactamente así: 502 facturas timbradas como PUE, $1.42 M de saldo abierto, 457 de ellas con forma de pago "Efectivo", 88 facturadas a un genérico tipo "CLIENTE MOSTRADOR", cero extractos bancarios cargados y cero de tres fechas de bloqueo contable configuradas. El estado de resultados estaba bien. El balance mentía.
Etapa dos: el remedio apresurado. Alguien resuelve el atraso agrupando todo y aplicándolo contra una sola cuenta de caja. Se aplicaron 24 pólizas de ingreso, 480 facturas conciliadas una por una, $1,359,077.56 movidos de Clientes, cero errores de conciliación. Clientes bajó exactamente lo que debía bajar. Visto desde arriba, quedó impecable.
Al revisar el ruteo apareció el segundo error: 45 de esas 480 facturas, $165,761.85, el 12.2 % del corte, estaban en la cuenta equivocada. Eran transferencias que el banco todavía no confirmaba y cobros con terminal que una adquirente tenía en su poder.
El segundo error es más caro que el primero, y por eso este manual existe.
Es más caro por tres razones concretas:
- Se ve resuelto. Nadie va a auditar un saldo de Clientes que ya bajó. El primer error grita; el
segundo se esconde detrás de un cuadre perfecto.
- Rompe el control que lo detectaría. El arqueo físico de caja deja de cuadrar todos los días por el
monto de las tarjetas. Cuando un control falla siempre, se abandona.
- Deja dinero en la mesa. Sin cuenta de terminal por cobrar, la comisión de la adquirente nunca se
contabiliza —no hay ninguna cuenta que quede descuadrada y obligue a explicarla— y el IVA acreditable de esa comisión se regala. Tampoco se puede detectar un depósito faltante: si la adquirente liquida de menos, nadie se entera.
Y es más caro porque corregirlo, hecho a mano, obliga a tocar lo que ya estaba bien: de esas 480 facturas, 435 estaban correctas y compartían póliza con las 45 malas.
De qué trata este manual. No de capturar un cobro: eso es el manual de usuario. Trata de la estrategia de corrección masiva: cómo se agrupa, se reacomoda y se corrige en bloque un universo de cientos de facturas sin perder la trazabilidad factura por factura ni mover de periodo el IVA ya declarado. Todo el diseño del módulo está subordinado a esa pregunta, incluidos los tres métodos correctivos —traspaso, liberación y reconstrucción— y el orden en que se eligen.
Para el implantador: este patrón no es de un cliente. Es el estado por defecto de cualquier empresa mexicana con mostrador que factura PUE y no tiene un proceso de corte diario. El diagnóstico de 30 minutos te dirá si el cliente está en la etapa uno, en la dos, o en las dos a la vez —que es lo más frecuente—.
Las tres preguntas que el módulo contesta todos los días:
| Pregunta | Dónde se contesta |
|---|---|
| ¿Qué se cobró? | la póliza de cobro: un cargo único a la cuenta de la ruta y una línea de abono por factura, a la cuenta de CxC que esa factura usó y con su partner_id |
| ¿Dónde quedó el dinero? | el mapa, que elige la ruta según la forma de pago SAT, y la ruta, que aporta la cuenta destino |
| ¿Cómo se corrige lo que quedó mal? | traspaso, liberación o reconstrucción, en ese orden de preferencia |
Si una operación no puede contestar las tres, no está terminada.
02La tesis contable en una página
La política PUE/PPD no determina la cuenta. La determina la forma de pago SAT (l10n_mx_edi_payment_method_id).
PUE significa "en una sola exhibición". No significa "en efectivo". Una venta de $50,000 liquidada íntegramente por SPEI es PUE con forma de pago 03. Una venta de $500 con tarjeta de débito es PUE con forma 28. Confundir la política con la forma es el error de origen, y su precio medido fue 12.2 % del corte.
La forma de pago tampoco se lee como catálogo, sino como respuesta a una sola pregunta:
¿Quién tiene ese dinero ahora mismo?
Hay cuatro respuestas posibles, y cada una es una naturaleza contable distinta:
| Respuesta | Naturaleza | Ruta |
|---|---|---|
| La empresa, en billete, en el cajón | tesorería propia | EFE |
| El banco de la empresa, sin confirmar en extracto | activo transitorio | TRF |
| Un tercero procesador (adquirente, emisor, plataforma) | cuenta por cobrar a un tercero | TPV, INT |
| No lo sé | cuarentena | IDF |
Las rutas que trae la semilla
Son diez, en data/veniu_poliza_ruta_plantilla_data.xml (modelo veniu.poliza.ruta.plantilla). Ocho declaran cuenta sugerida y tipo esperado; dos existen precisamente para no generar asiento (genera_poliza = False) y mandar al usuario al instrumento correcto.
| Código | Nombre | Cuenta sugerida | cuenta_tipo | poliza_tipo | Automatización | permite_cron |
|---|---|---|---|---|---|---|
EFE | Efectivo — Caja de venta | 101.02.01 Caja de venta | asset_cash | ingreso | masivo | sí |
TRF | Transferencia y cheque — Recibos pendientes | 102.01.04 Recibos pendientes de conciliar | asset_current | ingreso | masivo | no |
TPV | Terminal bancaria — por cobrar a la adquirente | 107.05.02 TPV / Terminal bancaria por cobrar | asset_current | ingreso | masivo | no |
INT | Intermediarios de pago y marketplaces | 107.05.03 Intermediarios de pago por cobrar | asset_current | ingreso | masivo | no |
IDF | Cobros por identificar (cuarentena) | 102.01.06 Cobros por identificar | asset_current | ingreso | manual | no |
ANT | Aplicación de anticipos de clientes | 206.01.01 Anticipos de clientes | liability_current | diario | masivo | no |
COM | Compensación contra cuentas por pagar | 201.01.01 Proveedores nacionales | liability_payable | diario | masivo | no |
INC | Prescripción o caducidad — incobrables | 108.01.01 Estimación de cuentas incobrables | asset_current | diario | manual | no |
ESP | Caso especial — asiento manual del contador | — (genera_poliza = False) | — | diario | no aplica | no |
NCR | Se resuelve con nota de crédito | — (genera_poliza = False) | — | diario | no aplica | no |
Los códigos de cuenta son sugerencias portables: el asistente de configuración los resuelve contra el catálogo real de la compañía y, ya creada la ruta, la cuenta se resuelve siempre por id, nunca por código.
Una sola ruta entra al cierre automático: EFE. El efectivo es lo único cuyo destino no admite duda.
El mapa (data/veniu_poliza_mapa_plantilla_data.xml) tiene 23 filas: las 22 claves del catálogo c_FormaPago del SAT —01, 02, 03, 04, 05, 06, 08, 12, 13, 14, 15, 17, 23, 24, 25, 26, 27, 28, 29, 30, 31, 99— más una fila comodín (aplica_sin_forma) para las facturas que traen el campo vacío. La llave es el código SAT como texto (forma_pago_code), no el XML-ID de l10n_mx_edi, para que la semilla sea portable entre versiones de la localización.
Veintidós claves convergen a diez rutas y solo ocho cuentas, y es deliberado. Crear una cuenta por clave SAT produce un catálogo con veintidós cuentas, casi todas en cero, imposible de auditar e imposible de mapear al código agrupador del Anexo 24. El mapa converge porque la pregunta que lo ordena no tiene veintidós respuestas: tiene cuatro.
Por qué TPV no es efectivo ni banco
Cuando el cliente pasa la tarjeta, el adquirente le cobra al emisor y retiene el dinero. Deposita a T+1 o T+2 —a veces mucho más en emisores no bancarios— y lo hace neto de su comisión más el IVA de esa comisión. Entre la venta y el depósito, el negocio tiene una cuenta por cobrar contra un tercero que ni siquiera es su cliente. No hay billete que arquear y no hay línea en el estado de cuenta.
Meterlo a caja tiene tres costos: el arqueo físico nunca cuadra, el balance declara efectivo refutable con un conteo sorpresa, y —el más caro— la comisión nunca se contabiliza, porque no queda ninguna cuenta descuadrada que obligue a explicarla. Gasto real no registrado, IVA acreditable regalado.
Se usa asset_current y no asset_receivable a propósito: la deuda es del adquirente, no del cliente. Con asset_receivable el saldo entra al mayor de clientes y a la antigüedad de saldos, y la cobranza persigue a un cliente que ya pagó. Crédito y débito comparten cuenta por la misma razón inversa: la adquirente deposita un solo importe neto mezclado; partir la cuenta obliga a prorratear cada liquidación a mano. Los intermediarios y marketplaces (INT) sí llevan cuenta propia y separada de la terminal, porque el orden de magnitud de su comisión y el desglose de su liquidación son distintos.
La regla dura
Ninguna ruta apunta jamás a Bancos.
El saldo de una cuenta de banco tiene un solo juez: el estado de cuenta. Si la póliza postea directo ahí, el día que se carga el extracto la conciliación abona otra vez el mismo importe y el ingreso queda duplicado. Entre el cobro y el banco siempre va una cuenta puente que la conciliación limpia sola.
Esto no vive en la costumbre: vive en el código. El @api.constrains _check_cuenta_destino_admisible de models/veniu_poliza_ruta.py valida tres cosas en orden de gravedad: que la cuenta no sea la cuenta por defecto de un diario de tipo bank de esa compañía; que ninguna ruta apunte a una cuenta asset_cash salvo la que declara esperar precisamente ese tipo —la de caja—; y que el tipo real de la cuenta coincida con el cuenta_tipo que la ruta declara esperar. El mensaje del primer caso nombra el diario culpable, no solo la cuenta.
La definición de "cuenta de banco" es única en todo el módulo: veniu.poliza.config.cuentas_reservadas_banco(). Mira el diario, no el account_type, porque en el plan mexicano Bancos (102) y Caja (101) comparten asset_cash. Y no incluye la transitoria del extracto (suspense_account_id): esa cuenta no es el banco, es justamente la puente que la conciliación vacía, y es un destino legítimo —a veces el único disponible— para la póliza de depósito.
Para el implantador: si el catálogo del cliente no tiene una cuenta que satisfaga un rol, la ruta queda sin cuenta destino y get_cuenta() falla con un mensaje accionable que remite al asistente de configuración. Ninguna ruta postea a ciegas. Esa es la diferencia entre una configuración incompleta y un asiento equivocado.
03Ruta 0: lo que NUNCA entra a una póliza
Antes de preguntar a qué cuenta va un cobro, hay que preguntar si ese documento es cobrable. Esa primera compuerta es la Ruta 0 y vive en account.move._veniu_motivo_exclusion(config). Devuelve una clave de texto o False. Si devuelve clave, la factura no entra: ni al asistente, ni al motor, ni al cron.
La Ruta 0 filtra por naturaleza del documento —tipo, estado del asiento, estado del CFDI, política de pago SAT y saldo abierto—, nunca por cuenta destino. Decidir a dónde va el dinero es trabajo del mapa de rutas, que corre después. Si se mezclaran las dos preguntas, una configuración incompleta de cuentas podría dejar pasar un CFDI cancelado y una regla fiscal podría acabar dependiendo de un catálogo contable.
3.1 Los siete motivos
El método evalúa en orden y se detiene en el primero que aplica: una factura tiene un solo motivo, el más grave. Los textos legibles viven en MOTIVOS_EXCLUSION (veniu_poliza_lote.py), que es el que se imprime cuando el motor aborta; el asistente de agrupar tiene su propia tabla con la misma clave y una redacción más larga para la pestaña de excluidas.
| Clave técnica | Qué significa | Qué se hace con esa factura |
|---|---|---|
no_es_factura_cliente | El move_type no es out_invoice ni out_refund | Nada. Es una factura de proveedor, un pago o un asiento de diario que se coló en la selección |
asiento_cancelado | El asiento no está posted (borrador o cancelado) | Publicarlo si corresponde, o dejarlo fuera. Un borrador no tiene CxC que abonar |
cfdi_cancelado | l10n_mx_edi_cfdi_state vale cancel o global_cancel y politica_cfdi_cancelado = 'excluir' | No es ingreso. Se resuelve en el mundo del CFDI —cancelar el asiento, nota de crédito— antes de tocar tesorería |
sin_timbrar | No hay estado de CFDI y politica_sin_timbrar = 'excluir' | Por defecto la política es advertir, así que no excluye: la factura entra en ámbar. Cobrable sí; comprobante ante el SAT, no |
es_ppd | veniu_politica_pago_sat = 'ppd' bajo la política de detección vigente. Ese campo es propio y almacenado, y se calcula del plazo de pago: hay parcialidades (invoice_payment_term_id con más de una línea) o el vencimiento es posterior a la fecha de factura | Se cobra con account.payment y su REP. Nunca con póliza masiva |
sin_cxc_abierta | Ninguna línea de CxC con residual distinto de cero (respetando el filtro opcional config.cuenta_cxc_id) | Ya está cobrada o su saldo se fue por otro camino. Verificar contra qué se conció |
ya_en_poliza | Todas sus líneas abiertas ya están en una póliza de cobro aplicada y no liberadas | Abrir la póliza que la contiene. Si de verdad hay que sacarla, se libera |
Para el implantador: solo tres motivos dependen de configuración (cfdi_cancelado, sin_timbrar, es_ppd). Los otros cuatro son estructurales y no se pueden apagar. En una implantación nueva, la única política que se discute con el contador es politica_sin_timbrar; las otras dos se dejan en su valor de fábrica (excluir para el CFDI cancelado, pue para la detección).
3.2 CFDI cancelado no es asiento cancelado
Son dos mundos que no se hablan. state = 'cancel' es una decisión de Odoo. l10n_mx_edi_cfdi_state = 'cancel' es una decisión que ya está registrada en el SAT. Existe —y es el caso caro— la factura con CFDI cancelado cuyo asiento sigue posted con saldo abierto en Clientes. Trae forma de pago Efectivo, pasa cualquier filtro ingenuo y carga a la caja dinero que no existe.
El filtro va por exclusión, no por igualdad:
Regla dura. El estado del CFDI se compara contra la constanteCFDI_CANCELADO = ('cancel', 'global_cancel'). Nunca contra la cadena'cancel'suelta, y nunca por igualdad a'sent'.
La razón es el mostrador. Las facturas globales de público en general viven en el par global_sent / global_cancel. Filtrar por igualdad a 'sent' habría dejado fuera todas las globales legítimas; comparar la cancelación solo contra 'cancel' habría dejado pasar en verde precisamente el volumen más alto de cancelaciones. La constante vive en veniu_poliza_config.py y de ahí la importan tanto el motor como el semáforo del asistente (_compute_semaforo) por esa razón: dos definiciones de "cancelado" en un mismo módulo es una de las dos mintiendo.
3.3 PPD: por qué no entra
Una factura PPD le afirmó al SAT que está pendiente de cobro y declara forma de pago 99. Su cobro se documenta con un pago que emite el complemento de recepción de pagos (REP), y el IVA se considera cobrado en la fecha de ese REP. Una póliza masiva la dejaría saldada sin REP: el papel de trabajo cuadra contra Odoo y no cuadra contra el SAT, y el IVA trasladado del mes del cobro se omite. Es la omisión más cara que puede cometer este módulo.
La severidad la manda config.politica_deteccion, no el método:
| Valor | Comportamiento de la Ruta 0 |
|---|---|
pue (por defecto) | Ninguna PPD entra: motivo es_ppd |
pue_vencidas | Solo pasa la PPD ya vencida (invoice_date_due <= hoy); la vigente —y también la que no tiene fecha de vencimiento— devuelve es_ppd |
todas | No hay motivo es_ppd: el contador asume el riesgo a propósito |
pue_vencidas usa la misma regla que dominio_cobrables() usa para pintar el tablero. Si divergieran, el asistente rechazaría lo que el tablero acaba de ofrecer.
El semáforo la marca siempre, en los tres casos. El campo es_ppd del asistente se calcula por el hecho del documento (veniu_politica_pago_sat == 'ppd'), no por el motivo de exclusión. Si se derivara del motivo, con todas la PPD entraría marcada y en blanco. La línea nace desmarcada (incluir = not es_ppd), sale dentro de la lista principal —no escondida en la pestaña de excluidas— y action_marcar_todo la salta a propósito. Lo que cambia con la política es solo si el semáforo dice bloqueo o aviso: con pue y con pue_vencidas mientras no venza, la línea sale en rojo; con todas sale en ámbar y el aviso dice literalmente que se cobrará sin REP.
Para el contador: ver una PPD marcada en la lista no es un error del sistema. Está ahí para que sepas dónde quedó y cuál es su salida, no para que la fuerces.
3.4 Sin CxC abierta y "ya en póliza"
sin_cxc_abierta significa que la factura no tiene ninguna línea de cuentas por cobrar con residual distinto de cero. Es el motivo más frecuente y casi siempre benigno: ya se cobró. Cuando aparece en una factura que el operador jura que está abierta, la causa suele ser el filtro opcional config.cuenta_cxc_id, que restringe qué líneas se consideran.
ya_en_poliza sale de _lineas_ya_aplicadas(), que busca líneas del detalle con lote_id.state = 'aplicado', lote_id.tipo_operacion = 'cobro' y liberada = False. Dos consecuencias:
- Una póliza de traspaso o de depósito nunca marca una factura como "ya cobrada". Solo la de cobro.
- Una factura liberada vuelve a ser agrupable. La póliza de liberación la sacó del asiento y su línea quedó marcada como liberada; sin ese filtro quedaría excluida para siempre con el motivo "ya está en una póliza" y nadie podría volver a cobrarla.
El motivo solo se emite cuando len(ya) == len(abiertas). Una factura con dos parcialidades, una ya en póliza y otra abierta, sí entra: el motor recolecta únicamente la línea libre.
3.5 La regla de la interfaz
Lo que la Ruta 0 excluye se bloquea, no se advierte.
Las facturas con motivo van a la pestaña Excluidas por regla, que es de solo lectura y no tiene casilla: no hay clic que las incluya. La única excepción visual es la PPD, que se muestra en la lista principal desmarcada porque esconderla hacía que el contador la buscara entre 500 renglones sin encontrarla.
El bloqueo no es cosmético. crear_desde_facturas() vuelve a correr _excluir_facturas() en el paso 2, antes de mirar forma de pago alguna: lo excluido no llega a la recolección de líneas, y si ninguna factura de la selección sobrevive, aborta con UserError nombrando factura por factura —hasta quince— con su motivo. Una llamada por RPC, un botón nuevo o el cron topan con la misma compuerta. Y el tablero Facturas por agrupar lleva veniu_politica_pago_sat = 'pue' en el dominio de la acción, no en un search_default: como facet quitable bastaba con cerrar la etiqueta para que el tablero empezara a ofrecer PPD.
04IVA base flujo: la regla que gobierna toda la estrategia
En México el IVA se causa sobre lo efectivamente cobrado (art. 1-B LIVA), no sobre lo devengado. Odoo implementa esa regla con impuestos cash basis: la factura registra el IVA en una cuenta transitoria de "IVA trasladado no cobrado", y cuando el cobro se concilia contra la factura, un diario dedicado genera el asiento que lo mueve a "IVA trasladado cobrado" —el que se declara.
El disparador fiscal, entonces, no es la póliza. Es la conciliación. La póliza importa porque concilia. Todo lo demás de este capítulo se deduce de ahí.
4.1 Los tres corolarios
1. Cambiar la cuenta de activo no toca el IVA. Rutear una venta a la ruta TPV (Terminal bancaria — por cobrar a la adquirente, cuenta sugerida 107.05.02) en lugar de a la ruta EFE (Efectivo — Caja de venta, 101.02.01) concilia exactamente la misma línea de CxC, por el mismo importe y en la misma fecha. El IVA cobrado se reconoce igual. Corregir el ruteo no altera nada de lo ya declarado. Un traspaso —cargo a la cuenta correcta, abono a la que se usó por error— no toca la CxC, no desconcilia nada y no genera un solo asiento de base flujo.
2. Desconciliar y volver a conciliar sí lo re-dispara. Al romper la conciliación se reversan los asientos de base flujo; al reconciliar se vuelven a generar. Si eso cae en otro periodo, se mueve IVA entre declaraciones: complementaria, actualización y recargos si el periodo ya se presentó. La reconstrucción hace exactamente esto, y lo hace con todas las facturas de la póliza, no solo con las que estaban mal.
3. De ahí sale la jerarquía de los tres métodos correctivos:
| Método | Toca conciliaciones | Re-dispara IVA | Cuándo |
|---|---|---|---|
Traspaso (tipo_operacion correccion, folio POL-COR) | No | No | Siempre que el problema sea a qué cuenta fue el dinero |
Liberación (liberacion, folio POL-LIB) | Solo la de la factura liberada | Solo esa | Cuando hay que sacar una factura concreta del asiento |
| Reconstrucción | Todas las de la póliza | Todas | Último recurso |
Regla dura. Toda corrección conserva la fecha original de la póliza. Nunca la de hoy. Una fecha nueva mueve el IVA de periodo y descuadra el corte de caja del día original.
4.2 Qué significa en dinero
En el caso que originó el producto, un lote de 480 facturas tenía 45 mal ruteadas por $165,761.85 —el 12.2 %. Antes de la liberación, sacar o corregir algo dentro de una póliza obligaba a reconstruirla entera: de las 348 facturas tocadas, 301 estaban correctas y se iban a desconciliar y reconciliar sin necesidad, solo por compartir póliza con una mal ruteada. El 63 % del lote pasando por una operación destructiva para arreglar el 13 %.
El costo de esas 301 no es teórico: cada una reversa y regenera su asiento de base flujo. Si el mes ya se declaró, son 301 movimientos de IVA que hay que explicar en una complementaria para arreglar 45 errores que un traspaso corrige sin tocar una sola conciliación.
Por eso el módulo blinda esta jerarquía en el modelo, no en la costumbre: metodo_correccion_default = 'traspaso' y permitir_reconstruccion se puede apagar por compañía. Y una póliza que ya tiene traspasos o liberaciones aplicados encima no se revierte ni se reconstruye: _check_sin_derivadas_vivas() lo impide nombrando el folio de la derivada. El descuadre que evita es silencioso —la cuenta de origen queda en negativo y la de destino cargada dos veces, con un asiento que cuadra consigo mismo—. El orden correcto es al revés: primero la derivada, después la original.
Para el implantador: en un cliente con IVA base flujo delicado o con periodos ya declarados, apaga permitir_reconstruccion en la configuración y déjalo por escrito. Es un interruptor, no una recomendación.
4.3 Las fechas de bloqueo contable
El asiento de la póliza se publica al final, cuando el contador ya revisó cientos de renglones. Si su fecha cae en un periodo cerrado, Odoo rechaza el asiento y todo el trabajo se pierde en un rollback sin explicación útil.
El asistente de agrupar lo dice antes. _candados_periodo() lee los cierres vigentes de res.company en orden de severidad —hard_lock_date (bloqueo irreversible), fiscalyear_lock_date (cierre de ejercicio), tax_lock_date (cierre de impuestos), sale_lock_date (cierre de ventas), period_lock_date (cierre para usuarios no asesores)— y comprueba cada nombre contra company._fields antes de leerlo, porque los campos cambiaron entre versiones y un acceso a un campo inexistente convertiría un aviso preventivo en una pantalla que no abre.
Dos detalles que cambian el resultado:
- Los candados de Odoo son inclusivos: una fecha igual al tope ya está cerrada (
fecha <= tope). Se recorren del más severo al más laxo para nombrar el peor. - Se compara la fecha efectiva del asiento (
_fecha_efectiva), no la fecha de corte. La de corte es solo la llave de agrupación —el lunes de la semana, el día 1 del mes— y usarla hacía saltar falsas alarmas de periodo cerrado. Ante esa falsa alarma, lo único que la pantalla sugiere es forzar la fecha, que es justo lo que mueve el IVA de periodo.
El preflight avisa; no bloquea. La fecha puede ser legítima y quien tiene la llave del cierre es contabilidad, no este asistente. El aviso aparece en dos lugares: como renglón en rojo dentro de la previsualización de cada póliza, y como advertencia agregada con el número de pólizas atrapadas por cada candado. Forzar la fecha genera su propio aviso independiente.
Para el implantador: si el cliente no tiene ninguna fecha de bloqueo puesta —en Galgo, fiscalyear_lock_date, tax_lock_date y hard_lock_date están las tres vacías—, el preflight nunca dirá nada y el módulo escribirá alegremente en cualquier ejercicio. Proponer el bloqueo al cerrar el mes es parte de la implantación, no un extra.
Reglas de la Resolución Miscelánea Fiscal — validar contra el texto vigente. La RMF cambia cada año. Tres puntos de este capítulo dependen de ella y hay que confirmarlos antes de implantar en cada cliente y en cada ejercicio: (1) la facilidad que admite PUE cuando el pago se recibe a más tardar el último día del mes en que se expidió el CFDI; (2) el plazo para emitir el REP, históricamente el quinto día natural del mes siguiente al del cobro; (3) el plazo de cancelación de CFDI del ejercicio y sus motivos obligatorios. El módulo no codifica ninguna de las tres: las trata como criterio del contador.
05Anatomía del módulo
Cinco modelos sostienen la operación: tres son catálogo (se configuran una vez y se firman) y dos son operación (se generan todos los días). Aparte viven los dos modelos de plantilla (veniu.poliza.ruta.plantilla y veniu.poliza.mapa.plantilla), que son la semilla portable del módulo y no configuración del cliente.
| Modelo | Qué es | Quién lo toca |
|---|---|---|
veniu.poliza.config | Las decisiones de la compañía | El implantador con el contador, una vez |
veniu.poliza.ruta | El destino contable del dinero | Igual, y se revisa al cambiar el plan de cuentas |
veniu.poliza.mapa | Forma de pago SAT → ruta | Igual |
veniu.poliza.lote | La póliza | El operador, cada día |
veniu.poliza.lote.linea | El detalle congelado de la póliza | Nadie: lo escribe el motor |
veniu.poliza.config es una fila por compañía (constraint UNIQUE(company_id)). Ahí viven los tres diarios (diario_id, diario_traspaso_id, diario_deposito_id, los dos últimos con caída al primero), las cuentas de apoyo (cuenta_cxc_id, cuenta_deposito_id, cuenta_comision_id, cuenta_iva_comision_id), la granularidad del corte (día, semana, mes, única), la cuarentena (ruta_cuarentena_id, dias_cuarentena, y el campo vivo aviso_cuarentena, que mide saldo y antigüedad de los apuntes sin conciliar), la aprobación por monto (exige_aprobacion + aprobacion_monto) y el cron (cron_habilitado, cron_dias_gracia). Y cinco políticas, que son las que deciden qué se excluye antes de mirar la forma de pago: politica_deteccion, politica_cfdi_cancelado, politica_sin_timbrar, politica_mixta, politica_sin_mapa.
veniu.poliza.ruta responde una sola pregunta: ¿quién tiene ese dinero ahora mismo? Trae cuenta_destino_id (se resuelve siempre por id; cuenta_codigo_sugerido es una pista del asistente, jamás un resolvedor en ejecución), prefijo_folio, poliza_tipo, genera_poliza (apagado en ESP y NCR: son las rutas que existen para NO contabilizar), automatizable y permite_cron. veniu.poliza.mapa es tabla aparte porque 22 formas SAT convergen a diez destinos, más una regla comodín (aplica_sin_forma, única por compañía) para las facturas sin forma capturada.
veniu.poliza.lote es la póliza; veniu.poliza.lote.linea es su detalle, y la unidad es la línea de CxC, no la factura: una factura con parcialidades produce varias líneas. Casi todo el detalle es snapshot —forma_pago_id, cfdi_estado, ruta_correcta_id, ruta_aplicada_id, cuenta_cxc_id— porque la auditoría necesita saber qué decía la factura el día que se aplicó, no lo que dice hoy. ruta_original_id conserva la ruta con la que se aplicó originalmente cuando una corrección posterior reescribe la aplicada: sin ese campo, la póliza reimpresa en agosto contaría una historia distinta a la impresa en julio.
5.1 Ruta correcta, ruta real y estado de ruteo
Esta es la distinción que hay que entender o nada de lo demás sirve.
- Ruta correcta (
veniu_ruta_correcta_id): la que dicta el mapa segúnl10n_mx_edi_payment_method_id. Es la que debió usarse. - Ruta real (
veniu_ruta_real_id): la derivada deveniu_cuenta_real_id, que es la contrapartida dominante —la cuenta de mayor importe conciliado contra las líneas de CxC de la factura—. Es la que se usó. Si un traspaso ya reclasificó el saldo, manda el traspaso: la conciliación sigue apuntando a la cuenta vieja y sería una lectura falsa. - Estado de ruteo (
veniu_ruta_estado): el veredicto de comparar las dos.
| Estado | Cuándo sale | Qué hacer |
|---|---|---|
No aplica (na) | El asiento no es factura de cliente publicada | Nada |
Sin cobrar (pendiente) | Hay ruta correcta y ninguna contrapartida conciliada | Agrupar |
Ruteada correctamente (ok) | Ruta real = ruta correcta | Nada |
Mal ruteada (mal) | Ruta real ≠ ruta correcta | Traspaso |
Liquidada fuera de pólizas (externa) | Se concilió contra una cuenta que no es de ninguna ruta | Revisar a mano: el módulo no la generó |
Forma de pago sin ruta (sin_mapa) | La forma SAT no está mapeada | Completar el mapa o mandar a cuarentena |
La diferencia entre las dos primeras columnas es todo el producto: en el caso que lo originó, 45 de 480 facturas —$165,761.85, el 12.2 %— salían ok para el ojo y mal para el mapa.
5.2 El sello de la factura es computado
veniu_poliza_lote_id se calcula: el último lote aplicado y de tipo cobro que contiene esa factura. De ahí cuelgan, como related almacenados, veniu_poliza_folio, veniu_poliza_fecha y veniu_poliza_tipo.
La etiqueta no es la contabilidad. Un campo editable que dice "esta factura está en la póliza X" acaba mintiendo el día que la póliza se revierte, se libera o se reconstruye, y nadie lo nota.
El riesgo no es teórico, y se ve con claridad en la variante sin módulo (Vía B): ahí el sello se escribe por RPC y queda editable, así que quien lo toque puede dejar la lista de aplicadas y la de pendientes contando historias distintas de la misma factura. El campo computado no puede divergir del mayor porque no tiene vida propia: depende del state y del tipo_operacion del lote que la contiene, y cuando la póliza se revierte la etiqueta se vacía sola.
5.3 El folio
Determinista, sin ir.sequence: prefijo de la ruta (o POL-COR, POL-LIB, POL-DEP según el tipo de operación) más la fecha en AAAA-MM-DD, con sufijo de moneda cuando no es la de la compañía y un consecutivo -2, -3 solo si el candidato ya existe. La unicidad la garantiza UNIQUE(company_id, name), no un contador.
Gotcha verificado en producción. El folio viaja en el camporefdel asiento, no enname: elnamelo impone la secuencia del diario (POLI/2026/07/0001). Buscar asientos connameque empiece conPOL-devuelve cero, siempre.
Y la contraparte: el folio nunca se parsea. La fecha y la ruta de una póliza son campos (fecha, ruta_id, ruta_codigo). Deducirlos del texto rompe el día que alguien cambia folio_incluye_ruta.
5.4 La máquina de estados
borrador → por_aprobar → aplicado → revertido, más cancelado como salida lateral.
| De | A | Cómo |
|---|---|---|
| borrador | por_aprobar | Enviar a aprobación (o el lote nace ya en Por aprobar, si la compañía exige aprobación y el importe alcanza aprobacion_monto) |
| borrador / por_aprobar | aplicado | Aplicar póliza · Aprobar y aplicar |
| borrador / por_aprobar | cancelado | Cancelar (borra el asiento si aún estaba en borrador) |
| cancelado / por_aprobar | borrador | Volver a borrador |
| aplicado | revertido | Revertir (motivo obligatorio) |
La transición irreversible es aplicado. Desde ahí no hay vuelta a borrador ni cancelación: solo reversión, que publica un asiento nuevo. Y revertido es terminal: no existe camino de regreso, porque el expediente son dos asientos vivos. Ninguno de los dos estados se borra —el @api.ondelete lo impide, y una póliza revertida se archiva (active), que oculta sin tocar el asiento ni liberar el folio—. aplicar() y revertir() exigen el grupo Pólizas de cobro / Responsable dentro del motor, no solo en el botón, porque el método se alcanza por RPC; la única excepción es el modo superusuario, que es como corre el cron.
06Implantación: del catálogo vacío al primer corte
Para el implantador. El runbook completo son diecisiete pasos, de P0 a P16. Esto es la estrategia que los ordena: cinco decisiones, en secuencia, y ninguna se salta.
6.1 Primero se mide, y con eso se decide de qué conversación se trata
El diagnóstico es de solo lectura: once consultas por compañía, search_count, search_read y _read_group, cero escrituras. Miden el tamaño (PUE abiertas y su importe), la forma (distribución por forma de pago SAT: arriba de 5 % que no sea la forma 01, agrupar todo a una cuenta produce error material), el ruteo real (la contrapartida conciliada de cada factura ya cobrada), y si el ciclo se cierra (¿la caja tiene abonos? ¿hay extractos bancarios?).
Con tres banderas rojas o más, esto no es "instalar un módulo": es una regularización contable. Se dice así, se cotiza así, y el contador del cliente entra a la mesa desde la primera sesión.
La regla no es retórica. Si no hay un contador identificable por nombre que se haga responsable del mapa, se entrega el diagnóstico y ahí termina el compromiso: un mapa de rutas sin dueño contable es un error material esperando fecha.
6.2 Qué resuelve el asistente, y con qué nivel de confianza
El asistente de configuración (veniu.poliza.setup.wizard) existe porque la semilla del módulo no puede referenciar una cuenta del cliente: el plan normaliza la máscara del código en el create —una cuenta capturada como 101.002.000 puede quedar guardada como 101.02.00—. Así que la semilla es texto y el asistente la resuelve contra el catálogo real, en cuatro pasos, sin escribir nada hasta el último. Es idempotente.
Cada línea declara su confianza, y ese es el semáforo que el contador lee antes de firmar:
| Confianza | Cómo se resolvió | Acción | |
|---|---|---|---|
configurada | La ruta ya tenía cuenta guardada | Usar, sin re-adivinar nunca | 🟢 |
exacto | code idéntico al sugerido | Usar | 🟢 |
prefijo | Otra cuenta de la misma familia de código | Usar, marcada para revisión | 🟡 |
metodo_pago | Solo para TRF: la cuenta de recibos pendientes que ya usan los métodos de pago entrantes | Usar, marcada para revisión | 🟡 |
tipo | Solo coincide el account_type | Pendiente de decisión: el asistente no aplica | 🔴 |
ninguna | No hay candidata | Proponer crear la cuenta sugerida | — |
Dos matices que cambian el resultado. La familia es el código sin su último segmento (101.02.01 → 101.02), no el primer segmento: recortar a 101 hace que gane la primera cuenta que empiece así, que en un plan real suele ser Caja Chica, y amarrar el corte de caja a Caja Chica contamina todos los cortes del ejercicio. Y coincidir solo por tipo no identifica nada —un plan mexicano tiene decenas de asset_current—, así que mientras el asistente pueda crear la cuenta sugerida, prefiere crearla; la coincidencia por tipo solo se ofrece cuando crear está desactivado, en rojo y exigiendo decisión.
6.3 Lo que el asistente no resuelve
Cuatro cosas se deciden con el contador, no con el catálogo:
- La cuenta destino de depósitos. El asistente propone en cascada (cuenta de transferencia de la compañía; una cuenta de activo circulante con "traspaso", "liquidez", "tránsito" o "transferencia" en el nombre; la transitoria del extracto), pero jamás puede ser la cuenta de un diario de banco: postear ahí duplica el ingreso el día de la conciliación. La definición única de "cuenta de banco" vive en el método
veniu.poliza.config.cuentas_reservadas_bancoy es solodefault_account_idde los diariosbank—la transitoria del extracto sí es destino legítimo—. Si no encuentra nada, la deja vacía con un aviso rojo: mejor eso que un depósito amarrado a la cuenta equivocada. - Comisión e IVA acreditable de la comisión. Sin ellas, la liquidación de terminal no se puede armar. Regla dura: el IVA de la comisión no se acredita sin el CFDI de la adquirente.
- Las rutas sin cuenta obvia: anticipos (
ANT), incobrables (INC), intermediarios (INT) cuando el cliente no vende por marketplace. Casi siempre no existen en el catálogo y hay que crearlas o decidir posponerlas. - Si
ESPyNCRse quedan como están —no generan póliza a propósito— o el cliente quiere otro tratamiento.
6.4 El acta
La semilla trae 10 rutas (EFE, TRF, TPV, INT, IDF, ANT, COM, INC, ESP, NCR) y 23 filas de mapa: las 22 formas del catálogo c_FormaPago más la regla comodín para las facturas sin forma capturada. Conviene mirar dos antes de firmar: el cheque (forma 02) cae en TRF junto con la transferencia, y si el cliente quiere cuenta propia de cheques en tránsito hay que abrir una ruta; y las formas especiales (12, 13, 14, 23, 24, 27) caen en ESP, que no contabiliza nada por diseño.
El acta lista forma SAT → ruta → cuenta (por id, código y nombre) → si es conciliable, y se firma antes de aplicar la primera póliza. Es cláusula, no cortesía. Incluye tres compromisos: la cuarentena IDF queda en cero antes del cierre de cada mes, ninguna ruta apunta a Bancos, y la corrección de ruteo se hace por traspaso salvo autorización caso por caso.
6.5 El piloto
Un día. Una ruta. La más chica. Un día ya cerrado —uno que sigue facturando no se cierra—, agrupado, aplicado, impreso y validado contra el respaldo previo antes de tocar el siguiente. Las cinco amarras: la póliza quedó aplicado con el asiento posted; totalmente_conciliado en verdadero y cero líneas sin conciliar; la suma de la póliza igual a la de las facturas al centavo; CxC bajó exactamente ese importe; la cuenta destino subió exactamente ese importe. Si falla una, se revierte el piloto y no se avanza. En la implantación real un fallo en la llamada de conciliación por RPC dejó la póliza publicada pero sin conciliar: lo cacharon las amarras 2, 4 y 5. Sin piloto, ese error se habría multiplicado por veinticuatro pólizas.
6.6 Quién decide qué
| Decisión | Módulo | Implantador | Contador |
|---|---|---|---|
| Qué facturas son cobrables (Ruta 0) | ✔ | ||
| Ruta correcta de cada forma SAT | ✔ (según el mapa) | ✔ (define el mapa) | |
| A qué cuenta del catálogo apunta cada ruta | propone | firma | |
| Política de detección (PUE / PUE vencidas / todas) | recomienda pue | decide | |
| CFDI cancelado: excluir, advertir, permitir | recomienda excluir | decide | |
| Granularidad del corte | recomienda día | acepta | |
| Crear una cuenta nueva vs usar una existente | pregunta | decide | |
| Traspaso o reconstrucción en cada corrección | por defecto traspaso | ejecuta | autoriza la reconstrucción |
| Fecha de la póliza en periodos con IVA base flujo | conserva la fecha original al corregir | decide | |
| Encender el cron | doble interruptor, apagado de fábrica | propone cuando el cierre manual ya es rutina | autoriza |
| PUE de ejercicios anteriores y reenvío de balanza | lista | escala | decide |
| Acreditar el IVA de la comisión | pide la cuenta | escala | decide |
07El ciclo diario: detectar, agrupar, previsualizar, aplicar
El ciclo tiene cuatro tiempos y cada uno tiene un dueño distinto: el sistema detecta, el contador decide, la pantalla predice y el motor ejecuta. Confundirlos es lo que produce cortes de caja que cuadran contra Odoo y no contra el SAT.
7.1 Detectar: qué es "cobrable"
Contabilidad → Pólizas de cobro → Facturas por agrupar no es una vista con filtros sugeridos: su dominio está clavado en la acción y dice veniu_cobrable = True y veniu_politica_pago_sat = 'pue'.
veniu_cobrable es una definición fija, no configurable: factura o nota de crédito de cliente, state = 'posted', payment_state en not_paid o partial, y al menos una línea de cuentas por cobrar con residual distinto de cero. veniu_importe_pendiente suma ese residual.
El segundo término va en el dominio y no como facet de búsqueda a propósito. Como etiqueta quitable, bastaba un clic para que el tablero empezara a ofrecer PPD; agrupada en una póliza masiva, una PPD queda liquidada sin complemento de pago y el IVA trasladado del mes del cobro se omite. Quien necesite verlas entra por el filtro explícito "PPD (se cobra con pago y REP)" en la búsqueda de facturas.
El tablero abre agrupado por Ruta correcta. Así se lee: cada grupo es una futura póliza y su cuenta destino; el importe del grupo es lo que va a entrar a esa cuenta. El menú vecino, Revisión de ruteo, es el otro tablero: dominio veniu_ruta_estado in ('mal','sin_mapa','externa'), abierto con el filtro "Mal ruteadas" puesto y agrupado por ruta real. Ahí no se agrupa nada, se diagnostica. La columna que resuelve las dudas es Cuenta cargada (veniu_cuenta_real_id): cuando la ruta real viene vacía, es el único dato que dice dónde acabó el dinero.
7.2 La consola de agrupación y su semáforo
Se seleccionan las facturas en la lista y se lanza Agrupar en póliza de cobro. La consola expande cada factura a sus líneas de cuentas por cobrar abiertas —la unidad es la línea, no la factura— y calcula tres cosas por renglón: la ruta que dicta el mapa de formas SAT (ruta_correcta_id), la ruta que se va a aplicar (ruta_id, editable) y el semáforo.
El campo semaforo tiene tres valores y solo tres:
| Valor | Etiqueta | Qué significa | ¿Apaga "Agrupar y aplicar"? |
|---|---|---|---|
🟢 ok | Lista | Ruta que genera póliza y tiene cuenta destino, CFDI vigente, forma de pago SAT capturada, ruta no forzada y fuera de cuarentena | No |
🟡 aviso | Revisar | Sin timbrar o CFDI cancelado con política "advertir", sin forma de pago SAT, ruta forzada, va a cuarentena, PPD cuando la compañía detecta "todas" | No |
🔴 bloqueo | No aplicable | Sin ruta, ruta que no genera póliza, ruta sin cuenta destino, CFDI cancelado o sin timbrar con política "excluir", PPD con las políticas "solo PUE" y "PUE + PPD vencidas" | Sí |
El botón del pie desaparece cuando bloqueado es verdadero, y bloqueado se enciende con una sola línea marcada en bloqueo —también si no queda ninguna línea marcada, si alguna de las marcadas se quedó sin ruta, o si la política de la compañía prohíbe las selecciones mixtas—. El ámbar nunca bloquea: informa y queda escrito. Los botones de ayuda son cinco: "Marcar todo" (que salta las PPD a propósito), "Desmarcar todo", "Dejar solo las limpias" (desmarca todo lo que no sea ok), "Mandar a cuarentena", que reasigna a la ruta de cobros por identificar las líneas marcadas sin forma de pago o sin ruta, y "Recalcular", que solo repinta la pantalla.
Las PPD merecen párrafo aparte: no se esconden en la pestaña de excluidas. Entran a la lista principal, desmarcadas y en rojo, porque una factura que el contador buscó y no encontró se convierte en un pendiente invisible. Se marcan por el hecho del documento —su política de pago SAT—, no por el motivo de exclusión: con politica_deteccion = 'todas' el motor las dejaría pasar, y entonces el semáforo baja a ámbar y dice exactamente lo que va a ocurrir (cobro sin REP), en vez de mentir con un bloqueo inexistente.
7.3 La llave de agrupación: fecha de corte · ruta · moneda
_agrupar_lineas agrupa por la tripleta (fecha_corte, ruta_id, moneda_id). Cada combinación distinta es una póliza distinta.
La ruta está en la llave porque el error de origen fue agrupar solo por fecha. Un corte diario aplicado en bloque a una sola cuenta de caja mete transferencias y terminal dentro del efectivo. En el caso que originó el producto, 45 de 480 facturas —$165,761.85, el 12.2 % del corte— quedaron declaradas como efectivo sin haber sido efectivo nunca.
La moneda entra por una razón mecánica: un asiento con una línea en USD y otra en MXN hace imposible cuadrar el amount_currency del cargo. La restricción _check_moneda_unica lo impide a nivel de modelo, no solo de asistente.
La fecha de corte es llave de agrupación, no fecha contable. Con granularidad dia es la fecha del documento; con semana, el lunes; con mes, el día 1; con unica, ninguna. La fecha del asiento la fija _fecha_poliza: la mayor fecha de documento del grupo, salvo en diario, donde el grupo comparte fecha. fecha_forzada la sobrescribe, y la pantalla avisa de lo que cuesta: con IVA base flujo, mover la fecha mueve el periodo de reconocimiento del impuesto.
7.4 La previsualización
La pestaña Previsualización de las pólizas muestra una tabla con un renglón por póliza prevista: la llave legible (fecha · ruta · moneda), la cuenta destino, cuántas facturas y cuánto importe. Arriba, los contadores: seleccionadas, importe, rutas distintas y pólizas a generar.
No aparece el folio, y es deliberado: la tabla se recalcula con cada casilla que se marca, y pedir folios consultaría la secuencia una vez por grupo en cada clic. El folio lo asigna el motor al crear la póliza.
Lo que sí aparece es el preflight de periodo cerrado. Se leen los candados de la compañía que existan en la versión instalada —hard_lock_date, fiscalyear_lock_date, tax_lock_date, sale_lock_date, period_lock_date— y se comparan de forma inclusiva contra la fecha efectiva de cada grupo. Si alguno atrapa, el renglón sale en rojo y el bloque de advertencias lo explica. Es un aviso, no un bloqueo: quien tiene la llave del cierre es contabilidad, no este asistente. Sin él, Odoo rechazaba el asiento al final y se perdía la revisión de 500 renglones en un rollback.
7.5 Forzar la ruta de una línea
La columna Ruta a aplicar es editable. Es legítimo cambiarla en tres casos: mandar a cuarentena una línea cuya forma de pago SAT está vacía o es dudosa; corregir una forma de pago mal capturada cuyo cobro real se conoce y está documentado; y separar un cobro cuya clave SAT no distingue el instrumento real.
No es legítimo usarla para "acomodar" la caja. Y no queda oculto: si ruta_id difiere de ruta_correcta_id, la línea baja a ámbar, el bloque de advertencias cuenta cuántas son, la póliza queda con tiene_ruta_forzada encendido, el detalle se pinta en ámbar, el filtro Con ruta forzada las encuentra y el PDF de la póliza imprime el badge RUTA FORZADA en la línea más la leyenda al pie. El snapshot ruta_original_id congela la ruta con la que se aplicó, de modo que una corrección posterior no borre la evidencia: la póliza reimpresa en agosto cuenta lo mismo que la impresa en julio.
7.6 Aplicar: qué pasa exactamente
aplicar() exige el grupo Pólizas de cobro / Responsable en el propio método, no solo en el botón. Los groups de un botón son cosmética del cliente web y este método se alcanza por RPC, desde la lista de facturas y desde el cron (el cron corre en modo superusuario y se salta la compuerta a propósito: su decisión de negocio vive en sus dos interruptores).
Después, en este orden y dentro de una sola transacción:
_validar: la ruta genera póliza, tiene cuenta destino y esa cuenta es de la compañía. Se re-sincronizan los importes contra el residual vigente —si alguien cobró parcialmente entre la preparación y el clic, el cambio queda escrito en el chatter—. Se rechaza cualquier línea que ya no tenga saldo o que ya esté en otra póliza aplicada, y las que la política de la compañía manda excluir por CFDI cancelado o sin timbrar. Se comprueba que el detalle sume el total.- Se crea el asiento: un cargo a la cuenta de la ruta por el total, y un abono por factura a la cuenta de cuentas por cobrar que esa factura usó, con su cliente. Nunca una cuenta global del plan.
action_postpublica. Si la fecha cae en periodo cerrado o el diario no tiene secuencia, el error se traduce a un mensaje accionable._enlazar_lineas_polizaempareja cada línea del detalle con su contrapartida verificando la cuenta antes de sellar. Si no casan, aborta: no sella mal.- Se sellan
move_id,diario_id,cuenta_destino_id, usuario y fecha de aplicación; el estado pasa a Aplicado. _conciliarcasa par a par: línea de CxC contra su contrapartida. Una por una.
Si cualquiera de esos pasos falla, la excepción propaga y Odoo revierte todo. No existe el estado "publicada pero sin conciliar". Publicar 39 de 40 facturas sería peor que no publicar ninguna.
Para el implantador: no "mejores" esto con commits intermedios ni con un conciliar diferido. La atomicidad es la propiedad que hace auditable el producto.
Simulador: qué asiento genera cada forma de pago
La contracuenta es siempre la cuenta por cobrar que esa factura cargó, nunca una constante del plan. Los códigos son los sugeridos por la semilla: el asistente los resuelve contra el catálogo real de cada compañía.
7.7 Cómo se lee un folio
El folio es determinista: no hay ir.sequence; la unicidad la garantiza la constraint UNIQUE(company_id, name).
| Prefijo | Operación | De dónde sale |
|---|---|---|
POL-ING-… | Cobro (ingreso) | prefijo_folio de la ruta; las semillas traen POL-ING-EFE, POL-ING-TRF, POL-ING-TPV, POL-ING-INT, POL-ING-IDF |
POL-DIA-… | Cobro con póliza de diario | prefijo_folio de la ruta: POL-DIA-ANT, POL-DIA-COM, POL-DIA-INC (las semillas ESP y NCR llevan prefijo pero no generan póliza) |
POL-COR | Corrección por traspaso | Fijo en _generar_folio |
POL-LIB | Liberación de facturas | Fijo en _generar_folio |
POL-DEP | Depósito / liquidación | Fijo en _generar_folio |
Al prefijo se le añade el código de ruta si la compañía tiene folio_incluye_ruta; luego la fecha AAAA-MM-DD; el nombre de la moneda si no es la de la compañía; y un sufijo -2, -3 si ya existe uno igual.
El folio NUNCA se parsea para obtener la fecha ni la ruta: ambas son campos. Un folio con sufijo, con moneda o con la ruta suprimida por configuración rompe cualquier lector de cadenas.
Para el implantador: en el asiento contable el folio viaja en ref, no en name —el name lo impone la secuencia del diario—. Por eso buscar asientos con name like 'POL-' devuelve cero. Toda idempotencia y todo cruce se hacen por ref o, mejor, por el enlace move_id de la póliza.
08Los cuatro asientos correctivos y el árbol de decisión
Toda corrección de este módulo empieza por una sola pregunta, y la respuesta determina el método:
¿Hay que CAMBIAR DE CUENTA, o hay que SACAR ALGO del asiento?
Cambiar de cuenta significa que el cobro existió, es correcto y está bien reconocido: solo está en la cuenta equivocada. Sacar algo significa que ese renglón no debe estar reconocido como cobro en absoluto: el CFDI se canceló, entró un día que no era, o el cobro nunca ocurrió.
¿Qué está mal en la póliza aplicada?
│
┌─────────────────────────┴─────────────────────────┐
Está en la Algo no debe
cuenta equivocada estar dentro
│ │
TRASPASO (POL-COR) ┌──────────────┴──────────────┐
el DEFAULT Sale una parte Sale TODO
no desconcilia │ │
LIBERACIÓN (POL-LIB) REVERSIÓN
rompe solo ese par asiento de reversa
│ casado al original
¿imposible aislar el par?
│
RECONSTRUCCIÓN
último recurso
8.1 Traspaso (POL-COR) — el default
Reclasificación pura: carga la cuenta correcta y abona la que se usó por error. El asiento se agrupa por origen, así que si tres cuentas distintas alimentaron el error, hay tres abonos y un cargo.
Qué no hace, y es lo que lo vuelve el camino primario: no desconcilia nada, no toca payment_state y no vuelve a disparar el IVA de base de flujo, cuyo disparador es la conciliación, no la póliza. La factura sigue cobrada, con la misma fecha y el mismo periodo.
La cuenta de origen no se deduce de la ruta sino de veniu_cuenta_real_id: la contrapartida dominante de las conciliaciones de esa factura (y, solo si viene vacía, la cuenta sellada de la póliza o la de la ruta aplicada como respaldo). Por eso una segunda corrección parte de la cuenta ya corregida y no vuelve a mover lo movido. Y por eso es idempotente: si el origen ya coincide con el destino, esa línea se salta; si todas coinciden, el motor se niega con "ya están en su cuenta correcta: no hay nada que traspasar", en vez de publicar un asiento a cero.
La fecha del asiento es la de la póliza original (agrupado por día y ruta, que es lo que deja correcto el saldo diario de caja para el arqueo). Después, _conciliar_traspaso intenta casar el abono nuevo contra el cargo de la póliza original; si no puede —cuenta no conciliable, importes que no casan— deja constancia en el chatter y sigue: el traspaso ya cumplió su función contable.
El asistente Corregir ruteo enseña, antes de ejecutar, una tabla de "sale de (abono) / entra a (cargo)" con los importes.
8.2 Liberación (POL-LIB) — el bisturí
Saca facturas concretas de una póliza aplicada sin tocar a las demás. Mecánica, en tres pasos:
- Se rompe solo el par conciliado de esa factura: su línea de CxC contra la contrapartida que le tocó en la póliza (el sello
poliza_line_id, que es justo la pieza que permite ser quirúrgico). Quedan las dos abiertas: la factura vuelve a deber y el abono de la póliza queda huérfano. - Se publica la póliza de liberación: cargo a la CxC de esa factura —con su cliente— y abono a la cuenta que la póliza había cargado. La caja baja exactamente lo que subió de más.
- Ese cargo nuevo se concilia contra el abono huérfano de la original, que así queda cerrada consigo misma y sin saldo suelto.
La póliza original conserva su asiento, su folio y su expediente. Su línea queda marcada liberada con el enlace a la póliza que la sacó, y como _lineas_ya_aplicadas ignora las liberadas, la factura vuelve a quedar abierta y agrupable: se puede volver a cobrar en la póliza que le toque.
Dos límites que conviene conocer antes de necesitarlos:
- No se puede liberar la última factura viva. Si salen todas, eso no es una liberación: es una reversión, y el módulo lo dice con esas palabras y manda al botón Revertir, que además deja la reversa casada contra el original. El asistente ni siquiera abre si la póliza tiene menos de dos líneas vivas.
- Revertir una liberación deja un paso manual. Las líneas vuelven a figurar en la póliza original, pero la conciliación factura–póliza que la liberación rompió no se rehace sola. El chatter de la póliza original lo escribe: hay que conciliar a mano o volver a agrupar la factura.
También hace falta que la póliza tenga sellada su cuenta y que cada línea tenga su contrapartida sellada. Si la póliza se concilió por fuera del módulo, el motor se niega y remite a liberarla a mano o a la reconstrucción.
8.3 Reconstrucción — último recurso
Revierte la póliza entera y la rehace con la fecha original, que nunca se mueve: cambiarla movería de periodo el IVA base flujo.
Desconcilia todas las facturas del lote, incluidas las que estaban bien. El asistente cuantifica el daño antes de ejecutar: cuántas líneas correctas se van a desconciliar solo por compartir póliza. En el caso real habrían sido 301 correctas para arreglar 45.
Dos comportamientos fijos: siempre excluye los CFDI cancelados de la póliza nueva, sin depender de la política de la compañía —si no, la reconstrucción volvería a meter justo lo que se estaba sacando— y deja escrito en el chatter cuáles salieron. Y si todas las facturas del lote están canceladas, se detiene: la reversión que acaba de aplicarse ya dejó la contabilidad en su sitio.
Para el implantador: existe el interruptor permitir_reconstruccion en la configuración de la compañía. En clientes con IVA base flujo y periodos ya declarados, apágalo y deja vivos traspaso y liberación.
8.4 Reversión — deshacer la póliza entera
Desconcilia solo las contrapartidas propias (nunca button_draft, que arrastraría facturas ajenas), y revierte el asiento con _reverse_moves(cancel=True), que publica la reversa y la casa contra el original. Exige motivo escrito, la casilla explícita de "Confirmo que entiendo el impacto", el grupo Responsable y que la póliza esté aplicada.
Nunca borra. Una póliza aplicada o revertida no se elimina: el ondelete lo rechaza y remite a archivar (campo active). Borrarla arrastraría el detalle en cascada —con él, el historial de la factura, el motivo y el autor de la reversión— y liberaría el folio para que otra póliza recibiera el mismo número.
Árbol de decisión: ¿qué corrección aplico?
¿Qué está mal en esa póliza?
¿La factura sigue siendo un cobro legítimo (CFDI vigente)?
¿Cuántas facturas de esa póliza tienen que salir?
¿Hay que volver a generarla con las mismas facturas?
Es el camino barato y es el correcto. Carga la cuenta buena y abona la usada por error, con la fecha original. No desconcilia nada, no toca el estado de pago y no vuelve a disparar el IVA de base de flujo. Las demás facturas de la póliza ni se enteran.
Póliza aplicada → botón Corregir ruteo → método Traspaso.
El bisturí. Rompe solo el par conciliado de las facturas marcadas, publica el cargo a su cuenta por cobrar contra la cuenta que el corte había cargado, y lo concilia con el abono que queda huérfano. La póliza original conserva su asiento y su folio; las demás facturas no se tocan y su IVA no se vuelve a disparar. Las liberadas quedan abiertas y agrupables otra vez.
Póliza aplicada → botón Liberar facturas → marcar las que salen + motivo.
Último recurso: desconcilia TODAS las facturas del lote y les re-dispara el IVA de base de flujo, aunque estuvieran bien. En el caso real habría pasado 301 facturas correctas por una operación destructiva para arreglar 45. Antes de elegirla, pregúntate si la liberación no resuelve lo mismo tocando solo lo que hay que tocar.
Póliza aplicada → Corregir ruteo → método Reconstrucción (se puede prohibir por configuración).
Deshace la póliza completa con un asiento de reversa casado contra el original, con motivo obligatorio. Nunca borra nada. Si la póliza ya tiene traspasos o liberaciones aplicados encima, el módulo lo impide hasta que se deshagan esas derivadas primero: revertir sin mirarlas deja la cuenta de origen en negativo y la de destino cargada dos veces.
Póliza aplicada → botón Revertir.
8.5 Tabla comparativa
Traspaso POL-COR | Liberación POL-LIB | Reconstrucción | Reversión | |
|---|---|---|---|---|
| Qué toca | Solo cuentas de activo: carga la correcta, abona la usada | Rompe un par y publica cargo a CxC + abono a la cuenta cargada | Revierte el lote y crea póliza(s) nueva(s) | Un asiento de reversa casado contra el original |
| Qué desconcilia | Nada | Solo el par de las facturas que salen | Todas las facturas del lote | Todas las facturas del lote |
| ¿Re-dispara IVA base flujo? | No | Solo el de las facturas liberadas | Sí, el de todas | Sí, el de todas |
| Sobre qué factura actúa | Las mal ruteadas | Las que se seleccionan | Todo el lote | Todo el lote |
| Estado de la factura al final | Cobrada (sin cambio) | Abierta y agrupable de nuevo | Cobrada en la póliza nueva, o fuera si su CFDI está cancelado | Abierta |
| Fecha | La original de la póliza | La original de la póliza | La original de la póliza | La del asiento original |
| La póliza original | Intacta y aplicada | Intacta y aplicada | Queda revertida | Queda revertida |
| Coste operativo | Bajo | Bajo, acotado a lo que sale | Alto y colateral | Alto |
8.6 La regla dura de las derivadas
Una póliza que ya tiene traspasos o liberaciones aplicados encima NO se revierte ni se reconstruye hasta deshacer primero sus derivadas. El módulo lo bloquea nombrando el folio de la derivada. El orden correcto es al revés: primero la derivada, después la original.
El descuadre que esto evita no da ningún mensaje de error, porque cada asiento cuadra consigo mismo. Con cien pesos:
| Momento | Caja de venta | TPV por cobrar | CxC del cliente |
|---|---|---|---|
| Póliza P aplicada (cargo a Caja) | +100 | 0 | −100 |
| Traspaso T encima (carga TPV, abona Caja) | 0 | +100 | −100 |
| Se revierte P sin mirar a T | −100 | +100 | 0 |
| O se reconstruye P en TPV | −100 | +200 | −100 |
Caja queda en negativo por un cobro que sí entró, y TPV queda cargada dos veces por un cobro que solo ocurrió una. Nadie se entera hasta el arqueo, y para entonces hay que reconstruir la historia a mano.
Para el contador: si al intentar revertir aparece el mensaje con folios de pólizas derivadas, no busques la manera de saltarlo. Abre esas pólizas —el botón de estadística Derivadas las lista—, reviértelas y vuelve.
8.7 El caso típico: cancelaron un CFDI que ya estaba en una póliza
Es el caso más frecuente y el que separa un producto de un remiendo. El cliente pidió la cancelación, el CFDI pasó a cancel (o a global_cancel, si es una factura global de mostrador) y esa factura está dentro de una póliza aplicada junto a otras treinta y nueve que están bien.
Un CFDI cancelado no es ingreso. No se arregla moviéndolo de cuenta: hay que sacarlo del asiento. Y el traspaso lo bloquea a propósito, remitiendo al camino correcto.
Ruta correcta, paso a paso:
- Confirmar el estado real del comprobante en la factura. Si está
canceloglobal_cancel, sigue; si solo está en proceso, no toques nada todavía. - Comprobar en la póliza que no tenga derivadas aplicadas que afecten a esa línea. El botón Derivadas lo dice.
- Abrir la póliza y pulsar Liberar facturas. Marcar únicamente la factura cancelada, escribir el motivo —es lo que va a leer el contador dentro de seis meses— y dejar activada la casilla de fecha original.
- Leer el bloque "Qué va a pasar": dice cuántas facturas se quedan, cuánto sale y advierte de las canceladas.
- Aplicar. Nace la póliza
POL-LIBcon la fecha de la original, la caja baja exactamente ese importe, la factura queda abierta y la póliza original conserva asiento y folio. - Cerrar el asunto por la vía fiscal, no por la contable: si el cliente no pagó, la factura se queda abierta y sigue su curso de cartera; si pagó y el comprobante se canceló por error, lo que corresponde es nota de crédito o factura nueva, no volver a meterla en una póliza.
Por qué la liberación y no la reconstrucción: la reconstrucción resuelve lo mismo, pero desconcilia las treinta y nueve facturas correctas y vuelve a disparar su IVA de base de flujo. Si esas facturas cayeron en un mes ya declarado, el impuesto se mueve de periodo y la declaración deja de cuadrar contra la contabilidad. La liberación toca una y deja quietas a las demás.
Para el implantador: cuando el cliente llega con un lote histórico de cancelaciones dentro de pólizas ya aplicadas, la secuencia de implantación es esta —liberar una por una, verificando saldo de la cuenta origen después de cada bloque— y no una reconstrucción masiva. La reconstrucción se reserva para pólizas cuyo par conciliado no se puede aislar porque la conciliación se hizo fuera del módulo.
09Cerrar el ciclo: depósito a banco y liquidación de terminal
Una póliza de cobro mueve el dinero de Clientes a una cuenta de tesorería. Ahí no termina: termina cuando el dinero sale de esa cuenta de tránsito y llega al banco. Mientras eso no pase, el ciclo está a la mitad y el balance afirma algo que se puede refutar con un arqueo.
9.1 Por qué una cuenta que solo recibe cargos miente
Una cuenta de caja con cargos y cero abonos declara que el efectivo se acumula físicamente para siempre. En el 99 % de los casos es falso: el dinero sí se depositó, nadie registró el asiento. El daño no es estético:
- Es la partida más fácil de refutar. El efectivo se cuenta. Un arqueo sorpresa destruye el saldo en diez minutos.
- Alimenta la presunción de ingresos por depósitos bancarios no registrados (art. 59 fr. III del CFF): el dinero aparece en el estado de cuenta sin un asiento que lo explique.
- Una caja abultada se lee como préstamo a socios no documentado, dividendo ficto o venta no reportada.
- Vuelve inútil el propio corte de caja: nadie arquea contra un saldo que nunca baja.
9.2 El depósito: abono a la ruta, cargo a la cuenta puente
La operación vive en Pólizas de cobro → Depósito / liquidación. Genera una póliza tipo_operacion = 'deposito', tipo de póliza egreso, con folio POL-DEP-.
El asiento que arma _construir_move_vals_deposito() es literal:
POL-DEP-EFE-2026-08-11 ref: folio · ficha de depósito
Abono [cuenta de la ruta EFE] Depósito desde EFE ...... 88,379.50
Cargo [cuenta puente] ficha #NNNN ................. 88,379.50
Regla cardinal: ninguna póliza toca Bancos. El saldo del banco lo juzga el estado de cuenta, no la captura.
La pantalla la hace cumplir por partida doble. veniu.poliza.config.cuentas_reservadas_banco() es la definición del módulo de "cuenta de banco": la default_account_id de los diarios de tipo bank de esa compañía. Esas cuentas quedan fuera del dominio del selector de cuenta destino y, además, si la elegida está en esa lista action_depositar se niega y explica el porqué —el mismo depósito quedaría registrado dos veces el día que se importe el extracto—. No se mira account_type: en el plan mexicano Caja (101) y Bancos (102) comparten asset_cash, así que lo único que distingue al banco de verdad es que un diario bancario lo declara suyo.
La transitoria del extracto (suspense_account_id) sí es destino legítimo, y por eso queda fuera de la lista prohibida: no es el banco, es precisamente la cuenta puente que la conciliación bancaria limpia. Si la configuración trae como cuenta de depósito una cuenta reservada, el asistente abre el campo vacío en lugar de precargarla: no se convierte el error contable en el camino de menor resistencia.
9.3 La liquidación de terminal llega neta
El dinero de las rutas TPV e INT lo tiene un tercero. El asistente lo detecta solo (es_liquidacion: código de ruta TPV/INT, o cuenta de origen de tipo asset_receivable) y levanta el aviso que explica que el depósito llegará neto; los campos de comisión están siempre en pantalla.
El importe que se captura en Importe depositado es el bruto: el valor facial de lo que se le cobró al cliente, no lo que abonó la adquirente. La comisión y su IVA se capturan aparte, y neto = monto − comisión − IVA es lo que debe coincidir con el abono del estado de cuenta.
Abono [tpv_por_cobrar] Depósito desde TPV ........... 10,000.00
Cargo [cuenta puente] ........................ 9,779.60
Cargo [comisión] Comisión sobre el depósito ... 190.00
Cargo [IVA acreditable] IVA acreditable de la comisión . 30.40
Las cuentas de comisión e IVA que elige el operador en la pantalla mandan sobre las de la configuración: crear_deposito() las recibe como argumento y solo cae a la configuración si vienen vacías. Y los importes se persisten en la póliza (deposito_comision, deposito_iva_comision, referencia_externa), no solo en el asiento: son el soporte de la liquidación frente a la adquirente.
El IVA de la comisión no se acredita sin el CFDI de la adquirente. Si llega antes el estado de cuenta que el comprobante, la comisión va completa a gasto y se reclasifica al recibir el CFDI. Acreditar contra un estado de cuenta es un IVA acreditado improcedente.
Otras compuertas de action_depositar: importe mayor que cero, neto no negativo, la ruta de origen debe tener cuenta, destino distinto de origen (si no, el asiento se anula solo y el dinero sigue declarado en la caja), comisión sin cuenta de comisión, IVA sin cuenta de IVA, y toda cuenta y el diario deben pertenecer a la compañía. Aplicar exige el grupo Responsable, como cualquier póliza.
9.4 Antigüedad esperada por cuenta
Lo que se vigila no es el saldo: es su edad.
| Cuenta | Antigüedad esperada | Alarma |
|---|---|---|
| Caja de venta | ≤ 3 días, hasta el depósito | > 7 días |
| TPV por cobrar | ≤ 3 días con adquirente bancaria, ≤ 15 si no lo es | > 15 días |
| Recibos pendientes | hasta la próxima conciliación | > 30 días |
| Intermediarios por cobrar | lo que diga el contrato (15-30 días) | contrato + 15 |
| Cobros por identificar | cero al cierre del mes | cualquier saldo el día 1 |
La cuarentena se mide sola: alerta_cuarentena() lee los apuntes publicados y sin conciliar de la cuenta de la ruta de cuarentena y publica saldo, número de apuntes y días del más viejo contra dias_cuarentena (30 de fábrica).
9.5 Lo que este producto no hace
La conciliación bancaria contra extractos es proceso nativo de Odoo y este módulo no la ejecuta. Lo que hace es habilitarla: rutea a cuentas que el widget de conciliación ya conoce y deja saldos que sí se pueden amarrar contra una línea de extracto. Si el cliente nunca carga extractos, Bancos seguirá en cero por mucho depósito que se capture — y eso es un pendiente que se declara, no que se disimula.
Para el contador de Galgo: CAJA DE VENTA acumuló $1,359,077.56 en cargos contra $240,140.17 de abonos — el efectivo entró contablemente y casi nunca salió a banco. Pero la reapertura de PUE del 6 y 7 de agosto revirtió 22 de las 24 pólizas de julio, y esas reversas abonaron la cuenta: al cierre de esa jornada el saldo quedó en negativo, cerca de −$57,159.
Por eso aquí la primera tarea productiva no es depositar. Es cuadrar:
- Explicar el saldo negativo con el contador. El sospechoso está identificado: los $240,140.17 de cartera recuperada entraron por el diario
CAJAVy abonan la misma cuenta que ahora abonaron también las reversas. Si hay doble abono, se corrige antes de tocar nada más. - Reagrupar el universo reabierto por día × ruta × moneda, que es lo que vuelve a cargar la caja con lo que de verdad se cobró en efectivo.
- Entonces sí, depositar: una póliza por ficha de depósito, con su referencia, hasta que el saldo contable se parezca al efectivo que de verdad hay en el cajón. Nunca un solo asiento por el acumulado: el auditor pide la ficha, no el total.
10Controles, segregación de funciones y expediente
Una campaña de corrección masiva mueve millones con dos clics. El control no puede ser la buena voluntad del operador.
10.1 Los tres grupos
| Grupo (nombre exacto) | Hereda | Qué puede |
|---|---|---|
| Pólizas de cobro / Consulta y preparación | account.group_account_invoice | Ve el tablero y todas las pólizas. Crea y edita pólizas solo en borrador o por aprobar. Usa el asistente de agrupar. No publica. No borra. Catálogos (rutas, mapa, configuración) en solo lectura. |
| Pólizas de cobro / Responsable | Consulta + account.group_account_user | Aplica, aprueba, revierte, libera y corrige. Escribe sobre cualquier póliza, en cualquier estado. Únicos asistentes suyos: revertir, corregir, liberar, depósito. |
| Pólizas de cobro / Configuración | Responsable + account.group_account_manager | Rutas, mapa de formas SAT, políticas y asistente de configuración. |
base.group_system implica el grupo de Configuración: el administrador técnico lo tiene sin asignarlo a mano.
El reparto de reglas es tan importante como el de accesos. Las reglas globales acotan todo por company_id in company_ids. Sobre eso, dos reglas por estado: Consulta solo escribe pólizas y detalle cuyo estado esté en ('borrador','por_aprobar'); Responsable tiene una regla permisiva [(1,'=',1)] que no es decorativa — hereda Consulta por implied_ids, las reglas con grupos se combinan con OR, y sin ella el Responsable no podría escribir una póliza ya aplicada y por tanto no podría revertirla.
10.2 La compuerta vive en el motor, no en el botón
Los groups de un botón son cosmética del cliente web.
aplicar() llama a _check_responsable() en su primera línea, antes de cualquier validación de negocio. Lo mismo revertir(), action_aprobar, action_liberar, action_revertir, action_corregir, action_aplicar, liberar_lineas() y corregir_reconstruccion(). Esos métodos se alcanzan por RPC, desde la lista de facturas y desde el cron: esconder el botón no protege nada. Quien no tiene el grupo recibe un mensaje que además le dice qué sí puede hacer: preparar la póliza y dejarla en borrador o enviarla a aprobación.
La única excepción es el modo superusuario (env.su), donde la compuerta se salta a propósito: el cron de cierre corre con sudo() y no tiene un humano detrás al que exigirle un grupo. Ese camino está cerrado por otro lado (capítulo 11).
10.3 La aprobación por monto
Se enciende con exige_aprobacion en la configuración de la compañía y se calibra con aprobacion_monto. Cuando el importe absoluto de la póliza alcanza o rebasa ese monto, el lote nace en por_aprobar en lugar de aplicarse, y deja nota en el chatter. Con el monto en 0, todas las pólizas pasan por aprobación.
Quien la levanta es el Responsable, con Aprobar y aplicar: action_aprobar exige el grupo, registra en el chatter quién aprobó y encadena aplicar(). Si alguien intenta saltarse el paso con Aplicar póliza sobre un lote en borrador que rebasa el umbral, action_aplicar lo detiene y le dice que la envíe a aprobación.
10.4 Una póliza que tocó el mayor no se borra: se archiva
El @api.ondelete _no_borrar_aplicados rechaza el borrado de cualquier lote en estado aplicado o revertido, o que tenga asiento. Se archiva con el campo active. Dos razones concretas:
- Una póliza revertida es el expediente de dos asientos vivos —el original y su reversa—. Borrarla arrastraba
linea_idsen cascada, y con ellas el historial factura por factura, el motivo y el autor de la reversión. - Liberaba el folio.
_generar_foliobusca conactive_test=Falsejustamente para no reasignar el número de una póliza archivada. Sin el candado, dos operaciones distintas acabarían con el mismo folio en el expediente.
Archivar oculta de las listas sin tocar el asiento ni el folio. Lo que sí se borra es lo que nunca tocó el mayor: una póliza en borrador o cancelada.
10.5 El rastro que queda
| Qué preguntará el auditor | Dónde está |
|---|---|
| Quién la preparó | usuario_id — Preparado por |
| Quién la aplicó y cuándo | usuario_aplicacion_id, fecha_aplicacion |
| Quién la revirtió, cuándo y por qué | usuario_reversion_id, fecha_reversion, motivo_reversion (obligatorio: revertir() rechaza motivo vacío) |
| Contra qué cuenta se publicó | cuenta_destino_id — snapshot al aplicar; si mañana cambia la ruta, el histórico no miente |
| Qué método de corrección se usó | metodo_correccion (traspaso / reconstrucción) |
| Qué pólizas nacieron de ésta | lote_origen_id / lote_hijo_ids, pestaña Trazabilidad y botón Derivadas |
| Qué pasó con una factura concreta | en la línea: ruta_correcta_id, ruta_aplicada_id, ruta_original_id, ruta_forzada, liberada, lote_liberacion_id, conciliado, full_reconcile_id |
| Desde la factura, en qué póliza cayó | veniu_poliza_lote_id, veniu_poliza_folio |
| La narrativa completa | el chatter: el lote hereda mail.thread y publica en aplicar, aprobar, cancelar, revertir, liberar, depositar y cierre automático |
10.6 Qué se prueba antes del go-live
Para el implantador: con usuarios reales de cada grupo, no con el administrador. Un sudo de más invalida toda la prueba.
| Prueba | Resultado exigido |
|---|---|
| Consulta agrupa un día y deja la póliza en borrador | Funciona |
| Consulta pulsa Aplicar póliza | Error de _check_responsable |
| Consulta intenta editar una póliza ya aplicada | Bloqueado por la regla de estado |
Consulta llama aplicar() por RPC | Mismo error: la compuerta está en el motor |
| Responsable aplica, revierte con motivo y libera una factura | Funciona; el chatter registra las tres |
| Responsable intenta borrar una póliza aplicada | Error, con la sugerencia de archivar |
| Configuración cambia una ruta y su cuenta | Funciona; Responsable no |
| En multi-compañía, un usuario de la compañía A busca pólizas de la B | Cero resultados |
11Automatizar o no: el cron de cierre
El módulo trae un cierre automático. Viene apagado, y apagarlo dos veces es parte del diseño.
11.1 Los dos interruptores
| Interruptor | Dónde | Quién lo enciende | De fábrica |
|---|---|---|---|
ir.cron.active | acción planificada "Pólizas de cobro: cierre del día anterior" | el implantador | False |
cron_habilitado | configuración por compañía | el contador | False |
Encender uno solo no hace absolutamente nada: el cron corre, lee la configuración de cada compañía y sale sin tocar nada si cron_habilitado es falso. La razón no es redundancia por gusto: una restauración de base de datos con el cron activo no debe disparar cierres sorpresa en una copia de trabajo. Además el registro va con noupdate="1": una actualización del módulo nunca vuelve a encender un cron que el cliente apagó a propósito.
11.2 Qué entra
Solo rutas con permiso explícito de cierre automático: permite_cron = True, con cuenta destino resuelta y que generen póliza. En la semilla del producto únicamente la ruta EFE lo trae. El efectivo es lo único que se puede cerrar sin criterio humano — y aun así hacen falta los dos interruptores. Todas las demás (TRF, TPV, INT, IDF, ANT, COM, INC, ESP, NCR) están en False.
Sobre eso, el cron aplica el mismo dominio de cobrables que el tablero, más: la compañía de la configuración (forzada con allowed_company_ids, no la del usuario del cron), la ruta correcta dentro de las rutas permitidas, CFDI no cancelado, y estado de ruteo distinto de sin_mapa. Lo que no tiene ruta clara no se cierra solo: se queda esperando a una persona.
La aprobación por monto sigue mandando. crear_desde_facturas aplica solo los lotes que quedaron en borrador; los que la política mandó a por_aprobar se quedan ahí, esperando al Responsable, aunque los haya creado el cron.
11.3 Los topes
| Tope | Valor | Por qué |
|---|---|---|
| Días de gracia | cron_dias_gracia, 1 de fábrica | Nunca cierra el día en curso: la fecha tope es hoy menos los días de gracia, con mínimo de 1 |
| Ventana hacia atrás | CRON_VENTANA_DIAS = 30 | Sin cota inferior, el primer disparo barría todos los ejercicios abiertos en una sola transacción. Lo viejo es cartera histórica y se agrupa a mano, mirándolo |
| Facturas por corrida y compañía | CRON_LIMITE_FACTURAS = 500 | Un timeout del worker no debe dejar medio cierre aplicado. Al alcanzarse queda una advertencia en el log y el resto se cierra en la corrida siguiente |
| Aislamiento | savepoint por compañía | Una compañía que falla se deshace por completo y las demás siguen; la excepción queda en el log con su nombre |
La periodicidad del registro es de 1 día. Cada póliza que nace por esta vía queda marcada en su chatter como "Generada por el cierre automático", y el total de la corrida queda en el log del servidor.
11.4 Cuándo se enciende
REGLA: el cierre automático no se enciende hasta que 30 días de operación manual salgan limpios. Limpios significa: cuadre póliza ↔ facturas al peso, cuarentena en cero al corte del mes, cero facturas con estado sin mapa, y ninguna corrección de ruteo pendiente.
El error que este módulo existe para prevenir es exactamente automatizar el corte sin mirar la forma de pago: en el caso que originó el producto, 45 de 480 facturas —$165,761.85, el 12.2 %— acabaron declaradas como efectivo sin serlo. Un cron encendido de fábrica reproduce ese error todas las noches y sin testigo, porque nadie revisa lo que salió bien mil veces.
Hay una segunda razón, operativa y menos discutible: un día puede seguir facturando. El corte de caja se cierra contra el arqueo físico, no contra un reloj. El día que se documentó llevaba 23 facturas a las 13:26 h y cerró con 50 a las 22:16 h. Un cron que hubiera corrido a media tarde habría emitido una póliza incompleta y correcta, que es la peor combinación: cuadra consigo misma y no cuadra con la caja.
Para el implantador: el cierre automático es la última fase de la implantación, nunca la primera, y se entrega documentando qué rutas quedaron con permite_cron y con qué días de gracia. Si el cliente pide encenderlo el día uno, lo que está pidiendo es saltarse el piloto.
12Cierre mensual y evidencia
El mes no se cierra cuando se apagó la luz: se cierra cuando cada peso cobrado tiene una cuenta que lo explica y un papel que lo prueba. Este capítulo es el orden en que se revisa eso, y qué queda como evidencia después.
12.1 El checklist del cierre
Se corre en este orden. Cada punto habilita al siguiente: depositar la caja antes de cerrar los cortes deposita un saldo incompleto.
| # | Punto de control | Dónde se mira | Verde cuando |
|---|---|---|---|
| 1 | No quedan pólizas sin aplicar | Pólizas de cobro ▸ Pólizas, filtro Borrador (cubre Borrador y Por aprobar) + rango de Fecha | Cero resultados con fecha del mes |
| 2 | No quedan días sin corte | Facturas por agrupar (solo lista PUE cobrables), acotado al mes | Lista vacía, o cada excepción explicada |
| 3 | Cuarentena en cero | Mayor de la cuenta destino de la ruta IDF: apuntes publicados y sin conciliar | Sin saldo abierto — lo que alerta_cuarentena() reporta como "Cuarentena limpia" |
| 4 | Antigüedad de las transitorias revisada | Mayor de las cuentas de TRF, TPV e INT | Nada abierto más allá del ciclo normal de depósito de cada canal |
| 5 | Ruteo del mes sin errores | Revisión de ruteo, filtro Mal ruteadas | Cero. Lo que aparezca se corrige con traspaso antes de cerrar |
| 6 | Depósitos y liquidaciones aplicados | Pólizas POL-DEP del mes | El saldo de caja es el efectivo que de verdad está en la caja fuerte |
| 7 | Balanza cuadrada | Reportes de contabilidad | Cargos = abonos y ninguna cuenta destino con signo imposible (caja en negativo) |
Regla dura. La cuenta de cobros por identificar (ruta IDF) llega a CERO al cierre. No es una meta de calidad: un CFDI timbrado como PUE ya le afirmó al SAT que se cobró. Mientras viva en cuarentena, el ingreso está declarado y la tesorería no cuadra contra nada.
Para el contador: un punto 4 sucio casi siempre significa que la adquirente depositó y nadie aplicó la póliza de liquidación, no que falte un cobro.
Para el implantador: los puntos 1, 2 y 5 son consultas de un minuto. Deja los tres filtros guardados como favoritos del usuario del cliente en el arranque; es la diferencia entre un cierre que se hace y uno que se promete. El punto 3 es el mayor de la cuenta de la ruta IDF acotado a apuntes sin conciliar: déjalo también como favorito.
12.2 La alerta que el módulo ya calcula
alerta_cuarentena() (en models/veniu_poliza_config.py) mide la cuenta destino de la ruta configurada en ruta_cuarentena_id, para la compañía de la configuración, y sobre apuntes publicados y sin conciliar (full_reconcile_id vacío). Un apunte ya conciliado es un cobro que alguien reclasificó: salió de la cuarentena aunque el asiento siga existiendo.
Devuelve saldo, número de apuntes, fecha del apunte abierto más antiguo, antigüedad en días, la cuenta medida y si rebasó dias_cuarentena (30 de fábrica). El campo aviso_cuarentena traduce eso a una sola frase, en cuatro formas posibles:
- Cuarentena limpia:
no tiene cobros abiertos. — el punto 3 del checklist está verde. - En plazo · 12430.00 MXN en 7 apuntes sin conciliar · el más antiguo lleva 11 días (límite 30).
- VENCIDA · … — hay dinero cuyo canal de entrada nadie investigó desde hace más días de los permitidos.
- El motivo por el que no se pudo medir: no hay ruta de cuarentena configurada, o esa ruta no tiene cuenta destino.
El método action_ver_cuarentena() abre exactamente esos apuntes para depurarlos uno por uno. Tanto el campo como el método viven hoy en el modelo de configuración y no están colocados en el formulario de Ajustes generales: hasta que se añadan, el punto 3 se lee del mayor de la cuenta de la ruta IDF con el mismo filtro (publicados, sin conciliar).
Para el implantador: el campo no está almacenado a propósito. El saldo de una cuenta cambia con cada asiento y un campo almacenado quedaría mintiendo hasta el siguiente guardado de la configuración, que es un registro que nadie toca en meses. Se lee con sudo para que un usuario de cobranza sin permiso sobre el mayor no vea el formulario en blanco.
12.3 Qué se le entrega al contador externo
El paquete no es "los reportes de Odoo": es lo que permite a un tercero rehacer el mes sin llamarte. El diseño completo está en D:\Users\sergi\Claude Code\30_HERRAMIENTAS\veniu_polizas_cobro\90_docs\entregables_contador\:
| Documento | Qué resuelve |
|---|---|
PAQUETE_CONTABLE_MENSUAL.md | La especificación del entregable: estructura de carpetas, nomenclatura, generadores y el reporte de cuadre |
MANUAL_CICLO_CONTABLE_COMPLETO.md | El ciclo de la factura al SAT, con la rutina diaria, semanal, mensual y anual |
INV_SAT_ANEXO24.md | Qué exige el SAT y cuándo: qué es mensual y qué es solo a requerimiento |
INV_ODOO_NATIVO.md | Qué genera Odoo 19 de fábrica para México y qué falta construir |
INV_CONTPAQI.md | Qué puede importar CONTPAQi y por qué el .bak no es el camino |
Tres paquetes, no uno. El mensual lleva balanza, catálogo si cambió, soporte y cuadre. El de requerimiento lleva pólizas y auxiliares, y solo se arma ante un acto de la autoridad: el XML de pólizas exige un TipoSolicitud (acto de fiscalización, compulsa, devolución o compensación) y no existe el valor "mensual". El anual es el mes 13.
De este módulo sale una pieza de ese paquete: el PDF de cada póliza (reporte Póliza de cobro). El auxiliar de las cuentas de tránsito y su antigüedad se arma desde el mayor de Odoo, filtrando la cuenta destino de cada ruta. Ninguno sustituye a la balanza.
12.4 Cómo se audita un mes cerrado
Tres cortes, de grueso a fino. Los tres deben dar el mismo número.
Por ruta. Suma de monto_total (Importe total) de las pólizas aplicadas del mes, agrupadas por Ruta, contra el movimiento del periodo de la cuenta destino de cada ruta en la balanza. Una ruta cuya cuenta se movió más que sus pólizas tiene asientos manuales encima: hay que nombrarlos.
Por folio. El folio de la póliza encabeza siempre el campo Referencia (ref) del asiento; en Odoo el número del asiento lo impone la secuencia del diario, no el módulo. En el Libro Mayor, filtrando el diario de pólizas y el periodo y agrupando por referencia, cada renglón es un folio: POL-ING-EFE-2026-07-31. Abrir la póliza y comparar monto_total y cantidad_lineas (Líneas de CxC) contra el renglón.
Contra el mayor, factura por factura. Es la consulta que prueba que lo agrupado es lo cobrado: en la póliza, cada línea de detalle tiene Conciliado en verdadero, y la Conciliación (full_reconcile_id) queda sellada cuando el par cierra por completo contra la línea de cuentas por cobrar de su factura. Si el importe cuadra pero hay líneas sin conciliar, la póliza movió dinero sin cerrar la factura, y el mayor de clientes seguirá inflado.
Del lado de las facturas la misma prueba se hace al revés: en la lista de facturas, filtro Con póliza asignada, agrupado por Póliza. La etiqueta de póliza de la factura es un campo calculado sobre los detalles de póliza —solo cuenta los lotes de cobro aplicados—: por diseño no puede decir que hay póliza donde no la hay.
12.5 Qué hacer con lo que quedó fuera
Tres cosas sobreviven al cierre. Cada una tiene un instrumento y solo uno.
PUE abiertas de meses ya cerrados. Bandera roja fiscal, no un pendiente de cobranza: se le afirmó al SAT que la operación se cobró en una sola exhibición y no se cobró. No se resuelve con una póliza. Las salidas son cobrarla y documentarla, o cancelar y reexpedir como PPD, y cuál aplica lo decide el contador con el plazo de la facilidad vigente en la mano. Este módulo no la toca.
Facturas sin timbrar. La política politica_sin_timbrar viene en Advertir: se pueden agrupar, pero el operador ve la alerta Sin timbrar en la línea. Al cierre se listan aparte y se timbran o se explican; una factura sin CFDI que ya cobró efectivo es un ingreso sin comprobante.
PPD sin REP. La Ruta 0 las excluye con el motivo es_ppd cuando la política de detección es Solo PUE, y también con PUE + PPD vencidas mientras la factura no haya vencido. El asistente de agrupar las muestra siempre —marcadas como PPD y nacidas desmarcadas—: en rojo de bloqueo salvo que la política sea Todas las abiertas, donde queda en aviso porque el motor sí las dejaría pasar. No es una limitación: una PPD saldada por póliza masiva queda liquidada sin complemento de pago, y el IVA trasladado del mes del cobro se omite. Se cobran con un pago (account.payment) que arrastra el REP, dentro del plazo que fije la Resolución Miscelánea vigente.
13Anexos
Interactivo
Buscador: forma de pago SAT → ruta → cuenta
Clave Forma de pago SAT Ruta Cuenta destino Póliza
Ninguna clave coincide.
A. Forma de pago SAT → ruta → rol de la cuenta
Buscador: forma de pago SAT → ruta → cuenta
| Clave | Forma de pago SAT | Ruta | Cuenta destino | Póliza |
|---|
Ninguna clave coincide.
La pregunta que ordena toda la tabla es una sola: quién tiene ese dinero AHORA. La empresa (caja), el banco sin confirmar (transitoria), o un tercero procesador (cuenta por cobrar). Ninguna ruta apunta jamás a Bancos.
Son 23 filas: las 22 claves del catálogo c_FormaPago que trae la semilla más el comodín para las facturas sin forma de pago capturada.
| Clave | Nombre SAT | Ruta | Nota |
|---|---|---|---|
| 01 | Efectivo | EFE | La única forma que legítimamente entra a la caja de efectivo |
| 02 | Cheque nominativo | TRF | El papel en la mano no es dinero: hasta que el banco lo libera es un cobro pendiente de confirmación |
| 03 | Transferencia electrónica de fondos | TRF | No va a Bancos: postear ahí sin extracto duplica el ingreso el día de la conciliación |
| 04 | Tarjeta de crédito | TPV | Lo tiene la adquirente hasta que deposita, neto de comisión |
| 05 | Monedero electrónico | TPV | Lo liquida el emisor del monedero, no el cliente |
| 06 | Dinero electrónico | TPV | El saldo lo tiene la plataforma emisora hasta que deposita |
| 08 | Vales de despensa | TPV | Los reembolsa la emisora, con su comisión. Nunca es efectivo |
| 12 | Dación en pago | ESP | La contracuenta es el bien recibido y su avalúo. Asiento manual del contador |
| 13 | Pago por subrogación | ESP | Pagó un tercero y ocupa el lugar del acreedor: hay que leer el convenio |
| 14 | Pago por consignación | ESP | El dinero está ante una autoridad judicial, no en la empresa |
| 15 | Condonación | NCR | No genera póliza. Exige CFDI de egreso: el ingreso y el IVA ya se declararon |
| 17 | Compensación | COM | Cliente que también es proveedor. Póliza de diario, sin tesorería. Mismo RFC y convenio firmado |
| 23 | Novación | ESP | La obligación original se sustituyó: hay que leer el nuevo instrumento |
| 24 | Confusión | ESP | Acreedor y deudor son la misma persona. Normalmente ligado a una fusión |
| 25 | Remisión de deuda | NCR | No genera póliza. Mismo camino que la condonación |
| 26 | Prescripción o caducidad | INC | Se castiga contra la estimación de incobrables. La deducibilidad la decide el contador |
| 27 | A satisfacción del acreedor | ESP | Clave residual sin contracuenta única: obliga a leer el soporte |
| 28 | Tarjeta de débito | TPV | Misma cuenta que crédito: la adquirente deposita un solo importe neto mezclado |
| 29 | Tarjeta de servicios | TPV | Mismo circuito: liquida la adquirente, neto de comisión |
| 30 | Aplicación de anticipos | ANT | El dinero ya entró antes. Solo cancela el pasivo contra la CxC: póliza de diario |
| 31 | Intermediario de pagos | INT | Marketplaces y pasarelas: liquidan neto de comisiones del 15 al 30 % |
| 99 | Por definir | IDF | PUE + 99 es inconsistente con la guía de llenado del CFDI 4.0. Abrir el XML timbrado |
| — | Sin forma de pago (comodín) | IDF | Dato operativo faltante, no problema fiscal. Cuarentena hasta que el vendedor diga cómo entró el dinero |
Rol contable de cada ruta:
| Ruta | Cuenta sugerida | Tipo | Concilia | Folio | Póliza |
|---|---|---|---|---|---|
EFE | 101.02.01 Caja de venta | Efectivo | No | POL-ING-EFE | Ingreso |
TRF | 102.01.04 Recibos pendientes de conciliar | Activo circulante | Sí | POL-ING-TRF | Ingreso |
TPV | 107.05.02 TPV / Terminal bancaria por cobrar | Activo circulante | Sí | POL-ING-TPV | Ingreso |
INT | 107.05.03 Intermediarios de pago por cobrar | Activo circulante | Sí | POL-ING-INT | Ingreso |
IDF | 102.01.06 Cobros por identificar | Activo circulante | Sí | POL-ING-IDF | Ingreso |
ANT | 206.01.01 Anticipos de clientes | Pasivo circulante | Sí | POL-DIA-ANT | Diario |
COM | 201.01.01 Proveedores nacionales | Por pagar | Sí | POL-DIA-COM | Diario |
INC | 108.01.01 Estimación de cuentas incobrables | Activo circulante | Sí | POL-DIA-INC | Diario |
ESP | — | — | — | POL-DIA-ESP | No genera |
NCR | — | — | — | POL-NCR | No genera |
TPVusa activo circulante y no por cobrar: llevarlo aasset_receivableensuciaría la antigüedad de saldos y el mayor de clientes con un tercero que no es el cliente. SoloEFEtrae permite cierre automático de fábrica.
Para el implantador: los códigos son sugerencias. El asistente resuelve cada cuenta en este orden: la que la ruta ya tenía guardada, código exacto, familia del código (todos los segmentos menos el último, con el tipo de cuenta dentro del dominio), los métodos de pago entrantes —solo para TRF— y, únicamente si está apagado Crear las cuentas faltantes, una candidata por tipo de cuenta; si nada casa, crea la cuenta sugerida. Cada línea declara su confianza: Ya configurada y Código exacto se aplican solas; Familia de código y Métodos de pago se aplican, pero salen en amarillo para revisión; Solo por tipo nace como Pendiente de decisión y bloquea la aplicación hasta que el contador elija. Revisa las amarillas antes de aceptar.
B. Catálogo de mensajes y qué hacer
| Mensaje | Qué pasó | Qué hacer |
|---|---|---|
| Solo el grupo "Pólizas de cobro / Responsable" puede aplicar, aprobar, revertir, liberar o corregir pólizas. | Segregación de funciones dentro del motor, no solo en el botón | El usuario puede preparar y dejar en borrador o enviar a aprobación. Aplicar lo hace el responsable |
| La póliza … no se puede revertir: ya tiene N póliza(s) derivada(s) aplicada(s) sobre ella (…) | Encima de esa póliza ya hay un traspaso o una liberación | Revierte primero la derivada que el mensaje nombra. El orden inverso descuadra dos cuentas |
| La factura … ya no tiene saldo abierto: alguien la cobró después de preparar la póliza … | Se cobró por otro camino entre preparar y aplicar | Quita esa línea del lote y vuelve a aplicar |
| La línea de la factura … ya está incluida en otra póliza aplicada. Reaplicar duplicaría el cobro. | Doble captura del mismo cobro | Quítala. Busca la póliza que ya la contiene |
| No se pudo publicar la póliza … con fecha … | Fecha de bloqueo contable posterior a la de la póliza, o diario sin secuencia | Ajusta la fecha de la póliza o pide que abran el periodo; si el diario no tiene secuencia, arréglalo antes de reintentar |
| La cuenta … de la factura … no permite conciliación. | La cuenta de CxC de la factura no es conciliable | Activa Permitir conciliación en la cuenta, o la póliza dejará la factura abierta |
| La ruta … no genera póliza. + motivo | Cayó en ESP o NCR | ESP: asiento manual con el soporte. NCR: nota de crédito, no asiento |
| La factura … tiene la forma de pago SAT "…", que no tiene ruta configurada. | Falta una fila en el mapa de esa compañía | Agrega la fila, o cambia la política a cuarentena y depúrala después |
| Estás liberando TODAS las facturas que quedan en la póliza … | La liberación vaciaría la póliza | Usa Revertir: deja el asiento de reversa casado contra el original |
| La póliza … tiene una sola factura viva: sacarla es deshacer la póliza entera. | Mismo caso, visto desde el asistente | Igual: Revertir |
| Estas líneas no tienen sellada su contrapartida en el asiento (…): la póliza se concilió por fuera del módulo. | Alguien concilió a mano y se perdió el sello línea a línea | Libera a mano o usa la reconstrucción |
| El motivo de la liberación es obligatorio. | Falta el texto del motivo | Escríbelo: es lo que va a leer el contador cuando pregunte por qué esa factura salió de la póliza |
| La cuenta … es la que Odoo mueve al conciliar el estado de cuenta del diario bancario. | Se eligió la cuenta de Bancos como destino del depósito | Elige una cuenta puente de tránsito de liquidez. La transitoria del extracto sí sirve |
| La compañía … tiene prohibida la reconstrucción de pólizas. Usa el traspaso. | permitir_reconstruccion apagado | Corrige por traspaso: no desconcilia ni vuelve a disparar el IVA cobrado |
| La suma del detalle (…) no coincide con el importe total de la póliza (…). | El lote se editó y los totales quedaron viejos | Recalcula el lote antes de aplicar |
| El asiento de la póliza … generó N contrapartidas para M líneas de detalle. No se sella nada. | El asiento no salió con la forma esperada | No lo fuerces: revisa el diario y la moneda, y vuelve a generar |
| El asiento … está publicado: no se borra, se revierte. | Se intentó borrar un asiento vivo | Revierte la póliza. Un asiento publicado es expediente |
| La póliza … ya tocó la contabilidad (estado "…"): no se borra. | Aplicada o revertida | Si está aplicada, reviértela. Si ya está revertida, archívala con el botón de archivar |
C. Gotchas verificados en producción
- El folio vive en
ref, no enname. El número del asiento lo impone la secuencia del diario. El folio de la póliza encabeza siempre la referencia, y la referencia externa del soporte va detrás, nunca en su lugar. Ese es el campo por el que se rastrea en el mayor. - El folio no se parsea nunca. Fecha y ruta son campos, no substrings. En el caso real, derivar la fecha del folio con un reemplazo del prefijo se rompió en cuanto el folio incorporó la ruta.
- El detalle es un snapshot congelado. Forma de pago, clave SAT, estado del CFDI y ruta se guardan el día que se aplicó.
ruta_forzadase mide contraruta_original_idcuando existe: si no, una corrección posterior haría que la póliza impresa en agosto contara algo distinto de la impresa en julio. button_draftdesconcilia TODAS las líneas del asiento. Por eso el módulo nunca lo usa para deshacer:_desconciliar()rompe solo las contrapartidas de esta póliza, y la reversa se hace con_reverse_moves(cancel=True), que publica el asiento de reversa y lo casa contra el original. Usarbutton_draftarrastraría facturas ajenas.- CFDI cancelado también es
global_cancel. El filtro va por exclusión (not in ('cancel', 'global_cancel')). Filtrar por igualdad asentperdería las facturas globales, que son ingreso legítimo. - Una cuenta creada por RPC normaliza su código a la máscara del plan. Lo que se crea como
101.002.000puede quedar guardado como101.02.00. Las cuentas se resuelven por identificador, jamás por código; el código sugerido de la semilla es solo una pista para el asistente. - Borrar un asiento en borrador exige blanquear su
nameantes. El candado de la cadena de secuencia rechaza borrar un asiento conposted_beforeque no sea el último. El orden obligatorio esbutton_draft→ escribirname = '/'→ borrar. Ynamesolo se puede escribir en borrador. - Caja y Bancos comparten tipo de cuenta en el plan mexicano. Lo único que distingue al banco de verdad es que un diario de tipo
banklo declara suyo: esa es la definición única del módulo (cuentas_reservadas_banco). La transitoria del extracto no está vetada, porque es precisamente el destino legítimo de una póliza de depósito.
D. Qué NO hace este producto, a propósito
- No timbra ni cancela CFDI. Eso es la localización.
- No concilia el banco por extracto. Lo habilita, dejando saldos que sí se pueden amarrar.
- No declara impuestos ni decide la deducibilidad de un incobrable.
- No decide por el contador. Las claves 12, 13, 14, 23, 24 y 27 se separan, se marcan y se escalan; no se contabilizan solas. Las claves 15 y 25 mandan explícitamente a emitir nota de crédito.
- No opina sobre qué rol cumple cada cuenta del catálogo del cliente, sobre las PUE abiertas de ejercicios anteriores, ni sobre si hay que reenviar la balanza.
- No fija plazos fiscales. Toda regla que dependa de la Resolución Miscelánea —plazo del PUE, plazo del REP, plazo de cancelación— está marcada en la documentación, porque la RMF cambia cada año.
Documentos hermanos
| Documento | Para qué |
|---|---|
90_docs/MANUAL_USUARIO.md | Las siete tareas del día a día, paso a paso |
90_docs/MANUAL_IMPLANTACION.md | El runbook completo de implantación, con scripts y rollback |
90_docs/ESTRATEGIA_FISCAL.md | La fundamentación fiscal larga de cada criterio |
90_docs/GUIA_VIA_B_SAAS_STUDIO.md | Qué hacer cuando el cliente no tiene Odoo.sh |
90_docs/entregables_contador/ | Lo que se le entrega al contador externo cada mes |
90_docs/AUDITORIA_MODULO_2026-08-11.md | La auditoría que originó la versión 19.0.1.1.0 |
00_estrategia/SPEC_MODULO.md | El contrato técnico: modelos, campos, métodos, vistas |
00_estrategia/ESTRATEGIA_MAESTRA.md | El producto como oferta comercial y su pricing |
Cómo se mantiene este manual
Cada implantación que descubra un caso que aquí no esté descrito vuelve a este documento y a ESTRATEGIA_FISCAL.md. Un manual que no crece con el producto se convierte en folclore: alguien lo cita, ya no coincide con el código, y a partir de ahí nadie vuelve a confiar en él.
Tres reglas para editarlo:
- Nada se afirma sobre el módulo sin haberlo verificado en el código. Los nombres de campos,
métodos y estados de este manual son los reales; si el código cambia, cambia el manual en el mismo movimiento.
- Lo que no se pudo probar se declara. "No verificado en instancia viva" es una frase legítima;
inventar una certeza no lo es.
- Los casos nuevos entran con su número. Cuánto costó, cuántas facturas, qué cuenta. Este
producto existe porque alguien midió que 45 de 480 facturas estaban mal ruteadas; las cifras son lo que convierte una opinión en un criterio.
Grupo Veniu · VSolutions — Integrador de mundos. veniu_polizas_cobro 19.0.1.1.0 · OPL-1 · Este manual acompaña al módulo, no lo sustituye.
Manual de EstrategiaAjuste masivo de pólizas · Veniu
MANUAL DE ESTRATEGIA · Ajuste masivo y correctivo de pólizas
Producto: Veniu Pólizas de Cobro (veniu_polizas_cobro 19.0.1.1.0) · Odoo 19 Enterprise · México Grupo Veniu · VSolutions · 11 de agosto de 2026
De qué trata este manual
De cómo se agrupa, se reacomoda y se corrige en bloque un universo de cientos de facturas sin perder la trazabilidad factura por factura y sin mover de periodo el IVA ya declarado.
No es el manual de usuario —ése explica cómo se cierra la caja del día, y vive en 90_docs/MANUAL_USUARIO.md—. Tampoco es el runbook de implantación (90_docs/MANUAL_IMPLANTACION.md) ni el contrato técnico del módulo (00_estrategia/SPEC_MODULO.md). Este documento es la doctrina: qué se decide, en qué orden, con qué criterio, y qué no se hace nunca.
Para quién
| Lector | Capítulos |
|---|---|
| El contador que opera esto todos los días | 1 a 4, 7 a 9, 12 y los anexos |
| Quien lo implanta en un cliente nuevo | 5, 6, 10, 11 y los anexos |
Los dos deberían leer el capítulo 8: es el árbol de decisión de las correcciones, y es donde se gana o se pierde el dinero.
La idea en cuatro frases
- La política PUE/PPD no determina la cuenta contable. La determina la forma de pago SAT.
- El disparador del IVA de base de flujo es la conciliación, no la póliza. Por eso corregir la
cuenta es barato y desconciliar es caro.
- Toda corrección conserva la fecha original y deja rastro: nada se borra, todo se revierte o
se libera.
- El ciclo se cierra o miente: una caja que solo recibe cargos declara efectivo eterno.
Índice
| # | Capítulo |
|---|---|
| 1 | Por qué existe este módulo |
| 2 | La tesis contable en una página |
| 3 | Ruta 0: lo que nunca entra a una póliza |
| 4 | IVA base flujo: la regla que gobierna toda la estrategia |
| 5 | Anatomía del módulo |
| 6 | Implantación: del catálogo vacío al primer corte |
| 7 | El ciclo diario: detectar, agrupar, previsualizar, aplicar |
| 8 | Los cuatro asientos correctivos y el árbol de decisión |
| 9 | Cerrar el ciclo: depósito a banco y liquidación de terminal |
| 10 | Controles, segregación de funciones y expediente |
| 11 | Automatizar o no: el cron de cierre |
| 12 | Cierre mensual y evidencia |
| 13 | Anexos: tabla SAT, mensajes, gotchas y límites |
Las reglas marcadas con 📅 dependen de la Resolución Miscelánea Fiscal, que cambia cada año. Valídalas contra el texto vigente antes de citarlas ante un cliente o un auditor.
01Por qué existe este módulo
El cliente nunca describe el problema en términos contables. Lo dice así:
- "Mis facturas de contado siguen apareciendo como no pagadas."
- "Clientes crece cada mes aunque cobramos igual que siempre."
- "La cuenta de caja nunca baja."
- "El banco marca cero."
Los cuatro síntomas son la misma enfermedad en dos etapas.
Etapa uno: el cobro nunca se registra. La factura se emite, se timbra como PUE —es decir, se le declara al SAT que ya se cobró en una sola exhibición— y el dinero entra al cajón, a la terminal o a la cuenta bancaria. En Odoo no ocurre absolutamente nada. La factura queda con payment_state en not_paid, la cuenta por cobrar queda cargada y el balance afirma un activo que ya no existe. Con IVA base flujo, además, el asiento de IVA trasladado cobrado no se genera nunca, porque en Odoo lo dispara la conciliación, no el timbrado.
El caso que originó el producto llegó exactamente así: 502 facturas timbradas como PUE, $1.42 M de saldo abierto, 457 de ellas con forma de pago "Efectivo", 88 facturadas a un genérico tipo "CLIENTE MOSTRADOR", cero extractos bancarios cargados y cero de tres fechas de bloqueo contable configuradas. El estado de resultados estaba bien. El balance mentía.
Etapa dos: el remedio apresurado. Alguien resuelve el atraso agrupando todo y aplicándolo contra una sola cuenta de caja. Se aplicaron 24 pólizas de ingreso, 480 facturas conciliadas una por una, $1,359,077.56 movidos de Clientes, cero errores de conciliación. Clientes bajó exactamente lo que debía bajar. Visto desde arriba, quedó impecable.
Al revisar el ruteo apareció el segundo error: 45 de esas 480 facturas, $165,761.85, el 12.2 % del corte, estaban en la cuenta equivocada. Eran transferencias que el banco todavía no confirmaba y cobros con terminal que una adquirente tenía en su poder.
El segundo error es más caro que el primero, y por eso este manual existe.
Es más caro por tres razones concretas:
- Se ve resuelto. Nadie va a auditar un saldo de Clientes que ya bajó. El primer error grita; el
segundo se esconde detrás de un cuadre perfecto.
- Rompe el control que lo detectaría. El arqueo físico de caja deja de cuadrar todos los días por el
monto de las tarjetas. Cuando un control falla siempre, se abandona.
- Deja dinero en la mesa. Sin cuenta de terminal por cobrar, la comisión de la adquirente nunca se
contabiliza —no hay ninguna cuenta que quede descuadrada y obligue a explicarla— y el IVA acreditable de esa comisión se regala. Tampoco se puede detectar un depósito faltante: si la adquirente liquida de menos, nadie se entera.
Y es más caro porque corregirlo, hecho a mano, obliga a tocar lo que ya estaba bien: de esas 480 facturas, 435 estaban correctas y compartían póliza con las 45 malas.
De qué trata este manual. No de capturar un cobro: eso es el manual de usuario. Trata de la estrategia de corrección masiva: cómo se agrupa, se reacomoda y se corrige en bloque un universo de cientos de facturas sin perder la trazabilidad factura por factura ni mover de periodo el IVA ya declarado. Todo el diseño del módulo está subordinado a esa pregunta, incluidos los tres métodos correctivos —traspaso, liberación y reconstrucción— y el orden en que se eligen.
Para el implantador: este patrón no es de un cliente. Es el estado por defecto de cualquier empresa mexicana con mostrador que factura PUE y no tiene un proceso de corte diario. El diagnóstico de 30 minutos te dirá si el cliente está en la etapa uno, en la dos, o en las dos a la vez —que es lo más frecuente—.
Las tres preguntas que el módulo contesta todos los días:
| Pregunta | Dónde se contesta |
|---|---|
| ¿Qué se cobró? | la póliza de cobro: un cargo único a la cuenta de la ruta y una línea de abono por factura, a la cuenta de CxC que esa factura usó y con su partner_id |
| ¿Dónde quedó el dinero? | el mapa, que elige la ruta según la forma de pago SAT, y la ruta, que aporta la cuenta destino |
| ¿Cómo se corrige lo que quedó mal? | traspaso, liberación o reconstrucción, en ese orden de preferencia |
Si una operación no puede contestar las tres, no está terminada.
02La tesis contable en una página
La política PUE/PPD no determina la cuenta. La determina la forma de pago SAT (l10n_mx_edi_payment_method_id).
PUE significa "en una sola exhibición". No significa "en efectivo". Una venta de $50,000 liquidada íntegramente por SPEI es PUE con forma de pago 03. Una venta de $500 con tarjeta de débito es PUE con forma 28. Confundir la política con la forma es el error de origen, y su precio medido fue 12.2 % del corte.
La forma de pago tampoco se lee como catálogo, sino como respuesta a una sola pregunta:
¿Quién tiene ese dinero ahora mismo?
Hay cuatro respuestas posibles, y cada una es una naturaleza contable distinta:
| Respuesta | Naturaleza | Ruta |
|---|---|---|
| La empresa, en billete, en el cajón | tesorería propia | EFE |
| El banco de la empresa, sin confirmar en extracto | activo transitorio | TRF |
| Un tercero procesador (adquirente, emisor, plataforma) | cuenta por cobrar a un tercero | TPV, INT |
| No lo sé | cuarentena | IDF |
Las rutas que trae la semilla
Son diez, en data/veniu_poliza_ruta_plantilla_data.xml (modelo veniu.poliza.ruta.plantilla). Ocho declaran cuenta sugerida y tipo esperado; dos existen precisamente para no generar asiento (genera_poliza = False) y mandar al usuario al instrumento correcto.
| Código | Nombre | Cuenta sugerida | cuenta_tipo | poliza_tipo | Automatización | permite_cron |
|---|---|---|---|---|---|---|
EFE | Efectivo — Caja de venta | 101.02.01 Caja de venta | asset_cash | ingreso | masivo | sí |
TRF | Transferencia y cheque — Recibos pendientes | 102.01.04 Recibos pendientes de conciliar | asset_current | ingreso | masivo | no |
TPV | Terminal bancaria — por cobrar a la adquirente | 107.05.02 TPV / Terminal bancaria por cobrar | asset_current | ingreso | masivo | no |
INT | Intermediarios de pago y marketplaces | 107.05.03 Intermediarios de pago por cobrar | asset_current | ingreso | masivo | no |
IDF | Cobros por identificar (cuarentena) | 102.01.06 Cobros por identificar | asset_current | ingreso | manual | no |
ANT | Aplicación de anticipos de clientes | 206.01.01 Anticipos de clientes | liability_current | diario | masivo | no |
COM | Compensación contra cuentas por pagar | 201.01.01 Proveedores nacionales | liability_payable | diario | masivo | no |
INC | Prescripción o caducidad — incobrables | 108.01.01 Estimación de cuentas incobrables | asset_current | diario | manual | no |
ESP | Caso especial — asiento manual del contador | — (genera_poliza = False) | — | diario | no aplica | no |
NCR | Se resuelve con nota de crédito | — (genera_poliza = False) | — | diario | no aplica | no |
Los códigos de cuenta son sugerencias portables: el asistente de configuración los resuelve contra el catálogo real de la compañía y, ya creada la ruta, la cuenta se resuelve siempre por id, nunca por código.
Una sola ruta entra al cierre automático: EFE. El efectivo es lo único cuyo destino no admite duda.
El mapa (data/veniu_poliza_mapa_plantilla_data.xml) tiene 23 filas: las 22 claves del catálogo c_FormaPago del SAT —01, 02, 03, 04, 05, 06, 08, 12, 13, 14, 15, 17, 23, 24, 25, 26, 27, 28, 29, 30, 31, 99— más una fila comodín (aplica_sin_forma) para las facturas que traen el campo vacío. La llave es el código SAT como texto (forma_pago_code), no el XML-ID de l10n_mx_edi, para que la semilla sea portable entre versiones de la localización.
Veintidós claves convergen a diez rutas y solo ocho cuentas, y es deliberado. Crear una cuenta por clave SAT produce un catálogo con veintidós cuentas, casi todas en cero, imposible de auditar e imposible de mapear al código agrupador del Anexo 24. El mapa converge porque la pregunta que lo ordena no tiene veintidós respuestas: tiene cuatro.
Por qué TPV no es efectivo ni banco
Cuando el cliente pasa la tarjeta, el adquirente le cobra al emisor y retiene el dinero. Deposita a T+1 o T+2 —a veces mucho más en emisores no bancarios— y lo hace neto de su comisión más el IVA de esa comisión. Entre la venta y el depósito, el negocio tiene una cuenta por cobrar contra un tercero que ni siquiera es su cliente. No hay billete que arquear y no hay línea en el estado de cuenta.
Meterlo a caja tiene tres costos: el arqueo físico nunca cuadra, el balance declara efectivo refutable con un conteo sorpresa, y —el más caro— la comisión nunca se contabiliza, porque no queda ninguna cuenta descuadrada que obligue a explicarla. Gasto real no registrado, IVA acreditable regalado.
Se usa asset_current y no asset_receivable a propósito: la deuda es del adquirente, no del cliente. Con asset_receivable el saldo entra al mayor de clientes y a la antigüedad de saldos, y la cobranza persigue a un cliente que ya pagó. Crédito y débito comparten cuenta por la misma razón inversa: la adquirente deposita un solo importe neto mezclado; partir la cuenta obliga a prorratear cada liquidación a mano. Los intermediarios y marketplaces (INT) sí llevan cuenta propia y separada de la terminal, porque el orden de magnitud de su comisión y el desglose de su liquidación son distintos.
La regla dura
Ninguna ruta apunta jamás a Bancos.
El saldo de una cuenta de banco tiene un solo juez: el estado de cuenta. Si la póliza postea directo ahí, el día que se carga el extracto la conciliación abona otra vez el mismo importe y el ingreso queda duplicado. Entre el cobro y el banco siempre va una cuenta puente que la conciliación limpia sola.
Esto no vive en la costumbre: vive en el código. El @api.constrains _check_cuenta_destino_admisible de models/veniu_poliza_ruta.py valida tres cosas en orden de gravedad: que la cuenta no sea la cuenta por defecto de un diario de tipo bank de esa compañía; que ninguna ruta apunte a una cuenta asset_cash salvo la que declara esperar precisamente ese tipo —la de caja—; y que el tipo real de la cuenta coincida con el cuenta_tipo que la ruta declara esperar. El mensaje del primer caso nombra el diario culpable, no solo la cuenta.
La definición de "cuenta de banco" es única en todo el módulo: veniu.poliza.config.cuentas_reservadas_banco(). Mira el diario, no el account_type, porque en el plan mexicano Bancos (102) y Caja (101) comparten asset_cash. Y no incluye la transitoria del extracto (suspense_account_id): esa cuenta no es el banco, es justamente la puente que la conciliación vacía, y es un destino legítimo —a veces el único disponible— para la póliza de depósito.
Para el implantador: si el catálogo del cliente no tiene una cuenta que satisfaga un rol, la ruta queda sin cuenta destino y get_cuenta() falla con un mensaje accionable que remite al asistente de configuración. Ninguna ruta postea a ciegas. Esa es la diferencia entre una configuración incompleta y un asiento equivocado.
03Ruta 0: lo que NUNCA entra a una póliza
Antes de preguntar a qué cuenta va un cobro, hay que preguntar si ese documento es cobrable. Esa primera compuerta es la Ruta 0 y vive en account.move._veniu_motivo_exclusion(config). Devuelve una clave de texto o False. Si devuelve clave, la factura no entra: ni al asistente, ni al motor, ni al cron.
La Ruta 0 filtra por naturaleza del documento —tipo, estado del asiento, estado del CFDI, política de pago SAT y saldo abierto—, nunca por cuenta destino. Decidir a dónde va el dinero es trabajo del mapa de rutas, que corre después. Si se mezclaran las dos preguntas, una configuración incompleta de cuentas podría dejar pasar un CFDI cancelado y una regla fiscal podría acabar dependiendo de un catálogo contable.
3.1 Los siete motivos
El método evalúa en orden y se detiene en el primero que aplica: una factura tiene un solo motivo, el más grave. Los textos legibles viven en MOTIVOS_EXCLUSION (veniu_poliza_lote.py), que es el que se imprime cuando el motor aborta; el asistente de agrupar tiene su propia tabla con la misma clave y una redacción más larga para la pestaña de excluidas.
| Clave técnica | Qué significa | Qué se hace con esa factura |
|---|---|---|
no_es_factura_cliente | El move_type no es out_invoice ni out_refund | Nada. Es una factura de proveedor, un pago o un asiento de diario que se coló en la selección |
asiento_cancelado | El asiento no está posted (borrador o cancelado) | Publicarlo si corresponde, o dejarlo fuera. Un borrador no tiene CxC que abonar |
cfdi_cancelado | l10n_mx_edi_cfdi_state vale cancel o global_cancel y politica_cfdi_cancelado = 'excluir' | No es ingreso. Se resuelve en el mundo del CFDI —cancelar el asiento, nota de crédito— antes de tocar tesorería |
sin_timbrar | No hay estado de CFDI y politica_sin_timbrar = 'excluir' | Por defecto la política es advertir, así que no excluye: la factura entra en ámbar. Cobrable sí; comprobante ante el SAT, no |
es_ppd | veniu_politica_pago_sat = 'ppd' bajo la política de detección vigente. Ese campo es propio y almacenado, y se calcula del plazo de pago: hay parcialidades (invoice_payment_term_id con más de una línea) o el vencimiento es posterior a la fecha de factura | Se cobra con account.payment y su REP. Nunca con póliza masiva |
sin_cxc_abierta | Ninguna línea de CxC con residual distinto de cero (respetando el filtro opcional config.cuenta_cxc_id) | Ya está cobrada o su saldo se fue por otro camino. Verificar contra qué se conció |
ya_en_poliza | Todas sus líneas abiertas ya están en una póliza de cobro aplicada y no liberadas | Abrir la póliza que la contiene. Si de verdad hay que sacarla, se libera |
Para el implantador: solo tres motivos dependen de configuración (cfdi_cancelado, sin_timbrar, es_ppd). Los otros cuatro son estructurales y no se pueden apagar. En una implantación nueva, la única política que se discute con el contador es politica_sin_timbrar; las otras dos se dejan en su valor de fábrica (excluir para el CFDI cancelado, pue para la detección).
3.2 CFDI cancelado no es asiento cancelado
Son dos mundos que no se hablan. state = 'cancel' es una decisión de Odoo. l10n_mx_edi_cfdi_state = 'cancel' es una decisión que ya está registrada en el SAT. Existe —y es el caso caro— la factura con CFDI cancelado cuyo asiento sigue posted con saldo abierto en Clientes. Trae forma de pago Efectivo, pasa cualquier filtro ingenuo y carga a la caja dinero que no existe.
El filtro va por exclusión, no por igualdad:
Regla dura. El estado del CFDI se compara contra la constanteCFDI_CANCELADO = ('cancel', 'global_cancel'). Nunca contra la cadena'cancel'suelta, y nunca por igualdad a'sent'.
La razón es el mostrador. Las facturas globales de público en general viven en el par global_sent / global_cancel. Filtrar por igualdad a 'sent' habría dejado fuera todas las globales legítimas; comparar la cancelación solo contra 'cancel' habría dejado pasar en verde precisamente el volumen más alto de cancelaciones. La constante vive en veniu_poliza_config.py y de ahí la importan tanto el motor como el semáforo del asistente (_compute_semaforo) por esa razón: dos definiciones de "cancelado" en un mismo módulo es una de las dos mintiendo.
3.3 PPD: por qué no entra
Una factura PPD le afirmó al SAT que está pendiente de cobro y declara forma de pago 99. Su cobro se documenta con un pago que emite el complemento de recepción de pagos (REP), y el IVA se considera cobrado en la fecha de ese REP. Una póliza masiva la dejaría saldada sin REP: el papel de trabajo cuadra contra Odoo y no cuadra contra el SAT, y el IVA trasladado del mes del cobro se omite. Es la omisión más cara que puede cometer este módulo.
La severidad la manda config.politica_deteccion, no el método:
| Valor | Comportamiento de la Ruta 0 |
|---|---|
pue (por defecto) | Ninguna PPD entra: motivo es_ppd |
pue_vencidas | Solo pasa la PPD ya vencida (invoice_date_due <= hoy); la vigente —y también la que no tiene fecha de vencimiento— devuelve es_ppd |
todas | No hay motivo es_ppd: el contador asume el riesgo a propósito |
pue_vencidas usa la misma regla que dominio_cobrables() usa para pintar el tablero. Si divergieran, el asistente rechazaría lo que el tablero acaba de ofrecer.
El semáforo la marca siempre, en los tres casos. El campo es_ppd del asistente se calcula por el hecho del documento (veniu_politica_pago_sat == 'ppd'), no por el motivo de exclusión. Si se derivara del motivo, con todas la PPD entraría marcada y en blanco. La línea nace desmarcada (incluir = not es_ppd), sale dentro de la lista principal —no escondida en la pestaña de excluidas— y action_marcar_todo la salta a propósito. Lo que cambia con la política es solo si el semáforo dice bloqueo o aviso: con pue y con pue_vencidas mientras no venza, la línea sale en rojo; con todas sale en ámbar y el aviso dice literalmente que se cobrará sin REP.
Para el contador: ver una PPD marcada en la lista no es un error del sistema. Está ahí para que sepas dónde quedó y cuál es su salida, no para que la fuerces.
3.4 Sin CxC abierta y "ya en póliza"
sin_cxc_abierta significa que la factura no tiene ninguna línea de cuentas por cobrar con residual distinto de cero. Es el motivo más frecuente y casi siempre benigno: ya se cobró. Cuando aparece en una factura que el operador jura que está abierta, la causa suele ser el filtro opcional config.cuenta_cxc_id, que restringe qué líneas se consideran.
ya_en_poliza sale de _lineas_ya_aplicadas(), que busca líneas del detalle con lote_id.state = 'aplicado', lote_id.tipo_operacion = 'cobro' y liberada = False. Dos consecuencias:
- Una póliza de traspaso o de depósito nunca marca una factura como "ya cobrada". Solo la de cobro.
- Una factura liberada vuelve a ser agrupable. La póliza de liberación la sacó del asiento y su línea quedó marcada como liberada; sin ese filtro quedaría excluida para siempre con el motivo "ya está en una póliza" y nadie podría volver a cobrarla.
El motivo solo se emite cuando len(ya) == len(abiertas). Una factura con dos parcialidades, una ya en póliza y otra abierta, sí entra: el motor recolecta únicamente la línea libre.
3.5 La regla de la interfaz
Lo que la Ruta 0 excluye se bloquea, no se advierte.
Las facturas con motivo van a la pestaña Excluidas por regla, que es de solo lectura y no tiene casilla: no hay clic que las incluya. La única excepción visual es la PPD, que se muestra en la lista principal desmarcada porque esconderla hacía que el contador la buscara entre 500 renglones sin encontrarla.
El bloqueo no es cosmético. crear_desde_facturas() vuelve a correr _excluir_facturas() en el paso 2, antes de mirar forma de pago alguna: lo excluido no llega a la recolección de líneas, y si ninguna factura de la selección sobrevive, aborta con UserError nombrando factura por factura —hasta quince— con su motivo. Una llamada por RPC, un botón nuevo o el cron topan con la misma compuerta. Y el tablero Facturas por agrupar lleva veniu_politica_pago_sat = 'pue' en el dominio de la acción, no en un search_default: como facet quitable bastaba con cerrar la etiqueta para que el tablero empezara a ofrecer PPD.
04IVA base flujo: la regla que gobierna toda la estrategia
En México el IVA se causa sobre lo efectivamente cobrado (art. 1-B LIVA), no sobre lo devengado. Odoo implementa esa regla con impuestos cash basis: la factura registra el IVA en una cuenta transitoria de "IVA trasladado no cobrado", y cuando el cobro se concilia contra la factura, un diario dedicado genera el asiento que lo mueve a "IVA trasladado cobrado" —el que se declara.
El disparador fiscal, entonces, no es la póliza. Es la conciliación. La póliza importa porque concilia. Todo lo demás de este capítulo se deduce de ahí.
4.1 Los tres corolarios
1. Cambiar la cuenta de activo no toca el IVA. Rutear una venta a la ruta TPV (Terminal bancaria — por cobrar a la adquirente, cuenta sugerida 107.05.02) en lugar de a la ruta EFE (Efectivo — Caja de venta, 101.02.01) concilia exactamente la misma línea de CxC, por el mismo importe y en la misma fecha. El IVA cobrado se reconoce igual. Corregir el ruteo no altera nada de lo ya declarado. Un traspaso —cargo a la cuenta correcta, abono a la que se usó por error— no toca la CxC, no desconcilia nada y no genera un solo asiento de base flujo.
2. Desconciliar y volver a conciliar sí lo re-dispara. Al romper la conciliación se reversan los asientos de base flujo; al reconciliar se vuelven a generar. Si eso cae en otro periodo, se mueve IVA entre declaraciones: complementaria, actualización y recargos si el periodo ya se presentó. La reconstrucción hace exactamente esto, y lo hace con todas las facturas de la póliza, no solo con las que estaban mal.
3. De ahí sale la jerarquía de los tres métodos correctivos:
| Método | Toca conciliaciones | Re-dispara IVA | Cuándo |
|---|---|---|---|
Traspaso (tipo_operacion correccion, folio POL-COR) | No | No | Siempre que el problema sea a qué cuenta fue el dinero |
Liberación (liberacion, folio POL-LIB) | Solo la de la factura liberada | Solo esa | Cuando hay que sacar una factura concreta del asiento |
| Reconstrucción | Todas las de la póliza | Todas | Último recurso |
Regla dura. Toda corrección conserva la fecha original de la póliza. Nunca la de hoy. Una fecha nueva mueve el IVA de periodo y descuadra el corte de caja del día original.
4.2 Qué significa en dinero
En el caso que originó el producto, un lote de 480 facturas tenía 45 mal ruteadas por $165,761.85 —el 12.2 %. Antes de la liberación, sacar o corregir algo dentro de una póliza obligaba a reconstruirla entera: de las 348 facturas tocadas, 301 estaban correctas y se iban a desconciliar y reconciliar sin necesidad, solo por compartir póliza con una mal ruteada. El 63 % del lote pasando por una operación destructiva para arreglar el 13 %.
El costo de esas 301 no es teórico: cada una reversa y regenera su asiento de base flujo. Si el mes ya se declaró, son 301 movimientos de IVA que hay que explicar en una complementaria para arreglar 45 errores que un traspaso corrige sin tocar una sola conciliación.
Por eso el módulo blinda esta jerarquía en el modelo, no en la costumbre: metodo_correccion_default = 'traspaso' y permitir_reconstruccion se puede apagar por compañía. Y una póliza que ya tiene traspasos o liberaciones aplicados encima no se revierte ni se reconstruye: _check_sin_derivadas_vivas() lo impide nombrando el folio de la derivada. El descuadre que evita es silencioso —la cuenta de origen queda en negativo y la de destino cargada dos veces, con un asiento que cuadra consigo mismo—. El orden correcto es al revés: primero la derivada, después la original.
Para el implantador: en un cliente con IVA base flujo delicado o con periodos ya declarados, apaga permitir_reconstruccion en la configuración y déjalo por escrito. Es un interruptor, no una recomendación.
4.3 Las fechas de bloqueo contable
El asiento de la póliza se publica al final, cuando el contador ya revisó cientos de renglones. Si su fecha cae en un periodo cerrado, Odoo rechaza el asiento y todo el trabajo se pierde en un rollback sin explicación útil.
El asistente de agrupar lo dice antes. _candados_periodo() lee los cierres vigentes de res.company en orden de severidad —hard_lock_date (bloqueo irreversible), fiscalyear_lock_date (cierre de ejercicio), tax_lock_date (cierre de impuestos), sale_lock_date (cierre de ventas), period_lock_date (cierre para usuarios no asesores)— y comprueba cada nombre contra company._fields antes de leerlo, porque los campos cambiaron entre versiones y un acceso a un campo inexistente convertiría un aviso preventivo en una pantalla que no abre.
Dos detalles que cambian el resultado:
- Los candados de Odoo son inclusivos: una fecha igual al tope ya está cerrada (
fecha <= tope). Se recorren del más severo al más laxo para nombrar el peor. - Se compara la fecha efectiva del asiento (
_fecha_efectiva), no la fecha de corte. La de corte es solo la llave de agrupación —el lunes de la semana, el día 1 del mes— y usarla hacía saltar falsas alarmas de periodo cerrado. Ante esa falsa alarma, lo único que la pantalla sugiere es forzar la fecha, que es justo lo que mueve el IVA de periodo.
El preflight avisa; no bloquea. La fecha puede ser legítima y quien tiene la llave del cierre es contabilidad, no este asistente. El aviso aparece en dos lugares: como renglón en rojo dentro de la previsualización de cada póliza, y como advertencia agregada con el número de pólizas atrapadas por cada candado. Forzar la fecha genera su propio aviso independiente.
Para el implantador: si el cliente no tiene ninguna fecha de bloqueo puesta —en Galgo, fiscalyear_lock_date, tax_lock_date y hard_lock_date están las tres vacías—, el preflight nunca dirá nada y el módulo escribirá alegremente en cualquier ejercicio. Proponer el bloqueo al cerrar el mes es parte de la implantación, no un extra.
Reglas de la Resolución Miscelánea Fiscal — validar contra el texto vigente. La RMF cambia cada año. Tres puntos de este capítulo dependen de ella y hay que confirmarlos antes de implantar en cada cliente y en cada ejercicio: (1) la facilidad que admite PUE cuando el pago se recibe a más tardar el último día del mes en que se expidió el CFDI; (2) el plazo para emitir el REP, históricamente el quinto día natural del mes siguiente al del cobro; (3) el plazo de cancelación de CFDI del ejercicio y sus motivos obligatorios. El módulo no codifica ninguna de las tres: las trata como criterio del contador.
05Anatomía del módulo
Cinco modelos sostienen la operación: tres son catálogo (se configuran una vez y se firman) y dos son operación (se generan todos los días). Aparte viven los dos modelos de plantilla (veniu.poliza.ruta.plantilla y veniu.poliza.mapa.plantilla), que son la semilla portable del módulo y no configuración del cliente.
| Modelo | Qué es | Quién lo toca |
|---|---|---|
veniu.poliza.config | Las decisiones de la compañía | El implantador con el contador, una vez |
veniu.poliza.ruta | El destino contable del dinero | Igual, y se revisa al cambiar el plan de cuentas |
veniu.poliza.mapa | Forma de pago SAT → ruta | Igual |
veniu.poliza.lote | La póliza | El operador, cada día |
veniu.poliza.lote.linea | El detalle congelado de la póliza | Nadie: lo escribe el motor |
veniu.poliza.config es una fila por compañía (constraint UNIQUE(company_id)). Ahí viven los tres diarios (diario_id, diario_traspaso_id, diario_deposito_id, los dos últimos con caída al primero), las cuentas de apoyo (cuenta_cxc_id, cuenta_deposito_id, cuenta_comision_id, cuenta_iva_comision_id), la granularidad del corte (día, semana, mes, única), la cuarentena (ruta_cuarentena_id, dias_cuarentena, y el campo vivo aviso_cuarentena, que mide saldo y antigüedad de los apuntes sin conciliar), la aprobación por monto (exige_aprobacion + aprobacion_monto) y el cron (cron_habilitado, cron_dias_gracia). Y cinco políticas, que son las que deciden qué se excluye antes de mirar la forma de pago: politica_deteccion, politica_cfdi_cancelado, politica_sin_timbrar, politica_mixta, politica_sin_mapa.
veniu.poliza.ruta responde una sola pregunta: ¿quién tiene ese dinero ahora mismo? Trae cuenta_destino_id (se resuelve siempre por id; cuenta_codigo_sugerido es una pista del asistente, jamás un resolvedor en ejecución), prefijo_folio, poliza_tipo, genera_poliza (apagado en ESP y NCR: son las rutas que existen para NO contabilizar), automatizable y permite_cron. veniu.poliza.mapa es tabla aparte porque 22 formas SAT convergen a diez destinos, más una regla comodín (aplica_sin_forma, única por compañía) para las facturas sin forma capturada.
veniu.poliza.lote es la póliza; veniu.poliza.lote.linea es su detalle, y la unidad es la línea de CxC, no la factura: una factura con parcialidades produce varias líneas. Casi todo el detalle es snapshot —forma_pago_id, cfdi_estado, ruta_correcta_id, ruta_aplicada_id, cuenta_cxc_id— porque la auditoría necesita saber qué decía la factura el día que se aplicó, no lo que dice hoy. ruta_original_id conserva la ruta con la que se aplicó originalmente cuando una corrección posterior reescribe la aplicada: sin ese campo, la póliza reimpresa en agosto contaría una historia distinta a la impresa en julio.
5.1 Ruta correcta, ruta real y estado de ruteo
Esta es la distinción que hay que entender o nada de lo demás sirve.
- Ruta correcta (
veniu_ruta_correcta_id): la que dicta el mapa segúnl10n_mx_edi_payment_method_id. Es la que debió usarse. - Ruta real (
veniu_ruta_real_id): la derivada deveniu_cuenta_real_id, que es la contrapartida dominante —la cuenta de mayor importe conciliado contra las líneas de CxC de la factura—. Es la que se usó. Si un traspaso ya reclasificó el saldo, manda el traspaso: la conciliación sigue apuntando a la cuenta vieja y sería una lectura falsa. - Estado de ruteo (
veniu_ruta_estado): el veredicto de comparar las dos.
| Estado | Cuándo sale | Qué hacer |
|---|---|---|
No aplica (na) | El asiento no es factura de cliente publicada | Nada |
Sin cobrar (pendiente) | Hay ruta correcta y ninguna contrapartida conciliada | Agrupar |
Ruteada correctamente (ok) | Ruta real = ruta correcta | Nada |
Mal ruteada (mal) | Ruta real ≠ ruta correcta | Traspaso |
Liquidada fuera de pólizas (externa) | Se concilió contra una cuenta que no es de ninguna ruta | Revisar a mano: el módulo no la generó |
Forma de pago sin ruta (sin_mapa) | La forma SAT no está mapeada | Completar el mapa o mandar a cuarentena |
La diferencia entre las dos primeras columnas es todo el producto: en el caso que lo originó, 45 de 480 facturas —$165,761.85, el 12.2 %— salían ok para el ojo y mal para el mapa.
5.2 El sello de la factura es computado
veniu_poliza_lote_id se calcula: el último lote aplicado y de tipo cobro que contiene esa factura. De ahí cuelgan, como related almacenados, veniu_poliza_folio, veniu_poliza_fecha y veniu_poliza_tipo.
La etiqueta no es la contabilidad. Un campo editable que dice "esta factura está en la póliza X" acaba mintiendo el día que la póliza se revierte, se libera o se reconstruye, y nadie lo nota.
El riesgo no es teórico, y se ve con claridad en la variante sin módulo (Vía B): ahí el sello se escribe por RPC y queda editable, así que quien lo toque puede dejar la lista de aplicadas y la de pendientes contando historias distintas de la misma factura. El campo computado no puede divergir del mayor porque no tiene vida propia: depende del state y del tipo_operacion del lote que la contiene, y cuando la póliza se revierte la etiqueta se vacía sola.
5.3 El folio
Determinista, sin ir.sequence: prefijo de la ruta (o POL-COR, POL-LIB, POL-DEP según el tipo de operación) más la fecha en AAAA-MM-DD, con sufijo de moneda cuando no es la de la compañía y un consecutivo -2, -3 solo si el candidato ya existe. La unicidad la garantiza UNIQUE(company_id, name), no un contador.
Gotcha verificado en producción. El folio viaja en el camporefdel asiento, no enname: elnamelo impone la secuencia del diario (POLI/2026/07/0001). Buscar asientos connameque empiece conPOL-devuelve cero, siempre.
Y la contraparte: el folio nunca se parsea. La fecha y la ruta de una póliza son campos (fecha, ruta_id, ruta_codigo). Deducirlos del texto rompe el día que alguien cambia folio_incluye_ruta.
5.4 La máquina de estados
borrador → por_aprobar → aplicado → revertido, más cancelado como salida lateral.
| De | A | Cómo |
|---|---|---|
| borrador | por_aprobar | Enviar a aprobación (o el lote nace ya en Por aprobar, si la compañía exige aprobación y el importe alcanza aprobacion_monto) |
| borrador / por_aprobar | aplicado | Aplicar póliza · Aprobar y aplicar |
| borrador / por_aprobar | cancelado | Cancelar (borra el asiento si aún estaba en borrador) |
| cancelado / por_aprobar | borrador | Volver a borrador |
| aplicado | revertido | Revertir (motivo obligatorio) |
La transición irreversible es aplicado. Desde ahí no hay vuelta a borrador ni cancelación: solo reversión, que publica un asiento nuevo. Y revertido es terminal: no existe camino de regreso, porque el expediente son dos asientos vivos. Ninguno de los dos estados se borra —el @api.ondelete lo impide, y una póliza revertida se archiva (active), que oculta sin tocar el asiento ni liberar el folio—. aplicar() y revertir() exigen el grupo Pólizas de cobro / Responsable dentro del motor, no solo en el botón, porque el método se alcanza por RPC; la única excepción es el modo superusuario, que es como corre el cron.
06Implantación: del catálogo vacío al primer corte
Para el implantador. El runbook completo son diecisiete pasos, de P0 a P16. Esto es la estrategia que los ordena: cinco decisiones, en secuencia, y ninguna se salta.
6.1 Primero se mide, y con eso se decide de qué conversación se trata
El diagnóstico es de solo lectura: once consultas por compañía, search_count, search_read y _read_group, cero escrituras. Miden el tamaño (PUE abiertas y su importe), la forma (distribución por forma de pago SAT: arriba de 5 % que no sea la forma 01, agrupar todo a una cuenta produce error material), el ruteo real (la contrapartida conciliada de cada factura ya cobrada), y si el ciclo se cierra (¿la caja tiene abonos? ¿hay extractos bancarios?).
Con tres banderas rojas o más, esto no es "instalar un módulo": es una regularización contable. Se dice así, se cotiza así, y el contador del cliente entra a la mesa desde la primera sesión.
La regla no es retórica. Si no hay un contador identificable por nombre que se haga responsable del mapa, se entrega el diagnóstico y ahí termina el compromiso: un mapa de rutas sin dueño contable es un error material esperando fecha.
6.2 Qué resuelve el asistente, y con qué nivel de confianza
El asistente de configuración (veniu.poliza.setup.wizard) existe porque la semilla del módulo no puede referenciar una cuenta del cliente: el plan normaliza la máscara del código en el create —una cuenta capturada como 101.002.000 puede quedar guardada como 101.02.00—. Así que la semilla es texto y el asistente la resuelve contra el catálogo real, en cuatro pasos, sin escribir nada hasta el último. Es idempotente.
Cada línea declara su confianza, y ese es el semáforo que el contador lee antes de firmar:
| Confianza | Cómo se resolvió | Acción | |
|---|---|---|---|
configurada | La ruta ya tenía cuenta guardada | Usar, sin re-adivinar nunca | 🟢 |
exacto | code idéntico al sugerido | Usar | 🟢 |
prefijo | Otra cuenta de la misma familia de código | Usar, marcada para revisión | 🟡 |
metodo_pago | Solo para TRF: la cuenta de recibos pendientes que ya usan los métodos de pago entrantes | Usar, marcada para revisión | 🟡 |
tipo | Solo coincide el account_type | Pendiente de decisión: el asistente no aplica | 🔴 |
ninguna | No hay candidata | Proponer crear la cuenta sugerida | — |
Dos matices que cambian el resultado. La familia es el código sin su último segmento (101.02.01 → 101.02), no el primer segmento: recortar a 101 hace que gane la primera cuenta que empiece así, que en un plan real suele ser Caja Chica, y amarrar el corte de caja a Caja Chica contamina todos los cortes del ejercicio. Y coincidir solo por tipo no identifica nada —un plan mexicano tiene decenas de asset_current—, así que mientras el asistente pueda crear la cuenta sugerida, prefiere crearla; la coincidencia por tipo solo se ofrece cuando crear está desactivado, en rojo y exigiendo decisión.
6.3 Lo que el asistente no resuelve
Cuatro cosas se deciden con el contador, no con el catálogo:
- La cuenta destino de depósitos. El asistente propone en cascada (cuenta de transferencia de la compañía; una cuenta de activo circulante con "traspaso", "liquidez", "tránsito" o "transferencia" en el nombre; la transitoria del extracto), pero jamás puede ser la cuenta de un diario de banco: postear ahí duplica el ingreso el día de la conciliación. La definición única de "cuenta de banco" vive en el método
veniu.poliza.config.cuentas_reservadas_bancoy es solodefault_account_idde los diariosbank—la transitoria del extracto sí es destino legítimo—. Si no encuentra nada, la deja vacía con un aviso rojo: mejor eso que un depósito amarrado a la cuenta equivocada. - Comisión e IVA acreditable de la comisión. Sin ellas, la liquidación de terminal no se puede armar. Regla dura: el IVA de la comisión no se acredita sin el CFDI de la adquirente.
- Las rutas sin cuenta obvia: anticipos (
ANT), incobrables (INC), intermediarios (INT) cuando el cliente no vende por marketplace. Casi siempre no existen en el catálogo y hay que crearlas o decidir posponerlas. - Si
ESPyNCRse quedan como están —no generan póliza a propósito— o el cliente quiere otro tratamiento.
6.4 El acta
La semilla trae 10 rutas (EFE, TRF, TPV, INT, IDF, ANT, COM, INC, ESP, NCR) y 23 filas de mapa: las 22 formas del catálogo c_FormaPago más la regla comodín para las facturas sin forma capturada. Conviene mirar dos antes de firmar: el cheque (forma 02) cae en TRF junto con la transferencia, y si el cliente quiere cuenta propia de cheques en tránsito hay que abrir una ruta; y las formas especiales (12, 13, 14, 23, 24, 27) caen en ESP, que no contabiliza nada por diseño.
El acta lista forma SAT → ruta → cuenta (por id, código y nombre) → si es conciliable, y se firma antes de aplicar la primera póliza. Es cláusula, no cortesía. Incluye tres compromisos: la cuarentena IDF queda en cero antes del cierre de cada mes, ninguna ruta apunta a Bancos, y la corrección de ruteo se hace por traspaso salvo autorización caso por caso.
6.5 El piloto
Un día. Una ruta. La más chica. Un día ya cerrado —uno que sigue facturando no se cierra—, agrupado, aplicado, impreso y validado contra el respaldo previo antes de tocar el siguiente. Las cinco amarras: la póliza quedó aplicado con el asiento posted; totalmente_conciliado en verdadero y cero líneas sin conciliar; la suma de la póliza igual a la de las facturas al centavo; CxC bajó exactamente ese importe; la cuenta destino subió exactamente ese importe. Si falla una, se revierte el piloto y no se avanza. En la implantación real un fallo en la llamada de conciliación por RPC dejó la póliza publicada pero sin conciliar: lo cacharon las amarras 2, 4 y 5. Sin piloto, ese error se habría multiplicado por veinticuatro pólizas.
6.6 Quién decide qué
| Decisión | Módulo | Implantador | Contador |
|---|---|---|---|
| Qué facturas son cobrables (Ruta 0) | ✔ | ||
| Ruta correcta de cada forma SAT | ✔ (según el mapa) | ✔ (define el mapa) | |
| A qué cuenta del catálogo apunta cada ruta | propone | firma | |
| Política de detección (PUE / PUE vencidas / todas) | recomienda pue | decide | |
| CFDI cancelado: excluir, advertir, permitir | recomienda excluir | decide | |
| Granularidad del corte | recomienda día | acepta | |
| Crear una cuenta nueva vs usar una existente | pregunta | decide | |
| Traspaso o reconstrucción en cada corrección | por defecto traspaso | ejecuta | autoriza la reconstrucción |
| Fecha de la póliza en periodos con IVA base flujo | conserva la fecha original al corregir | decide | |
| Encender el cron | doble interruptor, apagado de fábrica | propone cuando el cierre manual ya es rutina | autoriza |
| PUE de ejercicios anteriores y reenvío de balanza | lista | escala | decide |
| Acreditar el IVA de la comisión | pide la cuenta | escala | decide |
07El ciclo diario: detectar, agrupar, previsualizar, aplicar
El ciclo tiene cuatro tiempos y cada uno tiene un dueño distinto: el sistema detecta, el contador decide, la pantalla predice y el motor ejecuta. Confundirlos es lo que produce cortes de caja que cuadran contra Odoo y no contra el SAT.
7.1 Detectar: qué es "cobrable"
Contabilidad → Pólizas de cobro → Facturas por agrupar no es una vista con filtros sugeridos: su dominio está clavado en la acción y dice veniu_cobrable = True y veniu_politica_pago_sat = 'pue'.
veniu_cobrable es una definición fija, no configurable: factura o nota de crédito de cliente, state = 'posted', payment_state en not_paid o partial, y al menos una línea de cuentas por cobrar con residual distinto de cero. veniu_importe_pendiente suma ese residual.
El segundo término va en el dominio y no como facet de búsqueda a propósito. Como etiqueta quitable, bastaba un clic para que el tablero empezara a ofrecer PPD; agrupada en una póliza masiva, una PPD queda liquidada sin complemento de pago y el IVA trasladado del mes del cobro se omite. Quien necesite verlas entra por el filtro explícito "PPD (se cobra con pago y REP)" en la búsqueda de facturas.
El tablero abre agrupado por Ruta correcta. Así se lee: cada grupo es una futura póliza y su cuenta destino; el importe del grupo es lo que va a entrar a esa cuenta. El menú vecino, Revisión de ruteo, es el otro tablero: dominio veniu_ruta_estado in ('mal','sin_mapa','externa'), abierto con el filtro "Mal ruteadas" puesto y agrupado por ruta real. Ahí no se agrupa nada, se diagnostica. La columna que resuelve las dudas es Cuenta cargada (veniu_cuenta_real_id): cuando la ruta real viene vacía, es el único dato que dice dónde acabó el dinero.
7.2 La consola de agrupación y su semáforo
Se seleccionan las facturas en la lista y se lanza Agrupar en póliza de cobro. La consola expande cada factura a sus líneas de cuentas por cobrar abiertas —la unidad es la línea, no la factura— y calcula tres cosas por renglón: la ruta que dicta el mapa de formas SAT (ruta_correcta_id), la ruta que se va a aplicar (ruta_id, editable) y el semáforo.
El campo semaforo tiene tres valores y solo tres:
| Valor | Etiqueta | Qué significa | ¿Apaga "Agrupar y aplicar"? |
|---|---|---|---|
🟢 ok | Lista | Ruta que genera póliza y tiene cuenta destino, CFDI vigente, forma de pago SAT capturada, ruta no forzada y fuera de cuarentena | No |
🟡 aviso | Revisar | Sin timbrar o CFDI cancelado con política "advertir", sin forma de pago SAT, ruta forzada, va a cuarentena, PPD cuando la compañía detecta "todas" | No |
🔴 bloqueo | No aplicable | Sin ruta, ruta que no genera póliza, ruta sin cuenta destino, CFDI cancelado o sin timbrar con política "excluir", PPD con las políticas "solo PUE" y "PUE + PPD vencidas" | Sí |
El botón del pie desaparece cuando bloqueado es verdadero, y bloqueado se enciende con una sola línea marcada en bloqueo —también si no queda ninguna línea marcada, si alguna de las marcadas se quedó sin ruta, o si la política de la compañía prohíbe las selecciones mixtas—. El ámbar nunca bloquea: informa y queda escrito. Los botones de ayuda son cinco: "Marcar todo" (que salta las PPD a propósito), "Desmarcar todo", "Dejar solo las limpias" (desmarca todo lo que no sea ok), "Mandar a cuarentena", que reasigna a la ruta de cobros por identificar las líneas marcadas sin forma de pago o sin ruta, y "Recalcular", que solo repinta la pantalla.
Las PPD merecen párrafo aparte: no se esconden en la pestaña de excluidas. Entran a la lista principal, desmarcadas y en rojo, porque una factura que el contador buscó y no encontró se convierte en un pendiente invisible. Se marcan por el hecho del documento —su política de pago SAT—, no por el motivo de exclusión: con politica_deteccion = 'todas' el motor las dejaría pasar, y entonces el semáforo baja a ámbar y dice exactamente lo que va a ocurrir (cobro sin REP), en vez de mentir con un bloqueo inexistente.
7.3 La llave de agrupación: fecha de corte · ruta · moneda
_agrupar_lineas agrupa por la tripleta (fecha_corte, ruta_id, moneda_id). Cada combinación distinta es una póliza distinta.
La ruta está en la llave porque el error de origen fue agrupar solo por fecha. Un corte diario aplicado en bloque a una sola cuenta de caja mete transferencias y terminal dentro del efectivo. En el caso que originó el producto, 45 de 480 facturas —$165,761.85, el 12.2 % del corte— quedaron declaradas como efectivo sin haber sido efectivo nunca.
La moneda entra por una razón mecánica: un asiento con una línea en USD y otra en MXN hace imposible cuadrar el amount_currency del cargo. La restricción _check_moneda_unica lo impide a nivel de modelo, no solo de asistente.
La fecha de corte es llave de agrupación, no fecha contable. Con granularidad dia es la fecha del documento; con semana, el lunes; con mes, el día 1; con unica, ninguna. La fecha del asiento la fija _fecha_poliza: la mayor fecha de documento del grupo, salvo en diario, donde el grupo comparte fecha. fecha_forzada la sobrescribe, y la pantalla avisa de lo que cuesta: con IVA base flujo, mover la fecha mueve el periodo de reconocimiento del impuesto.
7.4 La previsualización
La pestaña Previsualización de las pólizas muestra una tabla con un renglón por póliza prevista: la llave legible (fecha · ruta · moneda), la cuenta destino, cuántas facturas y cuánto importe. Arriba, los contadores: seleccionadas, importe, rutas distintas y pólizas a generar.
No aparece el folio, y es deliberado: la tabla se recalcula con cada casilla que se marca, y pedir folios consultaría la secuencia una vez por grupo en cada clic. El folio lo asigna el motor al crear la póliza.
Lo que sí aparece es el preflight de periodo cerrado. Se leen los candados de la compañía que existan en la versión instalada —hard_lock_date, fiscalyear_lock_date, tax_lock_date, sale_lock_date, period_lock_date— y se comparan de forma inclusiva contra la fecha efectiva de cada grupo. Si alguno atrapa, el renglón sale en rojo y el bloque de advertencias lo explica. Es un aviso, no un bloqueo: quien tiene la llave del cierre es contabilidad, no este asistente. Sin él, Odoo rechazaba el asiento al final y se perdía la revisión de 500 renglones en un rollback.
7.5 Forzar la ruta de una línea
La columna Ruta a aplicar es editable. Es legítimo cambiarla en tres casos: mandar a cuarentena una línea cuya forma de pago SAT está vacía o es dudosa; corregir una forma de pago mal capturada cuyo cobro real se conoce y está documentado; y separar un cobro cuya clave SAT no distingue el instrumento real.
No es legítimo usarla para "acomodar" la caja. Y no queda oculto: si ruta_id difiere de ruta_correcta_id, la línea baja a ámbar, el bloque de advertencias cuenta cuántas son, la póliza queda con tiene_ruta_forzada encendido, el detalle se pinta en ámbar, el filtro Con ruta forzada las encuentra y el PDF de la póliza imprime el badge RUTA FORZADA en la línea más la leyenda al pie. El snapshot ruta_original_id congela la ruta con la que se aplicó, de modo que una corrección posterior no borre la evidencia: la póliza reimpresa en agosto cuenta lo mismo que la impresa en julio.
7.6 Aplicar: qué pasa exactamente
aplicar() exige el grupo Pólizas de cobro / Responsable en el propio método, no solo en el botón. Los groups de un botón son cosmética del cliente web y este método se alcanza por RPC, desde la lista de facturas y desde el cron (el cron corre en modo superusuario y se salta la compuerta a propósito: su decisión de negocio vive en sus dos interruptores).
Después, en este orden y dentro de una sola transacción:
_validar: la ruta genera póliza, tiene cuenta destino y esa cuenta es de la compañía. Se re-sincronizan los importes contra el residual vigente —si alguien cobró parcialmente entre la preparación y el clic, el cambio queda escrito en el chatter—. Se rechaza cualquier línea que ya no tenga saldo o que ya esté en otra póliza aplicada, y las que la política de la compañía manda excluir por CFDI cancelado o sin timbrar. Se comprueba que el detalle sume el total.- Se crea el asiento: un cargo a la cuenta de la ruta por el total, y un abono por factura a la cuenta de cuentas por cobrar que esa factura usó, con su cliente. Nunca una cuenta global del plan.
action_postpublica. Si la fecha cae en periodo cerrado o el diario no tiene secuencia, el error se traduce a un mensaje accionable._enlazar_lineas_polizaempareja cada línea del detalle con su contrapartida verificando la cuenta antes de sellar. Si no casan, aborta: no sella mal.- Se sellan
move_id,diario_id,cuenta_destino_id, usuario y fecha de aplicación; el estado pasa a Aplicado. _conciliarcasa par a par: línea de CxC contra su contrapartida. Una por una.
Si cualquiera de esos pasos falla, la excepción propaga y Odoo revierte todo. No existe el estado "publicada pero sin conciliar". Publicar 39 de 40 facturas sería peor que no publicar ninguna.
Para el implantador: no "mejores" esto con commits intermedios ni con un conciliar diferido. La atomicidad es la propiedad que hace auditable el producto.
Simulador: qué asiento genera cada forma de pago
La contracuenta es siempre la cuenta por cobrar que esa factura cargó, nunca una constante del plan. Los códigos son los sugeridos por la semilla: el asistente los resuelve contra el catálogo real de cada compañía.
7.7 Cómo se lee un folio
El folio es determinista: no hay ir.sequence; la unicidad la garantiza la constraint UNIQUE(company_id, name).
| Prefijo | Operación | De dónde sale |
|---|---|---|
POL-ING-… | Cobro (ingreso) | prefijo_folio de la ruta; las semillas traen POL-ING-EFE, POL-ING-TRF, POL-ING-TPV, POL-ING-INT, POL-ING-IDF |
POL-DIA-… | Cobro con póliza de diario | prefijo_folio de la ruta: POL-DIA-ANT, POL-DIA-COM, POL-DIA-INC (las semillas ESP y NCR llevan prefijo pero no generan póliza) |
POL-COR | Corrección por traspaso | Fijo en _generar_folio |
POL-LIB | Liberación de facturas | Fijo en _generar_folio |
POL-DEP | Depósito / liquidación | Fijo en _generar_folio |
Al prefijo se le añade el código de ruta si la compañía tiene folio_incluye_ruta; luego la fecha AAAA-MM-DD; el nombre de la moneda si no es la de la compañía; y un sufijo -2, -3 si ya existe uno igual.
El folio NUNCA se parsea para obtener la fecha ni la ruta: ambas son campos. Un folio con sufijo, con moneda o con la ruta suprimida por configuración rompe cualquier lector de cadenas.
Para el implantador: en el asiento contable el folio viaja en ref, no en name —el name lo impone la secuencia del diario—. Por eso buscar asientos con name like 'POL-' devuelve cero. Toda idempotencia y todo cruce se hacen por ref o, mejor, por el enlace move_id de la póliza.
08Los cuatro asientos correctivos y el árbol de decisión
Toda corrección de este módulo empieza por una sola pregunta, y la respuesta determina el método:
¿Hay que CAMBIAR DE CUENTA, o hay que SACAR ALGO del asiento?
Cambiar de cuenta significa que el cobro existió, es correcto y está bien reconocido: solo está en la cuenta equivocada. Sacar algo significa que ese renglón no debe estar reconocido como cobro en absoluto: el CFDI se canceló, entró un día que no era, o el cobro nunca ocurrió.
¿Qué está mal en la póliza aplicada?
│
┌─────────────────────────┴─────────────────────────┐
Está en la Algo no debe
cuenta equivocada estar dentro
│ │
TRASPASO (POL-COR) ┌──────────────┴──────────────┐
el DEFAULT Sale una parte Sale TODO
no desconcilia │ │
LIBERACIÓN (POL-LIB) REVERSIÓN
rompe solo ese par asiento de reversa
│ casado al original
¿imposible aislar el par?
│
RECONSTRUCCIÓN
último recurso
8.1 Traspaso (POL-COR) — el default
Reclasificación pura: carga la cuenta correcta y abona la que se usó por error. El asiento se agrupa por origen, así que si tres cuentas distintas alimentaron el error, hay tres abonos y un cargo.
Qué no hace, y es lo que lo vuelve el camino primario: no desconcilia nada, no toca payment_state y no vuelve a disparar el IVA de base de flujo, cuyo disparador es la conciliación, no la póliza. La factura sigue cobrada, con la misma fecha y el mismo periodo.
La cuenta de origen no se deduce de la ruta sino de veniu_cuenta_real_id: la contrapartida dominante de las conciliaciones de esa factura (y, solo si viene vacía, la cuenta sellada de la póliza o la de la ruta aplicada como respaldo). Por eso una segunda corrección parte de la cuenta ya corregida y no vuelve a mover lo movido. Y por eso es idempotente: si el origen ya coincide con el destino, esa línea se salta; si todas coinciden, el motor se niega con "ya están en su cuenta correcta: no hay nada que traspasar", en vez de publicar un asiento a cero.
La fecha del asiento es la de la póliza original (agrupado por día y ruta, que es lo que deja correcto el saldo diario de caja para el arqueo). Después, _conciliar_traspaso intenta casar el abono nuevo contra el cargo de la póliza original; si no puede —cuenta no conciliable, importes que no casan— deja constancia en el chatter y sigue: el traspaso ya cumplió su función contable.
El asistente Corregir ruteo enseña, antes de ejecutar, una tabla de "sale de (abono) / entra a (cargo)" con los importes.
8.2 Liberación (POL-LIB) — el bisturí
Saca facturas concretas de una póliza aplicada sin tocar a las demás. Mecánica, en tres pasos:
- Se rompe solo el par conciliado de esa factura: su línea de CxC contra la contrapartida que le tocó en la póliza (el sello
poliza_line_id, que es justo la pieza que permite ser quirúrgico). Quedan las dos abiertas: la factura vuelve a deber y el abono de la póliza queda huérfano. - Se publica la póliza de liberación: cargo a la CxC de esa factura —con su cliente— y abono a la cuenta que la póliza había cargado. La caja baja exactamente lo que subió de más.
- Ese cargo nuevo se concilia contra el abono huérfano de la original, que así queda cerrada consigo misma y sin saldo suelto.
La póliza original conserva su asiento, su folio y su expediente. Su línea queda marcada liberada con el enlace a la póliza que la sacó, y como _lineas_ya_aplicadas ignora las liberadas, la factura vuelve a quedar abierta y agrupable: se puede volver a cobrar en la póliza que le toque.
Dos límites que conviene conocer antes de necesitarlos:
- No se puede liberar la última factura viva. Si salen todas, eso no es una liberación: es una reversión, y el módulo lo dice con esas palabras y manda al botón Revertir, que además deja la reversa casada contra el original. El asistente ni siquiera abre si la póliza tiene menos de dos líneas vivas.
- Revertir una liberación deja un paso manual. Las líneas vuelven a figurar en la póliza original, pero la conciliación factura–póliza que la liberación rompió no se rehace sola. El chatter de la póliza original lo escribe: hay que conciliar a mano o volver a agrupar la factura.
También hace falta que la póliza tenga sellada su cuenta y que cada línea tenga su contrapartida sellada. Si la póliza se concilió por fuera del módulo, el motor se niega y remite a liberarla a mano o a la reconstrucción.
8.3 Reconstrucción — último recurso
Revierte la póliza entera y la rehace con la fecha original, que nunca se mueve: cambiarla movería de periodo el IVA base flujo.
Desconcilia todas las facturas del lote, incluidas las que estaban bien. El asistente cuantifica el daño antes de ejecutar: cuántas líneas correctas se van a desconciliar solo por compartir póliza. En el caso real habrían sido 301 correctas para arreglar 45.
Dos comportamientos fijos: siempre excluye los CFDI cancelados de la póliza nueva, sin depender de la política de la compañía —si no, la reconstrucción volvería a meter justo lo que se estaba sacando— y deja escrito en el chatter cuáles salieron. Y si todas las facturas del lote están canceladas, se detiene: la reversión que acaba de aplicarse ya dejó la contabilidad en su sitio.
Para el implantador: existe el interruptor permitir_reconstruccion en la configuración de la compañía. En clientes con IVA base flujo y periodos ya declarados, apágalo y deja vivos traspaso y liberación.
8.4 Reversión — deshacer la póliza entera
Desconcilia solo las contrapartidas propias (nunca button_draft, que arrastraría facturas ajenas), y revierte el asiento con _reverse_moves(cancel=True), que publica la reversa y la casa contra el original. Exige motivo escrito, la casilla explícita de "Confirmo que entiendo el impacto", el grupo Responsable y que la póliza esté aplicada.
Nunca borra. Una póliza aplicada o revertida no se elimina: el ondelete lo rechaza y remite a archivar (campo active). Borrarla arrastraría el detalle en cascada —con él, el historial de la factura, el motivo y el autor de la reversión— y liberaría el folio para que otra póliza recibiera el mismo número.
Árbol de decisión: ¿qué corrección aplico?
¿Qué está mal en esa póliza?
¿La factura sigue siendo un cobro legítimo (CFDI vigente)?
¿Cuántas facturas de esa póliza tienen que salir?
¿Hay que volver a generarla con las mismas facturas?
Es el camino barato y es el correcto. Carga la cuenta buena y abona la usada por error, con la fecha original. No desconcilia nada, no toca el estado de pago y no vuelve a disparar el IVA de base de flujo. Las demás facturas de la póliza ni se enteran.
Póliza aplicada → botón Corregir ruteo → método Traspaso.
El bisturí. Rompe solo el par conciliado de las facturas marcadas, publica el cargo a su cuenta por cobrar contra la cuenta que el corte había cargado, y lo concilia con el abono que queda huérfano. La póliza original conserva su asiento y su folio; las demás facturas no se tocan y su IVA no se vuelve a disparar. Las liberadas quedan abiertas y agrupables otra vez.
Póliza aplicada → botón Liberar facturas → marcar las que salen + motivo.
Último recurso: desconcilia TODAS las facturas del lote y les re-dispara el IVA de base de flujo, aunque estuvieran bien. En el caso real habría pasado 301 facturas correctas por una operación destructiva para arreglar 45. Antes de elegirla, pregúntate si la liberación no resuelve lo mismo tocando solo lo que hay que tocar.
Póliza aplicada → Corregir ruteo → método Reconstrucción (se puede prohibir por configuración).
Deshace la póliza completa con un asiento de reversa casado contra el original, con motivo obligatorio. Nunca borra nada. Si la póliza ya tiene traspasos o liberaciones aplicados encima, el módulo lo impide hasta que se deshagan esas derivadas primero: revertir sin mirarlas deja la cuenta de origen en negativo y la de destino cargada dos veces.
Póliza aplicada → botón Revertir.
8.5 Tabla comparativa
Traspaso POL-COR | Liberación POL-LIB | Reconstrucción | Reversión | |
|---|---|---|---|---|
| Qué toca | Solo cuentas de activo: carga la correcta, abona la usada | Rompe un par y publica cargo a CxC + abono a la cuenta cargada | Revierte el lote y crea póliza(s) nueva(s) | Un asiento de reversa casado contra el original |
| Qué desconcilia | Nada | Solo el par de las facturas que salen | Todas las facturas del lote | Todas las facturas del lote |
| ¿Re-dispara IVA base flujo? | No | Solo el de las facturas liberadas | Sí, el de todas | Sí, el de todas |
| Sobre qué factura actúa | Las mal ruteadas | Las que se seleccionan | Todo el lote | Todo el lote |
| Estado de la factura al final | Cobrada (sin cambio) | Abierta y agrupable de nuevo | Cobrada en la póliza nueva, o fuera si su CFDI está cancelado | Abierta |
| Fecha | La original de la póliza | La original de la póliza | La original de la póliza | La del asiento original |
| La póliza original | Intacta y aplicada | Intacta y aplicada | Queda revertida | Queda revertida |
| Coste operativo | Bajo | Bajo, acotado a lo que sale | Alto y colateral | Alto |
8.6 La regla dura de las derivadas
Una póliza que ya tiene traspasos o liberaciones aplicados encima NO se revierte ni se reconstruye hasta deshacer primero sus derivadas. El módulo lo bloquea nombrando el folio de la derivada. El orden correcto es al revés: primero la derivada, después la original.
El descuadre que esto evita no da ningún mensaje de error, porque cada asiento cuadra consigo mismo. Con cien pesos:
| Momento | Caja de venta | TPV por cobrar | CxC del cliente |
|---|---|---|---|
| Póliza P aplicada (cargo a Caja) | +100 | 0 | −100 |
| Traspaso T encima (carga TPV, abona Caja) | 0 | +100 | −100 |
| Se revierte P sin mirar a T | −100 | +100 | 0 |
| O se reconstruye P en TPV | −100 | +200 | −100 |
Caja queda en negativo por un cobro que sí entró, y TPV queda cargada dos veces por un cobro que solo ocurrió una. Nadie se entera hasta el arqueo, y para entonces hay que reconstruir la historia a mano.
Para el contador: si al intentar revertir aparece el mensaje con folios de pólizas derivadas, no busques la manera de saltarlo. Abre esas pólizas —el botón de estadística Derivadas las lista—, reviértelas y vuelve.
8.7 El caso típico: cancelaron un CFDI que ya estaba en una póliza
Es el caso más frecuente y el que separa un producto de un remiendo. El cliente pidió la cancelación, el CFDI pasó a cancel (o a global_cancel, si es una factura global de mostrador) y esa factura está dentro de una póliza aplicada junto a otras treinta y nueve que están bien.
Un CFDI cancelado no es ingreso. No se arregla moviéndolo de cuenta: hay que sacarlo del asiento. Y el traspaso lo bloquea a propósito, remitiendo al camino correcto.
Ruta correcta, paso a paso:
- Confirmar el estado real del comprobante en la factura. Si está
canceloglobal_cancel, sigue; si solo está en proceso, no toques nada todavía. - Comprobar en la póliza que no tenga derivadas aplicadas que afecten a esa línea. El botón Derivadas lo dice.
- Abrir la póliza y pulsar Liberar facturas. Marcar únicamente la factura cancelada, escribir el motivo —es lo que va a leer el contador dentro de seis meses— y dejar activada la casilla de fecha original.
- Leer el bloque "Qué va a pasar": dice cuántas facturas se quedan, cuánto sale y advierte de las canceladas.
- Aplicar. Nace la póliza
POL-LIBcon la fecha de la original, la caja baja exactamente ese importe, la factura queda abierta y la póliza original conserva asiento y folio. - Cerrar el asunto por la vía fiscal, no por la contable: si el cliente no pagó, la factura se queda abierta y sigue su curso de cartera; si pagó y el comprobante se canceló por error, lo que corresponde es nota de crédito o factura nueva, no volver a meterla en una póliza.
Por qué la liberación y no la reconstrucción: la reconstrucción resuelve lo mismo, pero desconcilia las treinta y nueve facturas correctas y vuelve a disparar su IVA de base de flujo. Si esas facturas cayeron en un mes ya declarado, el impuesto se mueve de periodo y la declaración deja de cuadrar contra la contabilidad. La liberación toca una y deja quietas a las demás.
Para el implantador: cuando el cliente llega con un lote histórico de cancelaciones dentro de pólizas ya aplicadas, la secuencia de implantación es esta —liberar una por una, verificando saldo de la cuenta origen después de cada bloque— y no una reconstrucción masiva. La reconstrucción se reserva para pólizas cuyo par conciliado no se puede aislar porque la conciliación se hizo fuera del módulo.
09Cerrar el ciclo: depósito a banco y liquidación de terminal
Una póliza de cobro mueve el dinero de Clientes a una cuenta de tesorería. Ahí no termina: termina cuando el dinero sale de esa cuenta de tránsito y llega al banco. Mientras eso no pase, el ciclo está a la mitad y el balance afirma algo que se puede refutar con un arqueo.
9.1 Por qué una cuenta que solo recibe cargos miente
Una cuenta de caja con cargos y cero abonos declara que el efectivo se acumula físicamente para siempre. En el 99 % de los casos es falso: el dinero sí se depositó, nadie registró el asiento. El daño no es estético:
- Es la partida más fácil de refutar. El efectivo se cuenta. Un arqueo sorpresa destruye el saldo en diez minutos.
- Alimenta la presunción de ingresos por depósitos bancarios no registrados (art. 59 fr. III del CFF): el dinero aparece en el estado de cuenta sin un asiento que lo explique.
- Una caja abultada se lee como préstamo a socios no documentado, dividendo ficto o venta no reportada.
- Vuelve inútil el propio corte de caja: nadie arquea contra un saldo que nunca baja.
9.2 El depósito: abono a la ruta, cargo a la cuenta puente
La operación vive en Pólizas de cobro → Depósito / liquidación. Genera una póliza tipo_operacion = 'deposito', tipo de póliza egreso, con folio POL-DEP-<RUTA>-<fecha>.
El asiento que arma _construir_move_vals_deposito() es literal:
POL-DEP-EFE-2026-08-11 ref: folio · ficha de depósito
Abono [cuenta de la ruta EFE] Depósito desde EFE ...... 88,379.50
Cargo [cuenta puente] ficha #NNNN ................. 88,379.50
Regla cardinal: ninguna póliza toca Bancos. El saldo del banco lo juzga el estado de cuenta, no la captura.
La pantalla la hace cumplir por partida doble. veniu.poliza.config.cuentas_reservadas_banco() es la definición del módulo de "cuenta de banco": la default_account_id de los diarios de tipo bank de esa compañía. Esas cuentas quedan fuera del dominio del selector de cuenta destino y, además, si la elegida está en esa lista action_depositar se niega y explica el porqué —el mismo depósito quedaría registrado dos veces el día que se importe el extracto—. No se mira account_type: en el plan mexicano Caja (101) y Bancos (102) comparten asset_cash, así que lo único que distingue al banco de verdad es que un diario bancario lo declara suyo.
La transitoria del extracto (suspense_account_id) sí es destino legítimo, y por eso queda fuera de la lista prohibida: no es el banco, es precisamente la cuenta puente que la conciliación bancaria limpia. Si la configuración trae como cuenta de depósito una cuenta reservada, el asistente abre el campo vacío en lugar de precargarla: no se convierte el error contable en el camino de menor resistencia.
9.3 La liquidación de terminal llega neta
El dinero de las rutas TPV e INT lo tiene un tercero. El asistente lo detecta solo (es_liquidacion: código de ruta TPV/INT, o cuenta de origen de tipo asset_receivable) y levanta el aviso que explica que el depósito llegará neto; los campos de comisión están siempre en pantalla.
El importe que se captura en Importe depositado es el bruto: el valor facial de lo que se le cobró al cliente, no lo que abonó la adquirente. La comisión y su IVA se capturan aparte, y neto = monto − comisión − IVA es lo que debe coincidir con el abono del estado de cuenta.
Abono [tpv_por_cobrar] Depósito desde TPV ........... 10,000.00
Cargo [cuenta puente] ........................ 9,779.60
Cargo [comisión] Comisión sobre el depósito ... 190.00
Cargo [IVA acreditable] IVA acreditable de la comisión . 30.40
Las cuentas de comisión e IVA que elige el operador en la pantalla mandan sobre las de la configuración: crear_deposito() las recibe como argumento y solo cae a la configuración si vienen vacías. Y los importes se persisten en la póliza (deposito_comision, deposito_iva_comision, referencia_externa), no solo en el asiento: son el soporte de la liquidación frente a la adquirente.
El IVA de la comisión no se acredita sin el CFDI de la adquirente. Si llega antes el estado de cuenta que el comprobante, la comisión va completa a gasto y se reclasifica al recibir el CFDI. Acreditar contra un estado de cuenta es un IVA acreditado improcedente.
Otras compuertas de action_depositar: importe mayor que cero, neto no negativo, la ruta de origen debe tener cuenta, destino distinto de origen (si no, el asiento se anula solo y el dinero sigue declarado en la caja), comisión sin cuenta de comisión, IVA sin cuenta de IVA, y toda cuenta y el diario deben pertenecer a la compañía. Aplicar exige el grupo Responsable, como cualquier póliza.
9.4 Antigüedad esperada por cuenta
Lo que se vigila no es el saldo: es su edad.
| Cuenta | Antigüedad esperada | Alarma |
|---|---|---|
| Caja de venta | ≤ 3 días, hasta el depósito | > 7 días |
| TPV por cobrar | ≤ 3 días con adquirente bancaria, ≤ 15 si no lo es | > 15 días |
| Recibos pendientes | hasta la próxima conciliación | > 30 días |
| Intermediarios por cobrar | lo que diga el contrato (15-30 días) | contrato + 15 |
| Cobros por identificar | cero al cierre del mes | cualquier saldo el día 1 |
La cuarentena se mide sola: alerta_cuarentena() lee los apuntes publicados y sin conciliar de la cuenta de la ruta de cuarentena y publica saldo, número de apuntes y días del más viejo contra dias_cuarentena (30 de fábrica).
9.5 Lo que este producto no hace
La conciliación bancaria contra extractos es proceso nativo de Odoo y este módulo no la ejecuta. Lo que hace es habilitarla: rutea a cuentas que el widget de conciliación ya conoce y deja saldos que sí se pueden amarrar contra una línea de extracto. Si el cliente nunca carga extractos, Bancos seguirá en cero por mucho depósito que se capture — y eso es un pendiente que se declara, no que se disimula.
Para el contador de Galgo: CAJA DE VENTA acumuló $1,359,077.56 en cargos contra $240,140.17 de abonos — el efectivo entró contablemente y casi nunca salió a banco. Pero la reapertura de PUE del 6 y 7 de agosto revirtió 22 de las 24 pólizas de julio, y esas reversas abonaron la cuenta: al cierre de esa jornada el saldo quedó en negativo, cerca de −$57,159.
Por eso aquí la primera tarea productiva no es depositar. Es cuadrar:
- Explicar el saldo negativo con el contador. El sospechoso está identificado: los $240,140.17 de cartera recuperada entraron por el diario
CAJAVy abonan la misma cuenta que ahora abonaron también las reversas. Si hay doble abono, se corrige antes de tocar nada más. - Reagrupar el universo reabierto por día × ruta × moneda, que es lo que vuelve a cargar la caja con lo que de verdad se cobró en efectivo.
- Entonces sí, depositar: una póliza por ficha de depósito, con su referencia, hasta que el saldo contable se parezca al efectivo que de verdad hay en el cajón. Nunca un solo asiento por el acumulado: el auditor pide la ficha, no el total.
10Controles, segregación de funciones y expediente
Una campaña de corrección masiva mueve millones con dos clics. El control no puede ser la buena voluntad del operador.
10.1 Los tres grupos
| Grupo (nombre exacto) | Hereda | Qué puede |
|---|---|---|
| Pólizas de cobro / Consulta y preparación | account.group_account_invoice | Ve el tablero y todas las pólizas. Crea y edita pólizas solo en borrador o por aprobar. Usa el asistente de agrupar. No publica. No borra. Catálogos (rutas, mapa, configuración) en solo lectura. |
| Pólizas de cobro / Responsable | Consulta + account.group_account_user | Aplica, aprueba, revierte, libera y corrige. Escribe sobre cualquier póliza, en cualquier estado. Únicos asistentes suyos: revertir, corregir, liberar, depósito. |
| Pólizas de cobro / Configuración | Responsable + account.group_account_manager | Rutas, mapa de formas SAT, políticas y asistente de configuración. |
base.group_system implica el grupo de Configuración: el administrador técnico lo tiene sin asignarlo a mano.
El reparto de reglas es tan importante como el de accesos. Las reglas globales acotan todo por company_id in company_ids. Sobre eso, dos reglas por estado: Consulta solo escribe pólizas y detalle cuyo estado esté en ('borrador','por_aprobar'); Responsable tiene una regla permisiva [(1,'=',1)] que no es decorativa — hereda Consulta por implied_ids, las reglas con grupos se combinan con OR, y sin ella el Responsable no podría escribir una póliza ya aplicada y por tanto no podría revertirla.
10.2 La compuerta vive en el motor, no en el botón
Los groups de un botón son cosmética del cliente web.
aplicar() llama a _check_responsable() en su primera línea, antes de cualquier validación de negocio. Lo mismo revertir(), action_aprobar, action_liberar, action_revertir, action_corregir, action_aplicar, liberar_lineas() y corregir_reconstruccion(). Esos métodos se alcanzan por RPC, desde la lista de facturas y desde el cron: esconder el botón no protege nada. Quien no tiene el grupo recibe un mensaje que además le dice qué sí puede hacer: preparar la póliza y dejarla en borrador o enviarla a aprobación.
La única excepción es el modo superusuario (env.su), donde la compuerta se salta a propósito: el cron de cierre corre con sudo() y no tiene un humano detrás al que exigirle un grupo. Ese camino está cerrado por otro lado (capítulo 11).
10.3 La aprobación por monto
Se enciende con exige_aprobacion en la configuración de la compañía y se calibra con aprobacion_monto. Cuando el importe absoluto de la póliza alcanza o rebasa ese monto, el lote nace en por_aprobar en lugar de aplicarse, y deja nota en el chatter. Con el monto en 0, todas las pólizas pasan por aprobación.
Quien la levanta es el Responsable, con Aprobar y aplicar: action_aprobar exige el grupo, registra en el chatter quién aprobó y encadena aplicar(). Si alguien intenta saltarse el paso con Aplicar póliza sobre un lote en borrador que rebasa el umbral, action_aplicar lo detiene y le dice que la envíe a aprobación.
10.4 Una póliza que tocó el mayor no se borra: se archiva
El @api.ondelete _no_borrar_aplicados rechaza el borrado de cualquier lote en estado aplicado o revertido, o que tenga asiento. Se archiva con el campo active. Dos razones concretas:
- Una póliza revertida es el expediente de dos asientos vivos —el original y su reversa—. Borrarla arrastraba
linea_idsen cascada, y con ellas el historial factura por factura, el motivo y el autor de la reversión. - Liberaba el folio.
_generar_foliobusca conactive_test=Falsejustamente para no reasignar el número de una póliza archivada. Sin el candado, dos operaciones distintas acabarían con el mismo folio en el expediente.
Archivar oculta de las listas sin tocar el asiento ni el folio. Lo que sí se borra es lo que nunca tocó el mayor: una póliza en borrador o cancelada.
10.5 El rastro que queda
| Qué preguntará el auditor | Dónde está |
|---|---|
| Quién la preparó | usuario_id — Preparado por |
| Quién la aplicó y cuándo | usuario_aplicacion_id, fecha_aplicacion |
| Quién la revirtió, cuándo y por qué | usuario_reversion_id, fecha_reversion, motivo_reversion (obligatorio: revertir() rechaza motivo vacío) |
| Contra qué cuenta se publicó | cuenta_destino_id — snapshot al aplicar; si mañana cambia la ruta, el histórico no miente |
| Qué método de corrección se usó | metodo_correccion (traspaso / reconstrucción) |
| Qué pólizas nacieron de ésta | lote_origen_id / lote_hijo_ids, pestaña Trazabilidad y botón Derivadas |
| Qué pasó con una factura concreta | en la línea: ruta_correcta_id, ruta_aplicada_id, ruta_original_id, ruta_forzada, liberada, lote_liberacion_id, conciliado, full_reconcile_id |
| Desde la factura, en qué póliza cayó | veniu_poliza_lote_id, veniu_poliza_folio |
| La narrativa completa | el chatter: el lote hereda mail.thread y publica en aplicar, aprobar, cancelar, revertir, liberar, depositar y cierre automático |
10.6 Qué se prueba antes del go-live
Para el implantador: con usuarios reales de cada grupo, no con el administrador. Un sudo de más invalida toda la prueba.
| Prueba | Resultado exigido |
|---|---|
| Consulta agrupa un día y deja la póliza en borrador | Funciona |
| Consulta pulsa Aplicar póliza | Error de _check_responsable |
| Consulta intenta editar una póliza ya aplicada | Bloqueado por la regla de estado |
Consulta llama aplicar() por RPC | Mismo error: la compuerta está en el motor |
| Responsable aplica, revierte con motivo y libera una factura | Funciona; el chatter registra las tres |
| Responsable intenta borrar una póliza aplicada | Error, con la sugerencia de archivar |
| Configuración cambia una ruta y su cuenta | Funciona; Responsable no |
| En multi-compañía, un usuario de la compañía A busca pólizas de la B | Cero resultados |
11Automatizar o no: el cron de cierre
El módulo trae un cierre automático. Viene apagado, y apagarlo dos veces es parte del diseño.
11.1 Los dos interruptores
| Interruptor | Dónde | Quién lo enciende | De fábrica |
|---|---|---|---|
ir.cron.active | acción planificada "Pólizas de cobro: cierre del día anterior" | el implantador | False |
cron_habilitado | configuración por compañía | el contador | False |
Encender uno solo no hace absolutamente nada: el cron corre, lee la configuración de cada compañía y sale sin tocar nada si cron_habilitado es falso. La razón no es redundancia por gusto: una restauración de base de datos con el cron activo no debe disparar cierres sorpresa en una copia de trabajo. Además el registro va con noupdate="1": una actualización del módulo nunca vuelve a encender un cron que el cliente apagó a propósito.
11.2 Qué entra
Solo rutas con permiso explícito de cierre automático: permite_cron = True, con cuenta destino resuelta y que generen póliza. En la semilla del producto únicamente la ruta EFE lo trae. El efectivo es lo único que se puede cerrar sin criterio humano — y aun así hacen falta los dos interruptores. Todas las demás (TRF, TPV, INT, IDF, ANT, COM, INC, ESP, NCR) están en False.
Sobre eso, el cron aplica el mismo dominio de cobrables que el tablero, más: la compañía de la configuración (forzada con allowed_company_ids, no la del usuario del cron), la ruta correcta dentro de las rutas permitidas, CFDI no cancelado, y estado de ruteo distinto de sin_mapa. Lo que no tiene ruta clara no se cierra solo: se queda esperando a una persona.
La aprobación por monto sigue mandando. crear_desde_facturas aplica solo los lotes que quedaron en borrador; los que la política mandó a por_aprobar se quedan ahí, esperando al Responsable, aunque los haya creado el cron.
11.3 Los topes
| Tope | Valor | Por qué |
|---|---|---|
| Días de gracia | cron_dias_gracia, 1 de fábrica | Nunca cierra el día en curso: la fecha tope es hoy menos los días de gracia, con mínimo de 1 |
| Ventana hacia atrás | CRON_VENTANA_DIAS = 30 | Sin cota inferior, el primer disparo barría todos los ejercicios abiertos en una sola transacción. Lo viejo es cartera histórica y se agrupa a mano, mirándolo |
| Facturas por corrida y compañía | CRON_LIMITE_FACTURAS = 500 | Un timeout del worker no debe dejar medio cierre aplicado. Al alcanzarse queda una advertencia en el log y el resto se cierra en la corrida siguiente |
| Aislamiento | savepoint por compañía | Una compañía que falla se deshace por completo y las demás siguen; la excepción queda en el log con su nombre |
La periodicidad del registro es de 1 día. Cada póliza que nace por esta vía queda marcada en su chatter como "Generada por el cierre automático", y el total de la corrida queda en el log del servidor.
11.4 Cuándo se enciende
REGLA: el cierre automático no se enciende hasta que 30 días de operación manual salgan limpios. Limpios significa: cuadre póliza ↔ facturas al peso, cuarentena en cero al corte del mes, cero facturas con estado sin mapa, y ninguna corrección de ruteo pendiente.
El error que este módulo existe para prevenir es exactamente automatizar el corte sin mirar la forma de pago: en el caso que originó el producto, 45 de 480 facturas —$165,761.85, el 12.2 %— acabaron declaradas como efectivo sin serlo. Un cron encendido de fábrica reproduce ese error todas las noches y sin testigo, porque nadie revisa lo que salió bien mil veces.
Hay una segunda razón, operativa y menos discutible: un día puede seguir facturando. El corte de caja se cierra contra el arqueo físico, no contra un reloj. El día que se documentó llevaba 23 facturas a las 13:26 h y cerró con 50 a las 22:16 h. Un cron que hubiera corrido a media tarde habría emitido una póliza incompleta y correcta, que es la peor combinación: cuadra consigo misma y no cuadra con la caja.
Para el implantador: el cierre automático es la última fase de la implantación, nunca la primera, y se entrega documentando qué rutas quedaron con permite_cron y con qué días de gracia. Si el cliente pide encenderlo el día uno, lo que está pidiendo es saltarse el piloto.
12Cierre mensual y evidencia
El mes no se cierra cuando se apagó la luz: se cierra cuando cada peso cobrado tiene una cuenta que lo explica y un papel que lo prueba. Este capítulo es el orden en que se revisa eso, y qué queda como evidencia después.
12.1 El checklist del cierre
Se corre en este orden. Cada punto habilita al siguiente: depositar la caja antes de cerrar los cortes deposita un saldo incompleto.
| # | Punto de control | Dónde se mira | Verde cuando |
|---|---|---|---|
| 1 | No quedan pólizas sin aplicar | Pólizas de cobro ▸ Pólizas, filtro Borrador (cubre Borrador y Por aprobar) + rango de Fecha | Cero resultados con fecha del mes |
| 2 | No quedan días sin corte | Facturas por agrupar (solo lista PUE cobrables), acotado al mes | Lista vacía, o cada excepción explicada |
| 3 | Cuarentena en cero | Mayor de la cuenta destino de la ruta IDF: apuntes publicados y sin conciliar | Sin saldo abierto — lo que alerta_cuarentena() reporta como "Cuarentena limpia" |
| 4 | Antigüedad de las transitorias revisada | Mayor de las cuentas de TRF, TPV e INT | Nada abierto más allá del ciclo normal de depósito de cada canal |
| 5 | Ruteo del mes sin errores | Revisión de ruteo, filtro Mal ruteadas | Cero. Lo que aparezca se corrige con traspaso antes de cerrar |
| 6 | Depósitos y liquidaciones aplicados | Pólizas POL-DEP del mes | El saldo de caja es el efectivo que de verdad está en la caja fuerte |
| 7 | Balanza cuadrada | Reportes de contabilidad | Cargos = abonos y ninguna cuenta destino con signo imposible (caja en negativo) |
Regla dura. La cuenta de cobros por identificar (ruta IDF) llega a CERO al cierre. No es una meta de calidad: un CFDI timbrado como PUE ya le afirmó al SAT que se cobró. Mientras viva en cuarentena, el ingreso está declarado y la tesorería no cuadra contra nada.
Para el contador: un punto 4 sucio casi siempre significa que la adquirente depositó y nadie aplicó la póliza de liquidación, no que falte un cobro.
Para el implantador: los puntos 1, 2 y 5 son consultas de un minuto. Deja los tres filtros guardados como favoritos del usuario del cliente en el arranque; es la diferencia entre un cierre que se hace y uno que se promete. El punto 3 es el mayor de la cuenta de la ruta IDF acotado a apuntes sin conciliar: déjalo también como favorito.
12.2 La alerta que el módulo ya calcula
alerta_cuarentena() (en models/veniu_poliza_config.py) mide la cuenta destino de la ruta configurada en ruta_cuarentena_id, para la compañía de la configuración, y sobre apuntes publicados y sin conciliar (full_reconcile_id vacío). Un apunte ya conciliado es un cobro que alguien reclasificó: salió de la cuarentena aunque el asiento siga existiendo.
Devuelve saldo, número de apuntes, fecha del apunte abierto más antiguo, antigüedad en días, la cuenta medida y si rebasó dias_cuarentena (30 de fábrica). El campo aviso_cuarentena traduce eso a una sola frase, en cuatro formas posibles:
- Cuarentena limpia: <cuenta> no tiene cobros abiertos. — el punto 3 del checklist está verde.
- En plazo · 12430.00 MXN en 7 apuntes sin conciliar · el más antiguo lleva 11 días (límite 30).
- VENCIDA · … — hay dinero cuyo canal de entrada nadie investigó desde hace más días de los permitidos.
- El motivo por el que no se pudo medir: no hay ruta de cuarentena configurada, o esa ruta no tiene cuenta destino.
El método action_ver_cuarentena() abre exactamente esos apuntes para depurarlos uno por uno. Tanto el campo como el método viven hoy en el modelo de configuración y no están colocados en el formulario de Ajustes generales: hasta que se añadan, el punto 3 se lee del mayor de la cuenta de la ruta IDF con el mismo filtro (publicados, sin conciliar).
Para el implantador: el campo no está almacenado a propósito. El saldo de una cuenta cambia con cada asiento y un campo almacenado quedaría mintiendo hasta el siguiente guardado de la configuración, que es un registro que nadie toca en meses. Se lee con sudo para que un usuario de cobranza sin permiso sobre el mayor no vea el formulario en blanco.
12.3 Qué se le entrega al contador externo
El paquete no es "los reportes de Odoo": es lo que permite a un tercero rehacer el mes sin llamarte. El diseño completo está en D:\Users\sergi\Claude Code\30_HERRAMIENTAS\veniu_polizas_cobro\90_docs\entregables_contador\:
| Documento | Qué resuelve |
|---|---|
PAQUETE_CONTABLE_MENSUAL.md | La especificación del entregable: estructura de carpetas, nomenclatura, generadores y el reporte de cuadre |
MANUAL_CICLO_CONTABLE_COMPLETO.md | El ciclo de la factura al SAT, con la rutina diaria, semanal, mensual y anual |
INV_SAT_ANEXO24.md | Qué exige el SAT y cuándo: qué es mensual y qué es solo a requerimiento |
INV_ODOO_NATIVO.md | Qué genera Odoo 19 de fábrica para México y qué falta construir |
INV_CONTPAQI.md | Qué puede importar CONTPAQi y por qué el .bak no es el camino |
Tres paquetes, no uno. El mensual lleva balanza, catálogo si cambió, soporte y cuadre. El de requerimiento lleva pólizas y auxiliares, y solo se arma ante un acto de la autoridad: el XML de pólizas exige un TipoSolicitud (acto de fiscalización, compulsa, devolución o compensación) y no existe el valor "mensual". El anual es el mes 13.
De este módulo sale una pieza de ese paquete: el PDF de cada póliza (reporte Póliza de cobro). El auxiliar de las cuentas de tránsito y su antigüedad se arma desde el mayor de Odoo, filtrando la cuenta destino de cada ruta. Ninguno sustituye a la balanza.
12.4 Cómo se audita un mes cerrado
Tres cortes, de grueso a fino. Los tres deben dar el mismo número.
Por ruta. Suma de monto_total (Importe total) de las pólizas aplicadas del mes, agrupadas por Ruta, contra el movimiento del periodo de la cuenta destino de cada ruta en la balanza. Una ruta cuya cuenta se movió más que sus pólizas tiene asientos manuales encima: hay que nombrarlos.
Por folio. El folio de la póliza encabeza siempre el campo Referencia (ref) del asiento; en Odoo el número del asiento lo impone la secuencia del diario, no el módulo. En el Libro Mayor, filtrando el diario de pólizas y el periodo y agrupando por referencia, cada renglón es un folio: POL-ING-EFE-2026-07-31. Abrir la póliza y comparar monto_total y cantidad_lineas (Líneas de CxC) contra el renglón.
Contra el mayor, factura por factura. Es la consulta que prueba que lo agrupado es lo cobrado: en la póliza, cada línea de detalle tiene Conciliado en verdadero, y la Conciliación (full_reconcile_id) queda sellada cuando el par cierra por completo contra la línea de cuentas por cobrar de su factura. Si el importe cuadra pero hay líneas sin conciliar, la póliza movió dinero sin cerrar la factura, y el mayor de clientes seguirá inflado.
Del lado de las facturas la misma prueba se hace al revés: en la lista de facturas, filtro Con póliza asignada, agrupado por Póliza. La etiqueta de póliza de la factura es un campo calculado sobre los detalles de póliza —solo cuenta los lotes de cobro aplicados—: por diseño no puede decir que hay póliza donde no la hay.
12.5 Qué hacer con lo que quedó fuera
Tres cosas sobreviven al cierre. Cada una tiene un instrumento y solo uno.
PUE abiertas de meses ya cerrados. Bandera roja fiscal, no un pendiente de cobranza: se le afirmó al SAT que la operación se cobró en una sola exhibición y no se cobró. No se resuelve con una póliza. Las salidas son cobrarla y documentarla, o cancelar y reexpedir como PPD, y cuál aplica lo decide el contador con el plazo de la facilidad vigente en la mano. Este módulo no la toca.
Facturas sin timbrar. La política politica_sin_timbrar viene en Advertir: se pueden agrupar, pero el operador ve la alerta Sin timbrar en la línea. Al cierre se listan aparte y se timbran o se explican; una factura sin CFDI que ya cobró efectivo es un ingreso sin comprobante.
PPD sin REP. La Ruta 0 las excluye con el motivo es_ppd cuando la política de detección es Solo PUE, y también con PUE + PPD vencidas mientras la factura no haya vencido. El asistente de agrupar las muestra siempre —marcadas como PPD y nacidas desmarcadas—: en rojo de bloqueo salvo que la política sea Todas las abiertas, donde queda en aviso porque el motor sí las dejaría pasar. No es una limitación: una PPD saldada por póliza masiva queda liquidada sin complemento de pago, y el IVA trasladado del mes del cobro se omite. Se cobran con un pago (account.payment) que arrastra el REP, dentro del plazo que fije la Resolución Miscelánea vigente.
13Anexos
Interactivo
Buscador: forma de pago SAT → ruta → cuenta
Clave Forma de pago SAT Ruta Cuenta destino Póliza
Ninguna clave coincide.
A. Forma de pago SAT → ruta → rol de la cuenta
Buscador: forma de pago SAT → ruta → cuenta
| Clave | Forma de pago SAT | Ruta | Cuenta destino | Póliza |
|---|
Ninguna clave coincide.
La pregunta que ordena toda la tabla es una sola: quién tiene ese dinero AHORA. La empresa (caja), el banco sin confirmar (transitoria), o un tercero procesador (cuenta por cobrar). Ninguna ruta apunta jamás a Bancos.
Son 23 filas: las 22 claves del catálogo c_FormaPago que trae la semilla más el comodín para las facturas sin forma de pago capturada.
| Clave | Nombre SAT | Ruta | Nota |
|---|---|---|---|
| 01 | Efectivo | EFE | La única forma que legítimamente entra a la caja de efectivo |
| 02 | Cheque nominativo | TRF | El papel en la mano no es dinero: hasta que el banco lo libera es un cobro pendiente de confirmación |
| 03 | Transferencia electrónica de fondos | TRF | No va a Bancos: postear ahí sin extracto duplica el ingreso el día de la conciliación |
| 04 | Tarjeta de crédito | TPV | Lo tiene la adquirente hasta que deposita, neto de comisión |
| 05 | Monedero electrónico | TPV | Lo liquida el emisor del monedero, no el cliente |
| 06 | Dinero electrónico | TPV | El saldo lo tiene la plataforma emisora hasta que deposita |
| 08 | Vales de despensa | TPV | Los reembolsa la emisora, con su comisión. Nunca es efectivo |
| 12 | Dación en pago | ESP | La contracuenta es el bien recibido y su avalúo. Asiento manual del contador |
| 13 | Pago por subrogación | ESP | Pagó un tercero y ocupa el lugar del acreedor: hay que leer el convenio |
| 14 | Pago por consignación | ESP | El dinero está ante una autoridad judicial, no en la empresa |
| 15 | Condonación | NCR | No genera póliza. Exige CFDI de egreso: el ingreso y el IVA ya se declararon |
| 17 | Compensación | COM | Cliente que también es proveedor. Póliza de diario, sin tesorería. Mismo RFC y convenio firmado |
| 23 | Novación | ESP | La obligación original se sustituyó: hay que leer el nuevo instrumento |
| 24 | Confusión | ESP | Acreedor y deudor son la misma persona. Normalmente ligado a una fusión |
| 25 | Remisión de deuda | NCR | No genera póliza. Mismo camino que la condonación |
| 26 | Prescripción o caducidad | INC | Se castiga contra la estimación de incobrables. La deducibilidad la decide el contador |
| 27 | A satisfacción del acreedor | ESP | Clave residual sin contracuenta única: obliga a leer el soporte |
| 28 | Tarjeta de débito | TPV | Misma cuenta que crédito: la adquirente deposita un solo importe neto mezclado |
| 29 | Tarjeta de servicios | TPV | Mismo circuito: liquida la adquirente, neto de comisión |
| 30 | Aplicación de anticipos | ANT | El dinero ya entró antes. Solo cancela el pasivo contra la CxC: póliza de diario |
| 31 | Intermediario de pagos | INT | Marketplaces y pasarelas: liquidan neto de comisiones del 15 al 30 % |
| 99 | Por definir | IDF | PUE + 99 es inconsistente con la guía de llenado del CFDI 4.0. Abrir el XML timbrado |
| — | Sin forma de pago (comodín) | IDF | Dato operativo faltante, no problema fiscal. Cuarentena hasta que el vendedor diga cómo entró el dinero |
Rol contable de cada ruta:
| Ruta | Cuenta sugerida | Tipo | Concilia | Folio | Póliza |
|---|---|---|---|---|---|
EFE | 101.02.01 Caja de venta | Efectivo | No | POL-ING-EFE | Ingreso |
TRF | 102.01.04 Recibos pendientes de conciliar | Activo circulante | Sí | POL-ING-TRF | Ingreso |
TPV | 107.05.02 TPV / Terminal bancaria por cobrar | Activo circulante | Sí | POL-ING-TPV | Ingreso |
INT | 107.05.03 Intermediarios de pago por cobrar | Activo circulante | Sí | POL-ING-INT | Ingreso |
IDF | 102.01.06 Cobros por identificar | Activo circulante | Sí | POL-ING-IDF | Ingreso |
ANT | 206.01.01 Anticipos de clientes | Pasivo circulante | Sí | POL-DIA-ANT | Diario |
COM | 201.01.01 Proveedores nacionales | Por pagar | Sí | POL-DIA-COM | Diario |
INC | 108.01.01 Estimación de cuentas incobrables | Activo circulante | Sí | POL-DIA-INC | Diario |
ESP | — | — | — | POL-DIA-ESP | No genera |
NCR | — | — | — | POL-NCR | No genera |
TPVusa activo circulante y no por cobrar: llevarlo aasset_receivableensuciaría la antigüedad de saldos y el mayor de clientes con un tercero que no es el cliente. SoloEFEtrae permite cierre automático de fábrica.
Para el implantador: los códigos son sugerencias. El asistente resuelve cada cuenta en este orden: la que la ruta ya tenía guardada, código exacto, familia del código (todos los segmentos menos el último, con el tipo de cuenta dentro del dominio), los métodos de pago entrantes —solo para TRF— y, únicamente si está apagado Crear las cuentas faltantes, una candidata por tipo de cuenta; si nada casa, crea la cuenta sugerida. Cada línea declara su confianza: Ya configurada y Código exacto se aplican solas; Familia de código y Métodos de pago se aplican, pero salen en amarillo para revisión; Solo por tipo nace como Pendiente de decisión y bloquea la aplicación hasta que el contador elija. Revisa las amarillas antes de aceptar.
B. Catálogo de mensajes y qué hacer
| Mensaje | Qué pasó | Qué hacer |
|---|---|---|
| Solo el grupo "Pólizas de cobro / Responsable" puede aplicar, aprobar, revertir, liberar o corregir pólizas. | Segregación de funciones dentro del motor, no solo en el botón | El usuario puede preparar y dejar en borrador o enviar a aprobación. Aplicar lo hace el responsable |
| La póliza … no se puede revertir: ya tiene N póliza(s) derivada(s) aplicada(s) sobre ella (…) | Encima de esa póliza ya hay un traspaso o una liberación | Revierte primero la derivada que el mensaje nombra. El orden inverso descuadra dos cuentas |
| La factura … ya no tiene saldo abierto: alguien la cobró después de preparar la póliza … | Se cobró por otro camino entre preparar y aplicar | Quita esa línea del lote y vuelve a aplicar |
| La línea de la factura … ya está incluida en otra póliza aplicada. Reaplicar duplicaría el cobro. | Doble captura del mismo cobro | Quítala. Busca la póliza que ya la contiene |
| No se pudo publicar la póliza … con fecha … | Fecha de bloqueo contable posterior a la de la póliza, o diario sin secuencia | Ajusta la fecha de la póliza o pide que abran el periodo; si el diario no tiene secuencia, arréglalo antes de reintentar |
| La cuenta … de la factura … no permite conciliación. | La cuenta de CxC de la factura no es conciliable | Activa Permitir conciliación en la cuenta, o la póliza dejará la factura abierta |
| La ruta … no genera póliza. + motivo | Cayó en ESP o NCR | ESP: asiento manual con el soporte. NCR: nota de crédito, no asiento |
| La factura … tiene la forma de pago SAT "…", que no tiene ruta configurada. | Falta una fila en el mapa de esa compañía | Agrega la fila, o cambia la política a cuarentena y depúrala después |
| Estás liberando TODAS las facturas que quedan en la póliza … | La liberación vaciaría la póliza | Usa Revertir: deja el asiento de reversa casado contra el original |
| La póliza … tiene una sola factura viva: sacarla es deshacer la póliza entera. | Mismo caso, visto desde el asistente | Igual: Revertir |
| Estas líneas no tienen sellada su contrapartida en el asiento (…): la póliza se concilió por fuera del módulo. | Alguien concilió a mano y se perdió el sello línea a línea | Libera a mano o usa la reconstrucción |
| El motivo de la liberación es obligatorio. | Falta el texto del motivo | Escríbelo: es lo que va a leer el contador cuando pregunte por qué esa factura salió de la póliza |
| La cuenta … es la que Odoo mueve al conciliar el estado de cuenta del diario bancario. | Se eligió la cuenta de Bancos como destino del depósito | Elige una cuenta puente de tránsito de liquidez. La transitoria del extracto sí sirve |
| La compañía … tiene prohibida la reconstrucción de pólizas. Usa el traspaso. | permitir_reconstruccion apagado | Corrige por traspaso: no desconcilia ni vuelve a disparar el IVA cobrado |
| La suma del detalle (…) no coincide con el importe total de la póliza (…). | El lote se editó y los totales quedaron viejos | Recalcula el lote antes de aplicar |
| El asiento de la póliza … generó N contrapartidas para M líneas de detalle. No se sella nada. | El asiento no salió con la forma esperada | No lo fuerces: revisa el diario y la moneda, y vuelve a generar |
| El asiento … está publicado: no se borra, se revierte. | Se intentó borrar un asiento vivo | Revierte la póliza. Un asiento publicado es expediente |
| La póliza … ya tocó la contabilidad (estado "…"): no se borra. | Aplicada o revertida | Si está aplicada, reviértela. Si ya está revertida, archívala con el botón de archivar |
C. Gotchas verificados en producción
- El folio vive en
ref, no enname. El número del asiento lo impone la secuencia del diario. El folio de la póliza encabeza siempre la referencia, y la referencia externa del soporte va detrás, nunca en su lugar. Ese es el campo por el que se rastrea en el mayor. - El folio no se parsea nunca. Fecha y ruta son campos, no substrings. En el caso real, derivar la fecha del folio con un reemplazo del prefijo se rompió en cuanto el folio incorporó la ruta.
- El detalle es un snapshot congelado. Forma de pago, clave SAT, estado del CFDI y ruta se guardan el día que se aplicó.
ruta_forzadase mide contraruta_original_idcuando existe: si no, una corrección posterior haría que la póliza impresa en agosto contara algo distinto de la impresa en julio. button_draftdesconcilia TODAS las líneas del asiento. Por eso el módulo nunca lo usa para deshacer:_desconciliar()rompe solo las contrapartidas de esta póliza, y la reversa se hace con_reverse_moves(cancel=True), que publica el asiento de reversa y lo casa contra el original. Usarbutton_draftarrastraría facturas ajenas.- CFDI cancelado también es
global_cancel. El filtro va por exclusión (not in ('cancel', 'global_cancel')). Filtrar por igualdad asentperdería las facturas globales, que son ingreso legítimo. - Una cuenta creada por RPC normaliza su código a la máscara del plan. Lo que se crea como
101.002.000puede quedar guardado como101.02.00. Las cuentas se resuelven por identificador, jamás por código; el código sugerido de la semilla es solo una pista para el asistente. - Borrar un asiento en borrador exige blanquear su
nameantes. El candado de la cadena de secuencia rechaza borrar un asiento conposted_beforeque no sea el último. El orden obligatorio esbutton_draft→ escribirname = '/'→ borrar. Ynamesolo se puede escribir en borrador. - Caja y Bancos comparten tipo de cuenta en el plan mexicano. Lo único que distingue al banco de verdad es que un diario de tipo
banklo declara suyo: esa es la definición única del módulo (cuentas_reservadas_banco). La transitoria del extracto no está vetada, porque es precisamente el destino legítimo de una póliza de depósito.
D. Qué NO hace este producto, a propósito
- No timbra ni cancela CFDI. Eso es la localización.
- No concilia el banco por extracto. Lo habilita, dejando saldos que sí se pueden amarrar.
- No declara impuestos ni decide la deducibilidad de un incobrable.
- No decide por el contador. Las claves 12, 13, 14, 23, 24 y 27 se separan, se marcan y se escalan; no se contabilizan solas. Las claves 15 y 25 mandan explícitamente a emitir nota de crédito.
- No opina sobre qué rol cumple cada cuenta del catálogo del cliente, sobre las PUE abiertas de ejercicios anteriores, ni sobre si hay que reenviar la balanza.
- No fija plazos fiscales. Toda regla que dependa de la Resolución Miscelánea —plazo del PUE, plazo del REP, plazo de cancelación— está marcada en la documentación, porque la RMF cambia cada año.
Documentos hermanos
| Documento | Para qué |
|---|---|
90_docs/MANUAL_USUARIO.md | Las siete tareas del día a día, paso a paso |
90_docs/MANUAL_IMPLANTACION.md | El runbook completo de implantación, con scripts y rollback |
90_docs/ESTRATEGIA_FISCAL.md | La fundamentación fiscal larga de cada criterio |
90_docs/GUIA_VIA_B_SAAS_STUDIO.md | Qué hacer cuando el cliente no tiene Odoo.sh |
90_docs/entregables_contador/ | Lo que se le entrega al contador externo cada mes |
90_docs/AUDITORIA_MODULO_2026-08-11.md | La auditoría que originó la versión 19.0.1.1.0 |
00_estrategia/SPEC_MODULO.md | El contrato técnico: modelos, campos, métodos, vistas |
00_estrategia/ESTRATEGIA_MAESTRA.md | El producto como oferta comercial y su pricing |
Cómo se mantiene este manual
Cada implantación que descubra un caso que aquí no esté descrito vuelve a este documento y a ESTRATEGIA_FISCAL.md. Un manual que no crece con el producto se convierte en folclore: alguien lo cita, ya no coincide con el código, y a partir de ahí nadie vuelve a confiar en él.
Tres reglas para editarlo:
- Nada se afirma sobre el módulo sin haberlo verificado en el código. Los nombres de campos,
métodos y estados de este manual son los reales; si el código cambia, cambia el manual en el mismo movimiento.
- Lo que no se pudo probar se declara. "No verificado en instancia viva" es una frase legítima;
inventar una certeza no lo es.
- Los casos nuevos entran con su número. Cuánto costó, cuántas facturas, qué cuenta. Este
producto existe porque alguien midió que 45 de 480 facturas estaban mal ruteadas; las cifras son lo que convierte una opinión en un criterio.
Grupo Veniu · VSolutions — Integrador de mundos. veniu_polizas_cobro 19.0.1.1.0 · OPL-1 · Este manual acompaña al módulo, no lo sustituye.