Contabilidad
Add-on de contabilidad: plan de cuentas, asientos contables, mayor general, balanza de comprobación, estados financieros, cierre mensual y reportes fiscales DGII (606/607).
Organización del menú
Cuando el addon Contabilidad está activo, su menú lateral se agrupa en sub-secciones:
- Cobros y Pagos — Facturas, Gastos, CxC, CxP, Retenciones. Facturas y Gastos son los mismos módulos del CRM (no son una copia): cuando activas Contabilidad, dejan de aparecer en el menú "Comercial" y aparecen acá para tener todo lo financiero junto. Al desactivar el addon, vuelven a su lugar original.
- Libro Mayor — Catálogo, Mapeo, Asientos, Mayor, Balanza.
- Estados Financieros — Estado de Resultados, Balance General.
- Reportes DGII — 606 / 607 (608 / IT-1 / IR-17 en roadmap).
- Operación — Cierre, Historial, Config.
Cómo usarlo
- Facturas — emisión y gestión de facturas a clientes. Misma vista que la del CRM core; la tenés acá para que viva al lado del libro mayor.
- Gastos — registro de gastos del negocio. Misma vista que la del CRM core.
- Catálogo — revisa y personaliza el plan de cuentas del tenant. Cada cuenta tiene un rol contable derivado automáticamente de la estructura: Control (padre que agrupa saldos), Auxiliar (posteable con desglose) o Detalle (hoja al máximo desglose). Ver sección Roles del catálogo más abajo.
- Mapeo — configura qué cuentas usa cada tipo de transacción (venta, ITBIS, pago a proveedor, etc.). Sin esto, el auto-posteo no funciona.
- Asientos — registra asientos manuales o revisa los automáticos que generan otros módulos (Facturas, Gastos, POS).
- Mayor / Balanza / Estado de Resultados / Balance General — vistas de consulta del saldo contable.
- Cierre — cierra un periodo contable cuando termine el mes. Una vez cerrado, no se pueden registrar asientos con fecha en ese mes sin reapertura formal.
- 606/607 — genera los reportes fiscales para la DGII en formato TXT.
Reglas del sistema
- Inmutabilidad post-posteo. Un asiento contabilizado (status
posted) es inamovible en montos y fechas. Para corregir montos: anúlalo (void) o emite una reversión. Para corregir solo el mapeo de cuenta (cuando un asiento posteó a la cuenta equivocada): usa Reasignar cuenta en la línea (ver más abajo). Sigue restringido al mismo rol DGII. - Partida doble estricta. Todo asiento debe cumplir
Débito = Crédito. El sistema bloquea asientos descuadrados al nivel de base de datos. - Idempotencia. Si un documento fuente (factura, gasto) ya tiene asiento registrado, un segundo intento se ignora. No hay duplicación silenciosa.
- Control de periodos. Asientos con fecha dentro de un periodo cerrado son rechazados. No se aceptan fechas futuras (> hoy + 1 día).
- Integridad del catálogo. Cuentas padre no reciben asientos (postea en su hija). Cuentas con movimientos históricos no se pueden eliminar, solo inactivar.
Roles del catálogo — Control / Auxiliar / Detalle
El plan de cuentas estándar RD se estructura en 5 niveles técnicos (Clase, Grupo, Cuenta, Subcuenta, Detalle) pero el contador suele pensar en 3 roles funcionales. La columna Rol del catálogo muestra cuál aplica a cada fila, derivado automáticamente:
- Control (badge ámbar) — Cuenta padre que tiene subcuentas. Su saldo es la suma de sus hijas. No recibe asientos directos — el sistema rechaza cualquier intento de postear sobre ella (trigger de base de datos). Cuando agregas una primera subcuenta a una cuenta antes "auxiliar", el rol pasa a control automáticamente; cuando eliminas todas las hijas, vuelve a ser posteable.
- Auxiliar (badge azul) — Cuenta posteable de mayor general (típicamente nivel Clase, Grupo, Cuenta o Subcuenta) que todavía permite más desglose. Recibe asientos y al mismo tiempo puede tener subcuentas si las creas más adelante.
- Detalle (badge verde) — Cuenta hoja al máximo nivel de desglose técnico (nivel 5, 8 dígitos). Es la línea más granular que puede recibir asientos. No admite más subcuentas.
Por qué importa: el contador identifica de un vistazo dónde puede postear (auxiliar/detalle) y dónde no (control), sin tener que abrir el árbol. Los reportes (Mayor, Balanza, Estado de Resultados) usan la misma jerarquía: el saldo del control siempre cuadra con la suma de sus auxiliares y detalles.
Recalculo automático: si conviertes una Auxiliar en padre agregándole subcuentas, su rol cambia a Control y deja de aceptar asientos nuevos (los asientos históricos sobre ella siguen visibles en el Mayor, no se pierden — pero ya no se pueden agregar más sobre la cuenta padre; deben ir sobre las hijas). El sistema lo hace solo, no requiere intervención manual.
Validación del formulario de asientos
El form de Nuevo Asiento Manual valida en tiempo real antes de habilitar los botones Guardar Borrador y Contabilizar. Si algún punto impide guardar, se muestra un panel rojo con la lista de correcciones; los botones quedan deshabilitados hasta resolverlos.
Puntos que bloquean el guardado:
- Balance. Débitos deben igualar a créditos (tolerancia de centavo).
- Total mayor que cero. No se permite un asiento con todos los montos en 0.
- Mínimo 2 partidas. Cada asiento requiere al menos 2 líneas con cuenta y monto.
- Una línea, una cara. No se puede tener débito y crédito en la misma línea.
- Línea con monto sin cuenta. Si ingresas un monto, hay que elegir la cuenta.
- Cuenta posteable. Debe existir en el catálogo y ser hoja (no control).
- Fecha y descripción. Ambas requeridas.
- Período abierto. Si la fecha cae en un período cerrado, se bloquea el guardado (mismo criterio que el servidor).
Advertencias no bloqueantes (panel ambar):
- Cuenta sin monto. Línea con cuenta seleccionada pero sin débito ni crédito — la vista asume que aún estás tipeando.
Reasignar cuenta sobre asiento posted
Cuando descubres que un asiento ya contabilizado (typically auto-generado desde una factura o un gasto) posteó a la cuenta contable equivocada, no hace falta anularlo y emitir reversión ni nota de crédito si solo cambia la cuenta (no los montos).
En vista Asientos, expande el asiento → cada línea muestra el botón Reasignar (icono ⇄). Se abre un modal con:
- La cuenta actual y los montos (que no cambian).
- Un select de cuentas destino válidas — filtrado al mismo rol DGII que la cuenta original (ingreso↔ingreso, ITBIS↔ITBIS, CxC↔CxC, NULL↔NULL). Si no hay otras cuentas del mismo rol, el select aparece vacío y el botón Reasignar queda deshabilitado.
- Un textarea de motivo (mínimo 5 caracteres) que queda persistido en la bitácora del asiento.
El botón no aparece para:
- Asientos en borrador (edítalos directamente).
- Asientos
cierre,aperturaoreversal(estructurales). - Asientos que ya tienen una reversión emitida (la reversión duplica las líneas; remapear desincronizaría el espejo).
- Asientos cuyo
entry_datecae en un período contable cerrado.
Cuándo NO usar Reasignar y sí usar Nota de Crédito / reversión:
- Cuando el monto está mal (un cero de más, ITBIS no calculado, etc.).
- Cuando la corrección cruza el rol DGII — ej. trasladar un ingreso a una cuenta de ITBIS, o un gasto a CxP. La RPC del servidor rechaza estos casos: el snapshot DGII (RNC/NCF/tipo de bienes) está anclado al rol original, y cambiarlo en frío rompería los reportes 606/607. La regla del contador en estos casos es Nota de Crédito.
- Cuando el documento fuente (la factura, el gasto) está mal en su totalidad. El asiento es el reflejo; corrige primero el documento o emítelo de nuevo.
Cada reasignación queda registrada en Historial del asiento como acción account_remap, con la cuenta vieja, la nueva, la razón, el usuario y el timestamp.
Modos de contabilización
Configurable en Config → Modo de contabilización:
- Auto — eventos de otros módulos generan y contabilizan asientos inmediatamente.
- Semi — se generan como borrador; requieren revisión manual antes de contabilizar.
- Manual — solo asientos creados explícitamente desde la UI.
Cuentas del Sistema (slugs)
El sistema opera con roles contables internos (slugs) que se mapean a tu plan de cuentas. Esto permite que el código no dependa de códigos de cuenta específicos — si tu chart usa 300101 en lugar de 3901, solo cambias el mapping en Config → Cuentas del Sistema y el resto sigue funcionando.
Slugs gestionados:
- Resultado del ejercicio — cuenta donde se netean ingresos/costos/gastos al cierre del período.
- ITBIS por pagar (ventas) — pasivo generado al emitir facturas; alimenta 607 e IT-1.
- ITBIS adelantado (compras) — activo generado al recibir compras; alimenta 606 e IT-1. Puede tener múltiples códigos (control + hojas).
- Retención ITBIS a favor — ITBIS retenido por clientes grandes.
- Retención ISR a favor — ISR retenido por clientes sobre servicios.
- CxC — Cuenta control — cuenta por cobrar clientes (sub-ledger).
- CxP — Cuenta control — cuenta por pagar proveedores (sub-ledger).
Si algún slug queda sin configurar, el posteo automático se guardará en borrador (Safe-Fail) hasta que lo completes. El botón Restaurar defaults DR reseta a los valores estándar de República Dominicana.
Permisos
- Admin / Accountant — acceso completo (crear, postear, anular, cerrar periodo, reportes, configurar cuentas del sistema).
- Sales / Viewer — solo lectura de vistas agregadas (no ven asientos de detalle).
Preguntas frecuentes
¿Cómo corrijo un asiento ya contabilizado?
No se edita. Anúlalo (botón "Anular") y registra un asiento de reversión o uno nuevo correcto. La historia del error queda visible para auditoría.
¿Por qué no me deja guardar con fecha en diciembre?
Ese periodo ya fue cerrado. Pide a un admin que lo reabra formalmente si realmente es necesario.
¿Qué es una "cuenta de control"?
Una cuenta padre cuyo saldo es la suma de sus hijas. No recibe asientos directamente; eso se hace en las cuentas auxiliares o de detalle (hijas). Ver la sección Roles del catálogo para la distinción completa Control / Auxiliar / Detalle.
Cierre de período — validación por-asiento (2026-04-21)
Antes de cerrar un período, el sistema corre una lista de verificación (preflight) que ahora incluye un check por cada asiento individual:
- Cada asiento individual está balanceado (DR = CR) — verifica que cada asiento posteado en el período tenga sus propios débitos y créditos cuadrados dentro de 1 centavo de tolerancia.
Por qué importa: el chequeo global del balance de comprobación podría cuadrar aunque dos asientos estén individualmente rotos (por ejemplo, uno con +$100 de exceso de débito y otro con −$100 de exceso de crédito se compensan). El trigger de base de datos chk_balanced evita esto al momento de insertar, pero asientos legados o data tocada manualmente pudieron haber escapado al trigger.
Si el check falla, el preflight lista hasta 5 asientos desbalanceados con:
- Número de asiento + fecha.
- DR total / CR total / diferencia con signo (
+= exceso de débito,−= exceso de crédito).
El botón Ver en Asientos te lleva al Mayor para inspeccionarlos. Debes revertir y re-registrar cada uno antes de que el período pueda cerrarse.
Retenciones — sub-ledger no integrado al Mayor (2026-04-21)
El módulo Retenciones registra las retenciones ITBIS / ISR que tu empresa practica a proveedores (como agente de retención DGII) y alimenta los reportes 606/607. Sin embargo, esos registros hoy no impactan el Mayor, la Balanza de Comprobación, ni el Balance General.
Qué significa en la práctica: si registras aquí una retención de $5,400 ITBIS pero no creas un asiento contable correspondiente, tu pasivo fiscal en los estados financieros aparecerá en cero aunque realmente debas ese monto a la DGII.
Solución temporal (hasta la Sesión 9 del saneamiento):
Cada vez que registres una retención, crea además un asiento manual en Contabilidad → Asientos → Nuevo manual con este patrón:
| Cuenta | Débito | Crédito |
|---|---|---|
| Cuentas por Pagar (del proveedor) | $retención | |
| Retenciones ITBIS / ISR por Pagar | $retención |
El banner ámbar en la vista Retenciones incluye un botón Crear asiento manual que te lleva directamente al formulario. Recuerda usar la fecha y el monto exactos del registro de retención, con una descripción como "Retención ITBIS 30% s/ factura B0100001234 RNC 101-12345-6".
Cuándo dejará de ser necesario: la Sesión 9 del plan de saneamiento integrará el sub-ledger directamente al Mayor — las retenciones generarán asientos automáticos y este banner desaparecerá.
Estados de carga y errores (nuevos 2026-04-21)
Todas las vistas de Contabilidad ahora comparten el mismo patrón de 3 estados:
- Cargando — spinner con mensaje específico ("Cargando asientos…", "Cargando mayor general…"). Indica que los datos están viajando desde Supabase.
- Vacío — ícono + texto cuando la consulta fue exitosa pero no hay filas (p. ej. cuenta sin movimientos, mes sin ventas).
- Error — cuadro rojo con el mensaje exacto del servidor + botón Reintentar. Haz clic sin recargar la página.
Banner ámbar "datos parciales"
En Asientos y Configuración, si una fuente opcional (configuración del addon, períodos cerrados, etc.) falla por un error de red transitorio, verás un banner ámbar:
⚠ No se pudo cargar (nombre): (motivo). La vista muestra datos parciales.
La vista sigue siendo utilizable — sólo algunas funciones dependientes del dato faltante quedan inhibidas. Ciérralo con la × cuando hayas leído el aviso, o recarga para reintentar todas las fuentes. Protocolo V2 punto 4 (Safe-Fail): preferimos avisarte antes que mostrar ceros engañosos en silencio.
Escalabilidad — carga diferida (nueva 2026-04-22)
Mayor General, Balance de Comprobación y Cierre de Período antes cargaban todas las líneas contables del tenant en cada entrada a la pestaña. En tenants grandes eso se traducía en segundos de espera y uso inútil de red. Ahora:
- Mayor General — al entrar solo carga el catálogo de cuentas; los movimientos se piden al servidor cuando seleccionas una cuenta, paginados (100 por página, botones Anterior / Siguiente). El saldo inicial y los totales del período vienen de un agregado del servidor (
cuentas_ledger_summary) que nunca se trunca; el saldo corrido de cada fila lo calcula Postgres (cuentas_ledger_movements, función de ventana sembrada con el saldo de apertura). Esto corrige el mismo riesgo de truncamiento a 1000 filas que la Balanza: el método anterior descargaba todos los asientos del tenant solo para resolver IDs y se cortaba en 1000 → saldo inicial errado + movimientos faltantes. El Mayor es inherentemente cronológico (el saldo acumulado solo tiene sentido en orden de fecha), por eso no hay orden por columnas ni búsqueda libre: la cuenta + el rango de fechas ya acotan la vista. Cambiar la cuenta, "Desde" o "Hasta" vuelve a consultar desde la página 1. - Balance de Comprobación — la suma por cuenta (
SUMde débitos y créditos) se calcula en el servidor (funcióncuentas_trial_balance), no en el navegador. Esto corrige un riesgo real: el método anterior descargaba todos los asientos del rango y los agregaba localmente, pero el servidor corta cualquier consulta a 1000 filas — un tenant con miles de asientos veía una balanza silenciosamente truncada e incorrecta. Ahora el agrupamiento ocurre en Postgres y nunca se trunca. Cambiar cualquiera de las dos fechas dispara una nueva consulta; "Mostrar cuentas sin movimiento" filtra localmente sin volver a pedir datos. Si hay movimientos posteados en cuentas no clasificadas o inactivas (que no aparecen en la tabla), un aviso amarillo lo indica con el monto, en lugar de ocultarlo. - Asientos (libro diario) — la lista se pagina en el servidor (50 por página, Anterior / Siguiente, orden fecha descendente con desempate determinista por
id). Los filtros tipo y estado y la búsqueda por número de asiento (AS-000123) se aplican en la consulta al servidor, no sobre una página truncada. Los KPIs (contabilizados, borradores, total movimientos, balance inicial) vienen de un agregado del tenant (cuentas_journal_summary) que nunca se trunca. La cadena de reversiones (badge Revertida, botón Revertir) se indexa con una consulta acotada a los asientos de reversión, por lo que el badge de un asiento original sigue siendo correcto aunque su reversión esté en otra página. Igual que el Mayor, no hay orden interactivo por columnas (la lista es una página de un conjunto ya ordenado en el servidor). - Cierre de Período — carga solo el histórico de períodos cuando entras; el pre-flight descarga los asientos del mes cuando haces clic en Verificar. Si cambias de mes y vuelves a verificar, se vuelve a consultar únicamente lo necesario para ese período.
Beneficio: en el tenant de Khaostozen la latencia pasó de 3-5 s a < 500 ms al entrar a Mayor, incluso con cientos de miles de líneas acumuladas.
Registro de auditoría — vista Historial (v1.8.66, 2026-04-22)
Cada cambio de estado de un asiento contable queda registrado automáticamente en la tabla journal_entry_audit_log (trigger trg_je_audit_log, migración 20260422000006). Las 5 acciones trazadas son:
- Creado — al insertar el asiento (borrador o directo).
- Contabilizado — transición
borrador → posted, conposted_byyposted_at. - Anulado — transición
* → voided, convoided_by,voided_atyvoid_reasonobligatorio. - Revertido — al generar un asiento de reversión; se anota contra la fila original (no contra la reversión) con el id/número del nuevo asiento reversor en
context. - Eliminado — al borrar un borrador (solo borradores son eliminables; posteados nunca se borran).
Cómo abrirlo
- Menú lateral Contabilidad → Historial (icono reloj de arena): muestra el mes en curso por defecto.
- Desde Asientos, al expandir el detalle de un asiento, pulsa Ver historial de auditoría de este asiento. La vista Historial se abre pre-filtrada por ese asiento (verás el banner azul "Mostrando solo eventos de …" con botón Quitar filtro).
Filtros disponibles
- Desde / Hasta — rango de fechas (inclusive, hora local).
- Acción — Todas, Creado, Contabilizado, Anulado, Revertido, Eliminado.
- Número de asiento — búsqueda exacta (ej.
AS-000123). Si no existe, la lista queda vacía. - Aplicar ejecuta la consulta; Reiniciar vuelve al mes en curso sin filtros.
KPIs de la página
Se calculan sobre los eventos de la página actual (no del historial total):
- Eventos (página) — filas devueltas.
- Asientos únicos —
entry_iddistintos en la página. - Actores únicos — usuarios distintos que ejecutaron alguna acción.
- Contabilizados / Revertidos / Anulados + Borrados — conteo por acción.
Columnas de la tabla
- Fecha / Hora — timestamp del evento en zona local.
- Asiento — número clicable (
AS-000123); al pulsarlo, se navega a Asientos con ese número en el filtro. El estado actual del asiento aparece como badge debajo. - Acción — pastilla de color por acción (verde Contabilizado, rojo Anulado, naranja Revertido, gris Creado, marrón Eliminado).
- Actor — usuario que ejecutó la acción. Si el trigger no pudo atribuir (p. ej. operación desde service role) aparece
—. - Descripción / Motivo —
void_reasoncuando existe, y la descripción del asiento. Si hubo cambio de estado se muestraprevious_status → new_status. El botón Detalle expande elcontextJSON del trigger (ids reversores, snapshots, etc.).
Paginación
50 eventos por página, ordenados por fecha descendente. Botones Anterior / Siguiente; el botón "Siguiente" se deshabilita cuando no hay más filas.
Consulta directa (alternativa SQL)
Para auditorías puntuales fuera de la UI: SELECT * FROM v_journal_entry_audit_recent WHERE entry_number = 'AS-00000123' ORDER BY actor_at DESC;.
Cuentas por Pagar (CxP)
La vista CxP agrupa tus gastos por proveedor (contacto con categoría Proveedor o equivalente) y calcula el total adeudado a cada uno, junto con una lista de próximos vencimientos y el desglose por categoría.
Qué muestra
- KPIs — Total Gastos, Con Proveedor, Sin Asignar, Próximos a Vencer.
- Saldos por Proveedor — ranking descendente por monto total. Click en una fila abre el Detalle del proveedor con la tabla de gastos ordenados por fecha.
- Por Categoría — barras horizontales con el % del gasto total por categoría.
- Próximos Vencimientos — hasta 8 gastos con
expirationDate >= hoy, ordenados ascendentemente. Los que vencen dentro de 7 días se marcan en rojo (urgente).
Filtros
- Categoría — muestra todas las categorías con al menos un gasto (y el count).
- Proveedor — todos los proveedores vinculados a gastos. Incluye a los proveedores cuyo contacto fue eliminado después (marcados "Contacto eliminado").
Qué hace si borras un contacto (2026-04-23)
Si eliminas un contacto del CRM después de haber registrado gastos a su nombre, la vista no oculta esos gastos. El asiento contable ya existe, tu deuda con ese proveedor es real — esconderla de la UI crearía una desincronización entre la vista y el Mayor General.
En su lugar, verás:
- En Saldos por Proveedor — la fila con un badge ámbar eliminado y color de monto ámbar en lugar del color CxP normal. El nombre cambia a "Contacto eliminado".
- En el filtro Proveedor — la opción aparece como "Contacto eliminado" al final de la lista.
- En el Detalle — el header muestra el badge ámbar y los gastos siguen viéndose tal como se registraron.
Cómo recuperar la visibilidad: vuelve a crear un contacto con el mismo id (raro, requiere soporte), o reasigna los gastos huérfanos a otro contacto vía el módulo Gastos.
Fechas de vencimiento inválidas (2026-04-23)
Si por alguna razón un gasto tiene una expirationDate malformada (null, vacía, con formato incorrecto, o NaN), antes veías textos como "NaN días" en la tarjeta de vencimiento. Ahora el sistema filtra estas fechas del listado de próximos vencimientos y, si se renderiza alguna, muestra "Fecha inválida" en lugar del cálculo erróneo. Reporta estos casos a soporte — suelen venir de importaciones o ediciones manuales fuera de la UI.
Asientos contables generados por CxP
- Al registrar el gasto → asiento automático
expense_created(DR 6xxx categoría / CR 2201 CxP Control). Si la factura trae ITBIS, se genera una línea adicional DR 170101 ITBIS Adelantado. - Al marcar el gasto como pagado → asiento de pago (DR 2201 CxP Control / CR 1102 Banco, o la cuenta de efectivo configurada).
- Si falta data crítica (mapping, slug
CXP_CONTROL, RNC/NCF cuando el gasto tiene e-CF), el asiento se crea en borrador (Safe-Fail) en lugar de postear con ceros.
Todos los asientos respetan el per-line snapshot DGII: la línea base carga rnc_contraparte + ncf_documento + tipo_bienes_servicios, las líneas de ITBIS cargan rnc + ncf, y la línea de control solo rnc + ncf. Esto alimenta directamente el 606 sin joins adicionales.
Bloqueo optimista — transiciones de asientos (v1.8.66, 2026-04-22)
Contabilizar, Anular y Eliminar borrador usan un bloqueo optimista para evitar que dos pestañas (o dos usuarios) pisen el mismo asiento al mismo tiempo. El sistema guarda el updated_at de la fila al momento de leerla y lo manda como condición en la escritura. Si el valor cambió en el servidor, la escritura afecta 0 filas y se aborta.
Qué verás si se detecta una colisión
Un toast ámbar con 12 s de duración:
⚠️ Este borrador fue modificado por otro usuario o en otra pestaña.
Recarga la vista para ver el estado actual antes de volver a contabilizar.
El mensaje cambia ligeramente según la acción (contabilizar / anular / eliminar), pero la resolución es siempre la misma: recarga la vista Asientos y reintenta sobre el estado fresco. Recargar es una decisión consciente del usuario — el sistema no reintenta automáticamente porque el asiento podría haber cambiado en formas que invalidan tu intención (p. ej. alguien más ya lo contabilizó).
Cuándo ocurre típicamente
- Tienes dos pestañas abiertas del CRM mirando la lista de asientos, contabilizas en una y luego intentas contabilizar el mismo asiento en la otra.
- Dos usuarios con rol Accountant abren el mismo borrador al mismo tiempo; el primero en pulsar Contabilizar gana, el segundo recibe el aviso.
- Dejaste una pestaña en segundo plano durante el cambio de período y un admin anuló o reversó el asiento mientras tanto.
Qué acciones NO requieren el bloqueo
- Crear un asiento nuevo — no hay fila previa contra la cual correr la condición.
- Reversar (botón Revertir) — la reversión se ejecuta en la función RPC
reverse_journal_entrycon su propia transacción atómica.
Por qué este diseño (vs. bloquear la fila en el servidor)
Bloqueo pesimista (lock explícito en DB) obligaría a marcar el asiento como "en edición" y liberarlo al salir; una pestaña cerrada abruptamente dejaría asientos bloqueados. El optimista solo detecta la colisión cuando realmente ocurre, es local a la escritura y no requiere coordinación entre pestañas.
e-CF emitidos sin asiento — red de seguridad (2026-06-12)
El asiento de venta de una factura se contabiliza en tu navegador cuando la guardas. Para facturas e-CF (serie E), el e-NCF se asigna recién al emitir a DGII, así que el asiento que se crea al guardar queda en borrador (le falta el e-NCF para poder postearse). Si luego DGII acepta el e-CF pero ese borrador nunca se completa (porque cerraste la pestaña, hubo un corte de red, etc.), tendrías un comprobante fiscal aceptado sin reflejo en el Mayor.
Para evitarlo hay un reconciliador automático en el servidor que corre cada 30 minutos:
- Busca todo e-CF aceptado por DGII cuya factura no tenga asiento posteado.
- Si ya existe el borrador del guardado, le completa el snapshot del e-NCF (ahora conocido) para que puedas postearlo.
- Si no existe ningún asiento, crea uno en borrador con los datos de la factura (CxC / ingreso / ITBIS, resueltos por los slugs configurados), marcado con razón
ecf_accepted_unposted.
El asiento siempre queda en borrador para que un humano lo valide y lo postee — el sistema nunca postea solo un comprobante fiscal. Encuéntralos en Asientos → Pendientes de completar.
Semáforo amarillo en el Dashboard
El Dashboard de Contabilidad muestra un banner amarillo cuando hay e-CF aceptados sin asiento posteado, con el conteo. El banner desaparece cuando todos están contabilizados (cero pendientes). Si ves el banner, ve a Asientos, revisa cada borrador ecf_accepted_unposted, corrige la cuenta de ingreso si la venta fue de servicios (el reconciliador asume productos por defecto), agrega el costo de venta (COGS) si aplica, y postéalo.
Nota para el contador: el reconciliador NO genera el asiento de costo de venta (COGS) — ese es un evento de inventario separado. Complétalo al revisar el borrador si la venta movió inventario físico.