# Informe Técnico de Revisión — Sistema de Gestión de Inventario, Ventas y Control Financiero Multimoneda ## 1. Resumen ejecutivo La propuesta original está bien pensada conceptualmente: identifica correctamente que el problema central no es "un inventario", sino un sistema de **contabilidad gerencial multimoneda con trazabilidad de costos**. El principio de usar USDT como moneda contable, congelar tasas por transacción y separar capital/ingreso/utilidad/retiro es sólido y es exactamente lo que evita los errores típicos de negocios que operan en economías con doble tipo de cambio. Dicho esto, el documento es una **especificación funcional/conceptual**, no un diseño técnico ejecutable. Faltan decisiones críticas de ingeniería que determinan si el sistema va a ser mantenible a los 12–24 meses: modelado de dinero, particionamiento de tablas de alto volumen (movimientos de inventario, tasas), estrategia de concurrencia, versión de FIFO por variante vs. por lote agregado, testing, CI/CD y separación entre "MVP" real y "wishlist". A continuación el análisis por capa, con lo que sí está bien resuelto y lo que necesita definirse antes de escribir código. --- ## 2. Infraestructura ### 2.1 Lo que está bien planteado - Monolito modular en lugar de microservicios: correcto para el tamaño del negocio. Microservicios aquí solo añadirían complejidad operativa sin beneficio real. - Separación lógica por dominios (`Inventory`, `Sales`, `Finance`, `ExchangeRates`, `Partners`) es la base correcta para, si algún día se necesita, extraer servicios. - Uso de colas y scheduler para actualización de tasas es apropiado. ### 2.2 Lo que falta definir - **Entornos**: no se menciona staging. Con lógica financiera (tasas, utilidades, distribución de socios) es indispensable tener un entorno de staging con datos realistas antes de tocar producción. - **Contenedores**: el documento asume Linux + Nginx + PHP-FPM "a pelo". Se recomienda Docker/Docker Compose desde el día uno (incluso sin Kubernetes) para que el entorno de desarrollo sea reproducible y el despliegue sea determinista. - **CI/CD**: no aparece en ninguna sección. Para un sistema donde un bug en `PricingService` o en el cálculo FIFO puede generar pérdidas reales, se necesita pipeline con tests automáticos antes de cada despliegue (GitHub Actions es suficiente). - **Redis**: se menciona pero no se define su uso. Debería usarse explícitamente para: cache de tasas vigentes, colas (`Laravel Queue`), y rate-limiting de las consultas a fuentes externas de tasas. - **Backups**: la política (7 diarios / 4 semanales / 3 mensuales) es razonable, pero falta el punto más importante: **pruebas de restauración periódicas**. Un backup no probado no es un backup. - **Secretos**: no se menciona gestión de credenciales (API keys de fuentes de tasas, credenciales de BD). Usar `.env` + un vault mínimo (o al menos variables de entorno gestionadas por el proveedor) desde el inicio. ### 2.3 Sugerencia de infraestructura mínima viable ``` Producción: - 1 VPS/VM para app (Nginx + PHP-FPM + Laravel) - 1 instancia PostgreSQL gestionada (con réplica de lectura si el presupuesto lo permite) - 1 instancia Redis - Backups automatizados fuera del servidor (S3-compatible) - Monitoreo básico: Laravel Telescope en staging, Sentry en producción ``` --- ## 3. Desarrollo / arquitectura de aplicación ### 3.1 Fortalezas - La idea de no poner lógica en controladores y usar `Services`/`Actions` es correcta. - Reconocer que las tasas deben "congelarse" dentro de la transacción es el punto de diseño más importante de todo el documento — hay que protegerlo con tests, no solo con buenas intenciones. ### 3.2 Riesgos de diseño que hay que resolver antes de codificar **a) FIFO real vs. FIFO declarado** El documento describe FIFO conceptualmente pero no resuelve el modelo de datos que lo soporta. FIFO exige que cada `sale_item` pueda quedar vinculado a **uno o más lotes de origen** (una venta de 120 unidades puede consumir dos lotes distintos, como el propio ejemplo lo muestra). Esto requiere una tabla intermedia: ``` sale_item_lot_consumptions sale_item_id inventory_lot_id quantity unit_cost_usdt (snapshot, no referencia al lote actual) ``` Sin esta tabla, "costo real vendido" será una aproximación, no un dato auditable. **b) Snapshot de tasas y costos, no solo referencia por FK** El documento dice correctamente "no recalcular históricamente", pero para que esto funcione en la práctica hay que **desnormalizar deliberadamente**: cada venta, compra, gasto y conversión debe guardar los valores numéricos usados (tasa BCV, tasa USDT, costo unitario) como columnas propias, no solo un `exchange_rate_id`. Si mañana se corrige un registro de tasa por error de captura, ninguna transacción histórica debe moverse. **c) Concurrencia sobre inventario** Con ventas simultáneas desde Venezuela y Colombia, hace falta bloqueo pesimista (`SELECT ... FOR UPDATE`) o control optimista con versión en la fila de stock por variante/ubicación, para evitar sobreventa. El documento menciona "impedir vender stock inexistente" como regla de integridad, pero eso se implementa a nivel de transacción de aplicación + constraint, no solo constraint. **d) Testing** No se menciona una sola vez. Dado que este sistema hace cálculos financieros (margen, utilidad, distribución de socios, diferencia cambiaria), se necesita: - Tests unitarios de `PricingService`, `ProfitService`, `ExchangeRateService` (estos son los que más van a cambiar y más daño hacen si fallan). - Tests de integración para el flujo completo de venta (transacción atómica con rollback). - Al menos un test de "congelamiento de tasa" que falle si alguien intenta recalcular una venta histórica con tasa actual. **e) Idempotencia en jobs de tasas** El scheduler que actualiza tasas cada 30–60 min necesita ser idempotente y tolerante a fallos de la fuente externa (reintentos con backoff, alerta si la fuente falla más de N veces seguidas, y un valor "última tasa válida" para no dejar el sistema sin tasa de referencia). ### 3.3 Servicios de dominio — ajuste sugerido La lista propuesta está bien, pero conviene separar explícitamente lectura de escritura en los servicios más sensibles: ``` PricingService → cálculo (puro, sin efectos secundarios) SalesService → orquestación + transacción InventoryValuationService → FIFO / costo real (debe ser el único punto que calcula costo de venta) ExchangeRateService → ingestión + snapshot, nunca recalcula histórico ``` Aislar `PricingService` como función pura (sin acceso a BD) facilita muchísimo el testing. --- ## 4. Base de datos ### 4.1 Lo correcto - Uso de `numeric`/`decimal` en vez de `float` para dinero: imprescindible, bien señalado. - Modelo de atributos flexibles (`attributes`/`attribute_values`/`product_variants`) es el patrón EAV correcto para productos con variantes heterogéneas (ropa vs. bisutería). - Kardex (`inventory_movements`) en vez de solo actualizar `stock`: correcto y necesario para auditoría. ### 4.2 Ajustes recomendados al modelo propuesto 1. **Volumen de `inventory_movements` y `exchange_rates`**: son las tablas de mayor crecimiento. Desde el diseño inicial conviene: - Índices compuestos por `(product_variant_id, location_id, created_at)`. - Particionamiento por rango de fecha (PostgreSQL native partitioning) si se espera más de un par de años de operación continua, para que los reportes no degraden con el tiempo. 2. **Precisión numérica diferenciada por moneda** (el documento lo menciona pero sin definir valores): ```sql -- USDT / tasas numeric(18,6) -- Bs (montos altos, sin decimales relevantes en la práctica) numeric(18,2) -- COP (igual, montos altos) numeric(18,2) ``` 3. **Constraint de no-negatividad de stock**: agregar `CHECK (quantity >= 0)` a nivel de tabla de stock agregada, además de la lógica de aplicación — es la última línea de defensa contra sobreventa por condición de carrera. 4. **Unicidad de SKU**: `UNIQUE` a nivel de base de datos sobre `product_variants.sku`, no solo validación en Laravel. 5. **Soft delete / estado `cancelled`**: correcto como está planteado (no eliminar transacciones financieras). Sugerencia adicional: usar un estado explícito por tabla (`sales.status`, `purchases.status`) en vez de un flag booleano genérico, porque una venta cancelada necesita disparar reversión de inventario, mientras que un gasto cancelado no. 6. **`profit_distributions` con snapshot del % de socio**: el porcentaje de cada socio puede cambiar con el tiempo (nuevos aportes). La distribución histórica debe guardar el porcentaje **vigente en el momento del cálculo**, no referenciar el porcentaje actual del socio. 7. **Falta explícita en el modelo**: una tabla `stock_summary` (o vista materializada) por `product_variant_id + location_id` que se mantenga como saldo corriente, alimentada por `inventory_movements`. Sin esto, cada consulta de "cuánto stock tengo" tendría que sumar el kardex completo, lo cual no escala. ### 4.3 Diagrama de relación ajustado (resumen) ``` inventory_lots ──< sale_item_lot_consumptions >── sale_items │ └── snapshot: unit_cost_usdt, purchase_id, exchange_rate usado en la compra exchange_rates (histórico, inmutable) │ └── se COPIA (no se referencia únicamente) a: sales, purchases, expenses, conversions ``` --- ## 5. Seguridad y auditoría - El esquema de roles (Administrador / Socio / Vendedor / Almacén) es razonable como punto de partida. Sugerencia: usar un paquete de permisos granular (Spatie Laravel-Permission) en lugar de roles hardcodeados, para poder ajustar accesos sin desplegar código. - La auditoría (`audit_logs`) debe implementarse con un *observer* genérico de Eloquent sobre los modelos sensibles, no con llamadas manuales dispersas en cada servicio — de lo contrario es fácil olvidar registrar un cambio. - Falta mencionar autenticación de dos factores para roles con acceso a capital/utilidades — dado que se manejan fondos reales en USDT, es una medida barata con alto valor. --- ## 6. Lo que el documento no menciona y sí debería considerarse | Tema | Por qué importa | |---|---| | Testing automatizado | Sin esto, cualquier cambio en `PricingService` es un riesgo financiero directo | | CI/CD | Evita desplegar cálculos financieros rotos a producción | | Idempotencia de jobs de tasas | Evita duplicar o corromper el histórico de tasas | | Particionamiento de tablas de alto volumen | Sin esto, reportes e inventario se vuelven lentos en 12–18 meses | | Vista/tabla de saldo corriente de stock | Sin esto, "cuánto tengo" se vuelve una consulta cara | | Manejo de fallos de la fuente de tasas externas | El sistema no puede quedar "ciego" si la fuente de BCV/USDT cae | | Política de redondeo consistente | Con tres monedas y conversiones encadenadas, un redondeo distinto en cada capa genera descuadres acumulados — definir una sola regla (ej. redondeo bancario a 2 decimales en moneda final, 6 en USDT) | | Doble entrada contable (partida doble) | No es obligatorio para el MVP, pero si el negocio crece, migrar `cash_movements`/`profit_distributions` hacia un modelo de partida doble evitará descuadres de caja difíciles de rastrear | --- ## 7. Priorización sugerida (ajuste al roadmap del documento original) El documento ya propone 6 fases, que en general están bien secuenciadas. El ajuste que recomiendo es **mover testing y el modelo FIFO con `sale_item_lot_consumptions` a la Fase 1**, no dejarlos implícitos: 1. **Fase 0 (nueva, antes de todo)**: infraestructura base con Docker, staging, CI con tests, y decisión final de precisión numérica/redondeo. Esto evita rehacer migraciones después. 2. **Fase 1 — Núcleo**: igual a la propuesta, agregando `sale_item_lot_consumptions` y `stock_summary` desde el inicio. 3. **Fase 2 — Ventas**: igual, con tests de concurrencia sobre inventario incluidos como criterio de "hecho". 4. **Fase 3 — Finanzas**: igual, con foco especial en que ningún cálculo de utilidad recalcule tasas históricas (test explícito para esto). 5. **Fase 4 — Sociedad**: igual, con snapshot de porcentaje de socio en cada distribución. 6. **Fase 5 y 6**: sin cambios respecto al documento original. --- ## 8. Conclusión El diseño conceptual es sólido y muestra comprensión real del problema de negocio (doble tipo de cambio, operación en dos países, necesidad de separar capital/utilidad). El riesgo no está en la visión general sino en los detalles de implementación que hoy quedan implícitos: modelo de consumo de lotes para FIFO, snapshot explícito de tasas y costos en cada transacción, control de concurrencia sobre inventario, y ausencia total de estrategia de testing/CI. Ninguno de estos puntos invalida la arquitectura propuesta (Laravel + PostgreSQL + Livewire + Redis sigue siendo la elección correcta para este tamaño de negocio); simplemente son las piezas que separan una "buena idea documentada" de un sistema que va a manejar dinero real de forma confiable durante años.