Appearance
Conversión de Presupuesto CRM a Prefa
Módulo: ventas (originado desde crm/presupuestos) Tipo: Process Estado: Implementado Fecha: 2026-03-13
Descripción
Proceso que convierte un presupuesto CRM aprobado en un comprobante pendiente a facturar (prefa) de tipo PRESUPUESTO CRM. El envío es opt-in: el usuario decide si convierte o no al momento de aprobar, y el presupuesto queda aprobado independientemente.
Una vez convertido, el usuario puede sincronizar cambios posteriores al presupuesto mientras la prefa no tenga factura asociada.
Precondiciones:
- Presupuesto con
approved = true - El contacto del registro CRM debe tener cliente asignado (
contacto.CLIENTE_ID) - El módulo Ventas habilitado en la empresa
- Al menos un vendedor configurado como vendedor por defecto (
ordven.defecto = true) - Si el presupuesto no tiene ítems: artículo por defecto configurado en
DataConfig.articulo_defecto_id
Frontend
Botón "Generar Prefa"
Visible en la fila del presupuesto cuando:
approved = true- No existe prefa vinculada (
prefaIdausente)
No visible cuando el presupuesto no está aprobado o ya tiene prefa.
Modal de confirmación (ConvertirAPrefaModal)
Se muestra al hacer clic en "Generar Prefa". Solicita confirmación antes de ejecutar la conversión. El botón de confirmación se deshabilita mientras la solicitud está en curso.
Modal de asignación de cliente (AssignClienteModal)
Si el backend devuelve un error indicando que el contacto no tiene cliente asignado, se abre automáticamente este modal. El usuario debe seleccionar un cliente existente del catálogo de clientes antes de poder continuar. Una vez asignado, la conversión se reintenta automáticamente.
El modal no permite crear clientes nuevos — solo asignar uno existente al contacto.
Botón "Sincronizar"
Visible en la fila del presupuesto cuando:
approved = true- Existe prefa vinculada (
prefaIdpresente) - La prefa no tiene factura (
facturaIdausente)
Permite actualizar la prefa con los datos actuales del presupuesto (ítems y cabecera) tras haberlo modificado.
Edición de presupuestos aprobados con prefa
Un presupuesto aprobado que tiene prefa sin factura puede editarse normalmente. El formulario de edición se habilita. Si la prefa ya tiene factura, el presupuesto vuelve a ser inmutable.
Backend
Endpoint de conversión
POST /mod-crm/crm-types/{tipoCrmId}/records/{recordId}/presupuestos/{budgetId}/convertRespuestas:
201 Created— conversión exitosa, retorna{ prefa_id, budget_id }409 Conflict— el presupuesto ya fue convertido422 Unprocessable Entity— presupuesto no aprobado, contacto sin cliente, vendedor por defecto no configurado, o presupuesto sin ítems y sin artículo por defecto configurado
Endpoint de sincronización
POST /mod-crm/crm-types/{tipoCrmId}/records/{recordId}/presupuestos/{budgetId}/sync-prefaRespuestas:
200 OK— sincronización exitosa422 Unprocessable Entity— prefa no existe o tiene factura asociada
Servicio (BudgetPrefaService)
Orquesta la conversión en tres fases dentro de una única transacción:
Fase 1 — Validación:
- Presupuesto existe y está aprobado
- No existe entrada previa en
budget_prefapara esebudget_id - El contacto del CRM record tiene
CLIENTE_IDasignado - Existe vendedor con
defecto = trueenordven - Si el presupuesto no tiene ítems:
DataConfig.articulo_defecto_idestá configurado
Fase 2 — Resolución de datos:
- Cliente (
zf):crm_records.contact_id → contacto.CLIENTE_ID → ordcon.CNRO - Vendedor (
vend): vendedor condefecto = trueenordven - Lista de precios:
ordcon.NROLISdel cliente; si no tiene →empres.lista - Fecha de entrega:
budget.date + DataConfig.dias_entrega_prefa(default: 7 días si la clave no está configurada) - Provincia: del cliente (
ordcon) - Ítems: ítems del presupuesto; si no hay ítems → un ítem con el artículo por defecto (cantidad 1)
Fase 3 — Creación atómica:
- INSERT en
prefa(tipo PRESUPUESTO CRM,modo = NULL,stock = false) - INSERT en
iterem(ítems de la prefa) - Incremento del numerador en
preformul - INSERT en
budget_prefa(relaciónbudget_id ↔ prefa_id)
Modelo BudgetPrefa
Gestiona la tabla budget_prefa:
existsByBudgetId(int $budgetId): boolgetByBudgetId(int $budgetId): ?arrayinsert(int $budgetId, int $prefaId): void
Tipo de comprobante
Nuevo tipo PRESUPUESTO CRM en tabla preformul, distinto del tipo PEDIDO. Tiene numerador propio e independiente. El campo modo de la prefa queda NULL al convertir — se determina al facturar.
Casos de uso
Caso 1: Conversión exitosa con ítems
- Usuario aprueba presupuesto con ítems y contacto con cliente asignado
- Hace clic en "Generar Prefa" → modal de confirmación
- Confirma → prefa creada con los ítems del presupuesto
- Botón "Generar Prefa" desaparece; aparece botón "Sincronizar"
Caso 2: Contacto sin cliente asignado
- Usuario hace clic en "Generar Prefa"
- Backend retorna 422 por
CLIENTE_IDfaltante - Frontend abre
AssignClienteModal - Usuario selecciona cliente → contacto actualizado → conversión reintentada automáticamente
Caso 3: Presupuesto sin ítems con artículo por defecto
- Presupuesto aprobado sin ítems
DataConfig.articulo_defecto_idconfigurado- Conversión exitosa → prefa creada con un ítem del artículo por defecto (cantidad 1)
Caso 4: Segundo intento de conversión
- Presupuesto ya convertido (entrada en
budget_prefa) - Se llama nuevamente al endpoint
- Retorna 409 Conflict — no se crea una segunda prefa
Caso 5: Modificación y sincronización
- Presupuesto aprobado con prefa sin factura
- Usuario modifica el presupuesto (posible porque prefa no tiene factura)
- Usuario hace clic en "Sincronizar"
- Prefa actualizada con los ítems y cabecera actuales del presupuesto
Consideraciones técnicas
- Idempotencia: UNIQUE constraint en
budget_prefa.budget_idgarantiza que un presupuesto produce como máximo una prefa a nivel de base de datos. - Transaccionalidad: Creación de prefa, ítems y registro en
budget_prefaen una sola transacción. Si cualquier paso falla, no quedan datos parciales. - Modo prueba/oficial: El campo
modode la prefa esNULLal convertir. Se determina al momento de facturar — el flujo de facturación existente no requiere cambios. - Multi-tenant: Toda la operación usa la conexión
oficialdel schema activo (víaX-Schema). Sin referencias cross-schema hardcodeadas. - Vendedor por defecto: Mecanismo de exclusividad en
ordven.defecto— solo un vendedor puede tenerdefecto = truea la vez (mismo patrón que clientes). - Artículo por defecto: Configurable en
DataConfig.articulo_defecto_id. Si no está configurado y el presupuesto no tiene ítems, la conversión se bloquea con mensaje accionable. - Fecha de entrega: Configurable en
DataConfig.dias_entrega_prefa. Default de 7 días si la clave no existe. - Sincronización: Solo actualiza ítems y cabecera del presupuesto. No recalcula vendedor ni fecha de entrega.