{"id":1095,"date":"2026-05-08T23:25:33","date_gmt":"2026-05-08T21:25:33","guid":{"rendered":"https:\/\/educaenelfuturo.com\/?page_id=1095"},"modified":"2026-05-09T17:40:11","modified_gmt":"2026-05-09T15:40:11","slug":"gestor-de-avisos","status":"publish","type":"page","link":"https:\/\/educaenelfuturo.com\/index.php\/gestor-de-avisos\/","title":{"rendered":"Smart design Iv\u00e1n Otero"},"content":{"rendered":"\n<p><\/p>\n\n\n\n<p><strong><em>Gestor de avisos v.0.2.<\/em><\/strong> 09\/05\/2026<\/p>\n\n\n\n<!DOCTYPE html>\n<html lang=\"es\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>AvisosMana V3 &#8211; CRM Profesional (Offline Mode)<\/title>\n    \n    <!-- Bootstrap 5 CSS -->\n    <link href=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@5.3.3\/dist\/css\/bootstrap.min.css\" rel=\"stylesheet\">\n    <!-- Bootstrap Icons -->\n    <link rel=\"stylesheet\" href=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap-icons@1.11.3\/font\/bootstrap-icons.min.css\">\n    <!-- Leaflet (Mapas) -->\n    <link rel=\"stylesheet\" href=\"https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.css\" \/>\n    <script src=\"https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.js\"><\/script>\n    <!-- Chart.js (Gr\u00e1ficos) -->\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js\"><\/script>\n\n    <style>\n        body { background-color: #f8f9fa; }\n        .card { box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); margin-bottom: 1.5rem; border: none; }\n        #map { height: 600px; width: 100%; border-radius: 0.375rem; }\n        .chart-container { position: relative; height: 350px; width: 100%; display: flex; justify-content: center; }\n        \n        .stat-card {\n            color: white; padding: 1.5rem; border-radius: 0.5rem;\n            background: linear-gradient(135deg, var(--bs-primary), #0a58ca);\n            position: relative;\n        }\n        .stat-card.green { background: linear-gradient(135deg, var(--bs-success), #146c43); }\n        .stat-card.purple { background: linear-gradient(135deg, #6f42c1, #4c2889); }\n        .stat-value { font-size: 2.5rem; font-weight: 700; margin-top: 0.5rem; line-height: 1; }\n        \n        .timeline { border-left: 2px solid #dee2e6; padding-left: 1rem; margin-left: 1rem; }\n        .timeline-item { margin-bottom: 1rem; position: relative; }\n        .timeline-item::before {\n            content: ''; position: absolute; left: -1.35rem; top: 0.25rem;\n            width: 12px; height: 12px; border-radius: 50%; background: var(--bs-primary);\n        }\n        \n        .cursor-pointer { cursor: pointer; }\n    <\/style>\n<\/head>\n<body>\n\n    <!-- Navbar -->\n    <nav class=\"navbar navbar-expand-lg navbar-dark bg-dark d-none\" id=\"main-nav\">\n        <div class=\"container-fluid\">\n            <a class=\"navbar-brand\" href=\"#\"><i class=\"bi bi-lightning-charge-fill text-warning\"><\/i> AvisosMana<\/a>\n            <div class=\"d-flex align-items-center gap-3\">\n                <span class=\"text-white fw-bold\" id=\"user-info\"><\/span>\n                <span class=\"badge bg-secondary\" id=\"user-role-badge\"><\/span>\n                <button class=\"btn btn-outline-light btn-sm\" id=\"btn-logout\"><i class=\"bi bi-box-arrow-right\"><\/i> Salir<\/button>\n            <\/div>\n        <\/div>\n    <\/nav>\n\n    <div class=\"container-fluid py-4 px-lg-5\">\n        \n        <!-- PANTALLA DE LOGIN -->\n        <div id=\"login-view\" class=\"row justify-content-center mt-5\">\n            <div class=\"col-md-5 col-lg-4\">\n                <div class=\"card p-4 text-center\">\n                    <h2 class=\"mb-3 text-dark\"><i class=\"bi bi-lightning-charge text-warning\"><\/i> AvisosMana<\/h2>\n                    <div class=\"mb-3\">\n                        <select id=\"login-role\" class=\"form-select form-select-lg\">\n                            <option value=\"admin_default\">\ud83d\udc51 Administrador Local<\/option>\n                        <\/select>\n                    <\/div>\n                    <button id=\"btn-login\" class=\"btn btn-dark btn-lg w-100\">Entrar al Sistema<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <!-- ================= VISTA MANAGER ================= -->\n        <div id=\"manager-view\" class=\"d-none\">\n            \n            <div class=\"d-flex justify-content-between align-items-center mb-3 flex-wrap gap-2\">\n                <h3 class=\"m-0\"><i class=\"bi bi-speedometer2\"><\/i> Panel de Administraci\u00f3n<\/h3>\n                <div>\n                    <button id=\"btn-seed-tech\" class=\"btn btn-outline-secondary btn-sm\"><i class=\"bi bi-person-plus\"><\/i> + T\u00e9cnico<\/button>\n                    <button id=\"btn-seed-client\" class=\"btn btn-outline-secondary btn-sm\"><i class=\"bi bi-building-add\"><\/i> + Cliente<\/button>\n                    <button id=\"btn-seed-resource\" class=\"btn btn-outline-secondary btn-sm\"><i class=\"bi bi-box-seam\"><\/i> + Recurso<\/button>\n                    <button id=\"btn-seed-incident\" class=\"btn btn-primary btn-sm\"><i class=\"bi bi-plus-circle\"><\/i> + Incidencia<\/button>\n                <\/div>\n            <\/div>\n\n            <!-- Navegaci\u00f3n por pesta\u00f1as (Manager) -->\n            <ul class=\"nav nav-tabs mb-4\" id=\"managerTabs\" role=\"tablist\">\n                <li class=\"nav-item\" role=\"presentation\"><button class=\"nav-link active\" id=\"stats-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#stats-pane\" type=\"button\" role=\"tab\">\ud83d\udcca Estad\u00edsticas<\/button><\/li>\n                <li class=\"nav-item\" role=\"presentation\"><button class=\"nav-link\" id=\"incidents-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#incidents-pane\" type=\"button\" role=\"tab\">\ud83d\udccb Incidencias<\/button><\/li>\n                <li class=\"nav-item\" role=\"presentation\"><button class=\"nav-link\" id=\"techs-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#techs-pane\" type=\"button\" role=\"tab\">\ud83d\udc77 T\u00e9cnicos<\/button><\/li>\n                <li class=\"nav-item\" role=\"presentation\"><button class=\"nav-link\" id=\"clients-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#clients-pane\" type=\"button\" role=\"tab\">\ud83c\udfe2 Clientes<\/button><\/li>\n                <li class=\"nav-item\" role=\"presentation\"><button class=\"nav-link\" id=\"resources-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#resources-pane\" type=\"button\" role=\"tab\">\ud83d\udd27 Recursos<\/button><\/li>\n                <li class=\"nav-item\" role=\"presentation\"><button class=\"nav-link\" id=\"map-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#map-pane\" type=\"button\" role=\"tab\">\ud83d\uddfa\ufe0f Mapa<\/button><\/li>\n            <\/ul>\n\n            <div class=\"tab-content\">\n                <!-- Pesta\u00f1a Estad\u00edsticas -->\n                <div class=\"tab-pane fade show active\" id=\"stats-pane\" role=\"tabpanel\">\n                    <div class=\"row mb-4\">\n                        <div class=\"col-md-4 mb-3\">\n                            <div class=\"stat-card\" data-bs-toggle=\"tooltip\" title=\"Suma de los Costes Iniciales (Presupuestado) de todas las incidencias\">\n                                <div><i class=\"bi bi-calculator\"><\/i> Presupuesto<\/div>\n                                <div class=\"stat-value\" id=\"kpi-budget\">0 \u20ac<\/div>\n                            <\/div>\n                        <\/div>\n                        <div class=\"col-md-4 mb-3\">\n                            <div class=\"stat-card purple\" data-bs-toggle=\"tooltip\" title=\"Suma de los Costes Finales de todas las incidencias facturadas\">\n                                <div><i class=\"bi bi-receipt\"><\/i> Factura<\/div>\n                                <div class=\"stat-value\" id=\"kpi-invoice\">0 \u20ac<\/div>\n                            <\/div>\n                        <\/div>\n                        <div class=\"col-md-4 mb-3\">\n                            <div class=\"stat-card green\" data-bs-toggle=\"tooltip\" title=\"Suma de Costes Finales solo de las incidencias en estado de cobro 'Cobrado'\">\n                                <div><i class=\"bi bi-safe\"><\/i> Caja (Real)<\/div>\n                                <div class=\"stat-value\" id=\"kpi-cash\">0 \u20ac<\/div>\n                            <\/div>\n                        <\/div>\n                    <\/div>\n                    <div class=\"row\">\n                        <div class=\"col-md-6\">\n                            <div class=\"card p-3\">\n                                <h5 class=\"text-center\">Distribuci\u00f3n de Incidencias<\/h5>\n                                <div class=\"chart-container\">\n                                    <canvas id=\"statusPieChart\"><\/canvas>\n                                <\/div>\n                            <\/div>\n                        <\/div>\n                    <\/div>\n                <\/div>\n\n                <!-- Pesta\u00f1a Incidencias -->\n                <div class=\"tab-pane fade\" id=\"incidents-pane\" role=\"tabpanel\">\n                    <div class=\"card p-0\">\n                        <div class=\"table-responsive\">\n                            <table class=\"table table-hover align-middle m-0\">\n                                <thead class=\"table-dark\">\n                                    <tr>\n                                        <th>Fecha<\/th>\n                                        <th>Cliente \/ T\u00edtulo<\/th>\n                                        <th>T\u00e9cnico<\/th>\n                                        <th>Estado T\u00e9cnico<\/th>\n                                        <th>Estado Cobro<\/th>\n                                        <th>Presup. \/ Factura<\/th>\n                                        <th class=\"text-end\">Acciones<\/th>\n                                    <\/tr>\n                                <\/thead>\n                                <tbody id=\"manager-table-body\"><\/tbody>\n                            <\/table>\n                        <\/div>\n                    <\/div>\n                <\/div>\n\n                <!-- Pesta\u00f1a T\u00e9cnicos -->\n                <div class=\"tab-pane fade\" id=\"techs-pane\" role=\"tabpanel\">\n                    <div class=\"card p-0\">\n                        <div class=\"table-responsive\">\n                            <table class=\"table table-hover align-middle m-0\">\n                                <thead class=\"table-dark\"><tr><th>Nombre<\/th><th>Especialidad<\/th><th>Activos<\/th><th class=\"text-end\">Acciones<\/th><\/tr><\/thead>\n                                <tbody id=\"techs-table-body\"><\/tbody>\n                            <\/table>\n                        <\/div>\n                    <\/div>\n                <\/div>\n\n                <!-- Pesta\u00f1a Clientes -->\n                <div class=\"tab-pane fade\" id=\"clients-pane\" role=\"tabpanel\">\n                    <div class=\"card p-0\">\n                        <div class=\"table-responsive\">\n                            <table class=\"table table-hover align-middle m-0\">\n                                <thead class=\"table-dark\"><tr><th>Nombre del Cliente<\/th><th>Rol<\/th><th>ID<\/th><\/tr><\/thead>\n                                <tbody id=\"clients-table-body\"><\/tbody>\n                            <\/table>\n                        <\/div>\n                    <\/div>\n                <\/div>\n\n                <!-- Pesta\u00f1a Recursos -->\n                <div class=\"tab-pane fade\" id=\"resources-pane\" role=\"tabpanel\">\n                    <div class=\"card p-0\">\n                        <div class=\"table-responsive\">\n                            <table class=\"table table-hover align-middle m-0\">\n                                <thead class=\"table-dark\"><tr><th>ID<\/th><th>Nombre del Recurso<\/th><th>Tipo<\/th><th>Disponibilidad<\/th><\/tr><\/thead>\n                                <tbody id=\"resources-table-body\"><\/tbody>\n                            <\/table>\n                        <\/div>\n                    <\/div>\n                <\/div>\n\n                <!-- Pesta\u00f1a Mapa -->\n                <div class=\"tab-pane fade\" id=\"map-pane\" role=\"tabpanel\">\n                    <div class=\"alert alert-info py-2 mb-2\"><i class=\"bi bi-info-circle\"><\/i> <b>Haz clic derecho<\/b> (o mant\u00e9n pulsado en m\u00f3vil) en cualquier punto del mapa para crear una nueva incidencia en esa ubicaci\u00f3n. Las incidencias Finalizadas\/Resueltas no se muestran aqu\u00ed.<\/div>\n                    <div class=\"card p-1 shadow-sm\">\n                        <div id=\"map\"><\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <!-- ================= VISTA T\u00c9CNICO ================= -->\n        <div id=\"tech-view\" class=\"d-none\">\n            <h3 class=\"mb-3\"><i class=\"bi bi-tools\"><\/i> Panel del T\u00e9cnico<\/h3>\n            \n            <ul class=\"nav nav-tabs mb-4\" id=\"techTabs\" role=\"tablist\">\n                <li class=\"nav-item\" role=\"presentation\"><button class=\"nav-link active\" id=\"tech-incidents-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#tech-incidents-pane\" type=\"button\" role=\"tab\">\ud83d\udd27 Mis Avisos (Por Prioridad)<\/button><\/li>\n                <li class=\"nav-item\" role=\"presentation\"><button class=\"nav-link\" id=\"tech-new-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#tech-new-pane\" type=\"button\" role=\"tab\">\u2795 Crear Incidencia<\/button><\/li>\n            <\/ul>\n\n            <div class=\"tab-content\">\n                <div class=\"tab-pane fade show active\" id=\"tech-incidents-pane\" role=\"tabpanel\">\n                    <div class=\"row\" id=\"tech-incidents-list\"><\/div>\n                <\/div>\n                <div class=\"tab-pane fade\" id=\"tech-new-pane\" role=\"tabpanel\">\n                    <div class=\"card p-4 mx-auto\" style=\"max-width: 600px;\">\n                        <h5 class=\"mb-4\">Reportar nueva aver\u00eda descubierta<\/h5>\n                        <div id=\"tech-form-container\"><\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n\n        <!-- ================= VISTA CLIENTE ================= -->\n        <div id=\"client-view\" class=\"d-none\">\n            <h3 class=\"mb-3\"><i class=\"bi bi-house-door\"><\/i> Portal del Cliente<\/h3>\n            \n            <ul class=\"nav nav-tabs mb-4\" id=\"clientTabs\" role=\"tablist\">\n                <li class=\"nav-item\" role=\"presentation\"><button class=\"nav-link active\" id=\"client-incidents-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#client-incidents-pane\" type=\"button\" role=\"tab\">\ud83c\udfe0 Mis Aver\u00edas Activas<\/button><\/li>\n                <li class=\"nav-item\" role=\"presentation\"><button class=\"nav-link\" id=\"client-new-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#client-new-pane\" type=\"button\" role=\"tab\">\ud83c\udd98 Solicitar Asistencia<\/button><\/li>\n            <\/ul>\n\n            <div class=\"tab-content\">\n                <div class=\"tab-pane fade show active\" id=\"client-incidents-pane\" role=\"tabpanel\">\n                    <div class=\"row\" id=\"client-incidents-list\"><\/div>\n                <\/div>\n                <div class=\"tab-pane fade\" id=\"client-new-pane\" role=\"tabpanel\">\n                    <div class=\"card p-4 mx-auto\" style=\"max-width: 600px;\">\n                        <h5 class=\"mb-4\">Apertura de Incidencia<\/h5>\n                        <div id=\"client-form-container\"><\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n\n    <\/div>\n\n    <!-- Plantilla de Formulario Compartida (T\u00e9cnico \/ Cliente) -->\n    <template id=\"form-template\">\n        <form class=\"new-incident-form\">\n            <div class=\"mb-3\">\n                <label class=\"form-label fw-bold\">T\u00edtulo de la aver\u00eda<\/label>\n                <input type=\"text\" class=\"form-control inc-title\" required>\n            <\/div>\n            <div class=\"row mb-3\">\n                <div class=\"col-md-6\">\n                    <label class=\"form-label fw-bold\">Categor\u00eda<\/label>\n                    <select class=\"form-select inc-type\" required>\n                        <option value=\"Electrica\">El\u00e9ctrica<\/option>\n                        <option value=\"Telecomunicaciones\">Telecomunicaciones<\/option>\n                        <option value=\"Fontaneria\">Fontaner\u00eda<\/option>\n                    <\/select>\n                <\/div>\n                <div class=\"col-md-6\">\n                    <label class=\"form-label fw-bold text-danger\">Prioridad<\/label>\n                    <select class=\"form-select inc-priority\">\n                        <option value=\"Baja\">Baja<\/option>\n                        <option value=\"Media\" selected>Media<\/option>\n                        <option value=\"Alta\">Alta<\/option>\n                    <\/select>\n                <\/div>\n            <\/div>\n            <div class=\"mb-4\">\n                <label class=\"form-label fw-bold\">Descripci\u00f3n detallada<\/label>\n                <textarea class=\"form-control inc-desc\" rows=\"3\" required><\/textarea>\n            <\/div>\n            <button type=\"submit\" class=\"btn btn-dark w-100 py-2\"><i class=\"bi bi-send\"><\/i> Registrar Incidencia<\/button>\n        <\/form>\n    <\/template>\n\n    <!-- Modal: Edici\u00f3n de Incidencia (Manager) -->\n    <div class=\"modal fade\" id=\"modalEdit\" tabindex=\"-1\">\n        <div class=\"modal-dialog\">\n            <div class=\"modal-content\">\n                <div class=\"modal-header bg-light\">\n                    <h5 class=\"modal-title\"><i class=\"bi bi-pencil-square\"><\/i> Editar Incidencia<\/h5>\n                    <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\"><\/button>\n                <\/div>\n                <div class=\"modal-body\">\n                    <input type=\"hidden\" id=\"edit-inc-id\">\n                    <div class=\"mb-3\">\n                        <label class=\"form-label fw-bold\">T\u00e9cnico Asignado<\/label>\n                        <select class=\"form-select\" id=\"edit-tech\"><\/select>\n                    <\/div>\n                    <div class=\"row mb-3\">\n                        <div class=\"col-6\">\n                            <label class=\"form-label fw-bold\">Estado Operativo<\/label>\n                            <select class=\"form-select\" id=\"edit-status\">\n                                <option value=\"Pendiente\">Pendiente<\/option>\n                                <option value=\"Asignada\">Asignada<\/option>\n                                <option value=\"En progreso\">En progreso<\/option>\n                                <option value=\"Resuelta\">Resuelta<\/option>\n                                <option value=\"Finalizada\">Finalizada (Cerrada)<\/option>\n                            <\/select>\n                        <\/div>\n                        <div class=\"col-6\">\n                            <label class=\"form-label fw-bold text-primary\">Estado de Cobro<\/label>\n                            <select class=\"form-select\" id=\"edit-payment\">\n                                <option value=\"Pendiente\">Pendiente<\/option>\n                                <option value=\"Emitido\">Factura Emitida<\/option>\n                                <option value=\"Cobrado\">Cobrado<\/option>\n                            <\/select>\n                        <\/div>\n                    <\/div>\n                    <div class=\"row\">\n                        <div class=\"col-6 mb-3\">\n                            <label class=\"form-label fw-bold\">Presupuesto Inicial (\u20ac)<\/label>\n                            <input type=\"number\" class=\"form-control\" id=\"edit-initial-cost\" step=\"0.01\">\n                        <\/div>\n                        <div class=\"col-6 mb-3\">\n                            <label class=\"form-label fw-bold\">Factura Final (\u20ac)<\/label>\n                            <input type=\"number\" class=\"form-control\" id=\"edit-final-cost\" step=\"0.01\">\n                        <\/div>\n                    <\/div>\n                <\/div>\n                <div class=\"modal-footer bg-light\">\n                    <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancelar<\/button>\n                    <button type=\"button\" class=\"btn btn-primary\" onclick=\"window.saveEditIncident()\">Guardar Cambios<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n    <\/div>\n\n    <!-- Modal: Seguimiento de Incidencia -->\n    <div class=\"modal fade\" id=\"modalTrack\" tabindex=\"-1\">\n        <div class=\"modal-dialog modal-lg\">\n            <div class=\"modal-content\">\n                <div class=\"modal-header bg-dark text-white\">\n                    <h5 class=\"modal-title\"><i class=\"bi bi-clock-history\"><\/i> Seguimiento de Incidencia<\/h5>\n                    <button type=\"button\" class=\"btn-close btn-close-white\" data-bs-dismiss=\"modal\"><\/button>\n                <\/div>\n                <div class=\"modal-body bg-light\">\n                    <div class=\"row mb-3\">\n                        <div class=\"col-md-6\"><small class=\"text-muted\">Creado por:<\/small> <div class=\"fw-bold\" id=\"track-creator\"><\/div><\/div>\n                        <div class=\"col-md-6\"><small class=\"text-muted\">Asignado a:<\/small> <div class=\"fw-bold\" id=\"track-tech\"><\/div><\/div>\n                    <\/div>\n                    <div class=\"card mb-3\">\n                        <div class=\"card-body py-2\"><p class=\"mb-0 small\" id=\"track-desc\"><\/p><\/div>\n                    <\/div>\n                    <h6 class=\"border-bottom pb-2\">Timeline de Actividad<\/h6>\n                    <div id=\"track-timeline\" class=\"timeline\"><\/div>\n                    \n                    <div class=\"mt-4\" id=\"track-add-comment-section\">\n                        <textarea class=\"form-control mb-2\" id=\"track-new-comment\" rows=\"2\" placeholder=\"Escribir nuevo comentario...\"><\/textarea>\n                        <button class=\"btn btn-sm btn-outline-primary\" onclick=\"window.addCommentToIncident()\"><i class=\"bi bi-chat-text\"><\/i> A\u00f1adir Comentario<\/button>\n                    <\/div>\n                <\/div>\n                <div class=\"modal-footer\" id=\"track-footer-manager\">\n                    <!-- Solo visible para Manager -->\n                    <button type=\"button\" class=\"btn btn-danger\" onclick=\"window.changeIncidentStatusQuick('Finalizada')\"><i class=\"bi bi-check-circle-fill\"><\/i> Finalizar Incidencia<\/button>\n                    <button type=\"button\" class=\"btn btn-warning\" onclick=\"window.changeIncidentStatusQuick('Pendiente')\"><i class=\"bi bi-arrow-counterclockwise\"><\/i> Reabrir Incidencia<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n    <\/div>\n\n    <!-- Modal: Seguimiento de T\u00e9cnico -->\n    <div class=\"modal fade\" id=\"modalTechTrack\" tabindex=\"-1\">\n        <div class=\"modal-dialog modal-lg\">\n            <div class=\"modal-content\">\n                <div class=\"modal-header bg-secondary text-white\">\n                    <h5 class=\"modal-title\">Rendimiento del T\u00e9cnico: <span id=\"tech-track-name\"><\/span><\/h5>\n                    <button type=\"button\" class=\"btn-close btn-close-white\" data-bs-dismiss=\"modal\"><\/button>\n                <\/div>\n                <div class=\"modal-body\">\n                    <ul class=\"nav nav-pills mb-3\" role=\"tablist\">\n                        <li class=\"nav-item\"><button class=\"nav-link active\" data-bs-toggle=\"pill\" data-bs-target=\"#tt-assigned\">Asignadas a \u00e9l<\/button><\/li>\n                        <li class=\"nav-item\"><button class=\"nav-link\" data-bs-toggle=\"pill\" data-bs-target=\"#tt-created\">Creadas por \u00e9l<\/button><\/li>\n                    <\/ul>\n                    <div class=\"tab-content\">\n                        <div class=\"tab-pane fade show active\" id=\"tt-assigned\">\n                            <ul class=\"list-group\" id=\"tech-assigned-list\"><\/ul>\n                        <\/div>\n                        <div class=\"tab-pane fade\" id=\"tt-created\">\n                            <ul class=\"list-group\" id=\"tech-created-list\"><\/ul>\n                        <\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n    <\/div>\n\n    <!-- Modal: Crear Incidencia desde el Mapa -->\n    <div class=\"modal fade\" id=\"modalMapCreate\" tabindex=\"-1\">\n        <div class=\"modal-dialog\">\n            <div class=\"modal-content\">\n                <div class=\"modal-header bg-primary text-white\">\n                    <h5 class=\"modal-title\">Crear Incidencia en esta Ubicaci\u00f3n<\/h5>\n                    <button type=\"button\" class=\"btn-close btn-close-white\" data-bs-dismiss=\"modal\"><\/button>\n                <\/div>\n                <div class=\"modal-body\">\n                    <input type=\"hidden\" id=\"map-lat\">\n                    <input type=\"hidden\" id=\"map-lng\">\n                    <div class=\"mb-3\">\n                        <label class=\"form-label fw-bold\">Cliente Afectado<\/label>\n                        <select class=\"form-select\" id=\"map-create-client\" required><\/select>\n                    <\/div>\n                    <div class=\"mb-3\">\n                        <label class=\"form-label fw-bold\">T\u00e9cnico Asignado (Opcional)<\/label>\n                        <select class=\"form-select\" id=\"map-create-tech\"><\/select>\n                    <\/div>\n                    <div class=\"mb-3\">\n                        <label class=\"form-label fw-bold\">Prioridad<\/label>\n                        <select class=\"form-select text-danger\" id=\"map-create-priority\">\n                            <option value=\"Alta\">Alta<\/option>\n                            <option value=\"Media\" selected>Media<\/option>\n                            <option value=\"Baja\">Baja<\/option>\n                        <\/select>\n                    <\/div>\n                    <div class=\"mb-3\">\n                        <label class=\"form-label fw-bold\">Descripci\u00f3n<\/label>\n                        <textarea class=\"form-control\" id=\"map-create-desc\" rows=\"2\" required><\/textarea>\n                    <\/div>\n                <\/div>\n                <div class=\"modal-footer\">\n                    <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancelar<\/button>\n                    <button type=\"button\" class=\"btn btn-primary\" onclick=\"window.saveMapIncident()\">Guardar Nueva Incidencia<\/button>\n                <\/div>\n            <\/div>\n        <\/div>\n    <\/div>\n\n    <!-- Bootstrap 5 JS Bundle -->\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/bootstrap@5.3.3\/dist\/js\/bootstrap.bundle.min.js\"><\/script>\n\n    <!-- L\u00d3GICA LOCAL (Offline Simulation) -->\n    <script>\n        \/\/ --- BASE DE DATOS LOCAL EN MEMORIA Y LOCALSTORAGE ---\n        let users = [];\n        let incidents = [];\n        let resources = [];\n        \n        function generateId() { return Math.random().toString(36).substr(2, 9); }\n        \n        function loadData() {\n            const data = localStorage.getItem('avisosmana_db');\n            if (data) {\n                const dbObj = JSON.parse(data);\n                users = dbObj.users || [];\n                incidents = dbObj.incidents || [];\n                resources = dbObj.resources || [];\n            }\n        }\n        \n        function saveData() {\n            localStorage.setItem('avisosmana_db', JSON.stringify({ users, incidents, resources }));\n            notifyDataChanged();\n        }\n\n        \/\/ Simula la funci\u00f3n onSnapshot: actualiza la UI cada vez que se modifican los datos\n        function notifyDataChanged() {\n            populateLoginSelect();\n            updateDropdowns();\n            if(currentUser) {\n                updateUI();\n                if(currentTrackId) renderTrackModal(currentTrackId);\n            }\n        }\n\n        \/\/ --- ESTADO GLOBAL ---\n        let currentUser = null;\n        let map = null;\n        let markers = [];\n        let statusPieChart = null;\n        \n        let editModal = null;\n        let trackModal = null;\n        let techTrackModal = null;\n        let mapCreateModal = null;\n        let currentTrackId = null;\n\n        const priorityOrder = { 'Alta': 1, 'Media': 2, 'Baja': 3 };\n        const statusColors = { 'Pendiente': 'danger', 'Asignada': 'warning', 'En progreso': 'info', 'Resuelta': 'primary', 'Finalizada': 'success' };\n        const paymentColors = { 'Pendiente': 'danger', 'Emitido': 'warning', 'Cobrado': 'success' };\n\n        \/\/ --- INICIALIZACI\u00d3N ---\n        document.addEventListener('DOMContentLoaded', () => {\n            editModal = new bootstrap.Modal(document.getElementById('modalEdit'));\n            trackModal = new bootstrap.Modal(document.getElementById('modalTrack'));\n            techTrackModal = new bootstrap.Modal(document.getElementById('modalTechTrack'));\n            mapCreateModal = new bootstrap.Modal(document.getElementById('modalMapCreate'));\n\n            const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle=\"tooltip\"]');\n            [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));\n\n            document.getElementById('btn-login').addEventListener('click', handleLogin);\n            document.getElementById('btn-logout').addEventListener('click', handleLogout);\n            \n            document.getElementById('btn-seed-tech').addEventListener('click', seedRandomTech);\n            document.getElementById('btn-seed-client').addEventListener('click', seedRandomClient);\n            document.getElementById('btn-seed-incident').addEventListener('click', () => seedRandomIncident(false));\n            document.getElementById('btn-seed-resource').addEventListener('click', seedRandomResource);\n\n            document.getElementById('client-form-container').appendChild(document.getElementById('form-template').content.cloneNode(true));\n            document.getElementById('tech-form-container').appendChild(document.getElementById('form-template').content.cloneNode(true));\n\n            document.querySelectorAll('.new-incident-form').forEach(form => form.addEventListener('submit', handleNewIncident));\n\n            document.getElementById('map-tab').addEventListener('shown.bs.tab', () => { if(map) map.invalidateSize(); updateMap(); });\n            document.getElementById('stats-tab').addEventListener('shown.bs.tab', () => updateChart());\n\n            \/\/ Cargar base de datos local\n            loadData();\n            if (users.length === 0) seedInitialManager();\n            populateLoginSelect();\n        });\n\n        \/\/ --- WRAPPER CONFIRMACI\u00d3N ---\n        async function runWithConfirm(msg, actionFn) {\n            if(confirm(msg)) await actionFn();\n        }\n\n        \/\/ --- M\u00c9TODOS LOCALES (Simulando Firestore) ---\n        function populateLoginSelect() {\n            const select = document.getElementById('login-role');\n            select.innerHTML = '<option value=\"admin_default\">\ud83d\udc51 Administrador Local<\/option>';\n            users.forEach(u => {\n                if(u.id !== 'admin_default') {\n                    const icon = u.role === 'manager' ? '\ud83d\udc51' : (u.role === 'tech' ? '\ud83d\udc77' : '\ud83c\udfe0');\n                    select.innerHTML += `<option value=\"${u.id}\">${icon} ${u.displayName} (${u.role})<\/option>`;\n                }\n            });\n        }\n\n        function seedInitialManager() {\n            users.push({ id: 'admin_default', role: 'manager', displayName: 'Administrador Local', createdAt: new Date().toISOString() });\n            saveData();\n        }\n\n        function seedRandomTech() {\n            const names = ['Carlos Ruiz', 'Elena Santos', 'Miguel Torres', 'Laura Vega'];\n            users.push({ id: generateId(), role: 'tech', displayName: names[Math.floor(Math.random()*4)] + ' ' + Math.floor(Math.random()*100), specialty: 'Multit\u00e9cnico', createdAt: new Date().toISOString() });\n            saveData();\n        }\n\n        function seedRandomClient() {\n            const names = ['Comunidad Los Robles', 'Edificio Sol', 'Particular (Juan)', 'Oficinas Nexus'];\n            users.push({ id: generateId(), role: 'client', displayName: names[Math.floor(Math.random()*4)] + ' ' + Math.floor(Math.random()*100), createdAt: new Date().toISOString() });\n            saveData();\n        }\n\n        function seedRandomResource() {\n            const types = ['Furgoneta', 'Herramienta Pesada', 'Gr\u00faa', 'Medidor Anal\u00edtico'];\n            resources.push({ id: generateId(), name: types[Math.floor(Math.random()*4)] + ' ' + Math.floor(Math.random()*100), type: 'Material', available: true, createdAt: new Date().toISOString() });\n            saveData();\n        }\n\n        function updateDropdowns() {\n            const techs = users.filter(u => u.role === 'tech');\n            const clients = users.filter(u => u.role === 'client');\n            \n            const techOptions = '<option value=\"\">Sin asignar<\/option>' + techs.map(t => `<option value=\"${t.id}\">${t.displayName}<\/option>`).join('');\n            const clientOptions = '<option value=\"\">Seleccionar Cliente<\/option>' + clients.map(c => `<option value=\"${c.id}\">${c.displayName}<\/option>`).join('');\n\n            const editTech = document.getElementById('edit-tech');\n            if(editTech) editTech.innerHTML = techOptions;\n\n            const mapTech = document.getElementById('map-create-tech');\n            if(mapTech) mapTech.innerHTML = techOptions;\n\n            const mapClient = document.getElementById('map-create-client');\n            if(mapClient) mapClient.innerHTML = clientOptions;\n        }\n\n        \/\/ --- AUTH LOGIC ---\n        function handleLogin() {\n            const userId = document.getElementById('login-role').value;\n            if(!userId) return;\n            \n            if (userId === 'admin_default') {\n                currentUser = users.find(u => u.id === 'admin_default') || { id: 'admin_default', role: 'manager', displayName: 'Administrador Local' };\n            } else {\n                currentUser = users.find(u => u.id === userId);\n            }\n            \n            document.getElementById('login-view').classList.add('d-none');\n            document.getElementById('main-nav').classList.remove('d-none');\n            document.getElementById('user-info').innerText = currentUser.displayName;\n            document.getElementById('user-role-badge').innerText = currentUser.role.toUpperCase();\n\n            document.getElementById('manager-view').classList.add('d-none');\n            document.getElementById('tech-view').classList.add('d-none');\n            document.getElementById('client-view').classList.add('d-none');\n\n            if (currentUser.role === 'manager') {\n                document.getElementById('manager-view').classList.remove('d-none');\n                initMap(); initChart();\n            } else if (currentUser.role === 'tech') {\n                document.getElementById('tech-view').classList.remove('d-none');\n            } else if (currentUser.role === 'client') {\n                document.getElementById('client-view').classList.remove('d-none');\n            }\n            updateUI();\n        }\n\n        function handleLogout() {\n            currentUser = null;\n            document.getElementById('login-view').classList.remove('d-none');\n            document.getElementById('main-nav').classList.add('d-none');\n            document.getElementById('manager-view').classList.add('d-none');\n            document.getElementById('tech-view').classList.add('d-none');\n            document.getElementById('client-view').classList.add('d-none');\n        }\n\n        function updateUI() {\n            if (currentUser.role === 'manager') {\n                updateManagerTable(); updateTechsTable(); updateClientsTable(); updateResourcesTable(); updateKPIs(); updateMap(); updateChart();\n            } else if (currentUser.role === 'tech') {\n                updateTechView();\n            } else if (currentUser.role === 'client') {\n                updateClientView();\n            }\n        }\n\n        function createHistoryEvent(action, comment = \"\") {\n            return { timestamp: new Date().toISOString(), user: currentUser.displayName, action: action, comment: comment };\n        }\n\n        \/\/ --- CREAR INCIDENCIA ---\n        function handleNewIncident(e) {\n            e.preventDefault();\n            runWithConfirm(\"\u00bfConfirmas la creaci\u00f3n de esta nueva aver\u00eda?\", async () => {\n                const form = e.target;\n                const isClient = currentUser.role === 'client';\n                const lat = 42.1 + Math.random() * 1.4;\n                const lng = -8.9 + Math.random() * 1.8;\n\n                const initHistory = [createHistoryEvent('Apertura', form.querySelector('.inc-desc').value)];\n                \n                incidents.push({\n                    id: generateId(),\n                    clientId: isClient ? currentUser.id : 'anon',\n                    clientName: isClient ? currentUser.displayName : 'Reporte Externo',\n                    title: form.querySelector('.inc-title').value,\n                    incidentType: form.querySelector('.inc-type').value,\n                    description: form.querySelector('.inc-desc').value,\n                    priority: form.querySelector('.inc-priority').value,\n                    status: 'Pendiente', paymentStatus: 'Pendiente',\n                    latitude: lat, longitude: lng,\n                    createdAt: new Date().toISOString(),\n                    initialCost: 0, finalCost: 0,\n                    techId: isClient ? null : currentUser.id,\n                    history: initHistory\n                });\n                saveData();\n                form.reset();\n                if(isClient) new bootstrap.Tab(document.getElementById('client-incidents-tab')).show();\n                else new bootstrap.Tab(document.getElementById('tech-incidents-tab')).show();\n            });\n        }\n\n        \/\/ --- VISTA T\u00c9CNICO ---\n        function updateTechView() {\n            const list = document.getElementById('tech-incidents-list');\n            list.innerHTML = '';\n            let myIncidents = incidents.filter(i => i.techId === currentUser.id && i.status !== 'Finalizada');\n            \/\/ Sort por prioridad y luego por fecha inversa\n            myIncidents.sort((a, b) => {\n                const pDiff = (priorityOrder[a.priority] || 99) - (priorityOrder[b.priority] || 99);\n                if (pDiff !== 0) return pDiff;\n                return new Date(b.createdAt) - new Date(a.createdAt);\n            });\n\n            if(myIncidents.length === 0) { list.innerHTML = '<div class=\"alert alert-success w-100\">Sin avisos activos.<\/div>'; return; }\n\n            myIncidents.forEach(inc => {\n                const pColor = inc.priority === 'Alta' ? 'danger' : (inc.priority === 'Media' ? 'warning' : 'info');\n                list.innerHTML += `\n                    <div class=\"col-md-6 mb-3\">\n                        <div class=\"card h-100 border-${statusColors[inc.status]}\">\n                            <div class=\"card-body\">\n                                <div class=\"d-flex justify-content-between mb-2\">\n                                    <span class=\"badge bg-${statusColors[inc.status]}\">${inc.status}<\/span>\n                                    <span class=\"badge bg-${pColor}\">Prioridad ${inc.priority}<\/span>\n                                <\/div>\n                                <h5 class=\"card-title\">${inc.title}<\/h5>\n                                <p class=\"card-text small bg-light p-2 rounded\">${inc.description}<\/p>\n                            <\/div>\n                            <div class=\"card-footer bg-transparent d-flex gap-2\">\n                                <button class=\"btn btn-primary btn-sm flex-fill\" onclick=\"window.openTrackModal('${inc.id}')\"><i class=\"bi bi-chat\"><\/i> Comentar \/ Ver<\/button>\n                                ${inc.status !== 'Resuelta' ? `<button class=\"btn btn-success btn-sm flex-fill\" onclick=\"window.updateIncidentStatus('${inc.id}', 'Resuelta')\">Marcar Resuelta<\/button>` : ''}\n                            <\/div>\n                        <\/div>\n                    <\/div>\n                `;\n            });\n        }\n\n        window.updateIncidentStatus = function(id, newStatus) {\n            runWithConfirm(`\u00bfCambiar estado a ${newStatus}?`, async () => {\n                const inc = incidents.find(i=>i.id===id);\n                if(inc) {\n                    inc.history.push(createHistoryEvent('Cambio Estado', `El t\u00e9cnico cambi\u00f3 el estado a ${newStatus}`));\n                    inc.status = newStatus;\n                    saveData();\n                }\n            });\n        };\n\n        \/\/ --- VISTA CLIENTE ---\n        function updateClientView() {\n            const list = document.getElementById('client-incidents-list');\n            list.innerHTML = '';\n            const myIncidents = incidents.filter(i => i.clientId === currentUser.id);\n\n            if(myIncidents.length === 0) { list.innerHTML = '<div class=\"alert alert-info w-100\">No tienes incidencias registradas.<\/div>'; return; }\n\n            myIncidents.sort((a,b) => new Date(b.createdAt) - new Date(a.createdAt)).forEach(inc => {\n                list.innerHTML += `\n                    <div class=\"col-12 mb-2\">\n                        <div class=\"card border-start border-4 border-${statusColors[inc.status]} cursor-pointer hover-shadow\" onclick=\"window.openTrackModal('${inc.id}')\">\n                            <div class=\"card-body d-flex justify-content-between align-items-center py-2\">\n                                <div><h6 class=\"mb-0\">${inc.title}<\/h6><small class=\"text-muted\">${inc.incidentType}<\/small><\/div>\n                                <div><span class=\"badge bg-${statusColors[inc.status]}\">${inc.status}<\/span><\/div>\n                            <\/div>\n                        <\/div>\n                    <\/div>\n                `;\n            });\n        }\n\n        \/\/ --- VISTA MANAGER ---\n        function updateManagerTable() {\n            const tbody = document.getElementById('manager-table-body');\n            tbody.innerHTML = '';\n            incidents.sort((a,b) => new Date(b.createdAt) - new Date(a.createdAt)).forEach(inc => {\n                const techName = users.find(u => u.id === inc.techId)?.displayName || '<span class=\"text-danger small\">Sin asignar<\/span>';\n                const date = new Date(inc.createdAt).toLocaleDateString();\n                tbody.innerHTML += `\n                    <tr>\n                        <td><small class=\"text-muted\">#${inc.id.substring(0,4)}<br>${date}<\/small><\/td>\n                        <td><b>${inc.clientName}<\/b><br><small>${inc.title}<\/small><\/td>\n                        <td>${techName}<\/td>\n                        <td><span class=\"badge bg-${statusColors[inc.status]}\">${inc.status}<\/span><\/td>\n                        <td><span class=\"badge bg-${paymentColors[inc.paymentStatus || 'Pendiente']}\">${inc.paymentStatus || 'Pendiente'}<\/span><\/td>\n                        <td><small>${inc.initialCost || 0}\u20ac \/ <span class=\"text-success fw-bold\">${inc.finalCost || 0}\u20ac<\/span><\/small><\/td>\n                        <td class=\"text-end\">\n                            <button class=\"btn btn-outline-primary btn-sm\" onclick=\"window.openManageModal('${inc.id}')\"><i class=\"bi bi-pencil\"><\/i><\/button>\n                            <button class=\"btn btn-outline-dark btn-sm\" onclick=\"window.openTrackModal('${inc.id}')\"><i class=\"bi bi-eye\"><\/i> Seguir<\/button>\n                        <\/td>\n                    <\/tr>\n                `;\n            });\n        }\n\n        function updateTechsTable() {\n            const tbody = document.getElementById('techs-table-body');\n            tbody.innerHTML = '';\n            users.filter(u => u.role === 'tech').forEach(t => {\n                const active = incidents.filter(i => i.techId === t.id && i.status !== 'Finalizada').length;\n                tbody.innerHTML += `<tr><td class=\"fw-bold\">${t.displayName}<\/td><td>${t.specialty || 'General'}<\/td><td><span class=\"badge bg-${active > 0 ? 'warning text-dark' : 'success'}\">${active} Activos<\/span><\/td><td class=\"text-end\"><button class=\"btn btn-sm btn-dark\" onclick=\"window.openTechTrack('${t.id}')\">Seguir T\u00e9cnico<\/button><\/td><\/tr>`;\n            });\n        }\n\n        function updateClientsTable() {\n            const tbody = document.getElementById('clients-table-body');\n            tbody.innerHTML = '';\n            users.filter(u => u.role === 'client').forEach(c => {\n                tbody.innerHTML += `<tr><td class=\"fw-bold\">${c.displayName}<\/td><td>Cliente<\/td><td><small>${c.id}<\/small><\/td><\/tr>`;\n            });\n        }\n\n        function updateResourcesTable() {\n            const tbody = document.getElementById('resources-table-body');\n            tbody.innerHTML = '';\n            resources.forEach(r => {\n                tbody.innerHTML += `<tr><td><small>${r.id.substring(0,5)}<\/small><\/td><td class=\"fw-bold\">${r.name}<\/td><td>${r.type}<\/td><td><span class=\"badge bg-success\">Disponible<\/span><\/td><\/tr>`;\n            });\n        }\n\n        function updateKPIs() {\n            const budget = incidents.reduce((acc, curr) => acc + (parseFloat(curr.initialCost) || 0), 0);\n            const invoice = incidents.reduce((acc, curr) => acc + (parseFloat(curr.finalCost) || 0), 0);\n            const cash = incidents.filter(i => i.paymentStatus === 'Cobrado').reduce((acc, curr) => acc + (parseFloat(curr.finalCost) || 0), 0);\n\n            document.getElementById('kpi-budget').innerText = budget.toFixed(2) + ' \u20ac';\n            document.getElementById('kpi-invoice').innerText = invoice.toFixed(2) + ' \u20ac';\n            document.getElementById('kpi-cash').innerText = cash.toFixed(2) + ' \u20ac';\n        }\n\n        \/\/ --- MAPA (Leaflet) ---\n        function initMap() {\n            if(map) return;\n            map = L.map('map').setView([42.8, -8.0], 8);\n            L.tileLayer('https:\/\/{s}.basemaps.cartocdn.com\/light_all\/{z}\/{x}\/{y}{r}.png', { attribution: '&copy; OpenStreetMap' }).addTo(map);\n\n            map.on('contextmenu', (e) => {\n                document.getElementById('map-lat').value = e.latlng.lat;\n                document.getElementById('map-lng').value = e.latlng.lng;\n                document.getElementById('map-create-desc').value = '';\n                mapCreateModal.show();\n            });\n            setTimeout(() => map.invalidateSize(), 500);\n        }\n\n        window.saveMapIncident = function() {\n            runWithConfirm(\"\u00bfCrear incidencia en esta ubicaci\u00f3n?\", async () => {\n                const clientSelect = document.getElementById('map-create-client');\n                const clientName = clientSelect.options[clientSelect.selectedIndex].text;\n                \n                incidents.push({\n                    id: generateId(),\n                    clientId: clientSelect.value, clientName: clientName,\n                    title: 'Incidencia desde Mapa', incidentType: 'General',\n                    description: document.getElementById('map-create-desc').value,\n                    priority: document.getElementById('map-create-priority').value,\n                    status: 'Pendiente', paymentStatus: 'Pendiente',\n                    latitude: parseFloat(document.getElementById('map-lat').value),\n                    longitude: parseFloat(document.getElementById('map-lng').value),\n                    createdAt: new Date().toISOString(),\n                    initialCost: 0, finalCost: 0,\n                    techId: document.getElementById('map-create-tech').value || null,\n                    history: [createHistoryEvent('Apertura', 'A\u00f1adida manualmente en el mapa por Administrador.')]\n                });\n                saveData();\n                mapCreateModal.hide();\n            });\n        }\n\n        function updateMap() {\n            if(!map) return;\n            markers.forEach(m => map.removeLayer(m));\n            markers = [];\n\n            const activeIncs = incidents.filter(i => i.status !== 'Finalizada' && i.status !== 'Resuelta');\n\n            activeIncs.forEach(inc => {\n                if(inc.latitude && inc.longitude) {\n                    let hexColor = inc.status === 'Pendiente' ? '#dc3545' : '#0dcaf0';\n                    const markerHtml = `<div style=\"background-color: ${hexColor}; width: 20px; height: 20px; border-radius: 50%; border: 3px solid white; box-shadow: 0 0 5px rgba(0,0,0,0.5);\"><\/div>`;\n                    const icon = L.divIcon({ html: markerHtml, className: '' });\n                    const m = L.marker([inc.latitude, inc.longitude], {icon}).addTo(map);\n                    \n                    const techOptions = '<option value=\"\">Sin asignar<\/option>' + users.filter(u=>u.role==='tech').map(t => `<option value=\"${t.id}\" ${inc.techId === t.id ? 'selected' : ''}>${t.displayName}<\/option>`).join('');\n                    const statusOptions = ['Pendiente', 'Asignada', 'En progreso', 'Resuelta', 'Finalizada'].map(s => `<option value=\"${s}\" ${inc.status === s ? 'selected' : ''}>${s}<\/option>`).join('');\n\n                    const popupContent = `\n                        <div style=\"min-width: 200px;\">\n                            <h6 class=\"border-bottom pb-2 mb-2\"><b>${inc.title}<\/b><\/h6>\n                            <label class=\"small fw-bold\">T\u00e9cnico:<\/label>\n                            <select id=\"pop-tech-${inc.id}\" class=\"form-select form-select-sm mb-2\">${techOptions}<\/select>\n                            <label class=\"small fw-bold\">Estado:<\/label>\n                            <select id=\"pop-status-${inc.id}\" class=\"form-select form-select-sm mb-3\">${statusOptions}<\/select>\n                            <button class=\"btn btn-primary btn-sm w-100\" onclick=\"window.savePopupData('${inc.id}')\">Guardar<\/button>\n                        <\/div>\n                    `;\n                    m.bindPopup(popupContent);\n                    markers.push(m);\n                }\n            });\n        }\n\n        window.savePopupData = function(id) {\n            runWithConfirm(\"\u00bfActualizar desde el mapa?\", async () => {\n                const techId = document.getElementById(`pop-tech-${id}`).value;\n                const status = document.getElementById(`pop-status-${id}`).value;\n                const inc = incidents.find(i=>i.id===id);\n                if(inc) {\n                    inc.history.push(createHistoryEvent('Actualizaci\u00f3n Mapa', `Cambio a estado: ${status}`));\n                    inc.techId = techId || null;\n                    inc.status = status;\n                    saveData();\n                    map.closePopup();\n                }\n            });\n        };\n\n        \/\/ --- GR\u00c1FICOS (Pie Chart) ---\n        function initChart() {\n            if(statusPieChart) return;\n            const ctx = document.getElementById('statusPieChart').getContext('2d');\n            statusPieChart = new Chart(ctx, {\n                type: 'doughnut',\n                data: {\n                    labels: ['Pendiente', 'Asignada', 'En progreso', 'Resuelta', 'Finalizada'],\n                    datasets: [{ data: [0,0,0,0,0], backgroundColor: ['#dc3545', '#ffc107', '#0dcaf0', '#0d6efd', '#198754'] }]\n                },\n                options: { responsive: true, maintainAspectRatio: false }\n            });\n        }\n\n        function updateChart() {\n            if(!statusPieChart) return;\n            const counts = { 'Pendiente':0, 'Asignada':0, 'En progreso':0, 'Resuelta':0, 'Finalizada':0 };\n            incidents.forEach(inc => { if(counts[inc.status] !== undefined) counts[inc.status]++; });\n            statusPieChart.data.datasets[0].data = Object.values(counts);\n            statusPieChart.update();\n        }\n\n        \/\/ --- MODALES Y SEGUIMIENTO ---\n        window.openManageModal = function(id) {\n            const inc = incidents.find(i => i.id === id);\n            if(!inc) return;\n            document.getElementById('edit-inc-id').value = id;\n            document.getElementById('edit-tech').value = inc.techId || '';\n            document.getElementById('edit-status').value = inc.status;\n            document.getElementById('edit-payment').value = inc.paymentStatus || 'Pendiente';\n            document.getElementById('edit-initial-cost').value = inc.initialCost || 0;\n            document.getElementById('edit-final-cost').value = inc.finalCost || 0;\n            editModal.show();\n        };\n\n        window.saveEditIncident = function() {\n            runWithConfirm(\"\u00bfConfirmas la actualizaci\u00f3n de la incidencia?\", async () => {\n                const id = document.getElementById('edit-inc-id').value;\n                const status = document.getElementById('edit-status').value;\n                const inc = incidents.find(i=>i.id===id);\n                if(inc) {\n                    if(inc.status !== status) inc.history.push(createHistoryEvent('Actualizaci\u00f3n', `Manager cambi\u00f3 estado a ${status}`));\n                    inc.techId = document.getElementById('edit-tech').value || null;\n                    inc.status = status;\n                    inc.paymentStatus = document.getElementById('edit-payment').value;\n                    inc.initialCost = parseFloat(document.getElementById('edit-initial-cost').value);\n                    inc.finalCost = parseFloat(document.getElementById('edit-final-cost').value);\n                    saveData();\n                    editModal.hide();\n                }\n            });\n        };\n\n        window.openTrackModal = function(id) { currentTrackId = id; renderTrackModal(id); trackModal.show(); };\n\n        function renderTrackModal(id) {\n            const inc = incidents.find(i => i.id === id);\n            if(!inc) return;\n            document.getElementById('track-creator').innerText = inc.clientName;\n            const techObj = users.find(u => u.id === inc.techId);\n            document.getElementById('track-tech').innerText = techObj ? techObj.displayName : 'Sin asignar';\n            document.getElementById('track-desc').innerText = inc.description;\n            const tl = document.getElementById('track-timeline');\n            tl.innerHTML = '';\n            (inc.history || []).forEach(h => {\n                tl.innerHTML += `<div class=\"timeline-item\"><small class=\"text-muted\">${new Date(h.timestamp).toLocaleString()} - <b>${h.user}<\/b> (${h.action})<\/small><p class=\"mb-0 bg-white p-2 border rounded shadow-sm\">${h.comment}<\/p><\/div>`;\n            });\n            const footer = document.getElementById('track-footer-manager');\n            if(currentUser.role === 'manager') footer.classList.remove('d-none'); else footer.classList.add('d-none');\n        }\n\n        window.addCommentToIncident = function() {\n            const text = document.getElementById('track-new-comment').value.trim();\n            if(!text) return;\n            runWithConfirm(\"\u00bfA\u00f1adir comentario al historial?\", async () => {\n                const inc = incidents.find(i => i.id === currentTrackId);\n                if(inc) {\n                    inc.history.push(createHistoryEvent('Comentario', text));\n                    saveData();\n                    document.getElementById('track-new-comment').value = '';\n                }\n            });\n        };\n\n        window.changeIncidentStatusQuick = function(status) {\n            runWithConfirm(`\u00bfMarcar incidencia como ${status}?`, async () => {\n                const inc = incidents.find(i => i.id === currentTrackId);\n                if(inc) {\n                    inc.history.push(createHistoryEvent('Acci\u00f3n R\u00e1pida', `Incidencia ${status} por Manager`));\n                    inc.status = status;\n                    saveData();\n                }\n            });\n        };\n\n        window.openTechTrack = function(techId) {\n            const tech = users.find(u => u.id === techId);\n            if(!tech) return;\n            document.getElementById('tech-track-name').innerText = tech.displayName;\n            const assigned = incidents.filter(i => i.techId === techId);\n            const created = incidents.filter(i => i.clientId === techId);\n            const renderList = (arr, containerId) => {\n                const c = document.getElementById(containerId);\n                c.innerHTML = '';\n                if(arr.length===0) c.innerHTML='<li class=\"list-group-item text-muted\">Ninguna<\/li>';\n                arr.forEach(i => { c.innerHTML += `<li class=\"list-group-item d-flex justify-content-between align-items-center\"><div><b>${i.title}<\/b><br><small>${i.clientName}<\/small><\/div><span class=\"badge bg-${statusColors[i.status]}\">${i.status}<\/span><\/li>`; });\n            };\n            renderList(assigned, 'tech-assigned-list');\n            renderList(created, 'tech-created-list');\n            techTrackModal.show();\n        };\n\n        async function seedRandomIncident(forClient = true) {\n            const types = ['Electrica', 'Telecomunicaciones', 'Fontaneria'];\n            const priorities = ['Alta', 'Media', 'Baja'];\n            const clients = users.filter(u => u.role === 'client');\n            if(clients.length === 0) { alert(\"Crea un cliente primero.\"); return; }\n            const client = clients[Math.floor(Math.random() * clients.length)];\n            \n            incidents.push({\n                id: generateId(),\n                clientId: client.id, clientName: client.displayName,\n                title: 'Aver\u00eda Generada Random ' + Math.floor(Math.random()*100),\n                incidentType: types[Math.floor(Math.random()*types.length)],\n                description: 'Aver\u00eda generada para testeo.',\n                priority: priorities[Math.floor(Math.random()*priorities.length)],\n                status: 'Pendiente', paymentStatus: 'Pendiente',\n                latitude: 42.1 + Math.random() * 1.4, longitude: -8.9 + Math.random() * 1.8,\n                createdAt: new Date().toISOString(),\n                initialCost: Math.floor(Math.random() * 200) + 50, finalCost: 0,\n                techId: null, history: [createHistoryEvent('Apertura', 'Generada por Bot\u00f3n Random')]\n            });\n            saveData();\n        }\n    <\/script>\n<\/body>\n<\/html>\n\n\n\n<p><strong><em>Gestor de avisos v.0.1<\/em><\/strong>. 08\/05\/2026<\/p>\n\n\n\n<p>Mejoras y cambios aplicados:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Framework Responsive.<\/li>\n\n\n\n<li><strong>Perfil administrador:<\/strong> Vista del perfil dividida en pesta\u00f1as [ Estad\u00edsticas, Incidencias, T\u00e9cnicos, Clientes, Recursos, Mapa] Esta pesta\u00f1a tiene botones para a\u00f1adir de forma Random, t\u00e9cnicos, clientes, recursos, incidencias.<\/li>\n\n\n\n<li><strong>Pesta\u00f1a de Estad\u00edsticas:<\/strong> Gr\u00e1fico con total incidencias. N\u00famero total de incidencias con un color que indica su estado. Cuadro de presupuesto con el coste inicial estimado. Cuadro Factura con el total de facturas emitidas. Cuadro Caja con lo realmente cobrado.<\/li>\n\n\n\n<li><strong>Pesta\u00f1a de Incidencias:<\/strong> Las incidencias deben tener dos acciones [Editar \/ Seguir]. Si incidencia finalizada no aparece en Mapa. La incidencia debe guardar un campo <strong>cobro <\/strong>(Pendiente, Emitido, Cobrado)<\/li>\n\n\n\n<li><strong>Pesta\u00f1a de T\u00e9cnicos: <\/strong>Dos pesta\u00f1as una con las incidencias ordenadas por prioridad y una segunda para crear nuevas incidencias. Para cada t\u00e9cnico el administrador debe poder revisar cada incidencia atendida por el t\u00e9cnico, los cambios de estado as\u00ed como los comentarios tanto del cliente como de t\u00e9cnico o <strong>t\u00e9cnicos<\/strong> (pendiente) que han intervenido.<\/li>\n\n\n\n<li><strong>Pesta\u00f1a de Recursos: <\/strong>Crear un listado de recursos base para asignar a t\u00e9cnicos<\/li>\n\n\n\n<li><strong>Pesta\u00f1a de Mapa: <\/strong>Con marcadores editables para asignar t\u00e9cnico, cambiar estado o a\u00f1adir comentarios. El administrador puede a\u00f1adir incidencias sobre el propio mapa con bot\u00f3n derecho.<\/li>\n<\/ul>\n\n\n\n<p><strong><em>Gestor de avisos v.0.3.<\/em><\/strong><\/p>\n\n\n\n<p>Mejoras y cambios <strong><em>pendientes <\/em><\/strong>por aplicar:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Incidencias: <\/strong>Pueden ser asignados m\u00e1s de un t\u00e9cnico a una incidencia, guard\u00e1ndose el motivo por el que se produce cambio de t\u00e9cnico o se necesitan m\u00e1s de uno.<\/li>\n\n\n\n<li><strong>T\u00e9cnicos: <\/strong>Calendario de tareas seg\u00fan el tiempo estimado asignado y el tiempo final que lleva atender la incidencia.<\/li>\n\n\n\n<li><strong>T\u00e9cnicos: <\/strong>Tendr\u00e1n una pesta\u00f1a m\u00e1s de Mapa con las incidencias asignadas y otra pesta\u00f1a m\u00e1s de las incidencias pendientes de ser asignas, con una lista de las m\u00e1s cercanas.<\/li>\n\n\n\n<li><strong>Mapa: <\/strong>Dos pesta\u00f1as: una con los marcadores de las incidencias, otra con la ubicaci\u00f3n real de los t\u00e9cnicos. \u00datil para asignar incidencias cercanas.<\/li>\n\n\n\n<li><strong>Estad\u00edsticas:<\/strong> Crear cuadro con lo pendiente por cobrar de las facturas emitidas, crear cuadro con los trabajos pendientes por emitir factura.<\/li>\n\n\n\n<li><strong>Recursos: <\/strong>Pendiente saber qu\u00e9 tipo de recursos para crear un inventario y que cada t\u00e9cnico sepa lo que le falta y hacer provisi\u00f3n de herramienta, material o recursos.<\/li>\n\n\n\n<li><strong>Agenda: <\/strong>Pesta\u00f1a nueva con la asignaci\u00f3n de todos los t\u00e9cnicos en la agenda.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Gestor de avisos v.0.2. 09\/05\/2026 AvisosMana V3 &#8211; CRM Profesional (Offline Mode) AvisosMana Salir AvisosMana \ud83d\udc51 Administrador Local Entrar al Sistema Panel de Administraci\u00f3n + T\u00e9cnico + Cliente + Recurso + Incidencia \ud83d\udcca Estad\u00edsticas \ud83d\udccb Incidencias \ud83d\udc77 T\u00e9cnicos \ud83c\udfe2 Clientes \ud83d\udd27 Recursos \ud83d\uddfa\ufe0f Mapa Presupuesto 0 \u20ac Factura 0 \u20ac Caja (Real) 0 \u20ac Distribuci\u00f3n <a class=\"more-link\" href=\"https:\/\/educaenelfuturo.com\/index.php\/gestor-de-avisos\/\">Leer m\u00e1s &#8230;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_exactmetrics_skip_tracking":false,"_exactmetrics_sitenote_active":false,"_exactmetrics_sitenote_note":"","_exactmetrics_sitenote_category":0,"footnotes":""},"class_list":["post-1095","page","type-page","status-publish","hentry"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/educaenelfuturo.com\/index.php\/wp-json\/wp\/v2\/pages\/1095","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/educaenelfuturo.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/educaenelfuturo.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/educaenelfuturo.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/educaenelfuturo.com\/index.php\/wp-json\/wp\/v2\/comments?post=1095"}],"version-history":[{"count":31,"href":"https:\/\/educaenelfuturo.com\/index.php\/wp-json\/wp\/v2\/pages\/1095\/revisions"}],"predecessor-version":[{"id":1139,"href":"https:\/\/educaenelfuturo.com\/index.php\/wp-json\/wp\/v2\/pages\/1095\/revisions\/1139"}],"wp:attachment":[{"href":"https:\/\/educaenelfuturo.com\/index.php\/wp-json\/wp\/v2\/media?parent=1095"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}