refactor: las afluencias viven en Oportunidades (no en Leads)

- POST /api/leads crea UN solo item en Oportunidades (Estado Afluencia)
  con todo el perfil (desarrollo, modelo, estado civil, credito, etc.)
  en las columnas del board; dedup por telefono contra Oportunidades;
  asignacion al capturista via columna Asesor o round-robin del equipo.
- Round-robin, escalado e inactividad operan sobre Oportunidades (solo
  etapa Afluencia sin asesor entra al reparto).
- Acciones del vendedor (contactado/cita/descartar) escriben directo en
  la oportunidad; descarte = Perdido + Motivo No Compra.
- Mi dia y reportes leen Oportunidades; el funnel de /dash arranca en
  Afluencia; misOportunidades.porEtapa ya es real (columna Asesor).
- El picker del alta busca en Leads (historico) Y Oportunidades, con
  insignia de origen; moverAAparto tolera ambos ids.
- Lectura de status con value null = vacio (quirk del default Monday).
- Script de migracion del historial de agosto Leads -> Oportunidades.
This commit is contained in:
2026-09-04 04:46:34 +00:00
parent d4fc9ee892
commit 845f314560
26 changed files with 1496 additions and 985 deletions

View File

@@ -225,9 +225,11 @@ export interface LeadResumen {
email: string
estado: string
origen: string
/** 'lead' = archivo histórico (tablero Leads); 'oportunidad' = modelo nuevo (tablero Oportunidades) */
fuente: 'lead' | 'oportunidad'
}
/** GET /leads/buscar?q= — autocompletado para prellenar el alta desde un lead */
/** GET /leads/buscar?q= — autocompletado para prellenar el alta (busca en AMBOS tableros) */
export async function buscarLeads(q: string): Promise<LeadResumen[]> {
const res = await fetch(`${API_URL}/leads/buscar?q=${encodeURIComponent(q)}`, conAuth())
manejar401(res)
@@ -236,8 +238,9 @@ export async function buscarLeads(q: string): Promise<LeadResumen[]> {
}
export interface LeadCreado {
/** id de la oportunidad creada (la afluencia ES la oportunidad: id === oportunidadId) */
id: string
estado: string
estado: string // 'Afluencia'
oportunidadId: string | null
/** 'capturista' = asignado a quien lo registró; 'round-robin' = entra al reparto */
asignacion?: 'capturista' | 'round-robin'
@@ -250,9 +253,10 @@ export type ResultadoCrearLead =
| { ok: false; motivo: 'conexion' }
/**
* POST /leads — captura ligera de afluencia: crea el lead en Monday y su
* oportunidad en etapa Afluencia (round-robin). Si el teléfono ya existe el
* API responde 409 con el lead existente (motivo 'duplicado').
* POST /leads — captura ligera de afluencia: crea UN item en el tablero
* Oportunidades (etapa Afluencia, con todo el perfil; la asigna el capturista
* o el round-robin). Si el teléfono ya existe el API responde 409 con la
* oportunidad existente (motivo 'duplicado').
*/
export async function crearLead(payload: {
nombre: string