Importar un conteo de inventario en Neges
Guía completa para descargar la plantilla, registrar un conteo físico, validar errores, aplicar el inventario de forma atómica y mantener o evolucionar el feature.
El importador convierte un conteo físico realizado en una bodega en movimientos de inventario trazables. La primera versión trabaja con una plantilla CSV generada por Neges, admite hasta 1.000 productos y aplica todos los cambios juntos o ninguno. Esta guía combina instrucciones operativas, explicación de errores y documentación técnica para mantenimiento futuro.
Alcance de la primera versión
| Capacidad | Comportamiento |
|---|---|
| Bodega | Una sola bodega por archivo. |
| Formato | CSV descargado desde Neges; no se aceptan planillas arbitrarias. |
| Tipo de conteo | Absoluto: se informa cuánto existe físicamente, no cuánto sumar o restar. |
| Tamaño | Hasta 1.000 filas de producto por plantilla. |
| Previsualización | Muestra todas las filas contadas, sus diferencias y todos sus errores. |
| Aplicación | Atómica: se aplican todos los cambios o ninguno. |
| Concurrencia | Rechaza el conteo completo si un saldo cambió después de la previsualización. |
| Productos | No crea productos ni variantes. Cada producto debe existir y estar activo. |
| Resultado | Genera movimientos, historial y un comprobante consultable. |
Permisos y requisitos previos
- La empresa correcta está seleccionada en el backoffice.
- El módulo Stock está habilitado para la empresa.
- Existe al menos una bodega activa.
- Los productos que se contarán ya existen y están activos.
- El usuario tiene el permiso stock.import para descargar, previsualizar y aplicar.
- El usuario tiene stock.read para consultar historial y comprobantes.
Los roles base Administrador y Manager incluyen stock.import. Viewer no lo incluye. Un rol personalizado puede recibir o perder el permiso independientemente de stock.adjust, por lo que restringir importaciones masivas no impide mantener ajustes manuales.
Flujo completo de uso
Abre Inventario → Importar conteo
Confirma la empresa activa. En el primer paso selecciona una bodega, el motivo del conteo y, opcionalmente, una nota interna.
Tip: La bodega queda incorporada dentro de cada fila de la plantilla para impedir que un archivo se aplique por error en otra ubicación.Descarga la plantilla oficial
Neges genera un CSV con todos los productos activos, su identificador estable, SKU, nombre y stock esperado en la bodega.
Tip: Si existen más de 1.000 productos activos, esta versión no genera la plantilla. El filtrado por categorías o último conteo queda como evolución futura.Realiza el conteo físico
Completa únicamente counted_on_hand. Escribe la cantidad total encontrada físicamente para cada producto que quieras contar.
Tip: Una celda vacía omite el producto. Un cero explícito significa que el producto fue contado y no se encontró ninguna unidad.Carga el CSV
Selecciona el mismo archivo CSV. El navegador revisa estructura, columnas obligatorias y límite antes de enviarlo a Strapi.
Revisa la previsualización completa
La tabla muestra stock esperado, cantidad contada, diferencia y estado. Si hay errores, ninguna fila puede aplicarse.
Confirma la aplicación
Neges vuelve a leer y bloquear todos los saldos. Si siguen iguales al snapshot, crea los movimientos y actualiza el conteo dentro de una única transacción.
Conserva el comprobante
El resultado muestra identificador del conteo, fecha, usuario, bodega, archivo, motivo, productos contados, movimientos generados y detalle por producto.
Formato de la plantilla CSV
warehouse_document_id,warehouse_code,product_document_id,sku,product_name,expected_on_hand,counted_on_hand
wh_01,CENTRO,prod_01,SKU-001,Café molido 250 g,12,10
wh_01,CENTRO,prod_02,SKU-002,Té negro 20 bolsas,8,8
wh_01,CENTRO,prod_03,SKU-003,Azúcar 1 kg,4,0
wh_01,CENTRO,prod_04,SKU-004,Leche 1 L,15,| Columna | Quién la completa | Uso |
|---|---|---|
| warehouse_document_id | Neges | Identificador estable de la bodega seleccionada. |
| warehouse_code | Neges | Código legible de referencia; no identifica por sí solo la bodega. |
| product_document_id | Neges | Identificador Strapi v5 del producto. Es la clave usada al validar. |
| sku | Neges | Código visible para facilitar el conteo. |
| product_name | Neges | Nombre visible al momento de generar la plantilla. |
| expected_on_hand | Neges | Stock físico registrado al descargar. No debe editarse. |
| counted_on_hand | Usuario | Cantidad física absoluta, entera y mayor o igual a cero. |
Validaciones y cómo corregir errores
| Validación | Qué significa | Cómo resolverla |
|---|---|---|
| Columnas obligatorias | El archivo no conserva el formato oficial. | Descarga una plantilla nueva y copia sólo counted_on_hand. |
| Más de 1.000 filas | El archivo supera el límite de esta versión. | No dividas manualmente una plantilla oficial; espera la versión con filtros o reduce productos activos de forma controlada. |
| Sin filas contadas | Todas las celdas counted_on_hand están vacías. | Registra al menos una cantidad, incluido 0 cuando corresponda. |
| Cantidad inválida | El valor no es entero o es negativo. | Usa enteros desde 0; no uses decimales, texto ni fórmulas. |
| Producto repetido | El mismo product_document_id aparece más de una vez. | Mantén una sola fila. Neges marca todas las apariciones para encontrarlas fácilmente. |
| Producto no encontrado | Fue eliminado, está inactivo o pertenece a otra empresa. | Descarga una plantilla actualizada desde la empresa correcta. |
| Bodega incorrecta | La fila pertenece a otra bodega. | No combines archivos. Descarga la plantilla de la bodega seleccionada. |
| Stock cambió | expected_on_hand ya no coincide con el saldo actual. | Descarga una plantilla nueva y repite o reconcilia el conteo. |
La previsualización persiste todas las líneas contadas y devuelve todos los errores de una vez. No existe “aplicar filas válidas”: una sola fila errónea bloquea el conteo completo.
Qué ocurre con productos reservados
counted_on_hand representa existencia física y siempre debe ser mayor o igual a cero. Las reservas son un concepto separado: quantityAvailable se calcula como quantityOnHand menos quantityReserved.
| Antes del conteo | Conteo físico | Resultado |
|---|---|---|
| On hand 10 · Reservado 3 · Disponible 7 | 8 | On hand 8 · Reservado 3 · Disponible 5 |
| On hand 10 · Reservado 3 · Disponible 7 | 0 | On hand 0 · Reservado 3 · Disponible -3 |
Estados, historial y trazabilidad
| Estado interno | Significado | Modifica stock | Visible en historial |
|---|---|---|---|
| invalid | La previsualización contiene al menos una línea errónea. | No | No |
| ready | Todas las líneas son válidas y existe un snapshot para confirmar. | No | No |
| completed | El journal, las entradas, los saldos y el comprobante fueron confirmados. | Sí | Sí |
El comprobante conserva sourceFilename, sourceChecksum, snapshotAt, motivo, notas, usuario creador, usuario aplicador, fecha de aplicación, bodega, cantidades resumidas y líneas. journalDocumentId conecta el comprobante con el ledger cuando hubo diferencias; un conteo sin diferencias puede completarse sin journal.
Cómo se garantiza la aplicación atómica
La confirmación bloquea primero el registro inventory_count y luego los stock_balances existentes en un orden estable. Después vuelve a comparar quantityOnHand con expectedOnHand. Sólo si todos coinciden crea el inventory_journal, sus inventory_entries, actualiza saldos y marca el conteo completed. Cuando un saldo aún no existe, la restricción única protege su creación concurrente.
Cualquier excepción produce rollback. Esto incluye conflictos, productos que ya no pueden resolverse, errores al crear movimientos y fallos al actualizar un saldo. La restricción única tenant/product/warehouse evita crear dos balances válidos para la misma ubicación.
Arquitectura técnica
| Responsabilidad | Ubicación principal |
|---|---|
| Rutas y pantallas Angular | neges-backoffice/src/app/features/stock/stock.routes.ts y pages/inventory-count-* |
| Parser CSV del navegador | neges-backoffice/src/app/features/stock/inventory-counts.csv.ts |
| Cliente HTTP y modelos | neges-backoffice/src/app/features/stock/data-access/inventory-counts.service.ts e inventory-counts.models.ts |
| Rutas y controlador Strapi | neges-strapi/src/api/inventory-count/routes e inventory-count/controllers |
| Validación y orquestación | neges-strapi/src/api/inventory-count/services |
| Persistencia de cabecera y líneas | inventory-count y inventory-count-line content-types |
| Ledger y actualización de saldos | inventory-journal/services e stock-balance/services |
| Permisos y módulo | neges-strapi/src/utils/rbac/default-rbac.ts y default-tenant-modules.ts |
| Restricción de saldos | database/migrations/2026-07-14-000002-unique-stock-balances.js |
Contrato HTTP y seguridad multi-tenant
GET /api/stock/counts/template?warehouseDocumentId=<documentId> stock.import
POST /api/stock/counts/preview stock.import
POST /api/stock/counts/:documentId/apply stock.import
GET /api/stock/counts?page=1&pageSize=25 stock.read
GET /api/stock/counts/:documentId stock.read
Authorization: Bearer <token>
X-Tenant-Id: <tenant-documentId>Todas las rutas pasan por require-auth, resolve-tenant, require-membership y require-feature. El tenant se obtiene del contexto autenticado; no se confía en un tenant enviado dentro del CSV. Las búsquedas de bodega, producto, conteo y saldo incluyen el tenant activo.
{
"data": {
"warehouseDocumentId": "wh_01",
"reasonCode": "physical_count",
"notes": "Conteo de cierre de mes",
"sourceFilename": "inventario-centro-2026-07-14.csv",
"rows": [
{
"lineNumber": 2,
"warehouseDocumentId": "wh_01",
"productDocumentId": "prod_01",
"productCode": "SKU-001",
"productName": "Café molido 250 g",
"expectedOnHand": "12",
"countedOnHand": "10"
}
]
}
}| Código relevante | HTTP | Uso |
|---|---|---|
| INVENTORY_COUNT_ROW_LIMIT_EXCEEDED | 422 | El archivo supera 1.000 filas. |
| INVENTORY_COUNT_PRODUCT_DUPLICATED | Previsualización inválida | El producto aparece más de una vez. |
| INVENTORY_COUNT_STOCK_CHANGED | 409 | Un saldo cambió antes de aplicar. Incluye todos los conflictos. |
| INVENTORY_COUNT_NOT_READY | 409 | El conteo es inválido, fue aplicado o ya no está disponible. |
| INVENTORY_JOURNAL_ALREADY_POSTED | 409 | Protección de idempotencia: ya existe un journal para esa fuente. |
Modelo de datos y decisiones importantes
| Entidad | Datos esenciales | Propósito |
|---|---|---|
| inventory_count | tenant, warehouse, workflowStatus, reasonCode, checksum, snapshot, contadores, usuarios, journalDocumentId | Cabecera, auditoría y comprobante. |
| inventory_count_line | productDocumentId estable, código/nombre snapshot, expected, counted, delta, estado y errores | Detalle completo incluso cuando una referencia de producto falla. |
| inventory_journal | sourceType inventory-count, sourceId count.documentId, estado posted | Documento contable del ledger de inventario. |
| inventory_entry | producto, bodega, quantityDelta, sourceLineId | Movimiento trazable por cada diferencia no cero. |
| stock_balance | quantityOnHand, quantityReserved, quantityAvailable | Lectura rápida del saldo actual; clave única por tenant/producto/bodega. |
Se usa documentId en contratos públicos de Strapi v5. workflowStatus evita el nombre reservado status. Las líneas guardan nombre y código como snapshot para que un comprobante histórico siga siendo legible aunque el producto cambie posteriormente.
La migración del índice único ignora claves legacy incompletas con relaciones nulas, porque PostgreSQL permite múltiples NULL en una restricción única. Las claves completas duplicadas sí detienen la migración para impedir una consolidación destructiva o una suma incorrecta.
Pruebas y checklist para modificar el código
- Mantener una bodega por archivo o versionar explícitamente el contrato CSV y API.
- Conservar la diferencia entre celda vacía y cero.
- Validar duplicados sobre todas sus apariciones, no sólo desde la segunda.
- No confiar en tenant, producto o bodega enviados por el cliente sin volver a resolverlos.
- No crear productos automáticamente dentro de este flujo.
- Releer y bloquear saldos dentro de la misma transacción usada para aplicar.
- Mantener sourceId/sourceType idempotentes en inventory-journal.
- Actualizar modelos, parser, tablas, comprobante y documentación si cambia una columna CSV.
- Actualizar neges-mcp y MCP-CONTRACT.md si el MCP comienza a consumir estos endpoints o content-types.
- Agregar migraciones reversibles y revisar datos legacy antes de imponer nuevas restricciones.
# neges-backoffice
npm run typecheck
npm run test:ci -- --include='src/app/features/stock/**/*.spec.ts'
npm run build:prod
# neges-strapi
npm run build:server
node --test tests/unit/inventory-count.test.cjs \
tests/unit/inventory-stock-ledger.test.cjs \
tests/unit/tenant-module-catalog.test.cjs \
tests/unit/unique-stock-balances-migration.test.cjs
# neges-mcp
npm run check:contractCómo evolucionar el feature de forma segura
| Necesidad futura | Evolución recomendada |
|---|---|
| Más de 1.000 productos | Generar plantillas por categoría, ubicación o fecha de último conteo, conservando una bodega y un snapshot por archivo. |
| Conteos cíclicos | Guardar alcance/filtro del conteo y priorizar productos nunca contados o con lastCountedAt más antiguo. |
| Archivos grandes | Procesar preview en job durable, pero mantener confirmación transaccional y comprobante único. |
| Productos nuevos | Crear un flujo separado de alta masiva con permiso products.create, validación global y transacción propia; no mezclarlo silenciosamente con el conteo. |
| Variantes | Introducir variantDocumentId como nueva identidad de línea y migrar el contrato de forma versionada. |
| Correcciones parciales | Preferir generar un nuevo archivo sólo con errores corregidos. No debilitar la atomicidad del conteo original. |
| Auditoría avanzada | Agregar exportación del comprobante, hash verificable y referencia al dispositivo o sesión. |
La regla central para cualquier evolución es separar preparación de aplicación: la preparación puede ser asíncrona, filtrada o más flexible; la aplicación debe seguir validando identidad, tenant, snapshot e idempotencia dentro de una transacción.
Resumen operativo
- Descarga una plantilla nueva para la bodega correcta.
- Completa sólo counted_on_hand con cantidades absolutas enteras.
- Deja vacío lo no contado y usa 0 cuando verificaste ausencia física.
- Corrige todos los errores antes de confirmar.
- Si aparece un conflicto, no fuerces el archivo: descarga una plantilla nueva.
- Comprueba el resultado desde Historial de conteos.
¿Te fue útil este contenido?
Tu feedback nos ayuda a mejorar la documentación de Neges.
¿No encuentras lo que buscas?
Conversa con nuestro equipo de soporte en español. Respondemos por WhatsApp, correo o ticket en menos de 24 horas hábiles.