feat(tagumedica): campos de usuario de los lotes (calidad, liberación, registro sanitario y ensayos)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
GeorgeandClaude Sonnet 5.5 committed 2026-10-20 08:42:43 -05:00
1 parent 6887f9e474
commit 58eafa72ec
3 files changed
+96 -15

No files matched your search

@@ -0,0 +1,26 @@
import { describe, expect, it } from 'vitest';
import { masterKeysOf } from '../master-model';
import { MASTER_SCREENS } from '../master-screens';
describe('Lotes de TAGUMEDICA', () => {
const screen = MASTER_SCREENS.find((s) => s.path === '/inventario/lotes')!;
it('suma sus campos de usuario en dos pestañas nuevas, después de las del base', () => {
expect(screen.layout.tabs.map((t) => t.id)).toEqual(['general', 'stock', 'detalles', 'calidad', 'registro']);
const keys = masterKeysOf(screen.layout);
for (const k of ['U_SIS_ESTLOT', 'U_SIS_ESTLIB', 'U_SIS_FECAPROB', 'U_SIS_FECLIB', 'U_SIS_RESPAPROB', 'U_SIS_OBSER', 'U_SIS_REGSAN', 'U_INT_NUMPRO', 'U_SIS_ACTMUE', 'U_SIS_INFENS', 'U_SIS_PROCED', 'U_INT_UBIAPT']) {
expect(keys, k).toContain(k);
}
});
it('la hora de aprobación se ve pero no se edita (la pone el proceso de aprobación)', () => {
const hora = screen.layout.tabs.flatMap((t) => [...t.left, ...t.right]).find((f) => 'key' in f && f.key === 'U_SIS_HORAPROB');
expect(hora && 'readonly' in hora && hora.readonly).toBe(true);
});
it('la lista muestra el estado de calidad y de liberación con su texto', () => {
const cols = Object.fromEntries(screen.columns.map((c) => [c.key, c]));
expect(cols['U_SIS_ESTLOT'].format?.({ U_SIS_ESTLOT: '102' })).toBe('Aprobado');
expect(cols['U_SIS_ESTLIB'].format?.({ U_SIS_ESTLIB: '102' })).toBe('No liberado');
});
});
@@ -1,17 +1,71 @@
import type { MasterExtensions } from '../master-extensions';
/** Estado del lote en calidad (`U_SIS_ESTLOT`, valores válidos de la tabla de usuario del lote en SAP). */
const ESTADO_LOTE = [
{ value: '101', label: 'Cuarentena' },
{ value: '102', label: 'Aprobado' },
{ value: '103', label: 'Rechazado' },
{ value: '104', label: 'Inmovilizado' },
];
/** Estado de liberación (`U_SIS_ESTLIB`). */
const ESTADO_LIBERACION = [
{ value: '101', label: 'Liberado' },
{ value: '102', label: 'No liberado' },
];
const label = (options: readonly { value: string; label: string }[], value: unknown): string => options.find((o) => o.value === String(value))?.label ?? String(value ?? '');
/**
* Campos que este cliente agrega a la pantalla de lotes. En `main` va vacío: el proyecto base solo lleva campos
* estándar de SAP. La rama `cliente/<nombre>` lo completa, por ruta de pantalla, con campos estándar adicionales o de
* usuario (`U_*`) y no toca ningún otro archivo.
* Campos de usuario de TAGUMEDICA en los lotes (`OBTN`, verificados en `CUFD`/`UFD1` de `SBO_TAGUMEDICA_21`). Solo
* están en esta rama; `main` lleva solo campos estándar.
*
* Además de agregar, puede ocultar campos (`hideFields`, `hideListColumns`) y dejarlos de solo lectura
* (`readOnlyFields`). Un nombre que no exista, o ocultar un campo obligatorio, lanza un error al arrancar.
*
* Ejemplo (los campos son de ejemplo):
*
* '/inventario/lotes': {
* newTabs: [{ id: 'calidad', label: 'Calidad', left: [{ type: 'text', key: 'U_Ubicacion', label: 'Ubicación' }], right: [] }],
* },
* Notas verificadas en la base: `U_SIS_HORAPROB` guarda la hora como entero `hhmm` (1223 = 12:23) y la pone el proceso de
* aprobación, por eso va de solo lectura; `U_SIS_REGSAN` es el código de la tabla de usuario `@SIS_REGSAN` (357
* registros; hoy se escribe el código, la ventana de selección llega con el registro de tablas de usuario). Los
* campos `U_SIS_MOTEST`, `U_SIS_COMEST` y `U_SIS_ACCTOM` no son del lote sino de la solicitud de traslado (`OWTQ`).
*/
export const LOTES_EXTENSIONS: MasterExtensions = {};
export const LOTES_EXTENSIONS: MasterExtensions = {
'/inventario/lotes': {
newTabs: [
{
id: 'calidad',
label: 'Calidad',
left: [
{ type: 'select', key: 'U_SIS_ESTLOT', label: 'Estado del lote', options: ESTADO_LOTE },
{ type: 'date', key: 'U_SIS_FECAPROB', label: 'Fecha de aprobación' },
{ type: 'text', key: 'U_SIS_HORAPROB', label: 'Hora de aprobación (hhmm)', readonly: true },
{ type: 'text', key: 'U_SIS_RESPAPROB', label: 'Responsable de aprobación' },
],
right: [
{ type: 'select', key: 'U_SIS_ESTLIB', label: 'Estado de liberación', options: ESTADO_LIBERACION },
{ type: 'date', key: 'U_SIS_FECLIB', label: 'Fecha de liberación' },
{ type: 'text', key: 'U_SIS_OBSER', label: 'Observaciones' },
],
},
{
id: 'registro',
label: 'Registro y ensayos',
left: [
{ type: 'text', key: 'U_SIS_REGSAN', label: 'Registro sanitario' },
{ type: 'text', key: 'U_INT_NUMPRO', label: 'Número de protocolo' },
{ type: 'text', key: 'U_SIS_PROCED', label: 'Procedencia' },
],
right: [
{ type: 'text', key: 'U_SIS_ACTMUE', label: 'Acta de muestreo' },
{ type: 'text', key: 'U_SIS_INFENS', label: 'Informe de ensayo' },
{ type: 'text', key: 'U_INT_UBIAPT', label: 'Ubicación APT' },
],
},
],
listColumns: [
{
key: 'U_SIS_ESTLOT',
title: 'Calidad',
width: '110px',
format: (r) => label(ESTADO_LOTE, r['U_SIS_ESTLOT']),
tone: (r) => (r['U_SIS_ESTLOT'] === '102' ? 'ok' : r['U_SIS_ESTLOT'] === '101' ? 'info' : r['U_SIS_ESTLOT'] === '103' ? 'err' : 'warn'),
},
{ key: 'U_SIS_ESTLIB', title: 'Liberación', width: '110px', format: (r) => label(ESTADO_LIBERACION, r['U_SIS_ESTLIB']), tone: (r) => (r['U_SIS_ESTLIB'] === '101' ? 'ok' : 'muted') },
],
},
};
@@ -15,9 +15,10 @@ const EXT: MasterExtensions = {
};
describe('extensiones por cliente de los maestros', () => {
it('en main no agregan nada', () => {
expect(MASTER_EXTENSIONS).toEqual({});
expect(MASTER_SCREENS).toEqual(BASE_MASTER_SCREENS);
// En `main` esta prueba exige `{}` (el base no lleva campos de cliente); en la rama del cliente se lista lo que agrega.
it('en esta rama solo se extienden las pantallas del cliente', () => {
expect(Object.keys(MASTER_EXTENSIONS)).toEqual(['/inventario/lotes']);
expect(MASTER_SCREENS.filter((s) => s.path !== '/inventario/lotes')).toEqual(BASE_MASTER_SCREENS.filter((s) => s.path !== '/inventario/lotes'));
});
it('agregan campos al final de la cabecera y de las pestañas, una pestaña nueva y columnas de lista', () => {