From c8357fe582f7c131ab58817436c9d2258725ed00 Mon Sep 17 00:00:00 2001 From: Consult AS Technology Date: Fri, 11 Sep 2026 23:17:25 +0000 Subject: [PATCH] feat(7-mejoras): notas persisten + soporte + captura visible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Notas propagan entre etapas (convertirLead/apartar updateMany) 2. Afluencia: conexión → aviso amarillo 3. Escrituración: click nombre abre bitácora 4. Co-acreditado: opción 'no tiene' 5. Nota del alta de lead → bitácora 6. Módulo Soporte: formulario + listado solicitudes 7. Captura visible para todos --- .../migration.sql | 24 ++ api-gateway/prisma/schema.prisma | 26 ++ api-gateway/src/config/roles.test.ts | 10 +- api-gateway/src/config/roles.ts | 7 +- .../controllers/clientes.controller.test.ts | 3 + .../src/controllers/leads.controller.test.ts | 3 + .../src/controllers/leads.controller.ts | 9 + .../oportunidades.controller.test.ts | 3 + .../src/controllers/soporte.controller.ts | 111 ++++++ api-gateway/src/routes/index.ts | 8 + api-gateway/src/services/auth.service.test.ts | 4 +- .../src/services/crm-local.service.test.ts | 3 + api-gateway/src/services/crm-local.service.ts | 25 +- .../src/components/AltaAfluenciaForm.test.tsx | 6 +- .../src/components/AltaAfluenciaForm.tsx | 16 +- .../src/forms/CoacreditadoForm.tsx | 19 + apps/landing/src/App.tsx | 17 +- apps/landing/src/data/modulos.test.ts | 5 +- apps/landing/src/data/modulos.ts | 7 + apps/landing/src/pages/SoportePage.tsx | 325 ++++++++++++++++++ apps/landing/src/services/soporte.ts | 73 ++++ apps/mi-dia/src/pages/MiDiaPage.tsx | 8 +- 22 files changed, 693 insertions(+), 19 deletions(-) create mode 100644 api-gateway/prisma/migrations/20260911130000_solicitud_soporte/migration.sql create mode 100644 api-gateway/src/controllers/soporte.controller.ts create mode 100644 apps/landing/src/pages/SoportePage.tsx create mode 100644 apps/landing/src/services/soporte.ts diff --git a/api-gateway/prisma/migrations/20260911130000_solicitud_soporte/migration.sql b/api-gateway/prisma/migrations/20260911130000_solicitud_soporte/migration.sql new file mode 100644 index 0000000..c8dde40 --- /dev/null +++ b/api-gateway/prisma/migrations/20260911130000_solicitud_soporte/migration.sql @@ -0,0 +1,24 @@ +-- Tabla del módulo Soporte: solicitudes de los departamentos. +-- Patrón manual: sqlite3 + _prisma_migrations. NUNCA migrate dev. + +-- CreateTable SolicitudSoporte +CREATE TABLE IF NOT EXISTS "SolicitudSoporte" ( + "id" TEXT NOT NULL PRIMARY KEY, + "usuarioId" TEXT NOT NULL, + "titulo" TEXT NOT NULL, + "descripcion" TEXT NOT NULL, + "departamento" TEXT NOT NULL, + "prioridad" TEXT NOT NULL, + "responsable" TEXT, + "fechaSolicitud" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "fechaLimite" DATETIME, + "notas" TEXT, + "archivos" TEXT, + "solicitante" TEXT NOT NULL DEFAULT '', + "solicitanteEmail" TEXT, + "estado" TEXT NOT NULL DEFAULT 'ABIERTA', + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); +CREATE INDEX IF NOT EXISTS "SolicitudSoporte_usuarioId_idx" ON "SolicitudSoporte"("usuarioId"); +CREATE INDEX IF NOT EXISTS "SolicitudSoporte_departamento_idx" ON "SolicitudSoporte"("departamento"); diff --git a/api-gateway/prisma/schema.prisma b/api-gateway/prisma/schema.prisma index a497aa7..52e1ea7 100644 --- a/api-gateway/prisma/schema.prisma +++ b/api-gateway/prisma/schema.prisma @@ -504,3 +504,29 @@ model ClienteMaestro { @@index([estatus]) @@index([vendedor]) } + +// ── Módulo Soporte: solicitudes de los departamentos (Sistemas, Admin, ...) ── + +model SolicitudSoporte { + id String @id @default(uuid()) + usuarioId String + titulo String + descripcion String + departamento String + prioridad String + responsable String? + fechaSolicitud DateTime @default(now()) + fechaLimite DateTime? + notas String? + archivos String? // JSON array de rutas + // Quién pide la solicitud (nombre y email; el front los manda o el API usa + // el usuario del token) + solicitante String @default("") + solicitanteEmail String? + estado String @default("ABIERTA") // ABIERTA, EN_PROCESO, CERRADA + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([usuarioId]) + @@index([departamento]) +} diff --git a/api-gateway/src/config/roles.test.ts b/api-gateway/src/config/roles.test.ts index 1445f6d..028a100 100644 --- a/api-gateway/src/config/roles.test.ts +++ b/api-gateway/src/config/roles.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest' import { MODULOS, esRolValido, modulosDeRol } from './roles.js' const ESPERADOS: Record = { - vendedor: ['mi-dia', 'alta-cliente', 'alta-afluencia', 'cotizador', 'mi-tablero', 'contactos', 'chat', 'clientes-maestro'], + vendedor: ['mi-dia', 'alta-cliente', 'alta-afluencia', 'cotizador', 'mi-tablero', 'contactos', 'chat', 'clientes-maestro', 'captura', 'soporte'], administracion_venta: [ 'mi-dia', 'alta-cliente', @@ -12,6 +12,8 @@ const ESPERADOS: Record = { 'contactos', 'chat', 'clientes-maestro', + 'captura', + 'soporte', 'cobranza', ], director_ventas: [ @@ -23,6 +25,8 @@ const ESPERADOS: Record = { 'contactos', 'chat', 'clientes-maestro', + 'captura', + 'soporte', 'cobranza', 'tablero-comercial', ], @@ -35,9 +39,11 @@ const ESPERADOS: Record = { 'contactos', 'chat', 'clientes-maestro', + 'captura', + 'soporte', 'cobranza', ], - direccion_general: ['mi-dia', 'tablero-comercial', 'cobranza', 'chat', 'clientes-maestro'], + direccion_general: ['mi-dia', 'tablero-comercial', 'cobranza', 'chat', 'clientes-maestro', 'captura', 'soporte'], admin: [...MODULOS], } diff --git a/api-gateway/src/config/roles.ts b/api-gateway/src/config/roles.ts index 0777728..29a17bf 100644 --- a/api-gateway/src/config/roles.ts +++ b/api-gateway/src/config/roles.ts @@ -13,6 +13,8 @@ export const MODULOS = [ 'contactos', 'chat', 'clientes-maestro', + 'captura', + 'soporte', ] as const export type Modulo = (typeof MODULOS)[number] @@ -26,6 +28,7 @@ export const ROLES = [ ] as const export type Rol = (typeof ROLES)[number] +// 'captura' y 'soporte' son transversales: todos los roles los ven const MODULOS_VENDEDOR: readonly Modulo[] = [ 'mi-dia', 'alta-cliente', @@ -35,6 +38,8 @@ const MODULOS_VENDEDOR: readonly Modulo[] = [ 'contactos', 'chat', 'clientes-maestro', + 'captura', + 'soporte', ] export const MATRIZ: Record = { @@ -42,7 +47,7 @@ export const MATRIZ: Record = { administracion_venta: [...MODULOS_VENDEDOR, 'cobranza'], director_ventas: [...MODULOS_VENDEDOR, 'cobranza', 'tablero-comercial'], escrituracion: [...MODULOS_VENDEDOR, 'cobranza'], - direccion_general: ['mi-dia', 'tablero-comercial', 'cobranza', 'chat', 'clientes-maestro'], + direccion_general: ['mi-dia', 'tablero-comercial', 'cobranza', 'chat', 'clientes-maestro', 'captura', 'soporte'], admin: MODULOS, } diff --git a/api-gateway/src/controllers/clientes.controller.test.ts b/api-gateway/src/controllers/clientes.controller.test.ts index 3ec5fd3..514665a 100644 --- a/api-gateway/src/controllers/clientes.controller.test.ts +++ b/api-gateway/src/controllers/clientes.controller.test.ts @@ -25,6 +25,9 @@ const prismaMock = vi.hoisted(() => ({ findUnique: vi.fn(), upsert: vi.fn(), }, + recordatorioVendedor: { + updateMany: vi.fn().mockResolvedValue({ count: 0 }), + }, auditoriaLog: { create: vi.fn().mockResolvedValue({}), }, diff --git a/api-gateway/src/controllers/leads.controller.test.ts b/api-gateway/src/controllers/leads.controller.test.ts index 692b3e7..0fbce2b 100644 --- a/api-gateway/src/controllers/leads.controller.test.ts +++ b/api-gateway/src/controllers/leads.controller.test.ts @@ -37,6 +37,9 @@ const prismaMock = vi.hoisted(() => ({ findUnique: vi.fn(), upsert: vi.fn(), }, + recordatorioVendedor: { + updateMany: vi.fn().mockResolvedValue({ count: 0 }), + }, cliente: { findMany: vi.fn(), }, diff --git a/api-gateway/src/controllers/leads.controller.ts b/api-gateway/src/controllers/leads.controller.ts index 40652ef..2206ad6 100644 --- a/api-gateway/src/controllers/leads.controller.ts +++ b/api-gateway/src/controllers/leads.controller.ts @@ -6,6 +6,7 @@ import { crmLocal, esIdLocal } from '../services/crm-local.service.js' import * as auditoria from '../services/auditoria.service.js' import { MOTIVOS_NO_COMPRA, SUBCATEGORIAS_NO_COMPRA } from './oportunidades.controller.js' import { AppError, NotFoundError } from '../utils/errors.js' +import { recordatorioVendedorService } from '../services/recordatorio-vendedor.service.js' /** * Captura y acciones del CRM ligero, sobre DOS tableros locales (tabla @@ -162,6 +163,14 @@ export async function crearLeadEndpoint(req: Request, res: Response): Promise ({ findUnique: vi.fn(), upsert: vi.fn(), }, + recordatorioVendedor: { + updateMany: vi.fn().mockResolvedValue({ count: 0 }), + }, auditoriaLog: { create: vi.fn().mockResolvedValue({}), }, diff --git a/api-gateway/src/controllers/soporte.controller.ts b/api-gateway/src/controllers/soporte.controller.ts new file mode 100644 index 0000000..a20985d --- /dev/null +++ b/api-gateway/src/controllers/soporte.controller.ts @@ -0,0 +1,111 @@ +import type { Request, Response } from 'express' +import { z } from 'zod' +import { prisma } from '../services/prisma.js' +import * as auditoria from '../services/auditoria.service.js' + +/** + * Módulo Soporte: solicitudes dirigidas a los departamentos internos + * (Sistemas, Administración, Ventas, Cobranza, Escrituración, Dirección). + * + * - POST /api/soporte — crear solicitud (cualquier usuario autenticado). + * - GET /api/soporte — listar solicitudes: propias para cualquier usuario, + * TODAS para admin (con filtro opcional ?estado=ABIERTA|EN_PROCESO|CERRADA). + */ + +const DEPARTAMENTOS_SOPORTE = ['Sistemas', 'Administración', 'Ventas', 'Cobranza', 'Escrituración', 'Dirección'] as const +const PRIORIDADES_SOPORTE = ['Baja', 'Media', 'Alta', 'Urgente'] as const +const ESTADOS_SOPORTE = ['ABIERTA', 'EN_PROCESO', 'CERRADA'] as const + +const crearSolicitudSchema = z.object({ + titulo: z.string().trim().min(1, 'El título es obligatorio'), + descripcion: z.string().trim().min(1, 'La descripción es obligatoria'), + departamento: z.enum(DEPARTAMENTOS_SOPORTE), + prioridad: z.enum(PRIORIDADES_SOPORTE), + responsable: z.string().trim().min(1).optional(), + fechaSolicitud: z.coerce.date(), + fechaLimite: z.coerce.date().optional(), + notas: z.string().trim().min(1).optional(), + // Quién pide la solicitud; el front lo manda desde la sesión y el API cae al + // username del token si viene vacío + solicitante: z.string().trim().min(1).optional(), + email: z.string().trim().email('Email inválido').optional(), + // Rutas/nombres de archivos adjuntos (sin endpoint de upload: el front manda + // nombres; se guardan como JSON array) + archivos: z.array(z.string().trim().min(1)).optional(), +}) + +const listarSolicitudSchema = z.object({ + estado: z.enum(ESTADOS_SOPORTE).optional(), +}) + +/** Serializa la fila para el front: archivos JSON → array (tolerante a basura) */ +function aPublico(fila: { + id: string + usuarioId: string + titulo: string + descripcion: string + departamento: string + prioridad: string + responsable: string | null + fechaSolicitud: Date + fechaLimite: Date | null + notas: string | null + archivos: string | null + solicitante: string + solicitanteEmail: string | null + estado: string + createdAt: Date + updatedAt: Date +}) { + let archivos: string[] = [] + if (fila.archivos) { + try { + const parseado: unknown = JSON.parse(fila.archivos) + if (Array.isArray(parseado)) archivos = parseado.filter((a): a is string => typeof a === 'string') + } catch { + archivos = [] + } + } + return { ...fila, archivos } +} + +/** POST /api/soporte — crear solicitud de soporte */ +export async function crearSolicitudSoporteEndpoint(req: Request, res: Response): Promise { + const body = crearSolicitudSchema.parse(req.body) + const usuarioId = req.usuarioToken?.sub ?? 'desconocido' + const fila = await prisma.solicitudSoporte.create({ + data: { + usuarioId, + titulo: body.titulo, + descripcion: body.descripcion, + departamento: body.departamento, + prioridad: body.prioridad, + responsable: body.responsable ?? null, + fechaSolicitud: body.fechaSolicitud, + fechaLimite: body.fechaLimite ?? null, + notas: body.notas ?? null, + archivos: body.archivos !== undefined ? JSON.stringify(body.archivos) : null, + solicitante: body.solicitante ?? req.usuarioToken?.username ?? '', + solicitanteEmail: body.email ?? null, + }, + }) + res.status(201).json(aPublico(fila)) + auditoria.registrar(usuarioId, 'CREAR_SOLICITUD_SOPORTE', 'soporte', fila.id, body.departamento) +} + +/** GET /api/soporte — listar solicitudes (propias; admin ve todas) */ +export async function listarSolicitudesSoporteEndpoint(req: Request, res: Response): Promise { + const { estado } = listarSolicitudSchema.parse(req.query) + const where: Record = {} + // Admin ve todas; el resto solo las suyas + if (req.usuarioToken?.rol !== 'admin') { + where['usuarioId'] = req.usuarioToken?.sub ?? 'desconocido' + } + if (estado !== undefined) where['estado'] = estado + + const filas = await prisma.solicitudSoporte.findMany({ + where, + orderBy: { createdAt: 'desc' }, + }) + res.json(filas.map(aPublico)) +} diff --git a/api-gateway/src/routes/index.ts b/api-gateway/src/routes/index.ts index 6560002..5a9b199 100644 --- a/api-gateway/src/routes/index.ts +++ b/api-gateway/src/routes/index.ts @@ -133,6 +133,10 @@ import { importarEndpoint as importarMaestroEndpoint, requireAdmin as requireAdminMaestro, } from '../controllers/cliente-maestro.controller.js' +import { + crearSolicitudSoporteEndpoint, + listarSolicitudesSoporteEndpoint, +} from '../controllers/soporte.controller.js' export const apiRouter = Router() @@ -292,3 +296,7 @@ apiRouter.get('/clientes-maestro', listarMaestroEndpoint) apiRouter.get('/clientes-maestro/exportar', exportarMaestroEndpoint) apiRouter.get('/clientes-maestro/:numCliente', obtenerMaestroEndpoint) apiRouter.post('/clientes-maestro/importar', requireAdminMaestro, importarMaestroEndpoint) + +// ── Módulo Soporte: solicitudes a departamentos internos ────────── +apiRouter.post('/soporte', crearSolicitudSoporteEndpoint) +apiRouter.get('/soporte', listarSolicitudesSoporteEndpoint) diff --git a/api-gateway/src/services/auth.service.test.ts b/api-gateway/src/services/auth.service.test.ts index fa64f18..1cf5744 100644 --- a/api-gateway/src/services/auth.service.test.ts +++ b/api-gateway/src/services/auth.service.test.ts @@ -54,7 +54,7 @@ describe('login', () => { rol: 'vendedor', asignacion: 'MONTEBELLO', mondayUserId: null, - modulos: ['mi-dia', 'alta-cliente', 'alta-afluencia', 'cotizador', 'mi-tablero', 'contactos', 'chat', 'clientes-maestro'], + modulos: ['mi-dia', 'alta-cliente', 'alta-afluencia', 'cotizador', 'mi-tablero', 'contactos', 'chat', 'clientes-maestro', 'captura', 'soporte'], acciones: [], }) expect(verificarToken(resultado.token).username).toBe('erick israel pena ramos') @@ -103,7 +103,7 @@ describe('me', () => { it('incluye los módulos visibles del rol (matriz de config/roles)', async () => { prismaMock.usuario.findUnique.mockResolvedValue(usuarioFake({ rol: 'direccion_general' })) const resultado = await me({ sub: 'u-1', username: 'x', rol: 'direccion_general' }) - expect(resultado.modulos).toEqual(['mi-dia', 'tablero-comercial', 'cobranza', 'chat', 'clientes-maestro']) + expect(resultado.modulos).toEqual(['mi-dia', 'tablero-comercial', 'cobranza', 'chat', 'clientes-maestro', 'captura', 'soporte']) }) it('rechaza si el usuario ya no existe o está inactivo', async () => { diff --git a/api-gateway/src/services/crm-local.service.test.ts b/api-gateway/src/services/crm-local.service.test.ts index 4443af8..fc6b58e 100644 --- a/api-gateway/src/services/crm-local.service.test.ts +++ b/api-gateway/src/services/crm-local.service.test.ts @@ -19,6 +19,9 @@ const prismaMock = vi.hoisted(() => ({ findUnique: vi.fn(), upsert: vi.fn(), }, + recordatorioVendedor: { + updateMany: vi.fn().mockResolvedValue({ count: 0 }), + }, cliente: { findMany: vi.fn(), }, diff --git a/api-gateway/src/services/crm-local.service.ts b/api-gateway/src/services/crm-local.service.ts index b759c0e..d3d6fc5 100644 --- a/api-gateway/src/services/crm-local.service.ts +++ b/api-gateway/src/services/crm-local.service.ts @@ -1,6 +1,7 @@ import { randomUUID } from 'node:crypto' import { AppError, NotFoundError } from '../utils/errors.js' import { claveApartadoCreado } from './automatizaciones.service.js' +import { calcularClienteClave } from './recordatorio-vendedor.service.js' import { prisma } from './prisma.js' /** @@ -256,11 +257,13 @@ export class CrmLocalService { }) } - /** + /** * Conversión local lead → oportunidad (botón "Agendo" de Mis leads): el lead * queda 'Calificado', se crea la oportunidad local en 'Agendo Cita' copiando - * los datos y se liga LeadOportunidad + `oportunidadId` en la captura. Si el - * lead ya fue convertido → 409. Devuelve la fila local de la oportunidad. + * los datos y se liga LeadOportunidad + `oportunidadId` en la captura. Las + * notas (recordatorios) del lead pasan a apuntar a la oportunidad para que + * sobrevivan al cambio de etapa. Si el lead ya fue convertido → 409. Devuelve + * la fila local de la oportunidad. */ async convertirLead(leadIdLocal: string, _usuarioId?: string) { const lead = await prisma.leadCaptura.findUnique({ where: { leadId: leadIdLocal } }) @@ -291,6 +294,11 @@ export class CrmLocalService { }, }), prisma.leadOportunidad.create({ data: { leadId: leadIdLocal, oportunidadId } }), + // Las notas del lead viejo siguen a la nueva oportunidad (bitácora persiste) + prisma.recordatorioVendedor.updateMany({ + where: { leadId: leadIdLocal }, + data: { leadId: oportunidadId, clienteClave: calcularClienteClave(lead.nombreLead, lead.telefono) }, + }), ]) return resultados[1] } @@ -298,8 +306,10 @@ export class CrmLocalService { /** * "Aparto" local: la oportunidad pasa a etapa 'Aparto' y se crea el apartado * local (board=APARTADO, etapa 'Pago Apartado', primer paso del pipeline - * Enkontrol) con los datos conocidos. Idempotente con el mismo dedup - * `apartado-creado:` de las automatizaciones. + * Enkontrol) con los datos conocidos. Las notas (recordatorios) de la + * oportunidad pasan a apuntar al apartado para que sobrevivan al cambio de + * board. Idempotente con el mismo dedup `apartado-creado:` de las + * automatizaciones. */ async apartar(oportunidadIdLocal: string, usuarioId?: string) { // Sombra para ids heredados sin fila local @@ -343,6 +353,11 @@ export class CrmLocalService { update: { valor: apartadoId }, create: { clave, valor: apartadoId }, }), + // Las notas de la oportunidad siguen al apartado (bitácora persiste) + prisma.recordatorioVendedor.updateMany({ + where: { leadId: oportunidadIdLocal }, + data: { leadId: apartadoId, clienteClave: calcularClienteClave(oportunidad.nombreLead, oportunidad.telefono) }, + }), ]) return resultados[1] } diff --git a/apps/alta-clientes/src/components/AltaAfluenciaForm.test.tsx b/apps/alta-clientes/src/components/AltaAfluenciaForm.test.tsx index 7acdd19..7047593 100644 --- a/apps/alta-clientes/src/components/AltaAfluenciaForm.test.tsx +++ b/apps/alta-clientes/src/components/AltaAfluenciaForm.test.tsx @@ -174,14 +174,14 @@ describe('AltaAfluenciaForm', () => { ) }) - it('con error de red muestra un mensaje reintentable', async () => { + it('con error de red muestra aviso amarillo: el registro se guardó igualmente', async () => { fetchMock.mockRejectedValueOnce(new Error('red caída')) render( undefined} onPasarAAltaCliente={() => undefined} />) llenarRequeridos() fireEvent.click(screen.getByRole('button', { name: /Registrar en afluencia/ })) - expect((await screen.findByRole('alert')).textContent).toContain( - 'No se pudo registrar el lead. Revisa tu conexión e intenta de nuevo.', + expect((await screen.findByRole('status')).textContent).toContain( + 'La afluencia se registró, pero hubo un problema de conexión. Verifica en Mi día.', ) }) diff --git a/apps/alta-clientes/src/components/AltaAfluenciaForm.tsx b/apps/alta-clientes/src/components/AltaAfluenciaForm.tsx index 0269166..7758c9d 100644 --- a/apps/alta-clientes/src/components/AltaAfluenciaForm.tsx +++ b/apps/alta-clientes/src/components/AltaAfluenciaForm.tsx @@ -75,6 +75,7 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente, valor const [leadCreado, setLeadCreado] = useState(null) const [asignacion, setAsignacion] = useState<'capturista' | 'round-robin' | null>(null) const [aviso, setAviso] = useState('') + const [avisoTipo, setAvisoTipo] = useState<'error' | 'advertencia'>('error') const cambiar = (campo: keyof typeof FORM_INICIAL) => (e: { target: { value: string } }) => { setForm((prev) => ({ ...prev, [campo]: e.target.value })) @@ -190,8 +191,15 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente, valor }) setAsignacion(res.lead.asignacion ?? 'round-robin') } else if (res.motivo === 'duplicado') { + setAvisoTipo('error') setAviso(`Ya existe un lead con ese teléfono: ${res.nombre}.`) + } else if (res.motivo === 'conexion') { + // El backend es local: el registro YA se guardó aunque el fetch se haya + // cortado (timeout/red). Aviso amarillo en vez de error rojo. + setAvisoTipo('advertencia') + setAviso('La afluencia se registró, pero hubo un problema de conexión. Verifica en Mi día.') } else { + setAvisoTipo('error') setAviso('No se pudo registrar el lead. Revisa tu conexión e intenta de nuevo.') } } @@ -583,7 +591,13 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente, valor - {aviso && ( + {aviso && avisoTipo === 'advertencia' && ( +

+ {aviso} +

+ )} + + {aviso && avisoTipo === 'error' && (

{aviso}

diff --git a/apps/alta-clientes/src/forms/CoacreditadoForm.tsx b/apps/alta-clientes/src/forms/CoacreditadoForm.tsx index de73463..c2a0ac4 100644 --- a/apps/alta-clientes/src/forms/CoacreditadoForm.tsx +++ b/apps/alta-clientes/src/forms/CoacreditadoForm.tsx @@ -17,9 +17,28 @@ export default function CoacreditadoForm({ valores, onAnterior, onSiguiente }: P }) const tieneCoacreditado = metodos.watch('tieneCoacreditado') + // "El cliente no tiene co-acreditado" es el espejo del toggle: marcado oculta + // todos los campos y el schema valida sin datos (superRefine solo exige el + // nombre cuando hay co-acreditado). + const cambiarSinCoacreditado = (e: { target: { checked: boolean } }) => { + metodos.setValue('tieneCoacreditado', !e.target.checked, { shouldValidate: false }) + metodos.clearErrors() + } + return (
+ +