Auditoria de 260 visitas (26 rutas x 10 roles) sobre el build actual: 0 hallazgos.
Dos defectos reales que salieron del barrido anterior se corrigen aqui.
1) Reportes: la matriz guarda claves '/reportes?tab=x' y puede conceder una
pestana suelta (Horas agenda al medico, Exportar a cosmetologia) sin dar el
modulo. Nadie la respetaba:
- App.tsx: nuevo ReportesRoute que evalua la pestana del URL; si el rol no
tiene '/reportes' pero si alguna pestana, aterriza en la primera permitida.
- Reportes.tsx: familias, favoritos y el boton "Exportar datos" se filtran
por canSee('/reportes?tab=x'). 'exportar' no pertenece a ninguna familia,
por eso se lista en TABS_FUERA_DE_FAMILIA (si no, el guard lo reescribia a
'general' y admin perdia la vista). El query se sincroniza con lo renderizado.
- SkeenLayout/HomeNestLayout: "Horas agenda" y "Exportar datos" aparecen en
Analitica para que el permiso sea alcanzable desde el menu, no solo a
mano. Clasico ya los traia.
- auth.py: _medico_reporte_permitido consulta la matriz antes de vetar
/reports/ al medico. Sin ese puente el menu ofrecia la opcion y la API
respondia 403. Las demas rutas vetadas (sales, payments, wallets, pos)
siguen cerradas: verificado comisiones=403, horas-agenda=200.
2) skeen_chat/_touch: 211 "could not serialize access due to concurrent update"
en un barrido. Bandeja, badge y heartbeat salen en paralelo y cada uno
emitia el UPDATE de last_seen aunque no aplicara cambios, abortando la
transaccion de otra peticion. Se corta antes de tocar la fila cuando el
registro es reciente; el savepoint queda para el caso de empate exacto.
Ráfaga de 128 peticiones concurrentes: todas 200, presencia sigue
actualizando (online=true, last_seen avanza) y el ruido bajo de 211 a 5.
El modelo tenia el campo price y el POS lo usa para cobrar, pero ni el
formulario de alta lo capturaba ni existia modal de edicion: los 170
articulos tenian precio en 0 y recepcion tecleaba el precio a mano en
cada venta.
- Backend: inventory_create acepta price; inventory_update lo permite
entre los campos editables (con conversion float).
- Frontend: columna 'Precio venta' en la tabla, campo en el alta y
modal nuevo 'Editar articulo' (nombre, precio, costo, SKU, categoria,
unidad, niveles y notas). La existencia sigue cambiando solo via
Ajustar para dejar registro del movimiento.
- Badge y bloques de la cuadricula: cada estado con su color propio y
fuerte (pendiente=ambar, confirmada=esmeralda, llego/en cabina=cielo,
en curso=violeta, completada=gris, cancelo/no show=rosa). Antes varios
compartian tonos pastel casi iguales. La leyenda del grid usa los
mismos colores.
- badgeForAppointmentState: llego e en_curso tenian variant de pendiente
(se veian igual); ahora arrived y progress, y no_show/waitlist tienen
las suyas.
- Boton 'Marcar en curso' (accion start del backend) en las acciones
rapidas de la vista de lista y tarjetas moviles, entre Llegada y
Completar.
- Mini-checklist del flujo en el tooltip de la cuadricula (solo lectura;
click abre el detalle con el checklist completo interactivo).
Recorrido dinamico de las 26 rutas con los 10 roles (260 visitas con
navegador real) mas revision estatica de endpoints FE vs BE. Hallazgos
y correcciones:
- frontend.py: expedientes_recientes no tenia @http.route (el endpoint
/expedientes-recientes respondia 404). Se expone con GET + lectura.
- auth.py: MEDICO_BLOCKED_PREFIXES hacia match por startswith, asi que
/sales_goal quedaba bloqueado para medicos (403 en consola en todas
las pantallas por el widget de meta del mes). Ahora el match exige
frontera de path (/sales bloquea /sales/x pero no /sales_goal).
- Reportes: getCommissions (endpoint admin-only) iba dentro de un
Promise.all; el 403 tumbaba todo el tab General para recepcion,
gerencia, enfermeria, psicologa, farmacia y lectura. Ahora va en
peticion aislada y solo se llama para admin; la seccion de comisiones
del tab General solo se renderiza para admin.
- Matriz de menus guardada: medico habia quedado fuera de /pacientes
y /visitas (los defaults del codigo lo incluyen y los medicos lo
necesitan para ver clientes e historial). Restaurado via PUT
/roles/permissions (queda en skeen_config_audit).
Verificado con navegador: medico entra a /pacientes y /visitas sin
403; recepcion y gerencia cargan /reportes sin error; admin sigue
viendo la seccion de comisiones. Usuarios temporales de auditoria
eliminados.
useClinicaPrint() quedaba despues de un return condicional en
EstadoCuentaPrint, ReciboPrint, CortePrint y ConteoPrint (migracion de
clinicSettings del punto 11). Al pasar de paciente null a uno real el
numero de hooks cambiaba entre renders y React tumaba la pantalla con
'Minified React error #310'. El hook ahora va antes de cualquier return
condicional en los 4 componentes.
- ErrorBoundary global: si un modulo falla al renderizar se muestra un
mensaje amable con recargar en lugar de pantalla en blanco.
- El bootstrap de auth ya no se queda cargando para siempre: si /auth/me
no responde en 20 s (red lenta o tunel caido) se muestra una pantalla
de reintento. Si la red fallo se conserva la sesion; solo se descarta
el token cuando el servidor contesta (401).
- Nuevo modelo skeen.ticket.config (AbstractModel) con layout, datos de clinica
y logo en ir.config_parameter; _mezclar() hace que layouts viejos hereden las
secciones nuevas; _validar() rechaza valores imposibles y textos largos.
- Endpoints GET/PUT /ticket_config, PUT /ticket_config/clinica,
POST /ticket_config/reset (reset solo admin) con auditoria en skeen_config_audit.
- Los datos de la clinica pasan de localStorage (cada caja imprimia distinto)
a configuracion compartida en el servidor.
- TicketDoc.tsx renderiza segun el diseno: ancho 58/80mm, fuente, tamano de letra,
separador, mayusculas, orden de bloques y 29 secciones activables.
- TicketDesigner.tsx: editor completo con vista previa en vivo, reorden de bloques,
textos personalizables y carga de logo.
- TicketPrint.tsx ahora es un wrapper de TicketDoc; ReciboPrint, EstadoCuentaPrint,
RecetaPrint, CortePrint y ConteoPrint usan useClinicaPrint().
- _sale_to_dict expone telefono del paciente, medico que atendio y datos de paquete;
pos_checkout devuelve recibido/cambio en MXN y USD para imprimirlos.
- Columna 'Estado' con switch Activo/Baja (confirmación previa) y checkbox
'Mostrar dados de baja' para poder reactivar.
- GET /doctors solo devuelve activos; include_inactive=1 para la pantalla de
Médicos. Cada médico expone su campo 'active'.
- Reglas en modelo: un empleado dado de baja no admite citas nuevas
(skeen.cita), ni asignación de pacientes (res.partner.primary_doctor_id),
ni líneas de venta (skeen.venta.line.prescribed_by_id). El historial
existente no se toca: los constraints solo corren cuando cambia la
asignación.
- ValidationError se responde 400 con mensaje legible en pacientes y ventas.
- fix: badges de estado de cita en agenda y dashboard usan la etiqueta de la
clínica en lugar del valor técnico.
- BloqueosModal: alta multi-médico, rango repetible por día de la semana,
motivos predefinidos, edición/borrado y cierre rápido desde la cabecera
de cada columna (todo el día / resto del día / rango).
- skeen.lista.espera + panel y modal en la agenda: prioridad, franja, rango
de fechas, agendar precargado, avisar por WhatsApp y aviso cuando se
libera un hueco compatible.
- duration en skeen.cita (0 = la del servicio): la cita ocupa sus bloques de
15 min reales en las 4 vistas, con celdas de continuación.
- Checklist de estados (Agendada → Confirmada → En cabina → En consulta →
Completada) con avance/retroceso, cancelación y no-show con confirmación,
y bitácora de quién/cuándo en skeen.cita.state.log.
- Etiquetas del sistema legacy alineadas (Canceló / Lista de espera / En
cabina) en badges de agenda y dashboard.
- Auto refresco silencioso cada 30 s (pausable, se pospone con modales,
escritura o drag) en citas + bloqueos + lista de espera en un solo request.
- Píldora verde 'N ventas' + gris 'N recomendaciones' y píldoras
índigo apiladas (usd / m.n. / tarjeta m.n.) en una sola columna
- recomendaciones_30d: pacientes captados con 'recomendado por'
- card_amount en ventas (pagos con terminal física)
- Stats por médico vía líneas recetadas, métrica consistente con comisiones
- 4 columnas nuevas en la página de Médicos
- Campo genera_monedero (default true) en servicios e items de inventario
- Puntos proporcionales a las líneas elegibles al liquidar la venta
- Toggle con icono Wallet en Servicios e Inventario