mirror of
https://github.com/efrainsosa27/cotizador-helgen.git
synced 2026-10-24 05:16:48 +00:00
Historial: nombre del vendedor también en cotizaciones viejas
Las filas anteriores a la sesión con Google no tienen vendedor_nombre/apellido y mostraban el correo. listarCotizaciones ahora arma un mapa correo → "Nombre Apellido" con las filas que sí lo tienen (nombresDeVendedores, gana la más reciente) y lo usa para las viejas (vendedorDeFila); si el correo no se conoce, sigue mostrando el correo. Solo cambia la respuesta: la hoja no se modifica. Pruebas en sesion.test.js. CLAUDE.md: §4.4, §4.6 y registros de prueba en la hoja real (HLG-2026-0005, HLG-2026-0006, PRY-2026-0001) para excluirlos de reportes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3b7f5ec953
commit
3c410b46ad
12
CLAUDE.md
12
CLAUDE.md
@ -118,8 +118,9 @@ o son lógica pura (sección 5 del archivo, sin servicios de Google, probables e
|
|||||||
### 4.4 Datos para la interfaz
|
### 4.4 Datos para la interfaz
|
||||||
- `getDatosIniciales()` *(UI, al cargar)* — IVA, vigencia, marcas, catálogo, clientes, `usuario` (correo) y `vendedor` (`{email, nombre, apellido}`).
|
- `getDatosIniciales()` *(UI, al cargar)* — IVA, vigencia, marcas, catálogo, clientes, `usuario` (correo) y `vendedor` (`{email, nombre, apellido}`).
|
||||||
- `getClientes()` *(UI)* — clientes que ya cotizaron, calculados desde la hoja `Cotizaciones` (`clientesDesdeFilas`).
|
- `getClientes()` *(UI)* — clientes que ya cotizaron, calculados desde la hoja `Cotizaciones` (`clientesDesdeFilas`).
|
||||||
- `listarCotizaciones(busqueda)` *(UI)* — últimas 100 cotizaciones, filtro por folio/cliente/empresa; `vendedor` = nombre + apellido
|
- `listarCotizaciones(busqueda)` *(UI)* — últimas 100 cotizaciones, filtro por folio/cliente/empresa/proyecto; `vendedor` =
|
||||||
(filas viejas: `vendedor_email` o "Elaboró").
|
`vendedorDeFila`: nombre + apellido de la fila; en filas viejas (sin Q:S), el nombre que ese correo tiene en otras filas
|
||||||
|
(`nombresDeVendedores`, gana la más reciente); si no se conoce, el correo (`vendedor_email` o "Elaboró"). No modifica la hoja.
|
||||||
|
|
||||||
### 4.5 Guardar cotización
|
### 4.5 Guardar cotización
|
||||||
- `guardarCotizacion(payload)` *(UI)* — exige vendedor verificado (si no, error de sesión); normaliza, agrega clave/unidad SAT
|
- `guardarCotizacion(payload)` *(UI)* — exige vendedor verificado (si no, error de sesión); normaliza, agrega clave/unidad SAT
|
||||||
@ -156,6 +157,7 @@ o son lógica pura (sección 5 del archivo, sin servicios de Google, probables e
|
|||||||
normalizados. `normalizarProyecto(p)`, `nombreCarpetaProyecto(folio, cliente, nombre)` (quita `/ \ : * ? " < > |`),
|
normalizados. `normalizarProyecto(p)`, `nombreCarpetaProyecto(folio, cliente, nombre)` (quita `/ \ : * ? " < > |`),
|
||||||
`proyectosDesdeFilas`, `filtrarProyectos`.
|
`proyectosDesdeFilas`, `filtrarProyectos`.
|
||||||
- `normalizarProducto(p)`, `indiceDeClave(claves, clave)`.
|
- `normalizarProducto(p)`, `indiceDeClave(claves, clave)`.
|
||||||
|
- `nombresDeVendedores(filas)` (correo en minúsculas → "Nombre Apellido"), `vendedorDeFila(r, nombres)`.
|
||||||
- `terminosParaPdf(cfg, marca)` — renglones de T&C de la marca; agrega la línea de vigencia si falta.
|
- `terminosParaPdf(cfg, marca)` — renglones de T&C de la marca; agrega la línea de vigencia si falta.
|
||||||
- `textoCelda(s)` — antepone `'` a texto que empiece con `= + - @` para que Sheets no lo lea como fórmula.
|
- `textoCelda(s)` — antepone `'` a texto que empiece con `= + - @` para que Sheets no lo lea como fórmula.
|
||||||
- `validarClaimsToken(claims, clientId, dominio, ahora)` — ver §10.3.
|
- `validarClaimsToken(claims, clientId, dominio, ahora)` — ver §10.3.
|
||||||
@ -196,6 +198,12 @@ del catálogo, claveSat/unidadSat), Notas, Marca (Helgen / Helgen Energy), Direc
|
|||||||
filas 0001–0004 no las tienen llenas. La T se agregó con Proyectos (10/2026): las filas anteriores quedan vacías y no se mueven.
|
filas 0001–0004 no las tienen llenas. La T se agregó con Proyectos (10/2026): las filas anteriores quedan vacías y no se mueven.
|
||||||
`guardarCotizacion` reescribe la fila de encabezados si no coincide con `ENCABEZADOS_COTIZACIONES`.
|
`guardarCotizacion` reescribe la fila de encabezados si no coincide con `ENCABEZADOS_COTIZACIONES`.
|
||||||
|
|
||||||
|
**Registros de prueba en la hoja real (excluir de reportes y totales; identificarlos por folio, no por texto):**
|
||||||
|
- `HLG-2026-0005` — cliente "PRUEBA - CANCELADA" (prueba de la migración, 19/10/2026).
|
||||||
|
- `HLG-2026-0006` — cliente renombrado a "PRUEBA – CANCELADA" (con guion largo; prueba del primer proyecto, 20/10/2026).
|
||||||
|
- `PRY-2026-0001` — "Proyecto de Prueba oficial", estado `Cancelado` (20/10/2026). Su carpeta en `Proyectos/2026` es de prueba.
|
||||||
|
- Las filas HLG-2026-0001 a 0004 son anteriores a la sesión con Google y también parecen pruebas, pero no se marcaron.
|
||||||
|
|
||||||
**Pestaña `Config`** (Clave | Valor, columna B en formato texto): `EMPRESA_NOMBRE` (Helgen Servicios Especializados),
|
**Pestaña `Config`** (Clave | Valor, columna B en formato texto): `EMPRESA_NOMBRE` (Helgen Servicios Especializados),
|
||||||
`EMPRESA_RFC` (HSE240304PC1), `EMPRESA_DIRECCION`, `EMPRESA_TELEFONO` (+52 442 644 5391), `EMPRESA_EMAIL`, `EMPRESA_WEB`,
|
`EMPRESA_RFC` (HSE240304PC1), `EMPRESA_DIRECCION`, `EMPRESA_TELEFONO` (+52 442 644 5391), `EMPRESA_EMAIL`, `EMPRESA_WEB`,
|
||||||
`PREFIJO_FOLIO` (HLG), `IVA_TASA` (0.16), `VIGENCIA_DIAS` (15), `MONEDA`, `LOGO_FILE_ID` (vacío = logo de `Logo.js`),
|
`PREFIJO_FOLIO` (HLG), `IVA_TASA` (0.16), `VIGENCIA_DIAS` (15), `MONEDA`, `LOGO_FILE_ID` (vacío = logo de `Logo.js`),
|
||||||
|
|||||||
33
Code.js
33
Code.js
@ -582,15 +582,17 @@ function getClientes() {
|
|||||||
/** Historial (últimas 100, con búsqueda opcional). */
|
/** Historial (últimas 100, con búsqueda opcional). */
|
||||||
function listarCotizaciones(busqueda) {
|
function listarCotizaciones(busqueda) {
|
||||||
const hoja = db_().getSheetByName('Cotizaciones');
|
const hoja = db_().getSheetByName('Cotizaciones');
|
||||||
const datos = hoja.getDataRange().getDisplayValues().slice(1).reverse();
|
const filas = hoja.getDataRange().getDisplayValues().slice(1);
|
||||||
|
// Correo → "Nombre Apellido" con todas las filas (antes de filtrar), para ponerle nombre a las filas viejas
|
||||||
|
const nombres = nombresDeVendedores(filas);
|
||||||
|
const datos = filas.reverse();
|
||||||
const q = String(busqueda || '').toLowerCase().trim();
|
const q = String(busqueda || '').toLowerCase().trim();
|
||||||
return datos
|
return datos
|
||||||
.filter(r => !q || (r[0] + ' ' + r[2] + ' ' + r[3] + ' ' + (r[19] || '')).toLowerCase().includes(q))
|
.filter(r => !q || (r[0] + ' ' + r[2] + ' ' + r[3] + ' ' + (r[19] || '')).toLowerCase().includes(q))
|
||||||
.slice(0, 100)
|
.slice(0, 100)
|
||||||
.map(r => ({
|
.map(r => ({
|
||||||
folio: r[0], fecha: r[1], cliente: r[2], empresa: r[3], total: r[9], elaboro: r[10], pdf: r[11],
|
folio: r[0], fecha: r[1], cliente: r[2], empresa: r[3], total: r[9], elaboro: r[10], pdf: r[11],
|
||||||
// Filas anteriores a la sesión con Google no tienen vendedor: se muestra el correo de "Elaboró"
|
vendedor: vendedorDeFila(r, nombres),
|
||||||
vendedor: [r[17], r[18]].filter(Boolean).join(' ') || r[16] || r[10] || '',
|
|
||||||
// Filas anteriores a Proyectos (o cotizadas sin proyecto)
|
// Filas anteriores a Proyectos (o cotizadas sin proyecto)
|
||||||
proyecto: r[19] || 'Sin proyecto',
|
proyecto: r[19] || 'Sin proyecto',
|
||||||
}));
|
}));
|
||||||
@ -985,6 +987,31 @@ function clientesDesdeFilas(filas) {
|
|||||||
return lista;
|
return lista;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correo (minúsculas) → "Nombre Apellido", tomado de las filas de Cotizaciones que sí tienen vendedor_nombre /
|
||||||
|
* vendedor_apellido (columnas Q:S). Filas en orden de la hoja: si un correo aparece con nombres distintos, gana la más reciente.
|
||||||
|
*/
|
||||||
|
function nombresDeVendedores(filas) {
|
||||||
|
const mapa = {};
|
||||||
|
(filas || []).forEach(r => {
|
||||||
|
const correo = String(r[16] || '').trim().toLowerCase();
|
||||||
|
const nombre = [r[17], r[18]].map(x => String(x || '').trim()).filter(Boolean).join(' ');
|
||||||
|
if (correo && nombre) mapa[correo] = nombre;
|
||||||
|
});
|
||||||
|
return mapa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendedor para el Historial: nombre y apellido de la fila; si no tiene (filas anteriores a la sesión con Google),
|
||||||
|
* el nombre que ese correo tiene en otras filas; si no se conoce, el correo (vendedor_email o "Elaboró").
|
||||||
|
*/
|
||||||
|
function vendedorDeFila(r, nombres) {
|
||||||
|
const propio = [r[17], r[18]].map(x => String(x || '').trim()).filter(Boolean).join(' ');
|
||||||
|
if (propio) return propio;
|
||||||
|
const correo = String(r[16] || r[10] || '').trim();
|
||||||
|
return (nombres || {})[correo.toLowerCase()] || correo;
|
||||||
|
}
|
||||||
|
|
||||||
/** Texto sin acentos, en minúsculas y con espacios simples (para comparar nombres). */
|
/** Texto sin acentos, en minúsculas y con espacios simples (para comparar nombres). */
|
||||||
function textoNormal(s) {
|
function textoNormal(s) {
|
||||||
return String(s || '').normalize('NFD').replace(/[̀-ͯ]/g, '')
|
return String(s || '').normalize('NFD').replace(/[̀-ͯ]/g, '')
|
||||||
|
|||||||
@ -84,6 +84,19 @@ assert.equal(post({funcion:'guardarCotizacion',args:[{...base,idSolicitud:'ok1'}
|
|||||||
// --- Historial con el nombre del vendedor; filas viejas muestran el correo de "Elaboró" ---
|
// --- Historial con el nombre del vendedor; filas viejas muestran el correo de "Elaboró" ---
|
||||||
const h=post({funcion:'listarCotizaciones',args:[''],token:'t-bueno'}).datos;
|
const h=post({funcion:'listarCotizaciones',args:[''],token:'t-bueno'}).datos;
|
||||||
assert.equal(h[0].vendedor,'Ana López Díaz'); assert.equal(h[1].vendedor,'antes@helgen.tech');
|
assert.equal(h[0].vendedor,'Ana López Díaz'); assert.equal(h[1].vendedor,'antes@helgen.tech');
|
||||||
|
// Filas viejas sin nombre: si su correo aparece con nombre en otra fila, se muestra ese nombre (sin tocar la hoja)
|
||||||
|
const vieja=['HLG-2026-0000','x','Vieja','','','','',1,0.16,1.16,'ANA.LOPEZ@helgen.tech','u','[]','','Helgen',''];
|
||||||
|
cot.m.splice(1,0,vieja.slice());
|
||||||
|
const h2=post({funcion:'listarCotizaciones',args:[''],token:'t-bueno'}).datos;
|
||||||
|
assert.equal(h2.find(c=>c.folio==='HLG-2026-0000').vendedor,'Ana López Díaz','correo conocido (sin importar mayúsculas) = nombre');
|
||||||
|
assert.equal(h2.find(c=>c.folio==='HLG-2026-0001').vendedor,'antes@helgen.tech','correo sin nombre conocido = correo');
|
||||||
|
assert.deepEqual(cot.m[1],vieja,'la hoja no se modifica');
|
||||||
|
cot.m.splice(1,1);
|
||||||
|
// Pura: si un correo tiene nombres distintos, gana la fila más reciente (la de más abajo)
|
||||||
|
const Q=(email,n,a)=>{const r=Array(19).fill('');r[16]=email;r[17]=n;r[18]=a;return r};
|
||||||
|
assert.equal(ctx.nombresDeVendedores([Q('a@helgen.tech','Ana','Viejo'),Q('A@helgen.tech','Ana','Nuevo'),Q('b@helgen.tech','','')])['a@helgen.tech'],'Ana Nuevo');
|
||||||
|
assert.equal(ctx.nombresDeVendedores([Q('b@helgen.tech','','')])['b@helgen.tech'],undefined,'sin nombre no entra al mapa');
|
||||||
|
assert.equal(ctx.vendedorDeFila(Q('','',''),{}),'','fila sin datos de vendedor');
|
||||||
// Datos iniciales: usuario = correo del token
|
// Datos iniciales: usuario = correo del token
|
||||||
const d=post({funcion:'getDatosIniciales',args:[],token:'t-bueno'}).datos;
|
const d=post({funcion:'getDatosIniciales',args:[],token:'t-bueno'}).datos;
|
||||||
assert.equal(d.usuario,'ana.lopez@helgen.tech'); assert.equal(d.vendedor.nombre,'Ana');
|
assert.equal(d.usuario,'ana.lopez@helgen.tech'); assert.equal(d.vendedor.nombre,'Ana');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user