feat: actualizar CDC Brief con fecha limite de entrega

This commit is contained in:
Isaac_Aracena
2026-05-25 21:20:48 -04:00
parent d7094c94cf
commit 82d59a3313
8 changed files with 1725 additions and 1624 deletions
+12 -1
View File
@@ -75,7 +75,9 @@ export default function App() {
const handleSendMessage = async (
text: string,
attachments: Attachment[],
audio?: AudioRecording
audio?: AudioRecording,
deliveryDate?: string,
deliveryDateStatus: 'defined' | 'pending' = 'defined'
) => {
const userMessageId = Math.random().toString(36).substring(7);
@@ -109,6 +111,15 @@ export default function App() {
formData.append('submittedByEmail', submittedByEmail);
formData.append('submittedByUid', user?.uid || '');
formData.append('submittedByPhotoUrl', user?.photoURL || '');
const deliveryDateValue = deliveryDateStatus === 'pending' ? '' : deliveryDate || '';
const deliveryDateLabel = deliveryDateStatus === 'pending' ? 'Por confirmar' : deliveryDateValue;
formData.append('deliveryDate', deliveryDateValue);
formData.append('fechaEntrega', deliveryDateValue);
formData.append('deliveryDateStatus', deliveryDateStatus);
formData.append('fechaEntregaEstado', deliveryDateStatus === 'pending' ? 'por_confirmar' : 'definida');
formData.append('deliveryDateLabel', deliveryDateLabel);
formData.append('fechaEntregaTexto', deliveryDateLabel);
let generalFileIndex = 0;
let campaignFileIndex = 0;
+90 -7
View File
@@ -21,7 +21,7 @@ import { Attachment, AttachmentCategory, AttachmentType, AudioRecording } from '
import { cn } from '../lib/utils';
interface InputAreaProps {
onSendMessage: (text: string, attachments: Attachment[], audio?: AudioRecording) => void;
onSendMessage: (text: string, attachments: Attachment[], audio?: AudioRecording, deliveryDate?: string, deliveryDateStatus?: 'defined' | 'pending') => void;
isProcessing: boolean;
}
@@ -88,6 +88,20 @@ const formatDuration = (seconds: number) => {
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
};
const formatDeliveryDate = (dateString: string) => {
if (!dateString) return 'Pendiente';
const [year, month, day] = dateString.split('-').map(Number);
if (!year || !month || !day) return dateString;
const date = new Date(year, month - 1, day);
return date.toLocaleDateString('es-DO', {
day: 'numeric',
month: 'long',
year: 'numeric',
});
};
export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessing }) => {
const [text, setText] = useState('');
const [attachments, setAttachments] = useState<Attachment[]>([]);
@@ -97,6 +111,8 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
const [currentStep, setCurrentStep] = useState<GuidedStep>('audio');
const [hasCampaignAssets, setHasCampaignAssets] = useState<CampaignAnswer>(null);
const [validationMessage, setValidationMessage] = useState<string | null>(null);
const [deliveryDate, setDeliveryDate] = useState('');
const [deliveryDatePending, setDeliveryDatePending] = useState(false);
const [audioPreviewPlaying, setAudioPreviewPlaying] = useState(false);
const [audioPreviewCurrentTime, setAudioPreviewCurrentTime] = useState(0);
@@ -132,7 +148,9 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
const requiredCompleted = Boolean(audioRecording) && imageAttachments.length > 0;
const campaignStepCompleted = hasCampaignAssets !== 'yes' || campaignAttachments.length > 0;
const canSend = requiredCompleted && campaignStepCompleted;
const hasDeliveryInfo = Boolean(deliveryDate) || deliveryDatePending;
const deliveryDateLabel = deliveryDatePending ? 'Por confirmar' : formatDeliveryDate(deliveryDate);
const canSend = requiredCompleted && campaignStepCompleted && hasDeliveryInfo;
const progressValue = ((STEP_DEFINITIONS.findIndex((step) => step.id === currentStep) + 1) / STEP_DEFINITIONS.length) * 100;
useEffect(() => {
@@ -396,7 +414,8 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
`Nota de voz: ${audioRecording ? 'sí' : 'no'}`,
`Imágenes de referencia: ${imageAttachments.length}`,
`Documentos relacionados: ${documentAttachments.length}`,
`Campaña o elementos gráficos existentes: ${hasCampaignAssets === 'yes' ? 'sí' : hasCampaignAssets === 'no' ? 'no' : 'no indicado'}`,
`Fecha límite de entrega: ${deliveryDateLabel}`,
`Campaña o elementos gráficos existentes: ${hasCampaignAssets === 'yes' ? 'sí' : hasCampaignAssets === 'no' ? 'no' : 'No indicado'}`,
`Archivos de campaña / elementos gráficos: ${campaignAttachments.length}`,
];
@@ -424,8 +443,19 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
return;
}
if (!hasDeliveryInfo) {
setValidationMessage('Selecciona la fecha límite de entrega o marca la opción Por confirmar antes de enviar.');
return;
}
if (!isProcessing) {
onSendMessage(buildSubmissionText(), attachments, audioRecording || undefined);
onSendMessage(
buildSubmissionText(),
attachments,
audioRecording || undefined,
deliveryDatePending ? '' : deliveryDate,
deliveryDatePending ? 'pending' : 'defined'
);
setText('');
setAttachments([]);
if (audioPreviewRef.current) {
@@ -438,6 +468,8 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
recordingDurationRef.current = 0;
setRecordingDuration(0);
setHasCampaignAssets(null);
setDeliveryDate('');
setDeliveryDatePending(false);
setCurrentStep('audio');
setValidationMessage(null);
}
@@ -789,19 +821,64 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
<p className="mb-5 text-sm leading-7 text-zinc-300">
Revisa el resumen antes de enviar. Este es el único paso donde se puede finalizar la solicitud.
</p>
<div className="mb-4 rounded-2xl border border-indigo-400/20 bg-indigo-500/10 p-4">
<label htmlFor="delivery-date" className="flex flex-col gap-2">
<span className="text-xs font-semibold uppercase tracking-[0.22em] text-indigo-200">
Fecha límite de entrega
</span>
<span className="text-sm leading-6 text-zinc-300">
Indica para cuándo debe entregarse esta solicitud. Este dato se enviará al flujo, al correo y al brief generado.
</span>
<input
id="delivery-date"
type="date"
value={deliveryDate}
onChange={(event) => {
setDeliveryDate(event.target.value);
if (event.target.value) {
setDeliveryDatePending(false);
setValidationMessage(null);
}
}}
disabled={isProcessing || deliveryDatePending}
className="mt-2 w-full rounded-2xl border border-white/10 bg-black/25 px-4 py-3 text-sm font-semibold text-white outline-none transition focus:border-indigo-300/60 disabled:opacity-60 sm:max-w-xs"
/>
<label className="mt-3 flex w-fit cursor-pointer items-center gap-3 rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-zinc-200 transition hover:border-white/20 hover:bg-black/30">
<input
type="checkbox"
checked={deliveryDatePending}
onChange={(event) => {
const checked = event.target.checked;
setDeliveryDatePending(checked);
if (checked) {
setDeliveryDate('');
setValidationMessage(null);
}
}}
disabled={isProcessing}
className="h-4 w-4 rounded border-white/20 bg-black/40 text-indigo-500 focus:ring-indigo-400"
/>
<span>Por confirmar</span>
</label>
</label>
</div>
<div className="grid gap-3 sm:grid-cols-2">
<div className="rounded-2xl border border-white/10 bg-black/20 p-4">
<p className="text-xs uppercase tracking-[0.22em] text-zinc-500">Obligatorios</p>
<div className="mt-3 space-y-2 text-sm text-zinc-300">
<p>Nota de voz: <span className={audioRecording ? 'text-emerald-300' : 'text-amber-300'}>{audioRecording ? 'lista' : 'pendiente'}</span></p>
<p>Nota de voz: <span className={audioRecording ? 'text-emerald-300' : 'text-amber-300'}>{audioRecording ? 'Lista' : 'Pendiente'}</span></p>
<p>Imágenes: <span className={imageAttachments.length ? 'text-emerald-300' : 'text-amber-300'}>{imageAttachments.length}</span></p>
<p>Fecha de entrega: <span className={hasDeliveryInfo ? 'text-emerald-300' : 'text-amber-300'}>{hasDeliveryInfo ? deliveryDateLabel : 'Pendiente'}</span></p>
</div>
</div>
<div className="rounded-2xl border border-white/10 bg-black/20 p-4">
<p className="text-xs uppercase tracking-[0.22em] text-zinc-500">Soporte</p>
<div className="mt-3 space-y-2 text-sm text-zinc-300">
<p>Documentos: <span className="text-white">{documentAttachments.length}</span></p>
<p>Campaña / gráficos: <span className="text-white">{hasCampaignAssets === 'yes' ? `${campaignAttachments.length} archivo(s)` : hasCampaignAssets === 'no' ? 'no aplica' : 'no indicado'}</span></p>
<p>Campaña / gráficos: <span className="text-white">{hasCampaignAssets === 'yes' ? `${campaignAttachments.length} archivo(s)` : hasCampaignAssets === 'no' ? 'no aplica' : 'No indicado'}</span></p>
<p>Enlaces: <span className="text-white">{linksCount}</span></p>
</div>
</div>
@@ -829,7 +906,7 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
<div className="text-sm text-zinc-400">
{canSend
? 'Todo está listo. El envío solo se realiza desde el paso final.'
: 'Completa primero la nota de voz y las imágenes. Si marcaste campaña, adjunta sus archivos.'}
: 'Completa la nota de voz, las imágenes y la fecha límite de entrega o marca Por confirmar. Si marcaste campaña, adjunta sus archivos.'}
</div>
<div className="flex flex-wrap items-center gap-2">
@@ -875,6 +952,12 @@ export const InputArea: React.FC<InputAreaProps> = ({ onSendMessage, isProcessin
{imageAttachments.length > 0 ? `${imageAttachments.length} cargada(s)` : 'Pendiente'}
</span>
</div>
<div className="flex items-center justify-between gap-3 text-sm">
<span className="text-zinc-300">Fecha de entrega</span>
<span className={cn('text-right font-semibold', hasDeliveryInfo ? 'text-emerald-300' : 'text-zinc-500')}>
{hasDeliveryInfo ? deliveryDateLabel : 'Pendiente'}
</span>
</div>
</div>
</div>