[bug] Los formularios tienen noValidate y no validan nada antes de enviar #21

Open
opened 2026-10-02 19:19:26 +00:00 by Carlos · 0 comments
Member

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

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
Jose was assigned by Carlos 2026-10-02 20:04:30 +00:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RynextTechnologies/PDV#21