diff --git a/web-v2/package.json b/web-v2/package.json index 431bb57..fda5257 100644 --- a/web-v2/package.json +++ b/web-v2/package.json @@ -1,6 +1,6 @@ { "name": "dhv2-web", - "version": "0.20.0-3", + "version": "0.20.0-4", "private": true, "type": "module", "engines": { diff --git a/web-v2/src/config/version.ts b/web-v2/src/config/version.ts index 18098f7..d7bab61 100644 --- a/web-v2/src/config/version.ts +++ b/web-v2/src/config/version.ts @@ -1,2 +1,2 @@ -export const APP_VERSION = '0.20.0-3'; -export const APP_PHASE = 'Fase F1.3 · Planificación inteligente de pendientes'; +export const APP_VERSION = '0.20.0-4'; +export const APP_PHASE = 'Fase F1.4 · Briefing contextual antes de salir'; diff --git a/web-v2/src/features/inspections/FieldBriefingPanel.tsx b/web-v2/src/features/inspections/FieldBriefingPanel.tsx new file mode 100644 index 0000000..5839bbb --- /dev/null +++ b/web-v2/src/features/inspections/FieldBriefingPanel.tsx @@ -0,0 +1,97 @@ +import { useEffect, useState } from 'react'; +import { Link } from 'react-router'; +import { Alert, EmptyState, LoadingBlock, errorMessage } from '../../components/Feedback'; +import { Icon } from '../../components/Icon'; +import type { InspectionVisit } from '../../lib/api'; +import { formatDate, formatDateOnly } from '../../lib/format'; +import { + getInspectionFieldBriefing, + type FieldBriefing, + type FieldBriefingActState, +} from './fieldBriefingApi'; + +function stateLabel(state: FieldBriefingActState): string { + if (state === 'ACT_RESPONSE_OVERDUE') return 'Respuesta del Acta vencida'; + if (state === 'ACT_RESPONSE_DUE_SOON') return 'Respuesta próxima a vencer'; + if (state === 'COMPANY_COMMITMENT_OVERDUE') return 'Compromiso de empresa vencido'; + if (state === 'VERIFICATION_PENDING') return 'Verificación pendiente'; + if (state === 'RESPONSE_RECEIVED') return 'Respuesta recibida'; + return 'Esperando respuesta'; +} + +function stateClass(state: FieldBriefingActState): string { + if (state === 'ACT_RESPONSE_OVERDUE' || state === 'COMPANY_COMMITMENT_OVERDUE') return 'danger'; + if (state === 'ACT_RESPONSE_DUE_SOON' || state === 'VERIFICATION_PENDING') return 'warning'; + return 'pending'; +} + +export function FieldBriefingPanel({ visit }: { visit: InspectionVisit }) { + const [briefing, setBriefing] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + + useEffect(() => { + let active = true; + setLoading(true); + setError(''); + getInspectionFieldBriefing(visit.id) + .then((value) => { if (active) setBriefing(value); }) + .catch((requestError) => { if (active) setError(errorMessage(requestError)); }) + .finally(() => { if (active) setLoading(false); }); + return () => { active = false; }; + }, [visit.id, visit.updatedAt]); + + return
+
+
+ ANTES DE CONFIRMAR LA SALIDA +

Pendientes para revisar en campo

+

Actas anteriores del mismo Área y Operadora que requieren atención durante esta inspección. El plazo administrativo pertenece al Acta y los hallazgos se mantienen dentro de su expediente de origen.

+
+
+ Vista imprimible +
+
+ + {error && {error}} + {loading ? : briefing && <> +
+
{briefing.summary.acts}actas con pendientes
+
{briefing.summary.findings}hallazgos a revisar
+
{briefing.summary.inventoryItems}registros involucrados
+
{briefing.summary.responseOverdue}respuestas vencidas
+
{briefing.summary.commitmentOverdue}compromisos vencidos
+
{briefing.summary.verificationPending}verificaciones pendientes
+
+ +
+ +

Contexto de salida: {briefing.inspection.area?.name ?? 'Área sin definir'} · {briefing.inspection.operatorCompany?.name ?? 'Operadora sin definir'} · {formatDateOnly(briefing.plannedOn)}.

+
+ + {briefing.acts.length === 0 + ? + :
{briefing.acts.map((act) =>
+
+
{act.actCode}{formatDate(act.occurredAt)} · {act.findings.length} hallazgo{act.findings.length === 1 ? '' : 's'}
+ {stateLabel(act.adminState)} +
+
+ + {act.findings.map((finding) => + + + + + + )} +
HallazgoInventarioGravedadControl
{finding.title}{finding.code} · {finding.status}{finding.asset.name}{finding.asset.code} · {finding.asset.typeName}{finding.severity ?? '—'}{finding.nextControlOn ? formatDateOnly(finding.nextControlOn) : 'A definir'}Ver Acta
+
+ Plazo empresa: {act.responseDueOn ? formatDateOnly(act.responseDueOn) : 'sin definir'} + Respuesta: {act.responseReceivedOn ? formatDateOnly(act.responseReceivedOn) : 'pendiente'} + Compromiso: {act.committedCorrectionOn ? formatDateOnly(act.committedCorrectionOn) : '—'} +
+
)}
} + } +
; +} diff --git a/web-v2/src/features/inspections/InspectionActsPanel.tsx b/web-v2/src/features/inspections/InspectionActsPanel.tsx index c8b44a5..a8f93d9 100644 --- a/web-v2/src/features/inspections/InspectionActsPanel.tsx +++ b/web-v2/src/features/inspections/InspectionActsPanel.tsx @@ -6,6 +6,7 @@ import { Icon } from '../../components/Icon'; import { listInspectionActs } from '../../lib/api'; import type { InspectionActListItem, InspectionVisit } from '../../lib/api'; import { formatDate } from '../../lib/format'; +import { FieldBriefingPanel } from './FieldBriefingPanel'; import { inspectionActStatusClass, inspectionActStatusLabel } from './inspectionActPresentation'; export function InspectionActsPanel({ visit }: { visit: InspectionVisit }) { @@ -25,17 +26,18 @@ export function InspectionActsPanel({ visit }: { visit: InspectionVisit }) { .finally(() => setLoading(false)); }, [canRead, visit.id]); - if (!canRead) return null; - - return
-
-
ACTAS DE LA INSPECCIÓN

Actas generadas

Una inspección puede generar múltiples actas. Cada acta agrupa sus propios hallazgos y tendrá un único informe.

- Sólo lectura -
-

Las actas se crean en la APK. El dashboard permite consultar cada acta, sus hallazgos, firmas e informe después de la sincronización.

- {error && {error}} - {loading ? : acts.length === 0 - ? - :
Actas de la inspecciónNumeración oficial global por año
{acts.map((act) => )}
ActaEstadoFechaRegistrosHallazgosVersión
{act.title}{act.code}
{inspectionActStatusLabel(act.status)}{formatDate(act.occurredAt)}{act.assetCount}{act.findingCount}v{act.currentVersion}
} -
; + return <> + + {canRead &&
+
+
ACTAS DE LA INSPECCIÓN

Actas generadas

Una inspección puede generar múltiples actas. Cada acta agrupa sus propios hallazgos y tendrá un único informe.

+ Sólo lectura +
+

Las actas se crean en la APK. El dashboard permite consultar cada acta, sus hallazgos, firmas e informe después de la sincronización.

+ {error && {error}} + {loading ? : acts.length === 0 + ? + :
Actas de la inspecciónNumeración oficial global por año
{acts.map((act) => )}
ActaEstadoFechaRegistrosHallazgosVersión
{act.title}{act.code}
{inspectionActStatusLabel(act.status)}{formatDate(act.occurredAt)}{act.assetCount}{act.findingCount}v{act.currentVersion}
} +
} + ; } diff --git a/web-v2/src/features/inspections/fieldBriefingApi.ts b/web-v2/src/features/inspections/fieldBriefingApi.ts new file mode 100644 index 0000000..a147b2c --- /dev/null +++ b/web-v2/src/features/inspections/fieldBriefingApi.ts @@ -0,0 +1,56 @@ +import { apiRequest } from '../../lib/api'; + +export type FieldBriefingActState = + | 'ACT_RESPONSE_OVERDUE' + | 'ACT_RESPONSE_DUE_SOON' + | 'WAITING_RESPONSE' + | 'COMPANY_COMMITMENT_OVERDUE' + | 'VERIFICATION_PENDING' + | 'RESPONSE_RECEIVED'; + +export interface FieldBriefingFinding { + id: string; + code: string; + title: string; + status: string; + severity: number | null; + nextControlOn: string | null; + asset: { id: string; code: string; name: string; typeName: string }; +} + +export interface FieldBriefingAct { + actId: string; + actCode: string; + occurredAt: string; + adminState: FieldBriefingActState; + responseDueOn: string | null; + responseReceivedOn: string | null; + committedCorrectionOn: string | null; + latestResponseId: string | null; + findings: FieldBriefingFinding[]; +} + +export interface FieldBriefing { + inspection: { + id: string; + code: string; + status: string; + plannedStartAt: string | null; + area: { id: string; code: string; name: string } | null; + operatorCompany: { id: string; code: string; name: string } | null; + }; + plannedOn: string; + summary: { + acts: number; + findings: number; + inventoryItems: number; + responseOverdue: number; + commitmentOverdue: number; + verificationPending: number; + }; + acts: FieldBriefingAct[]; +} + +export function getInspectionFieldBriefing(visitId: string) { + return apiRequest(`/inspection-visits/${visitId}/field-briefing`); +}