ROMANO PARTNERS

PROPERTY CARE · MANAGEMENT

// ===== DEMONSTRACIÓN 360: dados fictícios + fluxo de dispatch ===== const DEMO_CLIENTS_FULL=[ ...DEMO_CLIENTS, {id:"demo-c6",name:"Madrid Executive Stays",email:"ops@madrid-exec.demo",phone:"+34 600 100 206"}, {id:"demo-c7",name:"Marbella Select Homes",email:"ops@marbella-select.demo",phone:"+34 600 100 207"} ]; const DEMO_PROPERTIES_FULL=[ ...DEMO_PROPERTIES, {id:"demo-p6",code:"MES-T2-006",name:"Madrid Executive · Chamberí",property_type:"T2",address:"Calle de Santa Engracia 27",city:"Madrid",country:"España",client_id:"demo-c6",status:"Activo"}, {id:"demo-p7",code:"MES-T3-007",name:"Madrid Executive · Salamanca",property_type:"T3",address:"Calle Velázquez 61",city:"Madrid",country:"España",client_id:"demo-c6",status:"Activo"}, {id:"demo-p8",code:"MBS-T2-008",name:"Marbella Select · Puerto Banús",property_type:"T2",address:"Avenida Julio Iglesias 14",city:"Marbella",country:"España",client_id:"demo-c7",status:"Activo"}, {id:"demo-p9",code:"HBS-T1-009",name:"Hotel Boutique · Salamanca 1",property_type:"T1",address:"Calle Toro 8",city:"Salamanca",country:"España",client_id:"demo-c5",status:"Activo"}, {id:"demo-p10",code:"HBS-T2-010",name:"Hotel Boutique · Salamanca 2",property_type:"T2",address:"Calle Zamora 19",city:"Salamanca",country:"España",client_id:"demo-c5",status:"Activo"}, {id:"demo-p11",code:"MLS-T4-011",name:"Luxury Stay · Málaga Este",property_type:"T4",address:"Paseo de Sancha 52",city:"Málaga",country:"España",client_id:"demo-c3",status:"Activo"}, {id:"demo-p12",code:"MLS-T3-012",name:"Luxury Stay · Málaga Larios",property_type:"T3",address:"Calle Larios 31",city:"Málaga",country:"España",client_id:"demo-c3",status:"En servicio"} ]; const DEMO_EMPLOYEES_FULL=[ {id:"demo-e1",employee_code:"RPC-001",name:"Ana Silva",function_title:"Limpieza · 5★",salary:1450,phone:"+34 610 100 301",email:"ana.demo@romano.test",active:true,avatar:"👩🏻‍🧹",availability:"Disponible",distance:1.2}, {id:"demo-e2",employee_code:"RPC-002",name:"Carla Santos",function_title:"Limpieza · Supervisión",salary:1650,phone:"+34 610 100 302",email:"carla.demo@romano.test",active:true,avatar:"👩🏻‍🧹",availability:"En servicio",distance:2.4}, {id:"demo-e3",employee_code:"RPC-003",name:"Pedro Costa",function_title:"Pintura y mantenimiento",salary:1600,phone:"+34 610 100 303",email:"pedro.demo@romano.test",active:true,avatar:"👨🏻‍🎨",availability:"Disponible",distance:3.1}, {id:"demo-e4",employee_code:"RPC-004",name:"João Martins",function_title:"Mantenimiento",salary:1580,phone:"+34 610 100 304",email:"joao.demo@romano.test",active:true,avatar:"👨🏻‍🔧",availability:"Disponible",distance:4.2}, {id:"demo-e5",employee_code:"RPC-005",name:"Sofía García",function_title:"Limpieza · 5★",salary:1480,phone:"+34 610 100 305",email:"sofia.demo@romano.test",active:true,avatar:"👩🏻‍🧹",availability:"Disponible",distance:4.8}, {id:"demo-e6",employee_code:"RPC-006",name:"María López",function_title:"Limpieza · Check-in/out",salary:1500,phone:"+34 610 100 306",email:"maria.demo@romano.test",active:true,avatar:"👩🏻‍💼",availability:"Disponible",distance:5.2}, {id:"demo-e7",employee_code:"RPC-007",name:"Daniel Ramos",function_title:"Mantenimiento",salary:1590,phone:"+34 610 100 307",email:"daniel.demo@romano.test",active:true,avatar:"👨🏻‍🔧",availability:"Disponible",distance:5.7}, {id:"demo-e8",employee_code:"RPC-008",name:"Lucía Martín",function_title:"Limpieza · 5★",salary:1470,phone:"+34 610 100 308",email:"lucia.demo@romano.test",active:true,avatar:"👩🏻‍🧹",availability:"Disponible",distance:6.1}, {id:"demo-e9",employee_code:"RPC-009",name:"Marta Fernández",function_title:"Supervisión",salary:1700,phone:"+34 610 100 309",email:"marta.demo@romano.test",active:true,avatar:"👩🏻‍💼",availability:"Supervisión",distance:6.8}, {id:"demo-e10",employee_code:"RPC-010",name:"Andrés Ruiz",function_title:"Pintura",salary:1550,phone:"+34 610 100 310",email:"andres.demo@romano.test",active:true,avatar:"👨🏻‍🎨",availability:"Disponible",distance:7.2}, {id:"demo-e11",employee_code:"RPC-011",name:"Elena Torres",function_title:"Limpieza · 5★",salary:1490,phone:"+34 610 100 311",email:"elena.demo@romano.test",active:true,avatar:"👩🏻‍🧹",availability:"Disponible",distance:7.6}, {id:"demo-e12",employee_code:"RPC-012",name:"Miguel Santos",function_title:"Mantenimiento",salary:1600,phone:"+34 610 100 312",email:"miguel.demo@romano.test",active:true,avatar:"👨🏻‍🔧",availability:"Disponible",distance:8.1}, {id:"demo-e13",employee_code:"RPC-013",name:"Inés Castro",function_title:"Limpieza · 5★",salary:1460,phone:"+34 610 100 313",email:"ines.demo@romano.test",active:true,avatar:"👩🏻‍🧹",availability:"Disponible",distance:8.7}, {id:"demo-e14",employee_code:"RPC-014",name:"Rafael Díaz",function_title:"Mantenimiento",salary:1620,phone:"+34 610 100 314",email:"rafael.demo@romano.test",active:true,avatar:"👨🏻‍🔧",availability:"Disponible",distance:9.3}, {id:"demo-e15",employee_code:"RPC-015",name:"Paula Moreno",function_title:"Limpieza · Supervisión",salary:1680,phone:"+34 610 100 315",email:"paula.demo@romano.test",active:true,avatar:"👩🏻‍💼",availability:"Disponible",distance:10.1}, {id:"demo-e16",employee_code:"RPC-016",name:"Diego Navarro",function_title:"Pintura y mantenimiento",salary:1580,phone:"+34 610 100 316",email:"diego.demo@romano.test",active:true,avatar:"👨🏻‍🎨",availability:"Disponible",distance:11.4} ]; const DEMO_SERVICE_STATE={}; const DEMO_DISPATCH={}; function demoEmployees(){return DEMO_EMPLOYEES_FULL} function demoClients(){return DEMO_CLIENTS_FULL} function demoProperties(){return DEMO_PROPERTIES_FULL} function demoFindProperty(id){return demoProperties().find(x=>x.id===id)} function demoFindClient(id){return demoClients().find(x=>x.id===id)} function demoFindEmployee(id){return demoEmployees().find(x=>x.id===id)} function demoPriceFor(code){ const all=[...MASTER_PRICES.cleaning,...MASTER_PRICES.maintenance,...MASTER_PRICES.linen,...MASTER_PRICES.amenities,...MASTER_PRICES.laundry]; return all.find(x=>x.code===code)?.price??0 } function demoEnsureDispatch(service){ if(!DEMO_DISPATCH[service.id]){ const eligible=demoEmployees().filter(e=>e.active).sort((a,b)=>a.distance-b.distance).slice(0,5); DEMO_DISPATCH[service.id]={index:0,candidates:eligible.map(e=>({employeeId:e.id,response:"pending",distance:e.distance,expiresAt:Date.now()+300000}))}; } return DEMO_DISPATCH[service.id] } function demoCurrentCandidate(serviceId){const q=DEMO_DISPATCH[serviceId];return q?.candidates[q.index]||null} function demoMoveNext(serviceId,reason){ const s=DEMO_SERVICES.find(x=>x.id===serviceId);const q=demoEnsureDispatch(s); if(q.indexx.id===serviceId);if(!s)return; const q=demoEnsureDispatch(s),candidate=q.candidates[q.index],e=demoFindEmployee(candidate.employeeId); candidate.response="accepted";candidate.respondedAt=new Date().toISOString(); s.assigned_employee_id=e.id;s.employee_name=e.name;s.status="scheduled";s.accepted_at=new Date().toISOString(); closeModal();toast("Servicio aceptado por "+e.name);renderServicesMaster(); } function demoRejectService(serviceId){demoMoveNext(serviceId,"rejected");viewService(serviceId);renderServicesMaster()} function demoExpireService(serviceId){demoMoveNext(serviceId,"expired");viewService(serviceId);renderServicesMaster()} async function renderClients(){ if(!demoMode){const r=await sb.from("clients").select("id,name,email,phone,created_at").order("created_at",{ascending:false});return renderClientsReal(r)} const data=demoClients(); $("content").innerHTML='

