Facturas

ConceptoCant.PrecioImporteAlta
Bugs corregidos (8)
  1. El precio unitario perdía los céntimos.
    Qué fallaba: al dar de alta una línea, el precio se guardaba con parseInt(), de modo que «10,50 €» se convertía en «10 €» y todos los importes quedaban descuadrados.
    Cómo lo detecté: añadí una línea con precio 10.50 y la columna «Precio» mostraba 10,00 €; el subtotal era 10,00 € en lugar de 10,50 €.
    Cómo lo arreglé: el precio se parsea con parseFloat() (y se valida >= 0), conservando los céntimos.
  2. Al recargar se perdían las fechas (la página dejaba de funcionar).
    Qué fallaba: JSON.stringify() convierte los objetos Date en texto ISO, así que al recargar l.fecha era una cadena y l.fecha.getTime() lanzaba TypeError: l.fecha.getTime is not a function: ni se pintaba la tabla ni el filtro «Solo últimos 7 días».
    Cómo lo detecté: cargué varias líneas, recargué la página y revisé la consola: error de getTime.
    Cómo lo arreglé: al cargar, cada línea se rehidrata reconstruyendo la fecha (new Date(l.fecha)), descartando las entradas con fecha inválida.
  3. La paginación mostraba 4 líneas en vez de 5.
    Qué fallaba: el slice() terminaba en … + POR_PAGINA - 1, así que cada página mostraba 4 filas (la 5.ª se saltaba a la página siguiente).
    Cómo lo detecté: con 6 líneas añadidas, la página 1 mostraba 4 filas y la 2 mostraba 2, no 5 y 1.
    Cómo lo arreglé: slice(inicio, inicio + POR_PAGINA).
  4. El IVA se calculaba sobre el subtotal sin descontar.
    Qué fallaba: el IVA se aplicaba a sub en lugar de a la base ya descontada, así que con descuento el total salía demasiado alto (spec: «el IVA se calcula sobre la base ya descontada»).
    Cómo lo detecté: con un subtotal de 100,00 € y 10 % de descuento, la página mostraba IVA 21,00 € y total 111,00 €, cuando debe ser IVA 18,90 € y total 108,90 €.
    Cómo lo arreglé: se calcula base = sub - desc, iva = base * IVA y total = base + iva.
  5. El botón ✕ borraba la línea equivocada.
    Qué fallaba: el manejador hacía lineas.splice(i, 1) usando el índice de la fila dentro de la tabla visible, no el índice real en lineas; con filtro, orden o paginación se borraba otra línea (o ninguna).
    Cómo lo detecté: con 6 líneas, en la página 2, al pulsar ✕ en la última fila se borraba la primera línea de la lista original en lugar de esa.
    Cómo lo arreglé: cada fila conserva su línea original y el borrado usa lineas.indexOf(l) para localizar el índice real antes de hacer el splice.
  6. La búsqueda distinguía mayúsculas de minúsculas.
    Qué fallaba: se comparaba concepto.toLowerCase().includes(filtro) sin normalizar lo que el usuario escribía, así que buscar «agua» no encontraba «Agua».
    Cómo lo detecté: añadí «Agua mineral» y busqué «agua»: no aparecía ninguna fila.
    Cómo lo arreglé: el filtro se normaliza a minúsculas al comparar (filtro.toLowerCase()).
  7. «Ordenar» ordenaba de menor a mayor.
    Qué fallaba: el comparador importe(a) < importe(b) devuelve un booleano y ordenaba en sentido ascendente, cuando debe ser de mayor a menor.
    Cómo lo detecté: con importes 30 €, 10 € y 20 €, la tabla quedaba 10 €, 20 €, 30 €.
    Cómo lo arreglé: lineas.sort((a, b) => importe(b) - importe(a)) (y se guarda el orden, de modo que se mantiene al recargar).
  8. Inyección de HTML en el concepto.
    Qué fallaba: la fila se construía con innerHTML interpolando el concepto del usuario, así que un concepto como <img src=x onerror=alert(1)> se ejecutaba como HTML.
    Cómo lo detecté: di de alta una línea con ese concepto y observé que se insertaba un elemento img en la tabla en lugar de mostrarse como texto.
    Cómo lo arreglé: la fila se monta con createElement() y textContent, de modo que cualquier texto del usuario se muestra literalmente y nunca se interpreta como HTML.