Project import generated by Copybara.

GitOrigin-RevId: 63746295f1a5ab5a619056791995793d65529e62
diff --git a/src/dynamic/addincidentbulk.php b/src/dynamic/addincidentbulk.php
new file mode 100644
index 0000000..afb04ba
--- /dev/null
+++ b/src/dynamic/addincidentbulk.php
@@ -0,0 +1,86 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!security::checkParams("GET", [
+  ["workers", security::PARAM_ISARRAY]
+])) {
+  security::notFound();
+}
+?>
+
+<style>
+.notvisible {
+  display: none;
+}
+</style>
+
+<dynscript>
+document.getElementById("allday").addEventListener("change", e => {
+  var partialtime = document.getElementById("partialtime");
+  if (e.target.checked) {
+    partialtime.classList.add("notvisible");
+  } else {
+    partialtime.classList.remove("notvisible");
+  }
+});
+</dynscript>
+
+<form action="doaddincidentbulk.php" method="POST" autocomplete="off">
+  <h4 class="mdl-dialog__title">Añade una incidencia</h4>
+  <div class="mdl-dialog__content">
+    <div class="mdlext-selectfield mdlext-js-selectfield mdlext-selectfield--floating-label">
+      <select name="type" id="type" class="mdlext-selectfield__select" data-required>
+        <option></option>
+        <?php
+        foreach (incidents::getTypesForm() as $i) {
+          echo '<option value="'.(int)$i["id"].'">'.security::htmlsafe($i["name"]).'</option>';
+        }
+        ?>
+      </select>
+      <label for="type" class="mdlext-selectfield__label">Tipo</label>
+    </div>
+
+    <h5>Afectación</h5>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="date" name="day" id="day" autocomplete="off" data-required>
+      <label class="mdl-textfield__label always-focused" for="day">Día</label>
+    </div>
+    <br>
+    <p>
+      <label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="allday">
+        <input type="checkbox" id="allday" name="allday" value="1" class="mdl-switch__input">
+        <span class="mdl-switch__label">Día entero</span>
+      </label>
+    </p>
+    <div id="partialtime">De <input type="time" name="begins"> a <input type="time" name="ends"></div>
+
+    <h5>Detalles adicionales</h5>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <textarea class="mdl-textfield__input" name="details" id="details"></textarea>
+      <label class="mdl-textfield__label" for="details">Observaciones (opcional)</label>
+    </div>
+    <p>Las observaciones aparecerán en los PDFs que se exporten.</p>
+    <p>Después de crear la incidencia podrás añadir archivos adjuntos haciendo clic en el botón <i class="material-icons" style="vertical-align: middle;">attach_file</i>.</p>
+
+    <b>Trabajadores:</b>
+    <div class="copyto">
+      <ul>
+        <?php
+        foreach ($_GET["workers"] as $workerid) {
+          $worker = workers::get($workerid);
+          if ($worker === false) {
+            die("Error: Uno de los trabajadores seleccionados ya no existe");
+          }
+
+          echo "<li><input type='hidden' name='workers[]' value='".(int)$worker["id"]."'> ".security::htmlsafe($worker["name"])." (".security::htmlsafe($worker["companyname"]).")</li>";
+        }
+        ?>
+      </ul>
+    </div>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Añadir</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/addworkhistoryitem.php b/src/dynamic/addworkhistoryitem.php
new file mode 100644
index 0000000..712c3f6
--- /dev/null
+++ b/src/dynamic/addworkhistoryitem.php
@@ -0,0 +1,50 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$worker = workers::get($id);
+if ($worker === false) security::notFound();
+?>
+
+<dynscript>
+document.getElementById("cancel").addEventListener("click", e => {
+  e.preventDefault();
+  dynDialog.load("dynamic/workhistory.php?id="+parseInt(document.getElementById("cancel").getAttribute("data-worker-id")));
+});
+</dynscript>
+
+<form action="doaddworkhistoryitem.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$worker["id"]?>">
+  <h4 class="mdl-dialog__title">Añadir alta/baja</h4>
+  <div class="mdl-dialog__content">
+    <p><b>Persona:</b> <?=security::htmlsafe($worker["name"])?><br>
+    <b>Empresa:</b> <?=security::htmlsafe($worker["companyname"])?></p>
+
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="date" name="day" id="day" autocomplete="off" data-required>
+      <label class="mdl-textfield__label" for="day">Fecha</label>
+    </div>
+    <br>
+    <div class="mdlext-selectfield mdlext-js-selectfield mdlext-selectfield--floating-label">
+      <select name="status" id="status" class="mdlext-selectfield__select" data-required>
+        <option></option>
+        <?php
+        foreach (workers::$affiliationStatusesManual as $status) {
+          echo '<option value="'.(int)$status.'">'.security::htmlsafe(workers::affiliationStatusHelper($status)).'</option>';
+        }
+        ?>
+      </select>
+      <label for="status" class="mdlext-selectfield__label">Tipo</label>
+    </div>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Añadir</button>
+    <button id="cancel" class="mdl-button mdl-js-button mdl-js-ripple-effect" data-worker-id="<?=(int)$worker["id"]?>">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/authorsincident.php b/src/dynamic/authorsincident.php
new file mode 100644
index 0000000..25194f3
--- /dev/null
+++ b/src/dynamic/authorsincident.php
@@ -0,0 +1,43 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::WORKER, security::METHOD_NOTFOUND);
+security::checkWorkerUIEnabled();
+
+$isAdmin = security::isAllowed(security::ADMIN);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$incident = incidents::get($id, true);
+if ($incident === false) security::notFound();
+
+if (!$isAdmin) incidents::checkIncidentIsFromPerson($incident["id"]);
+?>
+
+<style>
+#dynDialog {
+  max-width: 380px;
+  width: auto;
+}
+</style>
+
+<h4 class="mdl-dialog__title">Autoría de la incidencia</h4>
+<div class="mdl-dialog__content">
+  <ul>
+    <?php if ($incident["creator"] != -1) { ?><li><b>Creador:</b> <?=security::htmlsafe(people::userData("name", $incident["creator"]))?></li><?php } ?>
+    <?php if ($incident["updatedby"] != -1) { ?><li><b>Última modificación por:</b> <?=security::htmlsafe(people::userData("name", $incident["updatedby"]))?></li><?php } ?>
+    <?php if ($incident["confirmedby"] != -1) { ?><li><b>Revisor:</b> <?=security::htmlsafe(people::userData("name", $incident["confirmedby"]))?></li><?php } ?>
+    <?php
+    if ($incident["state"] == incidents::STATE_VALIDATED_BY_WORKER) {
+      $validation = json_decode($incident["workervalidation"], true);
+      validationsView::renderValidationInfo($validation);
+    }
+    ?>
+  </ul>
+</div>
+<div class="mdl-dialog__actions">
+  <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Cerrar</button>
+</div>
diff --git a/src/dynamic/authorsrecord.php b/src/dynamic/authorsrecord.php
new file mode 100644
index 0000000..8888af3
--- /dev/null
+++ b/src/dynamic/authorsrecord.php
@@ -0,0 +1,43 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::WORKER, security::METHOD_NOTFOUND);
+security::checkWorkerUIEnabled();
+
+$isAdmin = security::isAllowed(security::ADMIN);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$record = registry::get($id, true);
+if ($record === false) security::notFound();
+
+if (!$isAdmin) registry::checkRecordIsFromPerson($record["id"]);
+?>
+
+<style>
+#dynDialog {
+  max-width: 380px;
+  width: auto;
+}
+</style>
+
+<h4 class="mdl-dialog__title">Autoría del elemento del registro</h4>
+<div class="mdl-dialog__content">
+  <ul>
+    <li><b>Creador:</b> <?=($record["creator"] == -1 ? "<span style='font-family: monospace;'>cron</span>" : security::htmlsafe(people::userData("name", $record["creator"])))?></li>
+    <li><b>Fecha de creación:</b> <?=date("d/m/Y H:i", $record["created"])?></li>
+    <?php if ($record["invalidatedby"] != -1) { ?><li><b>Invalidado por:</b> <?=security::htmlsafe(people::userData("name", $record["invalidatedby"]))?></li><?php } ?>
+    <?php
+    if ($record["state"] == registry::STATE_VALIDATED_BY_WORKER) {
+      $validation = json_decode($record["workervalidation"], true);
+      validationsView::renderValidationInfo($validation);
+    }
+    ?>
+  </ul>
+</div>
+<div class="mdl-dialog__actions">
+  <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Cerrar</button>
+</div>
diff --git a/src/dynamic/companyuser.php b/src/dynamic/companyuser.php
new file mode 100644
index 0000000..0acfac3
--- /dev/null
+++ b/src/dynamic/companyuser.php
@@ -0,0 +1,102 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$p = people::get($_GET["id"]);
+
+if ($p === false) {
+  security::notFound();
+}
+
+$companies = companies::getAll();
+?>
+
+<style>
+.mdl-dialog__content {
+  color: rgba(0,0,0,.87)!important;
+}
+
+#dynDialog {
+  max-width: 300px;
+  width: auto;
+}
+</style>
+
+<dynscript>
+var person = <?=(int)$p["id"]?>;
+
+document.querySelectorAll("button[data-company-id]").forEach(btn => {
+  btn.addEventListener("click", e => {
+    var id = e.currentTarget.getAttribute("data-company-id");
+    fetch("ajax/addpersontocompany.php", {
+      method: "post",
+      body: "person="+parseInt(person)+"&company="+parseInt(id),
+      headers: {
+        "Content-Type": "application/x-www-form-urlencoded"
+      }
+    }).then(response => {
+      response.text().then(text => console.log);
+      dynDialog.reload();
+    }).catch(error => {
+      alert("Ha habido un error dando de alta a este trabajador de esta empresa: "+error);
+    });
+  });
+});
+
+document.querySelectorAll("button[data-worker-id]").forEach(btn => {
+  btn.addEventListener("click", e => {
+    var id = e.currentTarget.getAttribute("data-worker-id");
+    dynDialog.load("dynamic/workhistory.php?id="+parseInt(id));
+  });
+});
+</dynscript>
+
+<h4 class="mdl-dialog__title"><?=security::htmlsafe($p["name"])?></h4>
+<div class="mdl-dialog__content">
+<?php
+$list = [];
+$list["visible"] = "";
+$list["hidden"] = "";
+
+$workers = workers::getPersonWorkers($p["id"]);
+foreach ($workers as $w) {
+  $list[($w["hidden"] ? "hidden" : "visible")] .= '<li>'.
+    security::htmlsafe($companies[$w["company"]]).'
+    <button class="mdl-button mdl-js-button mdl-button--icon" title="Acceder al historial de altas y bajas" data-worker-id="'.(int)$w["id"].'">
+      <i class="material-icons">history</i>
+    </button>
+    <br>
+    <span class="mdl-color-text--grey-600">'.($w["hidden"] ? "Dada de baja" : "Dada de alta").' el '.date("d/m/Y", $w["lastupdated"]).'</span></li>';
+}
+?>
+  <p><b>Dada de alta en:</b></p>
+  <?php
+  if (!empty($list["visible"])) {
+    echo "<ul>".$list["visible"]."</ul>";
+  }
+  ?>
+  <p><b>Dada de baja en:</b></p>
+  <?php
+  if (!empty($list["hidden"])) {
+    echo "<ul>".$list["hidden"]."</ul>";
+  }
+  ?>
+  <p><b>No dada de alta en:</b></p>
+  <ul>
+    <?php
+    foreach ($companies as $id => $name) {
+      if (in_array($id, $p["companies"])) continue;
+      ?>
+      <li><?=security::htmlsafe($name)?> <button class="mdl-button mdl-js-button mdl-button--icon mdl-color-text--green" title="Dar de alta en esta empresa" data-company-id="<?=(int)$id?>"><i class="material-icons">add</i></button></li>
+      <?php
+    }
+    ?>
+  </ul>
+</div>
+<div class="mdl-dialog__actions">
+  <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cerrar</button>
+</div>
diff --git a/src/dynamic/copytemplate.php b/src/dynamic/copytemplate.php
new file mode 100644
index 0000000..0f127ba
--- /dev/null
+++ b/src/dynamic/copytemplate.php
@@ -0,0 +1,52 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!security::checkParams("GET", [
+  ["workers", security::PARAM_ISARRAY]
+])) {
+  security::notFound();
+}
+?>
+
+<form action="docopytemplate.php" method="POST" autocomplete="off">
+  <h4 class="mdl-dialog__title">Copiar plantilla a trabajadores</h4>
+  <div class="mdl-dialog__content">
+    <div class="mdlext-selectfield mdlext-js-selectfield mdlext-selectfield--floating-label">
+      <select name="template" id="template" class="mdlext-selectfield__select" data-required>
+        <?php
+        $templates = schedules::getTemplates();
+        foreach ($templates as $t) {
+          echo '<option value="'.(int)$t["id"].'">'.security::htmlsafe($t["name"]).'</option>';
+        }
+        ?>
+      </select>
+      <label for="template" class="mdlext-selectfield__label">Plantilla</label>
+    </div>
+    <br>
+    <label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="active">
+      <input type="checkbox" id="active" name="active" value="1" class="mdl-switch__input">
+      <span class="mdl-switch__label">Activar horario</span>
+    </label>
+    <br><br>
+    <b>Copiar a:</b>
+    <div class="copyto">
+      <ul>
+        <?php
+        foreach ($_GET["workers"] as $workerid) {
+          $worker = workers::get($workerid);
+          if ($worker === false) {
+            die("Error: Uno de los trabajadores seleccionados ya no existe");
+          }
+
+          echo "<li><input type='hidden' name='workers[]' value='".(int)$worker["id"]."'> ".security::htmlsafe($worker["name"])." (".security::htmlsafe($worker["companyname"]).")</li>";
+        }
+        ?>
+      </ul>
+    </div>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Copiar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/deleteattachment.php b/src/dynamic/deleteattachment.php
new file mode 100644
index 0000000..66b6b1f
--- /dev/null
+++ b/src/dynamic/deleteattachment.php
@@ -0,0 +1,49 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::WORKER, security::METHOD_NOTFOUND);
+security::checkWorkerUIEnabled();
+
+if (!security::checkParams("GET", [
+  ["id", security::PARAM_ISINT],
+  ["name", security::PARAM_NEMPTY]
+])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+$name = $_GET["name"];
+
+$incident = incidents::get($id, true);
+if ($incident === false) security::notFound();
+
+if (!security::isAllowed(security::ADMIN)) incidents::checkIncidentIsFromPerson($incident["id"]);
+
+$attachments = incidents::getAttachmentsFromIncident($incident);
+
+if ($attachments === false || !count($attachments)) security::notFound();
+
+$flag = false;
+
+foreach ($attachments as $attachment) {
+  if ($attachment == $name) {
+    $flag = true;
+    ?>
+    <form action="dodeleteattachment.php" method="POST" autocomplete="off">
+      <input type="hidden" name="id" value="<?=(int)$id?>">
+      <?php visual::addContinueInput(); ?>
+      <input type="hidden" name="name" value="<?=security::htmlsafe($name)?>">
+      <h4 class="mdl-dialog__title">Eliminar archivo adjunto</h4>
+      <div class="mdl-dialog__content">
+        <p>¿Estás seguro que quieres eliminar el archivo adjunto <code><?=security::htmlsafe($name)?></code>? <span style="color:#EF5350;font-weight:bold;">Esta acción es irreversible</span></p>
+      </div>
+      <div class="mdl-dialog__actions">
+        <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Eliminar</button>
+        <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+      </div>
+    </form>
+    <?php
+    break;
+  }
+}
+
+if ($flag === false) security::notFound();
diff --git a/src/dynamic/deletecalendar.php b/src/dynamic/deletecalendar.php
new file mode 100644
index 0000000..83f19cc
--- /dev/null
+++ b/src/dynamic/deletecalendar.php
@@ -0,0 +1,26 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+if (!calendars::exists($id)) {
+  security::notFound();
+}
+?>
+
+<form action="dodeletecalendar.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$id?>">
+  <h4 class="mdl-dialog__title">Eliminar calendario</h4>
+  <div class="mdl-dialog__content">
+    <p>¿Estás seguro que quieres eliminar el calendario? <span style="color:#EF5350;font-weight:bold;">Esta acción es irreversible</span></p>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Eliminar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/deleteday.php b/src/dynamic/deleteday.php
new file mode 100644
index 0000000..d22206a
--- /dev/null
+++ b/src/dynamic/deleteday.php
@@ -0,0 +1,26 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+if (!schedules::dayExists($id)) {
+  security::notFound();
+}
+?>
+
+<form action="dodeleteday.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$id?>">
+  <h4 class="mdl-dialog__title">Eliminar horario</h4>
+  <div class="mdl-dialog__content">
+    <p>¿Estás seguro que quieres eliminar este horario diario? <span style="color:#EF5350;font-weight:bold;">Esta acción es irreversible</span></p>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Eliminar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/deleteincident.php b/src/dynamic/deleteincident.php
new file mode 100644
index 0000000..8ba455c
--- /dev/null
+++ b/src/dynamic/deleteincident.php
@@ -0,0 +1,31 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::WORKER, security::METHOD_NOTFOUND);
+security::checkWorkerUIEnabled();
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$incident = incidents::get($id);
+if ($incident === false) security::notFound();
+
+$isAdmin = security::isAdminView();
+$status = incidents::getStatus($incident);
+if (($isAdmin && !in_array($status, incidents::$canRemoveStates)) || (!$isAdmin && !in_array($status, incidents::$workerCanRemoveStates))) security::notFound();
+?>
+
+<form action="dodeleteincident.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$id?>">
+  <?php visual::addContinueInput(); ?>
+  <h4 class="mdl-dialog__title">Eliminar incidencia</h4>
+  <div class="mdl-dialog__content">
+    <p>¿Estás seguro que quieres eliminar esta incidencia? <span style="color:#EF5350;font-weight:bold;">Esta acción es irreversible</span></p>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Eliminar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/deleteincidentsbulk.php b/src/dynamic/deleteincidentsbulk.php
new file mode 100644
index 0000000..b3a397c
--- /dev/null
+++ b/src/dynamic/deleteincidentsbulk.php
@@ -0,0 +1,33 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!security::checkParams("GET", [
+  ["incidents", security::PARAM_ISARRAY]
+])) {
+  security::notFound();
+}
+?>
+
+<style>
+#dynDialog, #dynDialog .mdl-dialog__content {
+  background: #FFCDD2;
+}
+</style>
+
+<form action="dodeleteincidentsbulk.php" method="POST" autocomplete="off">
+  <?php
+  foreach ($_GET["incidents"] as $incident) {
+    echo "<input type='hidden' name='incidents[]' value='".(int)$incident."'></li>";
+  }
+  ?>
+  <h4 class="mdl-dialog__title">Eliminar/invalidar incidencias</h4>
+  <div class="mdl-dialog__content">
+    <p>¿Estás seguro que quieres eliminar/invalidar estas incidencias? <span style="color:#EF5350;font-weight:bold;">Esta acción es irreversible</span></p>
+    <p>Dependiendo del estado de cada incidencia, esta se eliminará o se invalidará.</p>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Eliminar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/deleteschedule.php b/src/dynamic/deleteschedule.php
new file mode 100644
index 0000000..52c14f9
--- /dev/null
+++ b/src/dynamic/deleteschedule.php
@@ -0,0 +1,26 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+if (!schedules::exists($id)) {
+  security::notFound();
+}
+?>
+
+<form action="dodeleteschedule.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$id?>">
+  <h4 class="mdl-dialog__title">Eliminar horario</h4>
+  <div class="mdl-dialog__content">
+    <p>¿Estás seguro que quieres eliminar este horario? <span style="color:#EF5350;font-weight:bold;">Esta acción es irreversible</span></p>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Eliminar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/deletescheduletemplate.php b/src/dynamic/deletescheduletemplate.php
new file mode 100644
index 0000000..ad3a1ae
--- /dev/null
+++ b/src/dynamic/deletescheduletemplate.php
@@ -0,0 +1,26 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+if (!schedules::templateExists($id)) {
+  security::notFound();
+}
+?>
+
+<form action="dodeletescheduletemplate.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$id?>">
+  <h4 class="mdl-dialog__title">Eliminar plantilla</h4>
+  <div class="mdl-dialog__content">
+    <p>¿Estás seguro que quieres eliminar esta plantilla? <span style="color:#EF5350;font-weight:bold;">Esta acción es irreversible</span></p>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Eliminar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/deletesecuritykey.php b/src/dynamic/deletesecuritykey.php
new file mode 100644
index 0000000..8b62609
--- /dev/null
+++ b/src/dynamic/deletesecuritykey.php
@@ -0,0 +1,24 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::WORKER, security::METHOD_NOTFOUND);
+security::checkWorkerUIEnabled();
+secondFactor::checkAvailability();
+
+if (!isset($_GET["id"])) security::notFound();
+$id = (int)$_GET["id"];
+
+$s = secondFactor::getSecurityKeyById($id);
+if ($s === false || people::userData("id") != $s["person"]) security::notFound();
+?>
+
+<form action="dodeletesecuritykey.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$s["id"]?>">
+  <h4 class="mdl-dialog__title">Eliminar llave de seguridad</h4>
+  <div class="mdl-dialog__content">
+    <p>¿Estás seguro que quieres eliminar la llave de seguridad <b><?=security::htmlsafe($s["name"])?></b>? <span style="color:#EF5350;font-weight:bold;">Una vez la elimines no tendrás la opción de escogerla como segundo factor.</span></p>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Eliminar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/deletetemplateday.php b/src/dynamic/deletetemplateday.php
new file mode 100644
index 0000000..63e0cb8
--- /dev/null
+++ b/src/dynamic/deletetemplateday.php
@@ -0,0 +1,26 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+if (!schedules::templateDayExists($id)) {
+  security::notFound();
+}
+?>
+
+<form action="dodeletetemplateday.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$id?>">
+  <h4 class="mdl-dialog__title">Eliminar horario</h4>
+  <div class="mdl-dialog__content">
+    <p>¿Estás seguro que quieres eliminar este horario de la plantilla? <span style="color:#EF5350;font-weight:bold;">Esta acción es irreversible</span></p>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Eliminar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/deleteworkhistoryitem.php b/src/dynamic/deleteworkhistoryitem.php
new file mode 100644
index 0000000..0dd5613
--- /dev/null
+++ b/src/dynamic/deleteworkhistoryitem.php
@@ -0,0 +1,44 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$item = workers::getWorkHistoryItem($id);
+if ($item === false) security::notFound();
+
+$isHidden = workers::isHidden($item["status"]);
+
+$worker = workers::get($item["worker"]);
+if ($worker === false) security::notFound();
+
+$helper = security::htmlsafe(strtolower(workers::affiliationStatusHelper($item["status"])));
+?>
+
+<dynscript>
+document.getElementById("cancel").addEventListener("click", e => {
+  e.preventDefault();
+  dynDialog.load("dynamic/workhistory.php?id="+parseInt(document.getElementById("cancel").getAttribute("data-worker-id")));
+});
+</dynscript>
+
+<form action="dodeleteworkhistoryitem.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$id?>">
+  <h4 class="mdl-dialog__title">Eliminar registro de <?=security::htmlsafe($helper)?></h4>
+
+  <div class="mdl-dialog__content">
+    <p><b>Persona:</b> <?=security::htmlsafe($worker["name"])?><br>
+    <b>Empresa:</b> <?=security::htmlsafe($worker["companyname"])?><br>
+    <b>Día:</b> <?=security::htmlsafe(date("d/m/Y", $item["day"]))?></p>
+
+    <p>¿Estás seguro que quieres eliminar este registro de <?=security::htmlsafe($helper)?>? <span style="color:#EF5350;font-weight:bold;">Esta acción es irreversible</span></p>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Eliminar</button>
+    <button id="cancel" class="mdl-button mdl-js-button mdl-js-ripple-effect" data-worker-id="<?=(int)$worker["id"]?>">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/disablesecondfactor.php b/src/dynamic/disablesecondfactor.php
new file mode 100644
index 0000000..ad9913e
--- /dev/null
+++ b/src/dynamic/disablesecondfactor.php
@@ -0,0 +1,55 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::WORKER, security::METHOD_NOTFOUND);
+security::checkWorkerUIEnabled();
+secondFactor::checkAvailability();
+
+if (!isset($_GET["id"])) security::notFound();
+$id = (int)$_GET["id"];
+
+if (!secondFactor::isEnabled($id)) {
+  security::notFound();
+}
+
+if (!security::isAllowed(security::ADMIN) && $id != people::userData("id")) security::notFound();
+
+if ($id == people::userData("id")) {
+?>
+<style>
+#dynDialog {
+  max-width: 500px;
+  width: auto;
+}
+</style>
+<?php
+}
+?>
+
+<form action="dodisablesecondfactor.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$id?>">
+  <h4 class="mdl-dialog__title">Desactivar la verificación en dos pasos</h4>
+  <div class="mdl-dialog__content">
+    <?php
+    if ($id != people::userData("id")) {
+      ?>
+      <p>¿Estás seguro que quieres desactivar la verificación en dos pasos para <b><?=security::htmlsafe(people::userData("name", $id))?></b>?</p>
+      <p>Esta acción solo debe tomarse cuando el trabajador no puede acceder a su cuenta, puesto que <span style="color:#EF5350;font-weight:bold;">esta acción solo la puede revertir el trabajador reactivando de nuevo la verificación en dos pasos</span></p>
+      <?php
+    } else {
+      ?>
+      <p>¿Estás seguro que quieres desactivar la verificación en dos pasos?</p>
+      <p>La verificación en 2 pasos ofrece seguridad extra a tu cuenta. Si inhabilitas la verificación en 2 pasos todas tus llaves de seguridad se desvincularán de esta cuenta y no te pediremos ningún código de verificación al iniciar sesión.</p>
+      <p>Si aun así sigues quiriendo desactivarla, introduce tu contraseña y haz clic en el botón Desactivar.</p>
+      <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+        <input class="mdl-textfield__input" type="password" name="password" id="password" data-required>
+        <label class="mdl-textfield__label" for="password">Contraseña actual</label>
+      </div>
+      <?php
+    }
+    ?>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Desactivar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/editcategory.php b/src/dynamic/editcategory.php
new file mode 100644
index 0000000..1057df1
--- /dev/null
+++ b/src/dynamic/editcategory.php
@@ -0,0 +1,53 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$c = categories::get($_GET["id"]);
+
+if ($c === false) {
+  security::notFound();
+}
+?>
+
+<form action="doeditcategory.php" method="POST" autocomplete="off">
+  <h4 class="mdl-dialog__title">Editar categoría</h4>
+  <div class="mdl-dialog__content">
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="text" name="id" id="edit_id" value="<?=security::htmlsafe($c['id'])?>" readonly="readonly" autocomplete="off">
+      <label class="mdl-textfield__label" for="edit_nombre">ID</label>
+    </div>
+    <br>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="text" name="name" id="edit_name"  value="<?=security::htmlsafe($c['name'])?>" autocomplete="off" data-required>
+      <label class="mdl-textfield__label" for="edit_name">Nombre de la categoría</label>
+    </div>
+    <br>
+    <div class="mdlext-selectfield mdlext-js-selectfield mdlext-selectfield--floating-label">
+      <select name="parent" id="parent" class="mdlext-selectfield__select">
+        <option value="0"></option>
+        <?php
+        foreach (categories::getAll(false) as $category) {
+          if ($category["parent"] == 0 && $category["id"] != $c["id"]) {
+            echo '<option value="'.$category["id"].'"'.($c["parent"] == $category["id"] ? "selected" : "").'>'.$category["name"].'</option>';
+          }
+        }
+        ?>
+      </select>
+      <label for="parent" class="mdlext-selectfield__label">Categoría padre</label>
+    </div>
+    <br>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <textarea class="mdl-textfield__input" name="emails" id="emails"><?=security::htmlsafe(categories::readableEmails($c["emails"]))?></textarea>
+      <label class="mdl-textfield__label" for="emails">Correos electrónicos de los responsables</label>
+    </div>
+    <span style="font-size: 12px;">Introduce los correos separados por comas.</span>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Confirmar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/editcompany.php b/src/dynamic/editcompany.php
new file mode 100644
index 0000000..1af315e
--- /dev/null
+++ b/src/dynamic/editcompany.php
@@ -0,0 +1,38 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$c = companies::get($_GET["id"]);
+
+if ($c === false) {
+  security::notFound();
+}
+?>
+
+<form action="doeditcompany.php" method="POST" autocomplete="off">
+  <h4 class="mdl-dialog__title">Editar empresa</h4>
+  <div class="mdl-dialog__content">
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="text" name="id" id="edit_id" value="<?=security::htmlsafe($c['id'])?>" readonly="readonly" autocomplete="off">
+      <label class="mdl-textfield__label" for="edit_nombre">ID</label>
+    </div>
+    <br>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="text" name="name" id="edit_name"  value="<?=security::htmlsafe($c['name'])?>" autocomplete="off" data-required>
+      <label class="mdl-textfield__label" for="edit_name">Nombre de la empresa</label>
+    </div>
+    <br>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="text" name="cif" id="edit_cif"  value="<?=security::htmlsafe($c['cif'])?>" autocomplete="off">
+      <label class="mdl-textfield__label" for="edit_cif">CIF (opcional)</label>
+    </div>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Confirmar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/editday.php b/src/dynamic/editday.php
new file mode 100644
index 0000000..3829b47
--- /dev/null
+++ b/src/dynamic/editday.php
@@ -0,0 +1,65 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$day = schedules::getDay($id);
+
+if ($day === false) {
+  security::notFound();
+}
+
+$empty = [];
+
+foreach (schedules::$allEvents as $date) {
+  $empty[$date] = (intervals::measure([$day["begins".$date], $day["ends".$date]]) == 0);
+}
+?>
+
+<form action="doeditdayschedule.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$day["id"]?>">
+  <h4 class="mdl-dialog__title">Modificar horario</h4>
+  <div class="mdl-dialog__content">
+    <h5>Día</h5>
+    <div class="mdlext-selectfield mdlext-js-selectfield mdlext-selectfield--floating-label">
+      <select id="edit_day" class="mdlext-selectfield__select" disabled>
+        <?php
+        foreach (calendars::$days as $id => $tday) {
+          echo '<option value="'.(int)$id.'"'.($day["day"] == $id ? " selected" : "").'>'.security::htmlsafe($tday).'</option>';
+        }
+        ?>
+      </select>
+      <label for="edit_day" class="mdlext-selectfield__label">Día de la semana</label>
+    </div>
+    <br>
+    <div class="mdlext-selectfield mdlext-js-selectfield mdlext-selectfield--floating-label">
+      <select id="edit_type" class="mdlext-selectfield__select" disabled>
+        <?php
+        foreach (calendars::$types as $id => $type) {
+          if ($id == calendars::TYPE_FESTIU) continue;
+          echo '<option value="'.(int)$id.'"'.($day["typeday"] == $id ? " selected" : "").'>'.security::htmlsafe($type).'</option>';
+        }
+        ?>
+      </select>
+      <label for="edit_type" class="mdlext-selectfield__label">Tipo de día</label>
+    </div>
+
+    <h5>Jornada laboral</h5>
+    <p>De <input type="time" name="beginswork" <?=(!$empty["work"] ? " value='".schedules::sec2time($day["beginswork"])."'" : "")?> required> a <input type="time" name="endswork" <?=(!$empty["work"] ? " value='".schedules::sec2time($day["endswork"])."'" : "")?> required></p>
+
+    <h5>Desayuno</h5>
+    <p>De <input type="time" name="beginsbreakfast" <?=(!$empty["breakfast"] ? " value='".schedules::sec2time($day["beginsbreakfast"])."'" : "")?>> a <input type="time" name="endsbreakfast" <?=(!$empty["breakfast"] ? " value='".schedules::sec2time($day["endsbreakfast"])."'" : "")?>></p>
+
+    <h5>Comida</h5>
+    <p>De <input type="time" name="beginslunch" <?=(!$empty["lunch"] ? " value='".schedules::sec2time($day["beginslunch"])."'" : "")?>> a <input type="time" name="endslunch" <?=(!$empty["lunch"] ? " value='".schedules::sec2time($day["endslunch"])."'" : "")?>></p>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--primary">Modificar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/editincident.php b/src/dynamic/editincident.php
new file mode 100644
index 0000000..808070e
--- /dev/null
+++ b/src/dynamic/editincident.php
@@ -0,0 +1,68 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::WORKER, security::METHOD_NOTFOUND);
+security::checkWorkerUIEnabled();
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$incident = incidents::get($id, true);
+if ($incident === false) security::notFound();
+
+$isAdmin = security::isAdminView();
+$status = incidents::getStatus($incident);
+
+if (($isAdmin && in_array($status, incidents::$cannotEditStates)) || (!$isAdmin && !in_array($status, incidents::$workerCanEditStates))) security::notFound();
+if (!$isAdmin) incidents::checkIncidentIsFromPerson($incident["id"]);
+?>
+
+<dynscript>
+document.getElementById("edit_allday").addEventListener("change", e => {
+  var partialtime = document.getElementById("edit_partialtime");
+  if (e.target.checked) {
+    partialtime.classList.add("notvisible");
+  } else {
+    partialtime.classList.remove("notvisible");
+  }
+});
+</dynscript>
+
+<form action="doeditincident.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$id?>">
+  <?php visual::addContinueInput(); ?>
+  <h4 class="mdl-dialog__title">Editar incidencia</h4>
+  <div class="mdl-dialog__content">
+    <div class="mdlext-selectfield mdlext-js-selectfield mdlext-selectfield--floating-label">
+      <select name="type" id="edit_type" class="mdlext-selectfield__select" data-required>
+        <option></option>
+        <?php
+        foreach (incidents::getTypesForm() as $i) {
+          echo '<option value="'.(int)$i["id"].'"'.($i["id"] == $incident["type"] ? " selected" : "").'>'.security::htmlsafe($i["name"]).'</option>';
+        }
+        ?>
+      </select>
+      <label for="edit_type" class="mdlext-selectfield__label">Tipo</label>
+    </div>
+
+    <h5>Afectación</h5>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="date" name="day" id="edit_day" autocomplete="off" value="<?=security::htmlsafe(date("Y-m-d", $incident["day"]))?>" data-required>
+      <label class="mdl-textfield__label always-focused" for="edit_day">Día</label>
+    </div>
+    <br>
+    <p>
+      <label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="edit_allday">
+        <input type="checkbox" id="edit_allday" name="allday" value="1" class="mdl-switch__input"<?=($incident["allday"] ? " checked" : "")?>>
+        <span class="mdl-switch__label">Día entero</span>
+      </label>
+    </p>
+    <div id="edit_partialtime"<?=($incident["allday"] ? ' class="notvisible"' : '')?>>De <input type="time" name="begins"<?=($incident["allday"] ? '' : ' value="'.schedules::sec2time($incident["begins"]).'"')?>> a <input type="time" name="ends"<?=($incident["allday"] ? '' : ' value="'.schedules::sec2time($incident["ends"]).'"')?>></div>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Editar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/editincidentcomment.php b/src/dynamic/editincidentcomment.php
new file mode 100644
index 0000000..8fd9dd6
--- /dev/null
+++ b/src/dynamic/editincidentcomment.php
@@ -0,0 +1,40 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::WORKER, security::METHOD_NOTFOUND);
+security::checkWorkerUIEnabled();
+
+$isAdmin = security::isAllowed(security::ADMIN);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$incident = incidents::get($id);
+if ($incident === false) security::notFound();
+
+if (!$isAdmin) incidents::checkIncidentIsFromPerson($incident["id"]);
+
+$status = incidents::getStatus($incident);
+$cantedit = (in_array($status, incidents::$cannotEditCommentsStates) || !$isAdmin);
+?>
+
+<form action="<?=(!$isAdmin ? "doeditworkerincidentcomment.php" : "doeditincidentcomment.php")?>" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$incident["id"]?>">
+  <h4 class="mdl-dialog__title">Observaciones incidencia</h4>
+  <div class="mdl-dialog__content">
+    <h5>Observaciones</h5>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <textarea class="mdl-textfield__input" name="details" id="details"<?=($cantedit ? " disabled" : "")?>><?=security::htmlsafe($incident["details"])?></textarea>
+      <label class="mdl-textfield__label" for="details">Observaciones (opcional)</label>
+    </div>
+
+    <h5>Observaciones del trabajador</h5>
+    <p><?=security::htmlsafe((!empty($incident["workerdetails"]) ? $incident["workerdetails"] : "-"))?></p>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--primary"<?=($cantedit ? " disabled" : "")?>>Modificar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/editincidenttype.php b/src/dynamic/editincidenttype.php
new file mode 100644
index 0000000..2901a4f
--- /dev/null
+++ b/src/dynamic/editincidenttype.php
@@ -0,0 +1,74 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$t = incidents::getType($_GET["id"]);
+
+if ($t === false) {
+  security::notFound();
+}
+?>
+
+<form action="doeditincidenttype.php" method="POST" autocomplete="off">
+  <h4 class="mdl-dialog__title">Edita tipo de incidencia</h4>
+  <div class="mdl-dialog__content">
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="text" name="id" id="edit_id" value="<?=security::htmlsafe($t['id'])?>" readonly="readonly" autocomplete="off">
+      <label class="mdl-textfield__label" for="edit_id">ID</label>
+    </div>
+    <br>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="text" name="name" id="e_name" value="<?=security::htmlsafe($t['name'])?>" autocomplete="off" data-required>
+      <label class="mdl-textfield__label" for="e_name">Nombre del tipo de incidencia</label>
+    </div>
+    <p>
+      <label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="e_present">
+        <input type="checkbox" id="e_present" name="present" value="1" class="mdl-switch__input" <?=($t["present"] ? " checked" : "")?>>
+        <span class="mdl-switch__label">Presente <i class="material-icons help" id="edit_present">help</i></span>
+      </label>
+      <div class="mdl-tooltip" for="edit_present">Márquese si el trabajador está físicamente presente en el espacio de trabajo durante la incidencia.</div>
+    </p>
+    <p>
+      <label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="e_paid">
+        <input type="checkbox" id="e_paid" name="paid" value="1" class="mdl-switch__input" <?=($t["paid"] ? " checked" : "")?>>
+        <span class="mdl-switch__label">Remunerada <i class="material-icons help" id="edit_paid">help</i></span>
+      </label>
+      <div class="mdl-tooltip" for="edit_paid">Márquese si el trabajador es remunerado las horas que dura la incidencia.</div>
+    </p>
+    <p>
+      <label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="e_workerfill">
+        <input type="checkbox" id="e_workerfill" name="workerfill" value="1" class="mdl-switch__input"<?=($t["workerfill"] ? " checked" : "")?>>
+        <span class="mdl-switch__label">Puede autorrellenarse <i class="material-icons help" id="edit_workerfill">help</i></span>
+      </label>
+    </p>
+    <div class="mdl-tooltip" for="edit_workerfill">Márquese si se permite que el trabajador pueda rellenar una incidencia de este tipo él mismo (con la posterior verificación por parte de un administrador).</div>
+    <p>
+      <label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="e_notifies">
+        <input type="checkbox" id="e_notifies" name="notifies" value="1" class="mdl-switch__input"<?=($t["notifies"] ? " checked" : "")?>>
+        <span class="mdl-switch__label">Notifica <i class="material-icons help" id="edit_notifies">help</i></span>
+      </label>
+      <div class="mdl-tooltip" for="edit_notifies">Márquese si la introducción de una incidencia de este tipo notifica por correo electrónico a las personas especificadas en la categoría del trabajador.</div>
+    </p>
+    <p>
+      <label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="e_autovalidates">
+        <input type="checkbox" id="e_autovalidates" name="autovalidates" value="1" class="mdl-switch__input"<?=($t["autovalidates"] ? " checked" : "")?>>
+        <span class="mdl-switch__label">Se autovalida <i class="material-icons help" id="edit_autovalidates">help</i></span>
+      </label>
+      <div class="mdl-tooltip" for="edit_autovalidates">Márquese si al introducir una incidencia de este tipo se quiere que se autovalide sin necesidad de ser validada posteriormente por el trabajador.</div>
+    </p>
+    <p>
+      <label class="mdl-switch mdl-js-switch mdl-js-ripple-effect" for="e_hidden">
+        <input type="checkbox" id="e_hidden" name="hidden" value="1" class="mdl-switch__input"<?=($t["hidden"] ? " checked" : "")?>>
+        <span class="mdl-switch__label">Oculto</span>
+      </label>
+    </p>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Confirmar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/editschedule.php b/src/dynamic/editschedule.php
new file mode 100644
index 0000000..c8d0b6a
--- /dev/null
+++ b/src/dynamic/editschedule.php
@@ -0,0 +1,34 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$s = schedules::get($_GET["id"]);
+
+if ($s === false) {
+  security::notFound();
+}
+?>
+
+<form action="doeditschedule.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$s["id"]?>">
+  <h4 class="mdl-dialog__title">Editar horario</h4>
+  <div class="mdl-dialog__content">
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="date" name="begins" id="begins" autocomplete="off"  value="<?=security::htmlsafe(date("Y-m-d", $s["begins"]))?>" data-required>
+      <label class="mdl-textfield__label always-focused" for="begins">Fecha inicio de validez del horario</label>
+    </div>
+    <br>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="date" name="ends" id="ends" autocomplete="off" value="<?=security::htmlsafe(date("Y-m-d", $s["ends"]))?>" data-required>
+      <label class="mdl-textfield__label always-focused" for="ends">Fecha fin de validez del horario</label>
+    </div>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Confirmar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/editscheduletemplate.php b/src/dynamic/editscheduletemplate.php
new file mode 100644
index 0000000..3c67d74
--- /dev/null
+++ b/src/dynamic/editscheduletemplate.php
@@ -0,0 +1,39 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$t = schedules::getTemplate($_GET["id"]);
+
+if ($t === false) {
+  security::notFound();
+}
+?>
+
+<form action="doeditscheduletemplate.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$t["id"]?>">
+  <h4 class="mdl-dialog__title">Editar plantilla</h4>
+  <div class="mdl-dialog__content">
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="text" name="name" id="name" autocomplete="off" value="<?=security::htmlsafe($t["name"])?>" data-required>
+      <label class="mdl-textfield__label" for="name">Nombre de la plantilla</label>
+    </div>
+    <br>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="date" name="begins" id="begins" autocomplete="off"  value="<?=security::htmlsafe(date("Y-m-d", $t["begins"]))?>" data-required>
+      <label class="mdl-textfield__label always-focused" for="begins">Fecha inicio de validez del horario</label>
+    </div>
+    <br>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="date" name="ends" id="ends" autocomplete="off" value="<?=security::htmlsafe(date("Y-m-d", $t["ends"]))?>" data-required>
+      <label class="mdl-textfield__label always-focused" for="ends">Fecha fin de validez del horario</label>
+    </div>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Confirmar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/edittemplateday.php b/src/dynamic/edittemplateday.php
new file mode 100644
index 0000000..136ff1a
--- /dev/null
+++ b/src/dynamic/edittemplateday.php
@@ -0,0 +1,65 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$day = schedules::getTemplateDay($id);
+
+if ($day === false) {
+  security::notFound();
+}
+
+$empty = [];
+
+foreach (schedules::$allEvents as $date) {
+  $empty[$date] = (intervals::measure([$day["begins".$date], $day["ends".$date]]) == 0);
+}
+?>
+
+<form action="doeditdayscheduletemplate.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$day["id"]?>">
+  <h4 class="mdl-dialog__title">Modificar horario</h4>
+  <div class="mdl-dialog__content">
+    <h5>Día</h5>
+    <div class="mdlext-selectfield mdlext-js-selectfield mdlext-selectfield--floating-label">
+      <select id="edit_day" class="mdlext-selectfield__select" disabled>
+        <?php
+        foreach (calendars::$days as $id => $tday) {
+          echo '<option value="'.(int)$id.'"'.($day["day"] == $id ? " selected" : "").'>'.security::htmlsafe($tday).'</option>';
+        }
+        ?>
+      </select>
+      <label for="edit_day" class="mdlext-selectfield__label">Día de la semana</label>
+    </div>
+    <br>
+    <div class="mdlext-selectfield mdlext-js-selectfield mdlext-selectfield--floating-label">
+      <select id="edit_type" class="mdlext-selectfield__select" disabled>
+        <?php
+        foreach (calendars::$types as $id => $type) {
+          if ($id == calendars::TYPE_FESTIU) continue;
+          echo '<option value="'.(int)$id.'"'.($day["typeday"] == $id ? " selected" : "").'>'.security::htmlsafe($type).'</option>';
+        }
+        ?>
+      </select>
+      <label for="edit_type" class="mdlext-selectfield__label">Tipo de día</label>
+    </div>
+
+    <h5>Jornada laboral</h5>
+    <p>De <input type="time" name="beginswork" <?=(!$empty["work"] ? " value='".schedules::sec2time($day["beginswork"])."'" : "")?> required> a <input type="time" name="endswork" <?=(!$empty["work"] ? " value='".schedules::sec2time($day["endswork"])."'" : "")?> required></p>
+
+    <h5>Desayuno</h5>
+    <p>De <input type="time" name="beginsbreakfast" <?=(!$empty["breakfast"] ? " value='".schedules::sec2time($day["beginsbreakfast"])."'" : "")?>> a <input type="time" name="endsbreakfast" <?=(!$empty["breakfast"] ? " value='".schedules::sec2time($day["endsbreakfast"])."'" : "")?>></p>
+
+    <h5>Comida</h5>
+    <p>De <input type="time" name="beginslunch" <?=(!$empty["lunch"] ? " value='".schedules::sec2time($day["beginslunch"])."'" : "")?>> a <input type="time" name="endslunch" <?=(!$empty["lunch"] ? " value='".schedules::sec2time($day["endslunch"])."'" : "")?>></p>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--primary">Modificar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/edituser.php b/src/dynamic/edituser.php
new file mode 100644
index 0000000..36fb51c
--- /dev/null
+++ b/src/dynamic/edituser.php
@@ -0,0 +1,79 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$p = people::get($_GET["id"]);
+
+if ($p === false) {
+  security::notFound();
+}
+?>
+
+<form action="doedituser.php" method="POST" autocomplete="off">
+  <h4 class="mdl-dialog__title">Edita persona</h4>
+  <div class="mdl-dialog__content">
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="text" name="id" id="edit_id" value="<?=security::htmlsafe($p['id'])?>" readonly="readonly" autocomplete="off">
+      <label class="mdl-textfield__label" for="edit_nombre">ID</label>
+    </div>
+    <br>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="text" name="username" id="edit_username" value="<?=security::htmlsafe($p['username'])?>" autocomplete="off" data-required>
+      <label class="mdl-textfield__label" for="edit_username">Nombre de usuario</label>
+    </div>
+    <br>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="text" name="name" id="edit_name"  value="<?=security::htmlsafe($p['name'])?>" autocomplete="off" data-required>
+      <label class="mdl-textfield__label" for="edit_name">Nombre</label>
+    </div>
+    <br>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="text" name="dni" id="edit_dni"  value="<?=security::htmlsafe($p['dni'])?>" autocomplete="off">
+      <label class="mdl-textfield__label" for="edit_dni">DNI (opcional)</label>
+    </div>
+    <br>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="email" name="email" id="edit_email"  value="<?=security::htmlsafe($p['email'])?>" autocomplete="off">
+      <label class="mdl-textfield__label" for="edit_email">Correo electrónico (opcional)</label>
+    </div>
+    <br>
+    <div class="mdlext-selectfield mdlext-js-selectfield mdlext-selectfield--floating-label">
+      <select name="category" id="edit_category" class="mdlext-selectfield__select">
+        <option value="-1"></option>
+        <?php
+        $categories = categories::getAll();
+        foreach ($categories as $id => $category) {
+          $selected = ($id == $p["categoryid"] ? " selected" : "");
+          echo '<option value="'.(int)$id.'"'.$selected.'>'.security::htmlsafe($category).'</option>';
+        }
+        ?>
+      </select>
+      <label for="edit_category" class="mdlext-selectfield__label">Categoría (opcional)</label>
+    </div>
+    <br>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="password" name="password" id="edit_password" autocomplete="off">
+      <label class="mdl-textfield__label" for="edit_password">Contraseña</label>
+    </div>
+    <p><?=security::htmlsafe(security::$passwordHelperText)?></p>
+    <div class="mdlext-selectfield mdlext-js-selectfield mdlext-selectfield--floating-label">
+      <select name="type" id="edit_type" class="mdlext-selectfield__select" data-required>
+        <?php
+        foreach (security::$types as $i => $type) {
+          $selected = ($i == $p["type"] ? " selected" : "");
+          echo '<option value="'.(int)$i.'"'.$selected.(security::isAllowed($i) ? "" : " disabled").'>'.security::htmlsafe($type).'</option>';
+        }
+        ?>
+      </select>
+      <label for="edit_type" class="mdlext-selectfield__label">Tipo</label>
+    </div>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Confirmar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/editworkhistoryitem.php b/src/dynamic/editworkhistoryitem.php
new file mode 100644
index 0000000..14fd04c
--- /dev/null
+++ b/src/dynamic/editworkhistoryitem.php
@@ -0,0 +1,56 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$item = workers::getWorkHistoryItem($id);
+if ($item === false) security::notFound();
+
+$isHidden = workers::isHidden($item["status"]);
+
+$worker = workers::get($item["worker"]);
+if ($worker === false) security::notFound();
+?>
+
+<dynscript>
+document.getElementById("cancel").addEventListener("click", e => {
+  e.preventDefault();
+  dynDialog.load("dynamic/workhistory.php?id="+parseInt(document.getElementById("cancel").getAttribute("data-worker-id")));
+});
+</dynscript>
+
+<form action="doeditworkhistoryitem.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$id?>">
+  <h4 class="mdl-dialog__title">Editar <?=security::htmlsafe(strtolower(workers::affiliationStatusHelper($item["status"])))?></h4>
+  <div class="mdl-dialog__content">
+    <p><b>Persona:</b> <?=security::htmlsafe($worker["name"])?><br>
+    <b>Empresa:</b> <?=security::htmlsafe($worker["companyname"])?></p>
+
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="date" name="day" id="day" autocomplete="off" data-required value="<?=security::htmlsafe(date("Y-m-d", $item["day"]))?>">
+      <label class="mdl-textfield__label" for="day">Fecha</label>
+    </div>
+    <br>
+    <div class="mdlext-selectfield mdlext-js-selectfield mdlext-selectfield--floating-label">
+      <select name="status" id="status" class="mdlext-selectfield__select" data-required>
+        <option></option>
+        <?php
+        foreach (workers::$affiliationStatusesManual as $status) {
+          $currentIsHidden = workers::isHidden($status);
+          echo '<option value="'.(int)$status.'"'.((($isHidden && $currentIsHidden) || (!$isHidden && !$currentIsHidden)) ? ' selected' : '').'>'.security::htmlsafe(workers::affiliationStatusHelper($status)).'</option>';
+        }
+        ?>
+      </select>
+      <label for="status" class="mdlext-selectfield__label">Tipo</label>
+    </div>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Editar</button>
+    <button id="cancel" class="mdl-button mdl-js-button mdl-js-ripple-effect" data-worker-id="<?=(int)$worker["id"]?>">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/enablesecondfactor.php b/src/dynamic/enablesecondfactor.php
new file mode 100644
index 0000000..633d36e
--- /dev/null
+++ b/src/dynamic/enablesecondfactor.php
@@ -0,0 +1,102 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::WORKER, security::METHOD_NOTFOUND);
+security::checkWorkerUIEnabled();
+secondFactor::checkAvailability();
+
+if (secondFactor::isEnabled()) {
+  security::notFound();
+}
+
+$secret = secondFactor::generateSecret();
+$url = "otpauth://totp/".str_replace("+", "%20", urlencode($conf["appName"])).":".urlencode(people::userData('username'))."?secret=".urlencode($secret)."&issuer=".str_replace("+", "%20", urlencode($conf["appName"]));
+?>
+
+<style>
+#dynDialog {
+  max-width: 500px;
+  width: auto;
+}
+
+.step {
+  padding: 10px 0;
+  border-bottom: 1px solid #ebebeb;
+}
+
+.step .number {
+  display: inline-block;
+  vertical-align: middle;
+  font-family: "Arial", sans-serif;
+  font-size: 36px;
+  font-weight: bold;
+  color: green;
+  margin: 0;
+  margin-right: 15px;
+  padding: 0;
+  line-height: normal;
+}
+
+.step .text {
+  display: inline-block;
+  vertical-align: middle;
+  margin: 0;
+  padding: 0;
+  width: Calc(100% - 40px);
+}
+
+.step .icon_container {
+  float: right;
+  height: 24px;
+  padding-top: 9px;
+  padding-right: 9px;
+}
+
+#qrcode {
+  margin: 8px 0;
+}
+
+#qrcode img, #qrcode canvas {
+  margin: auto;
+}
+</style>
+
+<dynscript>
+new QRCode(document.getElementById("qrcode"), {
+  text: "<?=security::htmlsafe($url)?>",
+  width: 200,
+	height: 200
+});
+</dynscript>
+
+<form action="doenablesecondfactor.php" method="POST" autocomplete="off">
+  <input type="hidden" name="secret" value="<?=security::htmlsafe($secret)?>">
+  <h4 class="mdl-dialog__title">Activa la verificación en dos pasos</h4>
+  <div class="mdl-dialog__content">
+    <p>Para activar la verificación en dos pasos, sigue los siguientes pasos:</p>
+
+    <div class="step">
+      <div class="number">1</div>
+      <div class="text"><b>Instala la aplicación Google Authenticator en tu <a href="http://appstore.com/googleauthenticator" target="_blank" rel="noopener noreferrer">iPhone</a> o <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2" target="_blank" rel="noopener noreferrer">Android</a>.</b><br>También puedes usar otra aplicación si lo prefieres.</div>
+    </div>
+    <div class="step">
+      <div class="number">2</div><div class="text"><b>Configura tu cuenta en la app Google Authenticator escaneando el siguiente código QR:</b></div>
+    </div>
+
+    <div id="qrcode"></div>
+
+    <div class="step" style="border-top: 1px solid #ebebeb;">
+        <div class="number">3</div><div class="text"><b>¿No puedes escanear el código QR? Introduce manualmente la siguiente clave secreta:</b><br><?=security::htmlsafe(secondFactorView::renderSecret($secret))?></div>
+    </div>
+    <div class="step" style="margin-bottom: 5px;">
+        <div class="number">4</div><div class="text"><b>Introduce el código de verificación de 6 dígitos:</b></div>
+    </div>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="text" name="code" id="code" autocomplete="off" pattern="[0-9]{6}" data-required>
+      <label class="mdl-textfield__label" for="code">Código de verificación</label>
+    </div>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--primary">Activar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/exportcalendar.php b/src/dynamic/exportcalendar.php
new file mode 100644
index 0000000..47b88fa
--- /dev/null
+++ b/src/dynamic/exportcalendar.php
@@ -0,0 +1,59 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$c = calendars::get($id);
+
+if ($c === false) {
+  security::notFound();
+}
+
+$details = json_decode($c["details"], true);
+$export = array(
+  "begins" => $c["begins"],
+  "ends" => $c["ends"],
+  "calendar" => $details
+);
+?>
+
+<style>
+textarea.code {
+  width: 100%;
+  height: 100px;
+}
+</style>
+
+<dynscript>
+document.querySelector("textarea.code").select();
+
+document.getElementById("copy").addEventListener("click", _ => {
+  navigator.clipboard.writeText(document.querySelector("textarea.code").value).then(_ => {
+    document.querySelector(".mdl-js-snackbar").MaterialSnackbar.showSnackbar({
+      message: "Se ha copiado el texto correctamente.",
+      timeout: 5000
+    });
+  }).catch(error => {
+    document.querySelector(".mdl-js-snackbar").MaterialSnackbar.showSnackbar({
+      message: "Ha ocurrido un error copiando el texto. Por favor, cópialo manualmente.",
+      timeout: 5000
+    });
+    console.error(error);
+  });
+});
+</dynscript>
+
+<h4 class="mdl-dialog__title">Exportar calendario</h4>
+<div class="mdl-dialog__content">
+  <p>Este es el código que contiene toda la información del calendario y que puedes usar de plantilla más tarde:</p>
+  <textarea class="code" readonly><?=security::htmlsafe(json_encode($export))?></textarea>
+</div>
+<div class="mdl-dialog__actions">
+  <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent cancel">Cerrar</button>
+  <button id="copy" class="mdl-button mdl-js-button mdl-js-ripple-effect">Copiar</button>
+</div>
diff --git a/src/dynamic/incidentattachments.php b/src/dynamic/incidentattachments.php
new file mode 100644
index 0000000..2402688
--- /dev/null
+++ b/src/dynamic/incidentattachments.php
@@ -0,0 +1,107 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::WORKER, security::METHOD_NOTFOUND);
+security::checkWorkerUIEnabled();
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$incident = incidents::get($id, true);
+if ($incident === false) security::notFound();
+
+$isAdmin = security::isAllowed(security::ADMIN);
+$status = incidents::getStatus($incident);
+
+$cantedit = in_array($status, incidents::$cannotEditCommentsStates);
+
+if (!$isAdmin) incidents::checkIncidentIsFromPerson($incident["id"]);
+?>
+
+<dynscript>
+document.querySelectorAll(".deleteattachment").forEach(el => {
+  el.addEventListener("click", e => {
+    dynDialog.load("dynamic/deleteattachment.php?id="+el.getAttribute("data-id")+"&name="+el.getAttribute("data-name")<?=(isset($_GET["continue"]) ? '+"&continue='.security::htmlsafe(urlencode($_GET["continue"])).'"' : '')?>);
+  });
+});
+</dynscript>
+
+<style>
+#dynDialog {
+  max-width: 380px;
+  width: auto;
+}
+
+.addAttachmentForm {
+  display: flex;
+  align-items: center;
+}
+
+.addAttachmentForm input[type="file"] {
+  width: 100%;
+  height: min-content;
+}
+
+.addAttachmentForm button {
+  min-width: min-content;
+}
+
+.attachmentDescription {
+  margin-top: 16px;
+}
+
+.attachmentDescription code {
+  font-size: 12px;
+}
+</style>
+
+<h4 class="mdl-dialog__title">Archivos adjuntos</h4>
+<div class="mdl-dialog__content">
+  <?php
+  $attachments = incidents::getAttachmentsFromIncident($incident);
+
+  if ($attachments === false) {
+    echo "<p>Ha ocurrido un problema cargando los archivos adjuntos.</p>";
+  } elseif (!count($attachments)) {
+    echo "<p>No hay ningún archivo adjunto</p>";
+  } else {
+    echo '<ul class="mdl-list">';
+    foreach ($attachments as $attachment) {
+      $extension = files::getFileExtension($attachment);
+      $icon = files::$mimeTypesIcons[$extension] ?? "broken_image";
+      $title = files::$readableMimeTypes[$extension] ?? "Documento desconocido";
+      echo '<li class="mdl-list__item">
+        <span class="mdl-list__item-primary-content">
+          <i class="material-icons mdl-list__item-icon">'.security::htmlsafe($icon).'</i>
+          '.security::htmlsafe($title).'
+        </span>
+        <a href="incidentattachment.php?id='.(int)$incident["id"].'&name='.security::htmlsafe($attachment).'" target="_blank" class="mdl-list__item-secondar-action mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect">
+          <i class="material-icons">open_in_new</i>
+        </a>'.
+        ($cantedit ? '' : '<button class="mdl-list__item-secondar-action mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect deleteattachment" data-id="'.(int)$id.'" data-name="'.security::htmlsafe($attachment).'">
+          <i class="material-icons">delete</i>
+        </button>').'
+      </li>';
+    }
+    echo "</ul>";
+  }
+
+  if (!$cantedit) {
+    ?>
+    <h5>Añade un archivo adjunto</h5>
+    <form action="doaddincidentattachment.php" method="POST" enctype="multipart/form-data" class="addAttachmentForm">
+      <input type="hidden" name="id" value="<?=(int)$incident["id"]?>">
+      <?php visual::addContinueInput(); ?>
+      <input type="file" name="file" accept="<?=security::htmlsafe(files::getAcceptAttribute())?>" required>
+      <button class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--raised">Subir</button>
+    </form>
+    <div class="attachmentDescription">Se aceptan archivos de hasta <?=security::htmlsafe(files::READABLE_MAX_SIZE)?> con los siguientes formatos: <code><?=security::htmlsafe(files::getAcceptAttribute(true))?></code></div>
+    <?php
+  }
+  ?>
+</div>
+<div class="mdl-dialog__actions">
+  <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Cerrar</button>
+</div>
diff --git a/src/dynamic/invalidateincident.php b/src/dynamic/invalidateincident.php
new file mode 100644
index 0000000..ceeeac3
--- /dev/null
+++ b/src/dynamic/invalidateincident.php
@@ -0,0 +1,29 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$incident = incidents::get($id);
+if ($incident === false) security::notFound();
+
+$status = incidents::getStatus($incident);
+if (!in_array($status, incidents::$canInvalidateStates)) security::notFound();
+?>
+
+<form action="doinvalidateincident.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$id?>">
+  <?php visual::addContinueInput(); ?>
+  <h4 class="mdl-dialog__title">Invalidar incidencia</h4>
+  <div class="mdl-dialog__content">
+    <p>¿Estás seguro que quieres invalidar esta incidencia? <span style="color:#EF5350;font-weight:bold;">Esta acción es irreversible</span></p>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Invalidar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/invalidaterecord.php b/src/dynamic/invalidaterecord.php
new file mode 100644
index 0000000..2dc0dfc
--- /dev/null
+++ b/src/dynamic/invalidaterecord.php
@@ -0,0 +1,29 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::WORKER, security::METHOD_NOTFOUND);
+security::checkWorkerUIEnabled();
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$record = registry::get($id);
+if ($record === false || $record["invalidated"] != 0) security::notFound();
+
+$isAdmin = security::isAllowed(security::ADMIN);
+if (!$isAdmin) registry::checkRecordIsFromPerson($record["id"]);
+?>
+
+<form action="doinvalidaterecord.php" method="POST" autocomplete="off">
+  <input type="hidden" name="id" value="<?=(int)$id?>">
+  <h4 class="mdl-dialog__title">Invalidar elemento del registro</h4>
+  <div class="mdl-dialog__content">
+    <p>¿Estás seguro que quieres eliminar este elemento del registro? <span style="color:#EF5350;font-weight:bold;">Esta acción es irreversible</span></p>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Invalidar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/log.php b/src/dynamic/log.php
new file mode 100644
index 0000000..1c10dc5
--- /dev/null
+++ b/src/dynamic/log.php
@@ -0,0 +1,59 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$log = registry::getLog($id);
+if ($log === false) security::notFound();
+?>
+
+<style>
+#dynDialog {
+  max-width: 500px;
+  width: auto;
+}
+
+.log {
+  white-space: pre-wrap;
+}
+</style>
+
+<h4 class="mdl-dialog__title">
+  Log
+  <?php
+  if ($log["warningpos"] > 0) {
+    visual::addTooltip("warning", "El log contiene mensajes de advertencia");
+    ?>
+    <i class="material-icons mdl-color-text--orange help" id="warning">warning</i>
+    <?php
+  }
+
+  if ($log["errorpos"] > 0) {
+    visual::addTooltip("error", "El log contiene mensajes de error");
+    ?>
+    <i class="material-icons mdl-color-text--red help" id="error">error</i>
+    <?php
+  }
+
+  if ($log["fatalerrorpos"] > 0) {
+    visual::addTooltip("fatalerror", "El log contiene errores fatales");
+    ?>
+    <i class="material-icons mdl-color-text--red help-900" id="fatalerror">error</i>
+    <?php
+  }
+  ?>
+</h4>
+<div class="mdl-dialog__content">
+  <pre class="log"><?=registry::beautifyLog(security::htmlsafe($log["logdetails"]))?></pre>
+</div>
+<div class="mdl-dialog__actions">
+  <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--primary">Cerrar</button>
+</div>
+<?php
+visual::renderTooltips();
+?>
diff --git a/src/dynamic/sethelpresource.php b/src/dynamic/sethelpresource.php
new file mode 100644
index 0000000..2171dde
--- /dev/null
+++ b/src/dynamic/sethelpresource.php
@@ -0,0 +1,31 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::HYPERADMIN, security::METHOD_NOTFOUND);
+
+if (!security::checkParams("GET", [
+  ["place", security::PARAM_ISINT]
+])) {
+  security::notFound();
+}
+
+$place = $_GET["place"];
+if (!in_array($place, help::$places)) security::notFound();
+
+$url = help::get($place);
+?>
+
+<form action="dosethelpresource.php" method="POST" autocomplete="off">
+  <input type="hidden" name="place" value="<?=(int)$place?>">
+  <h4 class="mdl-dialog__title">Enlace de ayuda</h4>
+  <div class="mdl-dialog__content">
+    <p><b>Lugar:</b> <?=security::htmlsafe(help::$placesName[$place] ?? "undefined")?></b></p>
+    <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
+      <input class="mdl-textfield__input" type="url" name="url" id="url" autocomplete="off"<?=($url !== false ? ' value="'.security::htmlsafe($url).'"' : '')?>>
+      <label class="mdl-textfield__label" for="url">URL</label>
+    </div>
+  </div>
+  <div class="mdl-dialog__actions">
+    <button type="submit" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--primary">Configurar</button>
+    <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cancelar</button>
+  </div>
+</form>
diff --git a/src/dynamic/user.php b/src/dynamic/user.php
new file mode 100644
index 0000000..d0aec1d
--- /dev/null
+++ b/src/dynamic/user.php
@@ -0,0 +1,59 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$p = people::get($_GET["id"], false);
+
+if ($p === false) {
+  security::notFound();
+}
+
+$companies = companies::getAll();
+$pcompanies = [];
+
+foreach($p["companies"] as $company) {
+  $pcompanies[] = $companies[$company];
+}
+
+$secondFactor = secondFactor::isEnabled($p["id"]);
+
+if ($secondFactor) {
+?>
+<dynscript>
+document.querySelector(".disable-second-factor").addEventListener("click", e => {
+  dynDialog.load("dynamic/disablesecondfactor.php?id=<?=(int)$p["id"]?>");
+});
+</dynscript>
+<?php
+}
+?>
+
+<style>
+#dynDialog {
+  max-width: 380px;
+  width: auto;
+}
+</style>
+
+<h4 class="mdl-dialog__title"><?=security::htmlsafe($p["name"])?></h4>
+<ul>
+  <li><b>Nombre de usuario:</b> <?=security::htmlsafe($p["username"])?></li>
+  <li><b>DNI:</b> <?=(!empty($p["dni"]) ? security::htmlsafe($p["dni"]) : "-")?></li>
+  <li><b>Correo electrónico:</b> <?=(!empty($p["email"]) ? "<a href=\"mailto:".security::htmlsafe(rawurlencode($p["email"]))."\" target=\"_blank\">".security::htmlsafe($p["email"])."</a>" : "-")?>
+  <li><b>Categoría:</b> <?=($p["categoryid"] == -1 ? "-" : security::htmlsafe($p["category"]))?></li>
+  <li><b>Dada de baja:</b> <?=($p["baixa"] == 1 ? visual::YES : "No")?></li>
+  <li><b>Empresas:</b> <?=security::htmlsafe((count($p["companies"]) ? implode(", ", $pcompanies) : "-"))?></li>
+  <li><b>Tipo de usuario:</b> <?=security::htmlsafe(security::$types[$p["type"]])?></li>
+  <?php if (secondFactor::isAvailable()) { ?><li><b>Verificación en dos pasos:</b> <?=($secondFactor ? 'activada <button class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--accent disable-second-factor">Desactivar</button>' : 'desactivada')?></li><?php } ?>
+</ul>
+
+<div class="mdl-dialog__actions">
+  <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect cancel">Cerrar</button>
+  <a href="userregistry.php?id=<?=(int)$p["id"]?>" class="mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect"><i class="material-icons">list</i><span class="mdl-ripple"></span></a>
+  <a href="userincidents.php?id=<?=(int)$p["id"]?>" class="mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect"><i class="material-icons">assignment_late</i><span class="mdl-ripple"></span></a>
+  <a href="workerschedule.php?id=<?=(int)$p["id"]?>" class="mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect"><i class="material-icons">timelapse</i><span class="mdl-ripple"></span></a>
+</div>
diff --git a/src/dynamic/workhistory.php b/src/dynamic/workhistory.php
new file mode 100644
index 0000000..4c74000
--- /dev/null
+++ b/src/dynamic/workhistory.php
@@ -0,0 +1,91 @@
+<?php
+require_once(__DIR__."/../core.php");
+security::checkType(security::ADMIN, security::METHOD_NOTFOUND);
+
+if (!isset($_GET["id"])) {
+  security::notFound();
+}
+
+$id = (int)$_GET["id"];
+
+$worker = workers::get($id);
+if ($worker === false) security::notFound();
+?>
+
+<dynscript>
+document.getElementById("additem").addEventListener("click", e => {
+  dynDialog.load("dynamic/addworkhistoryitem.php?id="+parseInt(document.getElementById("additem").getAttribute("worker-id")));
+});
+
+document.querySelectorAll(".edititem").forEach(el => {
+  el.addEventListener("click", e => {
+    dynDialog.load("dynamic/editworkhistoryitem.php?id="+parseInt(el.getAttribute("data-id")));
+  });
+});
+
+document.querySelectorAll(".deleteitem").forEach(el => {
+  el.addEventListener("click", e => {
+    dynDialog.load("dynamic/deleteworkhistoryitem.php?id="+parseInt(el.getAttribute("data-id")));
+  });
+});
+</dynscript>
+
+<style>
+#dynDialog {
+  max-width: 380px;
+  width: auto;
+}
+
+#dynDialog .mdl-list {
+  margin-top: 0;
+  padding-top: 0;
+}
+
+.float-right {
+  float: right;
+}
+</style>
+
+<h4 class="mdl-dialog__title">Historial de altas y bajas</h4>
+<div class="mdl-dialog__content">
+  <p><b>Persona:</b> <?=security::htmlsafe($worker["name"])?><br>
+  <b>Empresa:</b> <?=security::htmlsafe($worker["companyname"])?></p>
+
+  <div class="float-right"><button id="additem" class="mdl-button mdl-js-button mdl-js-ripple-effect" worker-id="<?=(int)$worker["id"]?>"><i class="material-icons">add</i> Añadir alta/baja</button></div>
+  <div style="clear: both;"></div>
+
+  <?php
+  $items = workers::getWorkHistory($id);
+
+  if ($items === false) {
+    echo "<p>Ha ocurrido un problema cargando los elementos del historial.</p>";
+  } elseif (!count($items)) {
+    echo "<p>No hay ningún elmento en el historial, así que el aplicativo está considerando que el trabajador está de baja.</p>";
+  } else {
+    echo '<ul class="mdl-list">';
+    foreach ($items as $item) {
+      $icon = security::htmlsafe(workers::affiliationStatusIcon($item["status"]) ?? "indeterminate_check_box");
+      $helper = workers::affiliationStatusHelper($item["status"]);
+      $day = date("d/m/Y", $item["day"]);
+      $isAutomatic = workers::isAutomaticAffiliation($item["status"]);
+      echo '<li class="mdl-list__item '.($isAutomatic ? 'mdl-list__item--two-line' : '').'">
+        <span class="mdl-list__item-primary-content">
+          <i class="material-icons mdl-list__item-icon">'.security::htmlsafe($icon).'</i>
+          <span>'.security::htmlsafe($helper).' ('.security::htmlsafe($day).')</span>
+          '.($isAutomatic ? '<span class="mdl-list__item-sub-title">Registro automático</span>' : '').'
+        </span>
+        <button class="mdl-list__item-secondar-action mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect edititem" data-id="'.(int)$item["id"].'">
+          <i class="material-icons">edit</i>
+        </button>
+        <button class="mdl-list__item-secondar-action mdl-button mdl-js-button mdl-button--icon mdl-js-ripple-effect deleteitem" data-id="'.(int)$item["id"].'">
+          <i class="material-icons">delete</i>
+        </button>
+      </li>';
+    }
+    echo "</ul>";
+  }
+  ?>
+</div>
+<div class="mdl-dialog__actions">
+  <button data-dyndialog-close class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--accent">Cerrar</button>
+</div>