Revisión de código · 26 de septiembre de 2026

Análisis de extractos bancarios: errores encontrados y corregidos

Revisamos el programa completo dos veces por separado (Claude y Codex) y verificamos cada hallazgo leyendo el código antes de tocarlo. Todo lo de esta lista ya está corregido y publicado en extractos.sirdeveloper.com, con 24 pruebas automáticas que pasan.

Corregidos26
Gravedad alta9
Archivos tocados12
Para decidir3
Tu copia en la PC no tiene estos arreglos.

Si subís tu versión al servidor, los pisa. Antes de seguir trabajando, aplicá los cambios: descargar cambios.diff (formato diff -ru, se aplica con patch -p1 desde la carpeta del proyecto; respeta tus finales de línea CRLF).

Plata y cálculos

7 errores
01
Alta

Los dólares se sumaban como pesos

Qué pasaba: un crédito de $ 1.000 y otro de U$S 100 daban un total de $ 1.100. Afectaba a las cuentas en dólares de Macro, Nación y Patagonia.

Arreglo: cada movimiento lleva su moneda. Los totales son solo en pesos; los dólares van aparte, con un aviso en pantalla, un bloque propio en el Excel y la columna «Moneda».

app.pyparsers/macro.pyparsers/nacion.pyparsers/patagonia.py
02
Alta

Las exportaciones de e-bank de Patagonia en dólares no se distinguían

Arreglo: la moneda se toma del encabezado CUENTA: de la exportación.

parsers/patagonia.py
03
Media

El «detalle por grupo» de la pantalla mezclaba monedas

Arreglo: se agrupa también por moneda y los montos en dólares se marcan con (U$S).

templates/index.html
04
Media

Los importes sin punto de miles no se leían

Qué pasaba: 1234,56 o 1234,56- no coincidían con el patrón de importe. En «Últimos movimientos» de Macro se perdía el movimiento entero.

Arreglo: el patrón acepta importes con y sin separador de miles.

parsers/macro.pyparsers/nacion.pyparsers/patagonia.py
05
Media

Galicia solo controlaba el saldo final

Qué pasaba: si faltaban un débito y un crédito del mismo importe, se compensaban y el saldo cerraba igual, sin aviso.

Arreglo: también se comparan los créditos y los débitos leídos con los totales que imprime el banco (con y sin la columna de IVA).

parsers/galicia.py
06
Media

Dos PDF con el mismo nombre no se detectaban como duplicados

Qué pasaba: la detección se guardaba por nombre de archivo; dos extracto.pdf se pisaban y el mismo período se sumaba dos veces sin aviso.

Arreglo: se compara cada archivo por su posición en el lote.

app.py
07
Media

Las reglas de conceptos largos se guardaban pero nunca se aplicaban

Qué pasaba: la clave se recortaba a 300 caracteres al guardarla, pero se buscaba entera. La app decía «guardado» y el concepto seguía sin clasificar.

Arreglo: la clave se guarda entera (hasta 2000 caracteres).

app.py

Cobros con Mercado Pago

8 errores
08
Alta

Podía cobrar dos veces

Qué pasaba: al cambiar de plan, si no se podía cancelar la suscripción anterior, el error se ignoraba y se creaba otra. Quedaban dos débitos y la vieja sin seguimiento.

Arreglo: si la anterior no queda cancelada, no se crea la nueva y se le avisa al usuario.

pagos.py
09
Alta

Pasar a alguien a «sin cargo» o a administrador le seguía cobrando

Qué pasaba: el débito seguía activo y la página de suscripción ya no le mostraba cómo cancelarlo.

Arreglo: primero se cancela el débito; si Mercado Pago no lo cancela, el cambio no se hace.

admin.py
10
Alta

La sincronización podía acortar un vencimiento

Qué pasaba: comparaba contra el usuario leído antes de consultar a Mercado Pago. Si mientras tanto el administrador regalaba días, la respuesta vieja los borraba.

Arreglo: el vencimiento solo se alarga, con la condición dentro del UPDATE.

pagos.py
11
Media

Cada sincronización deshacía los puestos fijados por el administrador

Arreglo: los puestos contratados se aplican solo cuando la suscripción pasa a autorizada, no en cada revisión.

pagos.py
12
Media

Una respuesta vieja podía aplicarse a una suscripción nueva

Arreglo: todo lo que escribe la sincronización queda atado al id de la suscripción consultada.

pagos.py
13
Media

La autorización y los puestos no se guardaban juntos

Qué pasaba: si fallaba entre una escritura y la otra, la suscripción quedaba autorizada con los puestos viejos y nunca se corregía.