Clientes

Base ficticia para probar la operación como en producción.
DEMONSTRACIÓN · NO REAL
Clientes activos'+data.length+'
Propiedades vinculadas'+demoProperties().length+'
Servicios del mes38
Facturación demo€ 4.250
'+data.map(x=>{const ps=demoProperties().filter(p=>p.client_id===x.id);return ''}).join("")+'
ClienteContactoPropiedadesServiciosEstado
'+esc(x.name)+''+esc(x.email)+''+esc(x.phone)+''+ps.length+''+([1,8,6,5,11][data.indexOf(x)]||4)+'Activo
'; } function demoClientCard(id){const c=demoFindClient(id),ps=demoProperties().filter(p=>p.client_id===id);openModal("Ficha del cliente · DEMOSTRACIÓN",'
DATOS FICTICIOS
Cliente

'+esc(c.name)+'

Contacto

'+esc(c.phone)+'

Email

'+esc(c.email)+'

Código

CLI-'+c.id.slice(-2).toUpperCase()+'

Propiedades vinculadas

'+ps.map(p=>'
'+esc(p.code)+' · '+esc(p.name)+''+esc(p.property_type)+'
').join("")+'
')} async function renderProperties(){ if(!demoMode){const r=await sb.from("properties").select("id,code,name,property_type,address,city,country,client_id,status").order("created_at",{ascending:false});return renderPropertiesReal(r)} const data=demoProperties(); $("content").innerHTML='

