Los 13 formularios del sistema tienen noValidate, que desactiva la validación del navegador (required, min, type="email", type="url"), y ningún handler valida a mano. El API tampoco valida (#4), así que cualquier dato llega tal cual a la base.
Formularios afectados: admin/clientes, admin/inventario (lista, [id] movimiento y conteo), admin/inventario/categorias, admin/inventario/productos (producto y precio), admin/proveedores, admin/usuarios, login/password-form y pos/cash-register-control (abrir caja, movimiento, cierre).
Entorno
local
Módulos
Web / frontend, Productos / precios / categorías, Usuarios, Clientes, Proveedores, Inventario / almacenes, Caja / sesiones de caja
Severidad
Molesta; hay workaround
Pasos para reproducir
Abrir Inventario → Productos → Nuevo producto.
Dejar nombre y SKU vacíos y guardar.
Abrir Precio de un producto y capturar -50 como precio de venta.
Abrir Usuarios → Nuevo usuario con email abc.
En el POS, abrir caja con el monto vacío.
En Inventario → [inventario] → Conteo, capturar -3 o texto.
Esperado
Cada formulario rechaza los datos inválidos con un mensaje junto al campo, antes de enviarlos al API.
Actual
Se guarda un producto con nombre y SKU vacíos.
Se guarda un precio negativo. Con el precio vacío se envía "" a una columna Decimal y el API responde 500.
Se guarda un usuario con email inválido o vacío.
La caja se abre con monto vacío o negativo.
El conteo físico acepta negativos y texto (inventario/[id]/page.tsx:345, el input no tiene type ni min).
Además faltan reglas de negocio:
Una salida de efectivo puede ser mayor que el efectivo esperado y dejar la caja en negativo (cash-register-control.tsx).
Se puede fijar un precio de venta menor al costo sin advertencia.
El RFC (taxId) de clientes y proveedores no tiene validación de formato.
Y faltan campos que sí existen en el contrato: Supplier.phone, email, address y taxId, Inventory.address y Product.unit no se pueden capturar desde la UI.
Evidencia
Encontrado en auditoría de código; no reproducido en ejecución.
$ grep -rn "noValidate" apps/web/src | wc -l
13
// productos/page.tsx — sin validación antes de enviar
const name= String(formData.get("name") ?? "").trim();
const sku= String(formData.get("sku") ?? "").trim();
...
await createProduct.mutateAsync(body);
Arreglo sugerido: un esquema por formulario (por ejemplo con zod) compartido con el API (#4), con errores por campo. O, como mínimo inmediato, quitar noValidate para recuperar la validación nativa. Workaround: capturar con cuidado; no hay protección hoy.
¿Está también en producción?
Sí — este issue no basta; hay que abrir un Incidente / hotfix
No — solo local / staging
No sé
Antes de abrir
Busqué un issue duplicado
### Qué pasa
Los 13 formularios del sistema tienen `noValidate`, que desactiva la validación del navegador (`required`, `min`, `type="email"`, `type="url"`), y ningún handler valida a mano. El API tampoco valida (#4), así que cualquier dato llega tal cual a la base.
Formularios afectados: `admin/clientes`, `admin/inventario` (lista, `[id]` movimiento y conteo), `admin/inventario/categorias`, `admin/inventario/productos` (producto y precio), `admin/proveedores`, `admin/usuarios`, `login/password-form` y `pos/cash-register-control` (abrir caja, movimiento, cierre).
### Entorno
local
### Módulos
Web / frontend, Productos / precios / categorías, Usuarios, Clientes, Proveedores, Inventario / almacenes, Caja / sesiones de caja
### Severidad
Molesta; hay workaround
### Pasos para reproducir
1. Abrir **Inventario → Productos → Nuevo producto**.
2. Dejar nombre y SKU vacíos y guardar.
3. Abrir **Precio** de un producto y capturar `-50` como precio de venta.
4. Abrir **Usuarios → Nuevo usuario** con email `abc`.
5. En el POS, abrir caja con el monto vacío.
6. En **Inventario → [inventario] → Conteo**, capturar `-3` o texto.
### Esperado
Cada formulario rechaza los datos inválidos con un mensaje junto al campo, antes de enviarlos al API.
### Actual
- Se guarda un producto con nombre y SKU vacíos.
- Se guarda un precio negativo. Con el precio vacío se envía `""` a una columna `Decimal` y el API responde `500`.
- Se guarda un usuario con email inválido o vacío.
- La caja se abre con monto vacío o negativo.
- El conteo físico acepta negativos y texto (`inventario/[id]/page.tsx:345`, el input no tiene `type` ni `min`).
Además faltan reglas de negocio:
- Una salida de efectivo puede ser mayor que el efectivo esperado y dejar la caja en negativo (`cash-register-control.tsx`).
- Se puede fijar un precio de venta menor al costo sin advertencia.
- El RFC (`taxId`) de clientes y proveedores no tiene validación de formato.
Y faltan campos que sí existen en el contrato: `Supplier.phone`, `email`, `address` y `taxId`, `Inventory.address` y `Product.unit` no se pueden capturar desde la UI.
### Evidencia
```shell
Encontrado en auditoría de código; no reproducido en ejecución.
$ grep -rn "noValidate" apps/web/src | wc -l
13
// productos/page.tsx — sin validación antes de enviar
const name = String(formData.get("name") ?? "").trim();
const sku = String(formData.get("sku") ?? "").trim();
...
await createProduct.mutateAsync(body);
```
Arreglo sugerido: un esquema por formulario (por ejemplo con `zod`) compartido con el API (#4), con errores por campo. O, como mínimo inmediato, quitar `noValidate` para recuperar la validación nativa. Workaround: capturar con cuidado; no hay protección hoy.
### ¿Está también en producción?
- [ ] Sí — este issue no basta; hay que abrir un Incidente / hotfix
- [ ] No — solo local / staging
- [x] No sé
### Antes de abrir
- [x] Busqué un issue duplicado
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Qué pasa
Los 13 formularios del sistema tienen
noValidate, que desactiva la validación del navegador (required,min,type="email",type="url"), y ningún handler valida a mano. El API tampoco valida (#4), así que cualquier dato llega tal cual a la base.Formularios afectados:
admin/clientes,admin/inventario(lista,[id]movimiento y conteo),admin/inventario/categorias,admin/inventario/productos(producto y precio),admin/proveedores,admin/usuarios,login/password-formypos/cash-register-control(abrir caja, movimiento, cierre).Entorno
local
Módulos
Web / frontend, Productos / precios / categorías, Usuarios, Clientes, Proveedores, Inventario / almacenes, Caja / sesiones de caja
Severidad
Molesta; hay workaround
Pasos para reproducir
-50como precio de venta.abc.-3o texto.Esperado
Cada formulario rechaza los datos inválidos con un mensaje junto al campo, antes de enviarlos al API.
Actual
""a una columnaDecimaly el API responde500.inventario/[id]/page.tsx:345, el input no tienetypenimin).Además faltan reglas de negocio:
cash-register-control.tsx).taxId) de clientes y proveedores no tiene validación de formato.Y faltan campos que sí existen en el contrato:
Supplier.phone,email,addressytaxId,Inventory.addressyProduct.unitno se pueden capturar desde la UI.Evidencia
Arreglo sugerido: un esquema por formulario (por ejemplo con
zod) compartido con el API (#4), con errores por campo. O, como mínimo inmediato, quitarnoValidatepara recuperar la validación nativa. Workaround: capturar con cuidado; no hay protección hoy.¿Está también en producción?
Antes de abrir