Arreglo: estado y puestos van en la misma transacción.

pagos.py
14
Media

Si fallaba el aviso de un pago, el pago no quedaba registrado

Qué pasaba: /mp/aviso respondía 200 aunque hubiera fallado. Mercado Pago no reintentaba y el pago faltaba para siempre en el historial.

Arreglo: ante un error responde 500 y Mercado Pago lo vuelve a mandar.

cuentas.py
15
Media

El acceso vencía a las 21 h del último día

Qué pasaba: el contenedor corría en UTC y los vencimientos son fechas de Argentina.

Arreglo: TZ=America/Argentina/Buenos_Aires en el contenedor.

docker-compose.yml

Seguridad y cuentas

5 errores
16
Alta

Un administrador bloqueado seguía entrando

Qué pasaba: el acceso y el panel solo miraban si era administrador, antes de mirar el bloqueo.

Arreglo: el bloqueo se revisa primero, también para administradores.

db.pycuentas.py
17
Alta

Alguien podía quedarse con una cuenta recién verificada

Qué pasaba: un registro con el mismo email, hecho justo cuando el dueño verificaba, reemplazaba la contraseña de una cuenta ya activa y le daba sesión al otro.

Arreglo: el reemplazo solo se aplica si la cuenta sigue sin verificar (condición en el UPDATE).

cuentas.py
18
Alta

Lo mismo mientras el dueño escribía el código

Qué pasaba: la cuenta quedaba activada con la contraseña del otro.

Arreglo: reemplazar un registro invalida el código, y la verificación exige que la contraseña y el código sigan siendo los leídos.

cuentas.py
19
Media

Registrarse de nuevo salteaba la espera entre códigos

Qué pasaba: servía para mandar códigos sin límite a una casilla ajena.

Arreglo: la espera de un minuto vale también al registrarse.

cuentas.py
20
Baja

Dos pedidos de código simultáneos también la salteaban

Arreglo: el envío se reserva con un UPDATE condicional antes de mandar el correo.

cuentas.py

Licencias por equipo

3 errores
21
Alta

Se podían usar más equipos que los puestos pagos

Qué pasaba: con 3 puestos y 2 en uso, dos computadoras nuevas entrando a la vez veían el mismo lugar libre y quedaban registradas las dos.

Arreglo: contar y registrar es una sola operación, con un candado del proceso (la app corre con gunicorn -w 1).

equipos.py
22
Media

Bajar puestos mientras entraba un equipo dejaba equipos de más

Arreglo: la reducción usa el mismo candado que el registro.

equipos.py
23
Media

El límite de 3 liberaciones por mes se podía pasar

Qué pasaba: confirmando dos enlaces a la vez, los dos veían una liberación disponible.

Arreglo: usar el enlace, contar y liberar van juntos, y un enlace ya usado no vale dos veces.

equipos.py

Estabilidad

3 errores
24
Media

Error 500 con varios usuarios a la vez

Qué pasaba: los análisis se guardan en un diccionario compartido por los 4 hilos. Si uno lo limpiaba mientras otro agregaba, Python cortaba con «dictionary changed size during iteration».

Arreglo: todo acceso a los análisis pasa por un candado.

app.py
25
Media

Reclasificar y bajar el Excel a la vez mezclaba reglas

Qué pasaba: los dos pedidos reclasificaban los mismos movimientos al mismo tiempo, así que un total podía salir con una regla y otro con la anterior.

Arreglo: clasificar y totalizar se hace con el mismo candado.

app.py
26
Baja

Un análisis que vencía mientras se usaba daba error 500

Arreglo: el análisis se valida y se usa sin soltar el candado; si venció, se muestra «El análisis expiró».

app.py

Para decidir

No se cambiaron
El Access Token de Mercado Pago cargado es el de producción de otra cuenta

Pertenece a Manantiales Alunco. Hoy los cobros de las suscripciones entrarían en esa cuenta. Hay que cargar el de la cuenta propia con configurar_mp.py.

Nación: el débito «INTERESES» va entero a comisiones

Ese débito incluye intereses, sellados, IVA y percepción de IVA; el lector ya lee el desglose de la liquidación del descubierto. La regla dice «confirmado por el usuario», por eso no la tocamos: es una decisión contable. Si se reparte según el desglose, el IVA y las percepciones dejan de quedar subestimados.

Bajar puestos libera equipos sin gastar el cupo mensual

Un usuario puede subir puestos, registrar un equipo nuevo y bajarlos: se libera el más viejo sin confirmación por correo ni descontar de las 3 liberaciones por mes. Arreglarlo cambia cómo funciona la licencia, así que queda a tu criterio.