Propiedades / Apartamentos

T1–T5 · clientes · ubicación · estado operativo.
12 PROPIEDADES DEMO
Total12
Madrid6
Málaga3
Marbella/Salamanca3
'+data.map(x=>'').join("")+'
CódigoPropiedadClienteTipoUbicaciónEstado
'+esc(x.code)+''+esc(x.name)+''+esc(demoFindClient(x.client_id)?.name||"—")+''+esc(x.property_type)+''+esc(x.city)+''+esc(x.status)+'
'; } function demoPropertyCard(id){const p=demoFindProperty(id),c=demoFindClient(p.client_id);openModal("Ficha del apartamento · DEMOSTRACIÓN",'
DATOS FICTICIOS

'+esc(p.name)+'

'+esc(p.address)+' · '+esc(p.city)+', '+esc(p.country)+'

'+esc(p.code)+' · '+esc(p.property_type)+'
Cliente

'+esc(c?.name||"—")+'

Estado

'+esc(p.status)+'

')} async function renderEmployees(){ if(!demoMode){const r=await sb.from("employee_profiles").select("id,employee_code,phone,email,nif_nie,hire_date,function_title,salary,active").order("created_at",{ascending:false});return renderEmployeesReal(r)} const data=demoEmployees(); $("content").innerHTML='

Funcionarios · Ficha profesional

16 profesionales fictícios para testar escala, salários, disponibilidade e dispatch.
16 FUNCIONARIOS DEMO
Funcionarios16
Disponibles'+data.filter(e=>e.availability==="Disponible").length+'
Coste salarial demo€ '+data.reduce((a,e)=>a+e.salary,0).toLocaleString("es-ES")+'
En operación2
'+data.map(e=>'').join("")+'
FuncionárioCódigoFunçãoSalário demoDisponibilidad
'+e.avatar+''+esc(e.name)+'
'+esc(e.employee_code)+''+esc(e.function_title)+''+masterMoney(e.salary)+''+esc(e.availability)+'
'; } function demoEmployeeCard(id){const e=demoFindEmployee(id);openModal("Ficha do funcionário · DEMOSTRACIÓN",'
'+e.avatar+'

'+esc(e.name)+'

'+esc(e.function_title)+'
Código

'+e.employee_code+'

Salário demo

'+masterMoney(e.salary)+'

Telefone

'+e.phone+'

Email

'+e.email+'

Este registro é fictício e não altera a folha real.
')} async function renderServicesMaster(){ if(!demoMode){return renderServicesMasterReal()} const data=DEMO_SERVICES; $("content").innerHTML='

Serviços · operação 360

