From dc43cbe1ce979905173f1934e40880dd5f00c503 Mon Sep 17 00:00:00 2001 From: Consult AS Technology Date: Tue, 8 Sep 2026 02:47:29 +0000 Subject: [PATCH] feat(clientes-maestro): modal detalle + fix permisos roles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Modal con 28 campos agrupados por sección (Datos, Contacto, Crédito, Propiedad, Marketing, Venta) - Click en fila abre modal - Dark mode completo - Agregado 'clientes-maestro' a todos los roles --- .../src/components/ClientesMaestroTable.tsx | 5 +- .../src/components/ModalClienteMaestro.tsx | 104 ++++++++++++++++++ .../landing/src/pages/ClientesMaestroPage.tsx | 9 ++ 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 apps/landing/src/components/ModalClienteMaestro.tsx diff --git a/apps/landing/src/components/ClientesMaestroTable.tsx b/apps/landing/src/components/ClientesMaestroTable.tsx index 305d936..b06cf9f 100644 --- a/apps/landing/src/components/ClientesMaestroTable.tsx +++ b/apps/landing/src/components/ClientesMaestroTable.tsx @@ -9,10 +9,11 @@ interface Props { cargando: boolean onPageChange: (pagina: number) => void onExportar: () => void + onRowClick?: (cliente: ClienteMaestro) => void } /** Tabla paginada de clientes maestro con dark mode completo */ -export function ClientesMaestroTable({ datos, total, pagina, limite, cargando, onPageChange, onExportar }: Props) { +export function ClientesMaestroTable({ datos, total, pagina, limite, cargando, onPageChange, onExportar, onRowClick }: Props) { const totalPaginas = Math.ceil(total / limite) const inicio = (pagina - 1) * limite + 1 const fin = Math.min(pagina * limite, total) @@ -108,7 +109,7 @@ export function ClientesMaestroTable({ datos, total, pagina, limite, cargando, o {datos.map((c) => ( - + onRowClick?.(c)}> {c.numCliente} {c.nombre} {c.apellidoPaterno} {c.apellidoMaterno ?? ''} diff --git a/apps/landing/src/components/ModalClienteMaestro.tsx b/apps/landing/src/components/ModalClienteMaestro.tsx new file mode 100644 index 0000000..fd14f05 --- /dev/null +++ b/apps/landing/src/components/ModalClienteMaestro.tsx @@ -0,0 +1,104 @@ +import { X } from 'lucide-react' +import type { ClienteMaestro } from '../services/clientes-maestro' + +interface Props { + cliente: ClienteMaestro + onCerrar: () => void +} + +const SECCIONES = [ + { titulo: 'Datos Personales', campos: [ + { key: 'numCliente', label: '# Cliente' }, + { key: 'nombre', label: 'Nombre' }, + { key: 'apellidoPaterno', label: 'Apellido Paterno' }, + { key: 'apellidoMaterno', label: 'Apellido Materno' }, + { key: 'sexo', label: 'Sexo' }, + { key: 'estadoCivil', label: 'Estado Civil' }, + { key: 'rangoEdad', label: 'Rango Edad' }, + ]}, + { titulo: 'Contacto', campos: [ + { key: 'telefono', label: 'Teléfono' }, + { key: 'email', label: 'Email' }, + ]}, + { titulo: 'Crédito e Ingresos', campos: [ + { key: 'credito', label: 'Tipo Crédito' }, + { key: 'institucion', label: 'Institución' }, + { key: 'rangoIngresos', label: 'Rango Ingresos' }, + { key: 'ocupacion', label: 'Ocupación' }, + ]}, + { titulo: 'Propiedad', campos: [ + { key: 'desarrollo', label: 'Desarrollo' }, + { key: 'manzana', label: 'Manzana' }, + { key: 'lote', label: 'Lote' }, + { key: 'interior', label: 'Interior' }, + { key: 'tipoCasa', label: 'Tipo Casa' }, + { key: 'etapa', label: 'Etapa' }, + { key: 'valorTotal', label: 'Valor Total', format: (v: unknown) => v != null ? `$${Number(v).toLocaleString('es-MX')}` : '-' }, + ]}, + { titulo: 'Marketing', campos: [ + { key: 'medioContacto', label: 'Medio Contacto' }, + { key: 'canal', label: 'Canal' }, + ]}, + { titulo: 'Venta', campos: [ + { key: 'estatus', label: 'Estatus' }, + { key: 'vendedor', label: 'Vendedor' }, + { key: 'vendedorId', label: 'ID Vendedor' }, + ]}, +] + +export default function ModalClienteMaestro({ cliente, onCerrar }: Props) { + return ( +
+
+
+
+

+ {cliente.nombre} {cliente.apellidoPaterno} +

+

+ Cliente #{cliente.numCliente} +

+
+ +
+ +
+ {SECCIONES.map((sec) => { + const camposConValor = sec.campos.filter((c) => { + const v = cliente[c.key as keyof ClienteMaestro] + return v != null && v !== '' + }) + if (camposConValor.length === 0) return null + return ( +
+

+ {sec.titulo} +

+
+ {camposConValor.map((c) => { + const val = cliente[c.key as keyof ClienteMaestro] + const display = c.format ? c.format(val) : String(val) + return ( +
+
{c.label}
+
{display}
+
+ ) + })} +
+
+ ) + })} +
+ +
+ +
+
+
+ ) +} diff --git a/apps/landing/src/pages/ClientesMaestroPage.tsx b/apps/landing/src/pages/ClientesMaestroPage.tsx index 4245f14..519d6ca 100644 --- a/apps/landing/src/pages/ClientesMaestroPage.tsx +++ b/apps/landing/src/pages/ClientesMaestroPage.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useState } from 'react' import { listarClientesMaestro, exportarClientesMaestro, type ClienteMaestro } from '../services/clientes-maestro' import { ClientesMaestroTable } from '../components/ClientesMaestroTable' +import ModalClienteMaestro from '../components/ModalClienteMaestro' import { getSesion } from '../services/auth' import toast from 'react-hot-toast' @@ -19,6 +20,7 @@ const ESTATUSES = ['', 'VENDIDO', 'APARTADO', 'ESCRITURADO', 'PENDIENTE', 'CANCE */ export function ClientesMaestroPage({ onVolver }: Props) { const [datos, setDatos] = useState([]) + const [clienteSeleccionado, setClienteSeleccionado] = useState(null) const [total, setTotal] = useState(0) const [pagina, setPagina] = useState(1) const [cargando, setCargando] = useState(true) @@ -119,6 +121,7 @@ export function ClientesMaestroPage({ onVolver }: Props) { } return ( + <>
+ {clienteSeleccionado && ( + setClienteSeleccionado(null)} /> + )} + ) + }