feat(herramientas): agregar soporte de fotografias y evidencias para herramientas, kits y asignaciones de custodia

This commit is contained in:
2026-11-01 03:11:33 -05:00
parent c07162c375
commit b361e4a9b4
11 changed files with 379 additions and 42 deletions
+103 -13
View File
@@ -146,6 +146,13 @@
<div class="col-md-6 col-xl-4">
<div class="app-card h-100 p-3.5 d-flex flex-column justify-content-between border shadow-sm rounded-3">
<div>
<% if (s.foto_url) { %>
<div class="mb-3 rounded overflow-hidden border shadow-sm position-relative" style="height: 140px; background-color: #f8fafc;">
<a href="<%= s.foto_url %>" target="_blank" title="Ver foto ampliada">
<img src="<%= s.foto_url %>" alt="<%= s.nombre %>" style="width: 100%; height: 100%; object-fit: cover;">
</a>
</div>
<% } %>
<div class="d-flex align-items-center justify-content-between mb-2">
<span class="badge bg-dark text-white font-monospace" style="font-size: 0.7rem;">
<%= s.codigo || 'KIT-' + s.id %>
@@ -171,8 +178,12 @@
<ul class="list-unstyled mb-0 small" style="font-size: 0.78rem;">
<% s.herramientas.forEach(h => { %>
<li class="d-flex align-items-center justify-content-between py-1 border-bottom border-light">
<div class="text-truncate me-2">
<i class="fas fa-check text-success me-1.5" style="font-size: 0.7rem;"></i>
<div class="text-truncate me-2 d-flex align-items-center gap-1.5">
<% if (h.foto_url) { %>
<img src="<%= h.foto_url %>" alt="Foto" class="rounded flex-shrink-0" style="width: 22px; height: 22px; object-fit: cover;">
<% } else { %>
<i class="fas fa-check text-success" style="font-size: 0.7rem;"></i>
<% } %>
<strong><%= h.nombre %></strong>
<% if (h.marca) { %><span class="text-muted">(<%= h.marca %>)</span><% } %>
</div>
@@ -211,7 +222,7 @@
<div class="modal fade" id="modalEditarSet<%= s.id %>" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content rounded-3 border-0 shadow">
<form action="/admin/herramientas/sets/edit/<%= s.id %>" method="POST">
<form action="/admin/herramientas/sets/edit/<%= s.id %>" method="POST" enctype="multipart/form-data">
<div class="modal-header">
<h5 class="modal-title fw-bold">Editar Kit: <%= s.nombre %></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
@@ -247,6 +258,16 @@
<label class="form-label small fw-bold">Descripción / Alcance</label>
<textarea name="descripcion" class="form-control" rows="2"><%= s.descripcion || '' %></textarea>
</div>
<div class="col-12">
<label class="form-label small fw-bold"><i class="fas fa-camera text-primary me-1"></i> Fotografía del Set / Maletín</label>
<% if (s.foto_url) { %>
<div class="d-flex align-items-center gap-2 mb-2 p-2 border rounded bg-light">
<img src="<%= s.foto_url %>" alt="Foto" class="rounded border" style="width: 50px; height: 50px; object-fit: cover;">
<span class="small text-muted">Fotografía actual del kit registrada. Selecciona una nueva imagen para reemplazarla.</span>
</div>
<% } %>
<input type="file" name="foto" accept="image/*" class="form-control">
</div>
<div class="col-12">
<label class="form-label small fw-bold mb-1">Seleccionar Herramientas del Inventario</label>
<div class="border rounded p-2.5" style="max-height: 200px; overflow-y: auto;">
@@ -277,7 +298,7 @@
<div class="modal fade" id="modalAsignarDirecto<%= s.id %>" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content rounded-3 border-0 shadow">
<form action="/admin/herramientas/asignar" method="POST">
<form action="/admin/herramientas/asignar" method="POST" enctype="multipart/form-data">
<input type="hidden" name="set_id" value="<%= s.id %>">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title fw-bold">Asignar Kit: <%= s.nombre %></h5>
@@ -304,6 +325,11 @@
<label class="form-label small fw-bold">Observaciones de Entrega</label>
<textarea name="observaciones_entrega" class="form-control" rows="2" placeholder="Ej: Entregado completo con estuche y cargadores en buen estado"></textarea>
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><i class="fas fa-camera text-primary me-1"></i> Foto de Evidencia / Entrega (Opcional)</label>
<input type="file" name="foto_entrega" accept="image/*" class="form-control">
<small class="text-muted" style="font-size: 0.72rem;">Respaldo fotográfico del estado del kit entregado en custodia.</small>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light border" data-bs-dismiss="modal">Cancelar</button>
@@ -347,8 +373,21 @@
<tr>
<td class="ps-3 font-monospace fw-bold text-dark"><%= h.codigo || 'HERR-' + h.id %></td>
<td>
<strong class="text-dark"><%= h.nombre %></strong>
<% if (h.notas) { %><div class="small text-muted text-truncate" style="max-width: 250px;"><%= h.notas %></div><% } %>
<div class="d-flex align-items-center gap-2.5">
<% if (h.foto_url) { %>
<a href="<%= h.foto_url %>" target="_blank" class="flex-shrink-0" title="Ver foto ampliada">
<img src="<%= h.foto_url %>" alt="<%= h.nombre %>" class="rounded border shadow-sm" style="width: 42px; height: 42px; object-fit: cover;">
</a>
<% } else { %>
<div class="icon-circle bg-light text-muted border flex-shrink-0" style="width: 42px; height: 42px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 0.95rem;">
<i class="fas fa-tools"></i>
</div>
<% } %>
<div>
<strong class="text-dark"><%= h.nombre %></strong>
<% if (h.notas) { %><div class="small text-muted text-truncate" style="max-width: 250px;"><%= h.notas %></div><% } %>
</div>
</div>
</td>
<td>
<span class="badge bg-light text-dark border"><%= h.categoria %></span>
@@ -387,7 +426,7 @@
<div class="modal fade" id="modalEditarHerramienta<%= h.id %>" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content rounded-3 border-0 shadow">
<form action="/admin/herramientas/edit/<%= h.id %>" method="POST">
<form action="/admin/herramientas/edit/<%= h.id %>" method="POST" enctype="multipart/form-data">
<div class="modal-header">
<h5 class="modal-title fw-bold">Editar Herramienta: <%= h.nombre %></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
@@ -443,6 +482,16 @@
<label class="form-label small fw-bold">Notas Adicionales</label>
<textarea name="notas" class="form-control" rows="2"><%= h.notas || '' %></textarea>
</div>
<div class="col-12">
<label class="form-label small fw-bold"><i class="fas fa-camera text-primary me-1"></i> Fotografía del Instrumento / Placa</label>
<% if (h.foto_url) { %>
<div class="d-flex align-items-center gap-2 mb-2 p-2 border rounded bg-light">
<img src="<%= h.foto_url %>" alt="Foto" class="rounded border" style="width: 50px; height: 50px; object-fit: cover;">
<span class="small text-muted">Fotografía actual registrada. Selecciona una nueva imagen si deseas actualizarla.</span>
</div>
<% } %>
<input type="file" name="foto" accept="image/*" class="form-control">
</div>
</div>
</div>
<div class="modal-footer">
@@ -474,6 +523,7 @@
<th>Modalidad</th>
<th>Fecha Asignación</th>
<th>Contexto / Visita</th>
<th>Evidencia Fotográfica</th>
<th>Estado</th>
<th class="text-end pe-3">Acciones</th>
</tr>
@@ -481,7 +531,7 @@
<tbody>
<% if (asignaciones.length === 0) { %>
<tr>
<td colspan="7" class="text-center py-4 text-muted">No hay asignaciones registradas actualmente.</td>
<td colspan="8" class="text-center py-4 text-muted">No hay asignaciones registradas actualmente.</td>
</tr>
<% } %>
@@ -523,6 +573,23 @@
<span class="text-muted small"><%= a.observaciones_entrega || 'Sin observaciones' %></span>
<% } %>
</td>
<td>
<div class="d-flex flex-column gap-1">
<% if (a.foto_entrega_url) { %>
<a href="<%= a.foto_entrega_url %>" target="_blank" class="badge bg-primary-subtle text-primary border border-primary-subtle text-decoration-none d-inline-flex align-items-center gap-1" style="font-size: 0.7rem;">
<i class="fas fa-camera"></i> <span>Foto Entrega</span>
</a>
<% } %>
<% if (a.foto_devolucion_url) { %>
<a href="<%= a.foto_devolucion_url %>" target="_blank" class="badge bg-success-subtle text-success border border-success-subtle text-decoration-none d-inline-flex align-items-center gap-1" style="font-size: 0.7rem;">
<i class="fas fa-camera"></i> <span>Foto Devolución</span>
</a>
<% } %>
<% if (!a.foto_entrega_url && !a.foto_devolucion_url) { %>
<span class="text-muted small" style="font-size: 0.72rem;">Sin fotos</span>
<% } %>
</div>
</td>
<td>
<% if (a.estado === 'asignado') { %>
<span class="badge bg-success"><i class="fas fa-user-check me-1"></i> En Custodia</span>
@@ -551,7 +618,7 @@
<div class="modal fade" id="modalDevolver<%= a.id %>" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content rounded-3 border-0 shadow">
<form action="/admin/herramientas/devolver/<%= a.id %>" method="POST">
<form action="/admin/herramientas/devolver/<%= a.id %>" method="POST" enctype="multipart/form-data">
<div class="modal-header bg-success text-white">
<h5 class="modal-title fw-bold">Registrar Devolución a Bodega</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
@@ -571,6 +638,11 @@
<label class="form-label small fw-bold">Observaciones de Devolución</label>
<textarea name="observaciones_devolucion" class="form-control" rows="2" placeholder="Ej: Kit revisado e ingresado completo al estante A1 sin novedades"></textarea>
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><i class="fas fa-camera text-success me-1"></i> Foto de Evidencia de Retorno (Opcional)</label>
<input type="file" name="foto_devolucion" accept="image/*" class="form-control">
<small class="text-muted" style="font-size: 0.72rem;">Constancia fotográfica del estado físico al reingreso en bodega.</small>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light border" data-bs-dismiss="modal">Cancelar</button>
@@ -596,7 +668,7 @@
<div class="modal fade" id="modalNuevaHerramienta" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content rounded-3 border-0 shadow">
<form action="/admin/herramientas/create" method="POST">
<form action="/admin/herramientas/create" method="POST" enctype="multipart/form-data">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title fw-bold"><i class="fas fa-plus-circle me-1"></i> Registrar Nueva Herramienta / Instrumento</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
@@ -651,6 +723,11 @@
<label class="form-label small fw-bold">Observaciones / Especificaciones</label>
<textarea name="notas" class="form-control" rows="2" placeholder="Detalles de calibración, accesorios incluidos o precauciones"></textarea>
</div>
<div class="col-12">
<label class="form-label small fw-bold"><i class="fas fa-camera text-primary me-1"></i> Fotografía del Instrumento / Placa (Opcional)</label>
<input type="file" name="foto" accept="image/*" class="form-control">
<small class="text-muted" style="font-size: 0.72rem;">Sube una foto del equipo o su número de serie para reconocimiento visual en bodega y campo (JPG, PNG o WEBP).</small>
</div>
</div>
</div>
<div class="modal-footer">
@@ -666,7 +743,7 @@
<div class="modal fade" id="modalNuevoSet" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content rounded-3 border-0 shadow">
<form action="/admin/herramientas/sets/create" method="POST">
<form action="/admin/herramientas/sets/create" method="POST" enctype="multipart/form-data">
<div class="modal-header bg-warning text-dark">
<h5 class="modal-title fw-bold"><i class="fas fa-layer-group me-1"></i> Crear Set / Kit de Herramientas</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
@@ -695,6 +772,11 @@
<label class="form-label small fw-bold">Descripción del Kit</label>
<input type="text" name="descripcion" class="form-control" placeholder="Ej: Kit completo para instalación y certificación en campo">
</div>
<div class="col-12">
<label class="form-label small fw-bold"><i class="fas fa-camera text-warning me-1"></i> Fotografía del Set / Maletín (Opcional)</label>
<input type="file" name="foto" accept="image/*" class="form-control">
<small class="text-muted" style="font-size: 0.72rem;">Foto general del kit o maletín con sus componentes organizados.</small>
</div>
<div class="col-12">
<label class="form-label small fw-bold mb-1">Seleccionar Herramientas a Integrar en este Set</label>
<div class="border rounded p-2.5 bg-light" style="max-height: 220px; overflow-y: auto;">
@@ -705,7 +787,10 @@
<div class="form-check py-1 border-bottom border-light">
<input class="form-check-input" type="checkbox" name="herramientas_ids" value="<%= h.id %>" id="checkNewSet_<%= h.id %>">
<label class="form-check-label small d-flex align-items-center justify-content-between" for="checkNewSet_<%= h.id %>">
<span>
<span class="d-flex align-items-center gap-1.5">
<% if (h.foto_url) { %>
<img src="<%= h.foto_url %>" alt="Foto" class="rounded" style="width: 22px; height: 22px; object-fit: cover;">
<% } %>
<strong><%= h.nombre %></strong>
<% if (h.marca) { %><span class="text-muted">(<%= h.marca %>)</span><% } %>
</span>
@@ -733,7 +818,7 @@
<div class="modal fade" id="modalAsignar" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content rounded-3 border-0 shadow">
<form action="/admin/herramientas/asignar" method="POST">
<form action="/admin/herramientas/asignar" method="POST" enctype="multipart/form-data">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title fw-bold"><i class="fas fa-user-tag me-1"></i> Asignar Set de Herramientas a Técnico</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
@@ -768,6 +853,11 @@
<label class="form-label small fw-bold">Observaciones de Entrega</label>
<textarea name="observaciones_entrega" class="form-control" rows="2" placeholder="Ej: Entregado maletín completo con precintos y cargadores"></textarea>
</div>
<div class="mb-3">
<label class="form-label small fw-bold"><i class="fas fa-camera text-primary me-1"></i> Foto de Evidencia / Entrega (Opcional)</label>
<input type="file" name="foto_entrega" accept="image/*" class="form-control">
<small class="text-muted" style="font-size: 0.72rem;">Respaldo fotográfico del estado del kit entregado al técnico.</small>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light border" data-bs-dismiss="modal">Cancelar</button>
+31
View File
@@ -67,6 +67,13 @@
<div class="col-md-6 col-xl-4">
<div class="app-card h-100 p-3.5 border shadow-sm rounded-3 d-flex flex-column justify-content-between">
<div>
<% if (a.set_foto_url || a.herramienta_foto_url) { %>
<div class="mb-3 rounded overflow-hidden border shadow-sm" style="height: 130px; background-color: #f8fafc;">
<a href="<%= a.set_foto_url || a.herramienta_foto_url %>" target="_blank" title="Ver foto">
<img src="<%= a.set_foto_url || a.herramienta_foto_url %>" alt="Foto Equipamiento" style="width: 100%; height: 100%; object-fit: cover;">
</a>
</div>
<% } %>
<div class="d-flex align-items-center justify-content-between mb-2">
<span class="badge <%= a.tipo_asignacion === 'dotacion_fija' ? 'bg-info-subtle text-info border border-info-subtle' : 'bg-warning-subtle text-dark border border-warning-subtle' %>" style="font-size: 0.7rem;">
<i class="fas <%= a.tipo_asignacion === 'dotacion_fija' ? 'fa-id-badge' : 'fa-truck-pickup' %> me-1"></i>
@@ -99,6 +106,15 @@
<% } %>
</div>
<% if (a.foto_entrega_url) { %>
<div class="mb-2">
<a href="<%= a.foto_entrega_url %>" target="_blank" class="badge bg-primary-subtle text-primary border border-primary-subtle text-decoration-none d-inline-flex align-items-center gap-1.5 py-1 px-2" style="font-size: 0.72rem;">
<i class="fas fa-camera"></i>
<span>Ver foto de entrega en custodia</span>
</a>
</div>
<% } %>
<% if (a.observaciones_entrega) { %>
<div class="alert alert-light border p-2 small mb-0 text-muted" style="font-size: 0.75rem;">
<i class="fas fa-info-circle me-1 text-info"></i> <%= a.observaciones_entrega %>
@@ -136,6 +152,7 @@
<th>Fecha Entrega</th>
<th>Fecha Devolución</th>
<th>Observaciones</th>
<th>Evidencias</th>
<th class="pe-3">Estado</th>
</tr>
</thead>
@@ -153,6 +170,20 @@
<td><%= new Date(h.fecha_asignacion).toLocaleDateString('es-EC') %></td>
<td><%= h.fecha_devolucion ? new Date(h.fecha_devolucion).toLocaleDateString('es-EC') : '-' %></td>
<td class="text-muted"><%= h.observaciones_devolucion || h.observaciones_entrega || '-' %></td>
<td>
<div class="d-flex flex-column gap-1">
<% if (h.foto_entrega_url) { %>
<a href="<%= h.foto_entrega_url %>" target="_blank" class="badge bg-primary-subtle text-primary border border-primary-subtle text-decoration-none" style="font-size: 0.68rem;">
<i class="fas fa-camera"></i> Entrega
</a>
<% } %>
<% if (h.foto_devolucion_url) { %>
<a href="<%= h.foto_devolucion_url %>" target="_blank" class="badge bg-success-subtle text-success border border-success-subtle text-decoration-none" style="font-size: 0.68rem;">
<i class="fas fa-camera"></i> Devolución
</a>
<% } %>
</div>
</td>
<td class="pe-3">
<span class="badge <%= h.estado === 'devuelto' ? 'bg-secondary' : 'bg-warning text-dark' %>">
<%= h.estado %>