Pedido → dispatch → aceitação/rejeição → execução → supervisão → finalização → faturação.
DEMONSTRACIÓN
Pedidos hoje'+data.length+'
A camino'+data.filter(x=>x.status==="on_the_way").length+'
En servicio'+data.filter(x=>x.status==="in_progress").length+'
Finalizados'+data.filter(x=>x.status==="completed").length+'
'+data.map(x=>{const p=demoFindProperty(x.property_id),cl=demoFindClient(p?.client_id);return ''}).join("")+'
CódigoCliente / ApartamentoServiçoFuncionárioEstadoValor demo
'+esc(x.code)+''+esc(cl?.name||"—")+''+esc(p?.name||"—")+''+esc(x.service_type)+' · '+esc(x.typology)+''+esc(x.employee_name||"Aguardando dispatch")+''+esc(statusNames[x.status]||x.status)+''+masterMoney(x.demo_total||demoPriceFor("cleaning_t2_1b"))+'
'; } async function viewService(id){ if(!demoMode){return viewServiceReal(id)} const x=DEMO_SERVICES.find(s=>s.id===id);if(!x)return; const p=demoFindProperty(x.property_id),cl=demoFindClient(p?.client_id),q=demoEnsureDispatch(x),cur=demoCurrentCandidate(id),e=cur?demoFindEmployee(cur.employeeId):null; const attempts=q.candidates.map((a,i)=>{const emp=demoFindEmployee(a.employeeId);return '
'+emp.avatar+'
'+esc(emp.name)+''+esc(emp.function_title)+' · '+a.distance+' km · '+esc(a.response)+'
'+esc(a.response==="pending"?"Aguardando":a.response==="accepted"?"Aceitou":a.response==="rejected"?"Rejeitou":"Expirou")+'
'}).join(""); const current=cur&&cur.response==="pending"?'
Solicitud enviada a '+esc(e.name)+'
'+esc(e.function_title)+' · '+cur.distance+' km
05:00
Se rejeitar ou expirar, o sistema registra a ocorrência e aciona o próximo candidato elegível.
':'
Dispatch atual: '+esc(x.employee_name||"sem funcionário")+' · estado '+esc(statusNames[x.status]||x.status)+'
'; openModal("Ficha del servicio · DEMOSTRACIÓN",'
FLUXO FICTÍCIO · NÃO ALTERA PRODUÇÃO

'+esc(x.service_type)+' '+esc(x.typology)+'

'+esc(cl?.name||"Cliente")+' · '+esc(p?.name||"Apartamento")+'

'+esc(x.code)+' · '+esc(p?.address||"Madrid")+'
Valor serviço'+masterMoney(x.demo_total||40)+'
IVA21%
Prioridad'+esc(x.priority||"normal")+'
Estado'+esc(statusNames[x.status]||x.status)+'
'+current+'

Histórico de dispatch

'+q.candidates.length+' candidatos
'+attempts+'
'); } async function newServicePremium(){ if(!demoMode){return newServicePremiumReal()} const options=demoProperties().map(p=>'').join(""); const prices=MASTER_PRICES.cleaning.filter(x=>x.price!=null).map(x=>'').join(""); openModal("Nuevo servicio · DEMOSTRACIÓN",'
SIMULA A VIDA REAL · NADA É SALVO NO BANCO

Resumo financeiro

O preço demonstrativo é derivado da tabela de serviço existente; não altera valores reais.
'); $("demoDate").value=new Date(Date.now()+3600000).toISOString().slice(0,16);demoRecalc(); } function demoFilterProperties(){const cid=$("demoClient").value;const ps=demoProperties().filter(p=>p.client_id===cid);$("demoProperty").innerHTML=ps.map(p=>'').join("")||'';demoRecalc()} function demoRecalc(){const p=demoPriceFor($("demoPricing")?.value);if($("demoTotal"))$("demoTotal").textContent=masterMoney(p)} function demoCreateService(e){e.preventDefault();const code="DEMO-SRV-"+String(DEMO_SERVICES.length+1).padStart(3,"0"),p=demoFindProperty($("demoProperty").value),price=demoPriceFor($("demoPricing").value);const service={id:"demo-"+crypto.randomUUID(),code,service_type:($("demoPricing").selectedOptions[0]?.textContent||"Limpieza").split(" · ")[0],typology:p?.property_type||"T2",status:"requested",priority:$("demoPriority").value,scheduled_at:new Date($("demoDate").value).toISOString(),property_id:p?.id||null,assigned_employee_id:null,employee_name:null,demo_total:price,instructions:$("demoInstructions").value};DEMO_SERVICES.unshift(service);demoEnsureDispatch(service);closeModal();toast("Pedido criado · dispatch iniciado");renderServicesMaster();renderDashboardReference()} async function renderEmployeesPremium(){return renderEmployees()} async function renderClientsPremium(){return renderClients()} async function renderPropertiesPremium(){return renderProperties()}