feat(captura): módulo de captura por departamentos
Backend: tabla CapturaDepartamento, 5 departamentos, 7 desarrollos, service CRUD + importador Excel + 6 endpoints. Frontend: tabla editable inline, tabs por dept, resumen avance, paginación, filtro estatus, permisos por rol, dark mode. 746 API tests + 42 landing tests.
This commit is contained in:
179
apps/landing/src/services/captura.ts
Normal file
179
apps/landing/src/services/captura.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import { API_URL, conAuth, manejar401 } from './auth'
|
||||
|
||||
// ─── Tipos ──────────────────────────────────────────────────────────
|
||||
|
||||
export interface CampoDef {
|
||||
campo: string
|
||||
tipo: string
|
||||
label: string
|
||||
options?: string[]
|
||||
}
|
||||
|
||||
export interface ResumenDepartamento {
|
||||
departamento: string
|
||||
camposTotales: number
|
||||
camposLlenos: number
|
||||
porcentaje: number
|
||||
}
|
||||
|
||||
export type DatosCaptura = Record<string, Record<string, string | null>>
|
||||
|
||||
// ─── Departamentos y campos (espejo del backend) ────────────────────
|
||||
|
||||
export const DEPARTAMENTOS = ['OBRA', 'VENTAS', 'ESCRITURACION', 'FINANZAS', 'POSTVENTA'] as const
|
||||
export type DepartamentoId = (typeof DEPARTAMENTOS)[number]
|
||||
|
||||
export const CAMPOS_POR_DEPARTAMENTO: Record<DepartamentoId, CampoDef[]> = {
|
||||
OBRA: [
|
||||
{ campo: 'certificacion_gas', tipo: 'boolean', label: 'Certificación Gas' },
|
||||
{ campo: 'to_c4_fianza', tipo: 'boolean', label: 'TO y C4-Fianza' },
|
||||
{ campo: 'regimen_inscrito', tipo: 'boolean', label: 'Régimen Inscrito' },
|
||||
{ campo: 'no_oficial', tipo: 'boolean', label: 'No. Oficial' },
|
||||
{ campo: 'clave_catastral', tipo: 'boolean', label: 'Clave Catastral' },
|
||||
{ campo: 'listas_avaluo', tipo: 'boolean', label: 'Listas para Avaluo' },
|
||||
{ campo: 'habitabilidad', tipo: 'date', label: 'Habitabilidad a partir de' },
|
||||
{ campo: 'comentarios_obra', tipo: 'text', label: 'Comentarios' },
|
||||
],
|
||||
VENTAS: [
|
||||
{ campo: 'cliente_id', tipo: 'text', label: 'Cliente ID' },
|
||||
{ campo: 'nombre_cliente', tipo: 'text', label: 'Nombre Cliente' },
|
||||
{ campo: 'ubicacion', tipo: 'text', label: 'Ubicación/Unidad' },
|
||||
{ campo: 'modelo', tipo: 'text', label: 'Modelo' },
|
||||
{ campo: 'tipo_credito', tipo: 'select', label: 'Tipo Crédito', options: ['BANCARIO', 'COFINAVIT', 'INFONAVIT', 'SANTANDER', 'HSBC', 'BBVA', 'FOVISSSTE', 'CONTADO'] },
|
||||
{ campo: 'estatus', tipo: 'select', label: 'Estatus', options: ['DISPONIBLE', 'APARTADO', 'VENDIDO', 'ESCRITURADO'] },
|
||||
{ campo: 'fecha_apartado', tipo: 'date', label: 'Fecha Apartado' },
|
||||
{ campo: 'fecha_venta', tipo: 'date', label: 'Fecha Venta' },
|
||||
{ campo: 'fecha_vencimiento', tipo: 'date', label: 'Fecha Vencimiento Contrato' },
|
||||
{ campo: 'precio_venta', tipo: 'number', label: 'Precio Venta' },
|
||||
],
|
||||
ESCRITURACION: [
|
||||
{ campo: 'fecha_autorizacion', tipo: 'date', label: 'Fecha Autorización' },
|
||||
{ campo: 'fecha_firma', tipo: 'date', label: 'Fecha Firma' },
|
||||
{ campo: 'notaria', tipo: 'text', label: 'Notaría' },
|
||||
{ campo: 'fecha_pago_banco', tipo: 'date', label: 'Fecha Pago Banco' },
|
||||
{ campo: 'fecha_pago_infonavit', tipo: 'date', label: 'Fecha Pago Infonavit' },
|
||||
{ campo: 'fecha_pago_fovissste', tipo: 'date', label: 'Fecha Pago Fovissste' },
|
||||
],
|
||||
FINANZAS: [
|
||||
{ campo: 'otros_ingresos', tipo: 'number', label: 'Otros Ingresos' },
|
||||
{ campo: 'pagado_transferencia', tipo: 'number', label: 'Pagado Transferencia' },
|
||||
{ campo: 'infonavit', tipo: 'number', label: 'Infonavit' },
|
||||
{ campo: 'bancario', tipo: 'number', label: 'Bancario' },
|
||||
{ campo: 'fovissste', tipo: 'number', label: 'Fovissste' },
|
||||
{ campo: 'credito_puente', tipo: 'number', label: 'Crédito Puente' },
|
||||
{ campo: 'saldo_favor', tipo: 'number', label: 'Saldo a Favor' },
|
||||
],
|
||||
POSTVENTA: [
|
||||
{ campo: 'entrega_sac', tipo: 'boolean', label: 'Entrega a SAC' },
|
||||
{ campo: 'comentarios_entrega', tipo: 'text', label: 'Comentarios' },
|
||||
],
|
||||
}
|
||||
|
||||
export const DESARROLLOS = ['SENDA', 'ATELIER', 'ESTANZA', 'DIAGONAL', 'SOLANA', 'SOLE', 'MUTUO'] as const
|
||||
|
||||
/** Labels legibles para cada departamento */
|
||||
export const DEPARTAMENTO_LABELS: Record<DepartamentoId, string> = {
|
||||
OBRA: 'Obra',
|
||||
VENTAS: 'Ventas',
|
||||
ESCRITURACION: 'Escrituración',
|
||||
FINANZAS: 'Finanzas',
|
||||
POSTVENTA: 'Postventa',
|
||||
}
|
||||
|
||||
// ─── Permisos por rol ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Departamentos visibles/editables por cada rol.
|
||||
* Director y admin ven todos.
|
||||
*/
|
||||
export const DEPARTAMENTOS_POR_ROL: Record<string, DepartamentoId[]> = {
|
||||
admin: [...DEPARTAMENTOS],
|
||||
director: [...DEPARTAMENTOS],
|
||||
director_ventas: [...DEPARTAMENTOS],
|
||||
vendedor: ['VENTAS'],
|
||||
escrituracion: ['ESCRITURACION'],
|
||||
cobranza: ['FINANZAS'],
|
||||
postventa: ['POSTVENTA'],
|
||||
obra: ['OBRA'],
|
||||
}
|
||||
|
||||
// ─── API calls ──────────────────────────────────────────────────────
|
||||
|
||||
/** GET /api/captura?desarrollo=X — datos agrupados por unidad */
|
||||
export async function obtenerCaptura(
|
||||
desarrollo: string,
|
||||
departamento?: string,
|
||||
): Promise<DatosCaptura> {
|
||||
const params = new URLSearchParams({ desarrollo })
|
||||
if (departamento) params.set('departamento', departamento)
|
||||
const resp = await fetch(`${API_URL}/captura?${params}`, conAuth())
|
||||
manejar401(resp)
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
return (await resp.json()) as DatosCaptura
|
||||
}
|
||||
|
||||
/** GET /api/captura/unidades?desarrollo=X — lista de códigos de unidad */
|
||||
export async function obtenerUnidadesCaptura(desarrollo: string): Promise<string[]> {
|
||||
const params = new URLSearchParams({ desarrollo })
|
||||
const resp = await fetch(`${API_URL}/captura/unidades?${params}`, conAuth())
|
||||
manejar401(resp)
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
return (await resp.json()) as string[]
|
||||
}
|
||||
|
||||
/** GET /api/captura/resumen?desarrollo=X — avance por departamento */
|
||||
export async function obtenerResumenCaptura(desarrollo: string): Promise<ResumenDepartamento[]> {
|
||||
const params = new URLSearchParams({ desarrollo })
|
||||
const resp = await fetch(`${API_URL}/captura/resumen?${params}`, conAuth())
|
||||
manejar401(resp)
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
return (await resp.json()) as ResumenDepartamento[]
|
||||
}
|
||||
|
||||
/** POST /api/captura — guardar campo individual */
|
||||
export async function guardarCampo(data: {
|
||||
desarrollo: string
|
||||
unidad: string
|
||||
departamento: string
|
||||
campo: string
|
||||
valor: string | null
|
||||
}): Promise<void> {
|
||||
const resp = await fetch(
|
||||
`${API_URL}/captura`,
|
||||
conAuth({
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
desarrollo: data.desarrollo,
|
||||
unidadCodigo: data.unidad,
|
||||
departamento: data.departamento,
|
||||
campo: data.campo,
|
||||
valor: data.valor,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
manejar401(resp)
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
}
|
||||
|
||||
/** POST /api/captura/bulk — guardar múltiples campos */
|
||||
export async function guardarBulk(data: {
|
||||
desarrollo: string
|
||||
departamento: string
|
||||
registros: { unidad: string; campo: string; valor: string | null }[]
|
||||
}): Promise<void> {
|
||||
const resp = await fetch(
|
||||
`${API_URL}/captura/bulk`,
|
||||
conAuth({
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
desarrollo: data.desarrollo,
|
||||
departamento: data.departamento,
|
||||
registros: data.registros,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
manejar401(resp)
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
|
||||
}
|
||||
Reference in New Issue
Block a user